Glimmer Klondike Solitaire + Canvas Shape Drag & Drop
Tags: ruby, dsl, gui, desktop
Published: 2021-08-01 13:41:37

[Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-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](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#klondike-solitaire)

![Glimmer Klondike Solitaire Jumbo Edition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9mz7zfmrsdyyvh8n6rc.png)
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!](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#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.  


```ruby
# 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:

![Hello Custom Shape](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-hello-custom-shape.png)

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!](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#hello-canvas-drag-and-drop) sample.


```ruby
# 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:

![Hello Canvas Drag and Drop](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-hello-canvas-drag-and-drop.gif)

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](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#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.](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/klondike_solitaire.rb)

![Solitaire](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-klondike-solitaire-played.png)

I am happy to report that thanks to Ruby and [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-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](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/klondike_solitaire.rb).

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

![Glimmer Klondike Solitaire Jumbo Edition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9mz7zfmrsdyyvh8n6rc.png)

Happy [Glimmering](https://github.com/AndyObtiva/glimmer-dsl-swt)!