hokusai-zero 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/Dockerfile +26 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +91 -0
  5. data/LICENSE +21 -0
  6. data/README.md +28 -0
  7. data/ast/genheader +3 -0
  8. data/ast/include/hashmap.c +1151 -0
  9. data/ast/include/hashmap.c.license +20 -0
  10. data/ast/include/hashmap.h +54 -0
  11. data/ast/src/core/ast.c +448 -0
  12. data/ast/src/core/ast.h +259 -0
  13. data/ast/src/core/common.h +24 -0
  14. data/ast/src/core/component.c +85 -0
  15. data/ast/src/core/component.h +35 -0
  16. data/ast/src/core/hml.c +665 -0
  17. data/ast/src/core/hml.h +11 -0
  18. data/ast/src/core/input.c +458 -0
  19. data/ast/src/core/input.h +118 -0
  20. data/ast/src/core/style.c +101 -0
  21. data/ast/src/core/style.h +41 -0
  22. data/ast/src/core/text.c +784 -0
  23. data/ast/src/core/text.h +93 -0
  24. data/ast/src/core/util.c +140 -0
  25. data/ast/src/core/util.h +48 -0
  26. data/ast/src/hokusai.c +6 -0
  27. data/ast/src/hokusai.h +6 -0
  28. data/ast/test/fixtures/test.ui +13 -0
  29. data/ast/test/greatest.h +1266 -0
  30. data/ast/test/hokusai.c +14 -0
  31. data/ast/test/parser.c +234 -0
  32. data/ast/test/text.c +116 -0
  33. data/ext/extconf.rb +27 -0
  34. data/grammar/Cargo.lock +80 -0
  35. data/grammar/Cargo.toml +26 -0
  36. data/grammar/binding.gyp +20 -0
  37. data/grammar/bindings/node/binding.cc +28 -0
  38. data/grammar/bindings/node/index.js +19 -0
  39. data/grammar/bindings/rust/build.rs +40 -0
  40. data/grammar/bindings/rust/lib.rs +52 -0
  41. data/grammar/corpus/1_document.txt +131 -0
  42. data/grammar/corpus/2_selectors.txt +58 -0
  43. data/grammar/corpus/3_spaces.txt +69 -0
  44. data/grammar/corpus/4_errors.txt +10 -0
  45. data/grammar/corpus/5_macros.txt +175 -0
  46. data/grammar/corpus/6_styles.txt +81 -0
  47. data/grammar/grammar.js +275 -0
  48. data/grammar/package-lock.json +34 -0
  49. data/grammar/package.json +33 -0
  50. data/grammar/src/grammar.json +1269 -0
  51. data/grammar/src/node-types.json +474 -0
  52. data/grammar/src/parser.c +5772 -0
  53. data/grammar/src/scanner.c +258 -0
  54. data/grammar/src/tree_sitter/parser.h +230 -0
  55. data/grammar/src/tree_sitter/scanner.h +12 -0
  56. data/grammar/test.nml +10 -0
  57. data/hokusai.gemspec +19 -0
  58. data/ui/examples/assets/DigitalDisplay.ttf +0 -0
  59. data/ui/examples/assets/OpenSans-Regular.ttf +0 -0
  60. data/ui/examples/assets/addy.png +0 -0
  61. data/ui/examples/assets/baby_sean.png +0 -0
  62. data/ui/examples/assets/football-troll.png +0 -0
  63. data/ui/examples/assets/gear.png +0 -0
  64. data/ui/examples/assets/icecold.ttf +0 -0
  65. data/ui/examples/assets/science-troll.png +0 -0
  66. data/ui/examples/buddy.rb +31 -0
  67. data/ui/examples/clock.rb +58 -0
  68. data/ui/examples/counter.rb +123 -0
  69. data/ui/examples/dynamic.rb +147 -0
  70. data/ui/examples/foobar.rb +236 -0
  71. data/ui/examples/stock.rb +115 -0
  72. data/ui/examples/stock_decider/option.rb +74 -0
  73. data/ui/examples/tic_tac_toe.rb +246 -0
  74. data/ui/lib/lib_hokusai.rb +425 -0
  75. data/ui/spec/hokusai/ast_spec.rb +88 -0
  76. data/ui/spec/hokusai/automation/keys_transcoder_spec.rb +50 -0
  77. data/ui/spec/hokusai/automation/selector_spec.rb +68 -0
  78. data/ui/spec/hokusai/block_spec.rb +126 -0
  79. data/ui/spec/hokusai/directives_spec.rb +327 -0
  80. data/ui/spec/hokusai/e2e/client_spec.rb +58 -0
  81. data/ui/spec/hokusai/e2e/meta_spec.rb +42 -0
  82. data/ui/spec/hokusai/publisher_spec.rb +38 -0
  83. data/ui/spec/hokusai/slots_spec.rb +150 -0
  84. data/ui/spec/hokusai/util/piece_table_spec.rb +90 -0
  85. data/ui/spec/hokusai_spec.rb +0 -0
  86. data/ui/spec/spec_helper.rb +30 -0
  87. data/ui/src/hokusai/ast.rb +446 -0
  88. data/ui/src/hokusai/automation/client.rb +167 -0
  89. data/ui/src/hokusai/automation/constants.rb +98 -0
  90. data/ui/src/hokusai/automation/converters/selector_converter.rb +61 -0
  91. data/ui/src/hokusai/automation/driver.rb +54 -0
  92. data/ui/src/hokusai/automation/driver_command_queue.rb +50 -0
  93. data/ui/src/hokusai/automation/driver_commands/base.rb +79 -0
  94. data/ui/src/hokusai/automation/driver_commands/get_attribute.rb +41 -0
  95. data/ui/src/hokusai/automation/driver_commands/invoke.rb +33 -0
  96. data/ui/src/hokusai/automation/driver_commands/locate.rb +48 -0
  97. data/ui/src/hokusai/automation/driver_commands/trigger_keyboard.rb +94 -0
  98. data/ui/src/hokusai/automation/driver_commands/trigger_mouse.rb +213 -0
  99. data/ui/src/hokusai/automation/keys_transcoder.rb +128 -0
  100. data/ui/src/hokusai/automation/selector.rb +39 -0
  101. data/ui/src/hokusai/automation/server.rb +114 -0
  102. data/ui/src/hokusai/automation.rb +3 -0
  103. data/ui/src/hokusai/backends/raylib/config.rb +47 -0
  104. data/ui/src/hokusai/backends/raylib/font.rb +113 -0
  105. data/ui/src/hokusai/backends/raylib/keys.rb +124 -0
  106. data/ui/src/hokusai/backends/raylib.rb +449 -0
  107. data/ui/src/hokusai/backends/sdl2/Monaco.ttf +0 -0
  108. data/ui/src/hokusai/backends/sdl2/color.rb +12 -0
  109. data/ui/src/hokusai/backends/sdl2/config.rb +31 -0
  110. data/ui/src/hokusai/backends/sdl2/font.rb +127 -0
  111. data/ui/src/hokusai/backends/sdl2/keys.rb +119 -0
  112. data/ui/src/hokusai/backends/sdl2.rb +529 -0
  113. data/ui/src/hokusai/block.rb +237 -0
  114. data/ui/src/hokusai/blocks/button.rb +100 -0
  115. data/ui/src/hokusai/blocks/checkbox.rb +51 -0
  116. data/ui/src/hokusai/blocks/circle.rb +28 -0
  117. data/ui/src/hokusai/blocks/clipped.rb +23 -0
  118. data/ui/src/hokusai/blocks/cursor.rb +49 -0
  119. data/ui/src/hokusai/blocks/dynamic.rb +37 -0
  120. data/ui/src/hokusai/blocks/empty.rb +10 -0
  121. data/ui/src/hokusai/blocks/hblock.rb +35 -0
  122. data/ui/src/hokusai/blocks/image.rb +18 -0
  123. data/ui/src/hokusai/blocks/input.rb +200 -0
  124. data/ui/src/hokusai/blocks/label.rb +39 -0
  125. data/ui/src/hokusai/blocks/panel.rb +126 -0
  126. data/ui/src/hokusai/blocks/rect.rb +24 -0
  127. data/ui/src/hokusai/blocks/scissor_begin.rb +18 -0
  128. data/ui/src/hokusai/blocks/scissor_end.rb +12 -0
  129. data/ui/src/hokusai/blocks/scrollbar.rb +103 -0
  130. data/ui/src/hokusai/blocks/selectable.rb +77 -0
  131. data/ui/src/hokusai/blocks/svg.rb +20 -0
  132. data/ui/src/hokusai/blocks/text.rb +214 -0
  133. data/ui/src/hokusai/blocks/titlebar/osx.rb +145 -0
  134. data/ui/src/hokusai/blocks/toggle.rb +55 -0
  135. data/ui/src/hokusai/blocks/vblock.rb +35 -0
  136. data/ui/src/hokusai/commands/base.rb +22 -0
  137. data/ui/src/hokusai/commands/circle.rb +47 -0
  138. data/ui/src/hokusai/commands/image.rb +45 -0
  139. data/ui/src/hokusai/commands/rect.rb +158 -0
  140. data/ui/src/hokusai/commands/scissor.rb +22 -0
  141. data/ui/src/hokusai/commands/text.rb +92 -0
  142. data/ui/src/hokusai/commands.rb +87 -0
  143. data/ui/src/hokusai/diff.rb +124 -0
  144. data/ui/src/hokusai/error.rb +3 -0
  145. data/ui/src/hokusai/event.rb +54 -0
  146. data/ui/src/hokusai/events/keyboard.rb +84 -0
  147. data/ui/src/hokusai/events/mouse.rb +172 -0
  148. data/ui/src/hokusai/font.rb +280 -0
  149. data/ui/src/hokusai/meta.rb +152 -0
  150. data/ui/src/hokusai/mounting/loop_entry.rb +230 -0
  151. data/ui/src/hokusai/mounting/mount_entry.rb +74 -0
  152. data/ui/src/hokusai/mounting/update_entry.rb +101 -0
  153. data/ui/src/hokusai/node.rb +98 -0
  154. data/ui/src/hokusai/node_mounter.rb +102 -0
  155. data/ui/src/hokusai/painter.rb +214 -0
  156. data/ui/src/hokusai/publisher.rb +32 -0
  157. data/ui/src/hokusai/style.rb +72 -0
  158. data/ui/src/hokusai/types.rb +266 -0
  159. data/ui/src/hokusai/util/clamping_iterator.rb +202 -0
  160. data/ui/src/hokusai/util/piece_table.rb +111 -0
  161. data/ui/src/hokusai/util/selection.rb +145 -0
  162. data/ui/src/hokusai.rb +120 -0
  163. data/ui/vendor/.gitkeep +0 -0
  164. data/vendor/.gitkeep +0 -0
  165. data/xmake.lua +192 -0
  166. metadata +222 -0
