class Bounded attr_reader :top, :right, :bottom, :left def initialize(top:, right:, bottom:, left:) @top = top @right = right @bottom = bottom @left = left end def width @right - @left end def height @bottom - @top end def center OpenStruct.new(x: width / 2.0 + left, y: height / 2.0 + top) end def translate(delta_x: 0, delta_y: 0) @top += delta_y @right += delta_x @bottom += delta_y @left += delta_x end def move_center_to(x: center.x, y: center.y) delta_x = x - center.x delta_y = y - center.y translate(delta_x: delta_x, delta_y: delta_y) end end class Control < Bounded def initialize(x:, y:, width:, height: width) super(top: y - height / 2.0, right: x + width / 2.0, bottom: y + height / 2.0, left: x - width / 2.0) end end class RoundControl < Control attr_reader :diameter def initialize(x:, y:, diameter:) super(x: x, y: y, width: diameter) @diameter = diameter end def radius diameter / 2.0 end end class ButtonControl < RoundControl DIAMETER = 6.0 def initialize(x: 0.0, y: 0.0, style:, state:, dark:, light:) super(x: x, y: y, diameter: DIAMETER) @style = style.to_sym @state = state.to_sym @button_color = @style == :dark ? dark : light if @style == :dark @state_color = @state == :on ? light : dark else @state_color = @state == :on ? dark : light end end def name "button-#{@style}-#{@state}" end def align(padding, alignment, other) new_x = case alignment when :right_of other.right + padding + radius when :left_of other.left - padding - radius else center.x end move_center_to(x: new_x, y: other.center.y) end def svg stroke_width = diameter / 6.0 circle_diameter = diameter - stroke_width circle_radius = circle_diameter / 2.0 %Q[ ] end end class KnobControl < RoundControl DIAMETER = 12.7 def initialize(x: 0.0, y: 0.0, knob_color:, pointer_color:) super(x: x, y: y, diameter: 12.7) @knob_color = knob_color @pointer_color = pointer_color end def name 'knob-large' end def svg pointer_width = radius / 8.0 pointer_length = radius - pointer_width %Q[ ] end end class PortControl < RoundControl DIAMETER = 8.4 def initialize(x: 0.0, y: 0.0, metal_color:, shadow_color:) super(x: x, y: y, diameter: 8.4) @metal_color = metal_color @shadow_color = shadow_color end def name 'port' end def svg stroke_width = diameter * 0.025 sleeve_diameter = diameter - stroke_width step = sleeve_diameter / 7.0 sleeve_radius = sleeve_diameter / 2.0 ring_radius = sleeve_radius - step tip_radius = ring_radius - step %Q[ ] end end class SwitchControl < Control WIDTH = 3.0 def initialize(x: 0.0, y: 0.0, positions:, state:, dark:, light:) super(x: x, y: y, width: WIDTH, height: positions * WIDTH) @positions = positions @state = state @dark = dark @light = light @position = case @state when :high 1.0 when :low -1.0 else 0.0 end end def name "switch-#{@positions}-#{@state}" end def svg box_stroke_width = width / 8.0 interior_inset = box_stroke_width / 2.0 box_width = width - box_stroke_width box_height = height - box_stroke_width box_left = -width / 2.0 + interior_inset box_top = -height / 2.0 + interior_inset interior_width = box_width - box_stroke_width interior_height = box_height - box_stroke_width corner_radius = interior_inset knurl_stroke_width = 0.25 knurl_inset = knurl_stroke_width * 2.0 knurl_length = interior_width - knurl_inset knurl_left = knurl_length / -2.0 knurl_right = knurl_left + knurl_length knurl_spacing = knurl_stroke_width * 2.0 lever_height = knurl_spacing * 4.0 + knurl_stroke_width lever_inset = knurl_stroke_width lever_distance = (interior_height - lever_height) / 2.0 - lever_inset lever_offset = lever_distance * -@position lever = (-2..2) .map {|index| knurl_spacing * index + lever_offset} .map {|y| %Q[]} .join("\n") %Q[ #{lever} ] end end