glimmer-dsl-swt 4.18.4.10 → 4.18.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/README.md +14 -5
  4. data/VERSION +1 -1
  5. data/bin/glimmer +3 -3
  6. data/docs/reference/GLIMMER_CONFIGURATION.md +7 -3
  7. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +451 -112
  8. data/docs/reference/GLIMMER_SAMPLES.md +76 -3
  9. data/glimmer-dsl-swt.gemspec +23 -13
  10. data/lib/ext/glimmer/config.rb +3 -7
  11. data/lib/glimmer/data_binding/list_selection_binding.rb +13 -7
  12. data/lib/glimmer/data_binding/table_items_binding.rb +22 -17
  13. data/lib/glimmer/data_binding/tree_items_binding.rb +19 -15
  14. data/lib/glimmer/data_binding/widget_binding.rb +12 -27
  15. data/lib/glimmer/dsl/swt/{file_dialog_expression.rb → auto_exec_expression.rb} +6 -18
  16. data/lib/glimmer/dsl/swt/checkbox_group_selection_data_binding_expression.rb +9 -6
  17. data/lib/glimmer/dsl/swt/color_expression.rb +1 -1
  18. data/lib/glimmer/dsl/swt/combo_selection_data_binding_expression.rb +16 -14
  19. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +4 -1
  20. data/lib/glimmer/dsl/swt/data_binding_expression.rb +1 -2
  21. data/lib/glimmer/dsl/swt/dialog_expression.rb +18 -9
  22. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  23. data/lib/glimmer/dsl/swt/exec_expression.rb +1 -1
  24. data/lib/glimmer/dsl/swt/font_expression.rb +1 -1
  25. data/lib/glimmer/dsl/swt/image_expression.rb +16 -2
  26. data/lib/glimmer/dsl/swt/list_selection_data_binding_expression.rb +11 -8
  27. data/lib/glimmer/dsl/swt/pixel_expression.rb +1 -1
  28. data/lib/glimmer/dsl/swt/radio_group_selection_data_binding_expression.rb +8 -5
  29. data/lib/glimmer/dsl/swt/shape_expression.rb +2 -2
  30. data/lib/glimmer/dsl/swt/shell_expression.rb +1 -1
  31. data/lib/glimmer/dsl/swt/widget_expression.rb +8 -4
  32. data/lib/glimmer/launcher.rb +3 -0
  33. data/lib/glimmer/rake_task/scaffold.rb +3 -0
  34. data/lib/glimmer/swt/color_proxy.rb +1 -1
  35. data/lib/glimmer/swt/custom/code_text.rb +33 -11
  36. data/lib/glimmer/swt/custom/drawable.rb +57 -1
  37. data/lib/glimmer/swt/custom/shape.rb +332 -48
  38. data/lib/glimmer/swt/custom/shape/arc.rb +60 -0
  39. data/lib/glimmer/{dsl/swt/directory_dialog_expression.rb → swt/custom/shape/focus.rb} +15 -20
  40. data/lib/glimmer/swt/custom/shape/image.rb +112 -0
  41. data/lib/glimmer/swt/custom/shape/line.rb +111 -0
  42. data/lib/glimmer/swt/custom/shape/oval.rb +61 -0
  43. data/lib/glimmer/swt/custom/shape/point.rb +49 -0
  44. data/lib/glimmer/swt/custom/shape/polygon.rb +114 -0
  45. data/lib/glimmer/swt/custom/shape/polyline.rb +115 -0
  46. data/lib/glimmer/swt/custom/shape/rectangle.rb +105 -0
  47. data/lib/glimmer/swt/custom/shape/text.rb +85 -0
  48. data/lib/glimmer/swt/date_time_proxy.rb +9 -3
  49. data/lib/glimmer/swt/dialog_proxy.rb +92 -0
  50. data/lib/glimmer/swt/display_proxy.rb +62 -2
  51. data/lib/glimmer/swt/expand_item_proxy.rb +18 -12
  52. data/lib/glimmer/swt/font_proxy.rb +13 -7
  53. data/lib/glimmer/swt/image_proxy.rb +15 -4
  54. data/lib/glimmer/swt/layout_data_proxy.rb +21 -15
  55. data/lib/glimmer/swt/layout_proxy.rb +19 -15
  56. data/lib/glimmer/swt/menu_proxy.rb +2 -2
  57. data/lib/glimmer/swt/message_box_proxy.rb +21 -7
  58. data/lib/glimmer/swt/properties.rb +3 -0
  59. data/lib/glimmer/swt/proxy_properties.rb +145 -0
  60. data/lib/glimmer/swt/scrolled_composite_proxy.rb +6 -2
  61. data/lib/glimmer/swt/shell_proxy.rb +94 -80
  62. data/lib/glimmer/swt/swt_proxy.rb +16 -0
  63. data/lib/glimmer/swt/tab_item_proxy.rb +5 -3
  64. data/lib/glimmer/swt/table_proxy.rb +32 -11
  65. data/lib/glimmer/swt/transform_proxy.rb +39 -35
  66. data/lib/glimmer/swt/tree_proxy.rb +11 -16
  67. data/lib/glimmer/swt/widget_listener_proxy.rb +6 -2
  68. data/lib/glimmer/swt/widget_proxy.rb +193 -138
  69. data/lib/glimmer/ui.rb +5 -0
  70. data/lib/glimmer/ui/custom_shell.rb +11 -5
  71. data/lib/glimmer/ui/custom_widget.rb +4 -5
  72. data/samples/elaborate/contact_manager.rb +9 -7
  73. data/samples/elaborate/login.rb +27 -21
  74. data/samples/elaborate/mandelbrot_fractal.rb +20 -25
  75. data/samples/elaborate/meta_sample.rb +2 -1
  76. data/samples/elaborate/tetris.rb +3 -1
  77. data/samples/elaborate/tic_tac_toe.rb +18 -14
  78. data/samples/elaborate/tic_tac_toe/board.rb +5 -5
  79. data/samples/elaborate/tic_tac_toe/cell.rb +5 -5
  80. data/samples/elaborate/user_profile.rb +10 -8
  81. data/samples/hello/hello_browser.rb +2 -0
  82. data/samples/hello/hello_button.rb +9 -7
  83. data/samples/hello/hello_canvas.rb +144 -40
  84. data/samples/hello/hello_canvas_animation.rb +2 -0
  85. data/samples/hello/hello_canvas_transform.rb +2 -0
  86. data/samples/hello/hello_checkbox.rb +18 -14
  87. data/samples/hello/hello_checkbox_group.rb +13 -9
  88. data/samples/hello/hello_code_text.rb +2 -0
  89. data/samples/hello/hello_color_dialog.rb +68 -0
  90. data/samples/hello/hello_combo.rb +16 -12
  91. data/samples/hello/hello_computed.rb +9 -7
  92. data/samples/hello/hello_cursor.rb +4 -1
  93. data/samples/hello/hello_custom_shell.rb +18 -21
  94. data/samples/hello/hello_custom_widget.rb +6 -6
  95. data/samples/hello/hello_date_time.rb +16 -12
  96. data/samples/hello/hello_dialog.rb +2 -0
  97. data/samples/hello/hello_directory_dialog.rb +9 -7
  98. data/samples/hello/hello_drag_and_drop.rb +5 -3
  99. data/samples/hello/hello_expand_bar.rb +10 -8
  100. data/samples/hello/hello_file_dialog.rb +9 -7
  101. data/samples/hello/hello_font_dialog.rb +84 -0
  102. data/samples/hello/hello_group.rb +20 -16
  103. data/samples/hello/hello_link.rb +2 -0
  104. data/samples/hello/hello_list_multi_selection.rb +15 -11
  105. data/samples/hello/hello_list_single_selection.rb +15 -11
  106. data/samples/hello/hello_menu_bar.rb +2 -0
  107. data/samples/hello/hello_message_box.rb +2 -0
  108. data/samples/hello/hello_pop_up_context_menu.rb +2 -0
  109. data/samples/hello/hello_progress_bar.rb +5 -5
  110. data/samples/hello/hello_radio.rb +20 -16
  111. data/samples/hello/hello_radio_group.rb +16 -12
  112. data/samples/hello/hello_sash_form.rb +2 -0
  113. data/samples/hello/hello_spinner.rb +9 -7
  114. data/samples/hello/hello_styled_text.rb +19 -17
  115. data/samples/hello/hello_tab.rb +7 -5
  116. data/samples/hello/hello_table.rb +12 -5
  117. data/samples/hello/hello_tree.rb +485 -0
  118. data/samples/hello/hello_world.rb +2 -0
  119. metadata +21 -22
  120. data/lib/glimmer/swt/directory_dialog_proxy.rb +0 -65
  121. data/lib/glimmer/swt/file_dialog_proxy.rb +0 -66
