rbgl 0.1.0 → 1.0.1
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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +20 -2
- data/README.md +130 -74
- data/Rakefile +7 -0
- data/bench/renderer_bench.rb +283 -0
- data/docs/.nojekyll +1 -0
- data/docs/index.html +38 -0
- data/examples/array_test.rb +1 -1
- data/examples/color_test.rb +1 -1
- data/examples/cube_spinning.rb +1 -1
- data/examples/gradient.rb +1 -1
- data/examples/multi_return_test.rb +1 -1
- data/examples/plasma.rb +1 -1
- data/examples/sphere_raymarch.rb +1 -1
- data/examples/triangle_window.rb +1 -1
- data/exe/rbgl +42 -0
- data/lib/rbgl/cli/doctor.rb +155 -0
- data/lib/rbgl/engine/buffer.rb +150 -40
- data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
- data/lib/rbgl/engine/context.rb +133 -63
- data/lib/rbgl/engine/framebuffer.rb +153 -48
- data/lib/rbgl/engine/immutable_color.rb +32 -0
- data/lib/rbgl/engine/pipeline.rb +38 -7
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
- data/lib/rbgl/engine/rasterizer.rb +65 -171
- data/lib/rbgl/engine/shader/base_shader.rb +51 -0
- data/lib/rbgl/engine/shader/builtins.rb +257 -0
- data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
- data/lib/rbgl/engine/shader.rb +5 -318
- data/lib/rbgl/engine/texture.rb +264 -51
- data/lib/rbgl/engine.rb +4 -1
- data/lib/rbgl/gui/backend.rb +21 -34
- data/lib/rbgl/gui/backend_factory.rb +89 -0
- data/lib/rbgl/gui/cocoa/backend.rb +59 -46
- data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
- data/lib/rbgl/gui/event.rb +4 -2
- data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
- data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
- data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
- data/lib/rbgl/gui/file_backend.rb +50 -55
- data/lib/rbgl/gui/wayland/backend.rb +214 -60
- data/lib/rbgl/gui/wayland/codec.rb +50 -0
- data/lib/rbgl/gui/wayland/connection.rb +188 -231
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
- data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
- data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
- data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
- data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
- data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
- data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
- data/lib/rbgl/gui/window/render_loop.rb +111 -0
- data/lib/rbgl/gui/window.rb +74 -84
- data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
- data/lib/rbgl/gui/x11/backend.rb +73 -49
- data/lib/rbgl/gui/x11/connection.rb +284 -219
- data/lib/rbgl/gui/x11/event_parser.rb +71 -0
- data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
- data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
- data/lib/rbgl/gui/x11/request_encoder.rb +155 -0
- data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
- data/lib/rbgl/gui/x11/transport.rb +81 -0
- data/lib/rbgl/gui/x11/x_authority.rb +107 -0
- data/lib/rbgl/gui.rb +3 -5
- data/lib/rbgl/version.rb +1 -1
- metadata +63 -24
- data/examples/chemical_heartbeat.rb +0 -166
- data/examples/dark_transit.rb +0 -166
- data/examples/fractured_orb.rb +0 -428
- data/examples/fractured_orb_rb.rb +0 -598
- data/examples/hexagonal_flow.rb +0 -333
- data/examples/teapot.rb +0 -362
- data/examples/teapot_mcu.rb +0 -344
data/lib/rbgl/engine.rb
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "version"
|
|
4
|
+
require_relative "engine/immutable_color"
|
|
3
5
|
require_relative "engine/framebuffer"
|
|
4
6
|
require_relative "engine/buffer"
|
|
5
7
|
require_relative "engine/shader"
|
|
6
8
|
require_relative "engine/texture"
|
|
7
9
|
require_relative "engine/rasterizer"
|
|
8
10
|
require_relative "engine/pipeline"
|
|
11
|
+
require_relative "engine/clip_space_clipper"
|
|
9
12
|
require_relative "engine/context"
|
|
10
13
|
|
|
11
14
|
module RBGL
|
|
12
15
|
module Engine
|
|
13
|
-
VERSION =
|
|
16
|
+
VERSION = RBGL::VERSION
|
|
14
17
|
end
|
|
15
18
|
end
|
data/lib/rbgl/gui/backend.rb
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module RBGL
|
|
4
4
|
module GUI
|
|
5
|
+
class BackendUnavailable < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class BackendSelectionError < ArgumentError
|
|
9
|
+
end
|
|
10
|
+
|
|
5
11
|
class Backend
|
|
6
12
|
attr_reader :width, :height, :title
|
|
7
13
|
|
|
8
|
-
def initialize(width, height, title = "RBGL")
|
|
14
|
+
def initialize(width, height, title = "RBGL", **_options)
|
|
9
15
|
@width = width
|
|
10
16
|
@height = height
|
|
11
17
|
@title = title
|
|
12
|
-
@key_callback = nil
|
|
13
|
-
@mouse_callback = nil
|
|
14
|
-
@resize_callback = nil
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def present(_framebuffer)
|
|
@@ -23,7 +26,12 @@ module RBGL
|
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def poll_events_raw
|
|
26
|
-
|
|
29
|
+
poll_events.filter_map { |event| event.to_h if event.respond_to?(:to_h) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def resize(width, height)
|
|
33
|
+
@width = width
|
|
34
|
+
@height = height
|
|
27
35
|
end
|
|
28
36
|
|
|
29
37
|
def should_close?
|
|
@@ -35,41 +43,20 @@ module RBGL
|
|
|
35
43
|
end
|
|
36
44
|
|
|
37
45
|
def set_pixels(buffer, width, height)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def metal_available?
|
|
42
|
-
false
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def native_handle
|
|
46
|
-
nil
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def on_key(&block)
|
|
50
|
-
@key_callback = block
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def on_mouse(&block)
|
|
54
|
-
@mouse_callback = block
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def on_resize(&block)
|
|
58
|
-
@resize_callback = block
|
|
46
|
+
bytes = validate_rgba_buffer(buffer, width, height)
|
|
47
|
+
present(Engine::Framebuffer.from_rgba_bytes(width, height, bytes))
|
|
59
48
|
end
|
|
60
49
|
|
|
61
50
|
protected
|
|
62
51
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
def validate_rgba_buffer(buffer, width, height)
|
|
53
|
+
bytes = String.try_convert(buffer)
|
|
54
|
+
raise ArgumentError, "Pixel buffer must be a String" unless bytes
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
end
|
|
56
|
+
expected_size = width * height * 4
|
|
57
|
+
return bytes if bytes.bytesize == expected_size
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
@resize_callback&.call(width, height)
|
|
59
|
+
raise ArgumentError, "Pixel buffer size mismatch: expected #{expected_size}, got #{bytes.bytesize}"
|
|
73
60
|
end
|
|
74
61
|
end
|
|
75
62
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class BackendFactory
|
|
6
|
+
AUTO_BACKEND_ERRORS = [LoadError, SystemCallError, BackendUnavailable].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def build(backend, width:, height:, title:, platform: RUBY_PLATFORM, env: ENV, **options)
|
|
10
|
+
case backend
|
|
11
|
+
when :auto
|
|
12
|
+
build_auto_backend(
|
|
13
|
+
width: width,
|
|
14
|
+
height: height,
|
|
15
|
+
title: title,
|
|
16
|
+
platform: platform,
|
|
17
|
+
env: env,
|
|
18
|
+
**options
|
|
19
|
+
)
|
|
20
|
+
else
|
|
21
|
+
build_specific_backend(backend, width: width, height: height, title: title, env: env, **options)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def build_auto_backend(width:, height:, title:, platform:, env:, builder: nil, **options)
|
|
28
|
+
builder ||= method(:build_specific_backend)
|
|
29
|
+
errors = []
|
|
30
|
+
|
|
31
|
+
native_backend_candidates(platform: platform, env: env).each do |candidate|
|
|
32
|
+
return builder.call(candidate, width: width, height: height, title: title, env: env, **options)
|
|
33
|
+
rescue *AUTO_BACKEND_ERRORS => error
|
|
34
|
+
errors << [candidate, error]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
raise_auto_backend_error(errors)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_specific_backend(backend, width:, height:, title:, env: ENV, **options)
|
|
41
|
+
case backend
|
|
42
|
+
when :file
|
|
43
|
+
FileBackend.new(width, height, title, **options)
|
|
44
|
+
when :x11
|
|
45
|
+
require_relative "x11/backend"
|
|
46
|
+
X11::Backend.new(width, height, title, env: env, **options)
|
|
47
|
+
when :wayland
|
|
48
|
+
require_relative "wayland/backend"
|
|
49
|
+
Wayland::Backend.new(width, height, title, env: env, **options)
|
|
50
|
+
when :cocoa
|
|
51
|
+
require_relative "cocoa/backend"
|
|
52
|
+
Cocoa::Backend.new(width, height, title, env: env, **options)
|
|
53
|
+
when Backend
|
|
54
|
+
unless options.empty?
|
|
55
|
+
raise ArgumentError, "Backend instances do not accept construction options: #{options.keys.join(', ')}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
backend
|
|
59
|
+
else
|
|
60
|
+
raise BackendSelectionError, "Unknown backend: #{backend}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def raise_auto_backend_error(errors)
|
|
65
|
+
return if errors.empty?
|
|
66
|
+
|
|
67
|
+
details = errors.map { |backend, error| "#{backend}: #{error.class}: #{error.message}" }.join(", ")
|
|
68
|
+
raise BackendUnavailable, "Failed to initialize any native backend (#{details})"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def native_backend_candidates(platform:, env:)
|
|
72
|
+
case platform
|
|
73
|
+
when /darwin/
|
|
74
|
+
[:cocoa]
|
|
75
|
+
when /linux/
|
|
76
|
+
candidates = []
|
|
77
|
+
candidates << :wayland if env["WAYLAND_DISPLAY"]
|
|
78
|
+
candidates << :x11 if env["DISPLAY"]
|
|
79
|
+
return candidates unless candidates.empty?
|
|
80
|
+
|
|
81
|
+
raise BackendUnavailable, "No display server found (DISPLAY or WAYLAND_DISPLAY not set)"
|
|
82
|
+
else
|
|
83
|
+
raise BackendUnavailable, "Unsupported platform: #{platform}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -1,58 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "metaco"
|
|
5
|
-
METACO_AVAILABLE = true
|
|
6
|
-
rescue LoadError
|
|
7
|
-
METACO_AVAILABLE = false
|
|
8
|
-
end
|
|
3
|
+
require_relative "key_mapper"
|
|
9
4
|
|
|
10
5
|
module RBGL
|
|
11
6
|
module GUI
|
|
12
7
|
module Cocoa
|
|
8
|
+
METACO_AVAILABLE = begin
|
|
9
|
+
require "metaco"
|
|
10
|
+
true
|
|
11
|
+
rescue LoadError
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
13
15
|
class Backend < GUI::Backend
|
|
14
|
-
def initialize(width, height, title = "RBGL")
|
|
16
|
+
def initialize(width, height, title = "RBGL", env: ENV, resizable: false, high_dpi: false, **_options)
|
|
17
|
+
@env = env
|
|
15
18
|
unless METACO_AVAILABLE
|
|
16
19
|
raise LoadError, "metaco gem is required for Cocoa backend. Install it with: gem install metaco"
|
|
17
20
|
end
|
|
18
21
|
|
|
19
|
-
super
|
|
22
|
+
super(width, height, title)
|
|
20
23
|
Metaco.init
|
|
21
|
-
@handle = Metaco.window_create(width, height, title)
|
|
24
|
+
@handle = Metaco.window_create(width, height, title, resizable: resizable, high_dpi: high_dpi)
|
|
25
|
+
@framebuffer_width, @framebuffer_height = Metaco.framebuffer_size(@handle)
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def present(framebuffer)
|
|
25
|
-
return unless @handle
|
|
29
|
+
return false unless @handle
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
bytes = framebuffer.to_rgba_bytes
|
|
32
|
+
width = @framebuffer_width
|
|
33
|
+
height = @framebuffer_height
|
|
34
|
+
bytes = scale_pixels(bytes, framebuffer.width, framebuffer.height, width, height) if [width, height] != [framebuffer.width, framebuffer.height]
|
|
35
|
+
Metaco.set_pixels(@handle, bytes, width, height)
|
|
28
36
|
Metaco.present(@handle)
|
|
37
|
+
true
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
def poll_events
|
|
32
41
|
return [] unless @handle
|
|
33
42
|
|
|
34
43
|
raw_events = Metaco.poll_events(@handle)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
raw_events.each do |e|
|
|
38
|
-
event = convert_event(e)
|
|
39
|
-
if event
|
|
40
|
-
events << event
|
|
41
|
-
emit_from_event(event)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
events
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def poll_events_raw
|
|
49
|
-
return [] unless @handle
|
|
50
|
-
|
|
51
|
-
Metaco.poll_events(@handle)
|
|
44
|
+
raw_events.filter_map { |event| convert_event(event) }
|
|
52
45
|
end
|
|
53
46
|
|
|
54
47
|
def should_close?
|
|
55
|
-
return
|
|
48
|
+
return true unless @handle
|
|
56
49
|
|
|
57
50
|
Metaco.should_close?(@handle)
|
|
58
51
|
end
|
|
@@ -67,10 +60,17 @@ module RBGL
|
|
|
67
60
|
def set_pixels(buffer, width, height)
|
|
68
61
|
return unless @handle
|
|
69
62
|
|
|
70
|
-
|
|
63
|
+
bytes = validate_rgba_buffer(buffer, width, height)
|
|
64
|
+
bytes = scale_pixels(bytes, width, height, @framebuffer_width, @framebuffer_height) if [width, height] != [@framebuffer_width, @framebuffer_height]
|
|
65
|
+
Metaco.set_pixels(@handle, bytes, @framebuffer_width, @framebuffer_height)
|
|
71
66
|
Metaco.present(@handle)
|
|
72
67
|
end
|
|
73
68
|
|
|
69
|
+
def resize(width, height)
|
|
70
|
+
super
|
|
71
|
+
@framebuffer_width, @framebuffer_height = Metaco.framebuffer_size(@handle)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
74
|
def metal_available?
|
|
75
75
|
return false unless @handle
|
|
76
76
|
|
|
@@ -88,32 +88,45 @@ module RBGL
|
|
|
88
88
|
|
|
89
89
|
case type
|
|
90
90
|
when :key_press
|
|
91
|
-
Event.new(:key_press, key: raw[:key], char: raw[:char])
|
|
91
|
+
Event.new(:key_press, key: normalize_key(raw[:key], raw[:char]), keycode: raw[:key], char: raw[:char], modifiers: raw[:modifiers] || [])
|
|
92
92
|
when :key_release
|
|
93
|
-
Event.new(:key_release, key: raw[:key])
|
|
93
|
+
Event.new(:key_release, key: normalize_key(raw[:key]), keycode: raw[:key], modifiers: raw[:modifiers] || [])
|
|
94
94
|
when :mouse_press
|
|
95
|
-
Event.new(:mouse_press, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
95
|
+
Event.new(:mouse_press, x: raw[:x], y: mouse_y(raw[:y]), button: raw[:button], modifiers: raw[:modifiers] || [])
|
|
96
96
|
when :mouse_release
|
|
97
|
-
Event.new(:mouse_release, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
97
|
+
Event.new(:mouse_release, x: raw[:x], y: mouse_y(raw[:y]), button: raw[:button], modifiers: raw[:modifiers] || [])
|
|
98
98
|
when :mouse_move
|
|
99
|
-
Event.new(:mouse_move, x: raw[:x], y: raw[:y])
|
|
99
|
+
Event.new(:mouse_move, x: raw[:x], y: mouse_y(raw[:y]), modifiers: raw[:modifiers] || [])
|
|
100
|
+
when :scroll
|
|
101
|
+
Event.new(:scroll, dx: raw[:dx], dy: raw[:dy], modifiers: raw[:modifiers] || [])
|
|
102
|
+
when :focus, :blur
|
|
103
|
+
Event.new(type)
|
|
104
|
+
when :resize
|
|
105
|
+
Event.new(:resize, width: raw[:width], height: raw[:height],
|
|
106
|
+
framebuffer_width: raw[:framebuffer_width], framebuffer_height: raw[:framebuffer_height])
|
|
100
107
|
else
|
|
101
108
|
nil
|
|
102
109
|
end
|
|
103
110
|
end
|
|
104
111
|
|
|
105
|
-
def
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
def normalize_key(key, char = nil)
|
|
113
|
+
KeyMapper.key(key, char)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def mouse_y(y)
|
|
117
|
+
@height ? @height - 1 - y : y
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def scale_pixels(bytes, source_width, source_height, target_width, target_height)
|
|
121
|
+
scaled = String.new(capacity: target_width * target_height * 4, encoding: Encoding::BINARY)
|
|
122
|
+
target_height.times do |y|
|
|
123
|
+
source_y = y * source_height / target_height
|
|
124
|
+
target_width.times do |x|
|
|
125
|
+
source_x = x * source_width / target_width
|
|
126
|
+
scaled << bytes.byteslice(((source_y * source_width) + source_x) * 4, 4)
|
|
127
|
+
end
|
|
116
128
|
end
|
|
129
|
+
scaled
|
|
117
130
|
end
|
|
118
131
|
end
|
|
119
132
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
module Cocoa
|
|
6
|
+
module KeyMapper
|
|
7
|
+
KEY_CODES = {
|
|
8
|
+
0 => :a, 1 => :s, 2 => :d, 3 => :f, 4 => :h, 5 => :g, 6 => :z, 7 => :x,
|
|
9
|
+
8 => :c, 9 => :v, 11 => :b, 12 => :q, 13 => :w, 14 => :e, 15 => :r,
|
|
10
|
+
16 => :y, 17 => :t, 18 => :one, 19 => :two, 20 => :three, 21 => :four,
|
|
11
|
+
22 => :six, 23 => :five, 24 => :equal, 25 => :nine, 26 => :seven,
|
|
12
|
+
27 => :minus, 28 => :eight, 29 => :zero, 30 => :right_bracket, 31 => :o,
|
|
13
|
+
32 => :u, 33 => :left_bracket, 34 => :i, 35 => :p, 36 => :enter, 37 => :l,
|
|
14
|
+
38 => :j, 39 => :quote, 40 => :k, 41 => :semicolon, 42 => :backslash,
|
|
15
|
+
43 => :comma, 44 => :slash, 45 => :n, 46 => :m, 47 => :period, 48 => :tab,
|
|
16
|
+
49 => :space, 50 => :grave, 51 => :backspace, 53 => :escape, 54 => :right_meta,
|
|
17
|
+
55 => :left_meta, 56 => :left_shift, 57 => :caps_lock, 58 => :left_alt,
|
|
18
|
+
59 => :left_control, 60 => :right_shift, 61 => :right_alt, 62 => :right_control,
|
|
19
|
+
63 => :function, 64 => :f17, 65 => :keypad_decimal, 67 => :keypad_multiply,
|
|
20
|
+
69 => :keypad_plus, 71 => :keypad_clear, 72 => :volume_up, 73 => :volume_down,
|
|
21
|
+
74 => :mute, 75 => :keypad_divide, 76 => :keypad_enter, 78 => :keypad_minus,
|
|
22
|
+
79 => :f18, 80 => :f19, 81 => :keypad_equal, 82 => :keypad_zero,
|
|
23
|
+
83 => :keypad_one, 84 => :keypad_two, 85 => :keypad_three, 86 => :keypad_four,
|
|
24
|
+
87 => :keypad_five, 88 => :keypad_six, 89 => :keypad_seven,
|
|
25
|
+
91 => :keypad_eight, 92 => :keypad_nine, 96 => :f5, 97 => :f6, 98 => :f7,
|
|
26
|
+
99 => :f3, 100 => :f8, 101 => :f9, 103 => :f11, 105 => :f13,
|
|
27
|
+
106 => :f16, 107 => :f14, 109 => :f10, 111 => :f12, 113 => :f15,
|
|
28
|
+
114 => :help, 115 => :home, 116 => :page_up, 117 => :delete,
|
|
29
|
+
118 => :f4, 119 => :end, 120 => :f2, 121 => :page_down, 122 => :f1,
|
|
30
|
+
123 => :left, 124 => :right, 125 => :down, 126 => :up
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
def key(code, char = nil)
|
|
36
|
+
return code if code.is_a?(Symbol)
|
|
37
|
+
return KEY_CODES.fetch(code, code) if code.is_a?(Integer)
|
|
38
|
+
|
|
39
|
+
text = char.to_s.empty? ? code.to_s : char.to_s
|
|
40
|
+
return :escape if text == "\e" || text.casecmp?("escape")
|
|
41
|
+
return text.downcase.to_sym if text.length == 1
|
|
42
|
+
|
|
43
|
+
code
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/rbgl/gui/event.rb
CHANGED
|
@@ -14,8 +14,10 @@ module RBGL
|
|
|
14
14
|
@data[key]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def method_missing(name, *args)
|
|
18
|
-
@data.
|
|
17
|
+
def method_missing(name, *args, &block)
|
|
18
|
+
return @data[name] if args.empty? && block.nil? && @data.key?(name)
|
|
19
|
+
|
|
20
|
+
super
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def respond_to_missing?(name, include_private = false)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
class BmpWriter < FrameWriter
|
|
7
|
+
def extension
|
|
8
|
+
"bmp"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def write(filename, framebuffer)
|
|
12
|
+
File.binwrite(filename, encode(framebuffer))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def encode(framebuffer)
|
|
18
|
+
width = framebuffer.width
|
|
19
|
+
height = framebuffer.height
|
|
20
|
+
row_size = (((24 * width) + 31) / 32) * 4
|
|
21
|
+
pixel_data_size = row_size * height
|
|
22
|
+
file_size = 54 + pixel_data_size
|
|
23
|
+
|
|
24
|
+
header = [
|
|
25
|
+
0x42, 0x4D,
|
|
26
|
+
file_size,
|
|
27
|
+
0, 0,
|
|
28
|
+
54
|
|
29
|
+
].pack("CCVvvV")
|
|
30
|
+
|
|
31
|
+
dib = [
|
|
32
|
+
40,
|
|
33
|
+
width, height,
|
|
34
|
+
1,
|
|
35
|
+
24,
|
|
36
|
+
0,
|
|
37
|
+
pixel_data_size,
|
|
38
|
+
2835, 2835,
|
|
39
|
+
0, 0
|
|
40
|
+
].pack("VVVvvVVVVVV")
|
|
41
|
+
|
|
42
|
+
header + dib + pixel_rows(framebuffer, width, height, row_size)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def pixel_rows(framebuffer, width, height, row_size)
|
|
46
|
+
pixels = +""
|
|
47
|
+
|
|
48
|
+
(height - 1).downto(0) do |y|
|
|
49
|
+
row = +""
|
|
50
|
+
width.times do |x|
|
|
51
|
+
bytes = framebuffer.get_pixel(x, y).to_bytes
|
|
52
|
+
row << [bytes[2], bytes[1], bytes[0]].pack("CCC")
|
|
53
|
+
end
|
|
54
|
+
row << ("\x00" * (row_size - (width * 3)))
|
|
55
|
+
pixels << row
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
pixels
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
class FrameWriter
|
|
7
|
+
def self.build(format, ppm_mode: :ascii)
|
|
8
|
+
case format
|
|
9
|
+
when :ppm
|
|
10
|
+
PpmWriter.new(binary: ppm_mode == :binary)
|
|
11
|
+
when :bmp
|
|
12
|
+
BmpWriter.new
|
|
13
|
+
when :png
|
|
14
|
+
begin
|
|
15
|
+
require "tessel"
|
|
16
|
+
rescue LoadError
|
|
17
|
+
raise ArgumentError, "format: :png requires the tessel gem"
|
|
18
|
+
end
|
|
19
|
+
PngWriter.new
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError, "Unsupported file backend format: #{format}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def extension
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def write(_filename, _framebuffer)
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend
|
|
6
|
+
class PngWriter < FrameWriter
|
|
7
|
+
def extension
|
|
8
|
+
"png"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def write(filename, framebuffer)
|
|
12
|
+
image = Tessel::Image.from_rgba(framebuffer.width, framebuffer.height, framebuffer.to_rgba_bytes)
|
|
13
|
+
image.write(filename)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
class PpmWriter < FrameWriter
|
|
7
|
+
def initialize(binary:)
|
|
8
|
+
super()
|
|
9
|
+
@binary = binary
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def extension
|
|
13
|
+
"ppm"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(filename, framebuffer)
|
|
17
|
+
if @binary
|
|
18
|
+
File.binwrite(filename, framebuffer.to_ppm_binary)
|
|
19
|
+
else
|
|
20
|
+
File.write(filename, framebuffer.to_ppm)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|