ruby-sfml 3.0.0.3 → 3.0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3261668821d3742648968ea78730bf09a83c72a428e69fe9fc5f2492f13f8557
4
- data.tar.gz: 860208fab91bf28c6825948892bca54478defebdc29c596cb30088e394a7f516
3
+ metadata.gz: 02adbbc1dc50cc42127dc845ad9be68829ee07c100cb915e83f15d2e8deb8df7
4
+ data.tar.gz: 59e6cadd1e7a325e70b7ad92b162f7ba3d73035fc482317e3ad0f28d8b7afcb6
5
5
  SHA512:
6
- metadata.gz: 2cae71d47e55c1d1fa1b39c8c18d7362fff927224ae410449ef33444fe07060ef2cf8c8c3c6b0b65beffd7d2f9c99e6cd89c6d2558a5573817150c636493375b
7
- data.tar.gz: 5a4e5a8f78ae23ed08d72bc62df15587787e172000abd53b8fe1b9869952f98348d9c775f37c606555f25e5c5b26f2009685e136a26b788ae4ce9123817090be
6
+ metadata.gz: da0f1e034c77f9a78fb202e4e40e90099aafac7fbe5b2261e94bd693e51f937098c5113491f723eefc45acc731f983b39cc6a27e372c81e6391f8b2777aee99e
7
+ data.tar.gz: a494f581e0cf1975c5233705838cc9f29f0f99fe88a793cd580478772c5170a0c440330c40f464947a689849d43b8e2c16b4b247c0cf4350eea5e35616d9ec90
data/CHANGELOG.md CHANGED
@@ -8,6 +8,57 @@ ruby-sfml's own patch level.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [3.0.0.4] — 2026-05-09
12
+
13
+ ### Added — graphics
14
+
15
+ - `Sprite#dup` / `#clone`, `Sprite#texture` (borrowed reference),
16
+ `Sprite#transform`, `Sprite#inverse_transform`.
17
+ - `View#scissor` / `View#scissor=` — normalised [0..1] clip rect
18
+ applied at render time (paired with the existing `viewport` API).
19
+ - `Texture.from_memory(bytes, smooth:, repeated:)` — decode +
20
+ upload a Ruby String of bytes (PNG / JPG / BMP / TGA / …) as a
21
+ texture. Bypasses the disk for embedded assets / network blobs.
22
+ - `Texture#resize(w, h)` — reallocate GPU memory in place.
23
+ - `Texture#swap(other)` — atomically swap GPU memory between two
24
+ textures (cheap double-buffer pattern).
25
+ - `Texture#native_handle` — the OpenGL texture-object name for
26
+ raw GL interop.
27
+ - `Texture#update_from_texture(source, offset:)`,
28
+ `Texture#update_from_render_window(window, offset:)`,
29
+ `Texture#update_from_window(window, offset:)` — copy pixels
30
+ from another GPU texture / from a window's back-buffer / from a
31
+ bare-Window's framebuffer into this texture at `offset`.
32
+ - `RenderWindow#viewport(view = self.view)` and
33
+ `RenderWindow#scissor(view = self.view)` — pixel-space
34
+ `SFML::Rect`s the view actually covers / clips. Same on
35
+ `RenderTexture`.
36
+ - `Shader#set_int_color(name, color)` — uploads a `SFML::Color`
37
+ as a `vec4` uniform (CSFML normalises 0–255 → 0.0–1.0 for you).
38
+
39
+ ### Added — audio
40
+ - `Sound#dup` / `#clone`, `Sound#buffer` reader.
41
+ - `Sound#pan` / `#pan=`, `Sound#min_gain`/`#max_gain` (and `=`),
42
+ `Sound#max_distance` (and `=`), `Sound#spatialization_enabled?` /
43
+ `#spatialization_enabled=`,
44
+ `Sound#directional_attenuation_factor` (and `=`) — the full
45
+ remaining 3D-audio surface from CSFML's SoundSource.
46
+ - Same set on `Music`: `pan`, gain clamps, max-distance,
47
+ spatialisation, directional-attenuation.
48
+ - `Music#channel_count`, `Music#sample_rate` — stream introspection.
49
+ - `Music#loop_points` / `#loop_points=` — set the looping window
50
+ inside a track ([offset_time, length_time] pair of `SFML::Time`s).
51
+ - `Music.from_memory(bytes, **opts)` — stream from a Ruby String
52
+ of bytes (in-memory MP3 / OGG / FLAC). Caller's bytes must
53
+ outlive the `Music` (we pin the Ruby buffer).
54
+ - `SoundBuffer.from_memory(bytes)` — decode `.wav` / `.ogg` /
55
+ `.flac` from RAM.
56
+ - `SoundBuffer.from_samples(samples, sample_rate:, channel_count:,
57
+ channel_map:)` — build a buffer from a Ruby Array of int16
58
+ samples. Default channel maps for 1- and 2-channel content.
59
+ - `SoundBuffer#dup`, `SoundBuffer#sample_count`,
60
+ `SoundBuffer#samples` — sample-level introspection.
61
+
11
62
  ## [3.0.0.3] — 2026-05-09