@@ -32,12 +32,12 @@ module Glimmer
32
32
  include ParentExpression
33
33
 
34
34
  def can_interpret?(parent, keyword, *args, &block)
35
- parent.is_a?(Glimmer::SWT::Custom::Drawable) and
35
+ (parent.is_a?(Glimmer::SWT::Custom::Drawable) or parent.is_a?(Glimmer::SWT::Custom::Shape)) and
36
36
  Glimmer::SWT::Custom::Shape.valid?(parent, keyword, *args, &block)
37
37
  end
38
38
 
39
39
  def interpret(parent, keyword, *args, &block)
40
- Glimmer::SWT::Custom::Shape.new(parent, keyword, *args, &block)
40
+ Glimmer::SWT::Custom::Shape.create(parent, keyword, *args, &block)
41
41
  end
42
42
 
43
43
  def add_content(parent, &block)
@@ -38,7 +38,7 @@ module Glimmer
38
38
 
39
39
  def interpret(parent, keyword, *args, &block)
40
40
  args = [parent] + args unless parent.nil?
41
- Glimmer::SWT::ShellProxy.send(:new, *args)
41
+ Glimmer::SWT::ShellProxy.new(*args)
42
42
  end
43
43
  end
44
44
  class WindowExpression < ShellExpression
@@ -33,17 +33,21 @@ module Glimmer
33
33
  EXCLUDED_KEYWORDS = %w[shell display tab_item] + Glimmer::SWT::Custom::Shape.keywords - ['text']
34
34
 
35
35
  def can_interpret?(parent, keyword, *args, &block)
36
- result = !EXCLUDED_KEYWORDS.include?(keyword) &&
37
- parent.respond_to?(:swt_widget) && #TODO change to composite?(parent)
36
+ !EXCLUDED_KEYWORDS.include?(keyword) and
37
+ parent.respond_to?(:swt_widget) and
38
+ !parent.is_a?(Glimmer::SWT::Custom::Shape) and
39
+ !((keyword.to_s == 'text') and (args.first.is_a?(String) or parent.swt_widget.class == org.eclipse.swt.widgets.Canvas)) and
38
40
  Glimmer::SWT::WidgetProxy.widget_exists?(keyword)
39
- (keyword.to_s == 'text' && args.first.is_a?(String)) ? false : result
40
41
  end
41
42
 
42
43
  def interpret(parent, keyword, *args, &block)
43
- Glimmer::SWT::WidgetProxy.create(keyword, parent, args)
44
+ Glimmer::SWT::WidgetProxy.create(keyword, parent, args).tap do |new_widget_proxy|
45
+ new_widget_proxy.paint_pixel_by_pixel(&block) if block&.parameters&.count == 2
46
+ end
44
47
  end
