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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +256 -0
- data/lib/kumiki/animation/animated_state.rb +83 -0
- data/lib/kumiki/animation/easing.rb +62 -0
- data/lib/kumiki/animation/value_tween.rb +69 -0
- data/lib/kumiki/app.rb +381 -0
- data/lib/kumiki/box.rb +40 -0
- data/lib/kumiki/chart/area_chart.rb +308 -0
- data/lib/kumiki/chart/bar_chart.rb +291 -0
- data/lib/kumiki/chart/base_chart.rb +213 -0
- data/lib/kumiki/chart/chart_helpers.rb +74 -0
- data/lib/kumiki/chart/gauge_chart.rb +174 -0
- data/lib/kumiki/chart/heatmap_chart.rb +223 -0
- data/lib/kumiki/chart/line_chart.rb +292 -0
- data/lib/kumiki/chart/pie_chart.rb +222 -0
- data/lib/kumiki/chart/scales.rb +79 -0
- data/lib/kumiki/chart/scatter_chart.rb +306 -0
- data/lib/kumiki/chart/stacked_bar_chart.rb +279 -0
- data/lib/kumiki/column.rb +351 -0
- data/lib/kumiki/core.rb +2511 -0
- data/lib/kumiki/dsl.rb +408 -0
- data/lib/kumiki/frame_ranma.rb +570 -0
- data/lib/kumiki/markdown/ast.rb +127 -0
- data/lib/kumiki/markdown/mermaid/layout.rb +389 -0
- data/lib/kumiki/markdown/mermaid/models.rb +235 -0
- data/lib/kumiki/markdown/mermaid/parser.rb +522 -0
- data/lib/kumiki/markdown/mermaid/renderer.rb +339 -0
- data/lib/kumiki/markdown/parser.rb +808 -0
- data/lib/kumiki/markdown/renderer.rb +642 -0
- data/lib/kumiki/markdown/theme.rb +168 -0
- data/lib/kumiki/render_node.rb +262 -0
- data/lib/kumiki/row.rb +288 -0
- data/lib/kumiki/spacer.rb +20 -0
- data/lib/kumiki/style.rb +799 -0
- data/lib/kumiki/theme.rb +567 -0
- data/lib/kumiki/themes/material.rb +40 -0
- data/lib/kumiki/themes/tokyo_night.rb +11 -0
- data/lib/kumiki/version.rb +5 -0
- data/lib/kumiki/widgets/button.rb +105 -0
- data/lib/kumiki/widgets/calendar.rb +1028 -0
- data/lib/kumiki/widgets/checkbox.rb +119 -0
- data/lib/kumiki/widgets/container.rb +111 -0
- data/lib/kumiki/widgets/data_table.rb +670 -0
- data/lib/kumiki/widgets/divider.rb +31 -0
- data/lib/kumiki/widgets/image.rb +105 -0
- data/lib/kumiki/widgets/input.rb +485 -0
- data/lib/kumiki/widgets/markdown.rb +58 -0
- data/lib/kumiki/widgets/modal.rb +165 -0
- data/lib/kumiki/widgets/multiline_input.rb +970 -0
- data/lib/kumiki/widgets/multiline_text.rb +180 -0
- data/lib/kumiki/widgets/net_image.rb +100 -0
- data/lib/kumiki/widgets/progress_bar.rb +72 -0
- data/lib/kumiki/widgets/radio_buttons.rb +93 -0
- data/lib/kumiki/widgets/slider.rb +135 -0
- data/lib/kumiki/widgets/switch.rb +84 -0
- data/lib/kumiki/widgets/tabs.rb +175 -0
- data/lib/kumiki/widgets/text.rb +120 -0
- data/lib/kumiki/widgets/tree.rb +434 -0
- data/lib/kumiki/widgets/webview.rb +87 -0
- data/lib/kumiki.rb +130 -0
- 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
|