Glimmer DSL for SWT 4.20.13.x added direct support for Canvas Shape Drag & Drop, which automates drag and drop operations for shapes within a canvas (was possible before, but with manual effort).
Unlike the SWT built-in drag and drop support for widgets, which does not move widgets around, yet changes the mouse cursor during dragging, the drag and drop support for shapes actually enables moving shapes around visually and then dropping them in designated drop target shapes.
This was used in the new sample: Klondike Solitaire
Before diving into how that card game was built, let's explain shape drag and drop a bit.
The lowest building block for drag and drop is listeners (observers), so shape-constrained listener support has been added, which enables declaring SWT events like on_mouse_up
on shapes directly, not just widgets.
Next, you can leverage SWT events like on_drag_detected
, on_mouse_move
, and on_mouse_up
to drag and move shapes without dropping them into other shapes. This is abstracted/automated with the drag_and_move true
property so you would not have to use the listeners directly.
The Hello, Custom Shape! sample has been amended to use the new drag_and_move true
property, which enables simply moving shapes around without caring to drop anywhere specific for consumption.
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#hello-custom-shape
require 'glimmer-dsl-swt'
# Creates a class-based custom shape representing the `stick_figure` keyword by convention
class StickFigure
include Glimmer::UI::CustomShape
options :x, :y, :width, :height
before_body {
@head_width = width*0.2
@head_height = height*0.2
@trunk_height = height*0.4
@extremity_length = height*0.4
}
body {
shape(x + @head_width/2.0 + @extremity_length, y) {
oval(0, 0, @head_width, @head_height)
line(@head_width/2.0, @head_height, @head_width/2.0, @head_height + @trunk_height)
line(@head_width/2.0, @head_height + @trunk_height, @head_width/2.0 + @extremity_length, @head_height + @trunk_height + @extremity_length)
line(@head_width/2.0, @head_height + @trunk_height, @head_width/2.0 - @extremity_length, @head_height + @trunk_height + @extremity_length)
line(@head_width/2.0, @head_height*2, @head_width/2.0 + @extremity_length, @head_height + @trunk_height - @extremity_length)
line(@head_width/2.0, @head_height*2, @head_width/2.0 - @extremity_length, @head_height + @trunk_height - @extremity_length)
}
}
end
class HelloCustomShape
include Glimmer::UI::CustomShell
WIDTH = 220
HEIGHT = 235
body {
shell {
text 'Hello, Custom Shape!'
minimum_size WIDTH, HEIGHT
@canvas = canvas {
background :white
15.times { |n|
x_location = (rand*WIDTH/2).to_i%WIDTH + (rand*15).to_i
y_location = (rand*HEIGHT/2).to_i%HEIGHT + (rand*15).to_i
foreground_color = rgb(rand*255, rand*255, rand*255)
a_stick_figure = stick_figure(x: x_location, y: y_location, width: 35+n*2, height: 35+n*2) {
foreground foreground_color
drag_and_move true
# on mouse click, change color
on_mouse_up do
a_stick_figure.foreground = rgb(rand*255, rand*255, rand*255)
end
}
}
}
}
}
end
HelloCustomShape.launch
Screenshot:
Now, those little stick figures can be dragged around, and when the mouse is released (on_mouse_up
event), they change color.
Next, we need support for being able to drop into a specific target shape that would consume the dragged shape.
This is done by using the drag_source true
property (instead of drag_and_move true
), which expects a drop target. Otherwise, if you drop the shape outside of a drop target, it simply goes back to its original location. This is then used in collaboration with an on_drop
event on the drop target shape to permit a full drag and drop operation.
It is demonstrated in the Hello, Canvas Drag and Drop! sample.
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#hello-canvas-drag-and-drop
require 'glimmer-dsl-swt'
class HelloCanvasDragAndDrop
include Glimmer::UI::CustomShell
body {
shell {
row_layout(:vertical) {
margin_width 0
margin_height 0
fill true
center true
}
text 'Hello, Canvas Drag & Drop!'
label(:center) {
text 'Drag orange balls and drop in the square.'
font height: 16
}
canvas {
layout_data {
width 350
height 350
}
background :white
10.times do |n|
an_oval = oval((rand*300).to_i, (rand*200).to_i, 50, 50) {
background rgb(255, 165, 0)
# declare shape as a drag source, which unlike `drag_and_move true`, it means the shape now
# goes back to original position if not dropped at an on_drop shape target
drag_source true
# unspecified width and height become max width and max height by default
oval(0, 0) {
foreground :black
}
}
end
@drop_square = rectangle(150, 260, 50, 50) {
background :white
# unspecified width and height become max width and max height by default
@drop_square_border = rectangle(0, 0) {
foreground :black
line_width 3
line_style :dash
}
@number_shape = text {
x :default
y :default
string '0'
}
on_mouse_move do
@drop_square_border.foreground = :red if Glimmer::SWT::Custom::Shape.dragging?
end
on_drop do |drop_event|
# drop_event attributes: :x, :y, :dragged_shape, :dragged_shape_original_x, :dragged_shape_original_y, :dragging_x, :dragging_y, :drop_shapes
# drop_event.doit = false # drop event can be cancelled by setting doit attribute to false
ball_count = @number_shape.string.to_i
@number_shape.dispose
@drop_square.content {
@number_shape = text {
x :default
y :default
string (ball_count + 1).to_s
}
}
drop_event.dragged_shape.dispose
end
}
on_mouse_up do
@drop_square_border.foreground = :black
end
}
}
}
end
HelloCanvasDragAndDrop.launch
Screenshot:
The on_drop
event receives a dragged shape, amends the count in the drop target text sub-shape, and then disposes (destroys) the dragged shape it received.
Finally, we put all of this together, and build the Klondike Solitaire game, which requires drag and drop from the dealt/column piles to the column/foundation piles.
You may check the code of the highly-modular MVC-architecture Klondike Solitaire by clicking here.
I am happy to report that thanks to Ruby and Glimmer DSL for SWT, this only took about one week to build.
The tableau is simply a canvas with a dark green background. Playing cards are represented by a custom shape called playing_card (composite shape representing each card by rank and suit). Cards can be arranged in piles starting with the dealing pile (dealing_pile custom shape) and dealt pile (dealt_pile custom shape), moving to the 7 column piles (column_pile custom shape), and ending with the 4 foundation piles (foundation_pile custom shape). As such, playing cards are a drag source (having drag_source true property). The column and foundation piles are drop targets (having on_drop event listeners).
One tricky scenario to deal with was dragging two or more nested cards together from one column pile to another. Thankfully, it was accomplished effortlessly with Glimmer DSL for SWT’s Canvas Shape Drag & Drop support since it automatically moves a composite shape with all its children if they were declared properly underneath their parent.
The rest is for you to check out by looking into the code.
Finally, the game has been extracted as its own standalone external sample application with added jumbo-size ultra-high-resolution playing cards:
https://github.com/AndyObtiva/glimmer_klondike_solitaire
Happy Glimmering!
Top comments (0)