45
48
 
46
49
  def add_content(parent, &block)
50
+ return if block&.parameters&.count == 2
47
51
  super
48
52
  parent.post_add_content
49
53
  parent.finish_add_content!
@@ -27,9 +27,12 @@ require 'fileutils'
27
27
  require 'os'
28
28
 
29
29
  module Glimmer
30
+ # Launcher of glimmer applications and main entry point for the `glimmer` command.
30
31
  class Launcher
31
32
  OPERATING_SYSTEMS_SUPPORTED = ["mac", "windows", "linux"]
32
33
 
34
+ # TODO convert to a bash script to achieve faster startup time
35
+
33
36
  TEXT_USAGE = <<~MULTI_LINE_STRING
34
37
  Glimmer (JRuby Desktop Development GUI Framework) - JRuby Gem: glimmer-dsl-swt v#{File.read(File.expand_path('../../../VERSION', __FILE__))}
35
38
  Usage: glimmer [--bundler] [--pd] [--quiet] [--debug] [--log-level=VALUE] [[ENV_VAR=VALUE]...] [[-jruby-option]...] (application.rb or task[task_args]) [[application2.rb]...]
@@ -94,6 +94,9 @@ module Glimmer
94
94
  # Mac
95
95
  .DS_Store
96
96
 
97
+ # Warbler
98
+ Jars.lock
99
+
97
100
  # Gladiator (Glimmer Editor)
98
101
  .gladiator
99
102
 
@@ -33,7 +33,7 @@ module Glimmer
33
33
  include_package 'org.eclipse.swt.graphics'
34
34
 
35
35
  class << self
36
- def flyweight(*args)
36
+ def create(*args)
37
37
  flyweight_color_proxies[args] ||= new(*args)
38
38
  end
39
39
 
@@ -64,6 +64,18 @@ module Glimmer
64
64
  respond_to?(method_name)
65
65
  end
66
66
 
67
+ def can_handle_observation_request?(observation_request)
68
+ @styled_text_proxy.can_handle_observation_request?(observation_request)
69
+ rescue
70
+ super
71
+ end
72
+
73
+ def handle_observation_request(observation_request, &block)
74
+ @styled_text_proxy.handle_observation_request(observation_request, &block)
75
+ rescue
76
+ super
77
+ end
78
+
67
79
  def root_block=(block)
68
80
  body_root.content(&block)
69
81
  end
@@ -124,13 +136,13 @@ module Glimmer
124
136
  editable false
125
137
  caret nil
126
138
  on_focus_gained {
127
- @styled_text_proxy&.swt_widget.setFocus
139
+ @styled_text_proxy&.setFocus
128
140
  }
129
141
  on_key_pressed {
130
- @styled_text_proxy&.swt_widget.setFocus
142
+ @styled_text_proxy&.setFocus
131
143
  }
132
144
  on_mouse_up {
133
- @styled_text_proxy&.swt_widget.setFocus
145
+ @styled_text_proxy&.setFocus
134
146
  }
135
147
  }
136
148
 
@@ -153,19 +165,29 @@ module Glimmer
153
165
  top_margin 5
154
166
  right_margin 5
155
167
  bottom_margin 5
168
+ tabs 2
156
169
 
157
170
  if default_behavior
158
171
  on_key_pressed { |event|
159
172
  character = event.keyCode.chr rescue nil
160
173
  case [event.stateMask, character]
161
174
  when [(OS.mac? ? swt(:command) : swt(:ctrl)), 'a']
162
- @styled_text_proxy.swt_widget.selectAll
175
+ @styled_text_proxy.selectAll
163
176
  when [(swt(:ctrl) if OS.mac?), 'a']
164
177
  jump_to_beginning_of_line
165
178
  when [(swt(:ctrl) if OS.mac?), 'e']
166
179
  jump_to_end_of_line
167
180
  end
168
181
  }
182
+ on_verify_text { |verify_event|
183
+ if verify_event.text == "\n"
184
+ line_index = verify_event.widget.get_line_at_offset(verify_event.widget.get_caret_offset)
185
+ line = verify_event.widget.get_line(line_index)
186
+ line_indent = line.match(/^([ ]*)/)[1].to_s.size
187
+ verify_event.text += ' '*line_indent
188
+ verify_event.text += ' '*2 if line.strip.end_with?('{') || line.strip.match(/do([ ]*[|][^|]*[|])?$/) || line.start_with?('class') || line.start_with?('module') || line.strip.start_with?('def')
189
+ end
190
+ }
169
191
  end
170
192
 
