konpeito 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,336 @@
1
+ # Mermaid diagram renderer - draws flowcharts using KUIRuntime primitives
2
+ # Handles node shapes, edge lines with arrows, labels, and subgraphs
3
+
4
+ class MermaidRenderer
5
+ def initialize(theme)
6
+ @theme = theme
7
+ @font_family = $theme.font_family
8
+ @font_size = 12.0
9
+ end
10
+
11
+ def render(painter, diagram, x, y, width)
12
+ # Layout the diagram
13
+ layout = MermaidLayout.new
14
+ layout.layout(diagram, width, 20.0)
15
+
16
+ # Calculate total dimensions
17
+ total_height = layout.calculate_height(diagram)
18
+ total_width = layout.calculate_width(diagram)
19
+
20
+ # Draw background
21
+ painter.fill_round_rect(x, y, width, total_height, 6.0, @theme.code_bg_color)
22
+
23
+ # Draw subgraphs first (behind nodes)
24
+ si = 0
25
+ while si < diagram.subgraphs.length
26
+ draw_subgraph(painter, diagram.subgraphs[si], x, y)
27
+ si = si + 1
28
+ end
29
+
30
+ # Draw edges
31
+ ei = 0
32
+ while ei < diagram.edges.length
33
+ draw_edge(painter, diagram, diagram.edges[ei], x, y)
34
+ ei = ei + 1
35
+ end
36
+
37
+ # Draw nodes on top
38
+ ni = 0
39
+ while ni < diagram.nodes.length
40
+ draw_node(painter, diagram.nodes[ni], x, y)
41
+ ni = ni + 1
42
+ end
43
+
44
+ total_height
45
+ end
46
+
47
+ # --- Node drawing ---
48
+
49
+ def draw_node(painter, node, ox, oy)
50
+ nx = ox + node.x
51
+ ny = oy + node.y
52
+ w = node.width
53
+ h = node.height
54
+ fill = @theme.mermaid_node_fill
55
+ stroke = @theme.mermaid_node_stroke
56
+ text_color = @theme.mermaid_node_text
57
+
58
+ shape = node.shape
59
+
60
+ if shape == MERMAID_SHAPE_ROUNDED
61
+ painter.fill_round_rect(nx, ny, w, h, 10.0, fill)
62
+ painter.stroke_round_rect(nx, ny, w, h, 10.0, stroke, 1.5)
63
+ elsif shape == MERMAID_SHAPE_STADIUM
64
+ r = h / 2.0
65
+ painter.fill_round_rect(nx, ny, w, h, r, fill)
66
+ painter.stroke_round_rect(nx, ny, w, h, r, stroke, 1.5)
67
+ elsif shape == MERMAID_SHAPE_CIRCLE
68
+ cx = nx + w / 2.0
69
+ cy = ny + h / 2.0
70
+ r = w / 2.0
71
+ painter.fill_circle(cx, cy, r, fill)
72
+ # Stroke circle via round rect with full radius
73
+ painter.stroke_round_rect(nx, ny, w, h, r, stroke, 1.5)
74
+ elsif shape == MERMAID_SHAPE_DIAMOND
75
+ draw_diamond(painter, nx, ny, w, h, fill, stroke)
76
+ elsif shape == MERMAID_SHAPE_HEXAGON
77
+ draw_hexagon(painter, nx, ny, w, h, fill, stroke)
78
+ elsif shape == MERMAID_SHAPE_SUBROUTINE
79
+ painter.fill_rect(nx, ny, w, h, fill)
80
+ painter.stroke_rect(nx, ny, w, h, stroke, 1.5)
81
+ # Inner vertical lines for subroutine
82
+ inset = 8.0
83
+ painter.draw_line(nx + inset, ny, nx + inset, ny + h, stroke, 1.5)
84
+ painter.draw_line(nx + w - inset, ny, nx + w - inset, ny + h, stroke, 1.5)
85
+ else
86
+ # RECT (default)
87
+ painter.fill_rect(nx, ny, w, h, fill)
88
+ painter.stroke_rect(nx, ny, w, h, stroke, 1.5)
89
+ end
90
+
91
+ # Draw label centered
92
+ label = node.label
93
+ text_w = painter.measure_text_width(label, @font_family, @font_size)
94
+ ascent = painter.get_text_ascent(@font_family, @font_size)
95
+ tx = nx + (w - text_w) / 2.0
96
+ ty = ny + (h + ascent) / 2.0
97
+ painter.draw_text(label, tx, ty, @font_family, @font_size, text_color)
98
+ end
99
+
100
+ def draw_diamond(painter, x, y, w, h, fill, stroke)
101
+ cx = x + w / 2.0
102
+ cy = y + h / 2.0
103
+
104
+ # Fill diamond using horizontal strips for proper shape
105
+ strips = 20
106
+ strip_h = h / 20.0
107
+ si = 0
108
+ while si < strips
109
+ sy = y + si * strip_h
110
+ # Distance from vertical center
111
+ dist = sy + strip_h / 2.0 - cy
112
+ if dist < 0.0
113
+ dist = 0.0 - dist
114
+ end
115
+ ratio = dist / (h / 2.0)
116
+ if ratio > 1.0
117
+ ratio = 1.0
118
+ end
119
+ sw = w * (1.0 - ratio)
120
+ sx = cx - sw / 2.0
121
+ painter.fill_rect(sx, sy, sw, strip_h + 1.0, fill)
122
+ si = si + 1
123
+ end
124
+
125
+ # Draw diamond outline
126
+ painter.draw_line(cx, y, x + w, cy, stroke, 1.5)
127
+ painter.draw_line(x + w, cy, cx, y + h, stroke, 1.5)
128
+ painter.draw_line(cx, y + h, x, cy, stroke, 1.5)
129
+ painter.draw_line(x, cy, cx, y, stroke, 1.5)
130
+ end
131
+
132
+ def draw_hexagon(painter, x, y, w, h, fill, stroke)
133
+ inset = w * 0.15
134
+ # Approximate hexagon with a rounded rect
135
+ painter.fill_round_rect(x, y, w, h, 4.0, fill)
136
+
137
+ # Draw hexagon outline (6 lines)
138
+ painter.draw_line(x + inset, y, x + w - inset, y, stroke, 1.5)
139
+ painter.draw_line(x + w - inset, y, x + w, y + h / 2.0, stroke, 1.5)
140
+ painter.draw_line(x + w, y + h / 2.0, x + w - inset, y + h, stroke, 1.5)
141
+ painter.draw_line(x + w - inset, y + h, x + inset, y + h, stroke, 1.5)
142
+ painter.draw_line(x + inset, y + h, x, y + h / 2.0, stroke, 1.5)
143
+ painter.draw_line(x, y + h / 2.0, x + inset, y, stroke, 1.5)
144
+ end
145
+
146
+ # --- Edge drawing ---
147
+
148
+ def draw_edge(painter, diagram, edge, ox, oy)
149
+ src = diagram.get_node(edge.source)
150
+ tgt = diagram.get_node(edge.target)
151
+ if !src || !tgt
152
+ return
153
+ end
154
+
155
+ color = @theme.mermaid_edge_color
156
+
157
+ # Calculate connection points (center of node edges)
158
+ src_cx = ox + src.x + src.width / 2.0
159
+ src_cy = oy + src.y + src.height / 2.0
160
+ tgt_cx = ox + tgt.x + tgt.width / 2.0
161
+ tgt_cy = oy + tgt.y + tgt.height / 2.0
162
+
163
+ # Determine which edges to connect from
164
+ horizontal = diagram.direction == MERMAID_DIR_LR || diagram.direction == MERMAID_DIR_RL
165
+
166
+ if horizontal
167
+ # Connect left/right edges
168
+ if src_cx < tgt_cx
169
+ x1 = ox + src.x + src.width
170
+ x2 = ox + tgt.x
171
+ else
172
+ x1 = ox + src.x
173
+ x2 = ox + tgt.x + tgt.width
174
+ end
175
+ y1 = src_cy
176
+ y2 = tgt_cy
177
+ else
178
+ # Connect top/bottom edges
179
+ if src_cy < tgt_cy
180
+ y1 = oy + src.y + src.height
181
+ y2 = oy + tgt.y
182
+ else
183
+ y1 = oy + src.y
184
+ y2 = oy + tgt.y + tgt.height
185
+ end
186
+ x1 = src_cx
187
+ x2 = tgt_cx
188
+ end
189
+
190
+ # Draw line based on line type
191
+ stroke_w = 1.5
192
+ if edge.line_type == MERMAID_LINE_THICK
193
+ stroke_w = 3.0
194
+ end
195
+
196
+ if edge.line_type == MERMAID_LINE_DASHED
197
+ draw_dashed_line(painter, x1, y1, x2, y2, color, stroke_w)
198
+ else
199
+ painter.draw_line(x1, y1, x2, y2, color, stroke_w)
200
+ end
201
+
202
+ # Draw arrow at target
203
+ if edge.arrow_type == MERMAID_ARROW_ARROW
204
+ draw_arrowhead(painter, x1, y1, x2, y2, color)
205
+ elsif edge.arrow_type == MERMAID_ARROW_CIRCLE
206
+ painter.fill_circle(x2, y2, 4.0, color)
207
+ elsif edge.arrow_type == MERMAID_ARROW_CROSS
208
+ draw_cross(painter, x2, y2, color)
209
+ end
210
+
211
+ # Draw edge label
212
+ if edge.label.length > 0
213
+ mid_x = (x1 + x2) / 2.0
214
+ mid_y = (y1 + y2) / 2.0
215
+ label_w = painter.measure_text_width(edge.label, @font_family, @font_size)
216
+ ascent = painter.get_text_ascent(@font_family, @font_size)
217
+
218
+ # Background for label
219
+ label_pad = 4.0
220
+ painter.fill_round_rect(mid_x - label_w / 2.0 - label_pad, mid_y - ascent / 2.0 - label_pad,
221
+ label_w + label_pad * 2.0, ascent + label_pad * 2.0,
222
+ 3.0, @theme.code_bg_color)
223
+ painter.draw_text(edge.label, mid_x - label_w / 2.0, mid_y + ascent / 2.0,
224
+ @font_family, @font_size, @theme.text_color)
225
+ end
226
+ end
227
+
228
+ def approx_sqrt(val)
229
+ # Newton's method sqrt approximation
230
+ if val <= 0.0
231
+ return 0.0
232
+ end
233
+ guess = val / 2.0
234
+ if guess < 1.0
235
+ guess = 1.0
236
+ end
237
+ iter = 0
238
+ while iter < 10
239
+ guess = (guess + val / guess) / 2.0
240
+ iter = iter + 1
241
+ end
242
+ guess
243
+ end
244
+
245
+ def draw_dashed_line(painter, x1, y1, x2, y2, color, stroke_w)
246
+ # Draw as series of short segments
247
+ dx = x2 - x1
248
+ dy = y2 - y1
249
+ length = approx_sqrt(dx * dx + dy * dy)
250
+ if length < 1.0
251
+ return
252
+ end
253
+
254
+ dash_len = 8.0
255
+ gap_len = 4.0
256
+ segment = dash_len + gap_len
257
+
258
+ nx = dx / length
259
+ ny = dy / length
260
+
261
+ dist = 0.0
262
+ while dist < length
263
+ seg_start_x = x1 + nx * dist
264
+ seg_start_y = y1 + ny * dist
265
+ seg_end = dist + dash_len
266
+ if seg_end > length
267
+ seg_end = length
268
+ end
269
+ seg_end_x = x1 + nx * seg_end
270
+ seg_end_y = y1 + ny * seg_end
271
+ painter.draw_line(seg_start_x, seg_start_y, seg_end_x, seg_end_y, color, stroke_w)
272
+ dist = dist + segment
273
+ end
274
+ end
275
+
276
+ def draw_arrowhead(painter, x1, y1, x2, y2, color)
277
+ # Draw a small triangle at (x2, y2) pointing away from (x1, y1)
278
+ dx = x2 - x1
279
+ dy = y2 - y1
280
+ length = approx_sqrt(dx * dx + dy * dy)
281
+ if length < 1.0
282
+ return
283
+ end
284
+
285
+ arrow_size = 8.0
286
+ nx = dx / length
287
+ ny = dy / length
288
+
289
+ # Arrow base center
290
+ base_x = x2 - nx * arrow_size
291
+ base_y = y2 - ny * arrow_size
292
+
293
+ # Perpendicular
294
+ px = 0.0 - ny
295
+ py = nx
296
+
297
+ # Three points of triangle
298
+ half = arrow_size * 0.5
299
+ p1x = base_x + px * half
300
+ p1y = base_y + py * half
301
+ p2x = base_x - px * half
302
+ p2y = base_y - py * half
303
+
304
+ # Draw as three lines forming a filled triangle
305
+ painter.draw_line(x2, y2, p1x, p1y, color, 1.5)
306
+ painter.draw_line(x2, y2, p2x, p2y, color, 1.5)
307
+ painter.draw_line(p1x, p1y, p2x, p2y, color, 1.5)
308
+ end
309
+
310
+ def draw_cross(painter, x, y, color)
311
+ size = 5.0
312
+ painter.draw_line(x - size, y - size, x + size, y + size, color, 1.5)
313
+ painter.draw_line(x - size, y + size, x + size, y - size, color, 1.5)
314
+ end
315
+
316
+ # --- Subgraph drawing ---
317
+
318
+ def draw_subgraph(painter, sg, ox, oy)
319
+ sx = ox + sg.x
320
+ sy = oy + sg.y
321
+ w = sg.width
322
+ h = sg.height
323
+
324
+ # Background
325
+ painter.fill_round_rect(sx, sy, w, h, 6.0, @theme.mermaid_subgraph_bg)
326
+ # Border
327
+ painter.stroke_round_rect(sx, sy, w, h, 6.0, @theme.mermaid_subgraph_border, 1.0)
328
+
329
+ # Title
330
+ if sg.title.length > 0
331
+ ascent = painter.get_text_ascent(@font_family, @font_size)
332
+ painter.draw_text(sg.title, sx + 8.0, sy + ascent + 4.0,
333
+ @font_family, @font_size, @theme.text_color)
334
+ end
335
+ end
336
+ end