hokusai-zero 0.1.3 → 0.1.4

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +4 -0
  4. data/README.md +2 -0
  5. data/ast/src/core/hml.c +9 -9
  6. data/ast/src/core/text.c +1 -3
  7. data/ast/test/text.c +3 -3
  8. data/docs.sh +29 -0
  9. data/ext/extconf.rb +50 -14
  10. data/grammar/corpus/1_document.txt +24 -0
  11. data/grammar/corpus/6_styles.txt +23 -0
  12. data/grammar/grammar.js +4 -4
  13. data/grammar/src/grammar.json +19 -19
  14. data/grammar/src/parser.c +1904 -1956
  15. data/grammar/test.nml +10 -8
  16. data/hokusai.gemspec +2 -1
  17. data/ui/examples/assets/Delius-Regular.ttf +0 -0
  18. data/ui/examples/assets/DoHyeon.ttf +0 -0
  19. data/ui/examples/assets/Inter-Regular.ttf +0 -0
  20. data/ui/examples/assets/ernest.gif +0 -0
  21. data/ui/examples/assets/icons/audio-x-generic.png +0 -0
  22. data/ui/examples/assets/icons/image-x-generic.png +0 -0
  23. data/ui/examples/assets/icons/media-playback-pause.png +0 -0
  24. data/ui/examples/assets/icons/media-playback-start.png +0 -0
  25. data/ui/examples/assets/icons/media-playback-stop.png +0 -0
  26. data/ui/examples/assets/icons/package-x-generic.png +0 -0
  27. data/ui/examples/assets/icons/text-x-generic.png +0 -0
  28. data/ui/examples/assets/icons/video-x-generic.png +0 -0
  29. data/ui/examples/buddy.rb +16 -14
  30. data/ui/examples/clock.rb +38 -36
  31. data/ui/examples/counter.rb +100 -98
  32. data/ui/examples/dynamic.rb +115 -113
  33. data/ui/examples/foobar.rb +189 -187
  34. data/ui/examples/forum/file.rb +54 -0
  35. data/ui/examples/forum/music.rb +76 -0
  36. data/ui/examples/forum/post.rb +146 -0
  37. data/ui/examples/forum.rb +198 -0
  38. data/ui/examples/spreadsheet/csv.rb +261 -0
  39. data/ui/examples/spreadsheet.rb +138 -0
  40. data/ui/examples/stock.rb +86 -92
  41. data/ui/examples/stock_decider/option.rb +1 -1
  42. data/ui/examples/tic_tac_toe.rb +193 -191
  43. data/ui/lib/lib_hokusai.rb +0 -1
  44. data/ui/src/hokusai/ast.rb +42 -43
  45. data/ui/src/hokusai/backends/raylib/font.rb +1 -2
  46. data/ui/src/hokusai/backends/raylib.rb +16 -7
  47. data/ui/src/hokusai/backends/sdl2/font.rb +13 -9
  48. data/ui/src/hokusai/backends/sdl2.rb +5 -5
  49. data/ui/src/hokusai/block.rb +7 -0
  50. data/ui/src/hokusai/blocks/hblock.rb +2 -2
  51. data/ui/src/hokusai/blocks/image.rb +5 -1
  52. data/ui/src/hokusai/blocks/input.rb +17 -0
  53. data/ui/src/hokusai/blocks/label.rb +5 -2
  54. data/ui/src/hokusai/blocks/text.rb +10 -5
  55. data/ui/src/hokusai/blocks/titlebar/osx.rb +4 -4
  56. data/ui/src/hokusai/blocks/variable.rb +33 -0
  57. data/ui/src/hokusai/blocks/vblock.rb +1 -1
  58. data/ui/src/hokusai/commands/rect.rb +4 -4
  59. data/ui/src/hokusai/commands.rb +33 -31
  60. data/ui/src/hokusai/diff.rb +11 -0
  61. data/ui/src/hokusai/event.rb +19 -5
  62. data/ui/src/hokusai/events/mouse.rb +9 -1
  63. data/ui/src/hokusai/font.rb +60 -0
  64. data/ui/src/hokusai/meta.rb +10 -16
  65. data/ui/src/hokusai/node.rb +1 -1
  66. data/ui/src/hokusai/painter.rb +1 -2
  67. data/ui/src/hokusai/util/clamping_iterator.rb +5 -6
  68. data/ui/src/hokusai.rb +36 -4
  69. metadata +37 -3
