ruby-sfml 3.0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +101 -0
- data/LICENSE.txt +21 -0
- data/README.md +245 -0
- data/ext/ruby-sfml/extconf.rb +69 -0
- data/lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt +78 -0
- data/lib/sfml/assets/fonts/DejaVuSans.ttf +0 -0
- data/lib/sfml/assets.rb +121 -0
- data/lib/sfml/audio/listener.rb +55 -0
- data/lib/sfml/audio/music.rb +88 -0
- data/lib/sfml/audio/sound.rb +102 -0
- data/lib/sfml/audio/sound_buffer.rb +38 -0
- data/lib/sfml/audio/sound_buffer_recorder.rb +71 -0
- data/lib/sfml/audio/sound_recorder.rb +30 -0
- data/lib/sfml/c/audio.rb +106 -0
- data/lib/sfml/c/graphics.rb +425 -0
- data/lib/sfml/c/network.rb +79 -0
- data/lib/sfml/c/system.rb +43 -0
- data/lib/sfml/c/window.rb +186 -0
- data/lib/sfml/c.rb +72 -0
- data/lib/sfml/game.rb +101 -0
- data/lib/sfml/graphics/blend_mode.rb +108 -0
- data/lib/sfml/graphics/circle_shape.rb +67 -0
- data/lib/sfml/graphics/color.rb +89 -0
- data/lib/sfml/graphics/convex_shape.rb +82 -0
- data/lib/sfml/graphics/font.rb +67 -0
- data/lib/sfml/graphics/image.rb +125 -0
- data/lib/sfml/graphics/rectangle_shape.rb +62 -0
- data/lib/sfml/graphics/render_states.rb +56 -0
- data/lib/sfml/graphics/render_target.rb +146 -0
- data/lib/sfml/graphics/render_texture.rb +72 -0
- data/lib/sfml/graphics/render_window.rb +154 -0
- data/lib/sfml/graphics/shader.rb +132 -0
- data/lib/sfml/graphics/sprite.rb +75 -0
- data/lib/sfml/graphics/text.rb +144 -0
- data/lib/sfml/graphics/texture.rb +79 -0
- data/lib/sfml/graphics/transform.rb +150 -0
- data/lib/sfml/graphics/transformable.rb +74 -0
- data/lib/sfml/graphics/vertex.rb +53 -0
- data/lib/sfml/graphics/vertex_array.rb +114 -0
- data/lib/sfml/graphics/view.rb +126 -0
- data/lib/sfml/network/ip_address.rb +67 -0
- data/lib/sfml/network/tcp_listener.rb +61 -0
- data/lib/sfml/network/tcp_socket.rb +74 -0
- data/lib/sfml/network/udp_socket.rb +71 -0
- data/lib/sfml/system/clock.rb +44 -0
- data/lib/sfml/system/rect.rb +64 -0
- data/lib/sfml/system/time.rb +48 -0
- data/lib/sfml/system/vector2.rb +66 -0
- data/lib/sfml/system/vector3.rb +63 -0
- data/lib/sfml/version.rb +19 -0
- data/lib/sfml/window/clipboard.rb +38 -0
- data/lib/sfml/window/cursor.rb +68 -0
- data/lib/sfml/window/event.rb +133 -0
- data/lib/sfml/window/joystick.rb +90 -0
- data/lib/sfml/window/keyboard.rb +60 -0
- data/lib/sfml/window/mouse.rb +71 -0
- data/lib/sfml/window/video_mode.rb +37 -0
- data/lib/sfml/window/window.rb +149 -0
- data/lib/sfml.rb +98 -0
- data/ruby-sfml.gemspec +38 -0
- metadata +163 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# The main drawing surface. Wraps sfRenderWindow.
|
|
3
|
+
#
|
|
4
|
+
# window = SFML::RenderWindow.new(800, 600, "Hello")
|
|
5
|
+
#
|
|
6
|
+
# while window.open?
|
|
7
|
+
# window.each_event do |event|
|
|
8
|
+
# case event
|
|
9
|
+
# in {type: :closed} then window.close
|
|
10
|
+
# in {type: :key_pressed, code: :escape} then window.close
|
|
11
|
+
# end
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# window.clear(SFML::Color.cornflower_blue)
|
|
15
|
+
# window.display
|
|
16
|
+
# end
|
|
17
|
+
class RenderWindow
|
|
18
|
+
include Graphics::RenderTarget
|
|
19
|
+
CSFML_PREFIX = :sfRenderWindow
|
|
20
|
+
|
|
21
|
+
DEFAULT_STYLE = C::Window::Style::DEFAULT
|
|
22
|
+
|
|
23
|
+
# The first form takes (width, height, title, **opts).
|
|
24
|
+
# The second form takes (video_mode, title, **opts) for full control.
|
|
25
|
+
#
|
|
26
|
+
# Options:
|
|
27
|
+
# style: bitmask of SFML::C::Window::Style constants
|
|
28
|
+
# fullscreen: true to use sfFullscreen state instead of sfWindowed
|
|
29
|
+
# framerate: cap to N FPS via sfRenderWindow_setFramerateLimit
|
|
30
|
+
# vsync: enable vertical sync
|
|
31
|
+
def initialize(*args, **opts)
|
|
32
|
+
mode, title = parse_args(args)
|
|
33
|
+
style = opts.fetch(:style, DEFAULT_STYLE)
|
|
34
|
+
state = opts[:fullscreen] ? :fullscreen : :windowed
|
|
35
|
+
|
|
36
|
+
ptr = C::Graphics.sfRenderWindow_create(
|
|
37
|
+
mode.to_native,
|
|
38
|
+
title.to_s,
|
|
39
|
+
style,
|
|
40
|
+
C::Window::State[state],
|
|
41
|
+
nil,
|
|
42
|
+
)
|
|
43
|
+
raise Error, "sfRenderWindow_create returned NULL" if ptr.null?
|
|
44
|
+
|
|
45
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRenderWindow_destroy))
|
|
46
|
+
@event_buffer = C::Window::Event.new
|
|
47
|
+
|
|
48
|
+
self.framerate_limit = opts[:framerate] if opts[:framerate]
|
|
49
|
+
self.vsync = opts[:vsync] unless opts[:vsync].nil?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def open?
|
|
53
|
+
C::Graphics.sfRenderWindow_isOpen(@handle)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def close
|
|
57
|
+
C::Graphics.sfRenderWindow_close(@handle)
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns the next pending Event or nil if the queue is empty.
|
|
62
|
+
def poll_event
|
|
63
|
+
return nil unless C::Graphics.sfRenderWindow_pollEvent(@handle, @event_buffer)
|
|
64
|
+
Event.from_native(@event_buffer)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Yields every pending event for this frame, then returns. Without a block
|
|
68
|
+
# returns an Enumerator.
|
|
69
|
+
def each_event
|
|
70
|
+
return enum_for(:each_event) unless block_given?
|
|
71
|
+
while (event = poll_event)
|
|
72
|
+
yield event
|
|
73
|
+
end
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def title=(value)
|
|
78
|
+
C::Graphics.sfRenderWindow_setTitle(@handle, value.to_s)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Apply a SFML::Cursor as the visible mouse pointer over this
|
|
82
|
+
# window. Keeps a Ruby reference so the Cursor object's lifetime
|
|
83
|
+
# spans at least until the next assignment.
|
|
84
|
+
def cursor=(cursor)
|
|
85
|
+
raise ArgumentError, "RenderWindow#cursor= requires a SFML::Cursor" unless cursor.is_a?(Cursor)
|
|
86
|
+
C::Graphics.sfRenderWindow_setMouseCursor(@handle, cursor.handle)
|
|
87
|
+
@cursor = cursor
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Toggle the OS mouse pointer's visibility while it's over the window.
|
|
91
|
+
def cursor_visible=(visible)
|
|
92
|
+
C::Graphics.sfRenderWindow_setMouseCursorVisible(@handle, visible ? true : false)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Lock the mouse pointer inside the window's client area while
|
|
96
|
+
# focused — useful for FPS-style games or when dragging widgets
|
|
97
|
+
# that need pixel-precise input.
|
|
98
|
+
def cursor_grabbed=(grabbed)
|
|
99
|
+
C::Graphics.sfRenderWindow_setMouseCursorGrabbed(@handle, grabbed ? true : false)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def framerate_limit=(value)
|
|
103
|
+
C::Graphics.sfRenderWindow_setFramerateLimit(@handle, Integer(value))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def vsync=(enabled)
|
|
107
|
+
C::Graphics.sfRenderWindow_setVerticalSyncEnabled(@handle, enabled ? true : false)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def size
|
|
111
|
+
v = C::Graphics.sfRenderWindow_getSize(@handle)
|
|
112
|
+
Vector2.new(v[:x], v[:y])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Convenience driver loop. Yields the per-frame delta (SFML::Time) and
|
|
116
|
+
# auto-pumps events + display. The block is responsible for #clear and
|
|
117
|
+
# any drawing.
|
|
118
|
+
#
|
|
119
|
+
# window.run do |dt, events|
|
|
120
|
+
# events.each { |e| ... }
|
|
121
|
+
# window.clear(...)
|
|
122
|
+
# window.draw(...)
|
|
123
|
+
# end
|
|
124
|
+
def run
|
|
125
|
+
clock = Clock.new
|
|
126
|
+
while open?
|
|
127
|
+
dt = clock.restart
|
|
128
|
+
events = each_event.to_a
|
|
129
|
+
yield dt, events
|
|
130
|
+
display
|
|
131
|
+
end
|
|
132
|
+
self
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
attr_reader :handle # :nodoc:
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def parse_args(args)
|
|
140
|
+
case args.length
|
|
141
|
+
when 2
|
|
142
|
+
# (video_mode, title)
|
|
143
|
+
[args[0], args[1]]
|
|
144
|
+
when 3
|
|
145
|
+
# (width, height, title)
|
|
146
|
+
[VideoMode.new(args[0], args[1]), args[2]]
|
|
147
|
+
else
|
|
148
|
+
raise ArgumentError,
|
|
149
|
+
"RenderWindow.new takes either (video_mode, title) or " \
|
|
150
|
+
"(width, height, title), got #{args.length} positional arg(s)"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A GLSL shader. Build one from a vertex and/or fragment source (either
|
|
3
|
+
# a file path or a literal source string), then set uniforms with
|
|
4
|
+
# bracket assignment and pass it to draw via the `shader:` kwarg:
|
|
5
|
+
#
|
|
6
|
+
# shader = SFML::Shader.from_source(fragment: <<~GLSL)
|
|
7
|
+
# uniform sampler2D texture;
|
|
8
|
+
# uniform float time;
|
|
9
|
+
# void main() {
|
|
10
|
+
# vec2 uv = gl_TexCoord[0].xy;
|
|
11
|
+
# uv.x += sin(uv.y * 20.0 + time * 3.0) * 0.02;
|
|
12
|
+
# gl_FragColor = texture2D(texture, uv) * gl_Color;
|
|
13
|
+
# }
|
|
14
|
+
# GLSL
|
|
15
|
+
#
|
|
16
|
+
# shader[:time] = clock.elapsed.as_seconds
|
|
17
|
+
# window.draw(sprite, shader: shader)
|
|
18
|
+
#
|
|
19
|
+
# Uniform types are inferred from the Ruby value:
|
|
20
|
+
# Float / Integer / Numeric → float (uniform float)
|
|
21
|
+
# true / false → bool
|
|
22
|
+
# SFML::Vector2 → vec2
|
|
23
|
+
# SFML::Vector3 → vec3
|
|
24
|
+
# SFML::Color → vec4 (normalised RGBA)
|
|
25
|
+
# SFML::Texture → sampler2D
|
|
26
|
+
# :current_texture (Symbol) → sampler2D bound to the drawable's own texture
|
|
27
|
+
# [a, b] → vec2 (floats)
|
|
28
|
+
# [a, b, c] → vec3
|
|
29
|
+
# [a, b, c, d] → vec4
|
|
30
|
+
#
|
|
31
|
+
# Need an int / bvec / matrix / array uniform? Use the explicit setters
|
|
32
|
+
# (#set_int, #set_ivec2, etc.) — they exist for completeness.
|
|
33
|
+
class Shader
|
|
34
|
+
# Class-level: is GLSL available on the current GPU at all?
|
|
35
|
+
def self.available?
|
|
36
|
+
C::Graphics.sfShader_isAvailable
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.geometry_available?
|
|
40
|
+
C::Graphics.sfShader_isGeometryAvailable
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Build a shader from one or more source files. Any of vertex /
|
|
44
|
+
# geometry / fragment may be omitted; at least one must be present.
|
|
45
|
+
def self.from_file(vertex: nil, geometry: nil, fragment: nil)
|
|
46
|
+
_check_at_least_one(vertex, geometry, fragment)
|
|
47
|
+
ptr = C::Graphics.sfShader_createFromFile(
|
|
48
|
+
vertex&.to_s, geometry&.to_s, fragment&.to_s,
|
|
49
|
+
)
|
|
50
|
+
raise Error, "sfShader_createFromFile failed (compile error or missing file?)" if ptr.null?
|
|
51
|
+
_wrap(ptr)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Build a shader directly from GLSL source strings.
|
|
55
|
+
def self.from_source(vertex: nil, geometry: nil, fragment: nil)
|
|
56
|
+
_check_at_least_one(vertex, geometry, fragment)
|
|
57
|
+
ptr = C::Graphics.sfShader_createFromMemory(vertex, geometry, fragment)
|
|
58
|
+
raise Error, "sfShader_createFromMemory failed (GLSL compile error?)" if ptr.null?
|
|
59
|
+
_wrap(ptr)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Set a uniform by name. Dispatches to the right CSFML setter based
|
|
63
|
+
# on the Ruby value's type — see the class-level docs for the table.
|
|
64
|
+
def []=(name, value)
|
|
65
|
+
n = name.to_s
|
|
66
|
+
case value
|
|
67
|
+
when true, false
|
|
68
|
+
C::Graphics.sfShader_setBoolUniform(@handle, n, value)
|
|
69
|
+
when Integer
|
|
70
|
+
C::Graphics.sfShader_setFloatUniform(@handle, n, value.to_f)
|
|
71
|
+
when Numeric
|
|
72
|
+
C::Graphics.sfShader_setFloatUniform(@handle, n, value.to_f)
|
|
73
|
+
when Vector2
|
|
74
|
+
v = C::System::Vector2f.new
|
|
75
|
+
v[:x] = value.x.to_f; v[:y] = value.y.to_f
|
|
76
|
+
C::Graphics.sfShader_setVec2Uniform(@handle, n, v)
|
|
77
|
+
when Vector3
|
|
78
|
+
v = C::System::Vector3f.new
|
|
79
|
+
v[:x] = value.x.to_f; v[:y] = value.y.to_f; v[:z] = value.z.to_f
|
|
80
|
+
C::Graphics.sfShader_setVec3Uniform(@handle, n, v)
|
|
81
|
+
when Color
|
|
82
|
+
C::Graphics.sfShader_setColorUniform(@handle, n, value.to_native)
|
|
83
|
+
when Texture
|
|
84
|
+
C::Graphics.sfShader_setTextureUniform(@handle, n, value.handle)
|
|
85
|
+
when :current_texture
|
|
86
|
+
C::Graphics.sfShader_setCurrentTextureUniform(@handle, n)
|
|
87
|
+
when Array
|
|
88
|
+
case value.length
|
|
89
|
+
when 2 then self[name] = Vector2.new(*value)
|
|
90
|
+
when 3 then self[name] = Vector3.new(*value)
|
|
91
|
+
when 4
|
|
92
|
+
v = C::Graphics::GlslVec4.new
|
|
93
|
+
v[:x] = value[0].to_f; v[:y] = value[1].to_f
|
|
94
|
+
v[:z] = value[2].to_f; v[:w] = value[3].to_f
|
|
95
|
+
C::Graphics.sfShader_setVec4Uniform(@handle, n, v)
|
|
96
|
+
else
|
|
97
|
+
raise ArgumentError, "Shader uniform array must be length 2, 3, or 4 (got #{value.length})"
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
raise ArgumentError,
|
|
101
|
+
"Shader uniform value must be Numeric, Vector2/3, Color, Texture, " \
|
|
102
|
+
"Array of 2-4 numbers, or :current_texture (got #{value.class})"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Explicit integer setters when a uniform really is `uniform int n`.
|
|
107
|
+
def set_int(name, value)
|
|
108
|
+
C::Graphics.sfShader_setIntUniform(@handle, name.to_s, Integer(value))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def set_ivec2(name, x, y)
|
|
112
|
+
v = C::System::Vector2i.new
|
|
113
|
+
v[:x] = Integer(x); v[:y] = Integer(y)
|
|
114
|
+
C::Graphics.sfShader_setIvec2Uniform(@handle, name.to_s, v)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
attr_reader :handle # :nodoc:
|
|
118
|
+
|
|
119
|
+
# @!visibility private
|
|
120
|
+
def self._wrap(ptr)
|
|
121
|
+
shader = allocate
|
|
122
|
+
shader.instance_variable_set(:@handle, FFI::AutoPointer.new(ptr, C::Graphics.method(:sfShader_destroy)))
|
|
123
|
+
shader
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @!visibility private
|
|
127
|
+
def self._check_at_least_one(*sources)
|
|
128
|
+
return unless sources.compact.empty?
|
|
129
|
+
raise ArgumentError, "Shader needs at least one of vertex:, geometry:, fragment:"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A textured rectangle. SFML 3 requires a Texture at construction time
|
|
3
|
+
# (no default-constructible Sprite anymore), so we keep the texture
|
|
4
|
+
# reference alive inside the Ruby Sprite to prevent the GC from freeing
|
|
5
|
+
# the GPU resource while it's still being drawn.
|
|
6
|
+
#
|
|
7
|
+
# tex = SFML::Texture.load("hero.png")
|
|
8
|
+
# sprite = SFML::Sprite.new(tex, position: [100, 100])
|
|
9
|
+
# window.draw(sprite)
|
|
10
|
+
class Sprite
|
|
11
|
+
include Graphics::Transformable
|
|
12
|
+
CSFML_PREFIX = :sfSprite
|
|
13
|
+
|
|
14
|
+
def initialize(texture, **opts)
|
|
15
|
+
raise ArgumentError, "Sprite requires a SFML::Texture" unless texture.is_a?(Texture)
|
|
16
|
+
|
|
17
|
+
ptr = C::Graphics.sfSprite_create(texture.handle)
|
|
18
|
+
raise Error, "sfSprite_create returned NULL" if ptr.null?
|
|
19
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfSprite_destroy))
|
|
20
|
+
@texture = texture # keep alive for GC
|
|
21
|
+
|
|
22
|
+
self.color = opts[:color] if opts.key?(:color)
|
|
23
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
24
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
25
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
26
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :texture
|
|
30
|
+
|
|
31
|
+
def texture=(new_texture)
|
|
32
|
+
raise ArgumentError, "Sprite#texture= requires a SFML::Texture" unless new_texture.is_a?(Texture)
|
|
33
|
+
C::Graphics.sfSprite_setTexture(@handle, new_texture.handle, false)
|
|
34
|
+
@texture = new_texture
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def color = Color.from_native(C::Graphics.sfSprite_getColor(@handle))
|
|
38
|
+
|
|
39
|
+
def color=(c)
|
|
40
|
+
C::Graphics.sfSprite_setColor(@handle, c.to_native)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Sub-region of the texture this sprite shows. Returns a SFML::Rect
|
|
44
|
+
# of integer pixel coordinates. Use it for sprite-sheet animation:
|
|
45
|
+
#
|
|
46
|
+
# sprite.texture_rect = SFML::Rect.new([frame * 32, 0], [32, 32])
|
|
47
|
+
def texture_rect
|
|
48
|
+
Rect.from_native(C::Graphics.sfSprite_getTextureRect(@handle))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def texture_rect=(rect)
|
|
52
|
+
raise ArgumentError, "Sprite#texture_rect= requires a SFML::Rect" unless rect.is_a?(Rect)
|
|
53
|
+
native = C::Graphics::IntRect.new
|
|
54
|
+
native[:position][:x] = Integer(rect.x)
|
|
55
|
+
native[:position][:y] = Integer(rect.y)
|
|
56
|
+
native[:size][:x] = Integer(rect.width)
|
|
57
|
+
native[:size][:y] = Integer(rect.height)
|
|
58
|
+
C::Graphics.sfSprite_setTextureRect(@handle, native)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def local_bounds
|
|
62
|
+
Rect.from_native(C::Graphics.sfSprite_getLocalBounds(@handle))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def global_bounds
|
|
66
|
+
Rect.from_native(C::Graphics.sfSprite_getGlobalBounds(@handle))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
70
|
+
target._draw_native(:Sprite, @handle, states_ptr)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
attr_reader :handle # :nodoc:
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Drawable rendered text. Like Sprite, requires a Font at construction time
|
|
3
|
+
# in SFML 3, and we hold a Ruby reference to it to keep the GPU resource
|
|
4
|
+
# alive while the Text object is in use.
|
|
5
|
+
#
|
|
6
|
+
# font = SFML::Font.find("DejaVuSans")
|
|
7
|
+
# score = SFML::Text.new(font, "0 : 0",
|
|
8
|
+
# character_size: 48,
|
|
9
|
+
# fill_color: SFML::Color.white,
|
|
10
|
+
# position: [400, 30],
|
|
11
|
+
# style: %i[bold])
|
|
12
|
+
# window.draw(score)
|
|
13
|
+
# score.string = "1 : 0" # mutate freely after construction
|
|
14
|
+
class Text
|
|
15
|
+
include Graphics::Transformable
|
|
16
|
+
CSFML_PREFIX = :sfText
|
|
17
|
+
|
|
18
|
+
STYLES = {
|
|
19
|
+
regular: 0,
|
|
20
|
+
bold: 1 << 0,
|
|
21
|
+
italic: 1 << 1,
|
|
22
|
+
underlined: 1 << 2,
|
|
23
|
+
strike_through: 1 << 3,
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def initialize(font, string = "", **opts)
|
|
27
|
+
raise ArgumentError, "Text requires a SFML::Font" unless font.is_a?(Font)
|
|
28
|
+
|
|
29
|
+
ptr = C::Graphics.sfText_create(font.handle)
|
|
30
|
+
raise Error, "sfText_create returned NULL" if ptr.null?
|
|
31
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfText_destroy))
|
|
32
|
+
@font = font # keep alive for GC
|
|
33
|
+
|
|
34
|
+
self.string = string
|
|
35
|
+
self.character_size = opts[:character_size] if opts.key?(:character_size)
|
|
36
|
+
self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
|
|
37
|
+
self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
|
|
38
|
+
self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
|
|
39
|
+
self.style = opts[:style] if opts.key?(:style)
|
|
40
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
41
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
42
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
43
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :font
|
|
47
|
+
|
|
48
|
+
def font=(new_font)
|
|
49
|
+
raise ArgumentError, "Text#font= requires a SFML::Font" unless new_font.is_a?(Font)
|
|
50
|
+
C::Graphics.sfText_setFont(@handle, new_font.handle)
|
|
51
|
+
@font = new_font
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Read the current string back as Ruby UTF-8. CSFML returns a pointer
|
|
55
|
+
# to a null-terminated sfChar32 (UTF-32) array; we walk it until the
|
|
56
|
+
# zero terminator and pack the codepoints back into a UTF-8 String.
|
|
57
|
+
def string
|
|
58
|
+
ptr = C::Graphics.sfText_getUnicodeString(@handle)
|
|
59
|
+
return "" if ptr.null?
|
|
60
|
+
|
|
61
|
+
codepoints = []
|
|
62
|
+
offset = 0
|
|
63
|
+
loop do
|
|
64
|
+
cp = ptr.get_uint32(offset)
|
|
65
|
+
break if cp.zero?
|
|
66
|
+
codepoints << cp
|
|
67
|
+
offset += 4
|
|
68
|
+
end
|
|
69
|
+
codepoints.pack("U*")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def string=(value)
|
|
73
|
+
str = value.to_s.encode("UTF-8")
|
|
74
|
+
cps = str.unpack("U*") # array of integer codepoints
|
|
75
|
+
buf = FFI::MemoryPointer.new(:uint32, cps.length + 1)
|
|
76
|
+
buf.write_array_of_uint32(cps + [0])
|
|
77
|
+
C::Graphics.sfText_setUnicodeString(@handle, buf)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def character_size = C::Graphics.sfText_getCharacterSize(@handle)
|
|
81
|
+
|
|
82
|
+
def character_size=(value)
|
|
83
|
+
C::Graphics.sfText_setCharacterSize(@handle, Integer(value))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fill_color = Color.from_native(C::Graphics.sfText_getFillColor(@handle))
|
|
87
|
+
|
|
88
|
+
def fill_color=(c)
|
|
89
|
+
C::Graphics.sfText_setFillColor(@handle, c.to_native)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def outline_color = Color.from_native(C::Graphics.sfText_getOutlineColor(@handle))
|
|
93
|
+
|
|
94
|
+
def outline_color=(c)
|
|
95
|
+
C::Graphics.sfText_setOutlineColor(@handle, c.to_native)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def outline_thickness = C::Graphics.sfText_getOutlineThickness(@handle)
|
|
99
|
+
|
|
100
|
+
def outline_thickness=(t)
|
|
101
|
+
C::Graphics.sfText_setOutlineThickness(@handle, t.to_f)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns an Array of style symbols, e.g. [:bold, :italic].
|
|
105
|
+
def style
|
|
106
|
+
bits = C::Graphics.sfText_getStyle(@handle)
|
|
107
|
+
return [:regular] if bits.zero?
|
|
108
|
+
STYLES.each_with_object([]) do |(name, value), acc|
|
|
109
|
+
acc << name if value != 0 && (bits & value) != 0
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Accepts a single Symbol, an Array of Symbols, or a raw integer bitmask.
|
|
114
|
+
def style=(value)
|
|
115
|
+
bits = case value
|
|
116
|
+
when Integer then value
|
|
117
|
+
when Symbol then STYLES.fetch(value)
|
|
118
|
+
when Array
|
|
119
|
+
value.reduce(0) { |acc, sym| acc | STYLES.fetch(sym) }
|
|
120
|
+
else
|
|
121
|
+
raise ArgumentError, "Text#style= expects Symbol, Array, or Integer; got #{value.class}"
|
|
122
|
+
end
|
|
123
|
+
C::Graphics.sfText_setStyle(@handle, bits)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Bounding box of the text in its own (untransformed) coordinate system.
|
|
127
|
+
# Use this to centre or align glyphs precisely:
|
|
128
|
+
# text.origin = [text.local_bounds.width / 2, 0]
|
|
129
|
+
def local_bounds
|
|
130
|
+
Rect.from_native(C::Graphics.sfText_getLocalBounds(@handle))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Bounding box after applying the Text's transform (position/scale/rotation).
|
|
134
|
+
def global_bounds
|
|
135
|
+
Rect.from_native(C::Graphics.sfText_getGlobalBounds(@handle))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
139
|
+
target._draw_native(:Text, @handle, states_ptr)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
attr_reader :handle # :nodoc:
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A 2D image stored on the GPU. Used by Sprite (and shapes) to draw textured
|
|
3
|
+
# geometry. Build one with .load:
|
|
4
|
+
#
|
|
5
|
+
# tex = SFML::Texture.load("assets/hero.png")
|
|
6
|
+
# tex = SFML::Texture.load("assets/tile.png", smooth: true, repeated: true)
|
|
7
|
+
class Texture
|
|
8
|
+
def self.load(path, smooth: false, repeated: false)
|
|
9
|
+
ptr = C::Graphics.sfTexture_createFromFile(path.to_s, nil)
|
|
10
|
+
raise Error, "Could not load texture from #{path.inspect}" if ptr.null?
|
|
11
|
+
|
|
12
|
+
tex = allocate
|
|
13
|
+
tex.send(:_take_ownership, ptr)
|
|
14
|
+
tex.smooth = smooth
|
|
15
|
+
tex.repeated = repeated
|
|
16
|
+
tex
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Upload a CPU-side SFML::Image to the GPU as a new Texture. Keeps
|
|
20
|
+
# the RGBA byte order and dimensions of the source image.
|
|
21
|
+
def self.from_image(image, smooth: false, repeated: false)
|
|
22
|
+
raise ArgumentError, "Texture.from_image needs a SFML::Image" unless image.is_a?(Image)
|
|
23
|
+
|
|
24
|
+
ptr = C::Graphics.sfTexture_createFromImage(image.handle, nil)
|
|
25
|
+
raise Error, "sfTexture_createFromImage returned NULL" if ptr.null?
|
|
26
|
+
|
|
27
|
+
tex = allocate
|
|
28
|
+
tex.send(:_take_ownership, ptr)
|
|
29
|
+
tex.smooth = smooth
|
|
30
|
+
tex.repeated = repeated
|
|
31
|
+
tex
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Re-upload an Image's pixels to this texture in place. The image
|
|
35
|
+
# must match the texture's size — use this for animated procedural
|
|
36
|
+
# textures (paint-buffer style) without re-allocating GPU memory.
|
|
37
|
+
def update(image)
|
|
38
|
+
raise ArgumentError, "Texture#update needs a SFML::Image" unless image.is_a?(Image)
|
|
39
|
+
offset = C::System::Vector2u.new
|
|
40
|
+
offset[:x] = 0; offset[:y] = 0
|
|
41
|
+
C::Graphics.sfTexture_updateFromImage(@handle, image.handle, offset)
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Read the texture back from the GPU into a fresh SFML::Image. Slow
|
|
46
|
+
# — useful for screenshots or post-processing inspection.
|
|
47
|
+
def to_image
|
|
48
|
+
ptr = C::Graphics.sfTexture_copyToImage(@handle)
|
|
49
|
+
raise Error, "sfTexture_copyToImage returned NULL" if ptr.null?
|
|
50
|
+
img = Image.allocate
|
|
51
|
+
img.send(:_take_ownership, ptr)
|
|
52
|
+
img
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def size
|
|
56
|
+
Vector2.from_native(C::Graphics.sfTexture_getSize(@handle))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def smooth? = C::Graphics.sfTexture_isSmooth(@handle)
|
|
60
|
+
|
|
61
|
+
def smooth=(value)
|
|
62
|
+
C::Graphics.sfTexture_setSmooth(@handle, !!value)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def repeated? = C::Graphics.sfTexture_isRepeated(@handle)
|
|
66
|
+
|
|
67
|
+
def repeated=(value)
|
|
68
|
+
C::Graphics.sfTexture_setRepeated(@handle, !!value)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
attr_reader :handle # :nodoc:
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def _take_ownership(ptr)
|
|
76
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfTexture_destroy))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|