lib/glimmer/swt/custom/shape.rb
# Copyright (c) 2007-2024 Andy Maleh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require 'glimmer/swt/properties'
require 'glimmer/swt/swt_proxy'
require 'glimmer/swt/display_proxy'
require 'glimmer/swt/color_proxy'
require 'glimmer/swt/font_proxy'
require 'glimmer/swt/transform_proxy'
require 'glimmer/swt/shape_listener_proxy'
class Java::OrgEclipseSwtGraphics::GC
def setLineDashOffset(value)
lineMiterLimit = getLineAttributes&.miterLimit || 999_999
setLineAttributes(Java::OrgEclipseSwtGraphics::LineAttributes.new(getLineWidth, getLineCap, getLineJoin, getLineStyle, getLineDash.map(&:to_f).to_java(:float), value, lineMiterLimit))
end
alias set_line_dash_offset setLineDashOffset
alias line_dash_offset= setLineDashOffset
def getLineDashOffset
getLineAttributes&.dashOffset
end
alias get_line_dash_offset getLineDashOffset
alias line_dash_offset getLineDashOffset
def setLineMiterLimit(value)
lineDashOffset = getLineAttributes&.dashOffset || 0
setLineAttributes(Java::OrgEclipseSwtGraphics::LineAttributes.new(getLineWidth, getLineCap, getLineJoin, getLineStyle, getLineDash.map(&:to_f).to_java(:float), lineDashOffset, value))
end
alias set_line_miter_limit setLineMiterLimit
alias line_miter_limit= setLineMiterLimit
def getLineMiterLimit
getLineAttributes&.miterLimit
end
alias get_line_miter_limit getLineMiterLimit
alias line_miter_limit getLineMiterLimit
end
module Glimmer
module SWT
module Custom
# Represents a shape (graphics) to be drawn on a control/widget/canvas/display
# That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
class Shape
include Properties
DropEvent = Struct.new(:doit, :x, :y, :dragged_shape, :dragged_shape_original_x, :dragged_shape_original_y, :dragging_x, :dragging_y, :drop_shapes, keyword_init: true)
class << self
attr_accessor :dragging, :dragging_x, :dragging_y, :dragged_shape, :dragged_shape_original_x, :dragged_shape_original_y, :drop_shape_handling_count
alias dragging? dragging
def create(parent, keyword, *args, &property_block)
potential_shape_class_name = keyword.to_s.camelcase(:upper).to_sym
if constants.include?(potential_shape_class_name)
const_get(potential_shape_class_name).new(parent, keyword, *args, &property_block)
else
new(parent, keyword, *args, &property_block)
end
end
def valid?(parent, keyword, *args, &block)
return true if keyword.to_s == 'shape'
gc_instance_methods.include?(method_name(keyword, arg_options(args))) ||
constants.include?(keyword.to_s.camelcase(:upper).to_sym)
end
def gc_instance_methods
@gc_instance_methods ||= org.eclipse.swt.graphics.GC.instance_methods.map(&:to_s)
end
def keywords
@keywords ||= gc_instance_methods.select do |method_name|
!method_name.end_with?('=') && (method_name.start_with?('draw_') || method_name.start_with?('fill_'))
end.reject do |method_name|
gc_instance_methods.include?("#{method_name}=") || gc_instance_methods.include?("set_#{method_name}")
end.map do |method_name|
method_name.gsub(/(draw|fill|gradient|round)_/, '')
end.uniq.compact.to_a
end
def arg_options(args, extract: false)
arg_options_method = extract ? :pop : :last
options = args.send(arg_options_method).symbolize_keys if args.last.is_a?(Hash)
# normalize :filled option as an alias to :fill
# options[:fill] = options.delete(:filled) if options&.keys&.include?(:filled)
options.nil? ? {} : options
end
def method_name(keyword, method_arg_options)
keyword = keyword.to_s
method_arg_options = method_arg_options.select {|key, value| %w[fill gradient round].include?(key.to_s)}
unless flyweight_method_names.keys.include?([keyword, method_arg_options])
gradient = 'Gradient' if method_arg_options[:gradient]
round = 'Round' if method_arg_options[:round]
gc_instance_method_name_prefix = !['polyline', 'point', 'image', 'focus'].include?(keyword) && (method_arg_options[:fill] || method_arg_options[:gradient]) ? 'fill' : 'draw'
flyweight_method_names[[keyword, method_arg_options]] = "#{gc_instance_method_name_prefix}#{gradient}#{round}#{keyword.capitalize}"
end
flyweight_method_names[[keyword, method_arg_options]]
end
def flyweight_method_names
@flyweight_method_names ||= {}
end
def pattern(*args)
found_pattern = flyweight_patterns[args]
if found_pattern.nil? || found_pattern.is_disposed
found_pattern = flyweight_patterns[args] = org.eclipse.swt.graphics.Pattern.new(*args)
end
found_pattern
end
def flyweight_patterns
@flyweight_patterns ||= {}
end
end
attr_reader :drawable, :parent, :name, :args, :options, :shapes, :properties, :disposed, :widget_listener_proxies
attr_accessor :extent
alias disposed? disposed
alias is_disposed disposed # for SWT widget compatibility
# TODO consider making shapes return a dup, always, in case consumers want to dispose
def initialize(parent, keyword, *args, &property_block)
@parent = parent
@drawable = @parent.is_a?(Drawable) ? @parent : @parent.drawable
@name = keyword
@options = self.class.arg_options(args, extract: true)
@method_name = self.class.method_name(keyword, @options) unless keyword.to_s == 'shape'
@args = args
@properties = {}
@shapes = [] # nested shapes
@options.reject {|key, value| %w[fill gradient round].include?(key.to_s)}.each do |property, property_args|
@properties[property] = property_args
end
@parent.add_shape(self)
post_add_content if property_block.nil?
end
def add_shape(shape)
@shapes << shape
calculated_args_changed_for_defaults!
end
def draw?
!fill?
end
alias drawn? draw?
def fill?
@options[:fill]
end
alias filled? fill?
def gradient?
@options[:gradient]
end
def round?
@options[:round]
end
# The bounding box top-left x, y, width, height in absolute positioning
def bounds
bounds_dependencies = [absolute_x, absolute_y, calculated_width, calculated_height]
if bounds_dependencies != @bounds_dependencies
# avoid repeating calculations
absolute_x, absolute_y, calculated_width, calculated_height = @bounds_dependencies = bounds_dependencies
@bounds = org.eclipse.swt.graphics.Rectangle.new(absolute_x, absolute_y, calculated_width, calculated_height)
end
@bounds
end
# The bounding box top-left x and y
def location
org.eclipse.swt.graphics.Point.new(bounds.x, bounds.y)
end
# The bounding box width and height (as a Point object with x being width and y being height)
def size
size_dependencies = [calculated_width, calculated_height]
if size_dependencies != @size_dependencies
# avoid repeating calculations
calculated_width, calculated_height = @size_dependencies = size_dependencies
@size = org.eclipse.swt.graphics.Point.new(calculated_width, calculated_height)
end
@size
end
def extent
@extent || size
end
# Returns if shape contains a point
# Subclasses (like polygon) may override to indicate if a point x,y coordinates falls inside the shape
# some shapes may choose to provide a fuzz factor to make usage of this method for mouse clicking more user friendly
def contain?(x, y)
x, y = inverse_transform_point(x, y)
# assume a rectangular filled shape by default (works for several shapes like image, text, and focus)
x.between?(self.absolute_x, self.absolute_x + calculated_width.to_f) && y.between?(self.absolute_y, self.absolute_y + calculated_height.to_f)
end
# Returns if shape includes a point. When the shape is filled, this is the same as contain. When the shape is drawn, it only returns true if the point lies on the edge (boundary/border)
# 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
# some shapes may choose to provide a fuzz factor to make usage of this method for mouse clicking more user friendly
def include?(x, y)
# assume a rectangular shape by default
contain?(x, y)
end
def include_with_children?(x, y, except_shape: nil)
included_in_self = (!self.equal?(except_shape) && include?(x, y))
# TODO note that expanded shapes does not filter when reject below happens, keeping nested shapes
included_in_self || expanded_shapes(except_shape: except_shape).any? { |shape| shape.include?(x, y) }
end
def bounds_contain?(x, y)
x, y = inverse_transform_point(x, y)
bounds.contains(x, y)
end
# Recursively checks if other shape is included in this shape, beginning by comparing to self
# and then recursing into children shapes.
def include_shape?(other)
self == other || shapes.any? { |shape| shape.include_shape?(other) }
end
# if there is a transform, apply on x, y point coordinates
def transform_point(x, y)
# keep in mind that transform is an array of a single element that is the transform object
current_transform = (transform || parent_shape_containers.map(&:transform).first)&.first
if current_transform
transform_array = [x, y].to_java(:float) # just placeholder data that gets overwritten with getElements
current_transform.transform(transform_array)
x = transform_array[0]
y = transform_array[1]
end
[x, y]
end
# if there is a transform, invert it and apply on x, y point coordinates
def inverse_transform_point(x, y)
# keep in mind that transform is an array of a single element that is the transform object
current_transform = (transform || parent_shape_containers.map(&:transform).compact.first)&.first
if current_transform
transform_array = [1,2,3,4,5,6].to_java(:float) # just placeholder data that gets overwritten with getElements
current_transform.getElements(transform_array)
inverse_transform = TransformProxy.new(DisplayProxy.instance.swt_display, *transform_array.to_a)
inverse_transform_array = [1,2,3,4,5,6].to_java(:float) # just placeholder data that gets overwritten with getElements
inverse_transform.getElements(inverse_transform_array)
# TODO avoid Matrix usage directly by relying on SWT Transform#invert method instead (much simpler)
matrix = Matrix[[inverse_transform_array[0], inverse_transform_array[1]], [inverse_transform_array[2], inverse_transform_array[3]]]
result = matrix * Matrix.column_vector([x, y])
x, y = result.to_a.flatten
x += inverse_transform_array[5]
y += inverse_transform_array[4]
end
[x, y]
end
# Indicates if a shape's x, y, width, height differ from its bounds calculation (e.g. arc / polygon)
def irregular?
false
end
# moves by x delta and y delta. Subclasses must implement
# provdies a default implementation that assumes moving x and y is sufficient by default (not for polygons though, which must override)
def move_by(x_delta, y_delta)
if respond_to?(:x) && respond_to?(:y) && respond_to?(:x=) && respond_to?(:y=)
if default_x?
self.x_delta += x_delta
else
self.x += x_delta
end
if default_y?
self.y_delta += y_delta
else
self.y += y_delta
end
end
end
alias translate move_by
# rotates shape for an angle around its center
# this operation is not cumulative (it resets angle every time)
# consumers may inspect corresponding rotation_angle attribute to know which angle the shape is currently at for convenience
# this overrides any pre-existing transforms that are applied to shape
def rotate(angle)
half_width = calculated_width/2.0
half_height = calculated_height/2.0
self.transform = Glimmer::SWT::TransformProxy.new(self).translate(half_width, half_height).rotate(angle).translate(-1.0*half_width, -1.0*half_height)
@rotation_angle = angle
end
# returns rotation angle
# consumers may inspect rotation_angle attribute to know which angle the shape is rotated at via rotate method
# it is not guaranteed to give the right result if a transform is applied outside of rotate method.
# starts at 0
def rotation_angle
@rotation_angle.to_f
end
def center_x
center_x_dependencies = [x_end, calculated_width]
if center_x_dependencies != @center_x_dependencies
@center_x_dependencies = center_x_dependencies
the_x_end, the_calculated_width = center_x_dependencies
@center_x = the_x_end - the_calculated_width/2.0
end
@center_x
end
def center_y
center_y_dependencies = [y_end, calculated_height]
if center_y_dependencies != @center_y_dependencies
@center_y_dependencies = center_y_dependencies
the_y_end, the_calculated_height = center_y_dependencies
@center_y = the_y_end - the_calculated_height/2.0
end
@center_y
end
def content(&block)
# TODO consider supporting adding content without redraw (redraw: false)
Glimmer::SWT::DisplayProxy.instance.auto_exec do
Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::SWT::ShapeExpression.new, @name, &block)
calculated_args_changed!(children: false)
end
end
def has_some_background?
@properties.keys.map(&:to_s).include?('background') || @properties.keys.map(&:to_s).include?('background_pattern')
end
def has_some_foreground?
@properties.keys.map(&:to_s).include?('foreground') || @properties.keys.map(&:to_s).include?('foreground_pattern')
end
def post_add_content
amend_method_name_options_based_on_properties!
if !@content_added || @method_name != @original_method_name
@drawable.setup_shape_painting unless @drawable.is_a?(ImageProxy)
@content_added = true
end
end
def apply_property_arg_conversions(property, args)
method_name = attribute_setter(property)
args = args.dup
the_java_method = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.detect {|m| m.name == method_name}
return args if the_java_method.nil?
if the_java_method.parameter_types.first == org.eclipse.swt.graphics.Color.java_class && args.first.is_a?(org.eclipse.swt.graphics.RGB)
args[0] = [args[0].red, args[0].green, args[0].blue]
end
if ['setBackground', 'setForeground'].include?(method_name.to_s) && args.first.is_a?(Array)
args[0] = ColorProxy.new(args[0])
end
if method_name.to_s == 'setLineDash' && args.size > 1
args[0] = args.dup
args[1..-1] = []
end
if method_name.to_s == 'setAntialias' && [nil, true, false].include?(args.first)
args[0] = case args.first
when true
args[0] = :on
when false
args[0] = :off
when nil
args[0] = :default
end
end
if args.first.is_a?(Symbol) || args.first.is_a?(::String)
if the_java_method.parameter_types.first == org.eclipse.swt.graphics.Color.java_class
args[0] = ColorProxy.new(args[0])
end
if method_name.to_s == 'setLineStyle'
args[0] = "line_#{args[0]}" if !args[0].to_s.downcase.start_with?('line_')
end
if method_name.to_s == 'setFillRule'
args[0] = "fill_#{args[0]}" if !args[0].to_s.downcase.start_with?('fill_')
end
if method_name.to_s == 'setLineCap'
args[0] = "cap_#{args[0]}" if !args[0].to_s.downcase.start_with?('cap_')
end
if method_name.to_s == 'setLineJoin'
args[0] = "join_#{args[0]}" if !args[0].to_s.downcase.start_with?('join_')
end
if the_java_method.parameter_types.first == Java::int.java_class
args[0] = SWTProxy.constant(args[0])
end
end
if args.first.is_a?(ColorProxy)
args[0] = args[0].swt_color
end
if (args.first.is_a?(Hash) || args.first.is_a?(org.eclipse.swt.graphics.FontData)) && the_java_method.parameter_types.first == org.eclipse.swt.graphics.Font.java_class
args[0] = FontProxy.new(args[0])
end
if args.first.is_a?(FontProxy)
args[0] = args[0].swt_font
end
if args.first.is_a?(TransformProxy)
args[0] = args[0].swt_transform
end
if ['setBackgroundPattern', 'setForegroundPattern'].include?(method_name.to_s)
@drawable.requires_shape_disposal = true
args = args.first if args.first.is_a?(Array)
args.each_with_index do |arg, i|
arg = ColorProxy.new(arg.red, arg.green, arg.blue) if arg.is_a?(org.eclipse.swt.graphics.RGB)
arg = ColorProxy.new(arg) if arg.is_a?(Symbol) || arg.is_a?(::String)
arg = arg.swt_color if arg.is_a?(ColorProxy)
args[i] = arg
end
@pattern_args ||= {}
pattern_type = method_name.to_s.match(/set(.+)Pattern/)[1]
if args.first.is_a?(org.eclipse.swt.graphics.Pattern)
new_args = @pattern_args[pattern_type]
else
new_args = args.first.is_a?(org.eclipse.swt.widgets.Display) ? args : ([DisplayProxy.instance.swt_display] + args)
@pattern_args[pattern_type] = new_args.dup
end
args[0] = pattern(*new_args, type: pattern_type)
args[1..-1] = []
end
args
end
def apply_shape_arg_conversions!
if @args.size > 1 && (['polygon', 'polyline'].include?(@name))
@args[0] = @args.dup
@args[1..-1] = []
end
if @name == 'image'
if @args.first.is_a?(::String)
@args[0] = ImageProxy.create(@args[0])
end
if @args.first.is_a?(ImageProxy)
@image = @args[0] = @args[0].swt_image
end
if @args.first.nil?
@image = nil
end
end
if @name == 'text'
if @args[3].is_a?(Symbol) || @args[3].is_a?(::String)
@args[3] = [@args[3]]
end
if @args[3].is_a?(Array)
if @args[3].size == 1 && @args[3].first.is_a?(Array)
@args[3] = @args[3].first
end
@args[3] = SWTProxy[*@args[3]]
end
end
end
def apply_shape_arg_defaults!
if current_parameter_name?(:dest_x) && dest_x.nil?
self.dest_x = :default
elsif parameter_name?(:x) && x.nil?
self.x = :default
end
if current_parameter_name?(:dest_y) && dest_y.nil?
self.dest_y = :default
elsif parameter_name?(:y) && y.nil?
self.y = :default
end
self.width = :default if current_parameter_name?(:width) && width.nil?
self.height = :default if current_parameter_name?(:height) && height.nil?
if @name.include?('rectangle') && round? && @args.size.between?(4, 5)
(6 - @args.size).times {@args << 60}
elsif @name.include?('rectangle') && gradient? && @args.size == 4
set_attribute('vertical', true, redraw: false)
elsif (@name.include?('text') || @name.include?('string')) && !@properties.keys.map(&:to_s).include?('background') && @args.size < 4
set_attribute('is_transparent', true, redraw: false)
end
if @name.include?('image')
@drawable.requires_shape_disposal = true
end
end
# Tolerates shape extra args added by user by mistake
# (e.g. happens when switching from round rectangle to a standard one without removing all extra args)
def tolerate_shape_extra_args!
the_java_method_arg_count = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.select do |m|
m.name == @method_name.camelcase(:lower)
end.map(&:parameter_types).map(&:size).max
if the_java_method_arg_count && @args.to_a.size > the_java_method_arg_count
@args[the_java_method_arg_count..-1] = []
end
end
def amend_method_name_options_based_on_properties!
@original_method_name = @method_name
return if @name == 'point'
if (@name != 'text' && @name != 'string' && has_some_background? && !has_some_foreground?) || (@name == 'path' && has_some_background?)
@options[:fill] = true
elsif !has_some_background? && has_some_foreground?
@options[:fill] = false
elsif @name == 'rectangle' && has_some_background? && has_some_foreground?
@options[:fill] = true
@options[:gradient] = true
end
if @name == 'rectangle' && @args.size > 4 && @args.last.is_a?(Numeric)
@options[:round] = true
end
@method_name = self.class.method_name(@name, @options)
end
# 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)
def parameter_names
[:x, :y, :width, :height]
end
# subclasses may override to specify location parameter names if different from x and y (e.g. all polygon points are location parameters)
# used in calculating movement changes
def location_parameter_names
[:x, :y]
end
def possible_parameter_names
parameter_names
end
def parameter_name?(attribute_name)
possible_parameter_names.map(&:to_s).include?(ruby_attribute_getter(attribute_name))
end
def current_parameter_name?(attribute_name)
parameter_names.include?(attribute_name.to_s.to_sym)
end
def parameter_index(attribute_name)
parameter_names.index(attribute_name.to_s.to_sym)
end
def get_parameter_attribute(attribute_name)
@args[parameter_index(ruby_attribute_getter(attribute_name))]
end
def set_parameter_attribute(attribute_name, *args)
@args[parameter_index(ruby_attribute_getter(attribute_name))] = args.size == 1 ? args.first : args
end
def has_attribute?(attribute_name, *args)
attribute_name == 'data' or
self.class.gc_instance_methods.include?(attribute_setter(attribute_name)) or
parameter_name?(attribute_name) or
(respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
end
def set_attribute(attribute_name, *args)
options = args.last if args.last.is_a?(Hash)
args.pop if !options.nil? && !options[:redraw].nil?
options ||= {}
perform_redraw = @perform_redraw
perform_redraw = options[:redraw] if perform_redraw.nil? && !options.nil?
perform_redraw ||= true
property_change = nil
ruby_attribute_getter_name = ruby_attribute_getter(attribute_name)
ruby_attribute_setter_name = ruby_attribute_setter(attribute_name)
if attribute_name == 'data'
set_data(*args)
elsif parameter_name?(attribute_name)
return if ruby_attribute_getter_name == (args.size == 1 ? args.first : args)
set_parameter_attribute(ruby_attribute_getter_name, *args)
elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter_name, super: true))
return if self.send(ruby_attribute_getter_name) == (args.size == 1 ? args.first : args)
self.send(ruby_attribute_setter_name, *args)
else
# TODO consider this optimization of preconverting args (removing conversion from other methods) to reject equal args
args = apply_property_arg_conversions(ruby_attribute_getter_name, args)
return if @properties[ruby_attribute_getter_name] == args
new_property = !@properties.keys.include?(ruby_attribute_getter_name)
@properties[ruby_attribute_getter_name] = args
amend_method_name_options_based_on_properties! if @content_added && new_property
property_change = true
calculated_paint_args_changed! if container?
end
if @content_added && perform_redraw && !drawable.is_disposed
redrawn = false
unless property_change
calculated_paint_args_changed!(children: false)
if is_a?(PathSegment)
root_path&.calculated_path_args = @calculated_path_args = false
calculated_args_changed!
root_path&.calculated_args_changed!
end
if location_parameter_names.map(&:to_s).include?(ruby_attribute_getter_name)
calculated_args_changed!(children: true)
redrawn = parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
end
if ['width', 'height'].include?(ruby_attribute_getter_name)
redrawn = calculated_args_changed_for_defaults!
end
end
# TODO consider redrawing an image proxy's gc in the future
# TODO consider ensuring only a single redraw happens for a hierarchy of nested shapes
drawable.redraw if !redrawn && !drawable.is_a?(ImageProxy)
end
end
def get_attribute(attribute_name)
if parameter_name?(attribute_name)
arg_index = parameter_index(attribute_name)
@args[arg_index] if arg_index
elsif (respond_to?(attribute_name, super: true) and respond_to?(ruby_attribute_setter(attribute_name), super: true))
self.send(attribute_name)
else
@properties[attribute_name.to_s]
end
end
def can_handle_observation_request?(observation_request)
observation_request = observation_request.to_s
observation_request.start_with?('on_') &&
(
observation_request == 'on_shape_disposed' ||
(
drawable.respond_to?(:can_handle_observation_request?) &&
drawable.can_handle_observation_request?(observation_request)
)
)
end
def handle_observation_request(observation_request, source_observation_request: nil, &block)
if observation_request.to_s == 'on_shape_disposed'
@on_shape_disposed_handlers ||= []
@on_shape_disposed_handlers << block
return ShapeListenerProxy.new(shape: self, drawable: drawable, shape_listener_block: block, observation_request: 'on_shape_disposed')
end
if observation_request == 'on_drop'
drawable.drop_shapes << self
handle_observation_request('on_mouse_up', source_observation_request: 'on_drop') do |event|
if Shape.dragging && include_with_children?(event.x, event.y, except_shape: Shape.dragged_shape)
drop_event = DropEvent.new(
doit: true,
dragged_shape: Shape.dragged_shape,
dragged_shape_original_x: Shape.dragged_shape_original_x,
dragged_shape_original_y: Shape.dragged_shape_original_y,
dragging_x: Shape.dragging_x,
dragging_y: Shape.dragging_y,
drop_shapes: drawable.drop_shapes,
x: event.x,
y: event.y
)
begin
block.call(drop_event)
rescue => e
Glimmer::Config.logger.error {e.full_message}
ensure
if drop_event.doit
Shape.dragging = false
Shape.dragged_shape = nil
elsif Shape.drop_shape_handling_count.to_i >= drawable.drop_shapes.count && Shape.dragged_shape
cancel_dragging!
end
end
else
if Shape.drop_shape_handling_count.to_i >= drawable.drop_shapes.count
cancel_dragging!
end
end
end
else
widget_listener_proxy = nil
shape_block = lambda do |event|
if @disposed
widget_listener_proxy.deregister
@widget_listener_proxies.delete(widget_listener_proxy)
else
Shape.drop_shape_handling_count += 1 if source_observation_request == 'on_drop' && event.x == Shape.dragging_x && event.y == Shape.dragging_y
if !event.respond_to?(:x) || !event.respond_to?(:y) || include_with_children?(event.x, event.y)
block.call(event)
elsif source_observation_request == 'on_drop' && Shape.drop_shape_handling_count.to_i >= drawable.drop_shapes.count
cancel_dragging!
end
end
end
widget_listener_proxy = drawable.respond_to?(:handle_observation_request) && drawable.handle_observation_request(observation_request, &shape_block)
@widget_listener_proxies ||= []
if widget_listener_proxy
@widget_listener_proxies << widget_listener_proxy
ShapeListenerProxy.new(shape: self, drawable: drawable, shape_listener_block: shape_block, observation_request: source_observation_request || observation_request, widget_listener_proxy: widget_listener_proxy)
end
end
end
def remove_shape_disposed_listener(listener_block)
@on_shape_disposed_handlers.delete(listener_block)
end
# Sets data just like SWT widgets
def set_data(key=nil, value)
@data ||= {}
@data[key] = value
end
alias setData set_data # for compatibility with SWT APIs
# Gets data just like SWT widgets
def get_data(key=nil)
@data ||= {}
@data[key]
end
alias getData get_data # for compatibility with SWT APIs
alias data get_data # for compatibility with SWT APIs
def shell_proxy
drawable.shell_proxy
end
def respond_to?(method_name, *args, &block)
options = args.last if args.last.is_a?(Hash)
super_invocation = options && options[:super]
if !super_invocation && has_attribute?(method_name)
true
else
result = super
return true if result
can_handle_observation_request?(method_name)
end
end
def method_missing(method_name, *args, &block)
if method_name.to_s.end_with?('=')
set_attribute(method_name, *args)
elsif has_attribute?(method_name)
get_attribute(method_name)
elsif block && can_handle_observation_request?(method_name)
handle_observation_request(method_name, &block)
else
super
end
end
def deregister_drag_listeners
@on_drag_detected&.deregister
@on_drag_detected = nil
@drawable_on_mouse_move&.deregister
@drawable_on_mouse_move = nil
@drawable_on_mouse_up&.deregister
@drawable_on_mouse_up = nil
end
# Enables dragging for drag and move within parent widget
# drag_and_move and drag_source are mutually exclusive
# if shape had drag_source true, it is disabled by deregistering its listeners
def drag_and_move=(drag_and_move_value)
deregister_drag_listeners if @drag_source
drag_and_move_old_value = @drag_and_move
@drag_and_move = drag_and_move_value
if @drag_and_move && !drag_and_move_old_value
@on_drag_detected ||= handle_observation_request('on_drag_detected') do |event|
Shape.dragging = true
Shape.dragging_x = event.x
Shape.dragging_y = event.y
Shape.drop_shape_handling_count = 0
Shape.dragged_shape = self
Shape.dragged_shape_original_x = x
Shape.dragged_shape_original_y = y
end
@drawable_on_mouse_move ||= drawable.handle_observation_request('on_mouse_move') do |event|
if Shape.dragging && Shape.dragged_shape.equal?(self)
Shape.dragged_shape.move_by((event.x - Shape.dragging_x), (event.y - Shape.dragging_y))
Shape.dragging_x = event.x
Shape.dragging_y = event.y
end
end
@drawable_on_mouse_up ||= drawable.handle_observation_request('on_mouse_up') do |event|
if Shape.dragging && Shape.dragged_shape.equal?(self)
Shape.dragging = false
end
end
elsif !@drag_and_move && drag_and_move_old_value
deregister_drag_listeners
end
end
def drag_and_move
@drag_and_move
end
# Makes shape a drag source (enables dragging for drag and drop)
# drag_source and drag_and_move are mutually exclusive
# if shape had drag_and_move true, it is disabled by deregistering its listeners
def drag_source=(drag_source_value)
deregister_drag_listeners if @drag_and_move
drag_source_old_value = @drag_source
@drag_source = drag_source_value
if @drag_source && !drag_source_old_value
@on_drag_detected ||= handle_observation_request('on_drag_detected') do |event|
Shape.dragging = true
Shape.dragging_x = event.x
Shape.dragging_y = event.y
Shape.drop_shape_handling_count = 0
Shape.dragged_shape = self
Shape.dragged_shape_original_x = x
Shape.dragged_shape_original_y = y
end
@drawable_on_mouse_move ||= drawable.handle_observation_request('on_mouse_move') do |event|
if Shape.dragging && Shape.dragged_shape.equal?(self)
Shape.dragged_shape.move_by((event.x - Shape.dragging_x), (event.y - Shape.dragging_y))
Shape.dragging_x = event.x
Shape.dragging_y = event.y
end
end
@drawable_on_mouse_up ||= drawable.handle_observation_request('on_mouse_up') do |event|
cancel_dragging! if Shape.dragging && Shape.dragged_shape.equal?(self) && !any_potential_drop_targets?(event.x, event.y)
end
elsif !@drag_source && drag_source_old_value
deregister_drag_listeners
end
end
def drag_source
@drag_source
end
def any_potential_drop_targets?(x, y)
drawable.drop_shapes.any? { |shape| shape.include_with_children?(x, y, except_shape: Shape.dragged_shape) }
end
def cancel_dragging!
Shape.dragging = false
if Shape.dragged_shape
Shape.dragged_shape.x = Shape.dragged_shape_original_x
Shape.dragged_shape.y = Shape.dragged_shape_original_y
Shape.dragged_shape = nil
end
end
def pattern(*args, type: nil)
instance_variable_name = "@#{type}_pattern"
the_pattern = instance_variable_get(instance_variable_name)
if the_pattern.nil? || the_pattern.is_disposed
the_pattern = self.class.pattern(*args)
end
the_pattern
end
def pattern_args(type: nil)
@pattern_args && @pattern_args[type.to_s.capitalize]
end
def background_pattern_args
pattern_args(type: 'background')
end
def foreground_pattern_args
pattern_args(type: 'foreground')
end
def dispose(dispose_images: true, dispose_patterns: true, redraw: true)
return if drawable.respond_to?(:shell_proxy) && drawable&.shell_proxy&.last_shell_closing?
return if @disposed
@disposed = true
deregister_drag_listeners
@widget_listener_proxies&.each(&:deregister)
if dispose_patterns
@background_pattern&.dispose
@background_pattern = nil
@foreground_pattern&.dispose
@foreground_pattern = nil
end
if dispose_images
@image&.dispose
@image = nil
end
shapes.dup.each { |shape| shape.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns, redraw: false) }
@parent.shapes.delete(self)
drawable.drop_shapes.delete(self)
@on_shape_disposed_handlers&.each {|handler| handler.call(self)} # TODO pass a custom event argument to handler
drawable.redraw if redraw && !drawable.is_a?(ImageProxy)
end
# clear all shapes
# indicate whether to dispose images, dispose patterns, and redraw after clearing shapes.
# redraw can be `:all` or `true` to mean redraw after all shapes are disposed, `:each` to mean redraw after each shape is disposed, or `false` to avoid redraw altogether
def clear_shapes(dispose_images: true, dispose_patterns: true, redraw: :all)
if redraw == true || redraw == :all
shapes.dup.each {|shape| shape.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns, redraw: false) }
drawable.redraw if redraw && !drawable.is_a?(ImageProxy)
elsif redraw == :each
shapes.dup.each {|shape| shape.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns, redraw: true) }
else
shapes.dup.each {|shape| shape.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns, redraw: false) }
end
end
alias dispose_shapes clear_shapes
# Indicate if this is a container shape (meaning a shape bag that is just there to contain nested shapes, but doesn't render anything of its own)
def container?
@name == 'shape'
end
# Indicate if this is a composite shape (meaning a shape that contains nested shapes like a rectangle with ovals inside it)
def composite?
!shapes.empty?
end
# ordered from closest to farthest parent
def parent_shapes
if @parent_shapes.nil?
if parent.is_a?(Drawable)
@parent_shapes = []
else
@parent_shapes = parent.parent_shapes + [parent]
end
end
@parent_shapes
end
# ordered from closest to farthest parent
def parent_shape_containers
if @parent_shape_containers.nil?
if parent.is_a?(Drawable)
@parent_shape_containers = []
elsif !parent.container?
@parent_shape_containers = parent.parent_shape_containers
else
@parent_shape_containers = parent.parent_shape_containers + [parent]
end
end
@parent_shape_containers
end
# ordered from closest to farthest parent
def parent_shape_composites
if @parent_shape_composites.nil?
if parent.is_a?(Drawable)
@parent_shape_composites = []
elsif !parent.container?
@parent_shape_composites = parent.parent_shape_composites
else
@parent_shape_composites = parent.parent_shape_composites + [parent]
end
end
@parent_shape_composites
end
def convert_properties!
if @properties != @converted_properties
@properties.each do |property, args|
@properties[property] = apply_property_arg_conversions(property, args)
end
@converted_properties = @properties.dup
end
end
def converted_properties
convert_properties!
@properties
end
def all_parent_properties
@all_parent_properties ||= parent_shape_containers.reverse.reduce({}) do |all_properties, parent_shape|
all_properties.merge(parent_shape.converted_properties)
end
end
def paint(paint_event)
paint_children(paint_event) if default_width? || default_height?
paint_self(paint_event)
# re-paint children from scratch in the special case of pre-calculating parent width/height to re-center within new parent dimensions
shapes.each(&:calculated_args_changed!) if default_width? || default_height?
paint_children(paint_event)
rescue => e
Glimmer::Config.logger.error {"Error encountered in painting shape (#{self.inspect}) with calculated args (#{@calculated_args}) and args (#{@args})"}
Glimmer::Config.logger.error {e.full_message}
end
def paint_self(paint_event)
@painting = true
unless container?
calculate_paint_args!
@original_gc_properties = {} # this stores GC properties before making calls to updates TODO avoid using in pixel graphics
@properties.each do |property, args|
method_name = attribute_setter(property)
@original_gc_properties[method_name] = paint_event.gc.send(method_name.sub('set', 'get')) rescue nil
paint_event.gc.send(method_name, *args)
if property == 'transform' && args.first.is_a?(TransformProxy)
args.first.swt_transform.dispose
end
end
ensure_extent(paint_event)
end
@calculated_args ||= calculate_args!
unless container?
# paint unless parent's calculated args are not calculated yet, meaning it is about to get painted and trigger a paint on this child anyways
passed_args = @calculated_args.dup
passed_args[0] = passed_args[0].to_java(:int) if @method_name == 'drawPolyline' # a weird fix needed for Linux JRuby 9.3.1.0 only (seems to lack a feature from Mac JRuby that automatically converts to appropriate java array type)
paint_event.gc.send(@method_name, *passed_args) unless (parent.is_a?(Shape) && !parent.calculated_args?)
@original_gc_properties.each do |method_name, value|
paint_event.gc.send(method_name, value)
end
end
@painting = false
rescue => e
Glimmer::Config.logger.error {"Error encountered in painting shape (#{self.inspect}) with method (#{@method_name}) calculated args (#{@calculated_args}) and args (#{@args})"}
Glimmer::Config.logger.error {e.full_message}
ensure
@painting = false
end
def paint_children(paint_event)
shapes.to_a.each do |shape|
shape.paint(paint_event)
end
end
def ensure_extent(paint_event)
old_extent = @extent
old_extent_args = @extent_args
if ['text', 'string'].include?(@name)
extent_args = [string]
extent_flags = SWTProxy[:draw_transparent, :draw_delimiter] if current_parameter_name?(:is_transparent) && is_transparent
extent_flags = flags if current_parameter_name?(:flags)
extent_args << extent_flags unless extent_flags.nil?
self.extent = paint_event.gc.send("#{@name}Extent", *extent_args)
@extent_args = extent_args
end
# comparing extent_args with old ones ensures that if content changes, calculated_args_changed! is called
if !@extent.nil? && (old_extent&.x != @extent&.x || old_extent&.y != @extent&.y || @extent_args != old_extent_args)
calculated_args_changed!
parent.calculated_args_changed_for_defaults! if parent.is_a?(Shape)
end
end
def expanded_shapes(except_shape: nil)
if shapes.to_a.any?
shapes.map do |shape|
shape.equal?(except_shape) ? [] : ([shape] + shape.expanded_shapes(except_shape: except_shape))
end.flatten
else
[]
end
end
def calculated_args_changed!(children: true)
@calculated_args = nil
shapes.each(&:calculated_args_changed!) if children
end
def calculated_paint_args_changed!(children: true)
@calculated_paint_args = nil
@all_parent_properties = nil
shapes.each(&:calculated_paint_args_changed!) if children
end
# Notifies object that calculated args changed for defaults. Returns true if redrawing and false otherwise.
def calculated_args_changed_for_defaults!
has_default_dimensions = default_width? || default_height?
parent_calculated_args_changed_for_defaults = has_default_dimensions
calculated_args_changed!(children: false) if default_x? || default_y? || has_default_dimensions
if has_default_dimensions && parent.is_a?(Shape)
parent.calculated_args_changed_for_defaults!
elsif @content_added && !drawable.is_disposed
# TODO consider optimizing in the future if needed by ensuring one redraw for all parents in the hierarchy at the end instead of doing one per parent that needs it
if !@painting && !drawable.is_a?(ImageProxy)
drawable.redraw
return true
end
end
false
end
def calculated_args?
!!@calculated_args
end
# args translated to absolute coordinates
def calculate_args!
return @args if parent.is_a?(Drawable) && !default_x? && !default_y? && !default_width? && !default_height? && !max_width? && !max_height?
calculated_args_dependencies = [
x,
y,
parent.is_a?(Shape) && parent.absolute_x,
parent.is_a?(Shape) && parent.absolute_y,
default_width? && default_width,
default_width? && width_delta,
default_height? && default_height,
default_height? && height_delta,
max_width? && max_width,
max_width? && width_delta,
max_height? && max_height,
max_height? && height_delta,
default_x? && default_x,
default_x? && x_delta,
default_y? && default_y,
default_y? && y_delta,
(['text', 'string'].include?(@name) && string),
]
if calculated_args_dependencies != @calculated_args_dependencies
# avoid recalculating values again
x, y, parent_absolute_x, parent_absolute_y, default_width, default_width_delta, default_height, default_height_delta, max_width, max_width_delta, max_height, max_height_delta, default_x, default_x_delta, default_y, default_y_delta, string = @calculated_args_dependencies = calculated_args_dependencies
# Note: Must set x and move_by because not all shapes have a real x and some must translate all their points with move_by
# TODO change that by setting a bounding box for all shapes with a calculated top-left x, y and
# a setter that does the moving inside them instead so that I could rely on absolute_x and absolute_y
# here to get the job done of calculating absolute args
@perform_redraw = false
original_x = nil
original_y = nil
original_width = nil
original_height = nil
if parent.is_a?(Shape)
@parent_absolute_x = parent_absolute_x
@parent_absolute_y = parent_absolute_y
end
if default_width?
original_width = width
self.width = default_width + default_width_delta
end
if default_height?
original_height = height
self.height = default_height + default_height_delta
end
if max_width?
original_width = width
self.width = max_width + max_width_delta
end
if max_height?
original_height = height
self.height = max_height + max_height_delta
end
if default_x?
original_x = x
self.x = default_x + default_x_delta
end
if default_y?
original_y = y
self.y = default_y + default_y_delta
end
if parent.is_a?(Shape)
move_by(@parent_absolute_x, @parent_absolute_y)
@result_calculated_args = @args.clone
move_by(-1*@parent_absolute_x, -1*@parent_absolute_y)
else
@result_calculated_args = @args.clone
end
if original_x
self.x = original_x
end
if original_y
self.y = original_y
end
if original_width
self.width = original_width
end
if original_height
self.height = original_height
end
@perform_redraw = true
end
@result_calculated_args
end
def default_x?
return false unless current_parameter_name?(:x)
x = self.x
x.nil? || x.to_s == 'default' || (x.is_a?(Array) && x.first.to_s == 'default')
end
def default_y?
return false unless current_parameter_name?(:y)
y = self.y
y.nil? || y.to_s == 'default' || (y.is_a?(Array) && y.first.to_s == 'default')
end
def default_width?
return false unless current_parameter_name?(:width)
width = self.width
(width.nil? || width == :default || width == 'default' || (width.is_a?(Array) && (width.first.to_s == :default || width.first.to_s == 'default')))
end
def default_height?
return false unless current_parameter_name?(:height)
height = self.height
(height.nil? || height == :default || height == 'default' || (height.is_a?(Array) && (height.first.to_s == :default || height.first.to_s == 'default')))
end
def max_width?
return false unless current_parameter_name?(:width)
width = self.width
(width.nil? || width.to_s == 'max' || (width.is_a?(Array) && width.first.to_s == 'max'))
end
def max_height?
return false unless current_parameter_name?(:height)
height = self.height
(height.nil? || height.to_s == 'max' || (height.is_a?(Array) && height.first.to_s == 'max'))
end
def default_x
default_x_dependencies = [parent.size.x, size.x, parent.is_a?(Shape) && parent.irregular? && parent.bounds.x, parent.is_a?(Shape) && parent.irregular? && parent.absolute_x]
if default_x_dependencies != @default_x_dependencies
@default_x_dependencies = default_x_dependencies
result = ((parent.size.x - size.x) / 2)
result += parent.bounds.x - parent.absolute_x if parent.is_a?(Shape) && parent.irregular?
@default_x = result
end
@default_x
end
def default_y
default_y_dependencies = [parent.size.y, size.y, parent.is_a?(Shape) && parent.irregular? && parent.bounds.y, parent.is_a?(Shape) && parent.irregular? && parent.absolute_y]
if default_y_dependencies != @default_y_dependencies
result = ((parent.size.y - size.y) / 2)
result += parent.bounds.y - parent.absolute_y if parent.is_a?(Shape) && parent.irregular?
@default_y = result
end
@default_y
end
# right-most x coordinate in this shape (adding up its width and location)
def x_end
x_end_dependencies = [calculated_width, default_x?, !default_x? && x]
if x_end_dependencies != @x_end_dependencies
# avoid recalculation of dependencies
calculated_width, is_default_x, x = @x_end_dependencies = x_end_dependencies
shape_width = calculated_width.to_f
shape_x = is_default_x ? 0 : x.to_f
@x_end = shape_x + shape_width
end
@x_end
end
# bottom-most y coordinate in this shape (adding up its height and location)
def y_end
y_end_dependencies = [calculated_height, default_y?, !default_y? && y]
if y_end_dependencies != @y_end_dependencies
# avoid recalculation of dependencies
calculated_height, is_default_y, y = @y_end_dependencies = y_end_dependencies
shape_height = calculated_height.to_f
shape_y = is_default_y ? 0 : y.to_f
@y_end = shape_y + shape_height
end
@y_end
end
def default_width
default_width_dependencies = [shapes.empty? && max_width, shapes.size == 1 && shapes.first.max_width? && parent.size.x, shapes.size >= 1 && !shapes.first.max_width? && shapes.map {|s| s.max_width? ? 0 : s.x_end}]
if default_width_dependencies != @default_width_dependencies
# Do not repeat calculations
max_width, parent_size_x, x_ends = @default_width_dependencies = default_width_dependencies
@default_width = if shapes.empty?
max_width
elsif shapes.size == 1 && shapes.first.max_width?
parent_size_x
else
x_ends.max.to_f
end
end
@default_width
end
def default_height
default_height_dependencies = [shapes.empty? && max_height, shapes.size == 1 && shapes.first.max_height? && parent.size.y, shapes.size >= 1 && !shapes.first.max_height? && shapes.map {|s| s.max_height? ? 0 : s.y_end}]
if default_height_dependencies != @default_height_dependencies
# Do not repeat calculations
max_height, parent_size_y, y_ends = @default_height_dependencies = default_height_dependencies
@default_height = if shapes.empty?
max_height
elsif shapes.size == 1 && shapes.first.max_height?
parent_size_y
else
y_ends.max.to_f
end
end
@default_height
end
def max_width
max_width_dependencies = [parent.is_a?(Drawable) && parent.size.x, !parent.is_a?(Drawable) && parent.calculated_width]
if max_width_dependencies != @max_width_dependencies
# do not repeat calculations
parent_size_x, parent_calculated_width = @max_width_dependencies = max_width_dependencies
@max_width = parent.is_a?(Drawable) ? parent_size_x : parent_calculated_width
end
@max_width
end
def max_height
max_height_dependencies = [parent.is_a?(Drawable) && parent.size.y, !parent.is_a?(Drawable) && parent.calculated_height]
if max_height_dependencies != @max_height_dependencies
# do not repeat calculations
parent_size_y, parent_calculated_height = @max_height_dependencies = max_height_dependencies
@max_height = parent.is_a?(Drawable) ? parent_size_y : parent_calculated_height
end
@max_height
end
def calculated_width
calculated_width_dependencies = [width, default_width? && (default_width + width_delta), max_width? && (max_width + width_delta)]
if calculated_width_dependencies != @calculated_width_dependencies
@calculated_width_dependencies = calculated_width_dependencies
result_width = width
result_width = (default_width + width_delta) if default_width?
result_width = (max_width + width_delta) if max_width?
@calculated_width = result_width
end
@calculated_width
end
def calculated_height
calculated_height_dependencies = [height, default_height? && (default_height + height_delta), max_height? && (max_height + height_delta)]
if calculated_height_dependencies != @calculated_height_dependencies
@calculated_height_dependencies = calculated_height_dependencies
result_height = height
result_height = (default_height + height_delta) if default_height?
result_height = (max_height + height_delta) if max_height?
@calculated_height = result_height
end
@calculated_height
end
def x_delta
return 0 unless x.is_a?(Array) && default_x?
x[1].to_f
end
def y_delta
return 0 unless y.is_a?(Array) && default_y?
y[1].to_f
end
def width_delta
return 0 unless width.is_a?(Array) && (default_width? || max_width?)
width[1].to_f
end
def height_delta
return 0 unless height.is_a?(Array) && (default_height? || max_height?)
height[1].to_f
end
def x_delta=(delta)
return unless default_x?
symbol = x.is_a?(Array) ? x.first : x
self.x = [symbol, delta]
end
def y_delta=(delta)
return unless default_y?
symbol = y.is_a?(Array) ? y.first : y
self.y = [symbol, delta]
end
def width_delta=(delta)
return unless default_width?
symbol = width.is_a?(Array) ? width.first : width
self.width = [symbol, delta]
end
def height_delta=(delta)
return unless default_height?
symbol = height.is_a?(Array) ? height.first : height
self.height = [symbol, delta]
end
def calculated_x
calculated_x_dependencies = [default_x? && default_x, !default_x? && self.x, self.x_delta]
if calculated_x_dependencies != @calculated_x_dependencies
default_x, x, x_delta = @calculated_x_dependencies = calculated_x_dependencies
result = default_x? ? default_x : x
result += x_delta
@calculated_x = result
end
@calculated_x
end
def calculated_y
calculated_y_dependencies = [default_y? && default_y, !default_y? && self.y, self.y_delta]
if calculated_y_dependencies != @calculated_y_dependencies
default_y, y, y_delta = @calculated_y_dependencies = calculated_y_dependencies
result = default_y? ? default_y : y
result += y_delta
@calculated_y = result
end
@calculated_y
end
def absolute_x
absolute_x_dependencies = [calculated_x, parent.is_a?(Shape) && parent.absolute_x]
if absolute_x_dependencies != @absolute_x_dependencies
# do not repeat calculations
calculated_x, parent_absolute_x = @absolute_x_dependencies = absolute_x_dependencies
x = calculated_x
@absolute_x = if parent.is_a?(Shape)
parent_absolute_x + x
else
x
end
end
@absolute_x
end
def absolute_y
absolute_y_dependencies = [calculated_y, parent.is_a?(Shape) && parent.absolute_y]
if absolute_y_dependencies != @absolute_y_dependencies
calculated_y, parent_absolute_y = @absolute_y_dependencies = absolute_y_dependencies
y = calculated_y
@absolute_y = if parent.is_a?(Shape)
parent_absolute_y + y
else
y
end
end
@absolute_y
end
# Overriding inspect to avoid printing very long nested shape hierarchies (recurses onces only)
def inspect(recursive: 1, calculated: false, args: true, properties: true, calculated_args: false)
recurse = recursive == true || recursive.is_a?(Integer) && recursive.to_i > 0
recursive = [recursive -= 1, 0].max if recursive.is_a?(Integer)
args_string = " args=#{@args.inspect}" if args
properties_string = " properties=#{@properties.inspect}}" if properties
calculated_args_string = " calculated_args=#{@calculated_args.inspect}" if calculated_args
calculated_string = " absolute_x=#{absolute_x} absolute_y=#{absolute_y} calculated_width=#{calculated_width} calculated_height=#{calculated_height}" if calculated
recursive_string = " shapes=#{@shapes.map {|s| s.inspect(recursive: recursive, calculated: calculated, args: args, properties: properties)}}" if recurse
"#<#{self.class.name}:0x#{self.hash.to_s(16)}#{args_string}#{properties_string}#{calculated_args_string}#{calculated_string}#{recursive_string}>"
rescue => e
Glimmer::Config.logger.error { e.full_message }
"#<#{self.class.name}:0x#{self.hash.to_s(16)}"
end
def calculate_paint_args!
unless @calculated_paint_args
if @name == 'pixel'
@name = 'point'
# optimized performance calculation for pixel points
if !@properties[:foreground].is_a?(org.eclipse.swt.graphics.Color)
if @properties[:foreground].is_a?(Array)
@properties[:foreground] = ColorProxy.new(@properties[:foreground], ensure_bounds: false)
end
if @properties[:foreground].is_a?(Symbol) || @properties[:foreground].is_a?(::String)
@properties[:foreground] = ColorProxy.new(@properties[:foreground], ensure_bounds: false)
end
if @properties[:foreground].is_a?(ColorProxy)
@properties[:foreground] = @properties[:foreground].swt_color
end
end
else
@original_properties ||= @properties
@properties = all_parent_properties.merge(@original_properties)
@properties['background'] = [@drawable.background] if fill? && !has_some_background?
@properties['foreground'] = [@drawable.foreground] if @drawable.respond_to?(:foreground) && draw? && !has_some_foreground?
# TODO regarding alpha, make sure to reset it to parent stored alpha once we allow setting shape properties on parents directly without shapes
@properties['font'] = [@drawable.font] if @drawable.respond_to?(:font) && @name == 'text' && draw? && !@properties.keys.map(&:to_s).include?('font')
# TODO regarding transform, make sure to reset it to parent stored transform once we allow setting shape properties on parents directly without shapes
# Also do that with all future-added properties
convert_properties!
apply_shape_arg_conversions!
apply_shape_arg_defaults!
tolerate_shape_extra_args!
@calculated_paint_args = true
end
end
end
end
end
end
end
Dir[File.expand_path(File.join(__dir__, 'shape', '**', '*.rb'))].each {|shape_file| require(shape_file)}