171
193
  on_modify_text { |event|
@@ -241,17 +263,17 @@ module Glimmer
241
263
  end
242
264
 
243
265
  def jump_to_beginning_of_line
244
- current_line_index = @styled_text_proxy.swt_widget.getLineAtOffset(@styled_text_proxy.swt_widget.getCaretOffset)
245
- beginning_of_current_line_offset = @styled_text_proxy.swt_widget.getOffsetAtLine(current_line_index)
246
- @styled_text_proxy.swt_widget.setSelection(beginning_of_current_line_offset, beginning_of_current_line_offset)
266
+ current_line_index = @styled_text_proxy.getLineAtOffset(@styled_text_proxy.getCaretOffset)
267
+ beginning_of_current_line_offset = @styled_text_proxy.getOffsetAtLine(current_line_index)
268
+ @styled_text_proxy.setSelection(beginning_of_current_line_offset, beginning_of_current_line_offset)
247
269
  end
248
270
 
249
271
  def jump_to_end_of_line
250
- current_line_index = @styled_text_proxy.swt_widget.getLineAtOffset(@styled_text_proxy.swt_widget.getCaretOffset)
251
- current_line = @styled_text_proxy.swt_widget.getLine(current_line_index)
252
- beginning_of_current_line_offset = @styled_text_proxy.swt_widget.getOffsetAtLine(current_line_index)
272
+ current_line_index = @styled_text_proxy.getLineAtOffset(@styled_text_proxy.getCaretOffset)
273
+ current_line = @styled_text_proxy.getLine(current_line_index)
274
+ beginning_of_current_line_offset = @styled_text_proxy.getOffsetAtLine(current_line_index)
253
275
  new_offset = beginning_of_current_line_offset + current_line.size
254
- @styled_text_proxy.swt_widget.setSelection(new_offset, new_offset)
276
+ @styled_text_proxy.setSelection(new_offset, new_offset)
255
277
  end
256
278
  end
257
279
  end
@@ -34,10 +34,22 @@ module Glimmer
34
34
  @shapes ||= []
35
35
  end
36
36
 
37
+ def expanded_shapes
38
+ @shapes.map do |shape|
39
+ [shape] + shape.expanded_shapes
40
+ end.flatten
41
+ end
42
+
37
43
  def image_buffered_shapes
38
44
  @image_buffered_shapes ||= []
39
45
  end
40
46
 
47
+ # TODO add a method like shapes that specifies drawable_properties to be able to adjust properties like transform in between shapes
48
+
49
+ def shape_at_location(x, y)
50
+ expanded_shapes.reverse.detect {|shape| shape.include?(x, y)}
51
+ end
52
+
41
53
  def add_shape(shape)
42
54
  if !@image_double_buffered || shape.args.first == @image_proxy_buffer
43
55
  shapes << shape
@@ -51,6 +63,50 @@ module Glimmer
51
63
  shapes.dup.each {|s| s.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns) } if requires_shape_disposal?
52
64
  end
53
65
 
66
+ def paint_pixel_by_pixel(width = nil, height = nil, &each_pixel_color)
67
+ if @image_double_buffered
68
+ work = lambda do |paint_event|
69
+ width ||= swt_drawable.bounds.width
70
+ height ||= swt_drawable.bounds.height
71
+ @image_proxy_buffer ||= ImageProxy.create_pixel_by_pixel(width, height, &each_pixel_color)
72
+ @image_proxy_buffer.shape(self).paint(paint_event)
73
+ end
74
+ else
75
+ work = lambda do |paint_event_or_image|
76
+ the_gc = paint_event_or_image.gc
77
+ current_foreground = nil
78
+ width ||= swt_drawable.bounds.width
79
+ height ||= swt_drawable.bounds.height
80
+ height.times do |y|
81
+ width.times do |x|
82
+ new_foreground = each_pixel_color.call(x, y)
83
+ new_foreground = Glimmer::SWT::ColorProxy.create(new_foreground, ensure_bounds: false) unless new_foreground.is_a?(ColorProxy) || new_foreground.is_a?(Color)
84
+ new_foreground = new_foreground.swt_color if new_foreground.is_a?(Glimmer::SWT::ColorProxy)
85
+ the_gc.foreground = current_foreground = new_foreground unless new_foreground == current_foreground
86
+ the_gc.draw_point x, y
87
+ end
88
+ end
89
+ end
90
+ end
91
+ if respond_to?(:gc)
92
+ work.call(self)
93
+ else
94
+ on_swt_paint(&work)
95
+ end
96
+ end
97
+
98
+ def swt_drawable
99
+ swt_drawable = nil
100
+ if respond_to?(:swt_image)
101
+ swt_drawable = swt_image
102
+ elsif respond_to?(:swt_display)
103
+ swt_drawable = swt_display
104
+ elsif respond_to?(:swt_widget)
105
+ swt_drawable = swt_widget
106
+ end
107
+ swt_drawable
108
+ end
109
+
54
110
  def deregister_shape_painting
55
111
  @paint_listener_proxy&.deregister
56
112
  end
@@ -84,7 +140,7 @@ module Glimmer
84
140
  @paint_listener_proxy = on_swt_paint(&shape_painter)
85
141
  end
86
142
  else
87
- redraw if @finished_add_content && !is_disposed
143
+ redraw if respond_to?(:redraw) && @finished_add_content && !is_disposed
88
144
  end
89
145
  end
90
146
  alias resetup_shape_painting setup_shape_painting
@@ -34,10 +34,17 @@ module Glimmer
34
34
  class Shape
35
35
  include Packages
36
36
  include Properties
37
- # TODO support textExtent sized shapes nested within text/string
38
- # TODO support a Pattern DSL for methods that take Pattern arguments
39
37
 
40
38
  class << self
39
+ def create(parent, keyword, *args, &property_block)
40
+ potential_shape_class_name = keyword.to_s.camelcase(:upper).to_sym
41
+ if constants.include?(potential_shape_class_name)
42
+ const_get(potential_shape_class_name).new(parent, keyword, *args, &property_block)
43
+ else
44
+ new(parent, keyword, *args, &property_block)
45
+ end
46
+ end
47
+
41
48
  def valid?(parent, keyword, *args, &block)
42
49
  gc_instance_methods.include?(method_name(keyword, arg_options(args)))
43
50
  end
@@ -79,27 +86,32 @@ module Glimmer
79
86
  end
80
87
 
81
88
  def pattern(*args)
82
- found_pattern = flyweigh_patterns[args]
89
+ found_pattern = flyweight_patterns[args]
83
90
  if found_pattern.nil? || found_pattern.is_disposed
