samples/elaborate/stock_ticker.rb
# Copyright (c) 2007-2024 Andy Maleh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require 'glimmer-dsl-swt'
# This Sample is an Early Alpha (New Canvas Path DSL Feature)
class StockTicker
class Stock
class << self
attr_writer :price_min, :price_max
def price_min
@price_min ||= 1
end
def price_max
@price_max ||= 600
end
end
attr_reader :name, :prices
attr_accessor :price
def initialize(name, price)
@name = name
@price = price
@prices = [@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
prices << self.price = [[@price + @delta_sign*delta, Stock.price_min].max, Stock.price_max].min
end
def start_new_trend!
@trend_length = (rand*12).to_i + 1
end
end
include Glimmer::UI::CustomShell
before_body do
@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]
margin = 5
@tabs = ['Lines', 'Quadratic Bezier Curves', 'Cubic Bezier Curves', 'Points'].map {|title| {title: title, stock_paths: []}}
@stocks.each_with_index do |stock, stock_index|
observe(stock, :price) do |new_price|
begin
@tabs.each do |tab|
new_x = stock.prices.count - 1
new_y = @tabs.first[:canvas].bounds.height - new_price - 1
if new_x > 0
case tab[:title]
when 'Cubic Bezier Curves'
if new_x%3 == 0 && stock.prices[new_x] && stock.prices[new_x - 1] && stock.prices[new_x - 2]
tab[:stock_paths][stock_index].content {
cubic(new_x - 2 + margin, @tabs.first[:canvas].bounds.height - stock.prices[new_x - 2] - 1, new_x - 1 + margin, @tabs.first[:canvas].bounds.height - stock.prices[new_x - 1] - 1, new_x + margin, new_y)
}
end
when 'Quadratic Bezier Curves'
if new_x%2 == 0 && stock.prices[new_x] && stock.prices[new_x - 1]
tab[:stock_paths][stock_index].content {
quad(new_x - 1 + margin, @tabs.first[:canvas].bounds.height - stock.prices[new_x - 1] - 1, new_x + margin, new_y)
}
end
when 'Lines'
tab[:stock_paths][stock_index].content {
line(new_x + margin, new_y)
}
when 'Points'
tab[:stock_paths][stock_index].content {
point(new_x + margin, new_y)
}
end
new_x_location = new_x + 2*margin
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 + (OS.mac? ? 0 : 20)) == canvas_width
end
else
tab[:canvas_header].content {
text(stock.name, 15, new_y - 10) {
foreground @stock_colors[stock_index]
font height: 13
}
}
end
end
rescue => e
Glimmer::Config.logger.error {e.full_message}
end
end
end
end
after_body do
@thread = Thread.new do
loop do
@stocks.each(&:tick!)
sleep(0.01)
end
end
end
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 {
grid_layout(2, false) {
margin_width 0
margin_height 0
}
text tab[:title]
tab[:canvas_header] = canvas {
layout_data(:center, :fill, false, true)
}
tab[:scrolled_composite] = scrolled_composite {
layout_data :fill, :fill, true, true
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 do
@drag_detected = false
end
on_drag_detected do |drag_detect_event|
@drag_detected = true
@drag_start_x = drag_detect_event.x
@drag_start_y = drag_detect_event.y
end
on_mouse_move do |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
end
on_mouse_up do |mouse_event|
@drag_detected = false
end
}
}
}
end
}
on_swt_show do
Stock.price_min = 25
Stock.price_max = @tabs.first[:canvas].bounds.height - 6
end
on_widget_disposed do
@thread.kill # safe to kill as data is in memory only
end
}
}
end
StockTicker.launch