12
63
 
13
64
  ### Added — typography
@@ -9,6 +9,29 @@ module SFML
9
9
  ptr = C::Audio.sfMusic_createFromFile(path.to_s)
10
10
  raise Error, "Could not load music from #{path.inspect}" if ptr.null?
11
11
 
12
+ _wrap(ptr, opts)
13
+ end
14
+
15
+ # Stream music from a Ruby String of bytes (an in-memory MP3,
16
+ # OGG, FLAC, …). Useful for embedded audio or downloaded
17
+ # tracks that bypass the disk. The bytes must outlive the
18
+ # Music object — SFML keeps a pointer into them.
19
+ def self.from_memory(bytes, **opts)
20
+ raise ArgumentError, "expected a String, got #{bytes.class}" unless bytes.is_a?(String)
21
+
22
+ buf = FFI::MemoryPointer.new(:uint8, bytes.bytesize)
23
+ buf.write_bytes(bytes)
24
+ ptr = C::Audio.sfMusic_createFromMemory(buf, bytes.bytesize)
25
+ raise Error, "sfMusic_createFromMemory returned NULL — unsupported format?" if ptr.null?
26
+
27
+ m = _wrap(ptr, opts)
28
+ m.instance_variable_set(:@_memory_pin, buf) # keep buffer alive
29
+ m
30
+ end
31
+
32
+ # Internal — finish initialising a Music from an already-built
33
+ # CSFML pointer.
34
+ def self._wrap(ptr, opts)
12
35
  m = allocate
13
36
  m.send(:_take_ownership, ptr)
14
37
  m.instance_variable_set(:@looping, false)
@@ -17,6 +40,7 @@ module SFML
17
40
  m.looping = opts[:looping] if opts.key?(:looping)
18
41
  m
19
42
  end
43
+ private_class_method :_wrap
20
44
 
21
45
  def play = C::Audio.sfMusic_play(@handle)
22
46
  def pause = C::Audio.sfMusic_pause(@handle)
@@ -143,6 +167,69 @@ module SFML
143
167
  C::Audio.sfMusic_setRelativeToListener(@handle, value ? true : false)
144
168
  end
145
169
 
170
+ # ---- Stream introspection ----
171
+
172
+ def channel_count = C::Audio.sfMusic_getChannelCount(@handle)
173
+ def sample_rate = C::Audio.sfMusic_getSampleRate(@handle)
174
+
175
+ # The portion of the track that loops when `looping = true`.
176
+ # Returns `[offset, length]` of `SFML::Time`s; defaults to the
177
+ # whole track. Set with `loop_points = [Time, Time]`.
178
+ def loop_points
179
+ span = C::Audio.sfMusic_getLoopPoints(@handle)
180
+ [Time.from_native(span[:offset]), Time.from_native(span[:length])]
181
+ end
182
+
183
+ def loop_points=(value)
184
+ offset_t, length_t = value
185
+ raise ArgumentError, "expected [offset_time, length_time]" unless offset_t && length_t
186
+
187
+ span = C::Audio::TimeSpan.new
188
+ span[:offset][:microseconds] = offset_t.is_a?(Time) ? offset_t.microseconds : Time.seconds(offset_t.to_f).microseconds
189
+ span[:length][:microseconds] = length_t.is_a?(Time) ? length_t.microseconds : Time.seconds(length_t.to_f).microseconds
190
+ C::Audio.sfMusic_setLoopPoints(@handle, span)
191
+ end
192
+
193
+ # ---- 3D-audio extras (mirror of Sound's) ----
194
+
195
+ def pan = C::Audio.sfMusic_getPan(@handle)
196
+
197
+ def pan=(v)
198
+ C::Audio.sfMusic_setPan(@handle, v.to_f)
199
+ end
200
+
201
+ def min_gain = C::Audio.sfMusic_getMinGain(@handle)
202
+
203
+ def min_gain=(v)
204
+ C::Audio.sfMusic_setMinGain(@handle, v.to_f)
205
+ end
206
+
207
+ def max_gain = C::Audio.sfMusic_getMaxGain(@handle)
208
+
209
+ def max_gain=(v)
210
+ C::Audio.sfMusic_setMaxGain(@handle, v.to_f)
211
+ end
212
+
213
+ def max_distance = C::Audio.sfMusic_getMaxDistance(@handle)
214
+
215
+ def max_distance=(v)
216
+ C::Audio.sfMusic_setMaxDistance(@handle, v.to_f)
217
+ end
218
+
219
+ def spatialization_enabled? = C::Audio.sfMusic_isSpatializationEnabled(@handle)
220
+
221
+ def spatialization_enabled=(v)
222
+ C::Audio.sfMusic_setSpatializationEnabled(@handle, v ? true : false)
223
+ end
224
+
225
+ def directional_attenuation_factor
226
+ C::Audio.sfMusic_getDirectionalAttenuationFactor(@handle)
227
+ end
228
+
229
+ def directional_attenuation_factor=(v)
230
+ C::Audio.sfMusic_setDirectionalAttenuationFactor(@handle, v.to_f)
231
+ end
232
+
146
233
  private
