fidgit 0.0.2alpha

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