hokusai-zero 0.1.9 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 750449495b6ff533259afe965f85b4ec8da53768613b1636a011a9c0d63bfa54
4
- data.tar.gz: abedbc5fa46e999dfcd8af09b38d0bced547b527718cb4a5f5e88b639f358391
3
+ metadata.gz: e03fd33f856cbaade06505c5e962980504107726d39d6ec630b16e2d4c75b480
4
+ data.tar.gz: eb7bf1b39ea4acae4e7d7cfe4d7d70d14cd7335e65c4b74dc7c7e37b697eb52b
5
5
  SHA512:
6
- metadata.gz: 1d4d7da22c16a69bb0e169027c25378395f3d3f58d7267bff3e26bdbcd323dafae067148e8d3c18dfcd59d87cd0b272072cd9735235f8f0f1681426f76474e73
7
- data.tar.gz: 8e8d26b0d9db21585c4575346233ba65467a6eb484a8ea17b121ab1b0972898c458393f284d0cfcd9a7e98e51061aaee71e3af878e304ef4345666d18b9e745e
6
+ metadata.gz: 9c85bc9f63b75685b60fc83d3e247c84cd3ee02e981608aadf810e080ec7c22043bf0d0ff5908678ed244ad40a2a0e9653d67eaefd77071b1cde15df81e2f641
7
+ data.tar.gz: 10e5e39e7185c0c3863a0958fe20085d62f2603bfe1621a5d92dbb16caff6d7183d1a8779f45a5d2e1624b99c54ea116f1dbe8318f6b78d2bc2a47ade2965d7d
data/hokusai.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'hokusai-zero'
3
- s.version = '0.1.9'
3
+ s.version = '0.2.0'
4
4
  s.licenses = ['MIT']
5
5
  s.summary = "A Ruby library for writing GUI applications"
6
6
  s.authors = ["skinnyjames"]
