fidgit 0.2.4 → 0.2.5

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 (85) hide show
  1. data/.gitignore +7 -7
  2. data/.rspec +2 -2
  3. data/CHANGELOG.md +30 -30
  4. data/Gemfile +3 -3
  5. data/LICENSE.txt +19 -19
  6. data/README.textile +139 -139
  7. data/Rakefile +37 -37
  8. data/config/default_schema.yml +216 -216
  9. data/examples/_all_examples.rb +9 -9
  10. data/examples/align_example.rb +55 -55
  11. data/examples/button_and_toggle_button_example.rb +37 -37
  12. data/examples/color_picker_example.rb +16 -16
  13. data/examples/color_well_example.rb +24 -24
  14. data/examples/combo_box_example.rb +23 -23
  15. data/examples/file_dialog_example.rb +41 -41
  16. data/examples/grid_packer_example.rb +28 -28
  17. data/examples/helpers/example_window.rb +16 -16
  18. data/examples/label_example.rb +22 -22
  19. data/examples/list_example.rb +22 -22
  20. data/examples/menu_pane_example.rb +26 -26
  21. data/examples/message_dialog_example.rb +64 -64
  22. data/examples/radio_button_example.rb +36 -36
  23. data/examples/readme_example.rb +31 -31
  24. data/examples/scroll_window_example.rb +48 -48
  25. data/examples/slider_example.rb +33 -33
  26. data/examples/splash_example.rb +41 -41
  27. data/examples/text_area_example.rb +32 -32
  28. data/fidgit.gemspec +35 -35
  29. data/lib/fidgit.rb +50 -50
  30. data/lib/fidgit/chingu_ext/window.rb +5 -5
  31. data/lib/fidgit/cursor.rb +37 -37
  32. data/lib/fidgit/elements/button.rb +112 -112
  33. data/lib/fidgit/elements/color_picker.rb +62 -62
  34. data/lib/fidgit/elements/color_well.rb +38 -38
  35. data/lib/fidgit/elements/combo_box.rb +113 -113
  36. data/lib/fidgit/elements/composite.rb +16 -16
  37. data/lib/fidgit/elements/container.rb +208 -208
  38. data/lib/fidgit/elements/element.rb +297 -297
  39. data/lib/fidgit/elements/file_browser.rb +151 -151
  40. data/lib/fidgit/elements/grid.rb +226 -226
  41. data/lib/fidgit/elements/group.rb +64 -64
  42. data/lib/fidgit/elements/horizontal.rb +11 -11
  43. data/lib/fidgit/elements/image_frame.rb +64 -64
  44. data/lib/fidgit/elements/label.rb +84 -84
  45. data/lib/fidgit/elements/list.rb +46 -46
  46. data/lib/fidgit/elements/main_packer.rb +24 -24
  47. data/lib/fidgit/elements/menu_pane.rb +160 -160
  48. data/lib/fidgit/elements/packer.rb +41 -41
  49. data/lib/fidgit/elements/radio_button.rb +85 -85
  50. data/lib/fidgit/elements/scroll_area.rb +67 -67
  51. data/lib/fidgit/elements/scroll_bar.rb +127 -127
  52. data/lib/fidgit/elements/scroll_window.rb +82 -82
  53. data/lib/fidgit/elements/slider.rb +124 -124
  54. data/lib/fidgit/elements/text_area.rb +493 -493
  55. data/lib/fidgit/elements/text_line.rb +91 -91
  56. data/lib/fidgit/elements/toggle_button.rb +66 -66
  57. data/lib/fidgit/elements/tool_tip.rb +34 -34
  58. data/lib/fidgit/elements/vertical.rb +11 -11
  59. data/lib/fidgit/event.rb +158 -158
  60. data/lib/fidgit/gosu_ext/color.rb +135 -135
  61. data/lib/fidgit/gosu_ext/gosu_module.rb +24 -24
  62. data/lib/fidgit/history.rb +90 -90
  63. data/lib/fidgit/redirector.rb +82 -82
  64. data/lib/fidgit/schema.rb +123 -123
  65. data/lib/fidgit/selection.rb +105 -105
  66. data/lib/fidgit/standard_ext/hash.rb +20 -20
  67. data/lib/fidgit/states/dialog_state.rb +51 -51
  68. data/lib/fidgit/states/file_dialog.rb +24 -24
  69. data/lib/fidgit/states/gui_state.rb +329 -329
  70. data/lib/fidgit/states/message_dialog.rb +60 -60
  71. data/lib/fidgit/version.rb +4 -4
  72. data/lib/fidgit/window.rb +19 -19
  73. data/spec/fidgit/elements/helpers/helper.rb +2 -2
  74. data/spec/fidgit/elements/helpers/tex_play_helper.rb +8 -8
  75. data/spec/fidgit/elements/image_frame_spec.rb +68 -68
  76. data/spec/fidgit/elements/label_spec.rb +36 -36
  77. data/spec/fidgit/event_spec.rb +209 -209
  78. data/spec/fidgit/gosu_ext/color_spec.rb +129 -129
  79. data/spec/fidgit/gosu_ext/helpers/helper.rb +2 -2
  80. data/spec/fidgit/helpers/helper.rb +3 -3
  81. data/spec/fidgit/history_spec.rb +153 -153
  82. data/spec/fidgit/redirector_spec.rb +77 -77
  83. data/spec/fidgit/schema_spec.rb +66 -66
  84. data/spec/fidgit/schema_test.yml +32 -32
  85. metadata +67 -22