84
- found_pattern = flyweigh_patterns[args] = org.eclipse.swt.graphics.Pattern.new(*args)
91
+ found_pattern = flyweight_patterns[args] = org.eclipse.swt.graphics.Pattern.new(*args)
85
92
  end
86
93
  found_pattern
87
94
  end
88
95
 
89
- def flyweigh_patterns
90
- @flyweigh_patterns ||= {}
96
+ def flyweight_patterns
97
+ @flyweight_patterns ||= {}
91
98
  end
92
99
  end
93
100
 
94
- attr_reader :parent, :name, :args, :options
101
+ attr_reader :drawable, :parent, :name, :args, :options, :shapes
102
+ attr_accessor :x_delta, :y_delta
95
103
 
96
104
  def initialize(parent, keyword, *args, &property_block)
97
105
  @parent = parent
106
+ @drawable = @parent.is_a?(Drawable) ? @parent : @parent.drawable
98
107
  @name = keyword
99
108
  @options = self.class.arg_options(args, extract: true)
100
109
  @method_name = self.class.method_name(keyword, @options)
101
110
  @args = args
102
111
  @properties = {}
112
+ @shapes = [] # nested shapes
113
+ @x_delta = 0
114
+ @y_delta = 0
103
115
  @options.reject {|key, value| %w[fill gradient round].include?(key.to_s)}.each do |property, property_args|
104
116
  @properties[property] = property_args
105
117
  end
@@ -107,13 +119,19 @@ module Glimmer
107
119
  post_add_content if property_block.nil?
108
120
  end
109
121
 
122
+ def add_shape(shape)
123
+ @shapes << shape
124
+ end
125
+
110
126
  def draw?
111
127
  !fill?
112
128
  end
129
+ alias drawn? draw?
113
130
 
114
131
  def fill?
115
132
  @options[:fill]
116
133
  end
134
+ alias filled? fill?
117
135
 
118
136
  def gradient?
119
137
  @options[:gradient]
@@ -123,6 +141,45 @@ module Glimmer
123
141
  @options[:round]
124
142
  end
125
143
 
144
+ # subclasses (like polygon) may override to indicate if a point x,y coordinates falls inside the shape
145
+ # some shapes may choose to provide a fuzz factor to make usage of this method for mouse clicking more user friendly
146
+ def contain?(x, y)
147
+ # assume a rectangular filled shape by default (works for several shapes like image, text, and focus)
148
+ if respond_to?(:x) && respond_to?(:y) && respond_to?(:width) && respond_to?(:height) && self.x && self.y && width && height
149
+ x.between?(self.absolute_x, self.absolute_x + width) && y.between?(self.absolute_y, self.absolute_y + height)
150
+ else
151
+ false # subclasses must provide implementation
152
+ end
153
+ end
154
+
155
+ # subclasses (like polygon) may override to indicate if a point x,y coordinates falls on the edge of a drawn shape or inside a filled shape
156
+ # some shapes may choose to provide a fuzz factor to make usage of this method for mouse clicking more user friendly
157
+ def include?(x, y)
158
+ # assume a rectangular shape by default
159
+ if respond_to?(:x) && respond_to?(:y) && respond_to?(:width) && respond_to?(:height)
160
+ contain?(x, y)
161
+ else
162
+ false # subclasses must provide implementation
163
+ end
164
+ end
165
+
166
+ # moves by x delta and y delta. Subclasses must implement
167
+ # provdies a default implementation that assumes moving x and y is sufficient by default (not for polygons though, which must override)
168
+ def move_by(x_delta, y_delta)
169
+ if respond_to?(:x) && respond_to?(:y) && respond_to?(:x=) && respond_to?(:y=)
170
+ if default_x?
171
+ self.x_delta += x_delta
172
+ else
173
+ self.x += x_delta
174
+ end
175
+ if default_y?
176
+ self.y_delta += y_delta
177
+ else
178
+ self.y += y_delta
179
+ end
180
+ end
181
+ end
182
+
126
183
  def has_some_background?
127
184
  @properties.keys.map(&:to_s).include?('background') || @properties.keys.map(&:to_s).include?('background_pattern')
128
185
  end
@@ -134,7 +191,7 @@ module Glimmer
134
191
  def post_add_content
135
192
  unless @content_added
136
193
  amend_method_name_options_based_on_properties!
137
- @parent.setup_shape_painting unless @parent.is_a?(ImageProxy)
194
+ @drawable.setup_shape_painting unless @drawable.is_a?(ImageProxy)
138
195
  @content_added = true
139
196
  end
140
197
  end
@@ -142,10 +199,13 @@ module Glimmer
142
199
  def apply_property_arg_conversions(method_name, property, args)
143
200
  args = args.dup
144
201
  the_java_method = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.detect {|m| m.name == method_name}
202
+ if the_java_method.parameter_types.first == Color.java_class && args.first.is_a?(RGB)
203
+ args[0] = [args[0].red, args[0].green, args[0].blue]
204
+ end
145
205
  if ['setBackground', 'setForeground'].include?(method_name.to_s) && args.first.is_a?(Array)
146
206
  args[0] = ColorProxy.new(args[0])
147
207
  end
148
- if args.first.is_a?(Symbol) || args.first.is_a?(String)
208
+ if args.first.is_a?(Symbol) || args.first.is_a?(::String)
149
209
  if the_java_method.parameter_types.first == Color.java_class
150
210
  args[0] = ColorProxy.new(args[0])
151
211
  end
@@ -156,7 +216,7 @@ module Glimmer
156
216
  if args.first.is_a?(ColorProxy)
157
217
  args[0] = args[0].swt_color
158
218
  end
159
- if args.first.is_a?(Hash) && the_java_method.parameter_types.first == Font.java_class
219
+ if (args.first.is_a?(Hash) || args.first.is_a?(FontData)) && the_java_method.parameter_types.first == Font.java_class
160
220
  args[0] = FontProxy.new(args[0])
