colstrom-fidgit 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
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