hokusai-zero 0.1.4 → 0.1.5

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: 83cc0a077934960a9cfec3c52388de538b6816b6b4b9eb606ef449dac6f90608
4
- data.tar.gz: 514429bc54c1b49743a425e1f6fb1056dd8f564658b4ff8d46905a595c2a0fd9
3
+ metadata.gz: 85dc0be97bf2f9c770f53b45a7790bd5b1dbfda261fb0f0da4f71c6c2c9c67eb
4
+ data.tar.gz: eb62a84b75ba128fbcd6b052e97a95853a0379572be85ea5584c98117af84b61
5
5
  SHA512:
6
- metadata.gz: 337549e39f52146323156c2c9f9deb4e2efd6ee03dfdce73eb6ccaee7416acc59924a3641d36e48c14425e9e1066c6f3c3c32736cfb20afe557c0927bf1572c2
7
- data.tar.gz: d29d8c032605dba3a48e930029a3e8ef588950ae2a4aaae70da6849b00a3243ff9505e5cdfce8edfc8635c2ff4b96d6aeab6d207ae0e81a8a0d70e9931b18e37
6
+ metadata.gz: 1ac8520911b6ba9b3112c68d1f1f653b179b43678c320489a732c97462ba930b637545f018aadc190418be97a6b9fac725eae62dc133ec576e253e87ac518cc6
7
+ data.tar.gz: fad9fccafdca8cca5ad855dd69b6e7741bd251c39152fb04bdcf1a9027dc47acb59661b247eeb2dbbc32dae09c7c8c7b1273eb1c55afd35db62301477da4e7c5
data/README.md CHANGED
@@ -9,13 +9,13 @@ A Ruby library for authoring GUI applications
9
9
 
10
10
  ## Getting started
11
11
 
