colstrom-fidgit 0.2.7

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