kumiki 0.1.1

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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +256 -0
  4. data/lib/kumiki/animation/animated_state.rb +83 -0
  5. data/lib/kumiki/animation/easing.rb +62 -0
  6. data/lib/kumiki/animation/value_tween.rb +69 -0
  7. data/lib/kumiki/app.rb +381 -0
  8. data/lib/kumiki/box.rb +40 -0
  9. data/lib/kumiki/chart/area_chart.rb +308 -0
  10. data/lib/kumiki/chart/bar_chart.rb +291 -0
  11. data/lib/kumiki/chart/base_chart.rb +213 -0
  12. data/lib/kumiki/chart/chart_helpers.rb +74 -0
  13. data/lib/kumiki/chart/gauge_chart.rb +174 -0
  14. data/lib/kumiki/chart/heatmap_chart.rb +223 -0
  15. data/lib/kumiki/chart/line_chart.rb +292 -0
  16. data/lib/kumiki/chart/pie_chart.rb +222 -0
  17. data/lib/kumiki/chart/scales.rb +79 -0
  18. data/lib/kumiki/chart/scatter_chart.rb +306 -0
  19. data/lib/kumiki/chart/stacked_bar_chart.rb +279 -0
  20. data/lib/kumiki/column.rb +351 -0
  21. data/lib/kumiki/core.rb +2511 -0
  22. data/lib/kumiki/dsl.rb +408 -0
  23. data/lib/kumiki/frame_ranma.rb +570 -0
  24. data/lib/kumiki/markdown/ast.rb +127 -0
  25. data/lib/kumiki/markdown/mermaid/layout.rb +389 -0
  26. data/lib/kumiki/markdown/mermaid/models.rb +235 -0
  27. data/lib/kumiki/markdown/mermaid/parser.rb +522 -0
  28. data/lib/kumiki/markdown/mermaid/renderer.rb +339 -0
  29. data/lib/kumiki/markdown/parser.rb +808 -0
  30. data/lib/kumiki/markdown/renderer.rb +642 -0
  31. data/lib/kumiki/markdown/theme.rb +168 -0
  32. data/lib/kumiki/render_node.rb +262 -0
  33. data/lib/kumiki/row.rb +288 -0
  34. data/lib/kumiki/spacer.rb +20 -0
  35. data/lib/kumiki/style.rb +799 -0
  36. data/lib/kumiki/theme.rb +567 -0
  37. data/lib/kumiki/themes/material.rb +40 -0
  38. data/lib/kumiki/themes/tokyo_night.rb +11 -0
  39. data/lib/kumiki/version.rb +5 -0
  40. data/lib/kumiki/widgets/button.rb +105 -0
  41. data/lib/kumiki/widgets/calendar.rb +1028 -0
  42. data/lib/kumiki/widgets/checkbox.rb +119 -0
  43. data/lib/kumiki/widgets/container.rb +111 -0
  44. data/lib/kumiki/widgets/data_table.rb +670 -0
  45. data/lib/kumiki/widgets/divider.rb +31 -0
  46. data/lib/kumiki/widgets/image.rb +105 -0
  47. data/lib/kumiki/widgets/input.rb +485 -0
  48. data/lib/kumiki/widgets/markdown.rb +58 -0
  49. data/lib/kumiki/widgets/modal.rb +165 -0
  50. data/lib/kumiki/widgets/multiline_input.rb +970 -0
  51. data/lib/kumiki/widgets/multiline_text.rb +180 -0
  52. data/lib/kumiki/widgets/net_image.rb +100 -0
  53. data/lib/kumiki/widgets/progress_bar.rb +72 -0
  54. data/lib/kumiki/widgets/radio_buttons.rb +93 -0
  55. data/lib/kumiki/widgets/slider.rb +135 -0
  56. data/lib/kumiki/widgets/switch.rb +84 -0
  57. data/lib/kumiki/widgets/tabs.rb +175 -0
  58. data/lib/kumiki/widgets/text.rb +120 -0
  59. data/lib/kumiki/widgets/tree.rb +434 -0
  60. data/lib/kumiki/widgets/webview.rb +87 -0
  61. data/lib/kumiki.rb +130 -0
  62. metadata +113 -0