@@ -0,0 +1,117 @@
1
+ describe Hokusai::Diff, focus: true do
2
+ def patches(before, after)
3
+ patches = []
4
+
5
+ Hokusai::Diff.new(before, after).patch do |item|
6
+ patches << item
7
+ end
8
+
9
+ patches
10
+ end
11
+
12
+ it "inserts new items" do
13
+ list = patches([], [[:key1, 1],[:key2, 2],[:key3, 3]])
14
+
15
+ expect(list.count).to eq(3)
16
+
17
+ list.each_with_index do |patch, i|
18
+ expect(patch).to be_a(Hokusai::InsertPatch)
19
+ expect(patch.target).to eq(i)
20
+ expect(patch.value).to eq(i+1)
21
+ expect(patch.delete).to be(true)
22
+ end
23
+ end
24
+
25
+ it "inserts new items and deletes old items" do
26
+ list = patches(
27
+ [[:key1, 1], [:key2, 2], [:key3, 3]],
28
+ [[:key1, 1], [:key4, 4], [:key3, 3]]
29
+ )
30
+
31
+ expect(list.count).to eq(1)
32
+
33
+ # removes key2 and inserts key4 with 1 insertpatch
34
+ expect(list.first).to be_a(Hokusai::InsertPatch)
35
+ expect(list.first.target).to eq(1)
36
+ expect(list.first.value).to eq(4)
37
+ expect(list.first.delete).to be(true)
38
+ end
39
+
40
+ it "updates items with new values on existing keys" do
41
+ list = patches(
42
+ [[:key1, 1], [:key2, 2], [:key3, 3]],
43
+ [[:key1, 1], [:key2, 4], [:key3, 3]]
44
+ )
45
+
46
+ expect(list.count).to eq(1)
47
+
48
+ # removes key2 and inserts key4 with 1 insertpatch
49
+ expect(list.first).to be_a(Hokusai::UpdatePatch)
50
+ expect(list.first.target).to eq(1)
51
+ expect(list.first.value).to eq(4)
52
+ end
53
+
54
+ it "moves items that have the same key and no longer are in the list" do
55
+ list = patches(
56
+ [[:key1, 1], [:key2, 2], [:key3, 3]],
57
+ [[:key1, 1], [:key3, 3]]
58
+ )
59
+
60
+ expect(list.count).to eq(1)
61
+ expect(list.first).to be_a(Hokusai::MovePatch)
62
+ expect(list.first.from).to eq(2)
63
+ expect(list.first.to).to eq(1)
64
+ expect(list.first.value).to eq(3)
65
+ expect(list.first.delete).to be(true)
66
+ end
67
+
68
+ it "deletes items that have different keys and no longer are in the list" do
69
+ list = patches(
70
+ [[:key1, 1], [:key2, 2], [:key3, 3]],
71
+ [[:key2, 2], [:key3, 3]]
72
+ )
73
+
74
+ expect(list.count).to eq(3)
75
+ # moves position 1 to 0
76
+ expect(list[0]).to be_a(Hokusai::MovePatch)
77
+ expect(list[0].from).to eq(1)
78
+ expect(list[0].to).to eq(0)
79
+ expect(list[0].delete).to be(true)
80
+
81
+ # moves position 2 to 1
82
+ expect(list[1]).to be_a(Hokusai::MovePatch)
83
+ expect(list[1].from).to eq(2)
84
+ expect(list[1].to).to eq(1)
85
+
86
+ # delete position 2
87
+ expect(list[2]).to be_a(Hokusai::DeletePatch)
88
+ expect(list[2].target).to eq(2)
89
+ end
90
+
91
+ it "moves items around" do
92
+ # [d, a, c] [(c), e, a, b]
93
+ list = patches(
94
+ [[:key1, :d], [:key2, :a], [:key3, :c]],
95
+ [[:key3, :c], [:key4, :e], [:key2, :a], [:key5, :b]]
96
+ )
97
+
98
+ expect(list.count).to eq(3)
99
+
100
+ # Moves [key3, c] to position 0 and deletes [key1, d]
101
+ expect(list[0]).to be_a(Hokusai::MovePatch)
102
+ expect(list[0].from).to eq(2)
103
+ expect(list[0].to).to eq(0)
104
+ expect(list[0].delete).to eq(true)
105
+
106
+ # Inserts [key4, e] into position 1
107
+ expect(list[1]).to be_a(Hokusai::InsertPatch)
108
+ expect(list[1].target).to eq(1)
109
+ expect(list[1].value).to eq(:e)
110
+ expect(list[1].delete).to be(false)
111
+
112
+ expect(list[2]).to be_a(Hokusai::InsertPatch)
113
+ expect(list[2].target).to eq(3)
114
+ expect(list[2].value).to eq(:b)
115
+ expect(list[2].delete).to eq(true) # delete doesn't need to be true here...
116
+ end
117
+ end
@@ -27,7 +27,7 @@ describe "Providers" do
27
27
  slot
28
28
  EOF
29
29
 
30
- provide :provision, counter_klass.new
30
+ provide :provision, counter_klass.new
31
31
 
32
32
  def state
33
33
  self.class.provides[:provision]
@@ -212,4 +212,91 @@ describe "Providers" do
212
212
  expect(provided.state.count).to eq(2)
213
213
  end
214
214
  end