12
- The build tooling of this project is [xmake](https://xmake.io/#/). You will need it to compile dependencies and run demos.
13
-
14
- Hokusai contains a tree-sitter grammar to parse templates, and uses `md4c` to parse markdown.
12
+ In your Gemfile
15
13
 
16
- When compiling the C portion of hokusai, tree-sitter will be statically linked.
14
+ ```ruby
15
+ gem "hokusai-zero", "0.1.4"
16
+ ```
17
17
 
18
- ## In order to run an application, you will also need to install a backend
18
+ ## In order to run an application, you will need to install a backend
19
19
 
20
20
  ### Raylib
21
21
 
@@ -23,7 +23,7 @@ When compiling the C portion of hokusai, tree-sitter will be statically linked.
23
23
  * Write your app
24
24
  * Run app with `RAYLIB_PATH=/libpath/for/libraylib.(so|dylib) ruby <your app>.rb`
25
25
 
26
- ### SDL
26
+ ### SDL2
27
27
 
28
28
  * Install the following deps
29
29
 
@@ -35,13 +35,6 @@ When compiling the C portion of hokusai, tree-sitter will be statically linked.
35
35
  * Write your app
36
36
  * Run app with `SDL_PATH=/libpath/for/libsdl.(so|dylib) ruby <your app>.rb`
37
37
 
38
-
39
- ## Gemfile
40
-
41
- ```ruby
42
- gem "hokusai-zero"
43
- ```
44
-
45
38
  ## Example counter application
46
39
 
47
40
  ```ruby
@@ -49,86 +42,35 @@ require "hokusai"
49
42
  require "hokusai/backends/raylib"
50
43
 
51
44
  class Counter < Hokusai::Block
52
- style <<~EOF
53
- [style]
54
- additionStyles {
55
- background: rgb(214, 49, 24);
56
- rounding: 0.0;
57
- outline: outline(1,4,4,1);
58
- outline_color: rgb(216, 26, 137);
59
- }
60
-
61
- additionLabel {
62
- size: 40;
63
- color: rgb(255,255,255);
64
- }
65
-
66
- subtractStyles {
67
- background: rgb(0, 85, 170);
68
- rounding: 0.0;
69
- }
70
-
71
- subtractLabel {
72
- size: 50;
73
- color: rgb(255,255,255);
74
- }
75
-
76
- scrollbar {
77
- width: 25;
78
- control_height: 100;
79
- control_padding: 5;
80
- control_rounding: 5;
81
- }
82
- EOF
83
-
84
45
  template <<-EOF
85
46
  [template]
86
- hblock { @keypress="update_keys"}
87
- vblock
88
- hblock
89
- label#count {
90
- :content="count"
91
- size="130"
92
- :color="count_color"
47
+ vblock
48
+ hblock
49
+ label {
50
+ :content="count"
51
+ size="130"
52
+ :color="count_color"
53
+ }
54
+ hblock
55
+ vblock
56
+ label {
57
+ content="Add"
58
+ @click="increment"
59
+ }
60
+ vblock
61
+ label {
62
+ content="Subtract"
63
+ @click="decrement"
93
64
  }
94
- hblock
95
- vblock#add { ...additionStyles }
96
- label {
97
- content="Add"
98
- @click="increment"
99
- ...additionLabel
100
- }
101
- vblock#subtract { ...subtractStyles }
102
- label {
103
- content="Subtract"
104
- @click="decrement"
105
- ...subtractLabel
106
- }
107
- [if="count_positive"]
108
- scrollbar { ...scrollbar }
109
65
  EOF
110
66
 
111
67
  uses(
112
68
  vblock: Hokusai::Blocks::Vblock,
113
69
  hblock: Hokusai::Blocks::Hblock,
114
70
  label: Hokusai::Blocks::Label,
115
- scrollbar: Hokusai::Blocks::Scrollbar,
116
- image: Hokusai::Blocks::Image
117
71
  )
118
72
 
119
- attr_accessor :count, :keys
120
-
121
- def count_positive
122
- count > 0
123
- end
124
-
125
- def modal
126
- !keys.empty?
127
- end
128
-
129
- def update_keys(event)
130
- @keys << event.char
131
- end
73
+ attr_accessor :count
132
74
 
133
75
  def increment(event)
134
76
  self.count += 1
@@ -144,7 +86,7 @@ class Counter < Hokusai::Block
144
86
 
145
87
  def initialize(**args)
146
88
  @count = 0
147
- @keys = ""
89
+
148
90
  super(**args)
149
91
  end
150
92
  end
@@ -158,6 +100,12 @@ end
158
100
 
159
101
  ## Development
160
102
 
103
+ The build tooling of this project is [xmake](https://xmake.io/#/). You will need it to compile dependencies and run demos.
104
+
105
+ Hokusai contains a tree-sitter grammar to parse templates, and uses `md4c` to parse markdown.
106
+
107
+ When compiling the C portion of hokusai, tree-sitter will be statically linked.
108
+
161
109
  Requirements:
162
110
  * [xmake](https://xmake.io/#/) to build dependencies
163
111
  * Ruby to run applications
data/ast/src/core/text.c CHANGED
@@ -3,6 +3,26 @@
3
3
 
4
4
  #include "text.h"
5
5
 
6
+ #ifdef _WIN32
7
+ size_t my_strnlen(const char* src, size_t n) {
8
+ size_t len = 0;
9
+ while (len < n && src[len]) len++;
10
+ return len;
11
+ }
12
+
13
+ char* strndup(const char* s, size_t n) {
14
+ size_t len = my_strnlen(s, n);
15
+ char* p = malloc(len + 1);
16
+ if (p)
17
+ {
18
+ memcpy(p, s, len);
19
+ p[len] = '\0';
20
+ }
21
+
22
+ return p;
23
+ }
24
+ #endif
25
+
6
26
  int hoku_text_char_init(hoku_char** character, int offset, int width)
7
27
  {
8
28
  hoku_char* init = malloc(sizeof(hoku_char));
data/ext/extconf.rb CHANGED
@@ -5,12 +5,14 @@ if MiniPortile.darwin?
5
5
  suffix = "dylib"
6
6
  elsif MiniPortile.linux?
7
7
  suffix = "so"
8
+ elsif MiniPortile.windows?
9
+ suffix = "dll"
8
10
  else
9
11
  raise "Currently only supporting darwin and linux"
10
12
  end
11
13
 
12
- tree_sitter = MiniPortile.new("libtree-sitter", "0.22.2", make_command: "make all")
13
- tree_sitter.files = ["https://github.com/tree-sitter/tree-sitter/archive/refs/tags/v0.22.2.zip"]
14
+ tree_sitter = MiniPortile.new("libtree-sitter", "master", make_command: "make")
15
+ tree_sitter.files = ["https://github.com/tree-sitter/tree-sitter/archive/refs/heads/master.zip"]
14
16
  tree_sitter.tap do |t|
15
17
  if t.source_directory
16
18
  t.prepare_build_directory
@@ -23,7 +25,7 @@ tree_sitter.tap do |t|
23
25
  def t.install
24
26
  return if installed?
25
27
 
26
- execute('install', %Q(PREFIX=#{File.expand_path(port_path)} #{make_cmd} all install))
28
+ execute('install', %Q(#{make_cmd} all install CC=gcc AR=ar), { env: { "PREFIX" => File.expand_path(port_path) }})
27
29
  end
28
30
 
29
31
  t.install
@@ -42,9 +44,25 @@ md4c.cook
42
44
  md4c.activate
43
45
 
44
46
  pre = "#{__dir__}/.."
47
+ cwd = "#{__dir__}"
48
+
49
+ files = %w[ast/src/core/hml.c ast/src/core/ast.c ast/rc/core/style.c ast/src/core/input.c ast/src/core/component.c ast/src/core/util.c ast/src/core/text.c grammar/src/parser.c grammar/src/scanner.c ast/include/hashmap.c]
50
+ objects = %w[hml.o ast.o style.o input.o component.o util.o text.o parser.o scanner.o hashmap.o]
51
+ libraries = ["#{tree_sitter.path}/lib/libtree-sitter.a", "#{md4c.path}/lib/libmd4c.a"]
52
+
53
+ if MiniPortile.windows?
54
+ cflags = "-shared -Wall -Wl,--export-all-symbols -Wl,--enable-auto-import"
55
+ mkdir = "mkdir #{pre}\\vendor\\lib"
56
+ else
57
+ cflags = "-shared -Wall"
58
+ mkdir = "mkdir -p #{pre}/vendor/lib"
59
+ end
45
60
 
46
61
  File.open("Makefile", "w") do |io|
47
62
  io << <<~EOF
63
+ src_files = #{files.map {|file| "#{pre}/#{file}"}.join(" ")}
64
+ object_files = #{objects.map {|file| "#{cwd}/#{file}"}.join(" ")}
65
+ libraries = #{libraries.join(" ")}
48
66
  src_files = #{tree_sitter.path}/lib/libtree-sitter.a #{md4c.path}/lib/libmd4c.a #{pre}/ast/src/core/hml.c #{pre}/ast/src/core/ast.c #{pre}/ast/src/core/style.c #{pre}/ast/src/core/input.c #{pre}/ast/src/core/component.c #{pre}/ast/src/core/util.c #{pre}/ast/src/core/text.c #{pre}/grammar/src/parser.c #{pre}/grammar/src/scanner.c #{pre}/ast/include/hashmap.c
49
67
  includes = -I#{pre}/ast/include -I#{pre}/ast/src -I#{pre}/grammar/src -I#{tree_sitter.path}/include -I#{md4c.path}/include
50
68
  all: clean hokusai
@@ -55,8 +73,9 @@ File.open("Makefile", "w") do |io|
55
73
  #{"\t"}echo "done"
56
74
 
57
75
  hokusai:
58
- #{"\t"}mkdir -p #{pre}/vendor/lib
59
- #{"\t"}gcc -shared -Wall $(includes) -o #{pre}/vendor/lib/libhokusai.#{suffix} $(src_files)
76
+ #{"\t"}#{mkdir}
77
+ #{"\t"}gcc -c $(includes) $(src_files) $(libraries)
78
+ #{"\t"}gcc #{cflags} $(includes) -o #{pre}/vendor/lib/libhokusai.#{suffix} $(object_files) $(libraries)
60
79
 
61
80
  clean:
62
81
  #{"\t"}rm -f vendor/lib/libhokusai.*
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.4'
3
+ s.version = '0.1.5'
4
4
  s.licenses = ['PPL']
5
5
  s.summary = "A Ruby library for writing GUI applications"
6
6
  s.authors = ["skinnyjames"]
@@ -16,6 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.metadata = { "source_code_uri" => "https://codeberg.org/skinnyjames/hokusai" }
17
17
 
18
18
  s.add_dependency "ffi", "~> 1.16"
19
+ s.add_dependency "colorize"
19
20
  s.add_dependency "concurrent-ruby", "~> 1.3.4"
20
21
  s.add_dependency "raylib-bindings", "~> 0.7.9"
21
22
  s.add_dependency "sdl2-bindings", "~> 0.2.3"
@@ -9,6 +9,8 @@ module LibHokusai
9
9
  ffi_lib "#{__dir__}/../../vendor/lib/libhokusai.dylib"
10
10
  when /linux/
11
11
  ffi_lib "#{__dir__}/../../vendor/lib/libhokusai.so"
12
+ when /mingw32|mswin|msys/
13
+ ffi_lib "#{__dir__}/../../vendor/lib/libhokusai.dll"
12
14
  else
13
15
  raise "Unsupported platform!"
14
16
  end
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><title>finite-icons</title><path d="M13.60851,6.08611L8.35425,11.339,3.06908,6.05389a0.5,0.5,0,0,1,0-.70711l0.539-.539a0.5,0.5,0,0,1,.70711,0l4.03907,4.039L12.36249,4.8399a0.5,0.5,0,0,1,.70705.00006l0.539,0.539A0.5,0.5,0,0,1,13.60851,6.08611Z" fill="#231f20"/></svg>
@@ -5,7 +5,18 @@ require 'memory_profiler'
5
5
 
6
6
  module Hokusai::Backends
7
7
  class RaylibBackend
8
- RAYLIB_PATH = ENV["RAYLIB_PATH"] || "#{__dir__}/../../../../vendor/lib"
8
+
9
+ RAYLIB_PATH = ENV["RAYLIB_PATH"] || begin
10
+ path = "#{__dir__}/../../../../vendor/lib"
11
+ case RbConfig::CONFIG['host_os']
12
+ when /darwin/
13
+ "#{path}/libraylib.dylib"
14
+ when /mswin|msys|mingw/
15
+ "#{path}/libraylib.dll"
16
+ when /linux/
17
+ "#{path}/libraylib.so"
18
+ end
19
+ end
9
20
 
10
21
  attr_reader :config
11
22
 
@@ -137,14 +148,7 @@ module Hokusai::Backends
137
148
  def run(block)
138
149
  self.class.reset
139
150
 
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
151
+ Raylib.load_lib(RAYLIB_PATH)
148
152
 
149
153
  resize = false
150
154
  initial = true
@@ -329,7 +333,6 @@ module Hokusai::Backends
329
333
  end
330
334
 
331
335
  Hokusai::Commands::SVG.on_draw do |command|
332
- command = command.as(Hokusai::Commands::SVG)
333
336
  texture = begin
334
337
  if self.class.images[command.source]
335
338
  self.class.images[command.source]
@@ -402,6 +402,33 @@ module Hokusai::Backends
402
402
  SDL.RenderCopy(renderer, texture, nil, rect)
403
403
  end
404
404
 
405
+ Hokusai::Commands::SVG.on_draw do |command|
406
+ next unless inside_scissor(command.x, command.y, command.height)
407
+
408
+ texture = begin
409
+ if self.class.images[command.source]
410
+ self.class.images[command.source]
411
+ else
412
+ file = File.read(command.source)
413
+
414
+ rw = SDL.RWFromConstMem(file, file.size)
415
+ surface_ptr = SDL.IMG_Load_RW(rw, 1)
416
+ if surface_ptr.null?
417
+ raise Hokusai::Error.new("Can't load Image: #{SDL.GetError.read_string}")
418
+ end
419
+
420
+ texture = SDL.CreateTextureFromSurface(renderer, surface_ptr)
421
+ self.class.images[command.source] = texture
422
+ SDL.FreeSurface(surface_ptr)
423
+ texture
424
+ end
425
+ end
426
+
427
+ SDL.SetTextureColorMod(texture, command.color.r, command.color.g, command.color.b)
428
+ rect = sdl_rect(command.x, command.y, command.width, command.height)
429
+ SDL.RenderCopy(renderer, texture, nil, rect)
430
+ end
431
+
405
432
  Hokusai::Commands::Rect.on_draw do |command|
406
433
  next unless inside_scissor(command.x, command.y, command.height)
407
434
  # draw background first
@@ -179,23 +179,23 @@ module Hokusai
179
179
 
180
180
  def draw(&block)
181
181
  instance_eval(&block)
182
- # node.meta.commands.each(&:draw)
183
- # node.meta.commands.clear!
184
182
  end
185
183
 
186
184
  def method_missing(name, *args,**kwargs, &block)
187
- if Hokusai::Meta.commands.respond_to?(name)
188
- return Hokusai::Meta.commands.send(name, *args, **kwargs, &block)
185
+ if node.meta.commands.respond_to?(name)
186
+ return node.meta.commands.send(name, *args, **kwargs, &block)
189
187
  end
190
188
 
191
189
  super
192
190
  end
193
191
 
194
192
  def draw_with
195
- yield Hokusai::Meta.commands
193
+ yield node.meta.commands
194
+ end
196
195
 
197
- # node.meta.commands.each(&:draw)
198
- # node.meta.commands.clear!
196
+ def execute_draw
197
+ node.meta.commands.execute
198
+ node.meta.commands.clear!
199
199
  end
200
200
 
201
201
  def render(canvas)
@@ -0,0 +1,205 @@
1
+ class Hokusai::Blocks::DropDownItem < Hokusai::Block
2
+ style <<~EOF
3
+ [style]
4
+ cursorStyle {
5
+ cursor: "pointer";
6
+ }
7
+ EOF
8
+
9
+ template <<~EOF
10
+ [template]
11
+ text {
12
+ @hover="hover_handler"
13
+ @mouseout="mouseout_handler"
14
+ ...cursorStyle
15
+ :content="option"
16
+ :color="color"
17
+ :size="size"
18
+ @click="emit_option"
19
+ @height_updated="update_height"
20
+ }
21
+ EOF
22
+
23
+ uses(text: Hokusai::Blocks::Text)
24
+
25
+ computed! :index
26
+ computed! :option
27
+ computed! :size
28
+ computed! :color
29
+
30
+ attr_accessor :hovered
31
+
32
+ def hover_handler(event)
33
+ self.hovered = true
34
+ end
35
+
36
+ def mouseout_handler(event)
37
+ self.hovered = false
38
+ end
39
+
40
+ def update_height(height)
41
+ node.meta.set_prop(:height, height)
42
+
43
+ emit("height", index, height)
44
+ end
45
+
46
+ def emit_option(event)
47
+ emit("option", index)
48
+ end
49
+
50
+ def render(canvas)
51
+ if hovered
52
+ draw do
53
+ rect(canvas.x, canvas.y, canvas.width, canvas.height) do |command|
54
+ command.color = Hokusai::Color.new(255, 255, 255, 20)
55
+ end
56
+ end
57
+ end
58
+
59
+ yield canvas
60
+ end
61
+ end
62
+
63
+ class Hokusai::Blocks::Dropdown < Hokusai::Block
64
+ style <<~EOF
65
+ [style]
66
+ panelStyle {
67
+ z: 1;
68
+ }
69
+ rootStyle {
70
+ height: 70.0;
71
+ color: rgb(122, 118, 135);
72
+ }
73
+
74
+ cursorStyle {
75
+ cursor: "pointer";
76
+ }
77
+ EOF
78
+
79
+ template <<~EOF
80
+ [template]
81
+ vblock {
82
+ @hover="dropdown_hover"
83
+ @mouseout="dropdown_mouseout"
84
+ @click="dropdown_toggle"
85
+ :background="background"
86
+ :height="size_with_padding"
87
+ :outline="outline"
88
+ :outline_color="outline_color"
89
+ }
90
+ hblock
91
+ label {
92
+ ...cursorStyle
93
+ :content="dropdown_option_selected"
94
+ :size="size"
95
+ :color="text_color"
96
+ }
97
+ image {
98
+ padding="5.0,10.0,5.0,5.0"
99
+ :source="dropdown_icon"
100
+ :height="size"
101
+ :width="size"
102
+ }
103
+ [if="dropdown_is_toggled"]
104
+ panel#first {
105
+ ...panelStyle
106
+ :height="panel_height"
107
+ :background="background"
108
+ }
109
+ [for="option in options"]
110
+ dropdown_item {
111
+ :color="text_color"
112
+ :size="size"
113
+ :height="option_height(index)"
114
+ :option="option_content(option)"
115
+ :key="dropdown_key(option, index)"
116
+ :index="index"
117
+ @option="option_emit_selected"
118
+ @height="option_update_height"
119
+ }
120
+ EOF
121
+
122
+ uses(
123
+ vblock: Hokusai::Blocks::Vblock,
124
+ hblock: Hokusai::Blocks::Hblock,
125
+ label: Hokusai::Blocks::Text,
126
+ panel: Hokusai::Blocks::Panel,
127
+ image: Hokusai::Blocks::Image,
128
+ dropdown_item: Hokusai::Blocks::DropDownItem
129
+ )
130
+
131
+ computed! :options
132
+ computed :option_callback, default: ->(option) { option }
133
+ computed :size, default: 25, convert: proc(&:to_i)
134
+ computed :padding, default: Hokusai::Padding.new(5.0, 5.0, 5.0, 5.0), convert: Hokusai::Padding
135
+ computed :background, default: Hokusai::Color.new(67, 64, 92), convert: Hokusai::Color
136
+ computed :outline, default: Hokusai::Outline.new(0.0, 0.0, 1.0, 0.0), convert: Hokusai::Outline
137
+ computed :outline_color, default: Hokusai::Color.new(122, 118, 135), convert: Hokusai::Color
138
+ computed :text_color, default: Hokusai::Color.new(255, 255, 255), convert: Hokusai::Color
139
+ computed :panel_width, default: 200.0, convert: proc(&:to_f)
140
+ computed :panel_height, default: 300.0, convert: proc(&:to_f)
141
+ computed :icon, default: "#{ASSETS_FOLDER}/arrow-drop-down-line.png"
142
+
143
+ attr_accessor :selected, :dropdown_is_toggled, :hovered
144
+ attr_reader :heights
145
+
146
+ def dropdown_icon
147
+ icon
148
+ end
149
+
150
+ def dropdown_hover(event)
151
+ self.hovered = true
152
+ end
153
+
154
+ def dropdown_mouseout(event)
155
+ self.hovered = false
156
+ end
157
+
158
+ def size_with_padding
159
+ size + padding.top + padding.bottom
160
+ end
161
+
162
+ def dropdown_key(option, index)
163
+ "option-#{option_callback.call(option)}-#{index}"
164
+ end
165
+
166
+ def dropdown_toggle(event)
167
+ self.dropdown_is_toggled = !dropdown_is_toggled
168
+ end
169
+
170
+ def dropdown_option_selected
171
+ option_callback.call(selected)
172
+ end
173
+
174
+ def option_height(index)
175
+ heights[index]
176
+ end
177
+
178
+ def option_update_height(index, height)
179
+ heights[index] = height
180
+ end
181
+
182
+ def option_content(option)
183
+ option_callback.call(option)
184
+ end
185
+
186
+ def after_updated
187
+ node.meta.set_prop(:height, size * 2)
188
+ end
189
+
190
+ def option_emit_selected(index)
191
+ emit("selected", options[index])
192
+
193
+ self.selected = options[index]
194
+ self.dropdown_is_toggled = false
195
+ end
196
+
197
+ def initialize(**args)
198
+ super
199
+
200
+ @hovered = false
201
+ @heights = {}
202
+ @dropdown_is_toggled = false
203
+ @selected = options.first
204
+ end
205
+ end
@@ -9,12 +9,13 @@ class Hokusai::Blocks::Image < Hokusai::Block
9
9
  computed! :source
10
10
  computed :width, default: nil
11
11
  computed :height, default: nil
12
+ computed :padding, default: Hokusai::Padding.new(0.0, 0.0, 0.0, 0.0), convert: Hokusai::Padding
12
13
 
13
14
  def render(canvas)
14
15
  src = Pathname.new(source).absolute? ? source : "#{File.dirname(caller[-1].split(":")[0])}/#{source}"
15
16
 
16
17
  draw do
17
- image(src, canvas.x, canvas.y, width&.to_f || canvas.width, height&.to_f || canvas.height)
18
+ image(src, canvas.x + padding.left, canvas.y + padding.top, (width&.to_f || canvas.width) - padding.right, (height&.to_f || canvas.height) - padding.bottom)
18
19
  end
19
20
 
20
21
  yield canvas
@@ -0,0 +1,62 @@
1
+ class Hokusai::Blocks::Modal < Hokusai::Block
2
+ style <<~EOF
3
+ [style]
4
+ closeButtonStyle {
5
+ width: 40;
6
+ height: 40;
7
+ cursor: "pointer";
8
+ padding: padding(10.0, 10.0, 10.0, 0.0)
9
+ }
10
+ EOF
11
+
12
+ template <<~EOF
13
+ [template]
14
+ hblock
15
+ empty
16
+ empty
17
+ image {
18
+ :source="close_icon"
19
+ ...closeButtonStyle
20
+ @click="emit_close"
21
+
22
+ }
23
+ hblock
24
+ empty
25
+ slot
26
+ empty
27
+ hblock
28
+ empty
29
+ EOF
30
+
31
+ uses(
32
+ vblock: Hokusai::Blocks::Vblock,
33
+ hblock: Hokusai::Blocks::Hblock,
34
+ empty: Hokusai::Blocks::Empty,
35
+ image: Hokusai::Blocks::Image
36
+ )
37
+
38
+ computed :active, default: false
39
+ computed :background, default: [0, 0, 0, 200], convert: Hokusai::Color
40
+ computed :close_icon, default: "#{ASSETS_FOLDER}/close-large-line.png"
41
+
42
+ def emit_close(event)
43
+ emit("close")
44
+ end
45
+
46
+ def on_mounted
47
+ node.meta.set_prop(:z, 1)
48
+ node.meta.set_prop(:ztarget, "root")
49
+ end
50
+
51
+ def render(canvas)
52
+ return unless active
53
+
54
+ draw do
55
+ rect(canvas.x, canvas.y, canvas.width, canvas.height) do |command|
56
+ command.color = background
57
+ end
58
+ end
59
+
60
+ yield canvas
61
+ end
62
+ end
@@ -62,8 +62,8 @@ class Hokusai::Blocks::Panel < Hokusai::Block
62
62
  if y = top
63
63
  if new_scroll_y < y
64
64
  self.scroll_goto_y = y
65
- elsif new_scroll_y > panel_height
66
- self.scroll_goto_y = panel_height
65
+ elsif new_scroll_y - top >= panel_height
66
+ self.scroll_goto_y = panel_height if scroll_percent != 1.0
67
67
  else
68
68
  self.scroll_goto_y = new_scroll_y
69
69
  end
@@ -50,8 +50,6 @@ class Hokusai::Blocks::Scrollbar < Hokusai::Block
50
50
  else
51
51
  self.scrolling = false
52
52
  end
53
-
54
- # event.stop
55
53
  end
56
54
 
57
55
  def scroll_top_height
@@ -15,6 +15,7 @@ class Hokusai::Blocks::Text < Hokusai::Block
15
15
  uses(empty: Hokusai::Blocks::Empty)
16
16
 
17
17
  computed! :content
18
+ computed :cursor, default: nil
18
19
  computed :font, default: nil
19
20
  computed :size, default: 16, convert: proc(&:to_i)
20
21
  computed :line_height, default: 5, convert: proc(&:to_f)
@@ -170,7 +171,7 @@ class Hokusai::Blocks::Text < Hokusai::Block
170
171
  end
171
172
  end
172
173
 
173
- if link_hovered
174
+ if link_hovered || cursor == "pointer"
174
175
  Hokusai.set_mouse_cursor(:pointer)
175
176
  elsif hovered
176
177
  Hokusai.set_mouse_cursor(:ibeam)
@@ -15,7 +15,9 @@ module Hokusai
15
15
  class Commands
16
16
  attr_reader :queue
17
17
 
18
- def initialize; end
18
+ def initialize
19
+ @queue = []
20
+ end
19
21
 
20
22
  # Draw a rectangle
21
23
  #
@@ -28,7 +30,8 @@ module Hokusai
28
30
 
29
31
  yield(command)
30
32
 
31
- command.draw
33
+ queue << command
34
+ # command.draw
32
35
  end
33
36
 
34
37
  # Draw a circle
@@ -41,7 +44,9 @@ module Hokusai
41
44
 
42
45
  yield(command)
43
46
 
44
- command.draw
47
+
48
+ queue << command
49
+ # command.draw
45
50
  end
46
51
 
47
52
  # Draws an SVG
@@ -55,23 +60,25 @@ module Hokusai
55
60
  command = Commands::SVG.new(source, x, y, w, h)
56
61
 
57
62
  yield(command)
63
+
64
+ queue << command
58
65
  end
59
66
 
60
67
  # Invokes an image command
61
68
  # from a filename, at position {x,y} with `w`x`h` dimensions
62
69
  def image(source, x, y, w, h)
63
- Commands::Image.new(source, x, y, w, h).draw
70
+ queue << Commands::Image.new(source, x, y, w, h)
64
71
  end
65
72
 
66
73
  # Invokes a scissor begin command
67
74
  # at position {x,y} with `w`x`h` dimensions
68
75
  def scissor_begin(x, y, w, h)
69
- Commands::ScissorBegin.new(x, y, w, h).draw
76
+ queue << Commands::ScissorBegin.new(x, y, w, h)
70
77
  end
71
78
 
72
79
  # Invokes a scissor stop command
73
80
  def scissor_end
74
- Commands::ScissorEnd.new.draw
81
+ queue << Commands::ScissorEnd.new
75
82
  end
76
83
 
77
84
  # Draws text
@@ -83,7 +90,15 @@ module Hokusai
83
90
  command = Commands::Text.new(content, x, y)
84
91
  yield command
85
92
 
86
- command.draw
93
+ queue << command
94
+ end
95
+
96
+ def execute
97
+ queue.each(&:draw)
98
+ end
99
+
100
+ def clear!
101
+ queue.clear
87
102
  end
88
103
  end
89
104
  end
@@ -5,7 +5,7 @@ module Hokusai
5
5
  attr_reader :focused, :parent, :target, :updater,
6
6
  :props, :publisher
7
7
 
8
- def self.commands
8
+ def commands
9
9
  @commands ||= Commands.new
10
10
  end
11
11
 
@@ -126,13 +126,6 @@ module Hokusai
126
126
 
127
127
  false
128
128
  end
129
- #
130
- # def destroy
131
- # children.each do |child|
132
- # child.before_destroy if child.respond_to?(:before_destroy)
133
- # child.node.destroy
134
- # end
135
- # end
136
129
 
137
130
  def child_delete(index)
138
131
  if child = children![index]
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "./event"
4
+ require "colorize"
4
5
 
5
6
  module Hokusai
6
7
  class PainterEntry
@@ -22,6 +23,9 @@ module Hokusai
22
23
  end
23
24
  end
24
25
 
26
+ ZTARGET_ROOT = "root"
27
+ ZTARGET_PARENT = "parent"
28
+
25
29
  class Painter
26
30
  attr_reader :root, :input, :before_render, :after_render,
27
31
  :events
@@ -52,17 +56,36 @@ module Hokusai
52
56
  @after_render = block
53
57
  end
54
58
 
59
+ # def debug(parent, children)
60
+ # @i ||= 0
61
+
62
+ # pp [
63
+ # "#{@i}",
64
+ # "parent: #{parent.block.class}##{parent.block.node.portal&.ast&.id} (z: #{parent.block.node.meta.get_prop(:z)})",
65
+ # "children: #{children.map {|c| "#{c.block.class}##{c.block.node.portal&.ast&.id} (z: #{c.block.node.meta.get_prop(:z)})"} }"
66
+ # ]
67
+ # @i += 1
68
+ # end
69
+
55
70
  # @return [Array(Commands::Base)] the command list
56
71
  def render(canvas, resize = false, capture: true)
57
72
  return if root.children.empty?
58
73
 
74
+ zindexed = {}
75
+ zindex_counter = 0
76
+ zroot_x = canvas.x
77
+ zroot_y = canvas.y
78
+ zroot_w = canvas.width
79
+ zroot_h = canvas.height
80
+
59
81
  @root.on_resize(canvas) if resize
60
82
 
61
83
  before_render&.call([root, nil], canvas, input)
62
84
 
63
85
  root_children = (canvas.reverse? ? root.children?&.reverse.dup : root.children?&.dup) || []
64
86
  groups = []
65
- groups << [root, measure(root_children, canvas)]
87
+ root_entry = PainterEntry.new(root, canvas.x, canvas.y, canvas.width, canvas.height)
88
+ groups << [root_entry, measure(root_children, canvas)]
66
89
 
67
90
  mouse_y = input.mouse.pos[:y]
68
91
  can_capture = mouse_y >= (canvas.y || 0.0) && mouse_y <= (canvas.y || 0.0) + canvas.height
@@ -71,9 +94,25 @@ module Hokusai
71
94
 
72
95
  while payload = groups.pop
73
96
  group_parent, group_children = payload
97
+
98
+ parent_z = group_parent.block.node.meta.get_prop(:z)&.to_i
99
+ zindex_counter -= 1 if (parent_z || 0) > 0 && group_children.empty?
74
100
 
75
101
  while group = group_children.shift
76
- canvas.reset(group.x, group.y, group.w, group.h)
102
+ z = group.block.node.meta.get_prop(:z)&.to_i || 0
103
+ ztarget = group.block.node.meta.get_prop(:ztarget)
104
+ entry = PainterEntry.new(group.block, group.x, group.y, group.w, group.h)
105
+
106
+ if (zindex_counter > 0 || z > 0)
107
+ case ztarget
108
+ when ZTARGET_ROOT
109
+ entry = PainterEntry.new(group.block, zroot_x || 0.0, zroot_y || 0.0, zroot_w, zroot_h).freeze
110
+ when ZTARGET_PARENT
111
+ entry = PainterEntry.new(group.block, group_parent.x || 0.0, group_parent.y || 0.0, group_parent.w, group_parent.h).freeze
112
+ end
113
+ end
114
+
115
+ canvas.reset(entry.x, entry.y, entry.w, entry.h)
77
116
 
78
117
  before_render&.call([group.block, group.parent], canvas, input.raw)
79
118
 
@@ -92,17 +131,36 @@ module Hokusai
92
131
 
93
132
  unless local_children.nil?
94
133
  groups << [group_parent, group_children]
95
- groups << [group.block, measure(local_children, local_canvas)]
134
+ parent = PainterEntry.new(group.block, canvas.x, canvas.y, canvas.width, canvas.height)
135
+ groups << [parent, measure(local_children, local_canvas)]
136
+
96
137
  breaked = true
97
138
  else
98
139
  breaked = false
99
140
  end
100
141
  end
142
+
143
+ if z > 0
144
+ zindex_counter += 1
145
+ # puts ["start (#{z}) <#{parent_z}> {#{zindex_counter}} #{group.block.class}".colorize(:blue), z, group.block.node.portal&.ast&.id]
146
+ zindexed[zindex_counter] ||= []
147
+ zindexed[zindex_counter] << group.block
148
+ elsif zindex_counter > 0
149
+ # puts ["push (#{z}) <#{parent_z}> {#{zindex_counter}} initial #{group.block.class}".colorize(:red), z, group.block.node.portal&.ast&.id]
150
+ zindexed[zindex_counter] << group.block
151
+ else
152
+ # puts ["draw (#{z}) <#{parent_z}> {#{zindex_counter}} #{group.block.class}".colorize(:yellow), z, group.block.node.portal&.ast&.id]
153
+ group.block.execute_draw
154
+ end
101
155
 
102
156
  break if breaked
103
157
  end
104
158
  end
105
159
 
160
+ zindexed.sort.each do |z, blocks|
161
+ blocks.each(&:execute_draw)
162
+ end
163
+
106
164
  if capture
107
165
  events[:hover].bubble
108
166
  events[:wheel].bubble
@@ -132,10 +190,12 @@ module Hokusai
132
190
  hsum = 0.0
133
191
 
134
192
  children.each do |block|
135
- # children.each do |block|
193
+ z = block.node.meta.get_prop?(:z)&.to_i || 0
136
194
  h = block.node.meta.get_prop?(:height)&.to_f
137
195
  w = block.node.meta.get_prop?(:width)&.to_f
138
196
 
197
+ next if z > 0
198
+
139
199
  if w
140
200
  wsum += w
141
201
  wcount = wcount.succ
@@ -195,12 +255,11 @@ module Hokusai
195
255
  events[:hover].capture(block, canvas)
196
256
  events[:click].capture(block, canvas)
197
257
  events[:wheel].capture(block, canvas)
198
- events[:mousedown].capture(block, canvas)
199
258
  events[:mouseup].capture(block, canvas)
259
+ events[:mousedown].capture(block, canvas)
200
260
  else
201
261
  events[:mouseout].capture(block, canvas)
202
262
  end
203
-
204
263
  events[:mousemove].capture(block, canvas)
205
264
 
206
265
  if block_is_hovered || block.node.meta.focused
data/ui/src/hokusai.rb CHANGED
@@ -1,3 +1,5 @@
1
+ ASSETS_FOLDER = "#{__dir__}/hokusai/assets"
2
+
1
3
  require_relative '../lib/lib_hokusai'
2
4
  require_relative './hokusai/error'
3
5
  require_relative './hokusai/types'
@@ -34,6 +36,8 @@ require_relative './hokusai/blocks/selectable'
34
36
  require_relative './hokusai/blocks/input'
35
37
  require_relative './hokusai/blocks/variable'
36
38
  require_relative './hokusai/blocks/titlebar/osx'
39
+ require_relative './hokusai/blocks/modal'
40
+ require_relative './hokusai/blocks/dropdown'
37
41
 
38
42
  require "concurrent"
39
43
 
@@ -48,6 +52,19 @@ module Hokusai
48
52
  fallback_policy: :caller_runs
49
53
  )
50
54
 
55
+ SHADER_UNIFORM_FLOAT = 0 # Shader uniform type: float
56
+ SHADER_UNIFORM_VEC2 = 1 # Shader uniform type: vec2 (2 float)
57
+ SHADER_UNIFORM_VEC3 = 2 # Shader uniform type: vec3 (3 float)
58
+ SHADER_UNIFORM_VEC4 = 3 # Shader uniform type: vec4 (4 float)
59
+ SHADER_UNIFORM_INT = 4 # Shader uniform type: int
60
+ SHADER_UNIFORM_IVEC2 = 5 # Shader uniform type: ivec2 (2 int)
61
+ SHADER_UNIFORM_IVEC3 = 6 # Shader uniform type: ivec3 (3 int)
62
+ SHADER_UNIFORM_IVEC4 = 7 # Shader uniform type: ivec4 (4 int)
63
+ SHADER_UNIFORM_UINT = 8 # Shader uniform type: unsigned int
64
+ SHADER_UNIFORM_UIVEC2 = 9 # Shader uniform type: uivec2 (2 unsigned int)
65
+ SHADER_UNIFORM_UIVEC3 = 10 # Shader uniform type: uivec3 (3 unsigned int)
66
+ SHADER_UNIFORM_UIVEC4 = 11 # Shader uniform type: uivec4 (4 unsigned int)
67
+
51
68
  # Access the font registry
52
69
  #
53
70
  # @return [Hokusai::FontRegistry]
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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - skinnyjames
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-11 00:00:00.000000000 Z
11
+ date: 2025-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: concurrent-ruby
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +206,9 @@ files:
192
206
  - ui/spec/hokusai_spec.rb
193
207
  - ui/spec/spec_helper.rb
194
208
  - ui/src/hokusai.rb
209
+ - ui/src/hokusai/assets/arrow-drop-down-line.png
210
+ - ui/src/hokusai/assets/chevron-down.svg
211
+ - ui/src/hokusai/assets/close-large-line.png
195
212
  - ui/src/hokusai/ast.rb
196
213
  - ui/src/hokusai/automation.rb
197
214
  - ui/src/hokusai/automation/client.rb
@@ -224,12 +241,14 @@ files:
224
241
  - ui/src/hokusai/blocks/circle.rb
225
242
  - ui/src/hokusai/blocks/clipped.rb
226
243
  - ui/src/hokusai/blocks/cursor.rb
244
+ - ui/src/hokusai/blocks/dropdown.rb
227
245
  - ui/src/hokusai/blocks/dynamic.rb
228
246
  - ui/src/hokusai/blocks/empty.rb
229
247
  - ui/src/hokusai/blocks/hblock.rb
230
248
  - ui/src/hokusai/blocks/image.rb
231
249
  - ui/src/hokusai/blocks/input.rb
232
250
  - ui/src/hokusai/blocks/label.rb
251
+ - ui/src/hokusai/blocks/modal.rb
233
252
  - ui/src/hokusai/blocks/panel.rb
234
253
  - ui/src/hokusai/blocks/rect.rb
235
254
  - ui/src/hokusai/blocks/scissor_begin.rb