@@ -25,15 +25,21 @@ module Hokusai
25
25
  end
26
26
 
27
27
  # The loop variable
28
+ #
28
29
  # eg [for="item in list"]
29
30
  # the var is `item`
31
+ #
32
+ # @return [String]
30
33
  def var
31
34
  @name ||= raw[:name].freeze
32
35
  end
33
36
 
34
37
  # The loop method
38
+ #
35
39
  # eg [for="item in list"]
36
40
  # the method is `list`
41
+ #
42
+ # @return [String]
37
43
  def method
38
44
  @list_name ||= raw[:list_name].freeze
39
45
  end
@@ -49,6 +55,7 @@ module Hokusai
49
55
  end
50
56
 
51
57
  # Name of the func
58
+ #
52
59
  # eg @target="run_this(one,two)"
53
60
  # the name is `run_this`
54
61
  #
@@ -58,6 +65,7 @@ module Hokusai
58
65
  end
59
66
 
60
67
  # Args of the func
68
+ #
61
69
  # eg @target="run_this(one, two)"
62
70
  # the args are [one, two]
63
71
  #
@@ -74,6 +82,7 @@ module Hokusai
74
82
  class Event
75
83
  attr_reader :raw
76
84
 
85
+ # @param [LibHokusai::Event] raw event
77
86
  def initialize(raw)
78
87
  @raw = raw
79
88
  end
@@ -93,6 +102,7 @@ module Hokusai
93
102
  end
94
103
  end
95
104
 
105
+ # A node representing an ast prop
96
106
  class Prop
97
107
  attr_reader :raw
98
108
 
@@ -100,14 +110,17 @@ module Hokusai
100
110
  @raw = raw
101
111
  end
102
112
 
113
+ # @return [Bool] is this prop computed?
103
114
  def computed?
104
115
  raw[:computed]
105
116
  end
106
117
 
118
+ # @return [String] the props name
107
119
  def name
108
120
  @name ||= raw[:name].freeze
109
121
  end
110
122
 
123
+ # @return [Ast::Func] the props value
111
124
  def value
112
125
  @call ||= raw[:call]
113
126
 
@@ -166,60 +179,74 @@ module Hokusai
166
179
  end
167
180
  end
168
181
 
182
+ # Marks this ast as dirty
183
+ # @return [Void]
169
184
  def dirty!
170
185
  @dirty = true
171
186
  end
172
187
 
188
+ # Is this ast dirty?
189
+ # @return [Bool]
173
190
  def dirty?
174
191
  @dirty
175
192
  end
176
193
 
194
+ # @return [Bool] is this node a slot?
177
195
  def slot?
178
196
  type == "slot"
179
197
  end
180
198
 
199
+ # @return [Bool] is this node virtual?
181
200
  def virtual?
182
201
  type == "virtual"
183
202
  end
184
203
 
204
+ # @return [Bool] does this node belong to a loop?
185
205
  def loop?
186
206
  @loop_condition = !raw[:loop].null? if @loop_condition.nil?
187
207
 
188
208
  @loop_condition
189
209
  end
190
210
 
211
+ # @return [Bool] does this node have an if condition?
191
212
  def has_if_condition?
192
213
  @if_condition = !raw[:cond].null? if @if_condition.nil?
193
214
 
194
215
  @if_condition
195
216
  end
196
217
 
218
+ # @return [Bool] does this node have an else condition?
197
219
  def has_else_condition?
198
220
  @else_condition = !raw[:else_relations].null? if @else_condition.nil?
199
221
 
200
222
  @else_condition
201
223
  end
202
224
 
225
+ # @return [Bool] is the else condition on this node currently active?
203
226
  def else_condition_active?
204
227
  has_else_condition? && @else_active == 1
205
228
  end
206
229
 