215
+
216
+ describe "Loops with conditionals" do
217
+ let(:loop_if_container) do
218
+ provider_klass = provider
219
+ inject_klass = inject
220
+
221
+ c = Class.new(Hokusai::Block) do
222
+ template <<~EOF
223
+ [template]
224
+ container
225
+ provision
226
+ [for="value in values"]
227
+ vblock { :key="value" }
228
+ [if="show"]
229
+ vblock
230
+ inject
231
+ [else]
232
+ vblock
233
+ injectelse
234
+ control
235
+ EOF
236
+
237
+ uses(
238
+ container: Hokusai::Blocks::Vblock,
239
+ vblock: Hokusai::Blocks::Vblock,
240
+ control: inject_klass,
241
+ inject: inject_klass,
242
+ injectelse: inject_klass,
243
+ provision: provider_klass
244
+ )
245
+
246
+ attr_accessor :show
247
+
248
+ def initialize(**args)
249
+ @show = false
250
+
251
+ super
252
+ end
253
+
254
+ def values
255
+ [1,2,3,4,5]
256
+ end
257
+
258
+ def toggle
259
+ self.show = !show
260
+ end
261
+ end
262
+
263
+ c.mount
264
+ end
265
+
266
+ it "provisions extend to conditional children in loops" do
267
+ loop_if_container.toggle
268
+ loop_if_container.update
269
+
270
+ injecteds = get_blocks_by_type(loop_if_container, "inject")
271
+ provided = get_block_by_type(loop_if_container, "provision")
272
+ control = get_block_by_type(loop_if_container, "control")
273
+
274
+ injecteds.each do |injected|
275
+ expect(injected.provision.count).to eq(0)
276
+ end
277
+
278
+ injecteds.first.provision.increment
279
+
280
+ injecteds.each do |injected|
281
+ expect(injected.provision.count).to eq(1)
282
+ end
283
+
284
+ expect(provided.state.count).to eq(1)
285
+ expect(control.provision).to be(nil)
286
+
287
+ loop_if_container.toggle
288
+ loop_if_container.update
289
+
290
+ inject_elses = get_blocks_by_type(loop_if_container, "injectelse")
291
+
292
+ inject_elses.each do |inject_else|
293
+ expect(inject_else.provision).not_to be(nil)
294
+ expect(inject_else.provision.count).to eq(1)
295
+ end
296
+
297
+ inject_elses.first.provision.increment
298
+
299
+ expect(provided.state.count).to eq(2)
300
+ end
301
+ end
215
302
  end
@@ -192,6 +192,12 @@ module Hokusai::Backends
192
192
  last_input_hash = nil
193
193
 
194
194
  until Raylib.WindowShouldClose
195
+ if Raylib.IsWindowFocused
196
+ Raylib.DisableEventWaiting
197
+ else
198
+ Raylib.EnableEventWaiting
199
+ end
200
+
195
201
  last_width = Raylib.GetScreenWidth
196
202
  last_height = Raylib.GetScreenHeight
197
203
 
@@ -206,9 +206,7 @@ class Hokusai::Blocks::Text < Hokusai::Block
206
206
  self.last_content = content
207
207
  end
208
208
 
209
- height = internal_render(last_clamping, canvas)
210
- self.last_content = last_content
211
-
209
+ height = internal_render(last_clamping, canvas)
212
210
  new_height = height + padding.bottom + padding.top
213
211
  new_height -= size if markdown
214
212
 
@@ -2,11 +2,12 @@ module Hokusai
2
2
  # Represents a patch to move a loop item
3
3
  # from one location to another
4
4
  class MovePatch
5
- attr_accessor :from, :to, :delete
5
+ attr_accessor :from, :to, :value, :delete
6
6
 
7
- def initialize(from:, to:, delete: false)
7
+ def initialize(from:, to:, value:, delete: false)
8
8
  @from = from
9
9
  @to = to
10
+ @value = value
10
11
  @delete = delete
11
12
  end
12
13
  end
@@ -96,12 +97,12 @@ module Hokusai
96
97
  # move a to 2
97
98
  before[bi[:index]] = [ckey, current] # before[2] = a
98
99
  # update index
99
- mapbefore[ckey] = { index: bi, value: current }
100
+ mapbefore[ckey] = { index: bi[:index], value: current }
100
101
 
101
102
  # move c to 0
102
- yield MovePatch.new(from: bi, to: i)
103
+ yield MovePatch.new(from: bi[:index], to: i, value: bi[:value])
103
104
  else
