lib/glimmer/swt/custom/drawable.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.
module Glimmer
module SWT
module Custom
# Represents SWT drawable controls (widgets like canvas) and display
module Drawable
attr_accessor :requires_shape_disposal, :image_double_buffered
alias requires_shape_disposal? requires_shape_disposal
alias image_double_buffered? image_double_buffered
include_package 'org.eclipse.swt.graphics'
def shapes
@shapes ||= []
end
def expanded_shapes
@shapes.map do |shape|
[shape] + shape.expanded_shapes
end.flatten
end
def image_buffered_shapes
@image_buffered_shapes ||= []
end
def drop_shapes
@drop_shapes ||= []
end
# TODO add a method like shapes that specifies drawable_properties to be able to adjust properties like transform in between shapes
def shape_at_location(x, y)
expanded_shapes.reverse.detect {|shape| shape.include?(x, y)}
end
def add_shape(shape)
if !@image_double_buffered || shape.args.first == @image_proxy_buffer
shapes << shape
else
image_buffered_shapes << shape
end
end
def clear_shapes(dispose_images: true, dispose_patterns: true)
# Optimize further by having a collection of disposable_shapes independent of shapes, which is much smaller and only has shapes that require disposal (shapes with patterns or image)
shapes.dup.each {|s| s.dispose(dispose_images: dispose_images, dispose_patterns: dispose_patterns) } if requires_shape_disposal?
end
alias dispose_shapes clear_shapes
def paint_pixel_by_pixel(width = nil, height = nil, &each_pixel_color)
if @image_double_buffered
work = lambda do |paint_event|
width ||= swt_drawable.bounds.width
height ||= swt_drawable.bounds.height
@image_proxy_buffer ||= ImageProxy.create_pixel_by_pixel(width, height, &each_pixel_color)
@image_proxy_buffer.shape(self).paint(paint_event)
end
else
work = lambda do |paint_event_or_image|
the_gc = paint_event_or_image.gc
current_foreground = nil
width ||= swt_drawable.bounds.width
height ||= swt_drawable.bounds.height
height.times do |y|
width.times do |x|
new_foreground = each_pixel_color.call(x, y)
new_foreground = Glimmer::SWT::ColorProxy.create(new_foreground, ensure_bounds: false) unless new_foreground.is_a?(ColorProxy) || new_foreground.is_a?(Color)
new_foreground = new_foreground.swt_color if new_foreground.is_a?(Glimmer::SWT::ColorProxy)
the_gc.foreground = current_foreground = new_foreground unless new_foreground == current_foreground
the_gc.draw_point x, y
end
end
end
end
if respond_to?(:gc)
work.call(self)
else
on_swt_paint(&work)
end
end
def swt_drawable
swt_drawable = nil
if respond_to?(:swt_image)
swt_drawable = swt_image
elsif respond_to?(:swt_display)
swt_drawable = swt_display
elsif respond_to?(:swt_widget)
swt_drawable = swt_widget
end
swt_drawable
end
def deregister_shape_painting
unless shell_proxy.last_shell_closing?
@paint_listener_proxy&.deregister
@resize_listener_proxy&.deregister
end
end
def setup_shape_painting
# TODO consider performance optimization relating to order of shape rendering (affecting only further shapes not previous ones)
if @paint_listener_proxy.nil?
shape_painter = lambda do |paint_event|
shape_painting_work = lambda do |paint_event|
paintable_shapes = @image_double_buffered ? image_buffered_shapes : shapes
paintable_shapes.each do |shape|
shape.paint(paint_event)
end
# When dragging, render dragged shape again on top of everything else.
if !@image_double_buffered && Glimmer::SWT::Custom::Shape.dragging?
Glimmer::SWT::Custom::Shape.dragged_shape.paint(paint_event)
end
end
if @image_double_buffered
if @image_proxy_buffer.nil?
swt_image = Image.new(DisplayProxy.instance.swt_display, bounds.width, bounds.height)
@image_proxy_buffer = ImageProxy.new(swt_image: swt_image)
shape_painting_work.call(@image_proxy_buffer)
end
@image_proxy_buffer.shape(self).paint(paint_event)
else
shape_painting_work.call(paint_event)
end
end
# TODO consider making this logic polymorphic (image vs other)
if respond_to?(:swt_image)
shape_painter.call(self) # treat self as paint event since image has its own gc and doesn't do repaints (it's a one time deal for now though could be adjusted in the future.)
else
@paint_listener_proxy = on_swt_paint(&shape_painter)
@resize_listener_proxy = on_swt_Resize { shapes.each(&:calculated_args_changed!) }
end
else
redraw if respond_to?(:redraw) && @finished_add_content && !is_disposed
end
end
alias resetup_shape_painting setup_shape_painting
end
end
end
end