hokusai-zero 0.2.7 → 0.2.8

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: 608b618b6ed01eea7db698a5e8d18d7916d8e06ef9227b6ce0b24a9e9e744d24
4
- data.tar.gz: f0ae7f8a0e94aa8056989af6090a8e734dc1674880a3b138338d6f15a55500d0
3
+ metadata.gz: 3430bbda86d7deae31c2e789646f7347e378bb8e0349bb7f37545010417b5d1f
4
+ data.tar.gz: 72457b126d79909ef0989cb67fd278993377b7aa18b9e759b6822e2c05380e8c
5
5
  SHA512:
6
- metadata.gz: 42dad85dbcaf2e3a5bf5af87cfb608b18be65b978cdeac5fe13aa6f27c944ae0661aa3c92045d296e65065049482f66d4a02d27a187950a18fa5060857b0712b
7
- data.tar.gz: ae90ae51b1025fe9f991b05ef632b7ff4f354c541f34ad507c8c8e90ac8cd80ee35e759ae82cb45f2d8060530572c9dba468a6433b76ec76b64df4705fb45991
6
+ metadata.gz: 407646a8c7bb287f18b1cc864d72150f4f93f12e29dbcb833befb9968ee5bbea48e0447fda35ca9b40513e4a371db499bc3cd4457e66f8f1d70a6b1d6c0692bb
7
+ data.tar.gz: ea2e824fb8df8fb0c0a28fbf2ffeb6768f6c2a66d900bf0f2146e04da68dad0a9d93e30eb36d654a60deec1fa4d0625321d9d21a47156a3162bd428f10049b8d
data/ast/src/core/hml.c CHANGED
@@ -707,6 +707,8 @@ int hoku_style_from_template(hoku_style** out, char* template)
707
707
  return -1;
708
708
  }
709
709
 
710
+ ts_tree_delete(tree);
711
+ ts_parser_delete(parser);
710
712
  *out = style;
711
713
  return 0;
712
714
  }
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.2.7'
3
+ s.version = '0.2.8'
4
4
  s.licenses = ['MIT']
5
5
  s.summary = "A Ruby library for writing GUI applications"
6
6
  s.authors = ["skinnyjames"]
@@ -19,7 +19,6 @@ describe Hokusai::Automation::Client do
19
19
  attr_accessor :count
20
20
 
21
21
  def increment(_event)
22
- # pp ["INCREMENTING FUCK"]
23
22
  self.count += 1
24
23
  end
25
24
 
@@ -26,7 +26,7 @@ module Hokusai
26
26
  class Font < Hokusai::Font
27
27
  attr_reader :raw, :sdf
28
28
 
29
- DEFAULT = "–—‘’“”…\r\n\t0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%%^&*(),.?/\"\\[]-_=+|~`{}<>;:'\0"
29
+ DEFAULT = "–—‘’“”…\r\n\t\s0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%%^&*(),.?/\"\\[]-_=+|~`{}<>;:'\0"
30
30
 
31
31
  def self.default
32
32
  font = Raylib.GetFontDefault
@@ -83,6 +83,26 @@ module Hokusai
83
83
  @raw = raw
84
84
  @spacing = spacing
85
85
  @sdf = sdf
86
+ @measure_map = {}
87
+
88
+ DEFAULT.split("").each do |char|
89
+ @measure_map[char] ||= begin
90
+ bcount = FFI::MemoryPointer.new(:int)
91
+ letter = Raylib.GetCodepoint(char, bcount)
92
+ bcount.free
93
+ info = Raylib.GetGlyphInfo(raw, letter)
94
+ if info.advanceX > 0
95
+ w = info.advanceX
96
+ else
97
+ w = info.image.width + info.offsetX
98
+ end
99
+
100
+ ->(size) do
101
+ width = (size.to_f / raw.baseSize.to_f) * w + (spacing)
102
+ width
103
+ end
104
+ end
105
+ end
86
106
  end
87
107
 
88
108
  # returns the spacing for this font
@@ -94,6 +114,16 @@ module Hokusai
94
114
  (ssize / (raw.baseSize.zero? ? 1 : raw.baseSize)).to_f
95
115
  end
96
116
 
117
+ def measure_char(char, size)
118
+ w = @measure_map[char]&.call(size)
119
+
120
+ if w.nil?
121
+ measure(char, size).first
122
+ else
123
+ w
124
+ end
125
+ end
126
+
97
127
  def measure(string, size)
98
128
  vec = Raylib.MeasureTextEx(raw, string, size, spacing)
99
129
 
@@ -380,6 +380,10 @@ module Hokusai::Backends
380
380
  end
381
381
  end
382
382
 
383
+ Hokusai.on_copy do |text|
384
+ Raylib.SetClipboardText(text)
385
+ end
386
+
383
387
  Hokusai.on_set_mouse_position do |mouse|
384
388
  Raylib.SetMousePosition(mouse.pos.x, mouse.pos.y)
385
389
  end
@@ -410,6 +414,34 @@ module Hokusai::Backends
410
414
  inside_scissor(canvas.x, canvas.y, canvas.height)
411
415
  end
412
416
 
