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