147
234
 
148
235
  def _take_ownership(ptr)
@@ -180,5 +180,77 @@ module SFML
180
180
  def relative_to_listener=(value)
181
181
  C::Audio.sfSound_setRelativeToListener(@handle, value ? true : false)
182
182
  end
183
+
184
+ # The buffer this Sound is currently bound to (the one passed
185
+ # to `.new`, or whoever last called `buffer=`). Returns nil
186
+ # if the underlying source has none.
187
+ def buffer
188
+ ptr = C::Audio.sfSound_getBuffer(@handle)
189
+ return nil if ptr.null?
190
+ @buffer # keep Ruby reference alive (the C handle is borrowed from it)
191
+ end
192
+
193
+ # Stereo pan in [-1.0, 1.0]: -1 = full left, 0 = centre, 1 = full right.
194
+ def pan = C::Audio.sfSound_getPan(@handle)
195
+
196
+ def pan=(v)
197
+ C::Audio.sfSound_setPan(@handle, v.to_f)
198
+ end
199
+
200
+ # Output gain clamping. `min_gain` floors the attenuated
201
+ # gain; `max_gain` caps it. Useful when you want a sound to
202
+ # always be at least faintly audible (or never louder than
203
+ # the listener's local SFX volume).
204
+ def min_gain = C::Audio.sfSound_getMinGain(@handle)
205
+
206
+ def min_gain=(v)
207
+ C::Audio.sfSound_setMinGain(@handle, v.to_f)
208
+ end
209
+
210
+ def max_gain = C::Audio.sfSound_getMaxGain(@handle)
211
+
212
+ def max_gain=(v)
213
+ C::Audio.sfSound_setMaxGain(@handle, v.to_f)
214
+ end
215
+
216
+ # The distance beyond which the source is fully attenuated.
217
+ def max_distance = C::Audio.sfSound_getMaxDistance(@handle)
218
+
219
+ def max_distance=(v)
220
+ C::Audio.sfSound_setMaxDistance(@handle, v.to_f)
221
+ end
222
+
223
+ # Whether 3D-positional / Doppler / cone math is applied at
224
+ # mix time. Off → the sound plays as a simple stereo source.
225
+ def spatialization_enabled? = C::Audio.sfSound_isSpatializationEnabled(@handle)
226
+
227
+ def spatialization_enabled=(v)
228
+ C::Audio.sfSound_setSpatializationEnabled(@handle, v ? true : false)
229
+ end
230
+
231
+ # Multiplier on the Listener's directional attenuation cone.
232
+ # 0 = source ignores the listener's facing direction (no
233
+ # cone falloff); 1 = full cone effect.
234
+ def directional_attenuation_factor
235
+ C::Audio.sfSound_getDirectionalAttenuationFactor(@handle)
236
+ end
237
+
238
+ def directional_attenuation_factor=(v)
239
+ C::Audio.sfSound_setDirectionalAttenuationFactor(@handle, v.to_f)
240
+ end
241
+
242
+ # Independent copy — same buffer (buffers are shareable),
243
+ # independent transport state (volume/pan/spatialisation/etc).
244
+ def dup
245
+ ptr = C::Audio.sfSound_copy(@handle)
246
+ raise Error, "sfSound_copy returned NULL" if ptr.null?
247
+
248
+ copy = self.class.allocate
249
+ copy.instance_variable_set(:@handle,
250
+ FFI::AutoPointer.new(ptr, C::Audio.method(:sfSound_destroy)))
251
+ copy.instance_variable_set(:@buffer, @buffer)
252
+ copy
253
+ end
254
+ alias clone dup
183
255
  end
184
256
  end
@@ -14,9 +14,83 @@ module SFML
14
14
  buf
15
15
  end
16
16
 