@@ -1,21 +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
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
21
  end
@@ -1,52 +1,52 @@
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
-
42
- def show
43
- $window.game_state_manager.push(self, finalize: false) unless $window.game_state_manager.game_states.include? self
44
- nil
45
- end
46
-
47
- def hide
48
- $window.game_state_manager.pop(setup: false) if $window.game_state_manager.current == self
49
- nil
50
- end
51
- end
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
+
42
+ def show
43
+ $window.game_state_manager.push(self, finalize: false) unless $window.game_state_manager.game_states.include? self
44
+ nil
45
+ end
46
+
47
+ def hide
48
+ $window.game_state_manager.pop(setup: false) if $window.game_state_manager.current == self
49
+ nil
50
+ end
51
+ end
52
52
  end
@@ -1,24 +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
- 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
-
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
+ 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
+
@@ -1,330 +1,330 @@
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
- extend Forwardable
9
-
10
- def_delegators :@container, :horizontal, :vertical, :grid
11
-
12
- # The Container that contains all the elements for this GuiState.
13
- # @return [Packer]
14
- attr_reader :container
15
-
16
- # The element with focus.
17
- # @return [Element]
18
- attr_reader :focus
19
-
20
- # The Cursor.
21
- # @return [Cursor]
22
- def cursor; @@cursor; end
23
-
24
- # Sets the focus to a particular element.
25
- def focus=(element)
26
- @focus.publish :blur if @focus and element
27
- @focus = element
28
- end
29
-
30
- # Delay, in ms, before a tool-tip will appear.
31
- def tool_tip_delay
32
- 500 # TODO: configure this.
33
- end
34
-
35
- # Show a file_dialog.
36
- # (see FileDialog#initialize)
37
- def file_dialog(type, options = {}, &block)
38
- FileDialog.new(type, options, &block)
39
- end
40
-
41
- # (see MenuPane#initialize)
42
- def menu(options = {}, &block); MenuPane.new(options, &block); end
43
-
44
- # (see MessageDialog#initialize)
45
- def message(text, options = {}, &block); MessageDialog.new(text, options, &block); end
46
-
47
- # (see Container#clear)
48
- def clear(*args, &block); @container.clear(*args, &block); end
49
-
50
- def initialize
51
- # The container is where the user puts their content.
52
- @container = MainPacker.new
53
- @menu = nil
54
- @last_cursor_pos = [-1, -1]
55
- @mouse_over = nil
56
-
57
- unless defined? @@draw_pixel
58
- media_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'media'))
59
- Gosu::Image.autoload_dirs << File.join(media_dir, 'images')
60
- Gosu::Sample.autoload_dirs << File.join(media_dir, 'sounds')
61
-
62
- @@draw_pixel = Gosu::Image.new($window, File.join(media_dir, 'images', PIXEL_IMAGE), true) # Must be tileable or it will blur.
63
- @@cursor = Cursor.new
64
- end
65
-
66
- @min_drag_distance = 0
67
-
68
- super()
69
-
70
- add_inputs(
71
- left_mouse_button: ->{ redirect_mouse_button(:left) },
72
- holding_left_mouse_button: ->{ redirect_holding_mouse_button(:left) },
73
- released_left_mouse_button: ->{ redirect_released_mouse_button(:left) },
74
-
75
- middle_mouse_button: ->{ redirect_mouse_button(:middle) },
76
- holding_middle_mouse_button: ->{ redirect_holding_mouse_button(:middle) },
77
- released_middle_mouse_button: ->{ redirect_released_mouse_button(:middle) },
78
-
79
- right_mouse_button: ->{ redirect_mouse_button(:right) },
80
- holding_right_mouse_button: ->{ redirect_holding_mouse_button(:right) },
81
- released_right_mouse_button: ->{ redirect_released_mouse_button(:right) },
82
-
83
- mouse_wheel_up: :redirect_mouse_wheel_up,
84
- mouse_wheel_down: :redirect_mouse_wheel_down,
85
-
86
- x: -> { if @focus and (holding_any?(:left_control, :right_control)) then @focus.cut end },
87
- c: -> { if @focus and (holding_any?(:left_control, :right_control)) then @focus.copy end },
88
- v: -> { if @focus and (holding_any?(:left_control, :right_control)) then @focus.paste end }
89
- )
90
- end
91
-
92
- # Internationalisation helper.
93
- def t(*args); I18n.t(*args); end
94
-
95
- # Clear the data which is specific to the current $window.
96
- def self.clear
97
- remove_class_variable '@@cursor' if defined? @@cursor
98
- remove_class_variable '@@draw_pixel' if defined? @@draw_pixel
99
- end
100
-
101
- def update
102
- cursor.update
103
- @tool_tip.update if @tool_tip
104
- @menu.update if @menu
105
- @container.update
106
-
107
- # Check menu first, then other elements.
108
- new_mouse_over = @menu.hit_element(cursor.x, cursor.y) if @menu
109
- new_mouse_over = @container.hit_element(cursor.x, cursor.y) unless new_mouse_over
110
-
111
- if new_mouse_over
112
- new_mouse_over.publish :enter if new_mouse_over != @mouse_over
113
- new_mouse_over.publish :hover, cursor.x, cursor.y
114
- end
115
-
116
- @mouse_over.publish :leave if @mouse_over and new_mouse_over != @mouse_over
117
-
118
- @mouse_over = new_mouse_over
119
-
120
- # Check if the mouse has moved, and no menu is shown, so we can show a tooltip.
121
- if [cursor.x, cursor.y] == @last_cursor_pos and (not @menu)
122
- if @mouse_over and (Gosu::milliseconds - @mouse_moved_at) > tool_tip_delay
123
- if text = @mouse_over.tip and not text.empty?
124
- @tool_tip ||= ToolTip.new
125
- @tool_tip.text = text
126
- @tool_tip.x = cursor.x
127
- @tool_tip.y = cursor.y + cursor.height # Place the tip beneath the cursor.
128
- else
129
- @tool_tip = nil
130
- @mouse_moved_at = Gosu::milliseconds
131
- end
132
- end
133
- else
134
- @tool_tip = nil
135
- @mouse_moved_at = Gosu::milliseconds
136
- end
137
-
138
- # The element that grabs input.
139
- @active_element = @dragging_element || @focus || @mouse_over
140
-
141
- @last_cursor_pos = [cursor.x, cursor.y]
142
-
143
- super
144
- end
145
-
146
- def write_tree
147
- puts "=== #{self.class} ==="
148
- indent = " "
149
- @container.write_tree(indent)
150
- @menu.write_tree(indent) if @menu
151
- @tool_tip.write_tree(indent) if @tool_tip
152
- end
153
-
154
- def draw
155
- @container.draw
156
- @menu.draw if @menu
157
- @tool_tip.draw if @tool_tip
158
- cursor.draw
159
-
160
- super
161
- end
162
-
163
- def setup
164
- super
165
-
166
- @tool_tip = nil
167
- @mouse_over = nil # Element the mouse is hovering over.
168
- @mouse_down_on = Hash.new # Element that each button was pressed over.
169
- @mouse_down_pos = Hash.new # Position that each button was pressed down at.
170
- @drag_button = nil
171
- @dragging_element = nil
172
- @focus = nil
173
- @mouse_moved_at = Gosu::milliseconds
174
-
175
- nil
176
- end
177
-
178
- def finalize
179
- unset_mouse_over
180
-
181
- if @focus
182
- @focus.publish :blur
183
- @focus = nil
184
- end
185
-
186
- @tool_tip = nil
187
-
188
- nil
189
- end
190
-
191
- # Called by active elements when they are disabled.
192
- def unset_mouse_over
193
- @mouse_over.publish :leave if @mouse_over
194
- @mouse_over = nil
195
- end
196
-
197
- # Set the menu pane to be displayed.
198
- #
199
- # @param [MenuPane] menu Menu to display.
200
- # @return nil
201
- def show_menu(menu)
202
- hide_menu if @menu
203
- @menu = menu
204
-
205
- nil
206
- end
207
-
208
- # Hides the currently shown menu, if any.
209
- # @return nil
210
- def hide_menu
211
- @menu = nil
212
-
213
- nil
214
- end
215
-
216
- # Flush all pending drawing to the screen.
217
- def flush
218
- $window.flush
219
- end
220
-
221
- # Draw a filled rectangle.
222
- def draw_rect(x, y, width, height, z, color, mode = :default)
223
- @@draw_pixel.draw x, y, z, width, height, color, mode
224
-
225
- nil
226
- end
227
-
228
- # Draw an unfilled rectangle.
229
- def draw_frame(x, y, width, height, thickness, z, color, mode = :default)
230
- draw_rect(x - thickness, y, thickness, height, z, color, mode) # left
231
- draw_rect(x - thickness, y - thickness, width + thickness * 2, thickness, z, color, mode) # top (full)
232
- draw_rect(x + width, y, thickness, height, z, color, mode) # right
233
- draw_rect(x - thickness, y + height, width + thickness * 2, thickness, z, color, mode) # bottom (full)
234
-
235
- nil
236
- end
237
-
238
- def distance(x1, y1, x2, y2)
239
- Gosu.distance(x1, y1, x2, y2)
240
- end
241
-
242
- def show
243
- $window.game_state_manager.push self unless $window.game_state_manager.game_states.include? self
244
- nil
245
- end
246
-
247
- def hide
248
- $window.game_state_manager.pop if $window.game_state_manager.current == self
249
- nil
250
- end
251
-
252
- protected
253
- def redirect_mouse_button(button)
254
- # Ensure that if the user clicks away from a menu, it is automatically closed.
255
- hide_menu unless @menu and @menu == @mouse_over
256
-
257
- # Blur if clicking outside the focused element.
258
- if @focus and @mouse_over != @focus
259
- @focus.publish :blur
260
- @focus = nil
261
- end
262
-
263
- # Publish :left_mouse_button for the element that is clicked.
264
- if @mouse_over
265
- @mouse_down_pos[button] = [cursor.x, cursor.y]
266
- @mouse_down_on[button] = @mouse_over
267
- @mouse_over.publish :"#{button}_mouse_button", *@mouse_down_pos[button]
268
- else
269
- @mouse_down_pos[button] = nil
270
- @mouse_down_on[button] = nil
271
- end
272
-
273
- nil
274
- end
275
-
276
- protected
277
- def redirect_released_mouse_button(button)
278
- # Ensure that if the user clicks away from a menu, it is automatically closed.
279
- hide_menu if @menu and @mouse_over != @menu
280
-
281
- if @mouse_over
282
- @mouse_over.publish :"released_#{button}_mouse_button", cursor.x, cursor.y
283
- @mouse_over.publish :"clicked_#{button}_mouse_button", cursor.x, cursor.y if @mouse_over == @mouse_down_on[button]
284
- end
285
-
286
- if @dragging_element and @drag_button == button
287
- @dragging_element.publish :end_drag, cursor.x, cursor.y, @drag_button, @mouse_over
288
- @dragging_element = nil
289
- @drag_button = nil
290
- end
291
-
292
- @mouse_down_on[button] = nil
293
- @mouse_down_pos[button] = nil
294
-
295
- nil
296
- end
297
-
298
- protected
299
- def redirect_holding_mouse_button(button)
300
- if not @dragging_element and @mouse_down_on[button] and @mouse_down_on[button].drag?(button) and
301
- distance(*@mouse_down_pos[button], cursor.x, cursor.y) > @min_drag_distance
302
- @drag_button = button
303
- @dragging_element = @mouse_down_on[button]
304
- @dragging_element.publish :begin_drag, *@mouse_down_pos[button], :left
305
- end
306
-
307
- if @dragging_element
308
- if @drag_button == button
309
- @dragging_element.publish :update_drag, cursor.x, cursor.y
310
- end
311
- else
312
- @mouse_over.publish :"holding_#{button}_mouse_button", cursor.x, cursor.y if @mouse_over
313
- end
314
-
315
- nil
316
- end
317
-
318
- protected
319
- def redirect_mouse_wheel_up
320
- @active_element.publish :mouse_wheel_up, cursor.x, cursor.y if @active_element
321
- nil
322
- end
323
-
324
- protected
325
- def redirect_mouse_wheel_down
326
- @active_element.publish :mouse_wheel_down, cursor.x, cursor.y if @active_element
327
- nil
328
- end
329
- end
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
+ extend Forwardable
9
+
10
+ def_delegators :@container, :horizontal, :vertical, :grid
11
+
12
+ # The Container that contains all the elements for this GuiState.
13
+ # @return [Packer]
14
+ attr_reader :container
15
+
16
+ # The element with focus.
17
+ # @return [Element]
18
+ attr_reader :focus
19
+
20
+ # The Cursor.
21
+ # @return [Cursor]
22
+ def cursor; @@cursor; end
23
+
24
+ # Sets the focus to a particular element.
25
+ def focus=(element)
26
+ @focus.publish :blur if @focus and element
27
+ @focus = element
28
+ end
29
+
30
+ # Delay, in ms, before a tool-tip will appear.
31
+ def tool_tip_delay
32
+ 500 # TODO: configure this.
33
+ end
34
+
35
+ # Show a file_dialog.
36
+ # (see FileDialog#initialize)
37
+ def file_dialog(type, options = {}, &block)
38
+ FileDialog.new(type, options, &block)
39
+ end
40
+
41
+ # (see MenuPane#initialize)
42
+ def menu(options = {}, &block); MenuPane.new(options, &block); end
43
+
44
+ # (see MessageDialog#initialize)
45
+ def message(text, options = {}, &block); MessageDialog.new(text, options, &block); end
46
+
47
+ # (see Container#clear)
48
+ def clear(*args, &block); @container.clear(*args, &block); end
49
+
50
+ def initialize
51
+ # The container is where the user puts their content.
52
+ @container = MainPacker.new
53
+ @menu = nil
54
+ @last_cursor_pos = [-1, -1]
55
+ @mouse_over = nil
56
+
57
+ unless defined? @@draw_pixel
58
+ media_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'media'))
59
+ Gosu::Image.autoload_dirs << File.join(media_dir, 'images')
60
+ Gosu::Sample.autoload_dirs << File.join(media_dir, 'sounds')
61
+
62
+ @@draw_pixel = Gosu::Image.new($window, File.join(media_dir, 'images', PIXEL_IMAGE), true) # Must be tileable or it will blur.
63
+ @@cursor = Cursor.new
64
+ end
65
+
66
+ @min_drag_distance = 0
67
+
68
+ super()
69
+
70
+ add_inputs(
71
+ left_mouse_button: ->{ redirect_mouse_button(:left) },
72
+ holding_left_mouse_button: ->{ redirect_holding_mouse_button(:left) },
73
+ released_left_mouse_button: ->{ redirect_released_mouse_button(:left) },
74
+
75
+ middle_mouse_button: ->{ redirect_mouse_button(:middle) },
76
+ holding_middle_mouse_button: ->{ redirect_holding_mouse_button(:middle) },
77
+ released_middle_mouse_button: ->{ redirect_released_mouse_button(:middle) },
78
+
79
+ right_mouse_button: ->{ redirect_mouse_button(:right) },
80
+ holding_right_mouse_button: ->{ redirect_holding_mouse_button(:right) },
81
+ released_right_mouse_button: ->{ redirect_released_mouse_button(:right) },
82
+
83
+ mouse_wheel_up: :redirect_mouse_wheel_up,
84
+ mouse_wheel_down: :redirect_mouse_wheel_down,
85
+
86
+ x: -> { if @focus and (holding_any?(:left_control, :right_control)) then @focus.cut end },
87
+ c: -> { if @focus and (holding_any?(:left_control, :right_control)) then @focus.copy end },
88
+ v: -> { if @focus and (holding_any?(:left_control, :right_control)) then @focus.paste end }
89
+ )
90
+ end
91
+
92
+ # Internationalisation helper.
93
+ def t(*args); I18n.t(*args); end
94
+
95
+ # Clear the data which is specific to the current $window.
96
+ def self.clear
97
+ remove_class_variable '@@cursor' if defined? @@cursor
98
+ remove_class_variable '@@draw_pixel' if defined? @@draw_pixel
99
+ end
100
+
101
+ def update
102
+ cursor.update
103
+ @tool_tip.update if @tool_tip
104
+ @menu.update if @menu
105
+ @container.update
106
+
107
+ # Check menu first, then other elements.
108
+ new_mouse_over = @menu.hit_element(cursor.x, cursor.y) if @menu
109
+ new_mouse_over = @container.hit_element(cursor.x, cursor.y) unless new_mouse_over
110
+
111
+ if new_mouse_over
112
+ new_mouse_over.publish :enter if new_mouse_over != @mouse_over
113
+ new_mouse_over.publish :hover, cursor.x, cursor.y
114
+ end
115
+
116
+ @mouse_over.publish :leave if @mouse_over and new_mouse_over != @mouse_over
117
+
118
+ @mouse_over = new_mouse_over
119
+
120
+ # Check if the mouse has moved, and no menu is shown, so we can show a tooltip.
121
+ if [cursor.x, cursor.y] == @last_cursor_pos and (not @menu)
122
+ if @mouse_over and (Gosu::milliseconds - @mouse_moved_at) > tool_tip_delay
123
+ if text = @mouse_over.tip and not text.empty?
124
+ @tool_tip ||= ToolTip.new
125
+ @tool_tip.text = text
126
+ @tool_tip.x = cursor.x
127
+ @tool_tip.y = cursor.y + cursor.height # Place the tip beneath the cursor.
128
+ else
129
+ @tool_tip = nil
130
+ @mouse_moved_at = Gosu::milliseconds
131
+ end
132
+ end
133
+ else
134
+ @tool_tip = nil
135
+ @mouse_moved_at = Gosu::milliseconds
136
+ end
137
+
138
+ # The element that grabs input.
139
+ @active_element = @dragging_element || @focus || @mouse_over
140
+
141
+ @last_cursor_pos = [cursor.x, cursor.y]
142
+
143
+ super
144
+ end
145
+
146
+ def write_tree
147
+ puts "=== #{self.class} ==="
148
+ indent = " "
149
+ @container.write_tree(indent)
150
+ @menu.write_tree(indent) if @menu
151
+ @tool_tip.write_tree(indent) if @tool_tip
152
+ end
153
+
154
+ def draw
155
+ @container.draw
156
+ @menu.draw if @menu
157
+ @tool_tip.draw if @tool_tip
158
+ cursor.draw
159
+
160
+ super
161
+ end
162
+
163
+ def setup
164
+ super
165
+
166
+ @tool_tip = nil
167
+ @mouse_over = nil # Element the mouse is hovering over.
168
+ @mouse_down_on = Hash.new # Element that each button was pressed over.
169
+ @mouse_down_pos = Hash.new # Position that each button was pressed down at.
170
+ @drag_button = nil
171
+ @dragging_element = nil
172
+ @focus = nil
173
+ @mouse_moved_at = Gosu::milliseconds
174
+
175
+ nil
176
+ end
177
+
178
+ def finalize
179
+ unset_mouse_over
180
+
181
+ if @focus
182
+ @focus.publish :blur
183
+ @focus = nil
184
+ end
185
+
186
+ @tool_tip = nil
187
+
188
+ nil
189
+ end
190
+
191
+ # Called by active elements when they are disabled.
192
+ def unset_mouse_over
193
+ @mouse_over.publish :leave if @mouse_over
194
+ @mouse_over = nil
195
+ end
196
+
197
+ # Set the menu pane to be displayed.
198
+ #
199
+ # @param [MenuPane] menu Menu to display.
200
+ # @return nil
201
+ def show_menu(menu)
202
+ hide_menu if @menu
203
+ @menu = menu
204
+
205
+ nil
206
+ end
207
+
208
+ # Hides the currently shown menu, if any.
209
+ # @return nil
210
+ def hide_menu
211
+ @menu = nil
212
+
213
+ nil
214
+ end
215
+
216
+ # Flush all pending drawing to the screen.
217
+ def flush
218
+ $window.flush
219
+ end
220
+
221
+ # Draw a filled rectangle.
222
+ def draw_rect(x, y, width, height, z, color, mode = :default)
223
+ @@draw_pixel.draw x, y, z, width, height, color, mode
224
+
225
+ nil
226
+ end
227
+
228
+ # Draw an unfilled rectangle.
229
+ def draw_frame(x, y, width, height, thickness, z, color, mode = :default)
230
+ draw_rect(x - thickness, y, thickness, height, z, color, mode) # left
231
+ draw_rect(x - thickness, y - thickness, width + thickness * 2, thickness, z, color, mode) # top (full)
232
+ draw_rect(x + width, y, thickness, height, z, color, mode) # right
233
+ draw_rect(x - thickness, y + height, width + thickness * 2, thickness, z, color, mode) # bottom (full)
234
+
235
+ nil
236
+ end
237
+
238
+ def distance(x1, y1, x2, y2)
239
+ Gosu.distance(x1, y1, x2, y2)
240
+ end
241
+
242
+ def show
243
+ $window.game_state_manager.push self unless $window.game_state_manager.game_states.include? self
244
+ nil
245
+ end
246
+
247
+ def hide
248
+ $window.game_state_manager.pop if $window.game_state_manager.current == self
249
+ nil
250
+ end
251
+
252
+ protected
253
+ def redirect_mouse_button(button)
254
+ # Ensure that if the user clicks away from a menu, it is automatically closed.
255
+ hide_menu unless @menu and @menu == @mouse_over
256
+
257
+ # Blur if clicking outside the focused element.
258
+ if @focus and @mouse_over != @focus
259
+ @focus.publish :blur
260
+ @focus = nil
261
+ end
262
+
263
+ # Publish :left_mouse_button for the element that is clicked.
264
+ if @mouse_over
265
+ @mouse_down_pos[button] = [cursor.x, cursor.y]
266
+ @mouse_down_on[button] = @mouse_over
267
+ @mouse_over.publish :"#{button}_mouse_button", *@mouse_down_pos[button]
268
+ else
269
+ @mouse_down_pos[button] = nil
270
+ @mouse_down_on[button] = nil
271
+ end
272
+
273
+ nil
274
+ end
275
+
276
+ protected
277
+ def redirect_released_mouse_button(button)
278
+ # Ensure that if the user clicks away from a menu, it is automatically closed.
279
+ hide_menu if @menu and @mouse_over != @menu
280
+
281
+ if @mouse_over
282
+ @mouse_over.publish :"released_#{button}_mouse_button", cursor.x, cursor.y
283
+ @mouse_over.publish :"clicked_#{button}_mouse_button", cursor.x, cursor.y if @mouse_over == @mouse_down_on[button]
284
+ end
285
+
286
+ if @dragging_element and @drag_button == button
287
+ @dragging_element.publish :end_drag, cursor.x, cursor.y, @drag_button, @mouse_over
288
+ @dragging_element = nil
289
+ @drag_button = nil
290
+ end
291
+
292
+ @mouse_down_on[button] = nil
293
+ @mouse_down_pos[button] = nil
294
+
295
+ nil
296
+ end
297
+
298
+ protected
299
+ def redirect_holding_mouse_button(button)
300
+ if not @dragging_element and @mouse_down_on[button] and @mouse_down_on[button].drag?(button) and
301
+ distance(*@mouse_down_pos[button], cursor.x, cursor.y) > @min_drag_distance
302
+ @drag_button = button
303
+ @dragging_element = @mouse_down_on[button]
304
+ @dragging_element.publish :begin_drag, *@mouse_down_pos[button], :left
305
+ end
306
+
307
+ if @dragging_element
308
+ if @drag_button == button
309
+ @dragging_element.publish :update_drag, cursor.x, cursor.y
310
+ end
311
+ else
312
+ @mouse_over.publish :"holding_#{button}_mouse_button", cursor.x, cursor.y if @mouse_over
313
+ end
314
+
315
+ nil
316
+ end
317
+
318
+ protected
319
+ def redirect_mouse_wheel_up
320
+ @active_element.publish :mouse_wheel_up, cursor.x, cursor.y if @active_element
321
+ nil
322
+ end
323
+
324
+ protected
325
+ def redirect_mouse_wheel_down
326
+ @active_element.publish :mouse_wheel_down, cursor.x, cursor.y if @active_element
327
+ nil
328
+ end
329
+ end
330
330
  end