fidgit 0.0.2alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +2 -0
  3. data/COPYING.txt +674 -0
  4. data/Gemfile +4 -0
  5. data/README.textile +138 -0
  6. data/Rakefile +38 -0
  7. data/config/default_schema.yml +180 -0
  8. data/examples/_all_examples.rb +9 -0
  9. data/examples/align_example.rb +56 -0
  10. data/examples/button_and_toggle_button_example.rb +27 -0
  11. data/examples/color_picker_example.rb +17 -0
  12. data/examples/color_well_example.rb +25 -0
  13. data/examples/combo_box_example.rb +24 -0
  14. data/examples/file_dialog_example.rb +42 -0
  15. data/examples/grid_packer_example.rb +29 -0
  16. data/examples/helpers/example_window.rb +17 -0
  17. data/examples/label_example.rb +17 -0
  18. data/examples/list_example.rb +23 -0
  19. data/examples/media/images/head_icon.png +0 -0
  20. data/examples/menu_pane_example.rb +27 -0
  21. data/examples/message_dialog_example.rb +65 -0
  22. data/examples/radio_button_example.rb +37 -0
  23. data/examples/readme_example.rb +32 -0
  24. data/examples/scroll_window_example.rb +49 -0
  25. data/examples/slider_example.rb +30 -0
  26. data/examples/splash_example.rb +42 -0
  27. data/examples/text_area_example.rb +28 -0
  28. data/fidgit.gemspec +28 -0
  29. data/lib/fidgit.rb +4 -0
  30. data/lib/fidgit/chingu_ext/window.rb +6 -0
  31. data/lib/fidgit/clipboard.rb +23 -0
  32. data/lib/fidgit/cursor.rb +38 -0
  33. data/lib/fidgit/elements/button.rb +68 -0
  34. data/lib/fidgit/elements/color_picker.rb +63 -0
  35. data/lib/fidgit/elements/color_well.rb +39 -0
  36. data/lib/fidgit/elements/combo_box.rb +85 -0
  37. data/lib/fidgit/elements/composite.rb +17 -0
  38. data/lib/fidgit/elements/container.rb +187 -0
  39. data/lib/fidgit/elements/element.rb +252 -0
  40. data/lib/fidgit/elements/file_browser.rb +152 -0
  41. data/lib/fidgit/elements/grid_packer.rb +219 -0
  42. data/lib/fidgit/elements/group.rb +66 -0
  43. data/lib/fidgit/elements/horizontal_packer.rb +12 -0
  44. data/lib/fidgit/elements/label.rb +77 -0
  45. data/lib/fidgit/elements/list.rb +47 -0
  46. data/lib/fidgit/elements/menu_pane.rb +149 -0
  47. data/lib/fidgit/elements/packer.rb +42 -0
  48. data/lib/fidgit/elements/radio_button.rb +86 -0
  49. data/lib/fidgit/elements/scroll_area.rb +75 -0
  50. data/lib/fidgit/elements/scroll_bar.rb +114 -0
  51. data/lib/fidgit/elements/scroll_window.rb +92 -0
  52. data/lib/fidgit/elements/slider.rb +119 -0
  53. data/lib/fidgit/elements/text_area.rb +351 -0
  54. data/lib/fidgit/elements/toggle_button.rb +67 -0
  55. data/lib/fidgit/elements/tool_tip.rb +35 -0
  56. data/lib/fidgit/elements/vertical_packer.rb +12 -0
  57. data/lib/fidgit/event.rb +99 -0
  58. data/lib/fidgit/gosu_ext/color.rb +123 -0
  59. data/lib/fidgit/history.rb +85 -0
  60. data/lib/fidgit/redirector.rb +83 -0
  61. data/lib/fidgit/schema.rb +123 -0
  62. data/lib/fidgit/selection.rb +106 -0
  63. data/lib/fidgit/standard_ext/hash.rb +21 -0
  64. data/lib/fidgit/states/dialog_state.rb +42 -0
  65. data/lib/fidgit/states/file_dialog.rb +24 -0
  66. data/lib/fidgit/states/gui_state.rb +301 -0
  67. data/lib/fidgit/states/message_dialog.rb +61 -0
  68. data/lib/fidgit/thumbnail.rb +29 -0
  69. data/lib/fidgit/version.rb +5 -0
  70. data/lib/fidgit/window.rb +19 -0
  71. data/media/images/arrow.png +0 -0
  72. data/media/images/file_directory.png +0 -0
  73. data/media/images/file_file.png +0 -0
  74. data/media/images/pixel.png +0 -0
  75. data/spec/fidgit/elements/helpers/helper.rb +3 -0
  76. data/spec/fidgit/elements/label_spec.rb +49 -0
  77. data/spec/fidgit/event_spec.rb +149 -0
  78. data/spec/fidgit/gosu_ext/color_spec.rb +130 -0
  79. data/spec/fidgit/gosu_ext/helpers/helper.rb +3 -0
  80. data/spec/fidgit/helpers/helper.rb +4 -0
  81. data/spec/fidgit/helpers/tex_play_helper.rb +9 -0
  82. data/spec/fidgit/history_spec.rb +144 -0
  83. data/spec/fidgit/redirector_spec.rb +78 -0
  84. data/spec/fidgit/schema_spec.rb +67 -0
  85. data/spec/fidgit/schema_test.yml +32 -0
  86. data/spec/fidgit/thumbnail_spec.rb +50 -0
  87. metadata +177 -0
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ module Fidgit
4
+ class Selection
5
+ MIN_DRAG_DISTANCE = 2
6
+
7
+ def size; @items.size; end
8
+ def empty?; @items.empty?; end
9
+ def [](index); @items[index]; end
10
+ def each(&block); @items.each(&block); end
11
+ def to_a; @items.dup; end
12
+ def include?(object); @items.include? object; end
13
+
14
+ # Current being dragged?
15
+ def dragging?; @dragging; end
16
+
17
+ # Actually moved during a dragging operation?
18
+ def moved?; @moved; end
19
+
20
+ def initialize
21
+ @items = []
22
+ @moved = false
23
+ @dragging = false
24
+ end
25
+
26
+ def add(object)
27
+ object.selected = true
28
+ @items.push(object)
29
+
30
+ self
31
+ end
32
+
33
+ def remove(object)
34
+ @items.delete(object)
35
+ object.selected = false
36
+ object.dragging = false
37
+
38
+ self
39
+ end
40
+
41
+ def clear
42
+ end_drag if dragging?
43
+ @items.each { |o| o.selected = false; o.dragging = false }
44
+ @items.clear
45
+
46
+ self
47
+ end
48
+
49
+ def begin_drag(x, y)
50
+ @initial_x, @initial_y = x, y
51
+ @last_x, @last_y = x, y
52
+ @dragging = true
53
+ @moved = false
54
+
55
+ self
56
+ end
57
+
58
+ def end_drag
59
+ @items.each do |object|
60
+ object.x, object.y = object.x.round, object.y.round
61
+ object.dragging = false
62
+ end
63
+ @dragging = false
64
+ @moved = false
65
+
66
+ self
67
+ end
68
+
69
+ # Move all dragged object back to original positions.
70
+ def reset_drag
71
+ if moved?
72
+ @items.each do |o|
73
+ o.x += @initial_x - @last_x
74
+ o.y += @initial_y - @last_y
75
+ end
76
+ end
77
+
78
+ self.end_drag
79
+
80
+ self
81
+ end
82
+
83
+ def update_drag(x, y)
84
+ x, y = x.round, y.round
85
+
86
+ # If the mouse has been dragged far enough from the initial click position, then 'pick up' the objects and drag.
87
+ unless moved?
88
+ if distance(@initial_x, @initial_y, x, y) > MIN_DRAG_DISTANCE
89
+ @items.each { |o| o.dragging = true }
90
+ @moved = true
91
+ end
92
+ end
93
+
94
+ if moved?
95
+ @items.each do |o|
96
+ o.x += x - @last_x
97
+ o.y += y - @last_y
98
+ end
99
+
100
+ @last_x, @last_y = x, y
101
+ end
102
+
103
+ self
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ class Hash
2
+ # Merge not only the hashes, but all nested hashes as well.
3
+ # Written by Stefan Rusterholz (apeiros) from http://www.ruby-forum.com/topic/142809
4
+ def deep_merge!(other)
5
+ merger = lambda do |key, a, b|
6
+ (a.is_a?(Hash) && b.is_a?(Hash)) ? a.merge!(b, &merger) : b
7
+ end
8
+
9
+ merge!(other, &merger)
10
+ end
11
+
12
+ # Merge not only the hashes, but all nested hashes as well.
13
+ # Written by Stefan Rusterholz (apeiros) from http://www.ruby-forum.com/topic/142809
14
+ def deep_merge(other)
15
+ merger = lambda do |key, a, b|
16
+ (a.is_a?(Hash) && b.is_a?(Hash)) ? a.merge(b, &merger) : b
17
+ end
18
+
19
+ merge(other, &merger)
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ module Fidgit
2
+ # A modal dialog.
3
+ # @abstract
4
+ class DialogState < GuiState
5
+ DEFAULT_BACKGROUND_COLOR = Gosu::Color.rgb(75, 75, 75)
6
+ DEFAULT_BORDER_COLOR = Gosu::Color.rgb(255, 255, 255)
7
+
8
+ DEFAULT_SHADOW_COLOR = Gosu::Color.rgba(0, 0, 0, 100)
9
+ DEFAULT_SHADOW_OFFSET = 8
10
+
11
+ def initialize(options = {})
12
+ # @option options [Gosu::Color] :shadow_color (transparent black) Color of the shadow.
13
+ # @option options [Gosu::Color] :shadow_offset (8) Distance shadow is offset to bottom and left.
14
+ # @option options [Gosu::Color] :shadow_full (false) Shadow fills whole screen. Ignores :shadow_offset option if true.
15
+ options = {
16
+ shadow_color: DEFAULT_SHADOW_COLOR,
17
+ shadow_offset: DEFAULT_SHADOW_OFFSET,
18
+ shadow_full: false,
19
+ }.merge! options
20
+
21
+ @shadow_color = options[:shadow_color].dup
22
+ @shadow_offset = options[:shadow_offset]
23
+ @shadow_full = options[:shadow_full]
24
+
25
+ super()
26
+ end
27
+
28
+ def draw
29
+ $window.game_state_manager.previous_game_state.draw # Keep the underlying state being shown.
30
+ $window.flush
31
+
32
+ if @shadow_full
33
+ draw_rect 0, 0, $window.width, $window.height, -Float::INFINITY, @shadow_color
34
+ elsif @shadow_offset > 0
35
+ dialog = container[0]
36
+ draw_rect dialog.x + @shadow_offset, dialog.y + @shadow_offset, dialog.width, dialog.height, -Float::INFINITY, @shadow_color
37
+ end
38
+
39
+ super
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module Fidgit
2
+ # A simple dialog that manages a message with a set of buttons beneath it.
3
+ class FileDialog < DialogState
4
+ def initialize(type, options = {}, &block)
5
+ options = {
6
+ show: true,
7
+ background_color: DEFAULT_BACKGROUND_COLOR,
8
+ border_color: DEFAULT_BORDER_COLOR,
9
+ }.merge! options
10
+
11
+ super(options)
12
+
13
+ pack :vertical, align: :center, padding: 0 do |packer|
14
+ FileBrowser.new(type, { parent: packer }.merge!(options)) do |sender, result, file_name|
15
+ hide
16
+ block.call result, file_name if block
17
+ end
18
+ end
19
+
20
+ show if options[:show]
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,301 @@
1
+ # encoding: utf-8
2
+
3
+ module Fidgit
4
+ class GuiState < Chingu::GameState
5
+ # A 1x1 white pixel used for drawing.
6
+ PIXEL_IMAGE = 'pixel.png'
7
+
8
+ # The Container that contains all the elements for this GuiState.
9
+ # @return [Packer]
10
+ attr_reader :container
11
+
12
+ # The element with focus.
13
+ # @return [Element]
14
+ attr_reader :focus
15
+
16
+ # The Cursor.
17
+ # @return [Cursor]
18
+ def cursor; @@cursor; end
19
+
20
+ # Sets the focus to a particular element.
21
+ def focus=(element)
22
+ @focus.publish :blur if @focus and element
23
+ @focus = element
24
+ end
25
+
26
+ # Delay, in ms, before a tool-tip will appear.
27
+ def tool_tip_delay
28
+ 500 # TODO: configure this.
29
+ end
30
+
31
+ # Show a file_dialog.
32
+ # (see FileDialog#initialize)
33
+ def file_dialog(type, options = {}, &block)
34
+ FileDialog.new(type, options, &block)
35
+ end
36
+
37
+ # (see MenuPane#initialize)
38
+ def menu(options = {}, &block); MenuPane.new(options, &block); end
39
+
40
+ # (see MessageDialog#initialize)
41
+ def message(text, options = {}, &block); MessageDialog.new(text, options, &block); end
42
+
43
+ # (see Container#pack)
44
+ def pack(*args, &block); @container.pack *args, &block; end
45
+
46
+ # (see Container#clear)
47
+ def clear(*args, &block); @container.clear *args, &block; end
48
+
49
+ def initialize
50
+ # The container is where the user puts their content.
51
+ @container = VerticalPacker.new(padding: 0, width: $window.width, height: $window.height)
52
+
53
+ unless defined? @@draw_pixel
54
+ media_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'media'))
55
+ Gosu::Image.autoload_dirs << File.join(media_dir, 'images')
56
+ Gosu::Sample.autoload_dirs << File.join(media_dir, 'sounds')
57
+
58
+ @@draw_pixel = Gosu::Image.new($window, File.join(media_dir, 'images', PIXEL_IMAGE), true) # Must be tileable or it will blur.
59
+ @@cursor = Cursor.new
60
+ end
61
+
62
+ @min_drag_distance = 0
63
+
64
+ super()
65
+
66
+ add_inputs(
67
+ left_mouse_button: ->{ redirect_mouse_button(:left) },
68
+ holding_left_mouse_button: ->{ redirect_holding_mouse_button(:left) },
69
+ released_left_mouse_button: ->{ redirect_released_mouse_button(:left) },
70
+
71
+ middle_mouse_button: ->{ redirect_mouse_button(:middle) },
72
+ holding_middle_mouse_button: ->{ redirect_holding_mouse_button(:middle) },
73
+ released_middle_mouse_button: ->{ redirect_released_mouse_button(:middle) },
74
+
75
+ right_mouse_button: ->{ redirect_mouse_button(:right) },
76
+ holding_right_mouse_button: ->{ redirect_holding_mouse_button(:right) },
77
+ released_right_mouse_button: ->{ redirect_released_mouse_button(:right) },
78
+
79
+ mouse_wheel_up: :redirect_mouse_wheel_up,
80
+ mouse_wheel_down: :redirect_mouse_wheel_down
81
+ )
82
+ end
83
+
84
+ # Internationalisation helper.
85
+ def t(*args); I18n.t(*args); end
86
+
87
+ # Clear the data which is specific to the current $window.
88
+ def self.clear
89
+ remove_class_variable '@@cursor' if defined? @@cursor
90
+ remove_class_variable '@@draw_pixel' if defined? @@draw_pixel
91
+ end
92
+
93
+ def update
94
+ cursor.update
95
+ @tool_tip.update if @tool_tip
96
+ @menu.update if @menu
97
+ @container.update
98
+
99
+ # Check menu first, then other elements.
100
+ new_mouse_over = @menu.hit_element(cursor.x, cursor.y) if @menu
101
+ new_mouse_over = @container.hit_element(cursor.x, cursor.y) unless new_mouse_over
102
+
103
+ if new_mouse_over
104
+ new_mouse_over.publish :enter if new_mouse_over != @mouse_over
105
+ new_mouse_over.publish :hover, cursor.x, cursor.y
106
+ end
107
+
108
+ @mouse_over.publish :leave if @mouse_over and new_mouse_over != @mouse_over
109
+
110
+ @mouse_over = new_mouse_over
111
+
112
+ # Check if the mouse has moved, and no menu is shown, so we can show a tooltip.
113
+ if [cursor.x, cursor.y] == @last_cursor_pos and (not @menu)
114
+ if @mouse_over and (Gosu::milliseconds - @mouse_moved_at) > tool_tip_delay
115
+ if text = @mouse_over.tip and not text.empty?
116
+ @tool_tip ||= ToolTip.new
117
+ @tool_tip.text = text
118
+ @tool_tip.x = cursor.x
119
+ @tool_tip.y = cursor.y + cursor.height # Place the tip beneath the cursor.
120
+ else
121
+ @tool_tip = nil
122
+ @mouse_moved_at = Gosu::milliseconds
123
+ end
124
+ end
125
+ else
126
+ @tool_tip = nil
127
+ @mouse_moved_at = Gosu::milliseconds
128
+ end
129
+
130
+ # The element that grabs input.
131
+ @active_element = @dragging_element || @focus || @mouse_over
132
+
133
+ @last_cursor_pos = [cursor.x, cursor.y]
134
+
135
+ super
136
+ end
137
+
138
+ def draw
139
+ @container.draw
140
+ @menu.draw if @menu
141
+ @tool_tip.draw if @tool_tip
142
+ cursor.draw
143
+
144
+ nil
145
+ end
146
+
147
+ def setup
148
+ super
149
+
150
+ @tool_tip = nil
151
+ @mouse_over = nil # Element the mouse is hovering over.
152
+ @mouse_down_on = Hash.new # Element that each button was pressed over.
153
+ @mouse_down_pos = Hash.new # Position that each button was pressed down at.
154
+ @drag_button = nil
155
+ @dragging_element = nil
156
+ @focus = nil
157
+ @mouse_moved_at = Gosu::milliseconds
158
+
159
+ nil
160
+ end
161
+
162
+ def finalize
163
+ @tool_tip = nil
164
+
165
+ nil
166
+ end
167
+
168
+ # Set the menu pane to be displayed.
169
+ #
170
+ # @param [MenuPane] menu Menu to display.
171
+ # @return nil
172
+ def show_menu(menu)
173
+ hide_menu if @menu
174
+ @menu = menu
175
+
176
+ nil
177
+ end
178
+
179
+ # Hides the currently shown menu, if any.
180
+ # @return nil
181
+ def hide_menu
182
+ @menu = nil
183
+
184
+ nil
185
+ end
186
+
187
+ # Flush all pending drawing to the screen.
188
+ def flush
189
+ $window.flush
190
+ end
191
+
192
+ # Draw a filled rectangle.
193
+ def draw_rect(x, y, width, height, z, color, mode = :default)
194
+ @@draw_pixel.draw x, y, z, width, height, color, mode
195
+
196
+ nil
197
+ end
198
+
199
+ # Draw an unfilled rectangle.
200
+ def draw_frame(x, y, width, height, thickness, z, color, mode = :default)
201
+ draw_rect(x - thickness, y, thickness, height, z, color, mode) # left
202
+ draw_rect(x - thickness, y - thickness, width + thickness * 2, thickness, z, color, mode) # top (full)
203
+ draw_rect(x + width, y, thickness, height, z, color, mode) # right
204
+ draw_rect(x - thickness, y + height, width + thickness * 2, thickness, z, color, mode) # bottom (full)
205
+
206
+ nil
207
+ end
208
+
209
+ def distance(x1, y1, x2, y2)
210
+ Gosu.distance(x1, y1, x2, y2)
211
+ end
212
+
213
+ def show
214
+ $window.game_state_manager.push self unless $window.game_state_manager.game_states.include? self
215
+ nil
216
+ end
217
+
218
+ def hide
219
+ $window.game_state_manager.pop if $window.game_state_manager.current == self
220
+ nil
221
+ end
222
+
223
+ protected
224
+ def redirect_mouse_button(button)
225
+ # Ensure that if the user clicks away from a menu, it is automatically closed.
226
+ hide_menu unless @menu and @menu == @mouse_over
227
+
228
+ # Blur if clicking outside the focused element.
229
+ if @focus and @mouse_over != @focus
230
+ @focus.publish :blur
231
+ @focus = nil
232
+ end
233
+
234
+ # Publish :left_mouse_button for the element that is clicked.
235
+ if @mouse_over
236
+ @mouse_down_pos[button] = [cursor.x, cursor.y]
237
+ @mouse_down_on[button] = @mouse_over
238
+ @mouse_over.publish :"#{button}_mouse_button", *@mouse_down_pos[button]
239
+ else
240
+ @mouse_down_pos[button] = nil
241
+ @mouse_down_on[button] = nil
242
+ end
243
+
244
+ nil
245
+ end
246
+
247
+ protected
248
+ def redirect_released_mouse_button(button)
249
+ # Ensure that if the user clicks away from a menu, it is automatically closed.
250
+ hide_menu if @menu and @mouse_over != @menu
251
+
252
+ if @mouse_over
253
+ @mouse_over.publish :"released_#{button}_mouse_button", cursor.x, cursor.y
254
+ @mouse_over.publish :"clicked_#{button}_mouse_button", cursor.x, cursor.y if @mouse_over == @mouse_down_on[button]
255
+ end
256
+
257
+ if @dragging_element and @drag_button == button
258
+ @dragging_element.publish :end_drag, cursor.x, cursor.y, @drag_button, @mouse_over
259
+ @dragging_element = nil
260
+ @drag_button = nil
261
+ end
262
+
263
+ @mouse_down_on[button] = nil
264
+ @mouse_down_pos[button] = nil
265
+
266
+ nil
267
+ end
268
+
269
+ protected
270
+ def redirect_holding_mouse_button(button)
271
+ if not @dragging_element and @mouse_down_on[button] and @mouse_down_on[button].drag?(button) and
272
+ distance(*@mouse_down_pos[button], cursor.x, cursor.y) > @min_drag_distance
273
+ @drag_button = button
274
+ @dragging_element = @mouse_down_on[button]
275
+ @dragging_element.publish :begin_drag, *@mouse_down_pos[button], :left
276
+ end
277
+
278
+ if @dragging_element
279
+ if @drag_button == button
280
+ @dragging_element.publish :update_drag, cursor.x, cursor.y
281
+ end
282
+ else
283
+ @mouse_over.publish :"holding_#{button}_mouse_button", cursor.x, cursor.y if @mouse_over
284
+ end
285
+
286
+ nil
287
+ end
288
+
289
+ protected
290
+ def redirect_mouse_wheel_up
291
+ @active_element.publish :mouse_wheel_up, cursor.x, cursor.y if @active_element
292
+ nil
293
+ end
294
+
295
+ protected
296
+ def redirect_mouse_wheel_down
297
+ @active_element.publish :mouse_wheel_down, cursor.x, cursor.y if @active_element
298
+ nil
299
+ end
300
+ end
301
+ end