230
+ # @param [Bool] else condition is active or not
207
231
  def else_active=(val)
208
232
  @else_active = val
209
233
  end
210
234
 
235
+ # @return [Hokusai::Ast?] the ast of the else condition
211
236
  def else_ast
212
237
  return nil unless has_else_condition?
213
238
 
214
239
  Ast.new(raw[:else_relations][:next_child])
215
240
  end
216
241
 
242
+ # @return [Ast::Loop?] the loop that the ast belongs to
217
243
  def loop
218
244
  return nil unless loop?
219
245
 
220
246
  @loop ||= Loop.new(raw[:loop])
221
247
  end
222
248
 
249
+ # @return [Ast::Func?] the if condition of this ast
223
250
  def if
224
251
  @cond ||= raw[:cond]
225
252
 
@@ -232,14 +259,17 @@ module Hokusai
232
259
  @func ||= Func.new(@call)
233
260
  end
234
261
 
262
+ # @return [String] the node type
235
263
  def type
236
264
  @type ||= raw[:type].nil? ? "(null)" : raw[:type]
237
265
  end
238
266
 
267
+ # @return [String] the node id
239
268
  def id
240
269
  @id ||= raw[:id].nil? ? "(null)" : raw[:id]
241
270
  end
242
271
 
272
+ # @return [Array<String>] the list of classes for this node
243
273
  def classes
244
274
  return @classes unless @classes.nil?
245
275
 
@@ -251,6 +281,7 @@ module Hokusai
251
281
  @classes
252
282
  end
253
283
 
284
+ # @return [Array<Hokusai::Ast>] all the children of this node
254
285
  def children
255
286
  return @children unless @children.nil?
256
287
 
@@ -262,6 +293,7 @@ module Hokusai
262
293
  @children
263
294
  end
264
295
 
296
+ # @return [Array<Hokusai::Ast>] all the siblings of this node
265
297
  def siblings
266
298
  return @siblings unless @siblings.nil?
267
299
 
@@ -277,6 +309,7 @@ module Hokusai
277
309
  @siblings
278
310
  end
279
311
 
312
+ # @return [Array<Hokusai::Ast>] the children of the else condition for this node
280
313
  def else_children
281
314
  children = []
282
315
 
@@ -290,6 +323,7 @@ module Hokusai
290
323
  children
291
324
  end
292
325
 
326
+ # @return [Array<Ast::Prop>] all the props of this node
293
327
  def props
294
328
  return @props unless @props.nil?
295
329
 
@@ -302,6 +336,7 @@ module Hokusai
302
336
  @props
303
337
  end
304
338
 
339
+ # @return [Array<Ast::Event>] all the events of this node
305
340
  def events
306
341
  return @events unless @events.nil?
307
342
 
@@ -382,60 +417,24 @@ module Hokusai
382
417
  i.free
383
418
  end
384
419
 
420
+ # Fetches a prop by name
421
+ #
422
+ # @param [String] name of the prop
423
+ # @return [Ast::Prop?] the prop or nil
385
424
  def prop(name)
386
425
  props[name]
387
426
  end
388
427
 
389
- # Search for a prop by name
390
- #
391
- # @param [String] name the name of the prop to find
392
- # @return [Hokusai::Ast::Prop?] the prop if found
393
- # def prop(name)
394
- # prop_ptr = FFI::MemoryPointer.new :pointer
395
- # code = LibHokusai.hoku_ast_prop_init(prop_ptr, name)
428
+ # Fetches an event by name
396
429
  #
397
- # raise Hokusai::Error.new("Failed to allocate prop") unless code.zero?
398
- #
399
- # prop_ptr2 = prop_ptr.get_pointer(0)
400
- # prop = LibHokusai::HmlAstProp.new(prop_ptr2)
401
- # response = LibHokusai.hoku_ast_get_prop(raw, prop)
402
- # return nil if response.null?
403
- #
404
- # Prop.new(response)
405
- # ensure
406
- # prop_ptr.free
407
- # end
408
-
430
+ # @param [String] the name of the event
431
+ # @return [Ast::Event?] the event or nil
409
432
  def event(name)
410
433
  events[name]
