Glimmer DSL for SWT Hello, Canvas Data-Binding! (+ Quad & Cubic)
Tags: ruby, desktop, gui, dsl
Published: 2022-07-15 23:54:07

[Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) v4.24.0.2 has been released with the following changes:

- Extend [Hello, Canvas Data-Binding!](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#hello-canvas-data-binding) to support editing a quadratic Bézier curve and a cubic Bézier curve, and Drag & Drop of endpoints / control points
- Fix issue with data-binding quad & cubic path shape point_array

Below are the updated Hello, Canvas Data-Binding! sample animated screenshot, code, and video tutorial.

## Updated Hello, Canvas Data-Binding! Video Demo:

![hello canvas data binding video](https://github.com/AndyObtiva/glimmer-dsl-swt/raw/master/images/glimmer-hello-canvas-data-binding.gif)

## Updated Hello, Canvas Data-Binding! Code:

```ruby
require 'glimmer-dsl-swt'

class HelloCanvasDataBinding
  class PathShape
    attr_accessor :foreground_red, :foreground_green, :foreground_blue, :line_width_value, :line_style_value
    
    def foreground_value
      [foreground_red, foreground_green, foreground_blue]
    end
    
    def line_style_value_options
      [:solid, :dash, :dot, :dashdot, :dashdotdot]
    end
  end
  
  class LinePathShape < PathShape
    attr_accessor :x1_value, :y1_value, :x2_value, :y2_value
  end
  
  class QuadPathShape < PathShape
    attr_accessor :x1_value, :y1_value, :cx_value, :cy_value, :x2_value, :y2_value
    
    def point_array
      [x1_value, y1_value, cx_value, cy_value, x2_value, y2_value]
    end
  end
  
  class CubicPathShape < PathShape
    attr_accessor :x1_value, :y1_value, :cx1_value, :cy1_value, :cx2_value, :cy2_value, :x2_value, :y2_value
    
    def point_array
      [x1_value, y1_value, cx1_value, cy1_value, cx2_value, cy2_value, x2_value, y2_value]
    end
  end
  
  include Glimmer::GUI::Application # alias for Glimmer::UI::CustomShell / Glimmer::UI::CustomWindow
  
  CANVAS_WIDTH  = 300
  CANVAS_HEIGHT = 300
  
  before_body do
    @line = LinePathShape.new
    @line.x1_value = 5
    @line.y1_value = 5
    @line.x2_value = CANVAS_WIDTH - 5
    @line.y2_value = CANVAS_HEIGHT - 5
    @line.foreground_red = 28
    @line.foreground_green = 128
    @line.foreground_blue = 228
    @line.line_width_value = 3
    @line.line_style_value = :dash
    
    @quad = QuadPathShape.new
    @quad.x1_value = 5
    @quad.y1_value = CANVAS_HEIGHT - 5
    @quad.cx_value = (CANVAS_WIDTH - 10)/2.0
    @quad.cy_value = 5
    @quad.x2_value = CANVAS_WIDTH - 5
    @quad.y2_value = CANVAS_HEIGHT - 5
    @quad.foreground_red = 28
    @quad.foreground_green = 128
    @quad.foreground_blue = 228
    @quad.line_width_value = 3
    @quad.line_style_value = :dot
    
    @cubic = CubicPathShape.new
    @cubic.x1_value = 5
    @cubic.y1_value = (CANVAS_WIDTH - 10)/2.0
    @cubic.cx1_value = (CANVAS_WIDTH - 10)*0.25
    @cubic.cy1_value = (CANVAS_WIDTH - 10)*0.25
    @cubic.cx2_value = (CANVAS_WIDTH - 10)*0.75
    @cubic.cy2_value = (CANVAS_WIDTH - 10)*0.75
    @cubic.x2_value = CANVAS_WIDTH - 5
    @cubic.y2_value = (CANVAS_WIDTH - 10)/2.0
    @cubic.foreground_red = 28
    @cubic.foreground_green = 128
    @cubic.foreground_blue = 228
    @cubic.line_width_value = 3
    @cubic.line_style_value = :dashdot
  end
  
  body {
    shell(:no_resize) {
      text 'Hello, Canvas Data-Binding!'
      
      tab_folder {
        tab_item {
          grid_layout(6, true) {
            margin_width 0
            margin_height 0
            horizontal_spacing 0
            vertical_spacing 0
          }
          text 'Line'
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'x1'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'y1'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@line, :x1_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@line, :y1_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'x2'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'y2'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@line, :x2_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@line, :y2_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground red'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground green'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground blue'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@line, :foreground_red]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@line, :foreground_green]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@line, :foreground_blue]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'line width'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'line style'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum 255
            selection <=> [@line, :line_width_value]
          }
          combo(:read_only) {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            selection <=> [@line, :line_style_value]
          }
          
          @line_canvas = canvas {
            layout_data(:center, :center, false, false) {
              horizontal_span 6
              width_hint CANVAS_WIDTH
              height_hint CANVAS_WIDTH
            }
            
            background :white
            
            line {
              x1         <= [@line, :x1_value]
              y1         <= [@line, :y1_value]
              x2         <= [@line, :x2_value]
              y2         <= [@line, :y2_value]
              foreground <= [@line, :foreground_value, computed_by: [:foreground_red, :foreground_green, :foreground_blue]]
              line_width <= [@line, :line_width_value]
              line_style <= [@line, :line_style_value]
            }
            
            @line_oval1 = oval {
              x          <= [@line, :x1_value, on_read: ->(val) {val - 5}]
              y          <= [@line, :y1_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :black
            }
            
            @line_oval2 = oval {
              x          <= [@line, :x2_value, on_read: ->(val) {val - 5}]
              y          <= [@line, :y2_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :black
            }
            
            on_mouse_down do |mouse_event|
              @selected_shape = @line_canvas.shape_at_location(mouse_event.x, mouse_event.y)
              @selected_shape = nil unless @selected_shape.is_a?(Glimmer::SWT::Custom::Shape::Oval)
              @line_canvas.cursor = :hand if @selected_shape
            end
            
            on_drag_detected do |drag_detect_event|
              @drag_detected = true
              @drag_current_x = drag_detect_event.x
              @drag_current_y = drag_detect_event.y
            end
            
            on_mouse_move do |mouse_event|
              if @drag_detected && @selected_shape
                delta_x = mouse_event.x - @drag_current_x
                delta_y = mouse_event.y - @drag_current_y
                case @selected_shape
                when @line_oval1
                  @line.x1_value += delta_x
                  @line.y1_value += delta_y
                when @line_oval2
                  @line.x2_value += delta_x
                  @line.y2_value += delta_y
                end
                @drag_current_x = mouse_event.x
                @drag_current_y = mouse_event.y
              end
            end
            
            on_mouse_up do |mouse_event|
              @line_canvas.cursor = :arrow
              @drag_detected = false
              @selected_shape = nil
            end
          }
        }
        
        tab_item {
          grid_layout(6, true) {
            margin_width 0
            margin_height 0
            horizontal_spacing 0
            vertical_spacing 0
          }
          text 'Quad'
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'x1'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'y1'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@quad, :x1_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@quad, :y1_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'control x'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'control y'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@quad, :cx_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@quad, :cy_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'x2'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'y2'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@quad, :x2_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@quad, :y2_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground red'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground green'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground blue'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@quad, :foreground_red]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@quad, :foreground_green]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@quad, :foreground_blue]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'line width'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'line style'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum 255
            selection <=> [@quad, :line_width_value]
          }
          combo(:read_only) {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            selection <=> [@quad, :line_style_value]
          }
          
          @quad_canvas = canvas {
            layout_data(:center, :center, false, false) {
              horizontal_span 6
              width_hint CANVAS_WIDTH
              height_hint CANVAS_WIDTH
            }
            
            background :white
            
            path {
              foreground  <= [@quad, :foreground_value, computed_by: [:foreground_red, :foreground_green, :foreground_blue]]
              line_width  <= [@quad, :line_width_value]
              line_style  <= [@quad, :line_style_value]
              
              quad {
                point_array <= [@quad, :point_array, computed_by: [:x1_value, :y1_value, :cx_value, :cy_value, :x2_value, :y2_value]]
              }
            }
            
            @quad_oval1 = oval {
              x          <= [@quad, :x1_value, on_read: ->(val) {val - 5}]
              y          <= [@quad, :y1_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :black
            }
            
            @quad_oval2 = oval {
              x          <= [@quad, :cx_value, on_read: ->(val) {val - 5}]
              y          <= [@quad, :cy_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :dark_gray
            }
            
            @quad_oval3 = oval {
              x          <= [@quad, :x2_value, on_read: ->(val) {val - 5}]
              y          <= [@quad, :y2_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :black
            }
            
            on_mouse_down do |mouse_event|
              @selected_shape = @quad_canvas.shape_at_location(mouse_event.x, mouse_event.y)
              @selected_shape = nil unless @selected_shape.is_a?(Glimmer::SWT::Custom::Shape::Oval)
              @quad_canvas.cursor = :hand if @selected_shape
            end
            
            on_drag_detected do |drag_detect_event|
              @drag_detected = true
              @drag_current_x = drag_detect_event.x
              @drag_current_y = drag_detect_event.y
            end
            
            on_mouse_move do |mouse_event|
              if @drag_detected && @selected_shape
                delta_x = mouse_event.x - @drag_current_x
                delta_y = mouse_event.y - @drag_current_y
                case @selected_shape
                when @quad_oval1
                  @quad.x1_value += delta_x
                  @quad.y1_value += delta_y
                when @quad_oval2
                  @quad.cx_value += delta_x
                  @quad.cy_value += delta_y
                when @quad_oval3
                  @quad.x2_value += delta_x
                  @quad.y2_value += delta_y
                end
                @drag_current_x = mouse_event.x
                @drag_current_y = mouse_event.y
              end
            end
            
            on_mouse_up do |mouse_event|
              @quad_canvas.cursor = :arrow
              @drag_detected = false
              @selected_shape = nil
            end
          }
        }
        
        tab_item {
          grid_layout(6, true) {
            margin_width 0
            margin_height 0
            horizontal_spacing 0
            vertical_spacing 0
          }
          text 'Cubic'
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'x1'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'y1'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@cubic, :x1_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@cubic, :y1_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'control 1 x'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'control 1 y'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@cubic, :cx1_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@cubic, :cy1_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'control 2 x'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'control 2 y'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@cubic, :cx2_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@cubic, :cy2_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'x2'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'y2'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_WIDTH
            increment 3
            selection <=> [@cubic, :x2_value]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum CANVAS_HEIGHT
            increment 3
            selection <=> [@cubic, :y2_value]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground red'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground green'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            text 'foreground blue'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@cubic, :foreground_red]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@cubic, :foreground_green]
          }
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 2
            }
            maximum 255
            increment 10
            selection <=> [@cubic, :foreground_blue]
          }
          
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'line width'
          }
          label {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            text 'line style'
          }
          
          spinner {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            maximum 255
            selection <=> [@cubic, :line_width_value]
          }
          combo(:read_only) {
            layout_data(:fill, :center, false, false) {
              horizontal_span 3
            }
            selection <=> [@cubic, :line_style_value]
          }
          
          @cubic_canvas = canvas {
            layout_data(:center, :center, false, false) {
              horizontal_span 6
              width_hint CANVAS_WIDTH
              height_hint CANVAS_WIDTH
            }
            
            background :white
            
            path {
              foreground  <= [@cubic, :foreground_value, computed_by: [:foreground_red, :foreground_green, :foreground_blue]]
              line_width  <= [@cubic, :line_width_value]
              line_style  <= [@cubic, :line_style_value]
              
              cubic {
                point_array <= [@cubic, :point_array, computed_by: [:x1_value, :y1_value, :cx1_value, :cy1_value, :cx2_value, :cy2_value, :x2_value, :y2_value]]
              }
            }
            
            @cubic_oval1 = oval {
              x          <= [@cubic, :x1_value, on_read: ->(val) {val - 5}]
              y          <= [@cubic, :y1_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :black
            }
            
            @cubic_oval2 = oval {
              x          <= [@cubic, :cx1_value, on_read: ->(val) {val - 5}]
              y          <= [@cubic, :cy1_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :dark_gray
            }
            
            @cubic_oval3 = oval {
              x          <= [@cubic, :cx2_value, on_read: ->(val) {val - 5}]
              y          <= [@cubic, :cy2_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :dark_gray
            }
            
            @cubic_oval4 = oval {
              x          <= [@cubic, :x2_value, on_read: ->(val) {val - 5}]
              y          <= [@cubic, :y2_value, on_read: ->(val) {val - 5}]
              width 10
              height 10
              background :black
            }
            
            on_mouse_down do |mouse_event|
              @selected_shape = @cubic_canvas.shape_at_location(mouse_event.x, mouse_event.y)
              @selected_shape = nil unless @selected_shape.is_a?(Glimmer::SWT::Custom::Shape::Oval)
              @cubic_canvas.cursor = :hand if @selected_shape
            end
            
            on_drag_detected do |drag_detect_event|
              @drag_detected = true
              @drag_current_x = drag_detect_event.x
              @drag_current_y = drag_detect_event.y
            end
            
            on_mouse_move do |mouse_event|
              if @drag_detected && @selected_shape
                delta_x = mouse_event.x - @drag_current_x
                delta_y = mouse_event.y - @drag_current_y
                case @selected_shape
                when @cubic_oval1
                  @cubic.x1_value += delta_x
                  @cubic.y1_value += delta_y
                when @cubic_oval2
                  @cubic.cx1_value += delta_x
                  @cubic.cy1_value += delta_y
                when @cubic_oval3
                  @cubic.cx2_value += delta_x
                  @cubic.cy2_value += delta_y
                when @cubic_oval4
                  @cubic.x2_value += delta_x
                  @cubic.y2_value += delta_y
                end
                @drag_current_x = mouse_event.x
                @drag_current_y = mouse_event.y
              end
            end
            
            on_mouse_up do |mouse_event|
              @cubic_canvas.cursor = :arrow
              @drag_detected = false
              @selected_shape = nil
            end
          }
        }
      }
    }
  }
end

HelloCanvasDataBinding.launch
```

## Updated Hello, Canvas Data-Binding! Video Tutorial

YouTube: 
{% youtube PQ_Y8e5VeDo %}

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