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,180 @@
|
|
|
1
|
+
module Kumiki
|
|
2
|
+
# MultilineText widget - displays multiline text with optional word wrapping
|
|
3
|
+
|
|
4
|
+
class MultilineText < Widget
|
|
5
|
+
def initialize(text)
|
|
6
|
+
super()
|
|
7
|
+
@text = text
|
|
8
|
+
@font_family = nil
|
|
9
|
+
@font_size_val = 14.0
|
|
10
|
+
@color_val = 0xFFC0CAF5
|
|
11
|
+
@custom_color = false
|
|
12
|
+
@kind_val = 0
|
|
13
|
+
@padding_val = 8.0
|
|
14
|
+
@line_spacing = 4.0
|
|
15
|
+
@wrap_enabled = false
|
|
16
|
+
@border_width_val = 1.0
|
|
17
|
+
@cached_lines = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def font_family(f)
|
|
21
|
+
@font_family = f
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def resolved_font_family
|
|
26
|
+
if @font_family != nil
|
|
27
|
+
@font_family
|
|
28
|
+
else
|
|
29
|
+
Kumiki.theme.font_family
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def font_size(s)
|
|
34
|
+
@font_size_val = s
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def color(c)
|
|
39
|
+
@color_val = c
|
|
40
|
+
@custom_color = true
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def kind(k)
|
|
45
|
+
@kind_val = k
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def padding(p)
|
|
50
|
+
@padding_val = p
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def line_spacing(s)
|
|
55
|
+
@line_spacing = s
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def wrap_text(w)
|
|
60
|
+
@wrap_enabled = w
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def set_text(t)
|
|
65
|
+
@text = t
|
|
66
|
+
@cached_lines = []
|
|
67
|
+
mark_dirty
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def get_text
|
|
71
|
+
@text
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def measure(painter)
|
|
75
|
+
lines = split_lines(painter)
|
|
76
|
+
@cached_lines = lines
|
|
77
|
+
if lines.length == 0
|
|
78
|
+
return Size.new(@padding_val * 2.0, @padding_val * 2.0)
|
|
79
|
+
end
|
|
80
|
+
ff = resolved_font_family
|
|
81
|
+
max_w = 0.0
|
|
82
|
+
i = 0
|
|
83
|
+
while i < lines.length
|
|
84
|
+
lw = painter.measure_text_width(lines[i], ff, @font_size_val)
|
|
85
|
+
if lw > max_w
|
|
86
|
+
max_w = lw
|
|
87
|
+
end
|
|
88
|
+
i = i + 1
|
|
89
|
+
end
|
|
90
|
+
w = max_w + (@padding_val + @border_width_val) * 2.0
|
|
91
|
+
h = @font_size_val * lines.length + @line_spacing * (lines.length - 1) + @padding_val * 2.0 + @border_width_val * 2.0
|
|
92
|
+
Size.new(w, h)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def redraw(painter, completely)
|
|
96
|
+
lines = split_lines(painter)
|
|
97
|
+
@cached_lines = lines
|
|
98
|
+
|
|
99
|
+
# Background
|
|
100
|
+
bg_c = Kumiki.theme.bg_primary
|
|
101
|
+
brd_c = Kumiki.theme.border
|
|
102
|
+
painter.fill_rect(0.0, 0.0, @width, @height, bg_c)
|
|
103
|
+
painter.stroke_rect(0.0, 0.0, @width, @height, brd_c, @border_width_val)
|
|
104
|
+
|
|
105
|
+
# Text
|
|
106
|
+
ff = resolved_font_family
|
|
107
|
+
c = @custom_color ? @color_val : Kumiki.theme.text_color_for_kind(@kind_val)
|
|
108
|
+
ascent = painter.get_text_ascent(ff, @font_size_val)
|
|
109
|
+
y = @padding_val + @border_width_val + ascent
|
|
110
|
+
i = 0
|
|
111
|
+
while i < lines.length
|
|
112
|
+
painter.draw_text(lines[i], @padding_val + @border_width_val, y, ff, @font_size_val, c)
|
|
113
|
+
y = y + @font_size_val + @line_spacing
|
|
114
|
+
i = i + 1
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def split_lines(painter)
|
|
119
|
+
raw_lines = @text.split("\n")
|
|
120
|
+
if !@wrap_enabled
|
|
121
|
+
return raw_lines
|
|
122
|
+
end
|
|
123
|
+
# Word wrapping
|
|
124
|
+
ff = resolved_font_family
|
|
125
|
+
line_width = @width - (@padding_val + @border_width_val) * 2.0
|
|
126
|
+
if line_width <= 0.0
|
|
127
|
+
return raw_lines
|
|
128
|
+
end
|
|
129
|
+
result = []
|
|
130
|
+
ri = 0
|
|
131
|
+
while ri < raw_lines.length
|
|
132
|
+
line = raw_lines[ri]
|
|
133
|
+
words = line.split(" ")
|
|
134
|
+
if words.length == 0
|
|
135
|
+
result.push("")
|
|
136
|
+
ri = ri + 1
|
|
137
|
+
next
|
|
138
|
+
end
|
|
139
|
+
current_words = []
|
|
140
|
+
current_width = 0.0
|
|
141
|
+
wi = 0
|
|
142
|
+
while wi < words.length
|
|
143
|
+
word = words[wi]
|
|
144
|
+
word_w = painter.measure_text_width(word, ff, @font_size_val)
|
|
145
|
+
space_w = 0.0
|
|
146
|
+
if current_words.length > 0
|
|
147
|
+
space_w = painter.measure_text_width(" ", ff, @font_size_val)
|
|
148
|
+
end
|
|
149
|
+
if current_width + space_w + word_w > line_width
|
|
150
|
+
if current_words.length > 0
|
|
151
|
+
result.push(current_words.join(" "))
|
|
152
|
+
current_words = [word]
|
|
153
|
+
current_width = word_w
|
|
154
|
+
else
|
|
155
|
+
# Single word wider than line - just include it
|
|
156
|
+
result.push(word)
|
|
157
|
+
current_words = []
|
|
158
|
+
current_width = 0.0
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
current_words.push(word)
|
|
162
|
+
current_width = current_width + space_w + word_w
|
|
163
|
+
end
|
|
164
|
+
wi = wi + 1
|
|
165
|
+
end
|
|
166
|
+
if current_words.length > 0
|
|
167
|
+
result.push(current_words.join(" "))
|
|
168
|
+
end
|
|
169
|
+
ri = ri + 1
|
|
170
|
+
end
|
|
171
|
+
result
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Top-level helper
|
|
176
|
+
def MultilineText(text)
|
|
177
|
+
MultilineText.new(text)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Kumiki
|
|
2
|
+
# NetImage widget - displays an image from a network URL
|
|
3
|
+
|
|
4
|
+
class NetImageWidget < Widget
|
|
5
|
+
def initialize(url)
|
|
6
|
+
super()
|
|
7
|
+
@url = url
|
|
8
|
+
@image_id = 0
|
|
9
|
+
@img_width = 0.0
|
|
10
|
+
@img_height = 0.0
|
|
11
|
+
@fit_mode = IMAGE_FIT_CONTAIN
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fit(mode)
|
|
15
|
+
@fit_mode = mode
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def set_url(url)
|
|
20
|
+
@url = url
|
|
21
|
+
@image_id = 0
|
|
22
|
+
@img_width = 0.0
|
|
23
|
+
@img_height = 0.0
|
|
24
|
+
mark_dirty
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_if_needed(painter)
|
|
28
|
+
if @image_id == 0
|
|
29
|
+
@image_id = painter.load_net_image(@url)
|
|
30
|
+
if @image_id != 0
|
|
31
|
+
@img_width = painter.get_image_width(@image_id) * 1.0
|
|
32
|
+
@img_height = painter.get_image_height(@image_id) * 1.0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def measure(painter)
|
|
38
|
+
load_if_needed(painter)
|
|
39
|
+
if @image_id != 0
|
|
40
|
+
Size.new(@img_width, @img_height)
|
|
41
|
+
else
|
|
42
|
+
Size.new(100.0, 100.0)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def redraw(painter, completely)
|
|
47
|
+
load_if_needed(painter)
|
|
48
|
+
if @image_id == 0
|
|
49
|
+
# Draw placeholder while loading or on error
|
|
50
|
+
painter.fill_round_rect(0.0, 0.0, @width, @height, 4.0, 0x40FFFFFF)
|
|
51
|
+
painter.stroke_round_rect(0.0, 0.0, @width, @height, 4.0, 0x80FFFFFF, 1.0)
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if @fit_mode == IMAGE_FIT_FILL
|
|
56
|
+
painter.draw_image(@image_id, 0.0, 0.0, @width, @height)
|
|
57
|
+
elsif @fit_mode == IMAGE_FIT_CONTAIN
|
|
58
|
+
draw_fitted(painter, true)
|
|
59
|
+
else
|
|
60
|
+
draw_fitted(painter, false)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def draw_fitted(painter, contain)
|
|
65
|
+
if @img_width < 1.0 || @img_height < 1.0 || @width < 1.0 || @height < 1.0
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
img_aspect = @img_width / @img_height
|
|
69
|
+
widget_aspect = @width / @height
|
|
70
|
+
|
|
71
|
+
if contain
|
|
72
|
+
if img_aspect > widget_aspect
|
|
73
|
+
new_w = @width
|
|
74
|
+
new_h = @width / img_aspect
|
|
75
|
+
else
|
|
76
|
+
new_h = @height
|
|
77
|
+
new_w = @height * img_aspect
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
if img_aspect > widget_aspect
|
|
81
|
+
new_h = @height
|
|
82
|
+
new_w = @height * img_aspect
|
|
83
|
+
else
|
|
84
|
+
new_w = @width
|
|
85
|
+
new_h = @width / img_aspect
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
dx = (@width - new_w) / 2.0
|
|
90
|
+
dy = (@height - new_h) / 2.0
|
|
91
|
+
painter.draw_image(@image_id, dx, dy, new_w, new_h)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Top-level helper
|
|
96
|
+
def NetImage(url)
|
|
97
|
+
NetImageWidget.new(url)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Kumiki
|
|
2
|
+
# ProgressBar widget - progress indicator
|
|
3
|
+
|
|
4
|
+
class ProgressBar < Widget
|
|
5
|
+
def initialize
|
|
6
|
+
super()
|
|
7
|
+
@value = 0.0
|
|
8
|
+
@width_policy = EXPANDING
|
|
9
|
+
@height_policy = FIXED
|
|
10
|
+
@height = 8.0
|
|
11
|
+
# Colors (Tokyo Night)
|
|
12
|
+
@track_color = 0xFF414868
|
|
13
|
+
@fill_color = 0xFF7AA2F7
|
|
14
|
+
@radius = 4.0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with_value(v)
|
|
18
|
+
if v < 0.0
|
|
19
|
+
@value = 0.0
|
|
20
|
+
elsif v > 1.0
|
|
21
|
+
@value = 1.0
|
|
22
|
+
else
|
|
23
|
+
@value = v
|
|
24
|
+
end
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_value(v)
|
|
29
|
+
if v < 0.0
|
|
30
|
+
v = 0.0
|
|
31
|
+
end
|
|
32
|
+
if v > 1.0
|
|
33
|
+
v = 1.0
|
|
34
|
+
end
|
|
35
|
+
if v != @value
|
|
36
|
+
@value = v
|
|
37
|
+
mark_dirty
|
|
38
|
+
update
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_value
|
|
43
|
+
@value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def fill_color(c)
|
|
47
|
+
@fill_color = c
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def measure(painter)
|
|
52
|
+
Size.new(200.0, 8.0)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def redraw(painter, completely)
|
|
56
|
+
# Track background
|
|
57
|
+
painter.fill_round_rect(0.0, 0.0, @width, @height, @radius, @track_color)
|
|
58
|
+
|
|
59
|
+
# Fill
|
|
60
|
+
fill_w = @width * @value
|
|
61
|
+
if fill_w > 0.0
|
|
62
|
+
painter.fill_round_rect(0.0, 0.0, fill_w, @height, @radius, @fill_color)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Top-level helper
|
|
68
|
+
def ProgressBar()
|
|
69
|
+
ProgressBar.new
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Kumiki
|
|
2
|
+
# RadioButtons widget - exclusive selection from a list
|
|
3
|
+
# Single widget that renders all options (avoids cross-class method calls)
|
|
4
|
+
|
|
5
|
+
class RadioButtons < Widget
|
|
6
|
+
def initialize(options)
|
|
7
|
+
super()
|
|
8
|
+
@options = options
|
|
9
|
+
@option_count = options.length
|
|
10
|
+
@selected = 0
|
|
11
|
+
@change_handler = nil
|
|
12
|
+
@hovered_index = -1
|
|
13
|
+
# Colors (Tokyo Night)
|
|
14
|
+
@text_color = 0xFFC0CAF5
|
|
15
|
+
@ring_color = 0xFF565F89
|
|
16
|
+
@selected_color = 0xFF7AA2F7
|
|
17
|
+
@font_size_val = 14.0
|
|
18
|
+
@item_height = 32.0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def with_selected(index)
|
|
22
|
+
@selected = index
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_change(&block)
|
|
27
|
+
@change_handler = block
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_selected
|
|
32
|
+
@selected
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def measure(painter)
|
|
36
|
+
max_w = 0.0
|
|
37
|
+
i = 0
|
|
38
|
+
while i < @option_count
|
|
39
|
+
tw = painter.measure_text_width(@options[i], Kumiki.theme.font_family, @font_size_val)
|
|
40
|
+
w = tw + 32.0
|
|
41
|
+
if w > max_w
|
|
42
|
+
max_w = w
|
|
43
|
+
end
|
|
44
|
+
i = i + 1
|
|
45
|
+
end
|
|
46
|
+
Size.new(max_w, @item_height * @option_count)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def redraw(painter, completely)
|
|
50
|
+
circle_r = 7.0
|
|
51
|
+
i = 0
|
|
52
|
+
while i < @option_count
|
|
53
|
+
iy = @item_height * i
|
|
54
|
+
cx = circle_r + 4.0
|
|
55
|
+
cy = iy + @item_height / 2.0
|
|
56
|
+
|
|
57
|
+
# Outer ring
|
|
58
|
+
painter.fill_circle(cx, cy, circle_r, @ring_color)
|
|
59
|
+
painter.fill_circle(cx, cy, circle_r - 1.5, 0xFF1A1B26)
|
|
60
|
+
|
|
61
|
+
# Selected dot
|
|
62
|
+
if @selected == i
|
|
63
|
+
painter.fill_circle(cx, cy, 4.0, @selected_color)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Label
|
|
67
|
+
ascent = painter.get_text_ascent(Kumiki.theme.font_family, @font_size_val)
|
|
68
|
+
text_x = cx + circle_r + 8.0
|
|
69
|
+
th = painter.measure_text_height(Kumiki.theme.font_family, @font_size_val)
|
|
70
|
+
text_y = iy + (@item_height - th) / 2.0 + ascent
|
|
71
|
+
painter.draw_text(@options[i], text_x, text_y, Kumiki.theme.font_family, @font_size_val, @text_color)
|
|
72
|
+
|
|
73
|
+
i = i + 1
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def mouse_down(ev)
|
|
78
|
+
index = (ev.pos.y / @item_height).to_i
|
|
79
|
+
if index >= 0 && index < @option_count && index != @selected
|
|
80
|
+
@selected = index
|
|
81
|
+
@change_handler.call(@selected) if @change_handler
|
|
82
|
+
mark_dirty
|
|
83
|
+
update
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Top-level helper
|
|
89
|
+
def RadioButtons(options)
|
|
90
|
+
RadioButtons.new(options)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
module Kumiki
|
|
2
|
+
# Slider widget - draggable range input
|
|
3
|
+
|
|
4
|
+
class Slider < Widget
|
|
5
|
+
def initialize(min_val, max_val)
|
|
6
|
+
super()
|
|
7
|
+
@min_val = min_val
|
|
8
|
+
@max_val = max_val
|
|
9
|
+
@value = min_val
|
|
10
|
+
@dragging = false
|
|
11
|
+
@change_handler = nil
|
|
12
|
+
@width_policy = EXPANDING
|
|
13
|
+
@height_policy = FIXED
|
|
14
|
+
@height = 30.0
|
|
15
|
+
# Colors (Tokyo Night)
|
|
16
|
+
@track_color = 0xFF414868
|
|
17
|
+
@fill_color = 0xFF7AA2F7
|
|
18
|
+
@thumb_color = 0xFFC0CAF5
|
|
19
|
+
@thumb_hover = 0xFFFFFFFF
|
|
20
|
+
@hovered = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_value(v)
|
|
24
|
+
if v < @min_val
|
|
25
|
+
@value = @min_val
|
|
26
|
+
elsif v > @max_val
|
|
27
|
+
@value = @max_val
|
|
28
|
+
else
|
|
29
|
+
@value = v
|
|
30
|
+
end
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_change(&block)
|
|
35
|
+
@change_handler = block
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_value
|
|
40
|
+
@value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def measure(painter)
|
|
44
|
+
Size.new(200.0, 30.0)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def redraw(painter, completely)
|
|
48
|
+
thumb_r = 8.0
|
|
49
|
+
track_h = 4.0
|
|
50
|
+
track_y = (@height - track_h) / 2.0
|
|
51
|
+
track_x = thumb_r
|
|
52
|
+
track_w = @width - thumb_r * 2.0
|
|
53
|
+
if track_w < 0.0
|
|
54
|
+
track_w = 0.0
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Track background
|
|
58
|
+
painter.fill_round_rect(track_x, track_y, track_w, track_h, 2.0, @track_color)
|
|
59
|
+
|
|
60
|
+
# Fill (progress)
|
|
61
|
+
ratio = 0.0
|
|
62
|
+
range = @max_val - @min_val
|
|
63
|
+
if range > 0.0
|
|
64
|
+
ratio = (@value - @min_val) / range
|
|
65
|
+
end
|
|
66
|
+
fill_w = track_w * ratio
|
|
67
|
+
if fill_w > 0.0
|
|
68
|
+
painter.fill_round_rect(track_x, track_y, fill_w, track_h, 2.0, @fill_color)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Thumb circle
|
|
72
|
+
thumb_x = track_x + fill_w
|
|
73
|
+
thumb_y = @height / 2.0
|
|
74
|
+
tc = @hovered ? @thumb_hover : @thumb_color
|
|
75
|
+
painter.fill_circle(thumb_x, thumb_y, thumb_r, tc)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def mouse_down(ev)
|
|
79
|
+
@dragging = true
|
|
80
|
+
update_from_pos(ev.pos.x)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def mouse_drag(ev)
|
|
84
|
+
if @dragging
|
|
85
|
+
update_from_pos(ev.pos.x)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def mouse_up(ev)
|
|
90
|
+
@dragging = false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def mouse_over
|
|
94
|
+
@hovered = true
|
|
95
|
+
mark_dirty
|
|
96
|
+
update
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def mouse_out
|
|
100
|
+
@hovered = false
|
|
101
|
+
mark_dirty
|
|
102
|
+
update
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def update_from_pos(x)
|
|
106
|
+
thumb_r = 8.0
|
|
107
|
+
track_x = thumb_r
|
|
108
|
+
track_w = @width - thumb_r * 2.0
|
|
109
|
+
if track_w <= 0.0
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
ratio = (x - track_x) / track_w
|
|
113
|
+
if ratio < 0.0
|
|
114
|
+
ratio = 0.0
|
|
115
|
+
end
|
|
116
|
+
if ratio > 1.0
|
|
117
|
+
ratio = 1.0
|
|
118
|
+
end
|
|
119
|
+
range = @max_val - @min_val
|
|
120
|
+
new_val = @min_val + ratio * range
|
|
121
|
+
if new_val != @value
|
|
122
|
+
@value = new_val
|
|
123
|
+
@change_handler.call(@value) if @change_handler
|
|
124
|
+
mark_dirty
|
|
125
|
+
update
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Top-level helper
|
|
131
|
+
def Slider(min_val, max_val)
|
|
132
|
+
Slider.new(min_val, max_val)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Kumiki
|
|
2
|
+
# Switch widget - ON/OFF toggle
|
|
3
|
+
|
|
4
|
+
class Switch < Widget
|
|
5
|
+
def initialize
|
|
6
|
+
super()
|
|
7
|
+
@on = false
|
|
8
|
+
@change_handler = nil
|
|
9
|
+
# Colors (Tokyo Night)
|
|
10
|
+
@on_color = 0xFF7AA2F7
|
|
11
|
+
@off_color = 0xFF414868
|
|
12
|
+
@knob_color = 0xFFFFFFFF
|
|
13
|
+
@hovered = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def with_on(v)
|
|
17
|
+
@on = v
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_change(&block)
|
|
22
|
+
@change_handler = block
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def is_on
|
|
27
|
+
@on
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def measure(painter)
|
|
31
|
+
Size.new(44.0, 24.0)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def redraw(painter, completely)
|
|
35
|
+
w = 44.0
|
|
36
|
+
h = 24.0
|
|
37
|
+
r = h / 2.0
|
|
38
|
+
|
|
39
|
+
# Track
|
|
40
|
+
track_color = @on ? @on_color : @off_color
|
|
41
|
+
painter.fill_round_rect(0.0, 0.0, w, h, r, track_color)
|
|
42
|
+
|
|
43
|
+
# Knob
|
|
44
|
+
knob_r = 9.0
|
|
45
|
+
knob_y = h / 2.0
|
|
46
|
+
knob_x = 0.0
|
|
47
|
+
if @on
|
|
48
|
+
knob_x = w - r
|
|
49
|
+
else
|
|
50
|
+
knob_x = r
|
|
51
|
+
end
|
|
52
|
+
painter.fill_circle(knob_x, knob_y, knob_r, @knob_color)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def mouse_up(ev)
|
|
56
|
+
if @on
|
|
57
|
+
@on = false
|
|
58
|
+
else
|
|
59
|
+
@on = true
|
|
60
|
+
end
|
|
61
|
+
@change_handler.call(@on) if @change_handler
|
|
62
|
+
mark_dirty
|
|
63
|
+
update
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def mouse_over
|
|
67
|
+
@hovered = true
|
|
68
|
+
mark_dirty
|
|
69
|
+
update
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def mouse_out
|
|
73
|
+
@hovered = false
|
|
74
|
+
mark_dirty
|
|
75
|
+
update
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Top-level helper
|
|
80
|
+
def Switch()
|
|
81
|
+
Switch.new
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|