17
+ # Decode a Ruby String of bytes (.wav, .ogg, .flac, …)
18
+ # straight into a buffer — bypass the disk.
19
+ def self.from_memory(bytes)
20
+ raise ArgumentError, "expected a String, got #{bytes.class}" unless bytes.is_a?(String)
21
+
22
+ buf_p = FFI::MemoryPointer.new(:uint8, bytes.bytesize)
23
+ buf_p.write_bytes(bytes)
24
+ ptr = C::Audio.sfSoundBuffer_createFromMemory(buf_p, bytes.bytesize)
25
+ raise Error, "sfSoundBuffer_createFromMemory returned NULL — unsupported format?" if ptr.null?
26
+
27
+ buf = allocate
28
+ buf.send(:_take_ownership, ptr)
29
+ buf
30
+ end
31
+
32
+ # Default channel-layout map for the common 1- and 2-channel
33
+ # cases — saves callers from spelling out the SoundChannel
34
+ # enum just to build a mono blip or stereo waveform.
35
+ DEFAULT_CHANNEL_MAPS = {
36
+ 1 => [1], # sfSoundChannelMono
37
+ 2 => [2, 3], # sfSoundChannelFrontLeft, FrontRight
38
+ }.freeze
39
+
40
+ # Build a buffer from raw 16-bit signed samples. `samples` is
41
+ # an Array of Integers in [-32768, 32767]. The caller specifies
42
+ # `sample_rate` (Hz) and `channel_count` (1 = mono, 2 = stereo).
43
+ # `channel_map` is an Array of `sfSoundChannel` enum values; for
44
+ # 1- and 2-channel content the default mono / front-L+R layout
45
+ # is filled in automatically.
46
+ def self.from_samples(samples, sample_rate:, channel_count:, channel_map: nil)
47
+ arr = samples.to_a.map { |s| Integer(s) }
48
+ buf = FFI::MemoryPointer.new(:int16, arr.length)
49
+ buf.write_array_of_int16(arr)
50
+
51
+ map = channel_map || DEFAULT_CHANNEL_MAPS.fetch(channel_count) do
52
+ raise ArgumentError,
53
+ "no default channel_map for #{channel_count} channels — pass `channel_map: [...]` explicitly"
54
+ end
55
+ map_buf = FFI::MemoryPointer.new(:int, map.length)
56
+ map_buf.write_array_of_int(map.map { |v| Integer(v) })
57
+
58
+ ptr = C::Audio.sfSoundBuffer_createFromSamples(buf, arr.length,
59
+ Integer(channel_count),
60
+ Integer(sample_rate),
61
+ map_buf, map.length)
62
+ raise Error, "sfSoundBuffer_createFromSamples returned NULL" if ptr.null?
63
+
64
+ sb = allocate
65
+ sb.send(:_take_ownership, ptr)
66
+ sb
67
+ end
68
+
17
69
  def duration = Time.from_native(C::Audio.sfSoundBuffer_getDuration(@handle))
18
70
  def sample_rate = C::Audio.sfSoundBuffer_getSampleRate(@handle)
19
71
  def channel_count = C::Audio.sfSoundBuffer_getChannelCount(@handle)
72
+ def sample_count = C::Audio.sfSoundBuffer_getSampleCount(@handle)
73
+
74
+ # The decoded 16-bit signed samples as a Ruby Array of
75
+ # Integers. Heavy — copies every sample. For analysis or
76
+ # custom DSP that needs the raw waveform.
77
+ def samples
78
+ ptr = C::Audio.sfSoundBuffer_getSamples(@handle)
79
+ return [] if ptr.null?
80
+ ptr.read_array_of_int16(sample_count)
81
+ end
82
+
83
+ # Independent copy — same decoded waveform, different
84
+ # underlying memory block.
85
+ def dup
86
+ ptr = C::Audio.sfSoundBuffer_copy(@handle)
87
+ raise Error, "sfSoundBuffer_copy returned NULL" if ptr.null?
88
+
89
+ copy = self.class.allocate
90
+ copy.send(:_take_ownership, ptr)
91
+ copy
92
+ end
93
+ alias clone dup
20
94
 
21
95
  # Write the buffer out to disk. Format is inferred from the file
22
96
  # extension (.wav / .ogg / .flac, depends on what CSFML was built
data/lib/sfml/c/audio.rb CHANGED
@@ -26,10 +26,31 @@ module SFML
26
26
  attach_function :sfSoundBuffer_getDuration, [:sound_buffer_t], System::Time.by_value
27
27
  attach_function :sfSoundBuffer_getSampleRate, [:sound_buffer_t], :uint32
28
28
  attach_function :sfSoundBuffer_getChannelCount,[:sound_buffer_t], :uint32
29
+ attach_function :sfSoundBuffer_copy, [:sound_buffer_t], :sound_buffer_t
30
+ attach_function :sfSoundBuffer_createFromMemory, [:pointer, :size_t], :sound_buffer_t
31
+ attach_function :sfSoundBuffer_createFromSamples,
32
+ [:pointer, :uint64, :uint32, :uint32, :pointer, :size_t], :sound_buffer_t
33
+ attach_function :sfSoundBuffer_getSampleCount, [:sound_buffer_t], :uint64
34
+ attach_function :sfSoundBuffer_getSamples, [:sound_buffer_t], :pointer
35
+ attach_function :sfSoundBuffer_getChannelMap, [:sound_buffer_t, :pointer], :pointer
29
36
 
30
37
  # ---- Sound ----
31
38
  attach_function :sfSound_create, [:sound_buffer_t], :sound_t
39
+ attach_function :sfSound_copy, [:sound_t], :sound_t
32
40
  attach_function :sfSound_destroy, [:sound_t], :void
