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.
- data/.gitignore +7 -7
- data/.rspec +2 -2
- data/CHANGELOG.md +30 -30
- data/Gemfile +3 -3
- data/LICENSE.txt +19 -19
- data/README.textile +139 -139
- data/Rakefile +37 -37
- data/config/default_schema.yml +216 -216
- data/examples/_all_examples.rb +9 -9
- data/examples/align_example.rb +55 -55
- data/examples/button_and_toggle_button_example.rb +37 -37
- data/examples/color_picker_example.rb +16 -16
- data/examples/color_well_example.rb +24 -24
- data/examples/combo_box_example.rb +23 -23
- data/examples/file_dialog_example.rb +41 -41
- data/examples/grid_packer_example.rb +28 -28
- data/examples/helpers/example_window.rb +16 -16
- data/examples/label_example.rb +22 -22
- data/examples/list_example.rb +22 -22
- data/examples/menu_pane_example.rb +26 -26
- data/examples/message_dialog_example.rb +64 -64
- data/examples/radio_button_example.rb +36 -36
- data/examples/readme_example.rb +31 -31
- data/examples/scroll_window_example.rb +48 -48
- data/examples/slider_example.rb +33 -33
- data/examples/splash_example.rb +41 -41
- data/examples/text_area_example.rb +32 -32
- data/fidgit.gemspec +35 -35
- data/lib/fidgit.rb +50 -50
- data/lib/fidgit/chingu_ext/window.rb +5 -5
- data/lib/fidgit/cursor.rb +37 -37
- data/lib/fidgit/elements/button.rb +112 -112
- data/lib/fidgit/elements/color_picker.rb +62 -62
- data/lib/fidgit/elements/color_well.rb +38 -38
- data/lib/fidgit/elements/combo_box.rb +113 -113
- data/lib/fidgit/elements/composite.rb +16 -16
- data/lib/fidgit/elements/container.rb +208 -208
- data/lib/fidgit/elements/element.rb +297 -297
- data/lib/fidgit/elements/file_browser.rb +151 -151
- data/lib/fidgit/elements/grid.rb +226 -226
- data/lib/fidgit/elements/group.rb +64 -64
- data/lib/fidgit/elements/horizontal.rb +11 -11
- data/lib/fidgit/elements/image_frame.rb +64 -64
- data/lib/fidgit/elements/label.rb +84 -84
- data/lib/fidgit/elements/list.rb +46 -46
- data/lib/fidgit/elements/main_packer.rb +24 -24
- data/lib/fidgit/elements/menu_pane.rb +160 -160
- data/lib/fidgit/elements/packer.rb +41 -41
- data/lib/fidgit/elements/radio_button.rb +85 -85
- data/lib/fidgit/elements/scroll_area.rb +67 -67
- data/lib/fidgit/elements/scroll_bar.rb +127 -127
- data/lib/fidgit/elements/scroll_window.rb +82 -82
- data/lib/fidgit/elements/slider.rb +124 -124
- data/lib/fidgit/elements/text_area.rb +493 -493
- data/lib/fidgit/elements/text_line.rb +91 -91
- data/lib/fidgit/elements/toggle_button.rb +66 -66
- data/lib/fidgit/elements/tool_tip.rb +34 -34
- data/lib/fidgit/elements/vertical.rb +11 -11
- data/lib/fidgit/event.rb +158 -158
- data/lib/fidgit/gosu_ext/color.rb +135 -135
- data/lib/fidgit/gosu_ext/gosu_module.rb +24 -24
- data/lib/fidgit/history.rb +90 -90
- data/lib/fidgit/redirector.rb +82 -82
- data/lib/fidgit/schema.rb +123 -123
- data/lib/fidgit/selection.rb +105 -105
- data/lib/fidgit/standard_ext/hash.rb +20 -20
- data/lib/fidgit/states/dialog_state.rb +51 -51
- data/lib/fidgit/states/file_dialog.rb +24 -24
- data/lib/fidgit/states/gui_state.rb +329 -329
- data/lib/fidgit/states/message_dialog.rb +60 -60
- data/lib/fidgit/version.rb +4 -4
- data/lib/fidgit/window.rb +19 -19
- data/spec/fidgit/elements/helpers/helper.rb +2 -2
- data/spec/fidgit/elements/helpers/tex_play_helper.rb +8 -8
- data/spec/fidgit/elements/image_frame_spec.rb +68 -68
- data/spec/fidgit/elements/label_spec.rb +36 -36
- data/spec/fidgit/event_spec.rb +209 -209
- data/spec/fidgit/gosu_ext/color_spec.rb +129 -129
- data/spec/fidgit/gosu_ext/helpers/helper.rb +2 -2
- data/spec/fidgit/helpers/helper.rb +3 -3
- data/spec/fidgit/history_spec.rb +153 -153
- data/spec/fidgit/redirector_spec.rb +77 -77
- data/spec/fidgit/schema_spec.rb +66 -66
- data/spec/fidgit/schema_test.yml +32 -32
- 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
|