cura 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +122 -0
  4. data/LICENSE +20 -0
  5. data/README.md +159 -0
  6. data/Rakefile +42 -0
  7. data/cura.gemspec +23 -0
  8. data/examples/box_model/bin/box_model +11 -0
  9. data/examples/box_model/debug.log +0 -0
  10. data/examples/box_model/lib/box_model.rb +21 -0
  11. data/examples/box_model/lib/box_model/application.rb +24 -0
  12. data/examples/hello_world/bin/hello_world +10 -0
  13. data/examples/hello_world/lib/hello_world.rb +201 -0
  14. data/examples/mruby-examples/README.md +10 -0
  15. data/examples/mruby-examples/include/cura_examples.h +6 -0
  16. data/examples/mruby-examples/mrbgem.rake +14 -0
  17. data/examples/mruby-examples/src/gem_init.c +34 -0
  18. data/examples/mruby-examples/tools/hello_world/hello_world.c +6 -0
  19. data/examples/todo_list/app.log +9 -0
  20. data/examples/todo_list/bin/todo_list +26 -0
  21. data/examples/todo_list/data.db +0 -0
  22. data/examples/todo_list/debug.log +0 -0
  23. data/examples/todo_list/lib/todo_list.rb +28 -0
  24. data/examples/todo_list/lib/todo_list/application.rb +54 -0
  25. data/examples/todo_list/lib/todo_list/component/header.rb +27 -0
  26. data/examples/todo_list/lib/todo_list/component/list.rb +50 -0
  27. data/examples/todo_list/lib/todo_list/component/list_item.rb +28 -0
  28. data/examples/todo_list/lib/todo_list/component/list_items.rb +97 -0
  29. data/examples/todo_list/lib/todo_list/component/lists.rb +89 -0
  30. data/examples/todo_list/lib/todo_list/database.rb +50 -0
  31. data/examples/todo_list/lib/todo_list/model/list.rb +9 -0
  32. data/examples/todo_list/lib/todo_list/model/list_item.rb +9 -0
  33. data/examples/todo_list/profile.html +11354 -0
  34. data/lib/cura.rb +13 -0
  35. data/lib/cura/adapter.rb +67 -0
  36. data/lib/cura/application.rb +245 -0
  37. data/lib/cura/attributes/has_ancestry.rb +40 -0
  38. data/lib/cura/attributes/has_application.rb +31 -0
  39. data/lib/cura/attributes/has_attributes.rb +89 -0
  40. data/lib/cura/attributes/has_children.rb +113 -0
  41. data/lib/cura/attributes/has_colors.rb +66 -0
  42. data/lib/cura/attributes/has_coordinates.rb +49 -0
  43. data/lib/cura/attributes/has_dimensions.rb +63 -0
  44. data/lib/cura/attributes/has_events.rb +89 -0
  45. data/lib/cura/attributes/has_focusability.rb +37 -0
  46. data/lib/cura/attributes/has_initialize.rb +15 -0
  47. data/lib/cura/attributes/has_offsets.rb +82 -0
  48. data/lib/cura/attributes/has_orientation.rb +53 -0
  49. data/lib/cura/attributes/has_relative_coordinates.rb +39 -0
  50. data/lib/cura/attributes/has_root.rb +104 -0
  51. data/lib/cura/attributes/has_side_attributes.rb +95 -0
  52. data/lib/cura/attributes/has_windows.rb +70 -0
  53. data/lib/cura/borders.rb +16 -0
  54. data/lib/cura/color.rb +330 -0
  55. data/lib/cura/component/base.rb +180 -0
  56. data/lib/cura/component/button.rb +57 -0
  57. data/lib/cura/component/group.rb +77 -0
  58. data/lib/cura/component/label.rb +224 -0
  59. data/lib/cura/component/listbox.rb +152 -0
  60. data/lib/cura/component/pack.rb +144 -0
  61. data/lib/cura/component/scrollbar.rb +184 -0
  62. data/lib/cura/component/textbox.rb +118 -0
  63. data/lib/cura/cursor.rb +77 -0
  64. data/lib/cura/error/base.rb +10 -0
  65. data/lib/cura/error/invalid_adapter.rb +18 -0
  66. data/lib/cura/error/invalid_application.rb +18 -0
  67. data/lib/cura/error/invalid_color.rb +18 -0
  68. data/lib/cura/error/invalid_component.rb +18 -0
  69. data/lib/cura/error/invalid_middleware.rb +18 -0
  70. data/lib/cura/event.rb +38 -0
  71. data/lib/cura/event/base.rb +108 -0
  72. data/lib/cura/event/click.rb +14 -0
  73. data/lib/cura/event/dispatcher.rb +122 -0
  74. data/lib/cura/event/focus.rb +14 -0
  75. data/lib/cura/event/handler.rb +74 -0
  76. data/lib/cura/event/key_down.rb +68 -0
  77. data/lib/cura/event/middleware/aimer/base.rb +38 -0
  78. data/lib/cura/event/middleware/aimer/dispatcher_target.rb +24 -0
  79. data/lib/cura/event/middleware/aimer/mouse_focus.rb +48 -0
  80. data/lib/cura/event/middleware/aimer/target_option.rb +38 -0
  81. data/lib/cura/event/middleware/base.rb +21 -0
  82. data/lib/cura/event/middleware/dispatch.rb +20 -0
  83. data/lib/cura/event/middleware/translator/base.rb +37 -0
  84. data/lib/cura/event/middleware/translator/mouse_click.rb +44 -0
  85. data/lib/cura/event/mouse.rb +34 -0
  86. data/lib/cura/event/mouse_button.rb +103 -0
  87. data/lib/cura/event/mouse_wheel_down.rb +14 -0
  88. data/lib/cura/event/mouse_wheel_up.rb +14 -0
  89. data/lib/cura/event/resize.rb +17 -0
  90. data/lib/cura/event/selected.rb +14 -0
  91. data/lib/cura/event/unfocus.rb +14 -0
  92. data/lib/cura/focus_controller.rb +89 -0
  93. data/lib/cura/key.rb +313 -0
  94. data/lib/cura/margins.rb +16 -0
  95. data/lib/cura/offsets.rb +91 -0
  96. data/lib/cura/padding.rb +16 -0
  97. data/lib/cura/pencil.rb +29 -0
  98. data/lib/cura/version.rb +6 -0
  99. data/lib/cura/window.rb +85 -0
  100. data/spec/cura/attributes/has_ancestry_spec.rb +108 -0
  101. data/spec/cura/attributes/has_application_spec.rb +59 -0
  102. data/spec/cura/attributes/has_attributes_spec.rb +75 -0
  103. data/spec/cura/attributes/has_children_spec.rb +169 -0
  104. data/spec/cura/attributes/has_colors_spec.rb +20 -0
  105. data/spec/cura/attributes/has_coordinates_spec.rb +19 -0
  106. data/spec/cura/attributes/has_dimensions_spec.rb +19 -0
  107. data/spec/cura/attributes/has_events_spec.rb +18 -0
  108. data/spec/cura/attributes/has_focusability_spec.rb +58 -0
  109. data/spec/cura/attributes/has_offsets_spec.rb +18 -0
  110. data/spec/cura/attributes/has_orientation_spec.rb +102 -0
  111. data/spec/cura/attributes/has_relative_coordinates_spec.rb +18 -0
  112. data/spec/cura/attributes/has_side_attributes_spec.rb +19 -0
  113. data/spec/spec_helper.rb +12 -0
  114. data/spec/support/shared_examples_for_attributes.rb +122 -0
  115. metadata +211 -0