41
+ attach_function :sfSound_getBuffer, [:sound_t], :sound_buffer_t
42
+ attach_function :sfSound_setPan, [:sound_t, :float], :void
43
+ attach_function :sfSound_getPan, [:sound_t], :float
44
+ attach_function :sfSound_setMinGain, [:sound_t, :float], :void
45
+ attach_function :sfSound_getMinGain, [:sound_t], :float
46
+ attach_function :sfSound_setMaxGain, [:sound_t, :float], :void
47
+ attach_function :sfSound_getMaxGain, [:sound_t], :float
48
+ attach_function :sfSound_setMaxDistance, [:sound_t, :float], :void
49
+ attach_function :sfSound_getMaxDistance, [:sound_t], :float
50
+ attach_function :sfSound_setSpatializationEnabled, [:sound_t, :bool], :void
51
+ attach_function :sfSound_isSpatializationEnabled, [:sound_t], :bool
52
+ attach_function :sfSound_setDirectionalAttenuationFactor, [:sound_t, :float], :void
53
+ attach_function :sfSound_getDirectionalAttenuationFactor, [:sound_t], :float
33
54
  attach_function :sfSound_play, [:sound_t], :void
34
55
  attach_function :sfSound_pause, [:sound_t], :void
35
56
  attach_function :sfSound_stop, [:sound_t], :void
@@ -66,8 +87,31 @@ module SFML
66
87
  attach_function :sfSound_setEffectProcessor, [:sound_t, :sf_effect_processor, :pointer], :void
67
88
 
68
89
  # ---- Music ----
90
+ class TimeSpan < FFI::Struct
91
+ layout :offset, System::Time,
92
+ :length, System::Time
93
+ end
94
+
69
95
  attach_function :sfMusic_createFromFile, [:string], :music_t
96
+ attach_function :sfMusic_createFromMemory, [:pointer, :size_t], :music_t
70
97
  attach_function :sfMusic_destroy, [:music_t], :void
98
+ attach_function :sfMusic_getChannelCount, [:music_t], :uint32
99
+ attach_function :sfMusic_getSampleRate, [:music_t], :uint32
100
+ attach_function :sfMusic_getChannelMap, [:music_t, :pointer], :pointer
101
+ attach_function :sfMusic_getLoopPoints, [:music_t], TimeSpan.by_value
102
+ attach_function :sfMusic_setLoopPoints, [:music_t, TimeSpan.by_value], :void
103
+ attach_function :sfMusic_setPan, [:music_t, :float], :void
104
+ attach_function :sfMusic_getPan, [:music_t], :float
105
+ attach_function :sfMusic_setMinGain, [:music_t, :float], :void
106
+ attach_function :sfMusic_getMinGain, [:music_t], :float
107
+ attach_function :sfMusic_setMaxGain, [:music_t, :float], :void
108
+ attach_function :sfMusic_getMaxGain, [:music_t], :float
109
+ attach_function :sfMusic_setMaxDistance, [:music_t, :float], :void
110
+ attach_function :sfMusic_getMaxDistance, [:music_t], :float
111
+ attach_function :sfMusic_setSpatializationEnabled, [:music_t, :bool], :void
112
+ attach_function :sfMusic_isSpatializationEnabled, [:music_t], :bool
113
+ attach_function :sfMusic_setDirectionalAttenuationFactor, [:music_t, :float], :void
114
+ attach_function :sfMusic_getDirectionalAttenuationFactor, [:music_t], :float
71
115
  attach_function :sfMusic_play, [:music_t], :void
72
116
  attach_function :sfMusic_pause, [:music_t], :void
73
117
  attach_function :sfMusic_stop, [:music_t], :void
@@ -121,6 +121,8 @@ module SFML
121
121
  attach_function :sfRenderWindow_resetGLStates, [:render_window_t], :void
122
122
  attach_function :sfRenderWindow_isSrgb, [:render_window_t], :bool
123
123
  attach_function :sfRenderWindow_waitEvent, [:render_window_t, System::Time.by_value, :pointer], :bool
124
+ attach_function :sfRenderWindow_getScissor, [:render_window_t, :pointer], IntRect.by_value
125
+ attach_function :sfRenderWindow_getViewport, [:render_window_t, :pointer], IntRect.by_value
124
126
 
125
127
  typedef :pointer, :texture_t
126
128
  typedef :pointer, :render_texture_t
@@ -197,6 +199,8 @@ module SFML
197
199
  attach_function :sfView_getRotation, [:view_t], :float
198
200
  attach_function :sfView_setViewport, [:view_t, FloatRect.by_value], :void
199
201
  attach_function :sfView_getViewport, [:view_t], FloatRect.by_value
202
+ attach_function :sfView_setScissor, [:view_t, FloatRect.by_value], :void
203
+ attach_function :sfView_getScissor, [:view_t], FloatRect.by_value
200
204
  attach_function :sfView_move, [:view_t, System::Vector2f.by_value], :void
201
205
  attach_function :sfView_rotate, [:view_t, :float], :void
202
206
  attach_function :sfView_zoom, [:view_t, :float], :void
@@ -221,7 +225,17 @@ module SFML
221
225
  attach_function :sfTexture_create, [System::Vector2u.by_value], :texture_t
222
226
  attach_function :sfTexture_createFromFile, [:string, :pointer], :texture_t
223
227
  attach_function :sfTexture_createFromImage,[:image_t, :pointer], :texture_t