161
221
  end
162
222
  if args.first.is_a?(FontProxy)
@@ -166,16 +226,23 @@ module Glimmer
166
226
  args[0] = args[0].swt_transform
167
227
  end
168
228
  if ['setBackgroundPattern', 'setForegroundPattern'].include?(method_name.to_s)
169
- @parent.requires_shape_disposal = true
229
+ @drawable.requires_shape_disposal = true
230
+ args = args.first if args.first.is_a?(Array)
170
231
  args.each_with_index do |arg, i|
171
- if arg.is_a?(Symbol) || arg.is_a?(String)
172
- args[i] = ColorProxy.new(arg).swt_color
173
- elsif arg.is_a?(ColorProxy)
174
- args[i] = arg.swt_color
175
- end
232
+ arg = ColorProxy.new(arg.red, arg.green, arg.blue) if arg.is_a?(RGB)
233
+ arg = ColorProxy.new(arg) if arg.is_a?(Symbol) || arg.is_a?(::String)
234
+ arg = arg.swt_color if arg.is_a?(ColorProxy)
235
+ args[i] = arg
236
+ end
237
+ @pattern_args ||= {}
238
+ pattern_type = method_name.to_s.match(/set(.+)Pattern/)[1]
239
+ if args.first.is_a?(Pattern)
240
+ new_args = @pattern_args[pattern_type]
241
+ else
242
+ new_args = args.first.is_a?(Display) ? args : ([DisplayProxy.instance.swt_display] + args)
243
+ @pattern_args[pattern_type] = new_args.dup
176
244
  end
177
- new_args = [DisplayProxy.instance.swt_display] + args
178
- args[0] = pattern(*new_args, type: method_name.to_s.match(/set(.+)Pattern/)[1])
245
+ args[0] = pattern(*new_args, type: pattern_type)
179
246
  args[1..-1] = []
180
247
  end
181
248
  args
@@ -186,29 +253,45 @@ module Glimmer
186
253
  @args[0] = @args.dup
187
254
  @args[1..-1] = []
188
255
  end
256
+ if @name == 'image'
257
+ if @args.first.is_a?(::String)
258
+ @args[0] = ImageProxy.new(@args[0])
259
+ end
260
+ if @args.first.is_a?(ImageProxy)
261
+ @image = @args[0] = @args[0].swt_image
262
+ end
263
+ if @args.first.nil?
264
+ @image = nil
265
+ end
266
+ end
267
+ if @name == 'text'
268
+ if @args[3].is_a?(Symbol) || @args[3].is_a?(::String)
269
+ @args[3] = [@args[3]]
270
+ end
271
+ if @args[3].is_a?(Array)
272
+ if @args[3].size == 1 && @args[3].first.is_a?(Array)
273
+ @args[3] = @args[3].first
274
+ end
275
+ @args[3] = SWTProxy[*@args[3]]
276
+ end
277
+ end
189
278
  end
190
279
 
191
280
  def apply_shape_arg_defaults!
192
281
  if @name.include?('rectangle') && round? && @args.size.between?(4, 5)
193
282
  (6 - @args.size).times {@args << 60}
194
283
  elsif @name.include?('rectangle') && gradient? && @args.size == 4
195
- @args << true
196
- elsif (@name.include?('text') || @name.include?('String')) && !@properties.keys.map(&:to_s).include?('background') && @args.size == 3
197
- @args << true
284
+ @args << true # vertical is true by default
285
+ elsif (@name.include?('text') || @name.include?('string')) && !@properties.keys.map(&:to_s).include?('background') && @args.size == 3
286
+ @args << true # is_transparent is true by default
198
287
  end
199
288
  if @name.include?('image')
200
- @parent.requires_shape_disposal = true
201
- if @args.size == 1
202
- @args[1] = 0
203
- @args[2] = 0
204
- end
205
- if @args.first.is_a?(String)
206
- @args[0] = ImageProxy.new(@args[0])
207
- end
208
- if @args.first.is_a?(ImageProxy)
209
- @image = @args[0] = @args[0].swt_image
210
- end
289
+ @drawable.requires_shape_disposal = true
211
290
  end
291
+ self.x = :default if current_parameter_name?(:x) && x.nil?
292
+ self.y = :default if current_parameter_name?(:y) && y.nil?
293
+ self.dest_x = :default if current_parameter_name?(:dest_x) && dest_x.nil?
294
+ self.dest_y = :default if current_parameter_name?(:dest_y) && dest_y.nil?
212
295
  end
213
296
 
214
297
  # Tolerates shape extra args added by user by mistake
@@ -217,14 +300,14 @@ module Glimmer
217
300
  the_java_method_arg_count = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.select do |m|
218
301
  m.name == @method_name.camelcase(:lower)
219
302
  end.map(&:parameter_types).map(&:size).max
220
- if @args.size > the_java_method_arg_count
303
+ if the_java_method_arg_count && @args.to_a.size > the_java_method_arg_count
221
304
  @args[the_java_method_arg_count..-1] = []
222
305
  end
223
306
  end
224
307
 
225
308
  def amend_method_name_options_based_on_properties!
226
309
  return if @name == 'point'
227
- if has_some_background? && !has_some_foreground?
310
+ if @name != 'text' && @name != 'string' && has_some_background? && !has_some_foreground?
228
311
  @options[:fill] = true
229
312
  elsif !has_some_background? && has_some_foreground?
230
313
  @options[:fill] = false
@@ -237,32 +320,118 @@ module Glimmer
237
320
  end
238
321
  @method_name = self.class.method_name(@name, @options)
239
322
  end
