You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

214 lines
5.4KB

  1. class Bounded
  2. attr_reader :top, :right, :bottom, :left
  3. def initialize(top:, right:, bottom:, left:)
  4. @top = top
  5. @right = right
  6. @bottom = bottom
  7. @left = left
  8. end
  9. def width
  10. @right - @left
  11. end
  12. def height
  13. @bottom - @top
  14. end
  15. def center
  16. OpenStruct.new(x: width / 2.0 + left, y: height / 2.0 + top)
  17. end
  18. def translate(delta_x: 0, delta_y: 0)
  19. @top += delta_y
  20. @right += delta_x
  21. @bottom += delta_y
  22. @left += delta_x
  23. end
  24. def move_center_to(x: center.x, y: center.y)
  25. delta_x = x - center.x
  26. delta_y = y - center.y
  27. translate(delta_x: delta_x, delta_y: delta_y)
  28. end
  29. end
  30. class Control < Bounded
  31. def initialize(x:, y:, width:, height: width)
  32. super(top: y - height / 2.0, right: x + width / 2.0, bottom: y + height / 2.0, left: x - width / 2.0)
  33. end
  34. end
  35. class RoundControl < Control
  36. attr_reader :diameter
  37. def initialize(x:, y:, diameter:)
  38. super(x: x, y: y, width: diameter)
  39. @diameter = diameter
  40. end
  41. def radius
  42. diameter / 2.0
  43. end
  44. end
  45. class ButtonControl < RoundControl
  46. DIAMETER = 6.0
  47. def initialize(x: 0.0, y: 0.0, style:, state:, dark:, light:)
  48. super(x: x, y: y, diameter: DIAMETER)
  49. @style = style.to_sym
  50. @state = state.to_sym
  51. @button_color = @style == :dark ? dark : light
  52. if @style == :dark
  53. @state_color = @state == :on ? light : dark
  54. else
  55. @state_color = @state == :on ? dark : light
  56. end
  57. end
  58. def name
  59. "button-#{@style}-#{@state}"
  60. end
  61. def align(padding, alignment, other)
  62. new_x = case alignment
  63. when :right_of
  64. other.right + padding + radius
  65. when :left_of
  66. other.left - padding - radius
  67. else
  68. center.x
  69. end
  70. move_center_to(x: new_x, y: other.center.y)
  71. end
  72. def svg
  73. stroke_width = diameter / 6.0
  74. circle_diameter = diameter - stroke_width
  75. circle_radius = circle_diameter / 2.0
  76. %Q[
  77. <circle cx="#{center.x}" cy="#{center.y}" r="#{circle_radius}" stroke-width="#{stroke_width}" fill="#{@state_color}" stroke="#{@button_color}"/>
  78. ]
  79. end
  80. end
  81. class KnobControl < RoundControl
  82. DIAMETER = 12.7
  83. def initialize(x: 0.0, y: 0.0, knob_color:, pointer_color:)
  84. super(x: x, y: y, diameter: 12.7)
  85. @knob_color = knob_color
  86. @pointer_color = pointer_color
  87. end
  88. def name
  89. 'knob-large'
  90. end
  91. def svg
  92. pointer_width = radius / 8.0
  93. pointer_length = radius - pointer_width
  94. %Q[
  95. <g transform="translate(#{center.x} #{center.y})" stroke="#{@pointer_color}" fill="#{@knob_color }">
  96. <circle r="#{radius}" stroke="none"/>
  97. <line y2="-#{pointer_length}" stroke-width="#{pointer_width }" stroke-linecap="round"/>
  98. </g>
  99. ]
  100. end
  101. end
  102. class PortControl < RoundControl
  103. DIAMETER = 8.4
  104. def initialize(x: 0.0, y: 0.0, metal_color:, shadow_color:)
  105. super(x: x, y: y, diameter: 8.4)
  106. @metal_color = metal_color
  107. @shadow_color = shadow_color
  108. end
  109. def name
  110. 'port'
  111. end
  112. def svg
  113. stroke_width = diameter * 0.025
  114. sleeve_diameter = diameter - stroke_width
  115. step = sleeve_diameter / 7.0
  116. sleeve_radius = sleeve_diameter / 2.0
  117. ring_radius = sleeve_radius - step
  118. tip_radius = ring_radius - step
  119. %Q[
  120. <g transform="translate(#{center.x} #{center.y})" stroke="#{@shadow_color}" fill="#{@metal_color}" stroke-width="#{stroke_width}">
  121. <circle r="#{sleeve_radius}"/>
  122. <circle r="#{ring_radius }"/>
  123. <circle r="#{tip_radius}" fill="#{@shadow_color}"/>
  124. </g>
  125. ]
  126. end
  127. end
  128. class SwitchControl < Control
  129. WIDTH = 3.0
  130. def initialize(x: 0.0, y: 0.0, positions:, state:, dark:, light:)
  131. super(x: x, y: y, width: WIDTH, height: positions * WIDTH)
  132. @positions = positions
  133. @state = state
  134. @dark = dark
  135. @light = light
  136. @position =
  137. case @state
  138. when :high
  139. 1.0
  140. when :low
  141. -1.0
  142. else
  143. 0.0
  144. end
  145. end
  146. def name
  147. "switch-#{@positions}-#{@state}"
  148. end
  149. def svg
  150. box_stroke_width = width / 8.0
  151. interior_inset = box_stroke_width / 2.0
  152. box_width = width - box_stroke_width
  153. box_height = height - box_stroke_width
  154. box_left = -width / 2.0 + interior_inset
  155. box_top = -height / 2.0 + interior_inset
  156. interior_width = box_width - box_stroke_width
  157. interior_height = box_height - box_stroke_width
  158. corner_radius = interior_inset
  159. knurl_stroke_width = 0.25
  160. knurl_inset = knurl_stroke_width * 2.0
  161. knurl_length = interior_width - knurl_inset
  162. knurl_left = knurl_length / -2.0
  163. knurl_right = knurl_left + knurl_length
  164. knurl_spacing = knurl_stroke_width * 2.0
  165. lever_height = knurl_spacing * 4.0 + knurl_stroke_width
  166. lever_inset = knurl_stroke_width
  167. lever_distance = (interior_height - lever_height) / 2.0 - lever_inset
  168. lever_offset = lever_distance * -@position
  169. lever = (-2..2)
  170. .map {|index| knurl_spacing * index + lever_offset}
  171. .map {|y| %Q[<line x1="#{knurl_left}" x2="#{knurl_right}" y1="#{y}" y2="#{y}" stroke-width="#{knurl_stroke_width}" stroke-linecap="round"/>]}
  172. .join("\n")
  173. %Q[
  174. <g transform="translate(#{center.x} #{center.y})" fill="#{@light}" stroke="#{@dark}">
  175. <rect x="#{box_left}" y="#{box_top}" width="#{box_width}" height="#{box_height}"
  176. rx="#{corner_radius}" ry="#{corner_radius}"
  177. stroke-width="#{box_stroke_width}"/>
  178. #{lever}
  179. </g>
  180. ]
  181. end
  182. end