DEV Community

loading...
Cover image for Glimmer DSL for SWT Canvas Path DSL

Glimmer DSL for SWT Canvas Path DSL

Andy Maleh
RailsConf / RubyConf / AgileConf / EclipseCon / EclipseWorld Presenter. Software Engineering Expert. Glimmer Ruby GUI OSS Author. MS in SE DePaul University Chicago. Snowboarder/Drummer. Ex-Groupon.
・5 min read

Glimmer DSL for 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, 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), 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.

# 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
Enter fullscreen mode Exit fullscreen mode

Example

In fact, two new samples have been added to Glimmer DSL for SWT to demo the new Canvas Path DSL capabilities.

Hello, Canvas Path!

Hello Canvas Path

# 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
Enter fullscreen mode Exit fullscreen mode

Stock Ticker

Stock Ticker

# 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
Enter fullscreen mode Exit fullscreen mode

There is nothing more productive than Glimmer DSL for 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 today by reading Glimmer blog posts and building your own apps while reporting feedback for improvement. It is about time Ruby's become the world leading language for desktop development, not just web.

Happy Glimmering!

Discussion (0)