@@ -0,0 +1,180 @@
1
+ if Kernel.respond_to?(:require)
2
+ require "cura/attributes/has_initialize"
3
+ require "cura/attributes/has_focusability"
4
+ require "cura/attributes/has_colors"
5
+ require "cura/attributes/has_dimensions"
6
+ require "cura/attributes/has_events"
7
+ require "cura/attributes/has_offsets"
8
+ require "cura/attributes/has_relative_coordinates"
9
+ end
10
+
11
+ module Cura
12
+ module Component
13
+
14
+ # The base class for all components.
15
+ #
16
+ # All components use a box model similar to CSS.
17
+ # Margins, borders, paddings, then content.
18
+ class Base
19
+
20
+ include Attributes::HasInitialize
21
+ include Attributes::HasAttributes
22
+ include Attributes::HasDimensions
23
+ include Attributes::HasEvents
24
+ include Attributes::HasFocusability
25
+ include Attributes::HasColors
26
+ include Attributes::HasOffsets
27
+ include Attributes::HasRelativeCoordinates
28
+
29
+ # Get the cursor for this application.
30
+ # TODO: Delegate something like: def_delegate(:cursor) { application }
31
+ #
32
+ # @return [Cursor]
33
+ def cursor
34
+ application.cursor
35
+ end
36
+
37
+ # Get the pencil for this application.
38
+ # TODO: Delegate
39
+ #
40
+ # @return [Pencil]
41
+ def pencil
42
+ application.pencil
43
+ end
44
+
45
+ # Get the application of this object.
46
+ #
47
+ # @return [Application]
48
+ def application
49
+ return nil if parent.nil?
50
+
51
+ parent.application
52
+ end
53
+
54
+ # Focus on this component.
55
+ #
56
+ # @return [Component]
57
+ def focus
58
+ application.dispatcher.target = self
59
+ end
60
+
61
+ # Check whether this component is focused.
62
+ #
63
+ # @return [Boolean]
64
+ def focused?
65
+ application.dispatcher.target == self
66
+ end
67
+
68
+ # Determine if the given absolute coordinates are within the bounds of this component.
69
+ #
70
+ # @param [#to_h] options
71
+ # @option options [#to_i] :x
72
+ # @option options [#to_i] :y
73
+ # @return [Boolean]
74
+ def contains_coordinates?(options={})
75
+ options = options.to_h
76
+
77
+ (absolute_x..absolute_x + width).include?(options[:x].to_i) && (absolute_y..absolute_y + width).include?(options[:y].to_i)
78
+ end
79
+
80
+ # Get the foreground color of this object.
81
+ #
82
+ # @return [Color]
83
+ def foreground
84
+ get_or_inherit_color(:foreground, Color.black)
85
+ end
86
+
87
+ # Get the background color of this object.
88
+ #
89
+ # @return [Color]
90
+ def background
91
+ get_or_inherit_color(:background, Color.white)
92
+ end
93
+
94
+ # Instance inspection.
95
+ #
96
+ # @return [String]
97
+ def inspect
98
+ "#<#{self.class}:0x#{__id__.to_s(16)} x=#{x} y=#{y} absolute_x=#{absolute_x} absolute_y=#{absolute_y} w=#{width} h=#{height} parent=#{@parent.class}:0x#{@parent.__id__.to_s(16)}>"
99
+ end
100
+
101
+ # Update this component.
102
+ #
103
+ # @return [Component]
104
+ def update
105
+ self
106
+ end
107
+
108
+ # Draw this component.
109
+ #
110
+ # @return [Component]
111
+ def draw
112
+ draw_background
113
+ draw_border
114
+
115
+ self
116
+ end
117
+
118
+ protected
119
+
120
+ # Draw a point.
121
+ def draw_point(x, y, color=Cura::Color.black)
122
+ x = absolute_x + @offsets.left + x
123
+ y = absolute_y + @offsets.top + y
124
+
125
+ pencil.draw_point(x, y, color)
126
+ end
127
+
128
+ # Draw a rectangle.
129
+ # TODO: filled argument
130
+ def draw_rectangle(x, y, width, height, color=Cura::Color.black)
131
+ x = absolute_x + @offsets.left + x
132
+ y = absolute_y + @offsets.top + y
133
+
134
+ pencil.draw_rectangle(x, y, width, height, color)
135
+ end
136
+
137
+ # Draw a single character.
138
+ def draw_character(x, y, character, foreground=Cura::Color.black, background=Cura::Color.white, bold=false, underline=false)
139
+ x = absolute_x + @offsets.left + x
140
+ y = absolute_y + @offsets.top + y
141
+
142
+ pencil.draw_character(x, y, character, foreground, background, bold, underline)
143
+ end
144
+
145
+ # Draw text.
146
+ def draw_text(x, y, text, foreground=Cura::Color.black, background=Cura::Color.white, bold=false, underline=false)
147
+ x = absolute_x + @offsets.left + x
148
+ y = absolute_y + @offsets.top + y
149
+
150
+ pencil.draw_text(x, y, text, foreground, background, bold, underline)
151
+ end
152
+
153
+ # Draw the background of this component.
154
+ def draw_background
155
+ x = absolute_x + @margin.left + @border.left
156
+ y = absolute_y + @margin.top + @border.top
157
+ width = self.width + @padding.width
158
+ height = self.height + @padding.height
159
+ color = background
160
+
161
+ pencil.draw_rectangle(x, y, width, height, color)
162
+ end
163
+
164
+ # Draw the border of this component.
165
+ def draw_border # TODO
166
+ end
167
+
168
+ def get_or_inherit_color(name, default)
169
+ value = instance_variable_get("@#{name}")
170
+
171
+ return value unless value == :inherit
172
+ return default unless respond_to?(:parent) && parent.respond_to?(name)
173
+
174
+ parent.send(name)
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,57 @@
1
+ if Kernel.respond_to?(:require)
2
+ require "cura/component/label"
3
+ end
4
+
5
+ module Cura
6
+ module Component
7
+
8
+ # A button component.
9
+ class Button < Label
10
+
11
+ on_event(:key_down) do |event|
12
+ click if event.target == self && event.name == :enter
13
+ end
14
+
15
+ on_event(:mouse_button) do |event|
16
+ click if event.target == self && event.up? && contains_coordinates?(x: event.x, y: event.y)
17
+ end
18
+
19
+ # @method focused_background
20
+ # Get the focused background color of this object.
21
+ #
22
+ # @return [Color]
23
+
24
+ # @method focused_background=(value)
25
+ # Set the focused background color of this object.
26
+ #
27
+ # @param [Color] value
28
+ # @return [Color]
29
+
30
+ attribute(:focused_background) { |value| validate_color_attribute(value) }
31
+
32
+ def initialize(attributes={})
33
+ @focusable = true
34
+ @foreground = Cura::Color.black
35
+ @background = Cura::Color.white
36
+ @focused_background = Color.new(78, 78, 78)
37
+
38
+ super
39
+ end
40
+
41
+ def background
42
+ focused? ? @focused_background : get_or_inherit_color(:background, Color.black)
43
+ end
44
+
45
+ # Click this button.
46
+ #
47
+ # @return [Button]
48
+ def click
49
+ application.dispatch_event(:click, target: self)
50
+
51
+ self
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,77 @@
1
+ if Kernel.respond_to?(:require)
2
+ require "cura/attributes/has_children"
3
+ require "cura/component/base"
4
+ end
5
+
6
+ module Cura
7
+ module Component
8
+
9
+ # A component with children.
10
+ # When children are added, their parent will be set to this group.
11
+ class Group < Base
12
+
13
+ include Attributes::HasChildren
14
+
15
+ # Get the width of this group.
16
+ #
17
+ # @return [Integer]
18
+ def width
19
+ return @width unless @width == :auto
20
+ return 0 if children.empty?
21
+
22
+ children.collect { |child| child.x + child.width + child.offsets.width }.max
23
+ end
24
+
25
+ # Get the height of this group.
26
+ #
27
+ # @return [Integer]
28
+ def height
29
+ return @height unless @height == :auto
30
+ return 0 if children.empty?
31
+
32
+ children.collect { |child| child.y + child.height + child.offsets.height }.max
33
+ end
34
+
35
+ # Add a child to this group and set it's parent to this Group.
36
+ #
37
+ # @param [Component] component
38
+ # @return [Component]
39
+ def add_child(component)
40
+ component = super
41
+
42
+ component.parent = self
43
+
44
+ component
45
+ end
46
+
47
+ # Remove a child from this object's children at the given index and set it's parent to nil.
48
+ #
49
+ # @param [Integer] index
50
+ # @return [Component]
51
+ def delete_child_at(index)
52
+ component = super
53
+
54
+ component.parent = nil
55
+
56
+ component
57
+ end
58
+
59
+ # Update all children.
60
+ def update
61
+ super
62
+
63
+ update_children
64
+ end
65
+
66
+ # Draw all children relative to this location.
67
+ # TODO: If the dimensions of this group of this group are less than the computed dimensions, the drawing will be clipped.
68
+ def draw
69
+ super
70
+
71
+ draw_children
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,224 @@
1
+ if Kernel.respond_to?(:require)
2
+ require "cura/component/base"
3
+ require "cura/attributes/has_attributes"
4
+ end
5
+
6
+ module Cura
7
+ module Component
8
+
9
+ # A component displaying text.
10
+ class Label < Base
11
+
12
+ include Attributes::HasAttributes
13
+
14
+ # Note that you can pass the following:
15
+ # alignment: { horizontal: true, vertical: true }
16
+ # instead of:
17
+ # horizontal_alignment: true, vertical_alignment: true
18
+ def initialize(attributes={})
19
+ @horizontal_alignment = :left
20
+ @vertical_alignment = :top
21
+ @bold = false
22
+ @underline = false
23
+ @text = ""
24
+
25
+ super
26
+ end
27
+
28
+ # TODO: #text_foreground, #text_background (? Maybe a separate Text component, like in )
29
+
30
+ # Get the width of this label.
31
+ #
32
+ # @return [Integer]
33
+ def width
34
+ return text_width if @width == :auto
35
+
36
+ @width
37
+ end
38
+
39
+ # Get the height of this label.
40
+ #
41
+ # @return [Integer]
42
+ def height
43
+ return text_height if @height == :auto
44
+
45
+ @height
46
+ end
47
+
48
+ # @method text
49
+ # Get the text of this label.
50
+ #
51
+ # @return [String]
52
+
53
+ # @method text=(value)
54
+ # Set the text of this label.
55
+ #
56
+ # @param [#to_s] value
57
+ # @return [String]
58
+ attribute(:text) { |value| value.to_s }
59
+
60
+ # Get the lines of this label.
61
+ #
62
+ # @return [<String>]
63
+ def lines
64
+ @text.split("\n") # NOTE: Would use String#lines but it's output doesn't think a trailing newline character constitutes a line unless it is followed by another character. #split also removes the newline characters.
65
+ end
66
+
67
+ # Get the width of the text of this label.
68
+ #
69
+ # @return [Integer]
70
+ def text_width
71
+ return 0 if @text.empty?
72
+
73
+ lines.collect(&:length).sort.last
74
+ end
75
+
76
+ # Get the height of the text of this label.
77
+ #
78
+ # @return [Integer]
79
+ def text_height
80
+ value = lines.length
81
+
82
+ value == 0 ? 1 : value
83
+ end
84
+
85
+ # @method bold?
86
+ # Get whether the text is bold.
87
+ #
88
+ # @return [Boolean]
89
+
90
+ # @method bold=(value)
91
+ # Set whether the text is bold.
92
+ #
93
+ # @return [Boolean]
94
+ attribute(:bold, query: true)
95
+
96
+ # @method underline?
97
+ # Get whether the text is underlined.
98
+ #
99
+ # @return [Boolean]
100
+
101
+ # @method underlined=(value)
102
+ # Set whether the text is underlined.
103
+ #
104
+ # @return [Boolean]
105
+ attribute(:underline, query: true)
106
+
107
+ # @method horizontal_alignment
108
+ # Get the horizontal alignment of this label.
109
+ #
110
+ # @return [Symbol]
111
+
112
+ # @method horizontal_alignment=(value)
113
+ # Set the horizontal alignment of this label.
114
+ # Must be :left, :center, or :right.
115
+ #
116
+ # @param [#to_sym] value
117
+ # @return [Symbol]
118
+ attribute(:horizontal_alignment) { |value| convert_horizontal_alignment_attribute(value) }
119
+
120
+ # @method vertical_alignment
121
+ # Get the vertical alignment of this label.
122
+ # Will be :left, :center, or :right.
123
+ #
124
+ # @return [Symbol]
125
+
126
+ # @method vertical_alignment=(value)
127
+ # Set the vertical alignment of this label.
128
+ # Must be :left, :center, or :right.
129
+ #
130
+ # @param [#to_sym] value
131
+ # @return [Symbol]
132
+ attribute(:vertical_alignment) { |value| convert_vertical_alignment_attribute(value) }
133
+
134
+ def draw
135
+ super
136
+
137
+ draw_text unless text.empty?
138
+ end
139
+
140
+ protected
141
+
142
+ # Helper method for subclasses
143
+ def text_to_draw
144
+ @text
145
+ end
146
+
147
+ # Helper method for subclasses
148
+ def character_to_draw(character)
149
+ character
150
+ end
151
+
152
+ # TODO: Should use instance vars
153
+ def draw_text
154
+ x_offset = x_offset_start = x_offset_from_alignment# + @offsets.left
155
+ y_offset = y_offset_from_alignment# + @offsets.top
156
+ absolute_x = self.absolute_x
157
+ absolute_y = self.absolute_y
158
+
159
+ text_to_draw.each_char do |character|
160
+ if character == "\n" # TODO: If multiline? Also check if outside the bounds of the drawing area
161
+ x_offset = x_offset_start
162
+
163
+ y_offset += 1
164
+ else
165
+ unless x_offset > width || y_offset > height
166
+ character = character_to_draw(character)
167
+ draw_character(x_offset, y_offset, character, foreground, background, @bold, @underline)
168
+ end
169
+
170
+ x_offset += 1
171
+ end
172
+ end
173
+ end
174
+
175
+ def x_offset_from_alignment
176
+ case horizontal_alignment
177
+ when :left then 0
178
+ when :center then ((text_width - width).abs / 2).to_i
179
+ when :right then (text_width - width).abs
180
+ end
181
+ end
182
+
183
+ def y_offset_from_alignment
184
+ case vertical_alignment
185
+ when :top then 0
186
+ when :center then ((text_height - height).abs / 2).to_i
187
+ when :bottom then (text_height - height).abs
188
+ end
189
+ end
190
+
191
+ protected
192
+
193
+ # TODO: Just use a #alignment attribute and have a Cura::Alignment object?
194
+ def convert_attributes(attributes={})
195
+ attributes = super
196
+
197
+ if attributes.key?(:alignment)
198
+ alignment_attributes = attributes.delete(:alignment).to_h
199
+
200
+ attributes[:horizontal_alignment] = alignment_attributes[:horizontal] if alignment_attributes.key?(:horizontal)
201
+ attributes[:vertical_alignment] = alignment_attributes[:vertical] if alignment_attributes.key?(:vertical)
202
+ end
203
+
204
+ attributes
205
+ end
206
+
207
+ def convert_horizontal_alignment_attribute(value)
208
+ value = value.to_sym
209
+ raise ArgumentError, "must be :left, :center, or :right" unless [:left, :center, :right].include?(value)
210
+
211
+ value
212
+ end
213
+
214
+ def convert_vertical_alignment_attribute(value)
215
+ value = value.to_sym
216
+ raise ArgumentError, "must be :top, :center, or :bottom" unless [:top, :center, :bottom].include?(value)
217
+
218
+ value
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+ end