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,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