@@ -0,0 +1,449 @@
1
+ require "raylib"
2
+ require 'memory_profiler'
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module Hokusai::Backends
7
+ class RaylibBackend
8
+ RAYLIB_PATH = ENV["RAYLIB_PATH"] || "#{__dir__}/../../../../vendor/lib"
9
+
10
+ attr_reader :config
11
+
12
+ def self.icons
13
+ @icons ||= {}
14
+ end
15
+
16
+ def self.images
17
+ @images ||= {}
18
+ end
19
+
20
+ def self.stopped?
21
+ @stop = false if @stop.nil?
22
+
23
+ @stop
24
+ end
25
+
26
+ def self.stop!
27
+ @stop = true
28
+ end
29
+
30
+ def self.reset
31
+ @stop = false
32
+ end
33
+
34
+ def initialize
35
+ @config = Config.new
36
+
37
+ yield(config)
38
+ end
39
+
40
+ def vec2(x, y)
41
+ if @raylib_vec2.nil?
42
+ @raylib_vec2 = Raylib::Vector2.new
43
+ end
44
+ @raylib_vec2[:x] = x
45
+ @raylib_vec2[:y] = y
46
+
47
+ @raylib_vec2
48
+ end
49
+
50
+ def hml_vec2(x, y)
51
+ if @hml_vec2.nil?
52
+ @hml_vec2 = LibHokusai::HmlVec2.create(x, y)
53
+ else
54
+ @hml_vec2[:x] = x
55
+ @hml_vec2[:y] = y
56
+ end
57
+
58
+ @hml_vec2
59
+ end
60
+
61
+ def color(hc)
62
+ if @raylib_color.nil?
63
+ @raylib_color = Raylib::Color.from_u8(hc.red, hc.green, hc.blue, hc.alpha)
64
+ else
65
+ @raylib_color[:r] = hc.red
66
+ @raylib_color[:g] = hc.green
67
+ @raylib_color[:b] = hc.blue
68
+ @raylib_color[:a] = hc.alpha
69
+ end
70
+
71
+ @raylib_color
72
+ end
73
+
74
+ def rect(arr)
75
+ if @raylib_rect.nil?
76
+ @raylib_rect = Raylib::Rectangle.create(arr[0], arr[1], arr[2], arr[3])
77
+ else
78
+ @raylib_rect[:x] = arr[0]
79
+ @raylib_rect[:y] = arr[1]
80
+ @raylib_rect[:width] = arr[2]
81
+ @raylib_rect[:height] = arr[3]
82
+ end
83
+
84
+ @raylib_rect
85
+ end
86
+
87
+ def mouse_button(clicked: false, down: false, released: false, up: false)
88
+ if @mouse_button.nil?
89
+ @mouse_button = LibHokusai::HmlInputMouseButton.create(clicked: clicked, down: down, released: released, up: up)
90
+ else
91
+ @mouse_button[:clicked] = clicked
92
+ @mouse_button[:down] = down
93
+ @mouse_button[:released] = released
94
+ @mouse_button[:up] = up
95
+ end
96
+
97
+ @mouse_button
98
+ end
99
+
100
+ def process_input(input)
101
+ raylib_mouse_pos = Raylib.GetMousePosition
102
+ raylib_mouse_delta = Raylib.GetMouseDelta
103
+ LibHokusai.hoku_input_mouse_set_scroll(input.raw, Raylib.GetMouseWheelMove)
104
+ LibHokusai.hoku_input_set_mouse_position(input.raw, hml_vec2(raylib_mouse_pos.x, raylib_mouse_pos.y))
105
+
106
+ input.raw[:mouse][:delta][:x] = raylib_mouse_delta.x
107
+ input.raw[:mouse][:delta][:y] = raylib_mouse_delta.y
108
+
109
+ [0,1,2].each do |button_id|
110
+ clicked = Raylib.IsMouseButtonPressed(button_id)
111
+ down = Raylib.IsMouseButtonDown(button_id)
112
+ released = Raylib.IsMouseButtonReleased(button_id)
113
+ up = Raylib.IsMouseButtonUp(button_id)
114
+
115
+ button = mouse_button(clicked: clicked, down: down, released: released, up: up)
116
+ LibHokusai.hoku_input_mouse_set_button(input.raw, button, button_id)
117
+ end
118
+
119
+ LibHokusai.hoku_input_keyboard_start(input.raw)
120
+
121
+ Keys.each do |(hoku_key, raylib_key)|
122
+ LibHokusai.hoku_input_keyboard_set_key(input.raw, hoku_key, Raylib.IsKeyDown(raylib_key))
123
+ end
124
+
125
+ LibHokusai.hoku_input_keyboard_stop(input.raw)
126
+ end
127
+
128
+ def self.run(app)
129
+ backend = new do |config|
130
+ yield config
131
+ end
132
+
133
+ block = app.mount
134
+ backend.run(block)
135
+ end
136
+
137
+ def run(block)
138
+ self.class.reset
139
+
140
+ case RbConfig::CONFIG['host_os']
141
+ when /darwin/
142
+ Raylib.load_lib("#{RAYLIB_PATH}/libraylib.dylib")
143
+ when /mswin|msys|mingw/
144
+ Raylib.load_lib("#{RAYLIB_PATH}/libraylib.dll")
145
+ when /linux/
146
+ Raylib.load_lib("#{RAYLIB_PATH}/libraylib.so")
147
+ end
148
+
149
+ resize = false
150
+ initial = true
151
+ width = config.width
152
+ height = config.height
153
+
154
+ register_command_handlers
155
+
156
+ ptr = FFI::MemoryPointer.new :pointer
157
+ LibHokusai.hoku_input_init(ptr)
158
+ raw = LibHokusai::HmlInput.new(ptr.get_pointer(0))
159
+ input = Hokusai::Input.new(raw)
160
+ ptr.free
161
+
162
+ Raylib.SetConfigFlags(config.config_flags)
163
+ Raylib.InitWindow(config.width, config.height, config.title)
164
+ Raylib.SetTargetFPS(config.fps)
165
+ Raylib.SetWindowState(config.window_state_flags)
166
+
167
+ unless Hokusai.fonts.get("default")
168
+ Hokusai.fonts.register "default", Hokusai::Backends::RaylibBackend::Font.default
169
+ Hokusai.fonts.activate "default"
170
+ end
171
+
172
+ config.after_load_cb&.call
173
+
174
+ if config.automated
175
+ config.start_automation_driver
176
+ end
177
+
178
+ canvas = Hokusai::Canvas.new(width.to_f, height.to_f, 0.0, 0.0)
179
+ @texture = Raylib.LoadRenderTexture(width, height)
180
+ Raylib.SetTextureFilter(@texture.texture, Raylib::TEXTURE_FILTER_POINT)
181
+
182
+ width = nil
183
+ height = nil
184
+
185
+ MemoryProfiler.start if ENV["PROFILE"]
186
+
187
+ Raylib.EnableEventWaiting if config.event_waiting
188
+ last_input_hash = nil
189
+
190
+ until Raylib.WindowShouldClose
191
+ last_width = Raylib.GetScreenWidth
192
+ last_height = Raylib.GetScreenHeight
193
+
194
+ if last_width != width || last_height != height
195
+ resize = true
196
+ else
197
+ resize = false
198
+ end
199
+
200
+ width = last_width
201
+ height = last_height
202
+
203
+ process_input(input)
204
+ block.update
205
+
206
+ canvas.reset(nil, nil, width.to_f, height.to_f)
207
+
208
+ Raylib.BeginDrawing
209
+ Raylib.ClearBackground(config.background)
210
+
211
+ painter = Hokusai::Painter.new(block, input)
212
+
213
+ if config.automation_driver
214
+ painter.on_before_render do |blocks, canvas, input|
215
+ config.automation_driver.process(blocks, canvas, input)
216
+ end
217
+
218
+ painter.on_after_render do
219
+ config.automation_driver.complete
220
+ end
221
+
222
+ sleep 0.004
223
+ end
224
+
225
+ painter.render(canvas, resize)
226
+
227
+ Raylib.DrawFPS(10, 10) if ENV["PROFILE"] || ENV["FPS"]
228
+ Raylib.EndDrawing
229
+
230
+
231
+ break if self.class.stopped?
232
+ end
233
+
234
+ Hokusai.fonts.fonts.each do |key, font|
235
+ Raylib.UnloadFont(font.raw)
236
+ end
237
+
238
+ LibHokusai.hoku_input_free(input.raw)
239
+
240
+ if ENV["PROFILE"]
241
+ report = MemoryProfiler.stop
242
+ report.pretty_print(scale_bytes: true)
243
+ end
244
+
245
+ Raylib.CloseWindow
246
+
247
+ config.automation_driver&.stop
248
+ end
249
+
250
+ def inside_scissor(x, y, h = 0)
251
+ return true if @scissor.nil?
252
+
253
+ val = y + h >= @scissor[1] && y <= @scissor[1] + @scissor[3]
254
+ val
255
+ end
256
+
257
+ def register_command_handlers
258
+ Hokusai.on_close_window do
259
+ self.class.stop!
260
+ end
261
+
262
+ Hokusai.on_set_mouse_cursor do |type|
263
+ raylib_type = {
264
+ default: Raylib::MOUSE_CURSOR_DEFAULT,
265
+ arrow: Raylib::MOUSE_CURSOR_ARROW,
266
+ ibeam: Raylib::MOUSE_CURSOR_IBEAM,
267
+ crosshair: Raylib::MOUSE_CURSOR_CROSSHAIR,
268
+ pointer: Raylib::MOUSE_CURSOR_POINTING_HAND,
269
+ }[type]
270
+
271
+ raise Hokusai::Error.new("Cursor #{type} not recognized") if raylib_type.nil?
272
+
273
+ Raylib::SetMouseCursor(raylib_type)
274
+ end
275
+
276
+ Hokusai.on_set_mouse_position do |mouse|
277
+ Raylib.SetMousePosition(mouse.pos.x, mouse.pos.y)
278
+ end
279
+
280
+ Hokusai.on_set_window_position do |(dx, dy)|
281
+ wpos = Raylib.GetWindowPosition
282
+
283
+ Raylib.SetWindowPosition(wpos.x + dx, wpos.y + dy)
284
+ end
285
+
286
+ Hokusai.on_restore_window do
287
+ Raylib.RestoreWindow
288
+ end
289
+
290
+ Hokusai.on_maximize_window do
291
+ if Raylib.IsWindowMaximized
292
+ Raylib.RestoreWindow
293
+ else
294
+ Raylib.MaximizeWindow
295
+ end
296
+ end
297
+
298
+ Hokusai.on_minimize_window do
299
+ Raylib.MinimizeWindow
300
+ end
301
+
302
+ Hokusai.on_renderable do |canvas|
303
+ inside_scissor(canvas.x, canvas.y, canvas.height)
304
+ end
305
+
306
+ Hokusai::Commands::ScissorBegin.on_draw do |command|
307
+ Raylib.BeginScissorMode(command.x, command.y, command.width, command.height)
308
+ @scissor = [command.x, command.y, command.width, command.height]
309
+ end
310
+
311
+ Hokusai::Commands::ScissorEnd.on_draw do |_|
312
+ Raylib.EndScissorMode
313
+ @scissor = nil
314
+ end
315
+
316
+ Hokusai::Commands::Circle.on_draw do |command|
317
+ next unless inside_scissor(command.x, command.y)
318
+
319
+ radius = command.radius - command.outline
320
+ rc = command.color
321
+ color = Raylib::Color.from_u8(rc.r, rc.g, rc.b, rc.a)
322
+ Raylib.DrawCircleV(vec2(command.x, command.y), radius, color)
323
+
324
+ if command.outline > 0
325
+ Raylib.DrawCircleLines(command.x.ceil.to_i, command.y.ceil.to_i, command.radius, color(command.color))
326
+ end
327
+ end
328
+
329
+ Hokusai::Commands::SVG.on_draw do |command|
330
+ command = command.as(Hokusai::Commands::SVG)
331
+ texture = begin
332
+ if self.class.images[command.source]
333
+ self.class.images[command.source]
334
+ else
335
+ img = Raylib.LoadImageSVG(command.source, command.width, command.height)
336
+ texture = Raylib.load_texture_from_image(img)
337
+ Raylib.UnloadImage(img)
338
+ self.class.images[command.source] = texture
339
+ Raylib.GenTextureMipmaps(texture)
340
+ texture
341
+ end
342
+ end
343
+
344
+ Raylib.DrawTexture(texture, command.x, command.y, color(command.color))
345
+ end
346
+
347
+ Hokusai::Commands::Image.on_draw do |command|
348
+ next unless inside_scissor(command.x, command.y, command.height)
349
+
350
+ texture = begin
351
+ if self.class.images[command.cache]
352
+ self.class.images[command.cache]
353
+ else
354
+ img = Raylib.LoadImage(command.source)
355
+ Raylib.ImageResize(img.to_ptr, command.width, command.height)
356
+
357
+ texture = Raylib.LoadTextureFromImage(img)
358
+ Raylib.UnloadImage(img)
359
+ self.class.images[command.cache] = texture
360
+ Raylib.GenTextureMipmaps(texture)
361
+ texture
362
+ end
363
+ end
364
+
365
+ Raylib.DrawTexture(texture, command.x, command.y, Raylib::WHITE)
366
+ end
367
+
368
+ Hokusai::Commands::Text.on_draw do |command|
369
+ next unless inside_scissor(command.x, command.y, command.size)
370
+
371
+ font = Hokusai.fonts.active
372
+ c = color(command.color)
373
+ x = command.x + command.padding.l
374
+ y = command.y + command.padding.t
375
+
376
+ if fnt = font
377
+ # content = FFI::MemoryPointer.from_string(command.content)
378
+ Raylib.DrawTextEx(fnt.raw, command.content.to_s, vec2(x, y), command.size, fnt.spacing, c)
379
+ # content.free
380
+ else
381
+ Raylib.DrawText(command.content, x, y, command.size, c)
382
+ end
383
+ end
384
+
385
+ Hokusai::Commands::Rect.on_draw do |command|
386
+ next unless inside_scissor(command.x, command.y, command.height)
387
+
388
+ background_color = color(command.color)
389
+ # the rect has an outline
390
+ # so we want to render twice
391
+ if command.outline?
392
+ # for Raylib, the boundaries for background and outline are the same
393
+ # rectangles render in from the boundary
394
+ # and outlines render out from the boundary
395
+ # background_rect = outline_rect
396
+ outline_rect = rect(command.background_boundary)
397
+ background_rect = outline_rect
398
+ # rect is rounded so we need a rounded outline and
399
+ # a regular one
400
+ if command.rounding > 0
401
+ Raylib.DrawRectangleRounded(background_rect, command.rounding, 50, background_color)
402
+ else
403
+ Raylib.DrawRectangleRec(background_rect, background_color)
404
+ end
405
+
406
+ outline_color = color(command.outline_color)
407
+
408
+ # now draw the outlines
409
+ if command.outline_uniform? && command.rounding > 0.0 && outline_color.a > 0
410
+ Raylib.DrawRectangleRoundedLines(outline_rect, command.rounding, 50, command.outline.top, outline_color)
411
+ elsif command.outline_uniform? && !(command.rounding <= 0.0) && outline_color.a > 0
412
+ Raylib.DrawRectangleLinesEx(outline_rect, command.outline.top, outline_color)
413
+ elsif !command.outline_uniform?
414
+ ox, oy, ow, oh = command.background_boundary
415
+
416
+ if command.outline.top > 0.0
417
+ Raylib.DrawLineEx(vec2(ox, oy), vec2(ox + ow, oy), command.outline.top, outline_color)
418
+ end
419
+
420
+ if command.outline.left > 0.0
421
+ Raylib.DrawLineEx(vec2(ox, oy), vec2(ox, oy + oh), command.outline.left, outline_color)
422
+ end
423
+
424
+ if command.outline.right > 0.0
425
+ Raylib.DrawLineEx(vec2(ox + ow, oy), vec2(ox + ow, oy + oh), command.outline.right, outline_color)
426
+ end
427
+
428
+ if command.outline.bottom > 0.0
429
+ Raylib.DrawLineEx(vec2(ox, oy + oh), vec2(ox + ow, oy + oh), command.outline.bottom, outline_color)
430
+ end
431
+ end
432
+ else
433
+ rect = rect(command.background_boundary)
434
+
435
+ if command.rounding > 0
436
+ Raylib.DrawRectangleRounded(rect, command.rounding, 50, background_color)
437
+ else
438
+ Raylib.DrawRectangleRec(rect, background_color)
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end
444
+ end
445
+
446
+ require_relative "../../hokusai/font"
447
+ require_relative "./raylib/config"
448
+ require_relative "./raylib/font"
449
+ require_relative "./raylib/keys"
@@ -0,0 +1,12 @@
1
+ module Hokusai::Backends
2
+ class SDLBackend
3
+ def self.color(r, g, b, a = 255)
4
+ color = SDL::Color.new
5
+ color[:r] = r
6
+ color[:g] = g
7
+ color[:b] = b
8
+ color[:a] = a
9
+ color
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module Hokusai::Backends
2
+ class SDLBackend
3
+ class Config
4
+ attr_accessor :width, :height, :fps, :init_flags,
5
+ :title, :background, :automation_driver,
6
+ :after_load_cb, :host, :port, :automated,
7
+ :window_config_flags
8
+
9
+ def after_load(&block)
10
+ self.after_load_cb = block
11
+ end
12
+
13
+ def initialize
14
+ @width = 500
15
+ @height = 500
16
+ @fps = 60
17
+
18
+ @init_flags = ::SDL::INIT_VIDEO | ::SDL::INIT_EVENTS
19
+ @window_config_flags = SDL::WINDOW_RESIZABLE
20
+ @title = "(Unknown Title)"
21
+ @background = SDLBackend.color(255,255,255, 0)
22
+
23
+ after_load do
24
+ font = Hokusai::Backends::SDLBackend::Font.from "#{__dir__}/Monaco.ttf"
25
+ Hokusai.fonts.register "default", font
26
+ Hokusai.fonts.activate "default"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,127 @@
1
+ module Hokusai::Backends
2
+ class SDLBackend
3
+ OnWidthCb = Proc.new do |char, raw|
4
+ minxp = FFI::MemoryPointer.new :int
5
+ maxxp = FFI::MemoryPointer.new :int
6
+ minyp = FFI::MemoryPointer.new :int
7
+ maxyp = FFI::MemoryPointer.new :int
8
+ advancep = FFI::MemoryPointer.new :int
9
+
10
+ SDL.TTF_GlyphMetrics32(raw, char.ord, minxp, maxxp, minyp, maxyp, advancep)
11
+
12
+ metrics = [minxp.read_int, maxxp.read_int, minyp.read_int, maxyp.read_int, advancep.read_int]
13
+
14
+ minxp.free
15
+ maxxp.free
16
+ minyp.free
17
+ maxyp.free
18
+ advancep.free
19
+
20
+ metrics[4].to_f
21
+ end
22
+
23
+ class Font < Hokusai::Font
24
+ def self.from(file = "#{__dir__}/Monaco.ttf", size=50)
25
+ raw = SDL.TTF_OpenFont(file, size)
26
+
27
+ raise Hokusai::Error.new("Cannot open font from #{file}: #{ttf_error}") if raw.null?
28
+
29
+ new(raw)
30
+ end
31
+
32
+ attr_reader :raw
33
+
34
+ def initialize(raw)
35
+ @raw = raw
36
+ end
37
+
38
+ def self.ttf_error
39
+ SDL.GetError.read_string
40
+ end
41
+
42
+ def set_style(bold: false, italic: false)
43
+ style = SDL::TTF_STYLE_NORMAL
44
+ style = style | SDL::TTF_STYLE_BOLD if bold
45
+ style = style | SDL::TTF_STYLE_ITALIC if italic
46
+
47
+ SDL.TTF_SetFontStyle(raw, style)
48
+ end
49
+
50
+ def render(text, color = Color.new(0, 0, 0, 255), **args)
51
+ set_style(**args)
52
+
53
+ render_color = SDLBackend.color(color.r, color.g, color.b, color.a)
54
+ SDL.TTF_RenderUTF8_Blended(raw, text, render_color)
55
+ end
56
+
57
+ def ascent
58
+ SDL.TTF_FontAscent(raw)
59
+ end
60
+
61
+ def descent
62
+ SDL.TTF_FontDescent(raw)
63
+ end
64
+
65
+ def height
66
+ SDL.TTF_FontHeight(raw)
67
+ end
68
+
69
+ def clamp(text, size, width, initial_offset = 0.0)
70
+ self.size = size
71
+
72
+ clamping_pointer = FFI::MemoryPointer.new :pointer
73
+
74
+ ret = LibHokusai.hoku_text_clamp(clamping_pointer, text, width, initial_offset, raw, SDLBackend::OnWidthCb)
75
+ raise Hokusai::Error.new("Clamping failed") unless ret.zero?
76
+
77
+ clamping = LibHokusai::HokuClamping.new(clamping_pointer.get_pointer(0))
78
+ Hokusai::Clamping.new(clamping)
79
+ end
80
+
81
+ def clamp_markdown(text, size, width, initial_offset = 0.0)
82
+ self.size = size
83
+
84
+ clamping_pointer = FFI::MemoryPointer.new :pointer
85
+
86
+ ret = LibHokusai.hoku_text_md_clamp(clamping_pointer, text, width, initial_offset, raw, SDLBackend::OnWidthCb)
87
+ raise Hokusai::Error.new("Clamping failed") unless ret.zero?
88
+
89
+ clamping = LibHokusai::HokuClamping.new(clamping_pointer.get_pointer(0))
90
+ Hokusai::Clamping.new(clamping, markdown: true)
91
+ end
92
+
93
+ def fits(text, width)
94
+ extent = FFI::MemoryPointer.new :int
95
+ count = FFI::MemoryPointer.new :int
96
+ SDL.TTF_Measure(raw, text, width.to_i, extent, count)
97
+
98
+ e = extent.read_int
99
+ c = count.read_int
100
+
101
+ extent.free
102
+ count.free
103
+
104
+ [e, c]
105
+ end
106
+
107
+ def measure(text, size = 15)
108
+ w = FFI::MemoryPointer.new :int
109
+ h = FFI::MemoryPointer.new :int
110
+
111
+ SDL.TTF_SizeText(raw, text, w, h)
112
+
113
+ width = w.read_int
114
+ height = h.read_int
115
+
116
+ w.free
117
+ h.free
118
+
119
+ [width, height]
120
+ end
121
+
122
+ def size=(val)
123
+ SDL.TTF_SetFontSize(raw, val.to_i)
124
+ end
125
+ end
126
+ end
127
+ end