411
434
  end
412
435
 
413
- # Search for an event by name
414
- #
415
- # @param [String] name the name of the prop to find
416
- # @return [Prop?] the prop if found
417
- # def event(name)
418
- # event_ptr = FFI::MemoryPointer.new :pointer
419
- # code = LibHokusai.hoku_ast_prop_init(event_ptr, name)
420
- #
421
- # raise Hokusai::Error.new("Failed to allocate prop") unless code.zero?
422
- #
423
- # event_ptr2 = event_ptr.get_pointer(0)
424
- # event = LibHokusai::HmlAstEvent.new(event_ptr2)
425
- # response = LibHokusai.hoku_ast_get_event(raw, event)
426
- # event_ptr.free
427
- #
428
- # return nil if response.null?
429
- #
430
- # Event.new(response)
431
- # ensure
432
- # event_ptr.free
433
- # end
434
-
435
436
  def destroy
436
- puts "before destroy"
437
437
  LibHokusai.hoku_ast_free(raw)
438
- puts "after destroy"
439
438
  end
440
439
 
441
440
  # dumps this ast to STDOUT
@@ -20,7 +20,7 @@ module Hokusai
20
20
  OnWidthCb = Proc.new do |char, ffi_pointer|
21
21
  data = DataForCb.new ffi_pointer
22
22
 
23
- Raylib.MeasureTextEx(data[:raw], "#{char.chr}", data[:size], data[:spacing]).x + data[:spacing]
23
+ Raylib.MeasureTextEx(data[:raw], "#{char.chr}", data[:size], data[:spacing]).x + 1.565#@ data[:spacing]
24
24
  end
25
25
 
26
26
  class Font < Hokusai::Font
@@ -83,7 +83,6 @@ module Hokusai
83
83
  raw.baseSize
84
84
  end
85
85
 
86
-
87
86
  def clamp_markdown(text, size, width, initial_offset = 0.0)
88
87
  clamping_pointer = FFI::MemoryPointer.new :pointer
89
88
  RaylibBackend::DataForCb.create(size, spacing, raw) do |ptr|
@@ -37,8 +37,8 @@ module Hokusai::Backends
37
37
  yield(config)
38
38
  end
39
39
 
40
- def vec2(x, y)
41
- if @raylib_vec2.nil?
40
+ def vec2(x, y, fresh = false)
41
+ if @raylib_vec2.nil? || fresh
42
42
  @raylib_vec2 = Raylib::Vector2.new
43
43
  end
44
44
  @raylib_vec2[:x] = x
@@ -201,7 +201,9 @@ module Hokusai::Backends
201
201
  height = last_height
202
202
 
203
203
  process_input(input)
204
+ block.public_send(:before_updated) if block.respond_to?(:before_updated)
204
205
  block.update
206
+ block.public_send(:after_updated) if block.respond_to?(:after_updated)
205
207
 
206
208
  canvas.reset(nil, nil, width.to_f, height.to_f)
207
209
 
@@ -368,7 +370,12 @@ module Hokusai::Backends
368
370
  Hokusai::Commands::Text.on_draw do |command|
369
371
  next unless inside_scissor(command.x, command.y, command.size)
370
372
 
373
+ active_name = Hokusai.fonts.active_font_name
371
374
  font = Hokusai.fonts.active
375
+
376
+ Hokusai.fonts.activate command.font.nil? ? active_name : command.font
377
+ font = Hokusai.fonts.active
378
+
372
379
  c = color(command.color)
373
380
  x = command.x + command.padding.l
374
381
  y = command.y + command.padding.t
@@ -380,6 +387,8 @@ module Hokusai::Backends
380
387
  else
381
388
  Raylib.DrawText(command.content, x, y, command.size, c)
382
389
  end
390
+
391
+ Hokusai.fonts.activate active_name
383
392
  end
384
393
 
385
394
  Hokusai::Commands::Rect.on_draw do |command|
@@ -407,26 +416,26 @@ module Hokusai::Backends
407
416
 
408
417
  # now draw the outlines
409
418
  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)