@@ -0,0 +1,339 @@
1
+ module Kumiki
2
+ # Mermaid diagram renderer - draws flowcharts using kumiki's painter primitives
3
+ # Handles node shapes, edge lines with arrows, labels, and subgraphs
4
+
5
+ class MermaidRenderer
6
+ def initialize(theme)
7
+ @theme = theme
8
+ @font_family = Kumiki.theme.font_family
9
+ @font_size = 12.0
10
+ end
11
+
12
+ def render(painter, diagram, x, y, width)
13
+ # Layout the diagram
14
+ layout = MermaidLayout.new
15
+ layout.layout(diagram, width, 20.0)
16
+
17
+ # Calculate total dimensions
18
+ total_height = layout.calculate_height(diagram)
19
+ total_width = layout.calculate_width(diagram)
20
+
21
+ # Draw background
22
+ painter.fill_round_rect(x, y, width, total_height, 6.0, @theme.code_bg_color)
23
+
24
+ # Draw subgraphs first (behind nodes)
25
+ si = 0
26
+ while si < diagram.subgraphs.length
27
+ draw_subgraph(painter, diagram.subgraphs[si], x, y)
28
+ si = si + 1
29
+ end
30
+
31
+ # Draw edges
32
+ ei = 0
33
+ while ei < diagram.edges.length
34
+ draw_edge(painter, diagram, diagram.edges[ei], x, y)
35
+ ei = ei + 1
36
+ end
37
+
38
+ # Draw nodes on top
39
+ ni = 0
40
+ while ni < diagram.nodes.length
41
+ draw_node(painter, diagram.nodes[ni], x, y)
42
+ ni = ni + 1
43
+ end
44
+
45
+ total_height
46
+ end
47
+
48
+ # --- Node drawing ---
49
+
50
+ def draw_node(painter, node, ox, oy)
51
+ nx = ox + node.x
52
+ ny = oy + node.y
53
+ w = node.width
54
+ h = node.height
55
+ fill = @theme.mermaid_node_fill
56
+ stroke = @theme.mermaid_node_stroke
57
+ text_color = @theme.mermaid_node_text
58
+
59
+ shape = node.shape
60
+
61
+ if shape == MERMAID_SHAPE_ROUNDED
62
+ painter.fill_round_rect(nx, ny, w, h, 10.0, fill)
63
+ painter.stroke_round_rect(nx, ny, w, h, 10.0, stroke, 1.5)
64
+ elsif shape == MERMAID_SHAPE_STADIUM
65
+ r = h / 2.0
66
+ painter.fill_round_rect(nx, ny, w, h, r, fill)
67
+ painter.stroke_round_rect(nx, ny, w, h, r, stroke, 1.5)
68
+ elsif shape == MERMAID_SHAPE_CIRCLE
69
+ cx = nx + w / 2.0
70
+ cy = ny + h / 2.0
71
+ r = w / 2.0
72
+ painter.fill_circle(cx, cy, r, fill)
73
+ # Stroke circle via round rect with full radius
74
+ painter.stroke_round_rect(nx, ny, w, h, r, stroke, 1.5)
75
+ elsif shape == MERMAID_SHAPE_DIAMOND
76
+ draw_diamond(painter, nx, ny, w, h, fill, stroke)
77
+ elsif shape == MERMAID_SHAPE_HEXAGON
78
+ draw_hexagon(painter, nx, ny, w, h, fill, stroke)
79
+ elsif shape == MERMAID_SHAPE_SUBROUTINE
80
+ painter.fill_rect(nx, ny, w, h, fill)
81
+ painter.stroke_rect(nx, ny, w, h, stroke, 1.5)
82
+ # Inner vertical lines for subroutine
83
+ inset = 8.0
84
+ painter.draw_line(nx + inset, ny, nx + inset, ny + h, stroke, 1.5)
85
+ painter.draw_line(nx + w - inset, ny, nx + w - inset, ny + h, stroke, 1.5)
86
+ else
87
+ # RECT (default)
88
+ painter.fill_rect(nx, ny, w, h, fill)
89
+ painter.stroke_rect(nx, ny, w, h, stroke, 1.5)
90
+ end
91
+
92
+ # Draw label centered
93
+ label = node.label
94
+ text_w = painter.measure_text_width(label, @font_family, @font_size)
95
+ ascent = painter.get_text_ascent(@font_family, @font_size)
96
+ tx = nx + (w - text_w) / 2.0
97
+ ty = ny + (h + ascent) / 2.0
98
+ painter.draw_text(label, tx, ty, @font_family, @font_size, text_color)
99
+ end
100
+
101
+ def draw_diamond(painter, x, y, w, h, fill, stroke)
102
+ cx = x + w / 2.0
103
+ cy = y + h / 2.0
104
+
105
+ # Fill diamond using horizontal strips for proper shape
106
+ strips = 20
107
+ strip_h = h / 20.0
108
+ si = 0
109
+ while si < strips
110
+ sy = y + si * strip_h
111
+ # Distance from vertical center
112
+ dist = sy + strip_h / 2.0 - cy
113
+ if dist < 0.0
114
+ dist = 0.0 - dist
115
+ end
116
+ ratio = dist / (h / 2.0)
117
+ if ratio > 1.0
118
+ ratio = 1.0
119
+ end
120
+ sw = w * (1.0 - ratio)
121
+ sx = cx - sw / 2.0
122
+ painter.fill_rect(sx, sy, sw, strip_h + 1.0, fill)
123
+ si = si + 1
124
+ end
125
+
126
+ # Draw diamond outline
127
+ painter.draw_line(cx, y, x + w, cy, stroke, 1.5)
128
+ painter.draw_line(x + w, cy, cx, y + h, stroke, 1.5)
129
+ painter.draw_line(cx, y + h, x, cy, stroke, 1.5)
130
+ painter.draw_line(x, cy, cx, y, stroke, 1.5)
131
+ end
132
+
133
+ def draw_hexagon(painter, x, y, w, h, fill, stroke)
134
+ inset = w * 0.15
135
+ # Approximate hexagon with a rounded rect
136
+ painter.fill_round_rect(x, y, w, h, 4.0, fill)
137
+
138
+ # Draw hexagon outline (6 lines)
139
+ painter.draw_line(x + inset, y, x + w - inset, y, stroke, 1.5)
140
+ painter.draw_line(x + w - inset, y, x + w, y + h / 2.0, stroke, 1.5)
141
+ painter.draw_line(x + w, y + h / 2.0, x + w - inset, y + h, stroke, 1.5)
142
+ painter.draw_line(x + w - inset, y + h, x + inset, y + h, stroke, 1.5)
143
+ painter.draw_line(x + inset, y + h, x, y + h / 2.0, stroke, 1.5)
144
+ painter.draw_line(x, y + h / 2.0, x + inset, y, stroke, 1.5)
145
+ end
146
+
147
+ # --- Edge drawing ---
148
+
149
+ def draw_edge(painter, diagram, edge, ox, oy)
150
+ src = diagram.get_node(edge.source)
151
+ tgt = diagram.get_node(edge.target)
152
+ if !src || !tgt
153
+ return
154
+ end
155
+
156
+ color = @theme.mermaid_edge_color
157
+
158
+ # Calculate connection points (center of node edges)
159
+ src_cx = ox + src.x + src.width / 2.0
160
+ src_cy = oy + src.y + src.height / 2.0
161
+ tgt_cx = ox + tgt.x + tgt.width / 2.0
162
+ tgt_cy = oy + tgt.y + tgt.height / 2.0
163
+
164
+ # Determine which edges to connect from
165
+ horizontal = diagram.direction == MERMAID_DIR_LR || diagram.direction == MERMAID_DIR_RL
166
+
167
+ if horizontal
168
+ # Connect left/right edges
169
+ if src_cx < tgt_cx
170
+ x1 = ox + src.x + src.width
171
+ x2 = ox + tgt.x
172
+ else
173
+ x1 = ox + src.x
174
+ x2 = ox + tgt.x + tgt.width
175
+ end
176
+ y1 = src_cy
177
+ y2 = tgt_cy
178
+ else
179
+ # Connect top/bottom edges
180
+ if src_cy < tgt_cy
181
+ y1 = oy + src.y + src.height
182
+ y2 = oy + tgt.y
183
+ else
184
+ y1 = oy + src.y
185
+ y2 = oy + tgt.y + tgt.height
186
+ end
187
+ x1 = src_cx
188
+ x2 = tgt_cx
189
+ end
190
+
191
+ # Draw line based on line type
192
+ stroke_w = 1.5
193
+ if edge.line_type == MERMAID_LINE_THICK
194
+ stroke_w = 3.0
195
+ end
196
+
197
+ if edge.line_type == MERMAID_LINE_DASHED
198
+ draw_dashed_line(painter, x1, y1, x2, y2, color, stroke_w)
199
+ else
200
+ painter.draw_line(x1, y1, x2, y2, color, stroke_w)
201
+ end
202
+
203
+ # Draw arrow at target
204
+ if edge.arrow_type == MERMAID_ARROW_ARROW
205
+ draw_arrowhead(painter, x1, y1, x2, y2, color)
206
+ elsif edge.arrow_type == MERMAID_ARROW_CIRCLE
207
+ painter.fill_circle(x2, y2, 4.0, color)
208
+ elsif edge.arrow_type == MERMAID_ARROW_CROSS
209
+ draw_cross(painter, x2, y2, color)
210
+ end
211
+
212
+ # Draw edge label
213
+ if edge.label.length > 0
214
+ mid_x = (x1 + x2) / 2.0
215
+ mid_y = (y1 + y2) / 2.0
216
+ label_w = painter.measure_text_width(edge.label, @font_family, @font_size)
217
+ ascent = painter.get_text_ascent(@font_family, @font_size)
218
+
219
+ # Background for label
220
+ label_pad = 4.0
221
+ painter.fill_round_rect(mid_x - label_w / 2.0 - label_pad, mid_y - ascent / 2.0 - label_pad,
222
+ label_w + label_pad * 2.0, ascent + label_pad * 2.0,
223
+ 3.0, @theme.code_bg_color)
224
+ painter.draw_text(edge.label, mid_x - label_w / 2.0, mid_y + ascent / 2.0,
225
+ @font_family, @font_size, @theme.text_color)
226
+ end
227
+ end
228
+
229
+ def approx_sqrt(val)
230
+ # Newton's method sqrt approximation
231
+ if val <= 0.0
232
+ return 0.0
233
+ end
234
+ guess = val / 2.0
235
+ if guess < 1.0
236
+ guess = 1.0
237
+ end
238
+ iter = 0
239
+ while iter < 10
240
+ guess = (guess + val / guess) / 2.0
241
+ iter = iter + 1
242
+ end
243
+ guess
244
+ end
245
+
246
+ def draw_dashed_line(painter, x1, y1, x2, y2, color, stroke_w)
247
+ # Draw as series of short segments
248
+ dx = x2 - x1
249
+ dy = y2 - y1
250
+ length = approx_sqrt(dx * dx + dy * dy)
251
+ if length < 1.0
252
+ return
253
+ end
254
+
255
+ dash_len = 8.0
256
+ gap_len = 4.0
257
+ segment = dash_len + gap_len
258
+
259
+ nx = dx / length
260
+ ny = dy / length
261
+
262
+ dist = 0.0
263
+ while dist < length
264
+ seg_start_x = x1 + nx * dist
265
+ seg_start_y = y1 + ny * dist
266
+ seg_end = dist + dash_len
267
+ if seg_end > length
268
+ seg_end = length
269
+ end
270
+ seg_end_x = x1 + nx * seg_end
271
+ seg_end_y = y1 + ny * seg_end
272
+ painter.draw_line(seg_start_x, seg_start_y, seg_end_x, seg_end_y, color, stroke_w)
273
+ dist = dist + segment
274
+ end
275
+ end
276
+
277
+ def draw_arrowhead(painter, x1, y1, x2, y2, color)
278
+ # Draw a small triangle at (x2, y2) pointing away from (x1, y1)
279
+ dx = x2 - x1
280
+ dy = y2 - y1
281
+ length = approx_sqrt(dx * dx + dy * dy)
282
+ if length < 1.0
283
+ return
284
+ end
285
+
286
+ arrow_size = 8.0
287
+ nx = dx / length
288
+ ny = dy / length
289
+
290
+ # Arrow base center
291
+ base_x = x2 - nx * arrow_size
292
+ base_y = y2 - ny * arrow_size
293
+
294
+ # Perpendicular
295
+ px = 0.0 - ny
296
+ py = nx
297
+
298
+ # Three points of triangle
299
+ half = arrow_size * 0.5
300
+ p1x = base_x + px * half
301
+ p1y = base_y + py * half
302
+ p2x = base_x - px * half
303
+ p2y = base_y - py * half
304
+
305
+ # Draw as three lines forming a filled triangle
306
+ painter.draw_line(x2, y2, p1x, p1y, color, 1.5)
307
+ painter.draw_line(x2, y2, p2x, p2y, color, 1.5)
308
+ painter.draw_line(p1x, p1y, p2x, p2y, color, 1.5)
309
+ end
310
+
311
+ def draw_cross(painter, x, y, color)
312
+ size = 5.0
313
+ painter.draw_line(x - size, y - size, x + size, y + size, color, 1.5)
314
+ painter.draw_line(x - size, y + size, x + size, y - size, color, 1.5)
315
+ end
316
+
317
+ # --- Subgraph drawing ---
318
+
319
+ def draw_subgraph(painter, sg, ox, oy)
320
+ sx = ox + sg.x
321
+ sy = oy + sg.y
322
+ w = sg.width
323
+ h = sg.height
324
+
325
+ # Background
326
+ painter.fill_round_rect(sx, sy, w, h, 6.0, @theme.mermaid_subgraph_bg)
327
+ # Border
328
+ painter.stroke_round_rect(sx, sy, w, h, 6.0, @theme.mermaid_subgraph_border, 1.0)
329
+
330
+ # Title
331
+ if sg.title.length > 0
332
+ ascent = painter.get_text_ascent(@font_family, @font_size)
333
+ painter.draw_text(sg.title, sx + 8.0, sy + ascent + 4.0,
334
+ @font_family, @font_size, @theme.text_color)
335
+ end
336
+ end
337
+ end
338
+
339
+ end