fidgit 0.0.2alpha

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 (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,12 @@
1
+ # encoding: utf-8
2
+
3
+ module Fidgit
4
+ # A vertically aligned element packing container.
5
+ class VerticalPacker < GridPacker
6
+ def initialize(options = {})
7
+ options[:num_columns] = 1
8
+
9
+ super options
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+ module Fidgit
4
+ # Adds simple event handling methods to an object (subscribe/publish pattern).
5
+ #
6
+ # @example
7
+ # class JumpingBean
8
+ # include Event
9
+ # event :jump
10
+ # end
11
+ #
12
+ # bean = JumpingBean.new
13
+ # bean.subscribe :jump do
14
+ # puts "Whee!"
15
+ # end
16
+ #
17
+ # bean.subscribe :jump do |object, direction, distance|
18
+ # puts "#{object.class.name} jumped #{distance} metres #{direction}"
19
+ # end
20
+ #
21
+ # bean.publish :jump, :up, 4
22
+ # # Whee!
23
+ # # JumpingBean jumped 4 metres up
24
+ #
25
+ module Event
26
+ # @overload subscribe(event, method)
27
+ # Add an event handler for an event, using a method.
28
+ # @return [nil]
29
+ #
30
+ # @overload subscribe(event, &block)
31
+ # Add an event handler for an event, using a block.
32
+ # @return [nil]
33
+ def subscribe(event, method = nil, &block)
34
+ raise ArgumentError, "Expected method or block for event handler" unless !block.nil? ^ !method.nil?
35
+ raise ArgumentError, "#{self.class} does not handle #{event.inspect}" unless events.include? event
36
+
37
+ @_event_handlers = Hash.new() { |hash, key| hash[key] = [] } unless @_event_handlers
38
+ @_event_handlers[event].push(method ? method : block)
39
+
40
+ nil
41
+ end
42
+
43
+ # Publish an event to all previously added handlers in the order they were added.
44
+ # It will automatically call the publishing object with the method named after the event if it is defined
45
+ # (this will be done before the manually added handlers are called).
46
+ #
47
+ # If any handler returns :handled, then no further handlers will be called.
48
+ #
49
+ # @param [Symbol] event Name of the event to publish.
50
+ # @param [Array] args Arguments to pass to the event handlers.
51
+ # @return [Symbol, nil] :handled if any handler handled the event or nil if none did.
52
+ def publish(event, *args)
53
+ raise ArgumentError, "#{self.class} does not handle #{event.inspect}" unless events.include? event
54
+
55
+ # Do nothing if the object is disabled.
56
+ return if respond_to?(:enabled?) and not enabled?
57
+
58
+ if respond_to? event
59
+ return :handled if send(event, self, *args) == :handled
60
+ end
61
+
62
+ if @_event_handlers
63
+ @_event_handlers[event].reverse_each do |handler|
64
+ return :handled if handler.call(self, *args) == :handled
65
+ end
66
+ end
67
+
68
+ nil
69
+ end
70
+
71
+ # The list of events that this object can publish/subscribe.
72
+ def events
73
+ self.class.events
74
+ end
75
+
76
+ # Add singleton methods to the class that includes Event.
77
+ def self.included(base)
78
+ class << base
79
+ def events
80
+ unless @events
81
+ # Copy the events already set up for your parent.
82
+ @events = if superclass.respond_to? :events
83
+ superclass.events.dup
84
+ else
85
+ []
86
+ end
87
+ end
88
+
89
+ @events
90
+ end
91
+
92
+ def event(event)
93
+ events.push event.to_sym unless events.include? event
94
+ event
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ module Gosu
4
+ class Color
5
+ # Is the color completely transparent?
6
+ def transparent?; alpha == 0; end
7
+ # Is the color completely opaque?
8
+ def opaque?; alpha == 255; end
9
+
10
+ # RGB in 0..255 format (Alpha assumed 255)
11
+ #
12
+ # @param [Integer] red
13
+ # @param [Integer] green
14
+ # @param [Integer] blue
15
+ # @return [Color]
16
+ def self.rgb(red, green, blue)
17
+ new(255, red, green, blue)
18
+ end
19
+
20
+ # RGBA in 0..255 format
21
+ #
22
+ # @param [Integer] red
23
+ # @param [Integer] green
24
+ # @param [Integer] blue
25
+ # @param [Integer] alpha
26
+ # @return [Color]
27
+ def self.rgba(red, green, blue, alpha)
28
+ new(alpha, red, green, blue)
29
+ end
30
+
31
+ # ARGB in 0..255 format (equivalent to Color.new, but explicit)
32
+ #
33
+ # @param [Integer] alpha
34
+ # @param (see Color.rgb)
35
+ # @return [Color]
36
+ def self.argb(alpha, red, green, blue)
37
+ new(alpha, red, green, blue)
38
+ end
39
+
40
+ # HSV format (alpha assumed to be 255)
41
+ #
42
+ # @param [Float] hue 0.0..360.0
43
+ # @param [Float] saturation 0.0..1.0
44
+ # @param [Float] value 0.0..1.0
45
+ # @return [Color]
46
+ def self.hsv(hue, saturation, value)
47
+ from_hsv(hue, saturation, value)
48
+ end
49
+
50
+ # HSVA format
51
+ #
52
+ # @param [Float] hue 0.0..360.0
53
+ # @param [Float] saturation 0.0..1.0
54
+ # @param [Float] value 0.0..1.0
55
+ # @param [Integer] alpha 1..255
56
+ # @return [Color]
57
+ def self.hsva(hue, saturation, value, alpha)
58
+ from_ahsv(alpha, hue, saturation, value)
59
+ end
60
+
61
+ class << self
62
+ alias_method :ahsv, :from_ahsv
63
+ end
64
+
65
+ # Convert from an RGBA array, as used by TexPlay.
66
+ #
67
+ # @param [Array<Float>] color TexPlay color [r, g, b, a] in range 0.0..1.0
68
+ # @return [Color]
69
+ def self.from_tex_play(color)
70
+ rgba(*color.map {|c| (c * 255).to_i })
71
+ end
72
+
73
+ # Convert to an RGBA array, as used by TexPlay.
74
+ #
75
+ # @return [Array<Float>] TexPlay color array [r, g, b, a] in range 0.0..1.0
76
+ def to_tex_play
77
+ [red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0]
78
+ end
79
+
80
+ # Show the Color as <RGBA [0, 0, 0, 0]> or, if opaque, <RGB [0, 0, 0]> (Gosu default is '(ARGB:0/0/0/0)')
81
+ def to_s
82
+ if opaque?
83
+ "<RGB [#{red}, #{green}, #{blue}]>"
84
+ else
85
+ "<RGBA [#{red}, #{green}, #{blue}, #{alpha}]>"
86
+ end
87
+ end
88
+
89
+ def +(other)
90
+ raise ArgumentError, "Can only add another #{self.class}" unless other.is_a? Color
91
+
92
+ copy = Color.new(0)
93
+
94
+ copy.red = [red + other.red, 255].min
95
+ copy.green = [green + other.green, 255].min
96
+ copy.blue = [blue + other.blue, 255].min
97
+ copy.alpha = [alpha + other.alpha, 255].min
98
+
99
+ copy
100
+ end
101
+
102
+ def -(other)
103
+ raise ArgumentError, "Can only take away another #{self.class}" unless other.is_a? Color
104
+
105
+ copy = Color.new(0)
106
+
107
+ copy.red = [red - other.red, 0].max
108
+ copy.green = [green - other.green, 0].max
109
+ copy.blue = [blue - other.blue, 0].max
110
+ copy.alpha = [alpha - other.alpha, 0].max
111
+
112
+ copy
113
+ end
114
+
115
+ def ==(other)
116
+ if other.is_a? Color
117
+ red == other.red and green == other.green and blue == other.blue and alpha == other.alpha
118
+ else
119
+ false
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+
3
+ module Fidgit
4
+ # Manages a history of actions, along with doing, undoing and redoing those actions.
5
+ class History
6
+ # Maximum number of actions in the History before Actions are deleted.
7
+ DEFAULT_MAX_SIZE = 250
8
+
9
+ # An action in the History. Inherit actions from this in order to add them to a History.
10
+ class Action
11
+ # Perform the action.
12
+ def do; raise NotImplementedError, "#{self.class} does not have a do method defined"; end
13
+
14
+ # Reverse the action.
15
+ def undo; raise NotImplementedError, "#{self.class} does not have an undo method defined"; end
16
+ end
17
+
18
+ # Is there an action that can be undone?
19
+ def can_undo?; @last_done >= 0; end
20
+
21
+ # Is there an action that has been undone that can now be redone?
22
+ def can_redo?; @last_done < (@actions.size - 1); end
23
+
24
+ def initialize(max_size = DEFAULT_MAX_SIZE)
25
+ @max_size = max_size
26
+ @actions = []
27
+ @last_done = -1 # Last command that was performed.
28
+ end
29
+
30
+ # Perform a History::Action, adding it to the history.
31
+ # If there are currently any actions that have been undone, they will be permanently lost and cannot be redone.
32
+ #
33
+ # @param [History::Action] action Action to be performed
34
+ def do(action)
35
+ raise ArgumentError, "Parameter, 'action', expected to be a #{Action}, but received: #{action}" unless action.is_a? Action
36
+
37
+ # Remove all undone actions when a new one is performed.
38
+ @actions = @actions[0..@last_done] if can_redo?
39
+
40
+ # If history is too big, remove the oldest action.
41
+ if @actions.size >= @max_size
42
+ @actions.shift
43
+ end
44
+
45
+ @last_done = @actions.size
46
+ @actions << action
47
+ action.do
48
+
49
+ nil
50
+ end
51
+
52
+ # Perform a History::Action, replacing the last action that was performed.
53
+ #
54
+ # @param [History::Action] action Action to be performed
55
+ def replace_last(action)
56
+ raise ArgumentError, "Parameter, 'action', expected to be a #{Action}, but received: #{action}" unless action.is_a? Action
57
+
58
+ @actions[@last_done].undo
59
+ @actions[@last_done] = action
60
+ action.do
61
+
62
+ nil
63
+ end
64
+
65
+ # Undo the last action that was performed.
66
+ def undo
67
+ raise "Can't undo unless there are commands in past" unless can_undo?
68
+
69
+ @actions[@last_done].undo
70
+ @last_done -= 1
71
+
72
+ nil
73
+ end
74
+
75
+ # Redo the last action that was undone.
76
+ def redo
77
+ raise "Can't redo if there are no commands in the future" unless can_redo?
78
+
79
+ @last_done += 1
80
+ @actions[@last_done].do
81
+
82
+ nil
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,83 @@
1
+ module Fidgit
2
+ # Redirects methods to an object, but does not mask methods and ivars from the calling context.
3
+ module RedirectorMethods
4
+ # Evaluate a block accessing methods and ivars from the calling context, but calling public methods
5
+ # (not ivars or non-public methods) on this object in preference.
6
+ def instance_methods_eval(&block)
7
+ raise ArgumentError, "block required" unless block_given?
8
+
9
+ context = eval('self', block.binding)
10
+
11
+ context.send :push_redirection_target, self
12
+
13
+ begin
14
+ yield context
15
+ ensure
16
+ context.send :pop_redirection_target
17
+ end
18
+
19
+ self
20
+ end
21
+
22
+ protected
23
+ def push_redirection_target(target)
24
+ meta_class = class << self; self; end
25
+ base_methods = Object.public_instance_methods
26
+
27
+ # Redirect just the public methods of the target, less those that are on Object.
28
+ methods_to_redirect = target.public_methods - base_methods
29
+
30
+ # Only hide those public/private/protected methods that are being redirected.
31
+ methods_overridden = []
32
+ [:public, :protected, :private].each do |access|
33
+ methods_to_hide = meta_class.send("#{access}_instance_methods", false) & methods_to_redirect
34
+ methods_to_hide.each do |meth|
35
+ # Take a reference to the method we are about to override.
36
+ methods_overridden.push [meth, method(meth), access]
37
+ meta_class.send :remove_method, meth
38
+ end
39
+ end
40
+
41
+ # Add a method, to redirect calls to the target.
42
+ methods_to_redirect.each do |meth|
43
+ meta_class.send :define_method, meth do |*args, &block|
44
+ target.send meth, *args, &block
45
+ end
46
+ end
47
+
48
+ redirection_stack.push [target, methods_overridden, methods_to_redirect]
49
+
50
+ target
51
+ end
52
+
53
+ protected
54
+ def pop_redirection_target
55
+ meta_class = class << self; self; end
56
+
57
+ target, methods_to_recreate, methods_to_remove = redirection_stack.pop
58
+
59
+ # Remove the redirection methods
60
+ methods_to_remove.reverse_each do |meth|
61
+ meta_class.send :remove_method, meth
62
+ end
63
+
64
+ # Replace with the previous versions of the methods.
65
+ methods_to_recreate.reverse_each do |meth, reference, access|
66
+ meta_class.send :define_method, meth, reference
67
+ meta_class.send access, meth unless access == :public
68
+ end
69
+
70
+ target
71
+ end
72
+
73
+ # Direct access to the redirection stack.
74
+ private
75
+ def redirection_stack
76
+ @_redirection_stack ||= []
77
+ end
78
+ end
79
+ end
80
+
81
+ class Object
82
+ include Fidgit::RedirectorMethods
83
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ module Fidgit
4
+ # An object that manages Schema values. Usually loaded from a YAML file.
5
+ #
6
+ # @example
7
+ # schema = Schema.new(YAML.load(file.read('default_schema.yml')))
8
+ # default_color = schema.default(Element, :disabled, :color)
9
+ # schema.merge_schema!(YAML.load(file.read('override_schema.yml'))
10
+ # overridden_color = schema.default(Element, :disabled, :color)
11
+ class Schema
12
+ CONSTANT_PREFIX = '?'
13
+
14
+ # @param [Hash<Symbol => Hash>] schema data containing
15
+ def initialize(schema)
16
+ @constants = {}
17
+ @elements = {}
18
+
19
+ merge_schema! schema
20
+ end
21
+
22
+ # Merge in a hash containing constant values.
23
+ #
24
+ # @param [Hash<Symbol => Hash>] constants_hash Containing :colors, :constants and :elements hashes.
25
+ def merge_schema!(schema)
26
+ merge_constants!(schema[:constants]) if schema[:constants]
27
+ merge_elements!(schema[:elements]) if schema[:elements]
28
+
29
+ self
30
+ end
31
+
32
+ # Merge in a hash containing constant values. Arrays will be resolved as colors in RGBA or RGB format.
33
+ #
34
+ # @param [Hash<Symbol => Object>] constants_hash
35
+ def merge_constants!(constants_hash)
36
+ constants_hash.each_pair do |name, value|
37
+ @constants[name] = case value
38
+ when Array
39
+ case value.size
40
+ when 3 then Gosu::Color.rgb(*value)
41
+ when 4 then Gosu::Color.rgba(*value)
42
+ else
43
+ raise "Colors must be in 0..255, RGB or RGBA array format"
44
+ end
45
+ else
46
+ value
47
+ end
48
+ end
49
+
50
+ self
51
+ end
52
+
53
+ # Merge in a hash containing default values for each element.
54
+ #
55
+ # @param [Hash<Symbol => Hash>] elements_hash
56
+ def merge_elements!(elements_hash)
57
+ elements_hash.each_pair do |klass_names, data|
58
+ klass = Fidgit
59
+ klass_names.to_s.split('::').each do |klass_name|
60
+ klass = klass.const_get klass_name
61
+ end
62
+
63
+ raise "elements must be names of classes derived from #{Element}" unless klass.ancestors.include? Fidgit::Element
64
+ @elements[klass] ||= {}
65
+ @elements[klass].deep_merge! data
66
+ end
67
+
68
+ self
69
+ end
70
+
71
+ # Get the constant value associated with +name+.
72
+ #
73
+ # @param [Symbol] name
74
+ # @return [Object]
75
+ def constant(name)
76
+ @constants[name]
77
+ end
78
+
79
+ # @param [Class] klass Class to look for defaults for.
80
+ # @param [Symbol, Array<Symbol>] names Hash names to search for in that class's schema.
81
+ def default(klass, names)
82
+ raise ArgumentError, "#{klass} is not a descendent of the #{Element} class" unless klass.ancestors.include? Element
83
+ value = default_internal(klass, Array(names), true)
84
+ raise("Failed to find named value #{names.inspect} for class #{klass}") unless value
85
+ value
86
+ end
87
+
88
+ protected
89
+ # @param [Class] klass Class to look for defaults for.
90
+ # @param [Array<Symbol>] names Hash names to search for in that class's schema.
91
+ # @param [Boolean] default_to_outer Whether to default to an outer value (used internally)
92
+ def default_internal(klass, names, default_to_outer)
93
+ # Find the value by moving through the nested hash via the names.
94
+ value = @elements[klass]
95
+
96
+ names.each do |name|
97
+ break unless value.is_a? Hash
98
+ value = value.has_key?(name) ? value[name] : nil
99
+ end
100
+
101
+ # Convert the value to a color/constant if they are symbols.
102
+ value = if value.is_a? String and value[0] == CONSTANT_PREFIX
103
+ str = value[1..-1]
104
+ constant(str.to_sym) || value # If the value isn't a constant, return the string.
105
+ else
106
+ value
107
+ end
108
+
109
+ # If we didn't find the value for this class, default to parent class value.
110
+ if value.nil? and klass != Element and klass.ancestors.include? Element
111
+ # Check if any ancestors define the fully named value.
112
+ value = default_internal(klass.superclass, names, false)
113
+ end
114
+
115
+ if value.nil? and default_to_outer and names.size > 1
116
+ # Check the outer values (e.g. if [:hover, :color] is not defined, try [:color]).
117
+ value = default_internal(klass, names[1..-1], true)
118
+ end
119
+
120
+ value
121
+ end
122
+ end
123
+ end