419
+ Raylib.DrawRectangleRoundedLinesEx(outline_rect, command.rounding, 50, command.outline.top + 1, outline_color)
411
420
  elsif command.outline_uniform? && !(command.rounding <= 0.0) && outline_color.a > 0
412
421
  Raylib.DrawRectangleLinesEx(outline_rect, command.outline.top, outline_color)
413
422
  elsif !command.outline_uniform?
414
423
  ox, oy, ow, oh = command.background_boundary
415
424
 
416
425
  if command.outline.top > 0.0
417
- Raylib.DrawLineEx(vec2(ox, oy), vec2(ox + ow, oy), command.outline.top, outline_color)
426
+ Raylib.DrawLineEx(vec2(ox, oy, true), vec2(ox + ow, oy, true), command.outline.top, outline_color)
418
427
  end
419
428
 
420
429
  if command.outline.left > 0.0
421
- Raylib.DrawLineEx(vec2(ox, oy), vec2(ox, oy + oh), command.outline.left, outline_color)
430
+ Raylib.DrawLineEx(vec2(ox, oy, true), vec2(ox, oy + oh, true), command.outline.left, outline_color)
422
431
  end
423
432
 
424
433
  if command.outline.right > 0.0
425
- Raylib.DrawLineEx(vec2(ox + ow, oy), vec2(ox + ow, oy + oh), command.outline.right, outline_color)
434
+ Raylib.DrawLineEx(vec2(ox + ow, oy, true), vec2(ox + ow, oy + oh, true), command.outline.right, outline_color)
426
435
  end
427
436
 
428
437
  if command.outline.bottom > 0.0
429
- Raylib.DrawLineEx(vec2(ox, oy + oh), vec2(ox + ow, oy + oh), command.outline.bottom, outline_color)
438
+ Raylib.DrawLineEx(vec2(ox, oy + oh, true), vec2(ox + ow, oy + oh, true), command.outline.bottom, outline_color)
430
439
  end
431
440
  end
432
441
  else
@@ -80,7 +80,6 @@ module Hokusai::Backends
80
80
 
81
81
  def clamp_markdown(text, size, width, initial_offset = 0.0)
82
82
  self.size = size
83
-
84
83
  clamping_pointer = FFI::MemoryPointer.new :pointer
85
84
 
86
85
  ret = LibHokusai.hoku_text_md_clamp(clamping_pointer, text, width, initial_offset, raw, SDLBackend::OnWidthCb)
@@ -105,18 +104,23 @@ module Hokusai::Backends
105
104
  end
106
105
 
107
106
  def measure(text, size = 15)
108
- w = FFI::MemoryPointer.new :int
109
- h = FFI::MemoryPointer.new :int
107
+ begin
108
+ self.size = size
109
+ w = FFI::MemoryPointer.new :int
110
+ h = FFI::MemoryPointer.new :int
110
111
 
111
- SDL.TTF_SizeText(raw, text, w, h)
112
+ SDL.TTF_SizeText(raw, text, w, h)
112
113
 
113
- width = w.read_int
114
- height = h.read_int
114
+ width = w.read_int
115
+ height = h.read_int
115
116
 
116
- w.free
117
- h.free
117
+ w.free
118
+ h.free
118
119
 
119
- [width, height]
120
+ [width, height]
121
+ ensure
122
+ self.size = height
123
+ end
120
124
  end
121
125
 
122
126
  def size=(val)
@@ -57,7 +57,7 @@ module Hokusai::Backends
57
57
  end
58
58
 
59
59
  def run(block)
60
- resize = false
60
+ resize = true
61
61
  self.render_width = config.width
62
62
  self.render_height = config.height
63
63
 
@@ -89,7 +89,7 @@ module Hokusai::Backends
89
89
 
90
90
  Hokusai.fonts.register "default", SDLBackend::Font.from("#{__dir__}/sdl2/Monaco.ttf", 121)
91
91
  Hokusai.fonts.activate "default"
92
- config.after_load&.call
92
+ config.after_load_cb&.call
93
93
 
94
94
  register_command_handlers(renderer, window)
95
95
  # MemoryProfiler.start if ENV["PROFILE"]