417
+ Hokusai::Commands::TranslationBegin.on_draw do |command|
418
+ Raylib.rlPushMatrix
419
+ Raylib.rlTranslatef(command.x, command.y, 0)
420
+ end
421
+
422
+ Hokusai::Commands::TranslationEnd.on_draw do |command|
423
+ Raylib.rlPopMatrix
424
+ end
425
+
426
+ Hokusai::Commands::RotationBegin.on_draw do |command|
427
+ Raylib.rlPushMatrix
428
+ Raylib.rlTranslatef(command.x, command.y, 0)
429
+ Raylib.rlRotatef(command.degrees, 0, 0, 1);
430
+ end
431
+
432
+ Hokusai::Commands::RotationEnd.on_draw do
433
+ Raylib.rlPopMatrix
434
+ end
435
+
436
+ Hokusai::Commands::ScaleBegin.on_draw do |command|
437
+ Raylib.rlPushMatrix
438
+ Raylib.rlScalef(command.x, command.y, 0)
439
+ end
440
+
441
+ Hokusai::Commands::ScaleEnd.on_draw do
442
+ Raylib.rlPopMatrix
443
+ end
444
+
413
445
  Hokusai::Commands::ShaderBegin.on_draw do |command|
414
446
  self.class.shaders[command.hash] ||= Raylib.LoadShaderFromMemory(command.vertex_shader, command.fragment_shader)
415
447
 
@@ -25,6 +25,8 @@ class Hokusai::Blocks::Dynamic < Hokusai::Block
25
25
  w += block.node.meta.get_prop?(:width)&.to_f || 0.0
26
26
  end
27
27
 
28
+ node.meta.set_prop(:height, h)
29
+
28
30
  [w, h]
29
31
  end
30
32
 
@@ -39,6 +39,8 @@ class Hokusai::Blocks::Panel < Hokusai::Block
39
39
  provide :panel_height, :panel_height
40
40
  provide :panel_top, :panel_top
41
41
 
42
+ inject :selection
43
+
42
44
  attr_accessor :top, :panel_height, :scroll_y, :scroll_percent,
43
45
  :scroll_goto_y, :clipped_offset, :clipped_content_height
44
46
 
@@ -1,7 +1,14 @@
1
1
  class Hokusai::Blocks::Scrollbar < Hokusai::Block
2
+ style <<~EOF
3
+ [style]
4
+ scrollbar {
5
+ cursor: "pointer";
6
+ }
7
+ EOF
2
8
  template <<~EOF
3
9
  [template]