228
+ attach_function :sfTexture_createFromMemory,[:pointer, :size_t, :pointer], :texture_t
224
229
  attach_function :sfTexture_copy, [:texture_t], :texture_t
230
+ attach_function :sfTexture_resize, [:texture_t, System::Vector2u.by_value], :bool
231
+ attach_function :sfTexture_swap, [:texture_t, :texture_t], :void
232
+ attach_function :sfTexture_getNativeHandle,[:texture_t], :uint
233
+ attach_function :sfTexture_updateFromTexture,
234
+ [:texture_t, :texture_t, System::Vector2u.by_value], :void
235
+ attach_function :sfTexture_updateFromRenderWindow,
236
+ [:texture_t, :render_window_t, System::Vector2u.by_value], :void
237
+ attach_function :sfTexture_updateFromWindow,
238
+ [:texture_t, :pointer, System::Vector2u.by_value], :void
225
239
  attach_function :sfTexture_isSrgb, [:texture_t], :bool
226
240
  attach_function :sfTexture_generateMipmap, [:texture_t], :bool
227
241
  attach_function :sfTexture_getMaximumSize, [], :uint
@@ -275,6 +289,10 @@ module SFML
275
289
  attach_function :sfSprite_setColor, [:sprite_t, Color.by_value], :void
276
290
  attach_function :sfSprite_getColor, [:sprite_t], Color.by_value
277
291
  attach_function :sfSprite_setTexture, [:sprite_t, :texture_t, :bool], :void
292
+ attach_function :sfSprite_getTexture, [:sprite_t], :texture_t
293
+ attach_function :sfSprite_copy, [:sprite_t], :sprite_t
294
+ attach_function :sfSprite_getTransform, [:sprite_t], Transform.by_value
295
+ attach_function :sfSprite_getInverseTransform, [:sprite_t], Transform.by_value
278
296
 
279
297
  # ---- CircleShape ----
280
298
  attach_function :sfCircleShape_create, [], :circle_shape_t
@@ -385,6 +403,8 @@ module SFML
385
403
  attach_function :sfRenderTexture_pushGLStates, [:render_texture_t], :void
386
404
  attach_function :sfRenderTexture_popGLStates, [:render_texture_t], :void
387
405
  attach_function :sfRenderTexture_resetGLStates, [:render_texture_t], :void
406
+ attach_function :sfRenderTexture_getScissor, [:render_texture_t, :view_t], IntRect.by_value
407
+ attach_function :sfRenderTexture_getViewport, [:render_texture_t, :view_t], IntRect.by_value
388
408
 
389
409
  attach_function :sfRenderTexture_mapPixelToCoords,
390
410
  [:render_texture_t, System::Vector2i.by_value, :view_t],
@@ -417,6 +437,7 @@ module SFML
417
437
  attach_function :sfShader_isAvailable, [], :bool
418
438
  attach_function :sfShader_bind, [:shader_t], :void
419
439
  attach_function :sfShader_getNativeHandle, [:shader_t], :uint
440
+ attach_function :sfShader_setIntColorUniform, [:shader_t, :string, Color.by_value], :void
420
441
  attach_function :sfShader_isGeometryAvailable, [], :bool
421
442
 
422
443
  attach_function :sfShader_setFloatUniform, [:shader_t, :string, :float], :void
@@ -78,6 +78,18 @@ module SFML
78
78
  def pop_gl_states = C::Graphics.sfRenderTexture_popGLStates(@handle)
79
79
  def reset_gl_states = C::Graphics.sfRenderTexture_resetGLStates(@handle)
80
80
 
81
+ # Pixel-space viewport / scissor for the given view (defaults
82
+ # to the active view). Same shape as RenderWindow's.
83
+ def viewport(view = self.view)
84
+ raise ArgumentError, "expected a SFML::View" unless view.is_a?(View)
85
+ Rect.from_native(C::Graphics.sfRenderTexture_getViewport(@handle, view.handle))
86
+ end
87
+
88
+ def scissor(view = self.view)
89
+ raise ArgumentError, "expected a SFML::View" unless view.is_a?(View)
90
+ Rect.from_native(C::Graphics.sfRenderTexture_getScissor(@handle, view.handle))
91
+ end
92
+
81
93
  # The Texture this RenderTexture is rendering into. Borrowed — its
82
94
  # lifetime is bounded by `self`. Memoised so repeated calls return
83
95
  # the same Ruby wrapper.
@@ -224,6 +224,22 @@ module SFML
224
224
  Event.from_native(@event_buffer)
225
225
  end
226
226
 
227
+ # The pixel-space rect a `view` projects onto inside this
228
+ # window. Combines the view's normalised viewport with the
229
+ # window's pixel size.
230
+ def viewport(view = self.view)
231
+ raise ArgumentError, "expected a SFML::View" unless view.is_a?(View)
232
+ Rect.from_native(C::Graphics.sfRenderWindow_getViewport(@handle, view.handle))
233
+ end
234
+
235
+ # The pixel-space scissor rect — same idea as `viewport` but
236
+ # for the view's `scissor` property. Pixels outside this rect
237
+ # are clipped before rendering.
238
+ def scissor(view = self.view)
239
+ raise ArgumentError, "expected a SFML::View" unless view.is_a?(View)
240
+ Rect.from_native(C::Graphics.sfRenderWindow_getScissor(@handle, view.handle))
241
+ end
242
+
227
243
  # Wrap an existing OS-level window. `handle` is a platform native