104
- yield MovePatch.new(from: bi, to: i, delete: true)
105
+ yield MovePatch.new(from: bi[:index], to: i, value: bi[:value], delete: true)
105
106
  mapbefore[ckey] = nil
106
107
  deletions += 1
107
108
  # next
@@ -120,7 +120,7 @@ module Hokusai
120
120
  end
121
121
 
122
122
  def has_ast?(ast, index)
123
- if portal = children![index]&.node&.portal
123
+ if portal = children![index]&.node&.portal&.portal
124
124
  return portal.ast.object_id == ast.object_id
125
125
  end
126
126
 
@@ -71,9 +71,7 @@ module Hokusai
71
71
  child_block.node.meta.set_prop(ast.loop.var.to_sym, value)
72
72
  child_block.node.meta.publisher.add(target)
73
73
 
74
- UpdateEntry.new(child_block, block, target).register(context: ctx)
75
-
76
- child_block.class.provides.merge!(parent.class.provides)
74
+ UpdateEntry.new(child_block, block, target).register(context: ctx, providers: mount_providers.merge(child_block.providers))
77
75
 
78
76
  block.node.meta << child_block
79
77
 
@@ -150,9 +148,8 @@ module Hokusai
150
148
  ctx.add_entry(ast.loop.var, patch.value)
151
149
  children[patch.target].node.add_styles(target.class)
152
150
  children[patch.target].node.add_props_from_block(target, context: ctx)
153
- children[patch.target].node.meta.set_prop(ast.loop.var.to_sym, patch.value)
154
151
 
155
- UpdateEntry.new(children[patch.target], uparent, utarget).register(context: ctx)
152
+ # UpdateEntry.new(children[patch.target], uparent, utarget).register(context: ctx)
156
153
  when MovePatch
157
154
  if patch.delete
158
155
  from = children[patch.from]
@@ -203,12 +200,10 @@ module Hokusai
203
200
  portal = Node.new(ast)
204
201
  node = child_block_class.compile(target_ast.type, portal)
205
202
  node.add_props_from_block(target, context: ctx)
206
- child_block = NodeMounter.new(node, child_block_class).mount(context: ctx)
203
+ child_block = NodeMounter.new(node, child_block_class).mount(context: nil, providers: mount_providers.merge(ublock.providers))
207
204
  child_block.node.add_styles(target.class)
208
205
  child_block.node.meta.publisher.add(target)
209
206
 
210
- child_block.class.provides.merge!(parent.class.provides)
211
-
212
207
  if patch.delete
213
208
  children[patch.target] = child_block
214
209
  else
@@ -40,18 +40,19 @@ module Hokusai
40
40
  unless child_present.call(child)
41
41
  portal = Node.new(child, Node.new(child))
42
42
  node = child_block_klass.compile("root", portal)
43
+ node.add_styles(target.class)
44
+ node.add_props_from_block(target, context: context)
45
+ node.meta.publisher.add(target)
46
+
43
47
  stack = []
44
48
  child.children.each_with_index do |ast, ast_index|
45
- stack << MountEntry.new(ast_index, ast, ublock, uparent, utarget)
49
+ stack << MountEntry.new(ast_index, ast, ublock, uparent, utarget, providers: providers)
46
50
  end
47
51
 
48
- child_block = NodeMounter.new(node, child_block_klass, [stack]).mount(context: context, providers: providers)
49
- UpdateEntry.new(child_block, block, target).register(context: context, providers: providers)
50
-
51
- child_block.node.add_styles(target.class)
52
- child_block.node.add_props_from_block(target, context: context)
53
- child_block.node.meta.publisher.add(target)
52
+ child_block = NodeMounter.new(node, child_block_klass, [stack], previous_providers: providers).mount(context: context, providers: providers)
54
53
 
54
+ UpdateEntry.new(child_block, block, target).register(context: context, providers: providers)
55
+
55
56
  meta.children!.insert(index, child_block)
