Glimmer DSL for SWT Canvas Path DSL
Tags: ruby, gui, dsl, desktop
Published: 2021-03-03 10:22:46

[Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) had a relatively major release in v4.18.6.0 and its follow-up v4.18.6.1 to introduce a new Sub-DSL called the [Canvas Path DSL](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#canvas-path-dsl), which enables creating paths consisting of the following:

- Points
- Lines
- Quadratic Bezier Curves
- Cubic Bezier Curves

Unlike other imperative graphing GUI libraries, Glimmer facilitates declarative creation of paths. In other words, it requires no explicit use of move_to, curve_to, etc... (even if possible directly via [SWT](https://www.eclipse.org/swt/)), yet clear and concise declaration of paths via nouns instead, such as `path`, `point`, `line`, `quad`, and `cubic`, thus enabling higher code understandability, maintainability, and productivity.

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/development/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#canvas-path-dsl

require 'glimmer-dsl-swt'

include Glimmer

shell {
  text 'Canvas Path Example'
  minimum_size 300, 300
  
  canvas {
    path {
      foreground :black
      250.times {|n|
        cubic(n + n%30, n+ n%50, 40, 40, 70, 70, n + 20 + n%30, n%30*-1 * n%50)
      }
    }
  }
  
}.open
```

![Example](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-example-canvas-path.png)

In fact, two new samples have been added to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) to demo the new [Canvas Path DSL](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#canvas-path-dsl) capabilities.

[Hello, Canvas Path!](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#hello-canvas-path)

![Hello Canvas Path](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-hello-canvas-path.png)

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/development/docs/reference/GLIMMER_SAMPLES.md#hello-canvas-path

require 'glimmer-dsl-swt'
        
include Glimmer
  
shell {
  text 'Hello, Canvas Path!'
  minimum_size 800, 700
  background :white
  
    canvas {
      background :white
      
      text('line', 15, 200) {
        foreground :red
      }
      @path1 = path {
        antialias :on
        foreground :red
      }
      
      text('quad', 15, 300) {
        foreground :dark_green
      }
      @path2 = path {
        antialias :on
        foreground :dark_green
      }
      
      text('cubic', 15, 400) {
        foreground :blue
      }
      @path3 = path {
        antialias :on
        foreground :blue
      }
    }
  
  on_swt_show {
    Thread.new {
      y1 = y2 = y3 = 300
      800.times.each do |x|
        x += 55
        x1 = x - 2
        x2 = x - 1
        x3 = x
        y1 = y3
        y2 = y1
        y3 = [[y3 + (rand*24 - 12), 0].max, 700].min
        @path1.content {
          line(x1, y1 - 100)
        }
        if x % 2 == 0
          @path2.content {
            quad(x1, y1, x2, y2)
          }
        end
        if x % 3 == 0
          @path3.content {
            cubic(x1, y1 + 100, x2, y2 + 100, x3, y3 + 100)
          }
        end
        sleep(0.01)
      end
    }
  }
}.open
```

[Stock Ticker](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#stock-ticker)

![Stock Ticker](https://res.cloudinary.com/practicaldev/image/fetch/s--69520sU_--/c_imagga_scale,f_auto,fl_progressive,h_420,q_66,w_1000/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8cf528g242gs7b53ngok.gif)

```ruby
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/development/docs/reference/GLIMMER_SAMPLES.md#stock-ticker

require 'glimmer-dsl-swt'

# This Sample is an Early Alpha (New Canvas Path DSL Feature)

class StockTicker
  class Stock
    class << self
      attr_writer :stock_price_min, :stock_price_max
      
      def stock_price_min
        @stock_price_min ||= 1
      end
      
      def stock_price_max
        @stock_price_max ||= 600
      end
    end
    
    attr_reader :name, :stock_prices
    attr_accessor :stock_price
    
    def initialize(name, stock_price)
      @name = name
      @stock_price = stock_price
      @stock_prices = [@stock_price]
      @delta_sign = 1
      start_new_trend!
    end
    
    def tick!
      @tick_count = @tick_count.to_i + 1
      delta = @tick_count%@trend_length
      if delta == 0
        @delta_sign *= -1
        start_new_trend!
      end
      stock_prices << self.stock_price = [[@stock_price + @delta_sign*delta, Stock.stock_price_min].max, Stock.stock_price_max].min
    end
    
    def start_new_trend!
      @trend_length = (rand*12).to_i + 1
    end
  end
  
  include Glimmer::UI::CustomShell
  
  before_body {
    @stocks = [
      Stock.new('DELL', 81),
      Stock.new('AAPL', 121),
      Stock.new('MSFT', 232),
      Stock.new('ADBE', 459),
    ]
    @stock_colors = [:red, :dark_green, :blue, :dark_magenta]
    max_stock_name_width = 0
    left_margin = 5
    @tabs = ['Lines', 'Quadratic Bezier Curves', 'Cubic Bezier Curves', 'Points'].map {|title| {title: title, stock_paths: [], stock_transforms: []}}
    @stocks.each_with_index do |stock, stock_index|
      observe(stock, :stock_price) do |new_price|
        begin
          @tabs.each do |tab|
            new_x = stock.stock_prices.count - 1
            new_y = @tabs.first[:canvas].bounds.height - new_price - 1
            max_stock_name_width = tab[:text]&.bounds&.width if tab[:text]&.bounds&.width.to_f > max_stock_name_width
            if new_x > 0
              case tab[:title]
              when 'Cubic Bezier Curves'
                if new_x%3 == 0 && stock.stock_prices[new_x] && stock.stock_prices[new_x - 1] && stock.stock_prices[new_x - 2]
                  tab[:stock_paths][stock_index].content {
                    cubic(new_x - 2, @tabs.first[:canvas].bounds.height - stock.stock_prices[new_x - 2] - 1, new_x - 1, @tabs.first[:canvas].bounds.height - stock.stock_prices[new_x - 1] - 1, new_x, new_y)
                    tab[:stock_transforms][stock_index] ||= transform {
                      translate max_stock_name_width + 5 + left_margin, tab[:text].bounds.height / 2.0
                    }
                  }
                end
              when 'Quadratic Bezier Curves'
                if new_x%2 == 0 && stock.stock_prices[new_x] && stock.stock_prices[new_x - 1]
                  tab[:stock_paths][stock_index].content {
                    quad(new_x - 1, @tabs.first[:canvas].bounds.height - stock.stock_prices[new_x - 1] - 1, new_x, new_y)
                    tab[:stock_transforms][stock_index] ||= transform {
                      translate max_stock_name_width + 5 + left_margin, tab[:text].bounds.height / 2.0
                    }
                  }
                end
              when 'Lines'
                tab[:stock_paths][stock_index].content {
                  line(new_x, new_y)
                  tab[:stock_transforms][stock_index] ||= transform {
                    translate max_stock_name_width + 5 + left_margin, tab[:text].bounds.height / 2.0
                  }
                }
              when 'Points'
                tab[:stock_paths][stock_index].content {
                  point(new_x, new_y)
                  tab[:stock_transforms][stock_index] ||= transform {
                    translate max_stock_name_width + 5 + left_margin, tab[:text].bounds.height / 2.0
                  }
                }
              end
              new_x_location = new_x + max_stock_name_width + 5 + left_margin + 5
              canvas_width = tab[:canvas].bounds.width
              if new_x_location > canvas_width
                tab[:canvas].set_size(new_x_location, @tabs.first[:canvas].bounds.height)
                tab[:canvas].cursor = :hand
                tab[:scrolled_composite].set_min_size(new_x_location, @tabs.first[:canvas].bounds.height)
                tab[:scrolled_composite].set_origin(tab[:scrolled_composite].origin.x + 1, tab[:scrolled_composite].origin.y) if (tab[:scrolled_composite].origin.x + tab[:scrolled_composite].client_area.width) == canvas_width
              end
            else
              tab[:canvas].content {
                tab[:text] = text(stock.name, new_x + left_margin, new_y) {
                  foreground @stock_colors[stock_index]
                }
              }
            end
          end
        rescue => e
          Glimmer::Config.logger.error {e.full_message}
        end
      end
    end
  }
  
  after_body {
    @thread = Thread.new {
      loop {
        @stocks.each(&:tick!)
        sleep(0.01)
      }
    }
  }
  
  body {
    shell {
      fill_layout {
        margin_width 15
        margin_height 15
      }
      text 'Stock Ticker'
      minimum_size 650, 650
      background :white
      
      @tab_folder = tab_folder {
        @tabs.each do |tab|
          tab_item {
            fill_layout {
              margin_width 0
              margin_height 0
            }
            text tab[:title]
            
            tab[:scrolled_composite] = scrolled_composite {
              tab[:canvas] = canvas {
                background :white
                
                @stocks.count.times do |stock_index|
                  tab[:stock_paths][stock_index] = path {
                    antialias :on
                    foreground @stock_colors[stock_index]
                  }
                end
                
                on_mouse_down {
                  @drag_detected = false
                }
                
                on_drag_detected { |drag_detect_event|
                  @drag_detected = true
                  @drag_start_x = drag_detect_event.x
                  @drag_start_y = drag_detect_event.y
                }
                
                on_mouse_move { |mouse_event|
                  if @drag_detected
                    origin = tab[:scrolled_composite].origin
                    new_x = origin.x - (mouse_event.x - @drag_start_x)
                    new_y = origin.y - (mouse_event.y - @drag_start_y)
                    tab[:scrolled_composite].set_origin(new_x, new_y)
                  end
                }
                
                on_mouse_up { |mouse_event|
                  @drag_detected = false
                }
              }
            }
          }
        end
      }
    
      on_swt_show {
        Stock.stock_price_min = 25
        Stock.stock_price_max = @tabs.first[:canvas].bounds.height - 6
        # pre-initialize all tabs by selecting them so that they render content when they are later in the background
        @tab_folder.items.each do |item|
          @tab_folder.selection = item
        end
        @tab_folder.selection = @tab_folder.items.first
      }
      
      on_widget_disposed {
        @thread.kill # safe to kill as data is in memory only
      }
    }
  }
end

StockTicker.launch
```

There is nothing more productive than [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) when it comes to building cross-platform desktop applications, bar none! And, it is only getting better every day. Start learning [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) today by reading [Glimmer blog posts](https://andymaleh.blogspot.com/search/label/Glimmer) and building your own apps while reporting feedback for improvement. It is about time [Ruby](https://www.ruby-lang.org/en/)'s become the world leading language for desktop development, not just web.
 
Happy [Glimmering](https://github.com/AndyObtiva/glimmer-dsl-swt)!