zaniah 0.1.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.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +70 -0
  5. data/assets/fonts/Abel-Regular.ttf +0 -0
  6. data/assets/fonts/OFL.txt +93 -0
  7. data/docs/adr/000-template.md +24 -0
  8. data/docs/adr/001-native-gpu-backends.md +36 -0
  9. data/docs/adr/002-objective-c-aggregates.md +34 -0
  10. data/docs/adr/003-fiddle-call-boundary.md +33 -0
  11. data/docs/adr/README.md +10 -0
  12. data/docs/native.md +66 -0
  13. data/docs/process_pool.md +67 -0
  14. data/docs/text.md +90 -0
  15. data/docs/vector_and_list.md +52 -0
  16. data/examples/ffi_bench.rb +16 -0
  17. data/examples/linux_smoke.rb +53 -0
  18. data/examples/mac_event_check.rb +61 -0
  19. data/examples/mac_poll_bench.rb +24 -0
  20. data/examples/metal_state_check.rb +72 -0
  21. data/examples/native_bench.rb +18 -0
  22. data/examples/native_drop.rb +82 -0
  23. data/examples/native_font.rb +15 -0
  24. data/examples/native_ime.rb +50 -0
  25. data/examples/native_smoke.rb +75 -0
  26. data/examples/native_text_bench.rb +83 -0
  27. data/examples/native_watch.rb +25 -0
  28. data/examples/vulkan_triangle.rb +20 -0
  29. data/examples/word_count_worker.rb +8 -0
  30. data/lib/zaniah/anchored.rb +10 -0
  31. data/lib/zaniah/app.rb +130 -0
  32. data/lib/zaniah/bounds.rb +22 -0
  33. data/lib/zaniah/canvas.rb +15 -0
  34. data/lib/zaniah/color.rb +35 -0
  35. data/lib/zaniah/configuration.rb +5 -0
  36. data/lib/zaniah/corners.rb +7 -0
  37. data/lib/zaniah/data_compat.rb +25 -0
  38. data/lib/zaniah/device_pixels.rb +7 -0
  39. data/lib/zaniah/div.rb +5 -0
  40. data/lib/zaniah/edges.rb +7 -0
  41. data/lib/zaniah/element.rb +106 -0
  42. data/lib/zaniah/entity.rb +9 -0
  43. data/lib/zaniah/entity_context.rb +17 -0
  44. data/lib/zaniah/error.rb +5 -0
  45. data/lib/zaniah/ffi/com.rb +24 -0
  46. data/lib/zaniah/ffi/library.rb +29 -0
  47. data/lib/zaniah/ffi/objc.rb +86 -0
  48. data/lib/zaniah/ffi/struct/signature.rb +46 -0
  49. data/lib/zaniah/ffi/struct.rb +61 -0
  50. data/lib/zaniah/ffi/wayland.rb +140 -0
  51. data/lib/zaniah/frame_context.rb +14 -0
  52. data/lib/zaniah/geometry.rb +16 -0
  53. data/lib/zaniah/gpu/buffer.rb +23 -0
  54. data/lib/zaniah/gpu/frame_encoder.rb +43 -0
  55. data/lib/zaniah/gpu/instance_packing.rb +53 -0
  56. data/lib/zaniah/gpu/metal.rb +209 -0
  57. data/lib/zaniah/gpu/open_gl.rb +210 -0
  58. data/lib/zaniah/gpu/pipeline.rb +7 -0
  59. data/lib/zaniah/gpu/software.rb +126 -0
  60. data/lib/zaniah/gpu/texture.rb +33 -0
  61. data/lib/zaniah/gpu/vulkan.rb +233 -0
  62. data/lib/zaniah/gpu.rb +18 -0
  63. data/lib/zaniah/hsla.rb +14 -0
  64. data/lib/zaniah/image.rb +19 -0
  65. data/lib/zaniah/input/action_registry.rb +12 -0
  66. data/lib/zaniah/input/composition.rb +7 -0
  67. data/lib/zaniah/input/context_predicate.rb +87 -0
  68. data/lib/zaniah/input/dispatcher.rb +59 -0
  69. data/lib/zaniah/input/file_drop.rb +7 -0
  70. data/lib/zaniah/input/focus_handle.rb +24 -0
  71. data/lib/zaniah/input/key_down.rb +7 -0
  72. data/lib/zaniah/input/key_up.rb +7 -0
  73. data/lib/zaniah/input/keymap/binding.rb +9 -0
  74. data/lib/zaniah/input/keymap.rb +55 -0
  75. data/lib/zaniah/input/keystroke.rb +20 -0
  76. data/lib/zaniah/input/mouse_down.rb +7 -0
  77. data/lib/zaniah/input/mouse_move.rb +7 -0
  78. data/lib/zaniah/input/mouse_up.rb +7 -0
  79. data/lib/zaniah/input/scroll_wheel.rb +7 -0
  80. data/lib/zaniah/input/text_input.rb +7 -0
  81. data/lib/zaniah/input.rb +17 -0
  82. data/lib/zaniah/layout/available_space.rb +11 -0
  83. data/lib/zaniah/layout/engine.rb +214 -0
  84. data/lib/zaniah/layout/node.rb +36 -0
  85. data/lib/zaniah/layout/style.rb +19 -0
  86. data/lib/zaniah/layout/tree.rb +16 -0
  87. data/lib/zaniah/layout.rb +11 -0
  88. data/lib/zaniah/length.rb +14 -0
  89. data/lib/zaniah/length_units.rb +9 -0
  90. data/lib/zaniah/list/height_index.rb +68 -0
  91. data/lib/zaniah/list.rb +101 -0
  92. data/lib/zaniah/overlay.rb +10 -0
  93. data/lib/zaniah/platform/display.rb +7 -0
  94. data/lib/zaniah/platform/file_event.rb +7 -0
  95. data/lib/zaniah/platform/headless/window.rb +185 -0
  96. data/lib/zaniah/platform/linux/appearance_aware.rb +60 -0
  97. data/lib/zaniah/platform/linux/displays.rb +55 -0
  98. data/lib/zaniah/platform/linux/free_type.rb +80 -0
  99. data/lib/zaniah/platform/linux/watcher.rb +70 -0
  100. data/lib/zaniah/platform/linux/wayland_display_discovery.rb +60 -0
  101. data/lib/zaniah/platform/linux/wayland_window.rb +369 -0
  102. data/lib/zaniah/platform/linux/window.rb +418 -0
  103. data/lib/zaniah/platform/linux.rb +3 -0
  104. data/lib/zaniah/platform/mac/app.rb +67 -0
  105. data/lib/zaniah/platform/mac/core_text.rb +94 -0
  106. data/lib/zaniah/platform/mac/displays.rb +23 -0
  107. data/lib/zaniah/platform/mac/watcher.rb +63 -0
  108. data/lib/zaniah/platform/mac/window.rb +298 -0
  109. data/lib/zaniah/platform/mac.rb +19 -0
  110. data/lib/zaniah/platform/tui/input_decoder.rb +145 -0
  111. data/lib/zaniah/platform/tui/text_renderer.rb +40 -0
  112. data/lib/zaniah/platform/tui/window.rb +91 -0
  113. data/lib/zaniah/platform/windows/terminal.rb +127 -0
  114. data/lib/zaniah/platform/windows/watcher.rb +91 -0
  115. data/lib/zaniah/platform/windows/window.rb +407 -0
  116. data/lib/zaniah/platform/windows.rb +3 -0
  117. data/lib/zaniah/platform.rb +34 -0
  118. data/lib/zaniah/png.rb +118 -0
  119. data/lib/zaniah/point.rb +8 -0
  120. data/lib/zaniah/process_pool/worker.rb +7 -0
  121. data/lib/zaniah/process_pool.rb +239 -0
  122. data/lib/zaniah/process_wire.rb +127 -0
  123. data/lib/zaniah/quad_packer.rb +15 -0
  124. data/lib/zaniah/scene/sprite_batch.rb +7 -0
  125. data/lib/zaniah/scene.rb +167 -0
  126. data/lib/zaniah/scene_renderer.rb +8 -0
  127. data/lib/zaniah/size.rb +5 -0
  128. data/lib/zaniah/subscription.rb +14 -0
  129. data/lib/zaniah/svg/path.rb +134 -0
  130. data/lib/zaniah/svg/rasterizable.rb +186 -0
  131. data/lib/zaniah/svg.rb +295 -0
  132. data/lib/zaniah/task/cancelled.rb +7 -0
  133. data/lib/zaniah/task/timeout.rb +7 -0
  134. data/lib/zaniah/task.rb +51 -0
  135. data/lib/zaniah/task_executor.rb +105 -0
  136. data/lib/zaniah/text.rb +24 -0
  137. data/lib/zaniah/text_system/atlas/entry.rb +9 -0
  138. data/lib/zaniah/text_system/atlas.rb +102 -0
  139. data/lib/zaniah/text_system/atlas_cache.rb +146 -0
  140. data/lib/zaniah/text_system/font_db/face.rb +9 -0
  141. data/lib/zaniah/text_system/font_db.rb +193 -0
  142. data/lib/zaniah/text_system/glyph.rb +7 -0
  143. data/lib/zaniah/text_system/line_layout.rb +20 -0
  144. data/lib/zaniah/text_system/renderer.rb +196 -0
  145. data/lib/zaniah/text_system/shaper.rb +430 -0
  146. data/lib/zaniah/text_system/typesetter/copy_error.rb +9 -0
  147. data/lib/zaniah/text_system/typesetter.rb +168 -0
  148. data/lib/zaniah/text_system.rb +9 -0
  149. data/lib/zaniah/unicode.rb +13 -0
  150. data/lib/zaniah/uniform_list.rb +30 -0
  151. data/lib/zaniah/version.rb +5 -0
  152. data/lib/zaniah/view.rb +13 -0
  153. data/lib/zaniah.rb +29 -0
  154. data/sig/app.rbs +80 -0
  155. data/sig/elements.rbs +129 -0
  156. data/sig/gpu.rbs +108 -0
  157. data/sig/input.rbs +114 -0
  158. data/sig/layout.rbs +53 -0
  159. data/sig/native.rbs +88 -0
  160. data/sig/platform.rbs +94 -0
  161. data/sig/scene.rbs +52 -0
  162. data/sig/shaper.rbs +8 -0
  163. data/sig/text.rbs +137 -0
  164. data/sig/zaniah.rbs +120 -0
  165. data/tools/check_dependencies.rb +52 -0
  166. metadata +249 -0
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "font_db"
4
+ require_relative "glyph"
5
+ require_relative "line_layout"
6
+ require_relative "shaper"
7
+
8
+ module Zaniah
9
+ module TextSystem
10
+ # Layout-only state can belong to a background worker without GPU atlases or
11
+ # sharing the rendering thread's mutable caches. Providers are owner-local.
12
+ class Typesetter
13
+ attr_reader :font_db, :font, :shaper, :segmenter
14
+
15
+ def initialize(font: nil, font_db: Zaniah.configuration.font_db, capacity: 2000,
16
+ shaper: Zaniah.configuration.shaper, segmenter: Zaniah.configuration.segmenter)
17
+ require "alhena"
18
+ raise ArgumentError, "layout cache capacity must be positive" unless capacity.is_a?(Integer) && capacity.positive?
19
+ @font_db = provider(font_db, :font_db, %i[find fallback]) { FontDB.new }
20
+ @shaper = provider(shaper, :shaper, [:shape]) { Shaper.new }
21
+ @segmenter = provider(segmenter, :segmenter, [:grapheme_clusters]) { Unicode }
22
+ @font, @capacity = font || @font_db.find, capacity
23
+ @cache = {}
24
+ @layout_lookup, @layout_keys = [nil, nil, nil], {}
25
+ end
26
+
27
+ def layout_line(text, font: nil, size: 14)
28
+ raise ArgumentError, "text must be valid UTF-8" unless text.is_a?(String) && text.encoding == Encoding::UTF_8 && text.valid_encoding?
29
+ raise ArgumentError, "font size must be finite and positive" unless size.is_a?(Numeric) && size.finite? && size.positive?
30
+ font ||= @font
31
+ cached = cached_layout(text, font, size)
32
+ return cached if cached
33
+
34
+ text = text.dup.freeze
35
+ boundaries = grapheme_boundaries(text)
36
+ glyphs = shape_glyphs(glyphs_for(text, font, size), text, size)
37
+ scale = size.to_f / font.units_per_em
38
+ line = LineLayout.new(text, glyphs.freeze, glyphs.sum(&:advance), font.ascent * scale,
39
+ -font.descent * scale, size, carets_for(glyphs, boundaries).freeze)
40
+ cache_layout(line, text, font, size)
41
+ end
42
+
43
+ # Copy only layout state, never a Renderer's atlases/rasterizer. Custom
44
+ # providers explicitly deep-copy their state without mutating the source.
45
+ def fork(capacity: @capacity)
46
+ copies = []
47
+ options = {font: @font, font_db: @font_db, shaper: @shaper, segmenter: @segmenter}.to_h do |kind, value|
48
+ copy = layout_copy(kind, value)
49
+ copies << copy unless copy.equal?(value)
50
+ [kind, copy]
51
+ end
52
+ Typesetter.new(**options, capacity: capacity).tap { |copy| copy.instance_variable_set(:@owned_font, options[:font]) }
53
+ rescue StandardError
54
+ copies.reverse.uniq(&:object_id).each do |copy|
55
+ copy.close if copy.respond_to?(:close)
56
+ rescue StandardError
57
+ nil # Preserve the original copy/validation error.
58
+ end
59
+ raise
60
+ end
61
+
62
+ def close
63
+ return if @closed
64
+ @closed = true
65
+ @cache.clear
66
+ @layout_keys.clear
67
+ [@shaper, @segmenter, @font_db, @owned_font].uniq(&:object_id).each { |value| value.close if value.respond_to?(:close) }
68
+ end
69
+
70
+ private
71
+
72
+ def cached_layout(text, font, size)
73
+ @layout_lookup[0], @layout_lookup[1], @layout_lookup[2] = text, font, size
74
+ return unless (cached = @cache.delete(@layout_lookup))
75
+ @cache[@layout_keys.fetch(cached.object_id)] = cached
76
+ end
77
+
78
+ def grapheme_boundaries(text)
79
+ characters = @segmenter.grapheme_clusters(text)
80
+ raise Error, "segmenter must partition the UTF-8 text into nonempty strings" unless characters.is_a?(Array) && characters.all? { |part| part.is_a?(String) && !part.empty? && part.valid_encoding? } && characters.join == text
81
+ boundaries, byte = [0], 0
82
+ characters.each { |character| boundaries << (byte += character.bytesize) }
83
+ boundaries
84
+ end
85
+
86
+ def glyphs_for(text, font, size)
87
+ x, offset, glyphs = 0.0, 0, []
88
+ text.each_char do |character|
89
+ if (character.ord.between?(0xFE00, 0xFE0F) || character.ord.between?(0xE0100, 0xE01EF)) && !glyphs.empty?
90
+ previous = glyphs.pop
91
+ base = text.byteslice(previous.start, previous.finish - previous.start).each_char.first
92
+ id = previous.font.glyph_id(base, variation_selector: character.ord)
93
+ glyphs << Glyph.new(previous.font, id, previous.start, offset + character.bytesize, previous.x, previous.advance)
94
+ offset += character.bytesize
95
+ next
96
+ end
97
+ selected = @font_db.fallback(character.ord, font)
98
+ id = selected.glyph_id(character)
99
+ advance = selected.advance(id, size: size)
100
+ glyphs << Glyph.new(selected, id, offset, offset + character.bytesize, x, advance)
101
+ x += advance
102
+ offset += character.bytesize
103
+ end
104
+ glyphs
105
+ end
106
+
107
+ def shape_glyphs(glyphs, text, size)
108
+ glyphs = @shaper.shape(glyphs, size: size, text: text)
109
+ valid = glyphs.is_a?(Array) && glyphs.all? do |glyph|
110
+ glyph.is_a?(Glyph) && glyph.start.is_a?(Integer) && glyph.finish.is_a?(Integer) &&
111
+ glyph.start >= 0 && glyph.finish.between?(glyph.start + 1, text.bytesize) &&
112
+ glyph.x.is_a?(Numeric) && glyph.x.finite? && glyph.advance.is_a?(Numeric) && glyph.advance.finite?
113
+ end
114
+ raise Error, "shaper must return finite Glyph values with valid UTF-8 byte ranges" unless valid
115
+ glyphs
116
+ end
117
+
118
+ def carets_for(glyphs, boundaries)
119
+ carets, pen, boundary = [[0, 0.0]], 0.0, 1
120
+ glyphs.chunk { |glyph| [glyph.start, glyph.finish] }.each do |(first, last), cluster|
121
+ advance = cluster.sum(&:advance)
122
+ first_boundary = boundary
123
+ boundary += 1 while boundary < boundaries.length && boundaries[boundary] <= last
124
+ count = boundary - first_boundary
125
+ count.times do |index|
126
+ carets << [boundaries[first_boundary + index], pen + advance * (index + 1) / count]
127
+ end
128
+ pen += advance
129
+ end
130
+ carets
131
+ end
132
+
133
+ def cache_layout(line, text, font, size)
134
+ @layout_keys.delete(@cache.shift.last.object_id) if @cache.length >= @capacity
135
+ key = @layout_keys[line.object_id] = [text, font, size].freeze
136
+ @cache[key] = line
137
+ end
138
+
139
+ def layout_copy(kind, value)
140
+ native = value.singleton_methods.empty?
141
+ return Alhena::Font.new(value.data, index: value.index, axes: value.axis_values) if kind == :font && native && value.instance_of?(Alhena::Font)
142
+ return FontDB.new(paths: value.paths) if kind == :font_db && native && value.instance_of?(FontDB)
143
+ return Shaper.new if kind == :shaper && native && value.instance_of?(Shaper)
144
+ return Unicode if kind == :segmenter && value.equal?(Unicode)
145
+ raise CopyError, "#{kind} provider must implement #layout_copy for independent text layout" unless value.respond_to?(:layout_copy)
146
+ copy = value.layout_copy
147
+ methods = {font: %i[units_per_em ascent descent family glyph_id advance tables], font_db: %i[find fallback], shaper: [:shape], segmenter: [:grapheme_clusters]}.fetch(kind)
148
+ unless !copy.equal?(value) && methods.all? { |name| copy.respond_to?(name) }
149
+ copy.close if !copy.equal?(value) && copy.respond_to?(:close)
150
+ raise CopyError, "#{kind} #layout_copy must return an independent provider responding to #{methods.join(', ')}"
151
+ end
152
+ copy
153
+ rescue CopyError
154
+ raise
155
+ rescue StandardError => error
156
+ raise CopyError, "#{kind} layout copy failed: #{error.message}"
157
+ end
158
+
159
+ def provider(value, kind, methods)
160
+ return yield if value == :native
161
+ raise ArgumentError, "unsupported #{kind} provider #{value.inspect}; use :native or an object responding to #{methods.join(', ')}" unless methods.all? { |name| value.respond_to?(name) }
162
+ value
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ require_relative "typesetter/copy_error"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "text_system/glyph"
4
+ require_relative "text_system/line_layout"
5
+ require_relative "text_system/font_db"
6
+ require_relative "text_system/shaper"
7
+ require_relative "text_system/atlas"
8
+ require_relative "text_system/typesetter"
9
+ require_relative "text_system/renderer"
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zaniah
4
+ module Unicode
5
+ # Ruby's Unicode-aware regexp engine implements extended grapheme clusters.
6
+ def self.grapheme_clusters(text) = text.grapheme_clusters
7
+ def self.width(text, ambiguous: 1)
8
+ require "rbconfig"
9
+ require "unicode/display_width"
10
+ ::Unicode::DisplayWidth.of(text, ambiguous, emoji: :rgi)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zaniah
4
+ class UniformList < Element
5
+ attr_accessor :scroll_y
6
+ attr_reader :visible_range
7
+
8
+ def initialize(count:, row_height:, &render_item)
9
+ super()
10
+ raise ArgumentError, "invalid list dimensions" unless count >= 0 && row_height.positive?
11
+ @count, @row_height, @render_item, @scroll_y = count, row_height, render_item, 0
12
+ style(overflow: :hidden)
13
+ end
14
+
15
+ def request_layout(cx)
16
+ viewport = @style[:height]
17
+ viewport = viewport.resolve(cx.window.content_size.height) if viewport.is_a?(Length)
18
+ viewport = cx.window.content_size.height unless viewport.is_a?(Numeric)
19
+ @scroll_y = @scroll_y.clamp(0, [@count * @row_height - viewport, 0].max)
20
+ first = (@scroll_y / @row_height).floor
21
+ last = [first + (viewport / @row_height).ceil + 1, @count].min
22
+ @visible_range = first...last
23
+ @children = @visible_range.map do |index|
24
+ @render_item.call(index).style(position: :absolute, left: 0, right: 0,
25
+ top: index * @row_height - @scroll_y, height: @row_height)
26
+ end
27
+ super
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zaniah
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zaniah
4
+ class View
5
+ include LengthUnits
6
+
7
+ def div = Div.new
8
+ def text(value, **options) = Text.new(value, **options)
9
+ def list(**options, &render_item) = List.new(**options, &render_item)
10
+ def svg(source, **options) = SVG.new(source, **options)
11
+ def render(_cx) = raise NotImplementedError, "implement View#render"
12
+ end
13
+ end
data/lib/zaniah.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "zaniah/version"
4
+ require_relative "zaniah/data_compat"
5
+ require_relative "zaniah/error"
6
+ require_relative "zaniah/configuration"
7
+
8
+ module Zaniah
9
+ def self.configuration
10
+ @configuration ||= Configuration.new(font_raster: :native, shaper: :native,
11
+ font_db: :native, segmenter: :native)
12
+ end
13
+ def self.configure = yield(configuration)
14
+ end
15
+
16
+ require_relative "zaniah/geometry"
17
+ require_relative "zaniah/unicode"
18
+ require_relative "zaniah/scene"
19
+ require_relative "zaniah/png"
20
+ require_relative "zaniah/gpu"
21
+ require_relative "zaniah/layout"
22
+ require_relative "zaniah/app"
23
+ require_relative "zaniah/process_pool"
24
+ require_relative "zaniah/input"
25
+ require_relative "zaniah/element"
26
+ Zaniah.autoload(:SVG, File.expand_path("zaniah/svg", __dir__))
27
+ require_relative "zaniah/list"
28
+ require_relative "zaniah/platform"
29
+ require_relative "zaniah/text_system"
data/sig/app.rbs ADDED
@@ -0,0 +1,80 @@
1
+ module Zaniah
2
+ class Subscription
3
+ def initialize: () { () -> void } -> void
4
+ def detach: () -> void
5
+ def attached?: () -> bool
6
+ end
7
+
8
+ class Entity[T] < Data
9
+ attr_reader id: Integer
10
+ attr_reader generation: Integer
11
+ def self.new: [U] (Integer id, Integer generation) -> Entity[U]
12
+ | [U] (id: Integer, generation: Integer) -> Entity[U]
13
+ def read: (App app) -> T
14
+ def update: [U] (App app) { (T, EntityContext[T]) -> U } -> U
15
+ def release: (App app) -> void
16
+ end
17
+ class EntityContext[T]
18
+ attr_reader app: App
19
+ attr_reader entity: Entity[T]
20
+ def initialize: (App app, Entity[T] entity) -> void
21
+ def notify: () -> void
22
+ def emit: [E] (E event) -> void
23
+ def observe: [U] (Entity[U] target) { (T, U, EntityContext[T]) -> void } -> Subscription
24
+ def subscribe: [U, E] (Entity[U] target) { (T, U, E, EntityContext[T]) -> void } -> Subscription
25
+ def spawn: [U] () { (EntityContext[T]) -> U } -> Task[U]
26
+ def background: [U] () { () -> U } -> U
27
+ def update: () { (App) -> void } -> void
28
+ def theme: () -> untyped
29
+ end
30
+ class App
31
+ attr_reader windows: Array[Platform::Headless::Window]
32
+ attr_reader executor: TaskExecutor
33
+ def initialize: () -> void
34
+ def new_entity: [T] () { (EntityContext[T]) -> T } -> Entity[T]
35
+ def read: [T] (Entity[T] entity) -> T
36
+ def update: [T, U] (Entity[T] entity) { (T, EntityContext[T]) -> U } -> U
37
+ def release: [T] (Entity[T] entity) -> void
38
+ def alive?: [T] (Entity[T] entity) -> bool
39
+ def entity_count: () -> Integer
40
+ # Global values are application-owned heterogeneous state.
41
+ def global: (Symbol | String key) -> untyped
42
+ | [T] (Symbol | String key, T value) -> T
43
+ def listen: [T] (:notify | :emit kind, Entity[T] target, ?owner: Entity[untyped]?) { (*untyped) -> void } -> Subscription
44
+ def enqueue: (:notify | :emit kind, Entity[untyped] entity, ?untyped event) -> void
45
+ def flush_effects: () -> void
46
+ def open_window: (?backend: Platform::backend, ?width: Numeric, ?height: Numeric, ?title: String, ?scale_factor: Numeric, ?input: IO, ?output: IO, ?gpu: :metal | :opengl | :gl, ?display_server: :auto | :x11 | :wayland) ?{ (Platform::Headless::Window) -> (Element | Scene | nil) } -> Platform::Headless::Window
47
+ def run: () -> void
48
+ end
49
+
50
+ # Task is the framework's cancellable future, shared by both executor queues.
51
+ class Task[T]
52
+ class Cancelled < Error
53
+ end
54
+ class Timeout < Error
55
+ end
56
+ def initialize: () -> void
57
+ def resolve: (?T? value, ?error: Exception?) -> void
58
+ def await: (?timeout: Numeric?) -> T
59
+ def cancel: () -> void
60
+ def done?: () -> bool
61
+ def on_complete: () { () -> void } -> Subscription
62
+ end
63
+ class TaskExecutor
64
+ def self.current: () -> TaskExecutor?
65
+ def initialize: (?workers: Integer) -> void
66
+ def background: [T] () { () -> T } -> Task[T]
67
+ def spawn: [T] () { () -> T } -> Task[T]
68
+ def await: [T] (Task[T] task, ?timeout: Numeric?) -> T
69
+ def post: () { () -> void } -> void
70
+ def drain: () -> void
71
+ def wait: (Numeric seconds) -> void
72
+ def shutdown: () -> void
73
+ end
74
+ class ProcessPool[Input, Output]
75
+ def initialize: (?workers: Integer, handler: String, ?requires: Array[String], ?load_paths: Array[String], ?max_pending: Integer, ?max_bytes: Integer) -> void
76
+ | (?workers: Integer, ?max_pending: Integer, ?max_bytes: Integer) { (Input) -> Output } -> void
77
+ def submit: (Input data) -> Task[Output]
78
+ def shutdown: (?timeout: Numeric) -> void
79
+ end
80
+ end
data/sig/elements.rbs ADDED
@@ -0,0 +1,129 @@
1
+ module Zaniah
2
+ class FrameContext
3
+ attr_reader window: Platform::Headless::Window
4
+ attr_reader scene: Scene
5
+ attr_reader dispatcher: Input::Dispatcher
6
+ attr_reader text_system: TextSystem::_System?
7
+ def initialize: (Platform::Headless::Window window) -> void
8
+ def state: [T] (Object key) { () -> T } -> T
9
+ end
10
+
11
+ class Element
12
+ include LengthUnits
13
+ attr_reader layout_node: Layout::Node?
14
+ # Child constructors forward their render blocks through super(); ignored here.
15
+ def initialize: () ?{ (*untyped) -> void } -> void
16
+ def child: (Element? element) -> self
17
+ def children: () -> Array[Element]
18
+ | (Array[Element?] elements) -> self
19
+ def style: (**Layout::style_value properties) -> self
20
+ def key: (Object value) -> self
21
+ def with_state: [T] () { () -> T } -> self
22
+ def bg: (color color) -> self
23
+ def border_color: (color color) -> self
24
+ def rounded: (radius radius) -> self
25
+ def tooltip: (String text) -> self
26
+ def context_menu: (menu items) -> self
27
+ def flex: () -> self
28
+ def flex_row: () -> self
29
+ def flex_col: () -> self
30
+ def items_center: () -> self
31
+ def justify_center: () -> self
32
+ def w_full: () -> self
33
+ def h_full: () -> self
34
+ def flex_1: () -> self
35
+ def overflow_hidden: () -> self
36
+ def w: (dimension value) -> self
37
+ def h: (dimension value) -> self
38
+ def min_w: (dimension value) -> self
39
+ def min_h: (dimension value) -> self
40
+ def max_w: (dimension value) -> self
41
+ def max_h: (dimension value) -> self
42
+ def p: (Layout::style_value value) -> self
43
+ def m: (Layout::style_value value) -> self
44
+ def gap: (dimension value) -> self
45
+ def border: (Layout::style_value value) -> self
46
+ def border_b: (dimension value) -> self
47
+ def on_click: () { (Input::MouseDown, FrameContext) -> void } -> self
48
+ def on_hover: () { (Input::MouseMove, FrameContext) -> void } -> self
49
+ def on_drag: () { (Input::MouseMove, FrameContext) -> void } -> self
50
+ def on_scroll_wheel: () { (Input::ScrollWheel, FrameContext) -> void } -> self
51
+ def on_mouse_down: () { (Input::MouseDown, FrameContext) -> void } -> self
52
+ def on_mouse_up: () { (Input::MouseUp, FrameContext) -> void } -> self
53
+ def request_layout: (FrameContext context) -> Layout::Node
54
+ def prepaint: (Bounds bounds, Object? state, FrameContext context) -> void
55
+ def paint: (Bounds bounds, Object? state, Object? prepaint, FrameContext context) -> void
56
+ end
57
+ class Div < Element
58
+ end
59
+ class Text < Element
60
+ def initialize: (String text, ?size: Numeric, ?color: color, ?font: TextSystem::_Font?) -> void
61
+ def measured: () { (Numeric width, Numeric height) -> Layout::measurement } -> self
62
+ end
63
+ class Image < Element
64
+ def initialize: (String path) -> void
65
+ end
66
+ class Canvas < Element
67
+ def initialize: () { (Bounds, FrameContext) -> void } -> void
68
+ end
69
+ class Overlay < Div
70
+ end
71
+ class Anchored < Div
72
+ def initialize: (anchor: Point, ?offset: Point) -> void
73
+ end
74
+ class UniformList < Element
75
+ attr_accessor scroll_y: Numeric
76
+ attr_reader visible_range: Range[Integer]?
77
+ def initialize: (count: Integer, row_height: Numeric) { (Integer) -> Element } -> void
78
+ end
79
+
80
+ class SVG < Element
81
+ interface _Outline
82
+ def commands: () -> Array[Symbol]
83
+ def coordinates: () -> Array[Float]
84
+ def empty?: () -> bool
85
+ def bounds: () -> [Numeric, Numeric, Numeric, Numeric]
86
+ end
87
+ class Path
88
+ def self.parse: (String source) -> _Outline
89
+ def initialize: (String source) -> void
90
+ def parse: () -> _Outline
91
+ end
92
+ attr_reader width: Numeric
93
+ attr_reader height: Numeric
94
+ attr_reader view_box: Array[Numeric]
95
+ def self.parse: (String source, ?color: String) -> SVG
96
+ def self.open: (String path, ?color: String) -> SVG
97
+ def initialize: (String source, ?color: String) -> void
98
+ def texture: (?width: Integer, ?height: Integer, ?color: String) -> GPU::Texture
99
+ end
100
+
101
+ class List < Element
102
+ attr_reader scroll_y: Float
103
+ attr_reader visible_range: Range[Integer]
104
+ attr_reader heights: HeightIndex
105
+ def initialize: (count: Integer, ?estimated_height: Numeric, ?overscan: Integer) { (Integer) -> Element } -> void
106
+ def scroll_y=: (Numeric value) -> Numeric
107
+ def total_height: () -> Float
108
+ def update_height: (Integer index, Numeric height) -> self
109
+ def scroll_to: (Integer index, ?align: :start | :center | :end | :nearest) -> self
110
+ class HeightIndex
111
+ attr_reader count: Integer
112
+ def initialize: (Integer count, Numeric estimate) -> void
113
+ def []: (Integer index) -> Float
114
+ def update: (Integer index, Numeric height) -> Float
115
+ def prefix: (Integer index) -> Float
116
+ def total: () -> Float
117
+ def index_at: (Numeric y) -> Integer
118
+ end
119
+ end
120
+
121
+ class View
122
+ include LengthUnits
123
+ def div: () -> Div
124
+ def text: (String value, ?size: Numeric, ?color: color, ?font: TextSystem::_Font?) -> Text
125
+ def render: (FrameContext | EntityContext[untyped] context) -> Element
126
+ def svg: (String source, ?color: String) -> SVG
127
+ def list: (count: Integer, ?estimated_height: Numeric, ?overscan: Integer) { (Integer) -> Element } -> List
128
+ end
129
+ end
data/sig/gpu.rbs ADDED
@@ -0,0 +1,108 @@
1
+ module Zaniah
2
+ module GPU
3
+ type format = :r8 | :rgba8
4
+ type shader = :quad | :mono_sprite | :poly_sprite
5
+ interface _Device
6
+ def width: () -> Numeric
7
+ def height: () -> Numeric
8
+ def pixels: () -> String
9
+ def resize: (Numeric width, Numeric height) -> void
10
+ def create_texture: (Integer width, Integer height, ?format: format, ?data: String?) -> Texture
11
+ def create_buffer: (Integer size, ?usage: Symbol, ?mutable: bool) -> Buffer
12
+ def create_pipeline: (shader: shader, ?blend: Symbol) -> Pipeline
13
+ def begin_frame: (?clear: color) -> FrameEncoder
14
+ def render: (Scene scene, ?clear: color) -> void
15
+ def write_png: (String path) -> Integer
16
+ def release: () -> void
17
+ end
18
+ def self.create: (?Platform::Headless::Window? window, ?backend: :software | :metal | :opengl, ?width: Numeric?, ?height: Numeric?) -> _Device
19
+
20
+ class Buffer
21
+ attr_reader size: Integer
22
+ attr_reader usage: Symbol
23
+ attr_reader data: String
24
+ def initialize: (Integer size, ?usage: Symbol, ?mutable: bool) -> void
25
+ def write: (String bytes, ?offset: Integer) -> self
26
+ def release: () -> String
27
+ end
28
+ class Texture
29
+ attr_reader width: Integer
30
+ attr_reader height: Integer
31
+ attr_reader format: format
32
+ attr_reader data: String
33
+ attr_reader revision: Integer
34
+ def initialize: (Integer width, Integer height, ?format: format, ?data: String?) -> void
35
+ def upload: (Integer x, Integer y, Integer width, Integer height, String bytes) -> self
36
+ def release: () -> Integer
37
+ end
38
+ class Pipeline < Data
39
+ attr_reader shader: shader
40
+ attr_reader blend: Symbol
41
+ def self.new: (shader shader, Symbol blend) -> Pipeline
42
+ | (shader: shader, blend: Symbol) -> Pipeline
43
+ end
44
+ class FrameEncoder
45
+ def initialize: (_Device device, color clear) -> void
46
+ def set_pipeline: (Pipeline pipeline) -> Pipeline
47
+ def set_uniforms: (**Numeric | Array[Numeric] values) -> Hash[Symbol, Numeric | Array[Numeric]]
48
+ def set_texture: (Integer slot, Texture texture) -> Texture
49
+ def set_scissor: (Bounds? bounds) -> Bounds?
50
+ def draw_instanced: (vertex_count: Integer, instance_buffer: Buffer, instance_count: Integer) -> void
51
+ def present: () -> void
52
+ end
53
+ class Software
54
+ attr_reader width: Integer
55
+ attr_reader height: Integer
56
+ attr_reader pixels: String
57
+ def initialize: (Numeric width, Numeric height) -> void
58
+ def resize: (Numeric width, Numeric height) -> void
59
+ def create_texture: (Integer width, Integer height, ?format: format, ?data: String?) -> Texture
60
+ def create_buffer: (Integer size, ?usage: Symbol, ?mutable: bool) -> Buffer
61
+ def create_pipeline: (shader: shader, ?blend: Symbol) -> Pipeline
62
+ def begin_frame: (?clear: color) -> FrameEncoder
63
+ def render: (Scene scene, ?clear: color) -> String
64
+ def write_png: (String path) -> Integer
65
+ def release: () -> String
66
+ end
67
+ class Metal
68
+ attr_reader width: Numeric
69
+ attr_reader height: Numeric
70
+ attr_reader draw_calls: Integer
71
+ def initialize: (Platform::Mac::Window window) -> void
72
+ def resize: (Numeric width, Numeric height) -> void
73
+ def create_texture: (Integer width, Integer height, ?format: format, ?data: String?) -> Texture
74
+ def create_buffer: (Integer size, ?usage: Symbol, ?mutable: bool) -> Buffer
75
+ def create_pipeline: (shader: shader, ?blend: Symbol) -> Pipeline
76
+ def begin_frame: (?clear: color) -> FrameEncoder
77
+ def render: (Scene scene, ?clear: color) -> void
78
+ def pixels: () -> String
79
+ def write_png: (String path) -> Integer
80
+ def release: () -> void
81
+ end
82
+ class OpenGL
83
+ attr_reader width: Numeric
84
+ attr_reader height: Numeric
85
+ attr_reader draw_calls: Integer
86
+ def resize: (Numeric width, Numeric height) -> void
87
+ def create_texture: (Integer width, Integer height, ?format: format, ?data: String?) -> Texture
88
+ def create_buffer: (Integer size, ?usage: Symbol, ?mutable: bool) -> Buffer
89
+ def create_pipeline: (shader: shader, ?blend: Symbol) -> Pipeline
90
+ def begin_frame: (?clear: color) -> FrameEncoder
91
+ def render: (Scene scene, ?clear: color, ?capture: bool) -> void
92
+ def pixels: () -> String
93
+ def write_png: (String path) -> Integer
94
+ def release: () -> void
95
+ end
96
+ # Optional offscreen triangle probe, not a full window-renderer interface.
97
+ class Vulkan
98
+ attr_reader width: Integer
99
+ attr_reader height: Integer
100
+ attr_reader pixels: String
101
+ attr_reader device_name: String
102
+ def initialize: (?width: Integer, ?height: Integer) -> void
103
+ def render_triangle: (vertex_spirv: String, fragment_spirv: String) -> void
104
+ def write_png: (String path) -> Integer
105
+ def release: () -> void
106
+ end
107
+ end
108
+ end