56
57
 
57
58
  child_block.public_send(:before_updated) if child_block.respond_to?(:before_updated)
@@ -66,21 +67,21 @@ module Hokusai
66
67
  end
67
68
 
68
69
  if child.has_else_condition? && !child.else_condition_active?
69
- portal = Node.new(child, Node.new(child))
70
+ portal = Node.new(child.else_ast, Node.new(child))
70
71
  else_child_block_klass = target.class.use(child.else_ast.type)
71
- node = else_child_block_klass.compile(child.else_ast.type, portal)
72
72
 
73
+ node = else_child_block_klass.compile(child.else_ast.type, portal)
74
+ node.add_styles(utarget.class)
75
+ node.add_props_from_block(utarget, context: context)
76
+ node.meta.publisher.add(utarget)
77
+
73
78
  stack = []
74
79
  child.else_ast.children.each_with_index do |ast, ast_index|
75
- stack << MountEntry.new(ast_index, ast, ublock, uparent, utarget)
80
+ stack << MountEntry.new(ast_index, ast, ublock, uparent, utarget, providers: providers)
76
81
  end
77
82
 
78
- child_block = NodeMounter.new(node, else_child_block_klass, [stack]).mount(context: context, providers: providers)
83
+ child_block = NodeMounter.new(node, else_child_block_klass, [stack], previous_providers: providers).mount(context: context, providers: providers)
79
84
  UpdateEntry.new(child_block, block, utarget).register(context: context, providers: providers)
80
-
81
- child_block.node.add_styles(utarget.class)
82
- child_block.node.add_props_from_block(utarget, context: context)
83
- child_block.node.meta.publisher.add(utarget)
84
85
  meta.children!.insert(index, child_block)
85
86
 
86
87
  child_block.public_send(:before_updated) if child_block.respond_to?(:before_updated)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "random/formatter"
3
+ require "securerandom"
4
4
  require "forwardable"
5
5
  require_relative "./node_mounter"
6
6
  require_relative "./meta"
@@ -21,7 +21,7 @@ module Hokusai
21
21
  def initialize(ast, portal = nil)
22
22
  @ast = ast
23
23
  @portal = portal
24
- @uuid = Random.hex(6).freeze
24
+ @uuid = SecureRandom.hex(6).freeze
25
25
  @meta = Meta.new
26
26
  end
27
27
 
@@ -7,8 +7,8 @@ module Hokusai
7
7
  attr_accessor :primary_stack, :secondary_stack
8
8
  attr_reader :root
9
9
 
10
- def initialize(node, klass, secondary_stack = [], previous_target = nil)
11
- @root = klass.new(node: node)
10
+ def initialize(node, klass, secondary_stack = [], previous_target = nil, previous_providers: {})
11
+ @root = klass.new(node: node, providers: previous_providers)
12
12
 
13
13
  raise Hokusai::Error.new("Root #{klass} doesn't have a node. Did you remember to call `super`?") if @root.node.nil?
14
14
 
@@ -106,7 +106,6 @@ module Hokusai
106
106
  end
107
107
  end
108
108
 
109
- root.public_send(:on_mounted) if root.respond_to?(:on_mounted)
110
109
  root
111
110
  end
112
111
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hokusai-zero
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - skinnyjames
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-25 00:00:00.000000000 Z
11
+ date: 2025-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -197,6 +197,7 @@ files:
197
197
  - ui/spec/hokusai/automation/keys_transcoder_spec.rb
198
198
  - ui/spec/hokusai/automation/selector_spec.rb
199
199
  - ui/spec/hokusai/block_spec.rb
200
+ - ui/spec/hokusai/diff_spec.rb
200
201
  - ui/spec/hokusai/directives_spec.rb
201
202
  - ui/spec/hokusai/e2e/client_spec.rb
202
203
  - ui/spec/hokusai/e2e/meta_spec.rb