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,1034 @@
1
+ # Calendar - date picker widget
2
+ # Features: day/month/year view modes, navigation, selection highlight
3
+ # All date computation uses integer arithmetic (no Time/Date classes)
4
+
5
+ # View mode constants
6
+ CAL_DAYS = 0
7
+ CAL_MONTHS = 1
8
+ CAL_YEARS = 2
9
+
10
+ # Layout constants
11
+ CAL_CELL_SIZE = 36.0
12
+ CAL_HEADER_HEIGHT = 40.0
13
+ CAL_WEEKDAY_HEIGHT = 24.0
14
+ CAL_NAV_BUTTON_W = 36.0
15
+
16
+ # ===== Date Utility Functions =====
17
+
18
+ def cal_is_leap(year)
19
+ if year % 400 == 0
20
+ return true
21
+ end
22
+ if year % 100 == 0
23
+ return false
24
+ end
25
+ if year % 4 == 0
26
+ return true
27
+ end
28
+ false
29
+ end
30
+
31
+ def cal_days_in_month(year, month)
32
+ if month == 1
33
+ return 31
34
+ end
35
+ if month == 2
36
+ if cal_is_leap(year)
37
+ return 29
38
+ end
39
+ return 28
40
+ end
41
+ if month == 3
42
+ return 31
43
+ end
44
+ if month == 4
45
+ return 30
46
+ end
47
+ if month == 5
48
+ return 31
49
+ end
50
+ if month == 6
51
+ return 30
52
+ end
53
+ if month == 7
54
+ return 31
55
+ end
56
+ if month == 8
57
+ return 31
58
+ end
59
+ if month == 9
60
+ return 30
61
+ end
62
+ if month == 10
63
+ return 31
64
+ end
65
+ if month == 11
66
+ return 30
67
+ end
68
+ 31
69
+ end
70
+
71
+ # Sakamoto's algorithm for day of week (0=Sunday, 1=Monday, ..., 6=Saturday)
72
+ def cal_day_of_week(year, month, day)
73
+ # t lookup table
74
+ t0 = 0
75
+ t1 = 3
76
+ t2 = 2
77
+ t3 = 5
78
+ t4 = 0
79
+ t5 = 3
80
+ t6 = 5
81
+ t7 = 1
82
+ t8 = 4
83
+ t9 = 6
84
+ t10 = 2
85
+ t11 = 4
86
+ y = year
87
+ if month < 3
88
+ y = y - 1
89
+ end
90
+ t_val = 0
91
+ if month == 1
92
+ t_val = t0
93
+ elsif month == 2
94
+ t_val = t1
95
+ elsif month == 3
96
+ t_val = t2
97
+ elsif month == 4
98
+ t_val = t3
99
+ elsif month == 5
100
+ t_val = t4
101
+ elsif month == 6
102
+ t_val = t5
103
+ elsif month == 7
104
+ t_val = t6
105
+ elsif month == 8
106
+ t_val = t7
107
+ elsif month == 9
108
+ t_val = t8
109
+ elsif month == 10
110
+ t_val = t9
111
+ elsif month == 11
112
+ t_val = t10
113
+ else
114
+ t_val = t11
115
+ end
116
+ (y + y / 4 - y / 100 + y / 400 + t_val + day) % 7
117
+ end
118
+
119
+ def cal_month_name(month)
120
+ if month == 1
121
+ return "January"
122
+ end
123
+ if month == 2
124
+ return "February"
125
+ end
126
+ if month == 3
127
+ return "March"
128
+ end
129
+ if month == 4
130
+ return "April"
131
+ end
132
+ if month == 5
133
+ return "May"
134
+ end
135
+ if month == 6
136
+ return "June"
137
+ end
138
+ if month == 7
139
+ return "July"
140
+ end
141
+ if month == 8
142
+ return "August"
143
+ end
144
+ if month == 9
145
+ return "September"
146
+ end
147
+ if month == 10
148
+ return "October"
149
+ end
150
+ if month == 11
151
+ return "November"
152
+ end
153
+ "December"
154
+ end
155
+
156
+ def cal_short_month_name(month)
157
+ if month == 1
158
+ return "Jan"
159
+ end
160
+ if month == 2
161
+ return "Feb"
162
+ end
163
+ if month == 3
164
+ return "Mar"
165
+ end
166
+ if month == 4
167
+ return "Apr"
168
+ end
169
+ if month == 5
170
+ return "May"
171
+ end
172
+ if month == 6
173
+ return "Jun"
174
+ end
175
+ if month == 7
176
+ return "Jul"
177
+ end
178
+ if month == 8
179
+ return "Aug"
180
+ end
181
+ if month == 9
182
+ return "Sep"
183
+ end
184
+ if month == 10
185
+ return "Oct"
186
+ end
187
+ if month == 11
188
+ return "Nov"
189
+ end
190
+ "Dec"
191
+ end
192
+
193
+ def cal_int_to_str(n)
194
+ if n == 1
195
+ return "1"
196
+ end
197
+ if n == 2
198
+ return "2"
199
+ end
200
+ if n == 3
201
+ return "3"
202
+ end
203
+ if n == 4
204
+ return "4"
205
+ end
206
+ if n == 5
207
+ return "5"
208
+ end
209
+ if n == 6
210
+ return "6"
211
+ end
212
+ if n == 7
213
+ return "7"
214
+ end
215
+ if n == 8
216
+ return "8"
217
+ end
218
+ if n == 9
219
+ return "9"
220
+ end
221
+ if n == 10
222
+ return "10"
223
+ end
224
+ if n == 11
225
+ return "11"
226
+ end
227
+ if n == 12
228
+ return "12"
229
+ end
230
+ if n == 13
231
+ return "13"
232
+ end
233
+ if n == 14
234
+ return "14"
235
+ end
236
+ if n == 15
237
+ return "15"
238
+ end
239
+ if n == 16
240
+ return "16"
241
+ end
242
+ if n == 17
243
+ return "17"
244
+ end
245
+ if n == 18
246
+ return "18"
247
+ end
248
+ if n == 19
249
+ return "19"
250
+ end
251
+ if n == 20
252
+ return "20"
253
+ end
254
+ if n == 21
255
+ return "21"
256
+ end
257
+ if n == 22
258
+ return "22"
259
+ end
260
+ if n == 23
261
+ return "23"
262
+ end
263
+ if n == 24
264
+ return "24"
265
+ end
266
+ if n == 25
267
+ return "25"
268
+ end
269
+ if n == 26
270
+ return "26"
271
+ end
272
+ if n == 27
273
+ return "27"
274
+ end
275
+ if n == 28
276
+ return "28"
277
+ end
278
+ if n == 29
279
+ return "29"
280
+ end
281
+ if n == 30
282
+ return "30"
283
+ end
284
+ "31"
285
+ end
286
+
287
+ # Convert year (integer) to string for display
288
+ # Simple lookup for common years, digit construction for others
289
+ def cal_year_to_str(year)
290
+ # Common decade prefix
291
+ prefix = ""
292
+ remainder = year
293
+ if year >= 2000
294
+ if year < 2100
295
+ prefix = "20"
296
+ remainder = year - 2000
297
+ end
298
+ end
299
+ if prefix == ""
300
+ if year >= 1900
301
+ if year < 2000
302
+ prefix = "19"
303
+ remainder = year - 1900
304
+ end
305
+ end
306
+ end
307
+ if prefix == ""
308
+ return "????"
309
+ end
310
+ # Convert 0-99 to two-digit string
311
+ tens = remainder / 10
312
+ ones = remainder % 10
313
+ tens_s = cal_digit_str(tens)
314
+ ones_s = cal_digit_str(ones)
315
+ prefix + tens_s + ones_s
316
+ end
317
+
318
+ def cal_digit_str(d)
319
+ if d == 0
320
+ return "0"
321
+ end
322
+ if d == 1
323
+ return "1"
324
+ end
325
+ if d == 2
326
+ return "2"
327
+ end
328
+ if d == 3
329
+ return "3"
330
+ end
331
+ if d == 4
332
+ return "4"
333
+ end
334
+ if d == 5
335
+ return "5"
336
+ end
337
+ if d == 6
338
+ return "6"
339
+ end
340
+ if d == 7
341
+ return "7"
342
+ end
343
+ if d == 8
344
+ return "8"
345
+ end
346
+ "9"
347
+ end
348
+
349
+ # JVM-safe float to int conversion (truncation)
350
+ # Avoids .to_i which is not available on JVM
351
+ def cal_floor(f)
352
+ i = 0
353
+ if f < 0.0
354
+ return 0
355
+ end
356
+ while i * 1.0 <= f
357
+ i = i + 1
358
+ end
359
+ i - 1
360
+ end
361
+
362
+ # ===== CalendarState =====
363
+
364
+ class CalendarState < ObservableBase
365
+ def initialize(year, month, day)
366
+ super()
367
+ @sel_year = year
368
+ @sel_month = month
369
+ @sel_day = day
370
+ @view_year = year
371
+ @view_month = month
372
+ @view_mode = CAL_DAYS
373
+ @on_change_cb = nil
374
+ end
375
+
376
+ def sel_year
377
+ @sel_year
378
+ end
379
+
380
+ def sel_month
381
+ @sel_month
382
+ end
383
+
384
+ def sel_day
385
+ @sel_day
386
+ end
387
+
388
+ def view_year
389
+ @view_year
390
+ end
391
+
392
+ def view_month
393
+ @view_month
394
+ end
395
+
396
+ def view_mode
397
+ @view_mode
398
+ end
399
+
400
+ def prev_month
401
+ @view_month = @view_month - 1
402
+ if @view_month < 1
403
+ @view_month = 12
404
+ @view_year = @view_year - 1
405
+ end
406
+ notify_observers
407
+ end
408
+
409
+ def next_month
410
+ @view_month = @view_month + 1
411
+ if @view_month > 12
412
+ @view_month = 1
413
+ @view_year = @view_year + 1
414
+ end
415
+ notify_observers
416
+ end
417
+
418
+ def prev_year
419
+ @view_year = @view_year - 1
420
+ notify_observers
421
+ end
422
+
423
+ def next_year
424
+ @view_year = @view_year + 1
425
+ notify_observers
426
+ end
427
+
428
+ def prev_year_page
429
+ @view_year = @view_year - 20
430
+ notify_observers
431
+ end
432
+
433
+ def next_year_page
434
+ @view_year = @view_year + 20
435
+ notify_observers
436
+ end
437
+
438
+ def select_date(year, month, day)
439
+ @sel_year = year
440
+ @sel_month = month
441
+ @sel_day = day
442
+ @view_year = year
443
+ @view_month = month
444
+ @view_mode = CAL_DAYS
445
+ if @on_change_cb != nil
446
+ @on_change_cb.call(year, month, day)
447
+ end
448
+ notify_observers
449
+ end
450
+
451
+ def select_month(month)
452
+ @view_month = month
453
+ @view_mode = CAL_DAYS
454
+ notify_observers
455
+ end
456
+
457
+ def select_year(year)
458
+ @view_year = year
459
+ @view_mode = CAL_MONTHS
460
+ notify_observers
461
+ end
462
+
463
+ def set_view_mode(mode)
464
+ @view_mode = mode
465
+ notify_observers
466
+ end
467
+
468
+ def formatted_date
469
+ m = @sel_month
470
+ d = @sel_day
471
+ mname = cal_month_name(m)
472
+ dstr = cal_int_to_str(d)
473
+ mname + " " + dstr
474
+ end
475
+
476
+ # ===== Wrapper methods for JVM type safety =====
477
+ # These methods access @view_year/@view_month etc. as fields (properly typed as :i64)
478
+ # instead of passing them as method call results (which are Object on JVM)
479
+
480
+ def days_in_current_month
481
+ cal_days_in_month(@view_year, @view_month)
482
+ end
483
+
484
+ def first_weekday
485
+ cal_day_of_week(@view_year, @view_month, 1)
486
+ end
487
+
488
+ def header_text
489
+ mn = cal_month_name(@view_month)
490
+ yr_str = cal_year_to_str(@view_year)
491
+ mn + " " + yr_str
492
+ end
493
+
494
+ def is_current_day(day)
495
+ if day != @sel_day
496
+ return false
497
+ end
498
+ if @view_month != @sel_month
499
+ return false
500
+ end
501
+ if @view_year != @sel_year
502
+ return false
503
+ end
504
+ true
505
+ end
506
+
507
+ def year_label(y)
508
+ # y comes via RubyDispatch so it's already a valid Integer
509
+ # But on JVM, invokestatic cal_year_to_str expects long
510
+ # For CalendarState's own use, we use view_year_label below
511
+ cal_year_to_str(y)
512
+ end
513
+
514
+ def view_year_label
515
+ cal_year_to_str(@view_year)
516
+ end
517
+
518
+ def year_range_text
519
+ start_y = @view_year - (@view_year % 20)
520
+ end_y = start_y + 19
521
+ sy_str = cal_year_to_str(start_y)
522
+ ey_str = cal_year_to_str(end_y)
523
+ sy_str + " - " + ey_str
524
+ end
525
+
526
+ def year_range_start
527
+ @view_year - (@view_year % 20)
528
+ end
529
+
530
+ def is_year_selected(year)
531
+ year == @sel_year
532
+ end
533
+
534
+ def is_month_selected(month)
535
+ month == @sel_month
536
+ end
537
+
538
+ def year_at_offset(i)
539
+ start = @view_year - (@view_year % 20)
540
+ start + i
541
+ end
542
+
543
+ def on_change(cb)
544
+ @on_change_cb = cb
545
+ self
546
+ end
547
+
548
+ def try_select_cell(row, col)
549
+ first_dow = first_weekday
550
+ cell_idx = row * 7 + col
551
+ day = cell_idx - first_dow + 1
552
+ days = days_in_current_month
553
+ if day >= 1
554
+ if day <= days
555
+ select_date(@view_year, @view_month, day)
556
+ end
557
+ end
558
+ end
559
+ end
560
+
561
+ # ===== Calendar Widget =====
562
+
563
+ class Calendar < Widget
564
+ def initialize(state)
565
+ super()
566
+ @state = state
567
+ @hover_cell = -1
568
+ @width_policy = FIXED
569
+ @height_policy = FIXED
570
+ @width = 7.0 * CAL_CELL_SIZE + 16.0
571
+ @height = CAL_HEADER_HEIGHT + CAL_WEEKDAY_HEIGHT + 6.0 * CAL_CELL_SIZE + 8.0
572
+ @state.attach(self)
573
+ end
574
+
575
+ def on_attach(observable)
576
+ end
577
+
578
+ def on_detach(observable)
579
+ end
580
+
581
+ def on_notify
582
+ mark_dirty
583
+ update
584
+ end
585
+
586
+ def redraw(painter, completely)
587
+ # Background
588
+ bg = $theme.bg_secondary
589
+ painter.fill_round_rect(0.0, 0.0, @width, @height, 8.0, bg)
590
+ # Border
591
+ bc = $theme.border
592
+ painter.stroke_rect(0.0, 0.0, @width, @height, bc, 1.0)
593
+
594
+ draw_cal_header(painter)
595
+
596
+ mode = @state.view_mode
597
+ if mode == CAL_DAYS
598
+ draw_days_view(painter)
599
+ elsif mode == CAL_MONTHS
600
+ draw_months_view(painter)
601
+ else
602
+ draw_years_view(painter)
603
+ end
604
+ end
605
+
606
+ def draw_cal_header(painter)
607
+ # Navigation: < [Title] >
608
+ tc = $theme.text_primary
609
+ ac = $theme.accent
610
+ ascent = painter.get_text_ascent($theme.font_family, 14.0)
611
+ mh = painter.measure_text_height($theme.font_family, 14.0)
612
+ ty = (CAL_HEADER_HEIGHT - mh) / 2.0 + ascent
613
+
614
+ # Left arrow
615
+ left_c = tc
616
+ if @hover_cell == -2
617
+ left_c = ac
618
+ end
619
+ painter.draw_text("<", 12.0, ty, $theme.font_family, 14.0, left_c)
620
+
621
+ # Right arrow
622
+ right_c = tc
623
+ if @hover_cell == -3
624
+ right_c = ac
625
+ end
626
+ rw = painter.measure_text_width(">", $theme.font_family, 14.0)
627
+ painter.draw_text(">", @width - 12.0 - rw, ty, $theme.font_family, 14.0, right_c)
628
+
629
+ # Title (clickable to change view mode)
630
+ title = cal_header_title
631
+ title_c = ac
632
+ tw = painter.measure_text_width(title, $theme.font_family, 14.0)
633
+ tx = (@width - tw) / 2.0
634
+ painter.draw_text(title, tx, ty, $theme.font_family, 14.0, title_c)
635
+ end
636
+
637
+ def cal_header_title
638
+ mode = @state.view_mode
639
+ if mode == CAL_DAYS
640
+ return @state.header_text
641
+ end
642
+ if mode == CAL_MONTHS
643
+ return @state.view_year_label
644
+ end
645
+ @state.year_range_text
646
+ end
647
+
648
+ # ===== DAYS VIEW =====
649
+
650
+ def draw_days_view(painter)
651
+ draw_weekday_headers(painter)
652
+ draw_day_cells(painter)
653
+ end
654
+
655
+ def draw_weekday_headers(painter)
656
+ headers = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
657
+ lc = $theme.text_secondary
658
+ ascent = painter.get_text_ascent($theme.font_family, 11.0)
659
+ base_y = CAL_HEADER_HEIGHT + ascent + 4.0
660
+ i = 0
661
+ while i < 7
662
+ label = headers[i]
663
+ lw = painter.measure_text_width(label, $theme.font_family, 11.0)
664
+ cx = 8.0 + i * 1.0 * CAL_CELL_SIZE + CAL_CELL_SIZE / 2.0 - lw / 2.0
665
+ painter.draw_text(label, cx, base_y, $theme.font_family, 11.0, lc)
666
+ i = i + 1
667
+ end
668
+ end
669
+
670
+ def draw_day_cells(painter)
671
+ days = @state.days_in_current_month
672
+ first_dow = @state.first_weekday
673
+ base_y = CAL_HEADER_HEIGHT + CAL_WEEKDAY_HEIGHT
674
+ # Draw 6 rows x 7 cols grid, only draw valid days
675
+ draw_day_row(painter, 0, first_dow, days, base_y)
676
+ draw_day_row(painter, 1, first_dow, days, base_y)
677
+ draw_day_row(painter, 2, first_dow, days, base_y)
678
+ draw_day_row(painter, 3, first_dow, days, base_y)
679
+ draw_day_row(painter, 4, first_dow, days, base_y)
680
+ draw_day_row(painter, 5, first_dow, days, base_y)
681
+ end
682
+
683
+ def draw_day_row(painter, row, first_dow, days, base_y)
684
+ col = 0
685
+ while col < 7
686
+ cell_idx = row * 7 + col
687
+ day = cell_idx - first_dow + 1
688
+ if day >= 1
689
+ if day <= days
690
+ cx = 8.0 + col * 1.0 * CAL_CELL_SIZE
691
+ cy = base_y + row * 1.0 * CAL_CELL_SIZE
692
+ draw_day_bg(painter, day, cell_idx, cx, cy)
693
+ draw_day_label(painter, day, cx, cy)
694
+ end
695
+ end
696
+ col = col + 1
697
+ end
698
+ end
699
+
700
+ def draw_day_bg(painter, day, cell_idx, cx, cy)
701
+ sel = is_day_selected(day)
702
+ circle_r = CAL_CELL_SIZE / 2.0 - 2.0
703
+ circle_cx = cx + CAL_CELL_SIZE / 2.0
704
+ circle_cy = cy + CAL_CELL_SIZE / 2.0
705
+ if sel
706
+ painter.fill_circle(circle_cx, circle_cy, circle_r, $theme.accent)
707
+ elsif @hover_cell == cell_idx
708
+ hc = painter.with_alpha($theme.accent, 40)
709
+ painter.fill_circle(circle_cx, circle_cy, circle_r, hc)
710
+ end
711
+ end
712
+
713
+ def is_day_selected(day)
714
+ @state.is_current_day(day)
715
+ end
716
+
717
+ def draw_day_label(painter, day, cx, cy)
718
+ ascent = painter.get_text_ascent($theme.font_family, 13.0)
719
+ mh = painter.measure_text_height($theme.font_family, 13.0)
720
+ label = painter.number_to_string(day * 1.0)
721
+ lw = painter.measure_text_width(label, $theme.font_family, 13.0)
722
+ lx = cx + CAL_CELL_SIZE / 2.0 - lw / 2.0
723
+ ly = cy + (CAL_CELL_SIZE - mh) / 2.0 + ascent
724
+ tc = $theme.text_primary
725
+ if is_day_selected(day)
726
+ tc = 4294967295
727
+ end
728
+ painter.draw_text(label, lx, ly, $theme.font_family, 13.0, tc)
729
+ end
730
+
731
+ # ===== MONTHS VIEW =====
732
+
733
+ def draw_months_view(painter)
734
+ ascent = painter.get_text_ascent($theme.font_family, 13.0)
735
+ mh = painter.measure_text_height($theme.font_family, 13.0)
736
+ base_y = CAL_HEADER_HEIGHT + 8.0
737
+ cell_w = (@width - 16.0) / 3.0
738
+ cell_h = (@height - CAL_HEADER_HEIGHT - 16.0) / 4.0
739
+
740
+ m = 1
741
+ while m <= 12
742
+ row = (m - 1) / 3
743
+ col = (m - 1) % 3
744
+ cx = 8.0 + col * 1.0 * cell_w
745
+ cy = base_y + row * 1.0 * cell_h
746
+
747
+ is_sel = @state.is_month_selected(m)
748
+ cell_idx = 100 + m
749
+ is_hover = (@hover_cell == cell_idx)
750
+
751
+ if is_sel
752
+ painter.fill_round_rect(cx + 2.0, cy + 2.0, cell_w - 4.0, cell_h - 4.0, 6.0, $theme.accent)
753
+ elsif is_hover
754
+ hc = painter.with_alpha($theme.accent, 40)
755
+ painter.fill_round_rect(cx + 2.0, cy + 2.0, cell_w - 4.0, cell_h - 4.0, 6.0, hc)
756
+ end
757
+
758
+ label = cal_short_month_name(m)
759
+ lw = painter.measure_text_width(label, $theme.font_family, 13.0)
760
+ lx = cx + cell_w / 2.0 - lw / 2.0
761
+ ly = cy + cell_h / 2.0 - mh / 2.0 + ascent
762
+
763
+ tc = $theme.text_primary
764
+ if is_sel
765
+ tc = 4294967295
766
+ end
767
+ painter.draw_text(label, lx, ly, $theme.font_family, 13.0, tc)
768
+ m = m + 1
769
+ end
770
+ end
771
+
772
+ # ===== YEARS VIEW =====
773
+
774
+ def draw_years_view(painter)
775
+ ascent = painter.get_text_ascent($theme.font_family, 12.0)
776
+ mh = painter.measure_text_height($theme.font_family, 12.0)
777
+ base_y = CAL_HEADER_HEIGHT + 8.0
778
+ cell_w = (@width - 16.0) / 4.0
779
+ cell_h = (@height - CAL_HEADER_HEIGHT - 16.0) / 5.0
780
+
781
+ i = 0
782
+ while i < 20
783
+ year = @state.year_at_offset(i)
784
+ row = i / 4
785
+ col = i % 4
786
+ cx = 8.0 + col * 1.0 * cell_w
787
+ cy = base_y + row * 1.0 * cell_h
788
+
789
+ is_sel = @state.is_year_selected(year)
790
+ cell_idx = 200 + i
791
+ is_hover = (@hover_cell == cell_idx)
792
+
793
+ if is_sel
794
+ painter.fill_round_rect(cx + 2.0, cy + 2.0, cell_w - 4.0, cell_h - 4.0, 6.0, $theme.accent)
795
+ elsif is_hover
796
+ hc = painter.with_alpha($theme.accent, 40)
797
+ painter.fill_round_rect(cx + 2.0, cy + 2.0, cell_w - 4.0, cell_h - 4.0, 6.0, hc)
798
+ end
799
+
800
+ label = painter.number_to_string(year * 1.0)
801
+ lw = painter.measure_text_width(label, $theme.font_family, 12.0)
802
+ lx = cx + cell_w / 2.0 - lw / 2.0
803
+ ly = cy + cell_h / 2.0 - mh / 2.0 + ascent
804
+
805
+ tc = $theme.text_primary
806
+ if is_sel
807
+ tc = 4294967295
808
+ end
809
+ painter.draw_text(label, lx, ly, $theme.font_family, 12.0, tc)
810
+ i = i + 1
811
+ end
812
+ end
813
+
814
+ # ===== EVENT HANDLERS =====
815
+
816
+ def mouse_up(ev)
817
+ mx = ev.pos.x
818
+ my = ev.pos.y
819
+
820
+ # Header navigation
821
+ if my < CAL_HEADER_HEIGHT
822
+ handle_header_click(mx)
823
+ return
824
+ end
825
+
826
+ mode = @state.view_mode
827
+ if mode == CAL_DAYS
828
+ handle_day_click(mx, my)
829
+ elsif mode == CAL_MONTHS
830
+ handle_month_click(mx, my)
831
+ else
832
+ handle_year_click(mx, my)
833
+ end
834
+ end
835
+
836
+ def handle_header_click(mx)
837
+ # Left arrow
838
+ if mx < CAL_NAV_BUTTON_W
839
+ mode = @state.view_mode
840
+ if mode == CAL_DAYS
841
+ @state.prev_month
842
+ elsif mode == CAL_MONTHS
843
+ @state.prev_year
844
+ else
845
+ @state.prev_year_page
846
+ end
847
+ return
848
+ end
849
+ # Right arrow
850
+ if mx > @width - CAL_NAV_BUTTON_W
851
+ mode = @state.view_mode
852
+ if mode == CAL_DAYS
853
+ @state.next_month
854
+ elsif mode == CAL_MONTHS
855
+ @state.next_year
856
+ else
857
+ @state.next_year_page
858
+ end
859
+ return
860
+ end
861
+ # Title click -> cycle view mode
862
+ mode = @state.view_mode
863
+ if mode == CAL_DAYS
864
+ @state.set_view_mode(CAL_MONTHS)
865
+ elsif mode == CAL_MONTHS
866
+ @state.set_view_mode(CAL_YEARS)
867
+ end
868
+ end
869
+
870
+ def handle_day_click(mx, my)
871
+ base_y = CAL_HEADER_HEIGHT + CAL_WEEKDAY_HEIGHT
872
+ if my >= base_y
873
+ handle_day_click_inner(mx, my, base_y)
874
+ end
875
+ end
876
+
877
+ def handle_day_click_inner(mx, my, base_y)
878
+ col = cal_floor((mx - 8.0) / CAL_CELL_SIZE)
879
+ row = cal_floor((my - base_y) / CAL_CELL_SIZE)
880
+ if col >= 0
881
+ if col <= 6
882
+ if row >= 0
883
+ if row <= 5
884
+ try_select_day(row, col)
885
+ end
886
+ end
887
+ end
888
+ end
889
+ end
890
+
891
+ def try_select_day(row, col)
892
+ @state.try_select_cell(row, col)
893
+ end
894
+
895
+ def handle_month_click(mx, my)
896
+ base_y = CAL_HEADER_HEIGHT + 8.0
897
+ cell_w = (@width - 16.0) / 3.0
898
+ cell_h = (@height - CAL_HEADER_HEIGHT - 16.0) / 4.0
899
+ col = cal_floor((mx - 8.0) / cell_w)
900
+ row = cal_floor((my - base_y) / cell_h)
901
+ if col >= 0
902
+ if col <= 2
903
+ if row >= 0
904
+ if row <= 3
905
+ month = row * 3 + col + 1
906
+ @state.select_month(month)
907
+ end
908
+ end
909
+ end
910
+ end
911
+ end
912
+
913
+ def handle_year_click(mx, my)
914
+ base_y = CAL_HEADER_HEIGHT + 8.0
915
+ cell_w = (@width - 16.0) / 4.0
916
+ cell_h = (@height - CAL_HEADER_HEIGHT - 16.0) / 5.0
917
+ col = cal_floor((mx - 8.0) / cell_w)
918
+ row = cal_floor((my - base_y) / cell_h)
919
+ if col >= 0
920
+ if col <= 3
921
+ if row >= 0
922
+ if row <= 4
923
+ i = row * 4 + col
924
+ year = @state.year_at_offset(i)
925
+ @state.select_year(year)
926
+ end
927
+ end
928
+ end
929
+ end
930
+ end
931
+
932
+ def cursor_pos(ev)
933
+ mx = ev.pos.x
934
+ my = ev.pos.y
935
+ old_hover = @hover_cell
936
+ @hover_cell = compute_hover_cell(mx, my)
937
+ if @hover_cell != old_hover
938
+ mark_dirty
939
+ update
940
+ end
941
+ end
942
+
943
+ def compute_hover_cell(mx, my)
944
+ if my < CAL_HEADER_HEIGHT
945
+ return compute_header_hover(mx)
946
+ end
947
+ mode = @state.view_mode
948
+ if mode == CAL_DAYS
949
+ return compute_day_hover(mx, my)
950
+ end
951
+ if mode == CAL_MONTHS
952
+ return compute_month_hover(mx, my)
953
+ end
954
+ compute_year_hover(mx, my)
955
+ end
956
+
957
+ def compute_header_hover(mx)
958
+ if mx < CAL_NAV_BUTTON_W
959
+ return -2
960
+ end
961
+ if mx > @width - CAL_NAV_BUTTON_W
962
+ return -3
963
+ end
964
+ -4
965
+ end
966
+
967
+ def compute_day_hover(mx, my)
968
+ base_y = CAL_HEADER_HEIGHT + CAL_WEEKDAY_HEIGHT
969
+ if my < base_y
970
+ return -1
971
+ end
972
+ col = cal_floor((mx - 8.0) / CAL_CELL_SIZE)
973
+ row = cal_floor((my - base_y) / CAL_CELL_SIZE)
974
+ if col >= 0
975
+ if col < 7
976
+ if row >= 0
977
+ if row < 6
978
+ return row * 7 + col
979
+ end
980
+ end
981
+ end
982
+ end
983
+ -1
984
+ end
985
+
986
+ def compute_month_hover(mx, my)
987
+ base_y = CAL_HEADER_HEIGHT + 8.0
988
+ cell_w = (@width - 16.0) / 3.0
989
+ cell_h = (@height - CAL_HEADER_HEIGHT - 16.0) / 4.0
990
+ col = cal_floor((mx - 8.0) / cell_w)
991
+ row = cal_floor((my - base_y) / cell_h)
992
+ if col >= 0
993
+ if col < 3
994
+ if row >= 0
995
+ if row < 4
996
+ return 100 + row * 3 + col + 1
997
+ end
998
+ end
999
+ end
1000
+ end
1001
+ -1
1002
+ end
1003
+
1004
+ def compute_year_hover(mx, my)
1005
+ base_y = CAL_HEADER_HEIGHT + 8.0
1006
+ cell_w = (@width - 16.0) / 4.0
1007
+ cell_h = (@height - CAL_HEADER_HEIGHT - 16.0) / 5.0
1008
+ col = cal_floor((mx - 8.0) / cell_w)
1009
+ row = cal_floor((my - base_y) / cell_h)
1010
+ if col >= 0
1011
+ if col < 4
1012
+ if row >= 0
1013
+ if row < 5
1014
+ return 200 + row * 4 + col
1015
+ end
1016
+ end
1017
+ end
1018
+ end
1019
+ -1
1020
+ end
1021
+
1022
+ def mouse_out
1023
+ if @hover_cell != -1
1024
+ @hover_cell = -1
1025
+ mark_dirty
1026
+ update
1027
+ end
1028
+ end
1029
+ end
1030
+
1031
+ # Top-level helper
1032
+ def Calendar(state)
1033
+ Calendar.new(state)
1034
+ end