240
-
323
+
324
+ # parameter names for arguments to pass to SWT GC.xyz method for rendering shape (e.g. draw_image(image, x, y) yields :image, :x, :y parameter names)
325
+ def parameter_names
326
+ []
327
+ end
328
+
329
+ # subclasses may override to specify location parameter names if different from x and y (e.g. all polygon points are location parameters)
330
+ # used in calculating movement changes
331
+ def location_parameter_names
332
+ [:x, :y]
333
+ end
334
+
335
+ def possible_parameter_names
336
+ parameter_names
337
+ end
338
+
339
+ def parameter_name?(attribute_name)
340
+ possible_parameter_names.map(&:to_s).include?(ruby_attribute_getter(attribute_name))
341
+ end
342
+
343
+ def current_parameter_name?(attribute_name)
344
+ parameter_names.map(&:to_s).include?(ruby_attribute_getter(attribute_name))
345
+ end
346
+
347
+ def parameter_index(attribute_name)
348
+ parameter_names.map(&:to_s).index(attribute_name.to_s)
349
+ end
350
+
351
+ def set_parameter_attribute(attribute_name, *args)
352
+ @args[parameter_index(ruby_attribute_getter(attribute_name))] = args.size == 1 ? args.first : args
353
+ end
354
+
241
355
  def has_attribute?(attribute_name, *args)
