lib/glimmer/swt/custom/shape/path.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/custom/shape'
require 'glimmer/swt/custom/shape/path_segment'
require 'glimmer/swt/swt_proxy'
require 'glimmer/swt/display_proxy'
require 'glimmer/swt/color_proxy'
require 'glimmer/swt/font_proxy'
require 'glimmer/swt/display_proxy'
module Glimmer
module SWT
module Custom
class Shape
# Represents a path 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
# swt_path is not guaranteed to have any object in it till after rendering
class Path < Shape
include PathSegment # a path may behave as a path segment in another path
attr_reader :swt_path, :path_segments
attr_accessor :calculated_path_args
def initialize(parent, keyword, *args, &property_block)
super
@path_segments = []
@uncalculated_path_segments = []
end
def parameter_names
[:swt_path]
end
def add_shape(shape)
if shape.is_a?(PathSegment)
Glimmer::SWT::DisplayProxy.instance.auto_exec do
@path_segments << shape
@uncalculated_path_segments << shape
end
else
super
end
end
def contain?(x, y)
include?(x, y, filled: true)
end
# checks if drawn or filled rectangle includes the point denoted by x and y (if drawn, it only returns true if point lies on the edge)
def include?(x, y, filled: nil)
x, y = inverse_transform_point(x, y)
filled = filled? if filled.nil?
makeshift_gc = org.eclipse.swt.graphics.GC.new(Glimmer::SWT::DisplayProxy.instance.swt_display)
@swt_path.contains(x.to_f, y.to_f, makeshift_gc, !filled)
end
def irregular?
true
end
def post_dispose_content(path_segment)
Glimmer::SWT::DisplayProxy.instance.auto_exec do
@path_segments.delete(path_segment)
@uncalculated_path_segments = @path_segments.dup
@swt_path&.dispose
@swt_path = nil
@args = []
calculated_args_changed!(children: false)
end
end
def clear
Glimmer::SWT::DisplayProxy.instance.auto_exec do
@path_segments.each { |path_segments| path_segments.class == Path && path_segments.dispose }
@path_segments.clear
@uncalculated_path_segments = @path_segments.dup
@swt_path&.dispose
@swt_path = nil
@args = []
calculated_args_changed!(children: false)
drawable.redraw unless drawable.is_a?(ImageProxy)
end
end
def dispose(dispose_images: true, dispose_patterns: true, redraw: true)
Glimmer::SWT::DisplayProxy.instance.auto_exec do
clear if self.class == Path
super(dispose_images: dispose_images, dispose_patterns: dispose_patterns, redraw: redraw) if (parent.is_a?(Shape) && (!parent.is_a?(PathSegment) || !parent.part_of_path?)) || parent.is_a?(Drawable)
end
end
def calculate_args!
new_swt_path = @swt_path.nil? || !@calculated_paint_args || !@calculated_path_args
if new_swt_path
Glimmer::SWT::DisplayProxy.instance.auto_exec do
@swt_path&.dispose
@swt_path = org.eclipse.swt.graphics.Path.new(Glimmer::SWT::DisplayProxy.instance.swt_display)
@uncalculated_path_segments = @path_segments.dup
end
end
# TODO recreate @swt_path only if one of the children get disposed (must notify parent on dispose)
@args = [@swt_path]
@uncalculated_path_segments.dup.each do |path_segment|
Glimmer::SWT::DisplayProxy.instance.auto_exec do
path_segment.add_to_swt_path(@swt_path)
@uncalculated_path_segments.delete(path_segment)
end
end
@calculated_path_args = true
if new_swt_path
@path_calculated_args = super
else
@path_calculated_args
end
rescue => e
Glimmer::Config.logger.error {e.full_message}
@args
end
def move_by(x_delta, y_delta)
@path_segments.each {|path_segment| path_segment.move_by(x_delta, y_delta)}
end
def bounds
if @path_segments != @bounds_path_segments
@bounds_path_segments = @path_segments
shape_bounds = geometry.getBounds2D
@bounds = org.eclipse.swt.graphics.Rectangle.new(shape_bounds.x, shape_bounds.y, shape_bounds.width, shape_bounds.height)
end
@bounds
end
def size
if @path_segments != @size_path_segments
@size_path_segments = @path_segments
shape_bounds = geometry.getBounds2D
@size = org.eclipse.swt.graphics.Point.new(shape_bounds.width, shape_bounds.height)
end
@size
end
# Logical x coordinate relative to parent
def x
x_value = bounds.x
x_value -= parent.absolute_x if parent.is_a?(Shape)
x_value
end
# Logical y coordinate relative to parent
def y
y_value = bounds.y
y_value -= parent.absolute_y if parent.is_a?(Shape)
y_value
end
def width
size.x
end
def height
size.y
end
def geometry
if @path_segments != @geometry_path_segments
@geometry_path_segments = @path_segments
@geometry = Java::JavaAwtGeom::Path2D::Double.new
@path_segments.each do |path_segment|
path_segment.add_to_geometry(@geometry)
end
end
@geometry
end
def path_segment_method_name
'addPath'
end
def path_segment_args
@args
end
def path_segment_geometry_method_name
if self.class == Path
'append'
else
super
end
end
def path_segment_geometry_args
if self.class == Path
# TODO consider supporting connected true instead of false (2nd arg)
[geometry, false]
else
super
end
end
def eql?(other)
(other.class == Path) && geometry.equals(other && other.respond_to?(:geometry) && other.geometry)
end
alias == eql?
def hash
geometry.hashCode
end
end
end
end
end
end