228
244
  # handle (Integer address or FFI::Pointer). Useful for embedding
229
245
  # the renderer inside another framework's window (Qt, Gtk, raw
@@ -152,6 +152,15 @@ module SFML
152
152
  # with raw GL libraries.
153
153
  def native_handle = C::Graphics.sfShader_getNativeHandle(@handle)
154
154
 
155
+ # Set a `vec4` uniform from an integer-channel `SFML::Color`
156
+ # (RGBA 0–255). Equivalent to writing `[c.r/255, ..., c.a/255]`
157
+ # by hand into a vec4 — the CSFML helper does the divide for
158
+ # you.
159
+ def set_int_color(name, color)
160
+ raise ArgumentError, "expected SFML::Color" unless color.is_a?(Color)
161
+ C::Graphics.sfShader_setIntColorUniform(@handle, name.to_s, color.to_native)
162
+ end
163
+
155
164
  attr_reader :handle # :nodoc:
156
165
 
157
166
  private
@@ -70,6 +70,38 @@ module SFML
70
70
  target._draw_native(:Sprite, @handle, states_ptr)
71
71
  end
72
72
 
73
+ # The Texture this sprite was last bound to. Borrowed — caller
74
+ # is responsible for keeping the source texture alive.
75
+ def texture
76
+ ptr = C::Graphics.sfSprite_getTexture(@handle)
77
+ return nil if ptr.null?
78
+ Texture.send(:_borrow, ptr)
79
+ end
80
+
81
+ # Combined transform (translation + rotation + scale + origin)
82
+ # the renderer applies when drawing this sprite.
83
+ def transform
84
+ C::Graphics.sfSprite_getTransform(@handle)
85
+ end
86
+
87
+ def inverse_transform
88
+ C::Graphics.sfSprite_getInverseTransform(@handle)
89
+ end
90
+
91
+ # Deep copy — same texture binding (textures are shared GPU
92
+ # objects), independent transform / colour state.
93
+ def dup
94
+ ptr = C::Graphics.sfSprite_copy(@handle)
95
+ raise Error, "sfSprite_copy returned NULL" if ptr.null?
96
+
97
+ copy = self.class.allocate
98
+ copy.instance_variable_set(:@handle,
99
+ FFI::AutoPointer.new(ptr, C::Graphics.method(:sfSprite_destroy)))
100
+ copy.instance_variable_set(:@texture, @texture) # keep source alive
101
+ copy
102
+ end
103
+ alias clone dup
104
+
73
105
  attr_reader :handle # :nodoc:
74
106
  end
75
107
  end
@@ -31,6 +31,24 @@ module SFML
31
31
  tex
32
32
  end
33
33
 
34
+ # Decode + upload a Ruby String of bytes (PNG, JPG, BMP, …) as
35
+ # a texture. Useful for embedded assets / network responses
36
+ # that bypass the disk.
37
+ def self.from_memory(bytes, smooth: false, repeated: false)
38
+ raise ArgumentError, "expected a String, got #{bytes.class}" unless bytes.is_a?(String)
39
+
40
+ buf = FFI::MemoryPointer.new(:uint8, bytes.bytesize)
41
+ buf.write_bytes(bytes)
42
+ ptr = C::Graphics.sfTexture_createFromMemory(buf, bytes.bytesize, nil)
43
+ raise Error, "sfTexture_createFromMemory returned NULL — unsupported format?" if ptr.null?
44
+
45
+ tex = allocate
46
+ tex.send(:_take_ownership, ptr)
47
+ tex.smooth = smooth
48
+ tex.repeated = repeated
49
+ tex
50
+ end
51
+
34
52
  # Upload a CPU-side SFML::Image to the GPU as a new Texture. Keeps
35
53
  # the RGBA byte order and dimensions of the source image.
36
54
  def self.from_image(image, smooth: false, repeated: false)
@@ -123,6 +141,54 @@ module SFML
123
141
  end
124
142
  alias clone dup
125
143
 