4
10
  vblock.scrollbar {
11
+ ...scrollbar
5
12
  @mousedown="scroll_start"
6
13
  @mousemove="scroll_handle"
7
14
  :background="background"
@@ -38,6 +38,7 @@ module Hokusai::Blocks
38
38
 
39
39
  def start_selection(event)
40
40
  if event.left.down && !selection.active?
41
+ selection.clear
41
42
  selection.start(event.pos.x, event.pos.y)
42
43
  end
43
44
  end
@@ -0,0 +1,130 @@
1
+ class Hokusai::Blocks::TextStream < Hokusai::Block
2
+ template <<~EOF
3
+ [template]
4
+ empty {
5
+ cursor="ibeam"
6
+ }
7
+ EOF
8
+
9
+ uses(empty: Hokusai::Blocks::Empty)
10
+
11
+ computed! :content
12
+ computed :font, default: nil
13
+ computed :size, default: 16, convert: proc(&:to_i)
14
+ computed :color, default: Hokusai::Color.new(33, 33, 33), convert: Hokusai::Color
15
+ computed :selection_color, default: Hokusai::Color.new(233,233,233), convert: Hokusai::Color
16
+ computed :padding, default: Hokusai::Padding.new(5.0, 5.0, 5.0, 5.0), convert: Hokusai::Padding
17
+ computed :cursor_offset, default: nil
18
+
19
+ inject :selection
20
+ inject :panel_top
21
+ inject :panel_height
22
+ inject :panel_offset
23
+
24
+ attr_accessor :last_commands, :last_content, :last_coords, :last_height,
25
+ :last_width, :last_selector, :last_panel_offset, :copying, :buffer
26
+
27
+ attr_reader :stream
28
+
29
+ def initialize(**args)
30
+ @last_commands = []
31
+ @last_coords = nil
32
+ @last_height = 0.0
33
+ @last_width = nil
34
+ @last_selector = nil
35
+ @last_content = nil
36
+ @last_panel_offset = nil
37
+ @last_canvas = nil
38
+ @copying = false
39
+ @reset = false
40
+ @buffer = ""
41
+
42
+ super
43
+ end
44
+
45
+ def after_updated
46
+ @stream&.reset(last_width)
47
+ end
48
+
49
+ def render(canvas)
50
+ w = canvas.width - padding.left - padding.right
51
+ poff = panel_offset || 0.0 - panel_top || 0.0
52
+ selection&.offset_y = poff
53
+
54
+ lfont = Hokusai.fonts.active_font_name
55
+ Hokusai.fonts.activate font unless font.nil?
56
+
57
+ # if Hokusai.can_render(canvas)
58
+ # if (last_content != content ||
59
+ # last_width != w ||
60
+ # panel_offset != last_panel_offset ||
61
+ # last_coords != selection&.coords ||
62
+ # copying ||
63
+ # last_selector != selection&.type)
64
+
65
+ last_commands.clear
66
+ self.last_panel_offset = panel_offset
67
+ self.last_content = content
68
+ self.last_width = w
69
+ self.last_selector = selection&.type
70
+ self.last_coords = selection&.coords
71
+
72
+ @stream ||= WrapStream.new(w) do |string, extra|
73
+ [Hokusai.fonts.active.measure_char(string, size), size]
74
+ end
75
+
76
+ stream.origin_x = canvas.x + padding.left
77
+ stream.origin_y = canvas.y + padding.top
78
+ stream.offset_y = 0.0
79
+ stream.reset(w)
80
+
81
+ draw do
82
+ stream.on_text_selection(selection, nil) do |wrapped|
83
+ self.buffer << wrapped.buffer if copying
84
+ rect(wrapped.x, wrapped.y, wrapped.width, wrapped.height) do |command|
85
+ command.color = selection_color
86
+
87
+ last_commands << command
88
+ end
89
+ end
90
+
91
+ stream.on_text do |wrapped|
92
+ text(wrapped.text, wrapped.x, wrapped.y) do |command|
93
+ command.color = color
94
+ command.size = size
95
+ command.font = font
96
+
97
+ last_commands << command
98
+ end
99
+ end
100
+
101
+ stream.wrap(content, nil)
102
+ stream.flush
103
+ stream.y += size
104
+ end
105
+
106
+ self.last_height = stream.y - canvas.y + padding.top + padding.bottom
107
+ emit("height_updated", last_height)
108
+ node.meta.set_prop(:height, last_height)
109
+
110
+
111
+ # else
112
+ # draw do
113
+ # queue.concat last_commands
114
+ # end
115
+ # end
116
+
117
+ if copying
118
+ Hokusai.copy(buffer)
119
+ self.buffer = ""
120
+ self.copying = false
121
+ end
122
+
123
+ # node.portal&.meta&.set_prop(:height, last_height + padding.top + padding.bottom)
124
+
125
+ yield canvas
126
+ # end
127
+
128
+ Hokusai.fonts.activate lfont
129
+ end
130
+ end
@@ -0,0 +1,91 @@
1
+ class Hokusai::Blocks::TranslationBlock < Hokusai::Block
2
+ template <<~EOF
3
+ [template]
4
+ dynamic { @size_updated="set_size" }
5
+ slot
6
+ EOF
7
+
8
+ uses(dynamic: Hokusai::Blocks::Dynamic)
9
+
10
+ attr_accessor :content_width, :content_height
11
+
12
+ def set_size(width, height)
13
+ self.content_width = width
14
+ self.content_height = height
15
+ node.meta.set_prop(:width, width)
16
+ node.meta.set_prop(:height, height)
17
+ end
18
+
19
+ computed :duration, default: 500.0, convert: proc(&:to_f)
20
+ computed :from, default: :top, convert: proc(&:to_sym)
21
+
22
+ def on_mounted
23
+ @start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
24
+ end
25
+
26
+ def circular_in(t)
27
+ return 1.0 - Math.sqrt(1.0 - t * t);
28
+ end
29
+
30
+ def bounce_out(x)
31
+ n1 = 7.5625;
32
+ d1 = 2.75;
33
+ if (x < 1 / d1)
34
+ return n1 * x * x;
35
+ elsif (x < 2 / d1)
36
+ return n1 * (x -= 1.5 / d1) * x + 0.75;
37
+ elsif (x < 2.5 / d1)
38
+ return n1 * (x -= 2.25 / d1) * x + 0.9375;
39
+ else
40
+ return n1 * (x -= 2.625 / d1) * x + 0.984375;
41
+ end
42
+ end
43
+
44
+ def bounce_in(t)
45
+ return 1.0 - bounce_out(1.0 - t);
46
+ end
47
+
48
+ def ease(x)
49
+ return 1 - Math.cos((x * Math::PI) / 2);
50
+ end
51
+
52
+ def render(canvas)
53
+ @canvas ||= canvas
54
+ time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @start
55
+
56
+ if time > duration
57
+ yield canvas
58
+
59
+ return
60
+ else
61
+ case from
62
+ when :top
63
+ @startx ||= canvas.x
64
+ @starty ||= canvas.y - canvas.height
65
+ when :left
66
+ @startx ||= canvas.x - canvas.width
67
+ @starty ||= canvas.y
68
+ when :right
69
+ @startx ||= canvas.x + canvas.width
70
+ @starty ||= canvas.y
71
+ when :bottom
72
+ @startx ||= canvas.x
73
+ @starty ||= canvas.y + canvas.height
74
+ end
75
+
76
+ @targetx ||= canvas.x
77
+ @targety ||= canvas.y
78
+
79
+ progress = bounce_in(time.to_f / duration)
80
+
81
+ if progress >= 1
82
+ progress = 1.0
83
+ end
84
+
85
+ canvas.x = (@startx + (-@startx * progress)) + (@targetx * progress)
86
+ canvas.y = (@starty + (-@starty * progress)) + (@targety * progress)
87
+
88
+ yield canvas
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,21 @@
1
+ module Hokusai
2
+ class Commands::RotationBegin < Commands::Base
3
+ attr_reader :x, :y, :degrees
4
+
5
+ def initialize(x, y, deg)
6
+ @x = x
7
+ @y = y
8
+ @degrees = deg
9
+ end
10
+
11
+ def hash
12
+ [self.class, x, y, degrees].hash
13
+ end
14
+ end
15
+
16
+ class Commands::RotationEnd < Commands::Base;
17
+ def hash
18
+ [self.class].hash
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Hokusai
2
+ class Commands::ScaleBegin < Commands::Base
3
+ attr_reader :x, :y
4
+
5
+ def initialize(x, y = x)
6
+ @x = x
7
+ @y = y
8
+ end
9
+
10
+ def hash
11
+ [self.class, x, y].hash
12
+ end
13
+ end
14
+
15
+ class Commands::ScaleEnd < Commands::Base;
16
+ def hash
17
+ [self.class].hash
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Hokusai
2
+ class Commands::TranslationBegin < Commands::Base
3
+ attr_reader :x, :y
4
+
5
+ def initialize(x, y = x)
6
+ @x = x
7
+ @y = y
8
+ end
9
+
10
+ def hash
11
+ [self.class, x, y].hash
12
+ end
13
+ end
14
+
15
+ class Commands::TranslationEnd < Commands::Base;
16
+ def hash
17
+ [self.class].hash
18
+ end
19
+ end
20
+ end
@@ -6,6 +6,9 @@ require_relative "./commands/scissor"
6
6
  require_relative "./commands/text"
7
7
  require_relative "./commands/shader"
8
8
  require_relative "./commands/texture"
9
+ require_relative "./commands/rotation"
10
+ require_relative "./commands/scale"
11
+ require_relative "./commands/translation"
9
12
 
10
13
  module Hokusai
11
14
  # A proxy class for invoking various UI commands
@@ -33,7 +36,6 @@ module Hokusai
33
36
  yield(command)
34
37
 
35
38
  queue << command
36
- # command.draw
37
39
  end
38
40
 
39
41
  # Draw a circle
@@ -46,9 +48,7 @@ module Hokusai
46
48
 
47
49
  yield(command)
48
50
 
49
-
50
51
  queue << command
51
- # command.draw
52
52
  end
53
53
 
54
54
  # Draws an SVG
@@ -95,6 +95,30 @@ module Hokusai
95
95
  queue << Commands::ShaderEnd.new
96
96
  end
97
97
 
98
+ def rotation_begin(x, y, deg)
99
+ queue << Commands::RotationBegin.new(x, y, deg)
100
+ end
101
+
102
+ def rotation_end
103
+ queue << Commands::RotationEnd.new
104
+ end
105
+
106
+ def scale_begin(*args)
107
+ queue << Commands::ScaleBegin.new(*args)
108
+ end
109
+
110
+ def scale_end
111
+ queue << Commands::ScaleEnd.new
112
+ end
113
+
114
+ def translation_Begin(x, y)
115
+ queue << Commands::TranslationBegin.new
116
+ end
117
+
118
+ def translation_end
119
+ queue << Commands::TranslationEnd.new
120
+ end
121
+
98
122
  def texture(x, y, w, h)
99
123
  command = Commands::Texture.new(x, y, w, h)
100
124
 
@@ -107,16 +107,18 @@ module Hokusai
107
107
  def update(block)
108
108
  if target_block = target
109
109
  if updater_block = updater
110
- block.public_send(:before_updated) if block.respond_to?(:before_updated)
110
+ block.before_updated if block.respond_to?(:before_updated)
111
111
 
112
112
  updater_block.call(block, target_block, target_block)
113
- block.public_send(:after_updated) if block.respond_to?(:after_updated)
113
+ block.after_updated if block.respond_to?(:after_updated)
114
114
  end
115
115
  end
116
116
 
117
117
  children?&.each do |child|
118
118
  child.update
119
119
  end
120
+
121
+ block.after_children_updated if block.respond_to?(:after_children_updated)
120
122
  end
121
123
  def has_ast?(ast, index, elsy = false)
122
124
  if elsy
@@ -154,7 +154,7 @@ module Hokusai
154
154
  if patch.delete
155
155
  from = children[patch.from]
156
156
  children[patch.to] = from
157
- children[patch.from].public_send(:on_destroy) if children[patch.from].respond_to? :on_destroy
157
+ children[patch.from].public_send(:before_destroy) if children[patch.from].respond_to? :before_destroy
158
158
  children[patch.from].node.destroy
159
159
  children[patch.from] = nil
160
160
  else
@@ -189,7 +189,7 @@ module Hokusai
189
189
  if !condition && ast.has_else_condition?
190
190
  target_ast = ast.else_ast
191
191
  elsif !condition
192
- children[patch.target].public_send(:on_destroy) if children[patch.target].respond_to? :on_destroy
192
+ children[patch.target].public_send(:before_destroy) if children[patch.target].respond_to? :before_destroy
193
193
  children[patch.target].node.destroy
194
194
  children[patch.target] = nil
195
195
  next
@@ -210,7 +210,7 @@ module Hokusai
210
210
  children.insert(patch.target, child_block)
211
211
  end
212
212
  when DeletePatch
213
- children[patch.target].public_send(:on_destroy) if children[patch.target].respond_to? :on_destroy
213
+ children[patch.target].public_send(:before_destroy) if children[patch.target].respond_to? :before_destroy
214
214
  children[patch.target].node.destroy
215
215
  children[patch.target] = nil
216
216
  # TODO: update rest of block props
@@ -52,9 +52,7 @@ module Hokusai
52
52
  child_block = NodeMounter.new(node, child_block_klass, [stack], previous_providers: providers).mount(context: context, providers: providers)
53
53
 
54
54
  UpdateEntry.new(child_block, block, target).register(context: context, providers: providers)
55
- pp [meta.has_ast?(child, index, false), "shouldn't be here?"]
56
55
  meta.children!.insert(index, child_block)
57
- pp ["children", meta.children!.size, meta.children!.map(&:class)]
58
56
 
59
57
  child_block.public_send(:before_updated) if child_block.respond_to?(:before_updated)
60
58
  child_block.update
@@ -117,7 +117,7 @@ module Hokusai
117
117
 
118
118
  # Color = Struct.new(:red, :green, :blue, :alpha) do
119
119
  class Color
120
- attr_reader :red, :green, :blue, :alpha
120
+ attr_accessor :red, :green, :blue, :alpha
121
121
  def initialize(red, green, blue, alpha = 255)
122
122
  @red = red.freeze
123
123
  @green = green.freeze
@@ -144,6 +144,10 @@ module Hokusai
144
144
  new(value[0], value[1], value[2], value[3] || 255)
145
145
  end
146
146
 
147
+ def to_shader_value
148
+ [(r / 255.0), (g / 255.0), (b / 255.0), (a / 255.0)]
149
+ end
150
+
147
151
  def hash
148
152
  [self.class, r, g, b, a].hash
149
153
  end
@@ -1,7 +1,7 @@
1
1
  module Hokusai::Util
2
2
  class Selection
3
3
  attr_reader :raw
4
- attr_accessor :started
4
+ attr_accessor :started, :cleared
5
5
 
6
6
  def initialize
7
7
  ptr = FFI::MemoryPointer.new :pointer
@@ -9,6 +9,9 @@ module Hokusai::Util
9
9
  @raw = LibHokusai::HokuSelection.new(ptr.get_pointer(0))
10
10
  ptr.free
11
11
  @started = false
12
+ @direction = nil
13
+ @changed_direction = false
14
+ @cleared = false
12
15
  end
13
16
 
14
17
  def type
@@ -30,9 +33,14 @@ module Hokusai::Util
30
33
  raw[:stop_y] = 0.0
31
34
  raw[:cursor] = nil
32
35
 
36
+ self.cleared = true
33
37
  activate!
34
38
  end
35
39
 
40
+ def changed_direction?
41
+ @changed_direction
42
+ end
43
+
36
44
  def active?
37
45
  type == :active
38
46
  end
@@ -73,8 +81,17 @@ module Hokusai::Util
73
81
  end
74
82
 
75
83
  def stop(x, y)
84
+ self.cleared = false
76
85
  raw[:stop_x] = x
77
86
  raw[:stop_y] = y
87
+
88
+ if up? && @direction == :down || down? && @direction == :up
89
+ @changed_direction = true
90
+ else
91
+ @changed_direction = false
92
+ end
93
+
94
+ @direction = up? ? :up : :down
78
95
  end
79
96
 
80
97
  def start_x
@@ -93,12 +110,12 @@ module Hokusai::Util
93
110
  raw[:start_y]
94
111
  end
95
112
 
96
- def up?
97
- stop_y < start_y
113
+ def up?(height = 0)
114
+ stop_y < start_y - height
98
115
  end
99
116
 
100
- def down?
101
- start_y <= stop_y
117
+ def down?(height = 0)
118
+ start_y <= stop_y - height
102
119
  end
103
120
 
104
121
  def left?
@@ -126,7 +143,7 @@ module Hokusai::Util
126
143
  return nil if raw[:cursor].null?
127
144
 
128
145
  if frozen?
129
- return [raw[:cursor][:x], raw[:cursor][:y] - offset_y, raw[:cursor][:w], raw[:cursor][:h]]
146
+ return [raw[:cursor][:x], raw[:cursor][:y] + offset_y, raw[:cursor][:w], raw[:cursor][:h]]
130
147
  end
131
148
 
132
149
  [raw[:cursor][:x], raw[:cursor][:y], raw[:cursor][:w], raw[:cursor][:h]]
@@ -139,7 +156,11 @@ module Hokusai::Util
139
156
  def selected(x, y, width, height)
140
157
  return false if none?
141
158
 
142
- LibHokusai.hoku_selection_selected(raw, x, y, width, height)
159
+ if frozen?
160
+ return LibHokusai.hoku_selection_selected(raw, x, y + offset_y, width, height)
161
+ else
162
+ LibHokusai.hoku_selection_selected(raw, x, y, width, height)
163
+ end
143
164
  end
144
165
  end
145
166
  end
@@ -1,193 +1,268 @@
1
- module Hokusai::Util
2
- class Wrapped
3
- attr_accessor :text, :x, :y, :extra
4
-
5
- def initialize(text, x, y, extra)
6
- @text = text
7
- @x = x
8
- @y = y
9
- @extra = extra
10
- end
1
+ class Wrapped
2
+ attr_accessor :text, :x, :y, :extra
3
+
4
+ def initialize(text, x, y, extra)
5
+ @text = text
6
+ @x = x
7
+ @y = y
8
+ @extra = extra
11
9
  end
10
+ end
12
11
 
13
- class SelectionWrapper
14
- attr_accessor :x, :y, :width, :height
12
+ class SelectionWrapper
13
+ attr_accessor :x, :y, :width, :height, :offset, :buffer
15
14
 
16
- def initialize(x, y, w, h)
17
- @x = x
18
- @y = y
19
- @width = w
20
- @height = h
21
- end
15
+ def initialize(x, y, w, h)
16
+ @x = x
17
+ @y = y
18
+ @width = w
19
+ @height = h
20
+ @offset = 0.0
21
+ @buffer = ""
22
+ end
22
23
 
23
- def <<(w)
24
- @width += w
25
- end
24
+ def <<(w)
25
+ @width += w
26
26
  end
27
+ end
27
28
 
28
- class WrapStream
29
- attr_accessor :width, :current_width, :x, :y,
30
- :origin_x, :origin_y, :buffer, :stack
31
- attr_reader :on_text_cb, :on_text_selection_cb, :on_advancex_cb, :selector, :padding
29
+ class WrapStream
30
+ attr_accessor :width, :current_width, :x, :y, :offset_y,
31
+ :origin_x, :origin_y, :buffer, :stack, :widths, :current_height,
32
+ :current_offset, :srange, :last, :first
33
+ attr_reader :on_text_cb, :on_text_selection_cb, :on_advancex_cb, :selector, :padding
34
+
35
+ def initialize(width, &measure)
36
+ @width = width
37
+ @current_width = 0.0
38
+
39
+ @origin_x = 0.0
40
+ @origin_y = 0.0
41
+ @x = @origin_x
42
+ @y = @origin_y
43
+
44
+ @current_offset = 0
45
+ @current_height = 0.0
46
+ @widths = []
47
+ @stack = []
48
+ @srange = nil
49
+ @selected = ""
50
+ @buffer = ""
51
+ @last = nil
52
+ @first = nil
53
+
54
+ @measure_cb = measure
55
+ end
32
56
 
33
- def initialize(width, &measure)
34
- @width = width
35
- @current_width = 0.0
57
+ def reset(width)
58
+ @width = width
59
+ @current_width = 0.0
36
60
 
37
- @origin_x = 0.0
38
- @origin_y = 0.0
39
- @x = @origin_x
40
- @y = @origin_y
61
+ @x = @origin_x
62
+ @y = @origin_y
41
63
 
42
- @stack = []
43
- @buffer = ""
64
+ @current_offset = 0
65
+ @current_height = 0.0
66
+ @widths = []
67
+ @stack = []
68
+ @selected = ""
69
+ @buffer = ""
70
+
71
+ @first = nil
44
72
 
45
- @measure_cb = measure
46
- end
73
+ if @selector&.cleared
74
+ @selector&.cursor = nil
47
75
 
48
- def on_advancex(&block)
49
- @on_advancex_cb = block
76
+ @srange = nil
77
+ @last = nil
50
78
  end
79
+ end
51
80
 
52
- def on_text_selection(selector, padding, &block)
53
- @selector = selector
54
- @padding = padding || Hokusai::Padding.new(0.0, 0.0, 0.0, 0.0)
55
- @on_text_selection_cb = block
56
- end
81
+ def on_text_selection(selector, padding, &block)
82
+ @selector = selector
83
+ @padding = padding || Hokusai::Padding.new(0.0, 0.0, 0.0, 0.0)
84
+ @on_text_selection_cb = block
85
+ end
57
86
 
58
- def on_text(&block)
59
- @on_text_cb = block
60
- end
87
+ def on_bounds(&block)
88
+ @on_bounds_cb = block
89
+ end
61
90
 
62
- def measure(string, extra)
63
- @measure_cb.call(string, extra)
64
- end
91
+ def on_text(&block)
92
+ @on_text_cb = block
93
+ end
94
+
95
+ def measure(string, extra)
96
+ @measure_cb.call(string, extra)
97
+ end
98
+
99
+ def flush
100
+ sx = x
101
+ wrapper = nil
102
+ ii = 0
103
+ in_bounds = @on_bounds_cb.nil? ? true : @on_bounds_cb.call(y - offset_y)
104
+
105
+ stack.each do |(range, extra)|
106
+ size = buffer[range].size
107
+ if selector && in_bounds
108
+ i = 0
109
+ while i < size
110
+ nw = widths[i]
111
+ nh = current_height
112
+
113
+ # A char is selected
114
+ if selector.active? && selector.selected(sx, y, nw, nh)
115
+ # srange is for selecting downward.
116
+ # We cache the first char (our pivot point) and as we continue processing
117
+ # we can just append the next offset to the range
118
+ # self.srange ||= ((current_offset + i)..(current_offset + i))
119
+
120
+ # Selecting upward is more tricky.
121
+ # We need to cache the current offset as the first and last.
122
+ # After we process the whole text, we can nil the first offset
123
+ # And the next iteration the selection will pick up the new first offset
124
+ # The last offset will remain the origin (pivot)
125
+ # self.last ||= current_offset + i
126
+ self.first ||= current_offset + i
127
+ self.last = current_offset + i
128
+ self.srange = (first..last)
129
+ elsif selector.frozen? && selector.up? && selector.selected(sx, y, nw, nh)
130
+ self.first ||= current_offset + i
131
+ end
65
132
 
66
- def flush
67
- sx = x + padding.left
68
- wrapper = nil
69
- stack.each do |(range, extra)|
70
- str = buffer[range]
71
- if selector
72
- str.split("").each do |char|
73
- nw, nh = measure(char, extra)
74
- ox = on_advancex_cb.call(char.codepoints.first)
75
-
76
- if selector.selected(sx, y + padding.top - selector.offset_y, nw, nh)
77
- wrapper ||= SelectionWrapper.new(sx, y + padding.top, 0.0, nh)
78
- wrapper << ox
133
+ if srange&.include?(current_offset + i)
134
+ wrapper ||= begin
135
+ w = SelectionWrapper.new(sx, y, 0.0, nh)
136
+ w
79
137
  end
80
138
 
81
- sx += ox
139
+ if selector.up?(nh) && first == current_offset + i
140
+ selector.cursor ||= [sx, y - offset_y, 1.0, nh]
141
+ elsif selector.down? && last == current_offset + i
142
+ selector.cursor = [sx + nw, y - offset_y, 1.0, nh]
143
+ end
144
+
145
+ wrapper << nw
146
+ wrapper.buffer << (buffer[ii + i] || "")
82
147
  end
83
148
 
84
- on_text_selection_cb.call(wrapper) if wrapper
85
- wrapper = nil
149
+ i = i.succ
150
+ sx += nw
86
151
  end
87
152
 
88
- nw, _ = measure(str, extra)
153
+ on_text_selection_cb.call(wrapper) if wrapper
154
+ wrapper = nil
155
+ end
89
156
 
90
- # hard break on the buffer, split on character
91
- wrap_and_call(str, extra)
157
+ ii += size
158
+ self.current_offset += size
92
159
 
93
- self.x += nw
94
- end
95
-
96
- self.current_width = 0.0
97
- self.buffer = ""
98
- stack.clear
99
- self.x = origin_x
160
+ nw = widths[range].sum
161
+ # hard break on the buffer, split on character
162
+ wrap_and_call(buffer[range], extra)
163
+ self.x += nw
100
164
  end
101
165
 
102
- def wrap(text, extra)
103
- offset = 0
104
- size = text.size
166
+ self.current_width = 0.0
167
+ self.buffer = ""
168
+ stack.clear
169
+ widths.clear
170
+ self.x = origin_x
171
+ end
105
172
 
106
- stack << [((buffer.size)..(text.size + buffer.size - 1)), extra]
173
+ NEW_LINE_REGEX = /\n|\r\n/
107
174
 
108
- while offset < size
109
- char = text[offset]
110
- w, h = measure(char, extra)
175
+ def wrap(text, extra)
176
+ offset = 0
177
+ size = text.size
111
178
 
112
- # if it's a newline we want to break
113
- if char =~ /\n|\r\n/
114
- flush
179
+ stack << [((buffer.size)..(text.size + buffer.size - 1)), extra]
115
180
 
116
- stack << [(0...(text.size - offset - 1)), extra]
117
- self.y += h
118
- self.x = origin_x
119
- offset += 1
181
+ while offset < size
182
+ w, h = measure(text[offset], extra)
183
+ self.current_height = h
120
184
 
121
- next
122
- end
185
+ # if it's a newline we want to break
186
+ if text[offset] =~ NEW_LINE_REGEX
187
+ self.widths << 0
188
+ self.buffer << text[offset]
189
+ flush
123
190
 
124
- # if adding this char extends beyond the boundary
125
- if current_width + w > width
126
- # find the last space
127
- idx = buffer.rindex(" ")
128
-
129
- # if there is a break in this line split the buffer
130
- # and render the current line
131
- unless idx.nil? || idx < (buffer.size / 2)
132
- cur = []
133
- nex = []
134
- found = false
135
-
136
- # We need to split up both the buffer, and the ranges
137
- while payload = stack.shift
138
- range, xtra = payload
139
- if range.include?(idx)
140
- # putting the space on this line
141
- cur << [(range.begin..idx), xtra]
142
- # pp [range, idx]
143
- nex << [(0..(range.end - idx - 1)), xtra] unless idx == range.end
144
-
145
- found = true
146
- elsif !found
147
- cur << payload
148
- else
149
- nex << [((range.begin - idx - 1)..(range.end - idx - 1)), xtra]
150
- end
151
- end
191
+ stack << [(0...(text.size - offset - 1)), extra]
192
+ self.y += h
193
+ self.x = origin_x
194
+ offset += 1
152
195
 
153
- scur = buffer[0..idx]
154
- snex = buffer[(idx + 1)..-1]
196
+ next
197
+ end
155
198
 
156
- cur.each do |(range, xtra)|
157
- str = scur[range]
199
+ # if adding this char extends beyond the boundary
200
+ if current_width + w > width
201
+ # find the last space
202
+ idx = buffer.rindex(" ")
203
+
204
+ # if there is a break in this line split the buffer
205
+ # and render the current line
206
+ unless idx.nil? || idx < (buffer.size / 2)
207
+ cur = []
208
+ nex = []
209
+
210
+ found = false
211
+
212
+ # We need to split up both the buffer, and the ranges
213
+ while payload = stack.shift
214
+ range, xtra = payload
215
+ if range.include?(idx)
216
+ # putting the space on this line
217
+ cur << [(range.begin..idx), xtra]
218
+ nex << [(0..(range.end - idx - 1)), xtra] unless idx == range.end
219
+
220
+ found = true
221
+ elsif !found
222
+ cur << payload
223
+ else
224
+ nex << [((range.begin - idx - 1)..(range.end - idx - 1)), xtra]
225
+ end
226
+ end
158
227
 
159
- nw, _ = measure(str, xtra)
160
- wrap_and_call(str, xtra)
228
+ scur = buffer[0..idx]
229
+ snex = buffer[(idx + 1)..-1]
230
+
231
+ wcur = widths[0..idx]
232
+ wnex = widths[(idx + 1)..-1]
161
233
 
162
- self.x += nw
163
- end
234
+ self.buffer = scur
235
+ self.widths = wcur
236
+ self.stack = cur
237
+ flush
164
238
 
165
- self.buffer = snex + char
166
- self.stack = nex
167
- self.y += h
168
- self.current_width = measure(buffer, extra).first
169
- self.x = origin_x
170
- else
171
- # break on this word
172
- flush
173
-
174
- self.y += h
175
- self.current_width = measure(char, extra).first
176
- self.buffer = char
177
- stack << [(0...(text.size - offset)), extra]
178
- end
239
+ self.buffer = snex + text[offset]
240
+ self.widths = wnex.concat([w])
241
+ self.stack = nex
242
+ self.y += h
243
+ self.current_width = widths.sum
244
+ self.x = origin_x
179
245
  else
180
- self.current_width += w
181
- buffer << char
182
- end
246
+ # break on this word
247
+ flush
183
248
 
184
- offset += 1
249
+ self.y += h
250
+ self.current_width = w
251
+ self.buffer = text[offset]
252
+ self.widths = [w]
253
+ stack << [(0...(text.size - offset)), extra]
254
+ end
255
+ else
256
+ self.current_width += w
257
+ widths << w
258
+ buffer << text[offset]
185
259
  end
186
-
187
- end
188
260
 
189
- private def wrap_and_call(text, extra)
190
- on_text_cb.call Wrapped.new(text, x, y, extra)
261
+ offset += 1
191
262
  end
192
263
  end
264
+
265
+ private def wrap_and_call(text, extra)
266
+ on_text_cb.call Wrapped.new(text, x, y, extra)
267
+ end
193
268
  end
data/ui/src/hokusai.rb CHANGED
@@ -13,6 +13,7 @@ require_relative './hokusai/event'
13
13
  require_relative './hokusai/painter'
14
14
  require_relative './hokusai/util/selection'
15
15
  require_relative './hokusai/util/piece_table'
16
+ require_relative './hokusai/util/wrap_stream'
16
17
 
17
18
  # A backend agnostic library for authoring
18
19
  # desktop applications
@@ -137,6 +138,14 @@ module Hokusai
137
138
  @on_set_mouse_cursor&.call(type)
138
139
  end
139
140
 
141
+ def self.on_copy(&block)
142
+ @on_copy = block
143
+ end
144
+
145
+ def self.copy(text)
146
+ @on_copy&.call(text)
147
+ end
148
+
140
149
  # Mobile support
141
150
  def self.on_show_keyboard(&block)
142
151
  @on_show_keyboard = block
@@ -192,4 +201,6 @@ require_relative './hokusai/blocks/shader_begin'
192
201
  require_relative './hokusai/blocks/shader_end'
193
202
  require_relative './hokusai/blocks/texture'
194
203
  require_relative './hokusai/blocks/color_picker'
195
- require_relative './hokusai/blocks/keyboard'
204
+ require_relative './hokusai/blocks/keyboard'
205
+ require_relative './hokusai/blocks/translation'
206
+ require_relative './hokusai/blocks/text_stream'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hokusai-zero
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - skinnyjames
@@ -263,9 +263,11 @@ files:
263
263
  - ui/src/hokusai/blocks/slider.rb
264
264
  - ui/src/hokusai/blocks/svg.rb
265
265
  - ui/src/hokusai/blocks/text.rb
266
+ - ui/src/hokusai/blocks/text_stream.rb
266
267
  - ui/src/hokusai/blocks/texture.rb
267
268
  - ui/src/hokusai/blocks/titlebar/osx.rb
268
269
  - ui/src/hokusai/blocks/toggle.rb
270
+ - ui/src/hokusai/blocks/translation.rb
269
271
  - ui/src/hokusai/blocks/variable.rb
270
272
  - ui/src/hokusai/blocks/vblock.rb
271
273
  - ui/src/hokusai/commands.rb
@@ -273,10 +275,13 @@ files:
273
275
  - ui/src/hokusai/commands/circle.rb
274
276
  - ui/src/hokusai/commands/image.rb
275
277
  - ui/src/hokusai/commands/rect.rb
278
+ - ui/src/hokusai/commands/rotation.rb
279
+ - ui/src/hokusai/commands/scale.rb
276
280
  - ui/src/hokusai/commands/scissor.rb
277
281
  - ui/src/hokusai/commands/shader.rb
278
282
  - ui/src/hokusai/commands/text.rb
279
283
  - ui/src/hokusai/commands/texture.rb
284
+ - ui/src/hokusai/commands/translation.rb
280
285
  - ui/src/hokusai/diff.rb
281
286
  - ui/src/hokusai/error.rb
282
287
  - ui/src/hokusai/event.rb