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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +101 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +245 -0
  5. data/ext/ruby-sfml/extconf.rb +69 -0
  6. data/lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt +78 -0
  7. data/lib/sfml/assets/fonts/DejaVuSans.ttf +0 -0
  8. data/lib/sfml/assets.rb +121 -0
  9. data/lib/sfml/audio/listener.rb +55 -0
  10. data/lib/sfml/audio/music.rb +88 -0
  11. data/lib/sfml/audio/sound.rb +102 -0
  12. data/lib/sfml/audio/sound_buffer.rb +38 -0
  13. data/lib/sfml/audio/sound_buffer_recorder.rb +71 -0
  14. data/lib/sfml/audio/sound_recorder.rb +30 -0
  15. data/lib/sfml/c/audio.rb +106 -0
  16. data/lib/sfml/c/graphics.rb +425 -0
  17. data/lib/sfml/c/network.rb +79 -0
  18. data/lib/sfml/c/system.rb +43 -0
  19. data/lib/sfml/c/window.rb +186 -0
  20. data/lib/sfml/c.rb +72 -0
  21. data/lib/sfml/game.rb +101 -0
  22. data/lib/sfml/graphics/blend_mode.rb +108 -0
  23. data/lib/sfml/graphics/circle_shape.rb +67 -0
  24. data/lib/sfml/graphics/color.rb +89 -0
  25. data/lib/sfml/graphics/convex_shape.rb +82 -0
  26. data/lib/sfml/graphics/font.rb +67 -0
  27. data/lib/sfml/graphics/image.rb +125 -0
  28. data/lib/sfml/graphics/rectangle_shape.rb +62 -0
  29. data/lib/sfml/graphics/render_states.rb +56 -0
  30. data/lib/sfml/graphics/render_target.rb +146 -0
  31. data/lib/sfml/graphics/render_texture.rb +72 -0
  32. data/lib/sfml/graphics/render_window.rb +154 -0
  33. data/lib/sfml/graphics/shader.rb +132 -0
  34. data/lib/sfml/graphics/sprite.rb +75 -0
  35. data/lib/sfml/graphics/text.rb +144 -0
  36. data/lib/sfml/graphics/texture.rb +79 -0
  37. data/lib/sfml/graphics/transform.rb +150 -0
  38. data/lib/sfml/graphics/transformable.rb +74 -0
  39. data/lib/sfml/graphics/vertex.rb +53 -0
  40. data/lib/sfml/graphics/vertex_array.rb +114 -0
  41. data/lib/sfml/graphics/view.rb +126 -0
  42. data/lib/sfml/network/ip_address.rb +67 -0
  43. data/lib/sfml/network/tcp_listener.rb +61 -0
  44. data/lib/sfml/network/tcp_socket.rb +74 -0
  45. data/lib/sfml/network/udp_socket.rb +71 -0
  46. data/lib/sfml/system/clock.rb +44 -0
  47. data/lib/sfml/system/rect.rb +64 -0
  48. data/lib/sfml/system/time.rb +48 -0
  49. data/lib/sfml/system/vector2.rb +66 -0
  50. data/lib/sfml/system/vector3.rb +63 -0
  51. data/lib/sfml/version.rb +19 -0
  52. data/lib/sfml/window/clipboard.rb +38 -0
  53. data/lib/sfml/window/cursor.rb +68 -0
  54. data/lib/sfml/window/event.rb +133 -0
  55. data/lib/sfml/window/joystick.rb +90 -0
  56. data/lib/sfml/window/keyboard.rb +60 -0
  57. data/lib/sfml/window/mouse.rb +71 -0
  58. data/lib/sfml/window/video_mode.rb +37 -0
  59. data/lib/sfml/window/window.rb +149 -0
  60. data/lib/sfml.rb +98 -0
  61. data/ruby-sfml.gemspec +38 -0
  62. 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