@@ -107,7 +107,7 @@ module Hokusai::Backends
107
107
  if process_input(input, event)
108
108
  # since we are using wait event, we need to process the render twice
109
109
  # once to capture all events, and once after updating block state.
110
- SDL.SetRenderDrawBlendMode(renderer, SDL::BLENDMODE_BLEND)
110
+ SDL.SetRenderDrawBlendMode(renderer, SDL::BLENDMODE_NONE)
111
111
  SDL.SetRenderDrawColor(renderer, config.background[:r], config.background[:g], config.background[:b], config.background[:a])
112
112
  SDL.RenderClear(renderer)
113
113
  canvas.reset(nil, nil, render_width.to_f, render_height.to_f)
@@ -116,7 +116,7 @@ module Hokusai::Backends
116
116
  block.update
117
117
  SDL.RenderPresent(renderer)
118
118
 
119
- SDL.SetRenderDrawBlendMode(renderer, SDL::BLENDMODE_BLEND)
119
+ SDL.SetRenderDrawBlendMode(renderer, SDL::BLENDMODE_NONE)
120
120
  SDL.SetRenderDrawColor(renderer, config.background[:r], config.background[:g], config.background[:b], config.background[:a])
121
121
  SDL.RenderClear(renderer)
122
122
  canvas.reset(nil, nil, render_width.to_f, render_height.to_f)
@@ -404,7 +404,6 @@ module Hokusai::Backends
404
404
 
405
405
  Hokusai::Commands::Rect.on_draw do |command|
406
406
  next unless inside_scissor(command.x, command.y, command.height)
407
-
408
407
  # draw background first
409
408
  x, y, w, h = [command.x, command.y, command.width, command.height]
410
409
 
@@ -480,6 +479,7 @@ module Hokusai::Backends
480
479
  rounding = command.rounding
481
480
 
482
481
  if command.rounding > 0
482
+ pp command.color
483
483
  SDL.roundedBoxRGBA(renderer, x, y, x + w, y + h, (command.rounding).ceil.to_i, command.color.r, command.color.g, command.color.b, command.color.a)
484
484
  else
485
485
  rect = SDL::Rect.new
@@ -4,6 +4,11 @@ require "forwardable"
4
4
 
5
5
  module Hokusai
6
6
  module Blocks; end
7
+ # A UI Component
8
+ #
9
+ # Blocks are reusable and can be mounted in other blocks via templates
10
+ #
11
+ # Blocks have `props`` and emit `events`
7
12
  class Block
8
13
  attr_reader :node
9
14
  attr_reader :publisher
@@ -61,6 +66,8 @@ module Hokusai
61
66
  @styles || {}
62
67
  end
63
68
 
69
+ # Defines blocks that this block uses in it's template
70
+ # Keys map to template node names, values map to a `Hokusai::Block`
64
71
  def self.uses(**args)
65
72
  args.each do |key, value|
66
73
  raise Hokusai::Error.new("#{key} value must be a Block, got #{value}") unless value.is_a?(Block.class)
@@ -15,12 +15,12 @@ class Hokusai::Blocks::Hblock < Hokusai::Block
15
15
  canvas.vertical = false
16
16
  canvas.reverse = reverse
17
17
 
18
- if background.nil?
18
+ if background.nil? && outline.nil?
19
19
  yield canvas
20
20
  else
21
21
  draw do
22
22
  rect(canvas.x, canvas.y, canvas.width, canvas.height) do |command|
23
- command.color = background
23
+ command.color = background if background
24
24
  command.outline = outline if outline
25
25
  command.outline_color = outline_color if outline_color
26
26
  command.round = rounding.to_f if rounding
@@ -1,3 +1,5 @@
1
+ require "pathname"
2
+
1
3
  class Hokusai::Blocks::Image < Hokusai::Block
2
4
  template <<~EOF
3
5
  [template]
@@ -9,8 +11,10 @@ class Hokusai::Blocks::Image < Hokusai::Block
9
11
  computed :height, default: nil
10
12
 
11
13
  def render(canvas)
