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,667 @@
1
+ # DataTable - sortable, scrollable data table widget
2
+ # Features: column headers, sort indicators, row selection, hover highlight,
3
+ # virtual scroll (only visible rows rendered), alternating row colors,
4
+ # column resize by dragging header borders
5
+
6
+ DT_HEADER_HEIGHT = 32.0
7
+ DT_ROW_HEIGHT = 28.0
8
+ DT_SCROLLBAR_WIDTH = 8.0
9
+ DT_RESIZE_ZONE = 5.0
10
+ DT_MIN_COL_WIDTH = 30.0
11
+
12
+ # Sort direction constants
13
+ DT_SORT_NONE = 0
14
+ DT_SORT_ASC = 1
15
+ DT_SORT_DESC = 2
16
+
17
+ class DataTable < Widget
18
+ def initialize(col_names, col_widths, rows)
19
+ super()
20
+ @col_names = col_names # Array of String
21
+ @col_widths = col_widths # Array of Float
22
+ @rows = rows # Array of Array[String]
23
+ @sort_col = -1
24
+ @sort_dir = DT_SORT_NONE
25
+ @sorted_indices = nil
26
+ @selected_row = -1
27
+ @hover_row = -1
28
+ @hover_header = -1
29
+ @scroll_y = 0.0
30
+ @max_scroll = 0.0
31
+ @scrollable_flag = true
32
+ @width_policy = EXPANDING
33
+ @height_policy = EXPANDING
34
+ @font_size = 13.0
35
+ @header_font_size = 13.0
36
+ @num_cols = col_names.length
37
+ @resizing_col = -1
38
+ @resize_start_x = 0.0
39
+ @resize_start_width = 0.0
40
+ @resize_hover_col = -1
41
+ build_sorted_indices
42
+ end
43
+
44
+ def font_size(s)
45
+ @font_size = s
46
+ self
47
+ end
48
+
49
+ def header_font_size(s)
50
+ @header_font_size = s
51
+ self
52
+ end
53
+
54
+ def set_data(col_names, col_widths, rows)
55
+ @col_names = col_names
56
+ @col_widths = col_widths
57
+ @rows = rows
58
+ @sort_col = -1
59
+ @sort_dir = DT_SORT_NONE
60
+ @sorted_indices = nil
61
+ @selected_row = -1
62
+ @scroll_y = 0.0
63
+ @num_cols = col_names.length
64
+ build_sorted_indices
65
+ mark_dirty
66
+ update
67
+ end
68
+
69
+ def set_rows(rows)
70
+ @rows = rows
71
+ build_sorted_indices
72
+ if @sort_col >= 0
73
+ apply_sort
74
+ end
75
+ mark_dirty
76
+ update
77
+ end
78
+
79
+ def selected_row
80
+ @selected_row
81
+ end
82
+
83
+ def get_scrollable
84
+ @scrollable_flag
85
+ end
86
+
87
+ def redraw(painter, completely)
88
+ return if @num_cols == 0
89
+
90
+ visible_h = @height - DT_HEADER_HEIGHT
91
+ if visible_h < 0.0
92
+ visible_h = 0.0
93
+ end
94
+ compute_scroll(visible_h)
95
+
96
+ # Background
97
+ painter.fill_rect(0.0, 0.0, @width, @height, $theme.bg_primary)
98
+
99
+ draw_header(painter)
100
+ draw_rows(painter, visible_h)
101
+ draw_scrollbar(painter, visible_h)
102
+ end
103
+
104
+ def draw_header(painter)
105
+ bg = $theme.bg_secondary
106
+ header_bg = painter.darken_color(bg, 0.1)
107
+ painter.fill_rect(0.0, 0.0, @width, DT_HEADER_HEIGHT, header_bg)
108
+ draw_header_columns(painter)
109
+ bc = $theme.border
110
+ painter.draw_line(0.0, DT_HEADER_HEIGHT, @width, DT_HEADER_HEIGHT, bc, 1.0)
111
+ end
112
+
113
+ def draw_header_columns(painter)
114
+ hx = 0.0
115
+ ci = 0
116
+ while ci < @num_cols
117
+ col_w = @col_widths[ci]
118
+ col_name = @col_names[ci]
119
+ draw_one_header(painter, ci, hx, col_w, col_name)
120
+ hx = hx + col_w
121
+ ci = ci + 1
122
+ end
123
+ end
124
+
125
+ def draw_one_header(painter, ci, hx, col_w, col_name)
126
+ if @hover_header == ci
127
+ ac = $theme.accent
128
+ hc = painter.with_alpha(ac, 30)
129
+ painter.fill_rect(hx, 0.0, col_w, DT_HEADER_HEIGHT, hc)
130
+ end
131
+
132
+ painter.save
133
+ painter.clip_rect(hx, 0.0, col_w - 2.0, DT_HEADER_HEIGHT)
134
+
135
+ ascent = painter.get_text_ascent($theme.font_family, @header_font_size)
136
+ mh = painter.measure_text_height($theme.font_family, @header_font_size)
137
+ ty = (DT_HEADER_HEIGHT - mh) / 2.0 + ascent
138
+ tc = $theme.text_primary
139
+ painter.draw_text(col_name, hx + 8.0, ty, $theme.font_family, @header_font_size, tc)
140
+
141
+ if @sort_col == ci
142
+ draw_sort_indicator(painter, col_name, hx, ty)
143
+ end
144
+
145
+ painter.restore
146
+
147
+ sep_x = hx + col_w - 1.0
148
+ if @resize_hover_col == ci
149
+ ac = $theme.accent
150
+ painter.draw_line(sep_x, 0.0, sep_x, DT_HEADER_HEIGHT, ac, 2.0)
151
+ else
152
+ bc = $theme.border
153
+ painter.draw_line(sep_x, 0.0, sep_x, DT_HEADER_HEIGHT, bc, 1.0)
154
+ end
155
+ end
156
+
157
+ def draw_sort_indicator(painter, col_name, hx, ty)
158
+ nw = painter.measure_text_width(col_name, $theme.font_family, @header_font_size)
159
+ ix = hx + 8.0 + nw + 8.0
160
+ ac = $theme.accent
161
+ s = 4.0
162
+ cy = DT_HEADER_HEIGHT / 2.0
163
+ if @sort_dir == DT_SORT_ASC
164
+ # Up arrow
165
+ painter.fill_triangle(ix - s, cy + s / 2.0, ix + s, cy + s / 2.0, ix, cy - s / 2.0, ac)
166
+ else
167
+ # Down arrow
168
+ painter.fill_triangle(ix - s, cy - s / 2.0, ix + s, cy - s / 2.0, ix, cy + s / 2.0, ac)
169
+ end
170
+ end
171
+
172
+ def draw_rows(painter, visible_h)
173
+ painter.save
174
+ painter.clip_rect(0.0, DT_HEADER_HEIGHT, @width, visible_h)
175
+
176
+ first_row = (@scroll_y / DT_ROW_HEIGHT).to_i
177
+ if first_row < 0
178
+ first_row = 0
179
+ end
180
+ last_row = (((@scroll_y + visible_h) / DT_ROW_HEIGHT) + 1).to_i
181
+ if last_row > @rows.length
182
+ last_row = @rows.length
183
+ end
184
+
185
+ ri = first_row
186
+ while ri < last_row
187
+ draw_single_row(painter, ri)
188
+ ri = ri + 1
189
+ end
190
+
191
+ painter.restore
192
+ end
193
+
194
+ def draw_single_row(painter, ri)
195
+ actual_index = resolve_row_index(ri)
196
+ ri_f = ri * 1.0
197
+ row_y = DT_HEADER_HEIGHT + ri_f * DT_ROW_HEIGHT - @scroll_y
198
+
199
+ row_bg = compute_row_bg(painter, ri)
200
+ painter.fill_rect(0.0, row_y, @width, DT_ROW_HEIGHT, row_bg)
201
+
202
+ cx = 0.0
203
+ ci = 0
204
+ while ci < @num_cols
205
+ col_w = @col_widths[ci]
206
+ if actual_index < @rows.length
207
+ if ci < @rows[actual_index].length
208
+ cell_text = @rows[actual_index][ci]
209
+ painter.save
210
+ painter.clip_rect(cx, row_y, col_w - 2.0, DT_ROW_HEIGHT)
211
+ ascent = painter.get_text_ascent($theme.font_family, @font_size)
212
+ ty = row_y + (DT_ROW_HEIGHT - painter.measure_text_height($theme.font_family, @font_size)) / 2.0 + ascent
213
+ painter.draw_text(cell_text, cx + 8.0, ty, $theme.font_family, @font_size, $theme.text_primary)
214
+ painter.restore
215
+ end
216
+ end
217
+ cx = cx + col_w
218
+ ci = ci + 1
219
+ end
220
+
221
+ row_line_c = painter.with_alpha($theme.border, 40)
222
+ painter.draw_line(0.0, row_y + DT_ROW_HEIGHT, @width, row_y + DT_ROW_HEIGHT, row_line_c, 1.0)
223
+ end
224
+
225
+ def draw_scrollbar(painter, visible_h)
226
+ if @max_scroll <= 0.0
227
+ return
228
+ end
229
+ rows_len = @rows.length * 1.0
230
+ content_h = rows_len * DT_ROW_HEIGHT
231
+ sb_x = @width - DT_SCROLLBAR_WIDTH
232
+ sb_ratio = visible_h / content_h
233
+ sb_h = visible_h * sb_ratio
234
+ if sb_h < 20.0
235
+ sb_h = 20.0
236
+ end
237
+ sb_travel = visible_h - sb_h
238
+ sb_pos = 0.0
239
+ if @max_scroll > 0.0
240
+ sb_pos = (@scroll_y / @max_scroll) * sb_travel
241
+ end
242
+ painter.fill_rect(sb_x, DT_HEADER_HEIGHT, DT_SCROLLBAR_WIDTH, visible_h, $theme.scrollbar_bg)
243
+ painter.fill_round_rect(sb_x + 1.0, DT_HEADER_HEIGHT + sb_pos, DT_SCROLLBAR_WIDTH - 2.0, sb_h, 3.0, $theme.scrollbar_fg)
244
+ end
245
+
246
+ # --- Event Handlers ---
247
+
248
+ def mouse_down(ev)
249
+ mx = ev.pos.x
250
+ my = ev.pos.y
251
+ if my < DT_HEADER_HEIGHT
252
+ border_col = col_border_at_x(mx)
253
+ if border_col >= 0
254
+ @resizing_col = border_col
255
+ @resize_start_x = mx
256
+ @resize_start_width = @col_widths[border_col]
257
+ return
258
+ end
259
+ end
260
+ end
261
+
262
+ def mouse_up(ev)
263
+ if @resizing_col >= 0
264
+ @resizing_col = -1
265
+ return
266
+ end
267
+
268
+ mx = ev.pos.x
269
+ my = ev.pos.y
270
+
271
+ if my < DT_HEADER_HEIGHT
272
+ col = column_at_x(mx)
273
+ if col >= 0
274
+ toggle_sort(col)
275
+ end
276
+ return
277
+ end
278
+
279
+ row_idx = row_at_y(my)
280
+ if row_idx >= 0
281
+ if row_idx < @rows.length
282
+ @selected_row = row_idx
283
+ mark_dirty
284
+ update
285
+ end
286
+ end
287
+ end
288
+
289
+ def mouse_drag(ev)
290
+ if @resizing_col >= 0
291
+ mx = ev.pos.x
292
+ delta = mx - @resize_start_x
293
+ new_w = @resize_start_width + delta
294
+ if new_w < DT_MIN_COL_WIDTH
295
+ new_w = DT_MIN_COL_WIDTH
296
+ end
297
+ @col_widths[@resizing_col] = new_w
298
+ mark_dirty
299
+ update
300
+ end
301
+ end
302
+
303
+ def cursor_pos(ev)
304
+ mx = ev.pos.x
305
+ my = ev.pos.y
306
+ old_hr = @hover_row
307
+ old_hh = @hover_header
308
+ old_rhc = @resize_hover_col
309
+
310
+ if my < DT_HEADER_HEIGHT
311
+ @resize_hover_col = col_border_at_x(mx)
312
+ if @resize_hover_col >= 0
313
+ @hover_header = -1
314
+ else
315
+ @hover_header = column_at_x(mx)
316
+ end
317
+ @hover_row = -1
318
+ else
319
+ @hover_header = -1
320
+ @hover_row = row_at_y(my)
321
+ @resize_hover_col = -1
322
+ end
323
+
324
+ need_redraw = false
325
+ if @hover_row != old_hr
326
+ need_redraw = true
327
+ end
328
+ if @hover_header != old_hh
329
+ need_redraw = true
330
+ end
331
+ if @resize_hover_col != old_rhc
332
+ need_redraw = true
333
+ end
334
+ if need_redraw
335
+ mark_dirty
336
+ update
337
+ end
338
+ end
339
+
340
+ def mouse_out
341
+ changed = false
342
+ if @hover_row != -1
343
+ @hover_row = -1
344
+ changed = true
345
+ end
346
+ if @hover_header != -1
347
+ @hover_header = -1
348
+ changed = true
349
+ end
350
+ if @resize_hover_col != -1
351
+ @resize_hover_col = -1
352
+ changed = true
353
+ end
354
+ if changed
355
+ mark_dirty
356
+ update
357
+ end
358
+ end
359
+
360
+ def dispatch_to_scrollable(p, is_direction_x)
361
+ if contain(p)
362
+ [self, p]
363
+ else
364
+ [nil, nil]
365
+ end
366
+ end
367
+
368
+ def mouse_wheel(ev)
369
+ @scroll_y = @scroll_y - ev.delta_y * 30.0
370
+ if @scroll_y < 0.0
371
+ @scroll_y = 0.0
372
+ end
373
+ if @scroll_y > @max_scroll
374
+ @scroll_y = @max_scroll
375
+ end
376
+ mark_dirty
377
+ update
378
+ end
379
+
380
+ private
381
+
382
+ def resolve_row_index(ri)
383
+ if @sorted_indices != nil
384
+ @sorted_indices[ri]
385
+ else
386
+ ri
387
+ end
388
+ end
389
+
390
+ def compute_row_bg(painter, ri)
391
+ bg = $theme.bg_primary
392
+ if ri == @selected_row
393
+ ac = $theme.accent
394
+ bg = painter.with_alpha(ac, 50)
395
+ elsif ri == @hover_row
396
+ bg = painter.lighten_color(bg, 0.08)
397
+ elsif ri % 2 != 0
398
+ bg = painter.darken_color(bg, 0.05)
399
+ end
400
+ bg
401
+ end
402
+
403
+ def compute_scroll(visible_h)
404
+ rows_len = @rows.length * 1.0
405
+ content_h = rows_len * DT_ROW_HEIGHT
406
+ @max_scroll = content_h - visible_h
407
+ if @max_scroll < 0.0
408
+ @max_scroll = 0.0
409
+ end
410
+ if @scroll_y > @max_scroll
411
+ @scroll_y = @max_scroll
412
+ end
413
+ if @scroll_y < 0.0
414
+ @scroll_y = 0.0
415
+ end
416
+ end
417
+
418
+ def total_columns_width
419
+ total = 0.0
420
+ i = 0
421
+ while i < @num_cols
422
+ total = total + @col_widths[i]
423
+ i = i + 1
424
+ end
425
+ total
426
+ end
427
+
428
+ def column_at_x(x)
429
+ cx = 0.0
430
+ i = 0
431
+ while i < @num_cols
432
+ col_w = @col_widths[i]
433
+ if x >= cx
434
+ if x < cx + col_w
435
+ return i
436
+ end
437
+ end
438
+ cx = cx + col_w
439
+ i = i + 1
440
+ end
441
+ -1
442
+ end
443
+
444
+ def col_border_at_x(x)
445
+ cx = 0.0
446
+ i = 0
447
+ while i < @num_cols
448
+ cx = cx + @col_widths[i]
449
+ diff = x - cx
450
+ if diff < 0.0
451
+ diff = 0.0 - diff
452
+ end
453
+ if diff < DT_RESIZE_ZONE
454
+ return i
455
+ end
456
+ i = i + 1
457
+ end
458
+ -1
459
+ end
460
+
461
+ def row_at_y(y)
462
+ if y < DT_HEADER_HEIGHT
463
+ return -1
464
+ end
465
+ row = ((y - DT_HEADER_HEIGHT + @scroll_y) / DT_ROW_HEIGHT).to_i
466
+ if row < 0
467
+ row = -1
468
+ end
469
+ if row >= @rows.length
470
+ row = -1
471
+ end
472
+ row
473
+ end
474
+
475
+ def build_sorted_indices
476
+ @sorted_indices = []
477
+ i = 0
478
+ while i < @rows.length
479
+ @sorted_indices << i
480
+ i = i + 1
481
+ end
482
+ end
483
+
484
+ def toggle_sort(col)
485
+ if @sort_col == col
486
+ if @sort_dir == DT_SORT_ASC
487
+ @sort_dir = DT_SORT_DESC
488
+ elsif @sort_dir == DT_SORT_DESC
489
+ @sort_dir = DT_SORT_NONE
490
+ @sort_col = -1
491
+ build_sorted_indices
492
+ mark_dirty
493
+ update
494
+ return
495
+ else
496
+ @sort_dir = DT_SORT_ASC
497
+ end
498
+ else
499
+ @sort_col = col
500
+ @sort_dir = DT_SORT_ASC
501
+ end
502
+ apply_sort
503
+ mark_dirty
504
+ update
505
+ end
506
+
507
+ def apply_sort
508
+ return if @sort_col < 0
509
+ build_sorted_indices
510
+ col = @sort_col
511
+ dir = @sort_dir
512
+
513
+ n = @sorted_indices.length
514
+ i = 1
515
+ while i < n
516
+ key = @sorted_indices[i]
517
+ j = i - 1
518
+ keep_going = true
519
+ while keep_going
520
+ if j < 0
521
+ keep_going = false
522
+ else
523
+ idx_j = @sorted_indices[j]
524
+ # Inline: get cell values directly (no helper method calls)
525
+ val_a = ""
526
+ if idx_j < @rows.length
527
+ row_a = @rows[idx_j]
528
+ if col < row_a.length
529
+ val_a = row_a[col]
530
+ end
531
+ end
532
+ val_b = ""
533
+ if key < @rows.length
534
+ row_b = @rows[key]
535
+ if col < row_b.length
536
+ val_b = row_b[col]
537
+ end
538
+ end
539
+ cmp = (val_a <=> val_b)
540
+ if cmp == nil
541
+ cmp = 0
542
+ end
543
+ should_swap = false
544
+ if dir == DT_SORT_ASC
545
+ if cmp > 0
546
+ should_swap = true
547
+ end
548
+ else
549
+ if cmp < 0
550
+ should_swap = true
551
+ end
552
+ end
553
+ if should_swap
554
+ @sorted_indices[j + 1] = @sorted_indices[j]
555
+ j = j - 1
556
+ else
557
+ keep_going = false
558
+ end
559
+ end
560
+ end
561
+ @sorted_indices[j + 1] = key
562
+ i = i + 1
563
+ end
564
+ end
565
+
566
+ def compare_cells_idx(idx_a, idx_b, col, dir)
567
+ cmp_a = get_cell_value(idx_a, col)
568
+ cmp_b = get_cell_value(idx_b, col)
569
+
570
+ num_a = try_parse_float(cmp_a)
571
+ num_b = try_parse_float(cmp_b)
572
+ if num_a != nil
573
+ if num_b != nil
574
+ if dir == DT_SORT_ASC
575
+ return num_a > num_b
576
+ else
577
+ return num_a < num_b
578
+ end
579
+ end
580
+ end
581
+ # Use spaceship operator for string comparison (op_lt/op_gt not in RubyDispatch)
582
+ cmp = (cmp_a <=> cmp_b)
583
+ if cmp == nil
584
+ cmp = 0
585
+ end
586
+ if dir == DT_SORT_ASC
587
+ cmp > 0
588
+ else
589
+ cmp < 0
590
+ end
591
+ end
592
+
593
+ def get_cell_value(row_idx, col)
594
+ if row_idx < @rows.length
595
+ row = @rows[row_idx]
596
+ if col < row.length
597
+ return row[col]
598
+ end
599
+ end
600
+ ""
601
+ end
602
+
603
+ def is_digit(c)
604
+ if c == "0"
605
+ return true
606
+ end
607
+ if c == "1"
608
+ return true
609
+ end
610
+ if c == "2"
611
+ return true
612
+ end
613
+ if c == "3"
614
+ return true
615
+ end
616
+ if c == "4"
617
+ return true
618
+ end
619
+ if c == "5"
620
+ return true
621
+ end
622
+ if c == "6"
623
+ return true
624
+ end
625
+ if c == "7"
626
+ return true
627
+ end
628
+ if c == "8"
629
+ return true
630
+ end
631
+ if c == "9"
632
+ return true
633
+ end
634
+ false
635
+ end
636
+
637
+ def try_parse_float(s)
638
+ return nil if s == nil
639
+ return nil if s == ""
640
+ i = 0
641
+ has_dot = false
642
+ if i < s.length
643
+ if s[i] == "-"
644
+ i = i + 1
645
+ end
646
+ end
647
+ return nil if i >= s.length
648
+ while i < s.length
649
+ c = s[i]
650
+ if c == "."
651
+ return nil if has_dot
652
+ has_dot = true
653
+ elsif is_digit(c)
654
+ # valid digit, continue
655
+ else
656
+ return nil
657
+ end
658
+ i = i + 1
659
+ end
660
+ s.to_f
661
+ end
662
+ end
663
+
664
+ # Top-level helper
665
+ def DataTable(col_names, col_widths, rows)
666
+ DataTable.new(col_names, col_widths, rows)
667
+ end
@@ -0,0 +1,29 @@
1
+ # Divider widget - horizontal line separator
2
+ # Port of Castella Divider
3
+
4
+ class Divider < Widget
5
+ def initialize
6
+ super
7
+ @color_val = 0
8
+ @custom_color = false
9
+ @width_policy = EXPANDING
10
+ @height_policy = FIXED
11
+ @height = 1.0
12
+ end
13
+
14
+ def color(c)
15
+ @color_val = c
16
+ @custom_color = true
17
+ self
18
+ end
19
+
20
+ def redraw(painter, completely)
21
+ c = @custom_color ? @color_val : $theme.border
22
+ painter.draw_line(0.0, 0.0, @width, 0.0, c, 1.0)
23
+ end
24
+ end
25
+
26
+ # Top-level helper
27
+ def Divider()
28
+ Divider.new
29
+ end