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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a5c07f3fe7c9d026edda7c5bea10a3c782b4957d468d2345285ea6abb2cdccd
4
+ data.tar.gz: fb06aaa4ad15d1c2abac021233158aa7aca6d2a07510267edc57bf351bbd841c
5
+ SHA512:
6
+ metadata.gz: c39a4c8d085fc796b6644710637f634e30bb3b2a62f06bff480a970784588beea74e42ce09b1aa83a48c848ea02233095a8fb320d3c7e7ebd7d3d4ce5b79ca42
7
+ data.tar.gz: 2c9111e037718a27643a88ec98e1d385d30233e120cae0670c5bcb90cf1979699373c7d93b06c9b9ea98e753de7b5b1cf3a80c9ebf1fbb114990275fe25466fb
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yasushi Itoh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # Kumiki
2
+
3
+ **A declarative, reactive GUI framework for Ruby**
4
+
5
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-CC342D?logo=ruby)](https://www.ruby-lang.org/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ Kumiki (組木) is a component-based desktop GUI framework for Ruby, based on a Ruby port of [castella](https://github.com/i2y/castella). Define your UI declaratively with a clean block-based DSL; the reactive state system handles updates automatically. Rendering is powered by [ranma](https://github.com/i2y/ranma) (tao + Vello GPU).
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ - **Reactive state** — `State` objects trigger automatic UI rebuilds on change
15
+ - **Block-based DSL** — `column`, `row`, `text`, `button`, ... with keyword-arg styling
16
+ - **GPU rendering** — hardware-accelerated via ranma (tao + Vello)
17
+ - **Rich widget set** — 20+ widgets covering inputs, layout, data display, and more
18
+ - **Animation** — `AnimatedState` with built-in easing functions
19
+ - **Theming** — Tokyo Night (default) and Material Design themes included
20
+ - **Charts** — 8 chart types: Bar, Line, Pie, Scatter, Area, StackedBar, Gauge, Heatmap
21
+ - **Markdown** — Markdown rendering with Mermaid diagram support
22
+ - **WebView** — embedded browser widget
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ Add to your `Gemfile`:
29
+
30
+ ```ruby
31
+ gem "kumiki"
32
+ ```
33
+
34
+ Or install directly:
35
+
36
+ ```sh
37
+ gem install kumiki
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Quick Start
43
+
44
+ ```ruby
45
+ require "kumiki"
46
+ include Kumiki
47
+
48
+ class Counter < Component
49
+ def initialize
50
+ super
51
+ @count = state(0)
52
+ end
53
+
54
+ def view
55
+ column(padding: 16.0, spacing: 8.0) {
56
+ text "Count: #{@count}", font_size: 32.0, align: :center
57
+ row(spacing: 8.0) {
58
+ button(" - ") { @count -= 1 }
59
+ button(" + ") { @count += 1 }
60
+ }
61
+ }
62
+ end
63
+ end
64
+
65
+ Kumiki.run("Counter", 400, 300) { Counter.new }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Widget Reference
71
+
72
+ ### Layouts
73
+
74
+ | Widget | Description |
75
+ |--------|-------------|
76
+ | `Column` / `column` | Vertical stack |
77
+ | `Row` / `row` | Horizontal stack |
78
+ | `Box` / `box` | Z-stack overlay |
79
+
80
+ ### Container
81
+
82
+ | Widget | Description |
83
+ |--------|-------------|
84
+ | `Container` / `container` | Background, border, border-radius, scrollable |
85
+
86
+ ### Leaf Widgets
87
+
88
+ | Widget | Description |
89
+ |--------|-------------|
90
+ | `Text` / `text` | Static or dynamic text |
91
+ | `Button` / `button` | Clickable button with `kind:` variants |
92
+ | `Input` / `input` | Single-line text input |
93
+ | `MultilineInput` / `multiline_input` | Multi-line text input |
94
+ | `MultilineText` / `multiline_text` | Read-only multi-line text |
95
+ | `Checkbox` / `checkbox` | Boolean toggle |
96
+ | `RadioButtons` / `radio_buttons` | Single selection from options |
97
+ | `Switch` / `switch` | Toggle switch |
98
+ | `Slider` / `slider` | Value slider with range |
99
+ | `ProgressBar` / `progress_bar` | Progress indicator |
100
+ | `Divider` / `divider` | Horizontal rule |
101
+ | `Spacer` / `spacer` | Flexible space |
102
+ | `ImageWidget` / `image` | Local image |
103
+ | `NetImageWidget` / `net_image` | Async remote image |
104
+ | `WebViewWidget` / `webview` | Embedded web browser |
105
+
106
+ ### Complex Widgets
107
+
108
+ | Widget | Description |
109
+ |--------|-------------|
110
+ | `Tabs` / `tabs` | Tabbed panels |
111
+ | `Tree` / `tree` | Collapsible tree navigation |
112
+ | `Calendar` / `calendar` | Month calendar with selection |
113
+ | `Modal` / `modal` | Overlay dialog |
114
+ | `DataTable` / `data_table` | Sortable, scrollable table |
115
+
116
+ ### Charts
117
+
118
+ | Widget | Description |
119
+ |--------|-------------|
120
+ | `BarChart` | Vertical bar chart |
121
+ | `LineChart` | Line / time-series chart |
122
+ | `PieChart` | Pie chart |
123
+ | `ScatterChart` | Scatter plot |
124
+ | `AreaChart` | Area chart |
125
+ | `StackedBarChart` | Stacked bar chart |
126
+ | `GaugeChart` | Circular gauge |
127
+ | `HeatmapChart` | 2D heatmap |
128
+
129
+ ### Markdown
130
+
131
+ | Widget | Description |
132
+ |--------|-------------|
133
+ | `Markdown` / `markdown_text` | Renders Markdown with Mermaid diagram support |
134
+
135
+ ---
136
+
137
+ ## DSL Style vs Object Style
138
+
139
+ Both styles can be mixed freely. The DSL is preferred for new code.
140
+
141
+ **DSL (block-based):**
142
+
143
+ ```ruby
144
+ column(padding: 16.0, spacing: 8.0) {
145
+ text "Hello", font_size: 24.0
146
+ button("Click me") { puts "clicked" }
147
+ }
148
+ ```
149
+
150
+ **Object-based:**
151
+
152
+ ```ruby
153
+ Column(
154
+ Text("Hello").font_size(24.0),
155
+ Button("Click me") { puts "clicked" }
156
+ ).padding(16.0).spacing(8.0)
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Animation
162
+
163
+ ```ruby
164
+ class MyApp < Component
165
+ def initialize
166
+ super
167
+ # AnimatedState.new(initial_value, duration_ms, easing)
168
+ @x = AnimatedState.new(0.0, 400.0, :ease_out)
169
+ @x.attach(self)
170
+ end
171
+
172
+ def view
173
+ column {
174
+ box {
175
+ container(x: @x.value, width: 60.0, height: 60.0, background: 0xFF7AA2F7)
176
+ }
177
+ button("Animate") { @x.set(200.0) }
178
+ }
179
+ end
180
+ end
181
+ ```
182
+
183
+ Available easing functions: `:linear`, `:ease_in`, `:ease_out`, `:ease_in_out`, `:ease_in_cubic`, `:ease_out_cubic`, `:ease_in_out_cubic`, `:bounce`
184
+
185
+ ---
186
+
187
+ ## Theming
188
+
189
+ ```ruby
190
+ require "kumiki"
191
+
192
+ # Default: Tokyo Night
193
+ Kumiki.run("App", 800, 600) { MyApp.new }
194
+
195
+ # Material Design (light)
196
+ Kumiki.theme = Kumiki.material_theme
197
+ Kumiki.run("App", 800, 600) { MyApp.new }
198
+ ```
199
+
200
+ Widget `kind:` prop applies semantic colors from the active theme:
201
+
202
+ ```ruby
203
+ button("OK", kind: :success)
204
+ button("Cancel", kind: :danger)
205
+ button("Info", kind: :info)
206
+ button("Warn", kind: :warning)
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Examples
212
+
213
+ The `examples/` directory contains runnable demos:
214
+
215
+ | File | Description |
216
+ |------|-------------|
217
+ | `dsl_counter_demo.rb` | Minimal counter — reactive state + DSL |
218
+ | `counter.rb` | Counter using object-based style |
219
+ | `all_widgets_demo.rb` | Comprehensive showcase of every widget |
220
+ | `animation_demo.rb` | Animation system with various easing functions |
221
+ | `chart_demo.rb` | All 8 chart types |
222
+ | `data_table_demo.rb` | Sortable DataTable |
223
+ | `calendar_demo.rb` | Calendar widget |
224
+ | `tree_demo.rb` | Tree navigation |
225
+ | `tabs_demo.rb` | Tabbed interface |
226
+ | `modal_demo.rb` | Modal dialogs |
227
+ | `markdown_demo.rb` | Markdown + Mermaid rendering |
228
+ | `input_demo.rb` | Input fields and text widgets |
229
+ | `scroll_demo.rb` | Scrollable containers |
230
+ | `focus_demo.rb` | Keyboard focus and Tab cycling |
231
+ | `theme_demo.rb` | Theme presets and `kind:` variants |
232
+ | `theme_mode_demo.rb` | Light / dark mode switching |
233
+ | `dsl_style_demo.rb` | DSL styling examples |
234
+ | `dsl_calc.rb` | Calculator app |
235
+ | `widgets_demo.rb` | Basic widgets overview |
236
+
237
+ Run any example:
238
+
239
+ ```sh
240
+ bundle install
241
+ bundle exec ruby examples/dsl_counter_demo.rb
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Requirements
247
+
248
+ - Ruby >= 3.1.0
249
+ - macOS or Linux
250
+ - The `ranma` gem is installed automatically as a dependency
251
+
252
+ ---
253
+
254
+ ## License
255
+
256
+ MIT — Copyright (c) 2026 Yasushi Itoh. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,83 @@
1
+ module Kumiki
2
+ # AnimatedState - observable value that transitions smoothly
3
+ # Inherits from ObservableBase so Components can subscribe
4
+
5
+ class AnimatedState < ObservableBase
6
+ def initialize(initial_value, duration, easing_fn)
7
+ super()
8
+ @value = initial_value
9
+ @target = initial_value
10
+ @duration = duration # milliseconds
11
+ @easing_fn = easing_fn # Symbol
12
+ @tween = nil
13
+ @animating = false
14
+ end
15
+
16
+ def value
17
+ @value
18
+ end
19
+
20
+ def target
21
+ @target
22
+ end
23
+
24
+ def animating?
25
+ @animating
26
+ end
27
+
28
+ # Set a new target — begins animation from current value
29
+ def set(new_target)
30
+ if new_target == @target && !@animating
31
+ return
32
+ end
33
+ @target = new_target
34
+ @tween = ValueTween.new(@value, @target, @duration, @easing_fn)
35
+ @animating = true
36
+ # Register with App's animation loop
37
+ app = App.current
38
+ if app != nil
39
+ app.register_animation(self)
40
+ end
41
+ end
42
+
43
+ # Immediately jump to a value (no animation)
44
+ def set_immediate(new_value)
45
+ @value = new_value
46
+ @target = new_value
47
+ @tween = nil
48
+ @animating = false
49
+ notify_observers
50
+ end
51
+
52
+ # Call each frame with delta time in ms
53
+ def tick(dt)
54
+ return false if !@animating || @tween == nil
55
+ @tween.tick(dt)
56
+ @value = @tween.current
57
+ if @tween.finished?
58
+ @value = @target
59
+ @animating = false
60
+ @tween = nil
61
+ end
62
+ notify_observers
63
+ @animating
64
+ end
65
+
66
+ def duration
67
+ @duration
68
+ end
69
+
70
+ def duration=(d)
71
+ @duration = d
72
+ end
73
+
74
+ def easing_fn
75
+ @easing_fn
76
+ end
77
+
78
+ def easing_fn=(e)
79
+ @easing_fn = e
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,62 @@
1
+ module Kumiki
2
+ # Easing functions for animation
3
+ # All functions take t in [0.0, 1.0] and return a value in [0.0, 1.0]
4
+
5
+ EASING_PI = 3.14159265358979323846
6
+
7
+ module Easing
8
+ def self.linear(t)
9
+ t
10
+ end
11
+
12
+ def self.ease_in(t)
13
+ t * t
14
+ end
15
+
16
+ def self.ease_out(t)
17
+ 1.0 - (1.0 - t) * (1.0 - t)
18
+ end
19
+
20
+ def self.ease_in_out(t)
21
+ if t < 0.5
22
+ 2.0 * t * t
23
+ else
24
+ 1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
25
+ end
26
+ end
27
+
28
+ def self.ease_in_cubic(t)
29
+ t * t * t
30
+ end
31
+
32
+ def self.ease_out_cubic(t)
33
+ v = 1.0 - t
34
+ 1.0 - v * v * v
35
+ end
36
+
37
+ def self.ease_in_out_cubic(t)
38
+ if t < 0.5
39
+ 4.0 * t * t * t
40
+ else
41
+ v = -2.0 * t + 2.0
42
+ 1.0 - v * v * v / 2.0
43
+ end
44
+ end
45
+
46
+ def self.bounce(t)
47
+ if t < 1.0 / 2.75
48
+ 7.5625 * t * t
49
+ elsif t < 2.0 / 2.75
50
+ t2 = t - 1.5 / 2.75
51
+ 7.5625 * t2 * t2 + 0.75
52
+ elsif t < 2.5 / 2.75
53
+ t2 = t - 2.25 / 2.75
54
+ 7.5625 * t2 * t2 + 0.9375
55
+ else
56
+ t2 = t - 2.625 / 2.75
57
+ 7.5625 * t2 * t2 + 0.984375
58
+ end
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,69 @@
1
+ module Kumiki
2
+ # ValueTween - interpolates between two numeric values over a duration
3
+ # Frame-based: call tick(dt) each frame to advance
4
+
5
+ class ValueTween
6
+ def initialize(from_val, to_val, duration, easing_fn)
7
+ @from_val = from_val
8
+ @to_val = to_val
9
+ @duration = duration # milliseconds
10
+ @easing_fn = easing_fn # Symbol: :linear, :ease_in, etc.
11
+ @elapsed = 0.0
12
+ @current = from_val
13
+ @finished = false
14
+ end
15
+
16
+ def tick(dt)
17
+ return if @finished
18
+ @elapsed = @elapsed + dt
19
+ if @elapsed >= @duration
20
+ @elapsed = @duration
21
+ @finished = true
22
+ end
23
+ t = @elapsed / @duration
24
+ eased = apply_easing(t)
25
+ @current = @from_val + (@to_val - @from_val) * eased
26
+ end
27
+
28
+ def current
29
+ @current
30
+ end
31
+
32
+ def finished?
33
+ @finished
34
+ end
35
+
36
+ def reset(from_val, to_val)
37
+ @from_val = from_val
38
+ @to_val = to_val
39
+ @elapsed = 0.0
40
+ @current = from_val
41
+ @finished = false
42
+ end
43
+
44
+ private
45
+
46
+ def apply_easing(t)
47
+ if @easing_fn == :linear
48
+ Easing.linear(t)
49
+ elsif @easing_fn == :ease_in
50
+ Easing.ease_in(t)
51
+ elsif @easing_fn == :ease_out
52
+ Easing.ease_out(t)
53
+ elsif @easing_fn == :ease_in_out
54
+ Easing.ease_in_out(t)
55
+ elsif @easing_fn == :ease_in_cubic
56
+ Easing.ease_in_cubic(t)
57
+ elsif @easing_fn == :ease_out_cubic
58
+ Easing.ease_out_cubic(t)
59
+ elsif @easing_fn == :ease_in_out_cubic
60
+ Easing.ease_in_out_cubic(t)
61
+ elsif @easing_fn == :bounce
62
+ Easing.bounce(t)
63
+ else
64
+ t
65
+ end
66
+ end
67
+ end
68
+
69
+ end