14
+ src = Pathname.new(source).absolute? ? source : "#{File.dirname(caller[-1].split(":")[0])}/#{source}"
15
+
12
16
  draw do
13
- image(source, canvas.x, canvas.y, width&.to_f || canvas.width, height&.to_f || canvas.height)
17
+ image(src, canvas.x, canvas.y, width&.to_f || canvas.width, height&.to_f || canvas.height)
14
18
  end
15
19
 
16
20
  yield canvas
@@ -45,6 +45,19 @@ class Hokusai::Blocks::Input < Hokusai::Block
45
45
  @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
46
46
  @buffer_offset = 0
47
47
  @offset = 0
48
+ @changed = false
49
+ end
50
+
51
+ def after_updated
52
+ if @changed && !growable
53
+ @table = ::Hokusai::Util::PieceTable.new(initial)
54
+ @buffer = ""
55
+ @buffer_count = 0
56
+ @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+ @buffer_offset = 0
58
+ @offset = 0
59
+ @changed = false
60
+ end
48
61
  end
49
62
 
50
63
  def update_height(value)
@@ -74,6 +87,7 @@ class Hokusai::Blocks::Input < Hokusai::Block
74
87
  end
75
88
 
76
89
  def dynamic_keypress_handle(event)
90
+ return unless node.meta.focused
77
91
  case event
78
92
  when proc(&:ctrl), proc(&:super)
79
93
  if event.char == "z"
@@ -116,6 +130,7 @@ class Hokusai::Blocks::Input < Hokusai::Block
116
130
  flush
117
131
 
118
132
  emit("change", table.to_s)
133
+ @changed = true
119
134
 
120
135
  self.table = ::Hokusai::Util::PieceTable.new("")
121
136
  self.buffer_offset = 0
@@ -142,6 +157,8 @@ class Hokusai::Blocks::Input < Hokusai::Block
142
157
  self.buffer += event.char
143
158
  end
144
159
  end
160
+
161
+ emit("modified")
145
162
  end
146
163
 
147
164
  def flush
@@ -6,6 +6,7 @@ class Hokusai::Blocks::Label < Hokusai::Block
6
6
  EOF
7
7
 
8
8
  computed! :content
9
+ computed :font, default: nil
9
10
  computed :size, default: 12
10
11
  computed :color, default: [33,33,33], convert: Hokusai::Color
11
12
  computed :padding, default: [5.0, 5.0, 5.0, 5.0], convert: Hokusai::Padding
@@ -21,8 +22,9 @@ class Hokusai::Blocks::Label < Hokusai::Block
21
22
 
22
23
  def render(canvas)
23
24
  if @last_content != content
24
- width, _ = Hokusai.fonts.active.measure(content.to_s, size.to_i)
25
-
25
+ width, height = Hokusai.fonts.active.measure(content.to_s, size.to_i)
26
+ node.meta.set_prop(:width, width + padding.right + padding.left)
27
+ node.meta.set_prop(:height, height + padding.top + padding.bottom)
26
28
  emit("width_updated", width + padding.right + padding.left)
27
29
 
28
30
  @last_content = content
@@ -33,6 +35,7 @@ class Hokusai::Blocks::Label < Hokusai::Block
33
35
  command.color = color
34
36
  command.size = size
35
37
  command.padding = padding
38
+ command.font = font unless font.nil?
36
39
  end
37
40
  end
38
41
  end
@@ -15,13 +15,14 @@ class Hokusai::Blocks::Text < Hokusai::Block
15
15
  uses(empty: Hokusai::Blocks::Empty)
16
16
 
17
17
  computed! :content
18
+ computed :font, default: nil
18
19
  computed :size, default: 16, convert: proc(&:to_i)
19
20
  computed :line_height, default: 5, convert: proc(&:to_f)
20
21
  computed :color, default: [33,33,33], convert: Hokusai::Color
21
22
  computed :selection_color, default: [233,233,233], convert: Hokusai::Color
22
23
  computed :padding, default: [5.0, 5.0, 5.0, 5.0], convert: Hokusai::Padding
23
24
  computed :cursor_offset, default: nil
24
- computed :markdown, default: false
25
+ computed :markdown, default: false, convert: ->(val) { val == "true" || val == true }
25
26
 