242
- self.class.gc_instance_methods.include?(attribute_setter(attribute_name))
356
+ self.class.gc_instance_methods.include?(attribute_setter(attribute_name)) or
357
+ parameter_name?(attribute_name) or
358
+ (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
243
359
  end
244
360
 
245
361
  def set_attribute(attribute_name, *args)
246
- @properties[attribute_name] = args
247
- if @content_added && !@parent.is_disposed
362
+ options = args.last if args.last.is_a?(Hash)
363
+ perform_redraw = @perform_redraw
364
+ perform_redraw = options[:redraw] if perform_redraw.nil? && !options.nil?
365
+ perform_redraw = true if perform_redraw.nil?
366
+ if parameter_name?(attribute_name)
367
+ set_parameter_attribute(attribute_name, *args)
368
+ elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
369
+ self.send(ruby_attribute_setter(attribute_name), *args)
370
+ else
371
+ @properties[ruby_attribute_getter(attribute_name)] = args
372
+ end
373
+ if @content_added && perform_redraw && !drawable.is_disposed
248
374
  @calculated_paint_args = false
249
- @parent.redraw
375
+ attribute_name = ruby_attribute_getter(attribute_name)
376
+ @calculated_absolute_args = false if (location_parameter_names.map(&:to_s) + ['x_delta', 'y_delta']).include?(attribute_name)
377
+
378
+ # TODO consider redrawing an image proxy's gc in the future
379
+ drawable.redraw unless drawable.is_a?(ImageProxy)
250
380
  end
251
381
  end
252
382
 
253
383
  def get_attribute(attribute_name)
254
- @properties.symbolize_keys[attribute_name.to_s.to_sym]
384
+ if parameter_name?(attribute_name)
385
+ arg_index = parameter_index(attribute_name)
386
+ @args[arg_index] if arg_index
387
+ elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
388
+ self.send(attribute_name)
389
+ else
390
+ @properties.symbolize_keys[attribute_name.to_s.to_sym]
391
+ end
392
+ end
393
+
394
+ def method_missing(method_name, *args, &block)
395
+ if method_name.to_s.end_with?('=')
396
+ set_attribute(method_name, *args)
397
+ elsif has_attribute?(method_name)
398
+ get_attribute(method_name)
399
+ else
400
+ super
401
+ end
402
+ end
403
+
404
+ def respond_to?(method_name, *args, &block)
405
+ options = args.last if args.last.is_a?(Hash)
406
+ super_invocation = options && options[:super]
407
+ if !super_invocation && has_attribute?(method_name)
408
+ true
409
+ else
410
+ super
411
+ end
255
412
  end
256
413
 
257
414
  def pattern(*args, type: nil)
258
415
  instance_variable_name = "@#{type}_pattern"
259
416
  the_pattern = instance_variable_get(instance_variable_name)
260
- if the_pattern.nil?
417
+ if the_pattern.nil? || the_pattern.is_disposed
261
418
  the_pattern = self.class.pattern(*args)
262
419
  end
263
420
  the_pattern
264
421
  end
265
422
 
423
+ def pattern_args(type: nil)
424
+ @pattern_args && @pattern_args[type.to_s.capitalize]
425
+ end
426
+
427
+ def background_pattern_args
428
+ pattern_args(type: 'background')
429
+ end
430
+
431
+ def foreground_pattern_args
432
+ pattern_args(type: 'foreground')
433
+ end
434
+
266
435
  def dispose(dispose_images: true, dispose_patterns: true)
267
436
  if dispose_patterns
268
437
  @background_pattern&.dispose
@@ -287,18 +456,127 @@ module Glimmer
287
456
  args.first.swt_transform.dispose
288
457
  end
289
458
  end
290
- paint_event.gc.send(@method_name, *@args)
459
+ self.extent = paint_event.gc.send("#{@name}Extent", *(([string, flags] if respond_to?(:flags)).compact)) if ['text', 'string'].include?(@name)
460
+ if !@calculated_absolute_args || parent_shape_absolute_location_changed?
461
+ @absolute_args = absolute_args
462
+ @calculated_absolute_args = true
463
+ end
464
+ paint_event.gc.send(@method_name, *@absolute_args)
465
+ paint_children(paint_event)
466
+ rescue => e
467
+ Glimmer::Config.logger.error {"Error encountered in painting shape: #{self.inspect}"}
468
+ Glimmer::Config.logger.error {e.full_message}
469
+ end
470
+
471
+ def paint_children(paint_event)
472
+ shapes.to_a.each do |shape|
473
+ shape.paint(paint_event)
474
+ end
475
+ end
476
+
477
+ def expanded_shapes
478
+ if shapes.to_a.any?
479
+ shapes.map do |shape|
480
+ [shape] + shape.expanded_shapes
481
+ end.flatten
482
+ else
483
+ []
484
+ end
485
+ end
486
+
487
+ def parent_shape_absolute_location_changed?
488
+ (parent.is_a?(Shape) && (parent.absolute_x != @parent_absolute_x || parent.absolute_y != @parent_absolute_y))
489
+ end
490
+
491
+ # args translated to absolute coordinates
492
+ def absolute_args
493
+ return @args if !default_x? && !default_y? && @x_delta == 0 && @y_delta == 0 && parent.is_a?(Drawable)
494
+ @perform_redraw = false
495
+ original_x = nil
496
+ original_y = nil
497
+ if default_x?
498
+ original_x = x
499
+ self.x = default_x
500
+ end
501
+ if default_y?
502
+ original_y = y
503
+ self.y = default_y
504
+ end
505
+ move_by(@x_delta, @y_delta)
506
+ if parent.is_a?(Shape)
507
+ @parent_absolute_x = parent.absolute_x
508
+ @parent_absolute_y = parent.absolute_y
509
+ move_by(parent.absolute_x, parent.absolute_y)
510
+ calculated_absolute_args = @args.clone
511
+ move_by(-1*parent.absolute_x, -1*parent.absolute_y)
512
+ else
513
+ calculated_absolute_args = @args.clone
514
+ end
515
+ move_by(-1*@x_delta, -1*@y_delta)
516
+ if original_x
517
+ self.x = original_x
518
+ end
519
+ if original_y
520
+ self.y = original_y
521
+ end
522
+ @perform_redraw = true
523
+ calculated_absolute_args
524
+ end
525
+
526
+ def default_x?
527
+ current_parameter_name?('x') && (x.nil? || x.to_s == 'default')
528
+ end
529
+
530
+ def default_y?
531
+ current_parameter_name?('y') && (y.nil? || y.to_s == 'default')
532
+ end
533
+
534
+ def default_x
535
+ if respond_to?(:width) && width && parent.respond_to?(:width) && parent.width
536
+ (parent.width - width) / 2
537
+ else
538
+ 0
539
+ end
540
+ end
541
+
542
+ def default_y
543
+ if respond_to?(:height) && height && parent.respond_to?(:height) && parent.height
544
+ (parent.height - height) / 2
545
+ else
546
+ 0
547
+ end
548
+ end
549
+
550
+ def absolute_x
551
+ x = default_x? ? default_x : self.x
552
+ x += @x_delta
553
+ if parent.is_a?(Shape)
554
+ parent.absolute_x + x
555
+ else
556
+ x
557
+ end
558
+ end
559
+
560
+ def absolute_y
561
+ y = default_y? ? default_y : self.y
562
+ y += @y_delta
563
+ if parent.is_a?(Shape)
564
+ parent.absolute_y + y
565
+ else
566
+ y
567
+ end
291
568
  end
292
569
 
293
570
  def calculate_paint_args!
294
571
  unless @calculated_paint_args
295
- if @name == 'point'
572
+ if @name == 'pixel'
573
+ @name = 'point'
296
574
  # optimized performance calculation for pixel points
297
575
  if !@properties[:foreground].is_a?(Color)
298
576
  if @properties[:foreground].is_a?(Array)
299
577
  @properties[:foreground] = ColorProxy.new(@properties[:foreground], ensure_bounds: false)
300
578
  end
301
- if @properties[:foreground].is_a?(Symbol) || @properties[:foreground].is_a?(String)
579
+ if @properties[:foreground].is_a?(Symbol) || @properties[:foreground].is_a?(::String)
302
580
  @properties[:foreground] = ColorProxy.new(@properties[:foreground], ensure_bounds: false)
303
581
  end
304
582
  if @properties[:foreground].is_a?(ColorProxy)
@@ -306,10 +584,14 @@ module Glimmer
306
584
  end
307
585
  end
308
586
  else
309
- @properties['background'] = [@parent.background] if fill? && !has_some_background?
310
- @properties['foreground'] = [@parent.foreground] if @parent.respond_to?(:foreground) && draw? && !has_some_foreground?
311
- @properties['font'] = [@parent.font] if @parent.respond_to?(:font) && draw? && !@properties.keys.map(&:to_s).include?('font')
312
- @properties['transform'] = [nil] if @parent.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
587
+ @properties['background'] = [@drawable.background] if fill? && !has_some_background?
588
+ @properties['foreground'] = [@drawable.foreground] if @drawable.respond_to?(:foreground) && draw? && !has_some_foreground?
589
+ # TODO regarding alpha, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
590
+ @properties['alpha'] ||= [255]
591
+ @properties['font'] = [@drawable.font] if @drawable.respond_to?(:font) && draw? && !@properties.keys.map(&:to_s).include?('font')
592
+ # TODO regarding transform, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
593
+ # Also do that with all future-added properties
594
+ @properties['transform'] = [nil] if @drawable.respond_to?(:transform) && !@properties.keys.map(&:to_s).include?('transform')
313
595
  @properties.each do |property, args|
314
596
  method_name = attribute_setter(property)
315
597
  converted_args = apply_property_arg_conversions(method_name, property, args)
@@ -330,3 +612,5 @@ module Glimmer
330
612
  end
331
613
 
332
614
  end
615
+
616
+ Dir[File.expand_path(File.join(__dir__, 'shape', '**', '*.rb'))].each {|shape_file| require(shape_file)}