144
+ # Reallocate this texture's GPU memory at a new size. Returns
145
+ # `false` if the GPU rejects the size (driver limit / OOM);
146
+ # the texture's contents become undefined on success.
147
+ def resize(width, height)
148
+ size = C::System::Vector2u.new
149
+ size[:x] = Integer(width); size[:y] = Integer(height)
150
+ C::Graphics.sfTexture_resize(@handle, size)
151
+ end
152
+
153
+ # Atomically swap the GPU memory between two textures —
154
+ # cheaper than `dup` + reassign for double-buffer-style
155
+ # patterns (paint-buffer ⇄ visible-buffer).
156
+ def swap(other)
157
+ raise ArgumentError, "Texture#swap needs a Texture" unless other.is_a?(Texture)
158
+ C::Graphics.sfTexture_swap(@handle, other.handle)
159
+ self
160
+ end
161
+
162
+ # The OpenGL texture-object name (a `glGenTextures` ID).
163
+ # Useful when feeding this texture into raw GL calls.
164
+ def native_handle = C::Graphics.sfTexture_getNativeHandle(@handle)
165
+
166
+ # Upload the contents of another texture into this one at
167
+ # `offset` (`[x, y]`). Both textures must remain alive for the
168
+ # duration of the call.
169
+ def update_from_texture(source, offset: [0, 0])
170
+ raise ArgumentError, "expected a Texture" unless source.is_a?(Texture)
171
+ C::Graphics.sfTexture_updateFromTexture(@handle, source.handle, _vec2u(offset))
172
+ self
173
+ end
174
+
175
+ # Read the back-buffer of a `RenderWindow` into this texture
176
+ # at `offset`. Useful for capturing the rendered scene
177
+ # without re-drawing into a separate `RenderTexture`.
178
+ def update_from_render_window(window, offset: [0, 0])
179
+ raise ArgumentError, "expected a RenderWindow" unless window.is_a?(RenderWindow)
180
+ C::Graphics.sfTexture_updateFromRenderWindow(@handle, window.handle, _vec2u(offset))
181
+ self
182
+ end
183
+
184
+ # Same as `update_from_render_window` for the bare `Window`
185
+ # (when you're managing GL yourself).
186
+ def update_from_window(window, offset: [0, 0])
187
+ raise ArgumentError, "expected a Window" unless window.is_a?(SFML::Window)
188
+ C::Graphics.sfTexture_updateFromWindow(@handle, window.handle, _vec2u(offset))
189
+ self
190
+ end
191
+
126
192
  attr_reader :handle # :nodoc:
127
193
 
128
194
  # Internal — borrow a CSFML-owned `sfTexture*` (e.g. one
@@ -140,5 +206,12 @@ module SFML
140
206
  def _take_ownership(ptr)
141
207
  @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfTexture_destroy))
142
208
  end
209
+
210
+ def _vec2u(value)
211
+ v = C::System::Vector2u.new
212
+ x, y = value.is_a?(Vector2) ? [value.x, value.y] : value
213
+ v[:x] = Integer(x); v[:y] = Integer(y)
214
+ v
215
+ end
143
216
  end
144
217
  end
@@ -88,12 +88,20 @@ module SFML
88
88
 
89
89
  def viewport=(rect)
90
90
  raise ArgumentError, "View#viewport= needs a SFML::Rect" unless rect.is_a?(Rect)
91
- native = C::Graphics::FloatRect.new
92
- native[:position][:x] = rect.x.to_f
93
- native[:position][:y] = rect.y.to_f
94
- native[:size][:x] = rect.width.to_f
95
- native[:size][:y] = rect.height.to_f
96
- C::Graphics.sfView_setViewport(@handle, native)
91
+ C::Graphics.sfView_setViewport(@handle, _to_floatrect(rect))
92
+ end
93
+
94
+ # Scissor rect in normalised [0,1] window coords — pixels
95
+ # *outside* this rect are clipped before rendering. Default
96
+ # `[0, 0, 1, 1]` (no clipping). Use it to render UI inside a
97
+ # sub-region of the window without re-projecting.
98
+ def scissor
99
+ Rect.from_native(C::Graphics.sfView_getScissor(@handle))
100
+ end
101
+
102
+ def scissor=(rect)
103
+ raise ArgumentError, "View#scissor= needs a SFML::Rect" unless rect.is_a?(Rect)
104
+ C::Graphics.sfView_setScissor(@handle, _to_floatrect(rect))
97
105
  end
98
106
 
99
107
  # Pan the camera by an offset in world units.
@@ -119,6 +127,15 @@ module SFML
119
127
 
120
128
  private
121
129
 
130
+ def _to_floatrect(rect)
131
+ native = C::Graphics::FloatRect.new
132
+ native[:position][:x] = rect.x.to_f
133
+ native[:position][:y] = rect.y.to_f
134
+ native[:size][:x] = rect.width.to_f
135
+ native[:size][:y] = rect.height.to_f
136
+ native
137
+ end
138
+
122
139
  def _vec2(value)
123
140
  value.is_a?(Vector2) ? value : Vector2.new(*value)
124
141
  end
data/lib/sfml/version.rb CHANGED
@@ -15,5 +15,5 @@ module SFML
15
15
  # "3.0.1.0" — CSFML 3.0.1 ships, we re-cut from upstream
16
16
  # "3.0.1.1" — our patch on top of CSFML 3.0.1
17
17
  # "3.1.0.0" — CSFML 3.1.0 ships, we add new bindings
18
- VERSION = "3.0.0.3"
18
+ VERSION = "3.0.0.4"
19
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-sfml
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.3
4
+ version: 3.0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mykhailo Melnyk