26
27
  inject :selection
27
28
  inject :panel_top
@@ -65,7 +66,6 @@ class Hokusai::Blocks::Text < Hokusai::Block
65
66
 
66
67
  draw_with do |commands|
67
68
  iterator.on_draw do |text, x, y, group|
68
-
69
69
  if selector = selection
70
70
  if cursor_offset&.zero? && !selector.started
71
71
  selector.cursor = [x, y + offset_y, 0.0, size.to_f]
@@ -86,6 +86,7 @@ class Hokusai::Blocks::Text < Hokusai::Block
86
86
  commands.text(text, x, y) do |command|
87
87
  command.color = color
88
88
  command.size = size
89
+ command.font = font unless font.nil?
89
90
 
90
91
  if group.respond_to?(:bold?)
91
92
 
@@ -186,7 +187,8 @@ class Hokusai::Blocks::Text < Hokusai::Block
186
187
 
187
188
  def clamp(text, width)
188
189
  if markdown
189
- Hokusai.fonts.active.clamp_markdown(text, size.to_i, width - size.to_f - padding.right)
190
+ clamping = Hokusai.fonts.active.clamp_markdown(text, size.to_i, width - size.to_f - padding.right)
191
+ clamping
190
192
  else
191
193
  Hokusai.fonts.active.clamp(text, size.to_i, width - size.to_f, padding.right)
192
194
  end
@@ -203,10 +205,13 @@ class Hokusai::Blocks::Text < Hokusai::Block
203
205
 
204
206
  height = internal_render(last_clamping, canvas)
205
207
  self.last_content = last_content
206
- emit("height_updated", height + padding.bottom) unless last_height == height + padding.bottom
208
+
209
+ new_height = height + padding.bottom + padding.top
210
+ new_height -= size if markdown
207
211
 
208
- self.last_height = height + padding.bottom + padding.top
212
+ emit("height_updated", new_height) unless last_height == new_height
209
213
 
214
+ self.last_height = new_height
210
215
 
211
216
  yield canvas
212
217
  end
@@ -32,12 +32,12 @@ module Hokusai::Blocks::Titlebar
32
32
  EOF
33
33
 
34
34
  computed :rounding, default: 0.0, convert: proc(&:to_f)
35
- computed :outline, default: [0.0, 0.0, 0.0, 0.0], convert: Hokusai::Outline
36
- computed :outline_color, default: [0,0,0,0], convert: Hokusai::Color
35
+ computed :outline, default: nil
36
+ computed :outline_color, default: nil
37
37
  computed :unhovered_color, default: DEFAULT, convert: Hokusai::Color
38
38
  computed :radius, default: 6.0, convert: proc(&:to_f)
39
- computed :background, default: nil, convert: Hokusai::Color
40
- computed :background_drag, default: nil, convert: Hokusai::Color
39
+ computed :background, default: nil
40
+ computed :background_drag, default: nil
41
41
 
42
42
 
43
43
  uses(
@@ -0,0 +1,33 @@
1
+ class Hokusai::Blocks::Variable < Hokusai::Block
2
+ template <<~EOF
3
+ [template]
4
+ empty
5
+ EOF
6
+
7
+ uses(empty: Hokusai::Blocks::Empty)
8
+
9
+ computed! :script
10
+
11
+ def after_updated
12
+ if @last_height != children[0].node.meta.get_prop(:height)
13
+ @last_height = children[0].node.meta.get_prop(:height)
14
+
15
+ node.meta.set_prop(:height, @last_height)
16
+ emit("height_updated", @last_height)
17
+ end
18
+ end
19
+
20
+ def on_mounted
21
+ klass = eval(script)
22
+
23
+ raise Hokusai::Error.new("Class #{klass} is not a Hokusai::Block") unless klass.ancestors.include?(Hokusai::Block)
24
+
25
+ node.meta.set_child(0, klass.mount)
26
+ end
27
+
28
+ def render(canvas)
29
+ if Hokusai.can_render(canvas)
30
+ yield canvas
31
+ end
32
+ end
33
+ end