ruby-sfml 3.0.0.0 → 3.0.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.
@@ -0,0 +1,206 @@
1
+ module SFML
2
+ # Procedural audio source. Subclass it and override `#on_get_data`
3
+ # to fill each chunk on demand — CSFML invokes the callback from
4
+ # its audio thread whenever the sound queue runs low.
5
+ #
6
+ # `#on_get_data` returns either:
7
+ # * an Array (or anything responding to `#to_a`) of Int16 PCM
8
+ # samples — they're packed into a buffer and handed to CSFML;
9
+ # * `nil` to stop the stream.
10
+ #
11
+ # Override `#on_seek(time)` to support `playing_offset=`. Default
12
+ # is a no-op (the next callback continues from wherever the
13
+ # subclass internal state happens to be).
14
+ #
15
+ # class SineWave < SFML::SoundStream
16
+ # def initialize(freq:, sample_rate: 44_100)
17
+ # super(channel_count: 1, sample_rate: sample_rate)
18
+ # @freq, @sample_rate = freq, sample_rate
19
+ # @phase = 0.0
20
+ # end
21
+ #
22
+ # def on_get_data
23
+ # n = @sample_rate / 10 # ~100ms chunks
24
+ # step = 2 * Math::PI * @freq / @sample_rate
25
+ # Array.new(n) do
26
+ # s = (Math.sin(@phase) * 30_000).to_i
27
+ # @phase = (@phase + step) % (2 * Math::PI)
28
+ # s
29
+ # end
30
+ # end
31
+ #
32
+ # def on_seek(time)
33
+ # @phase = 0.0
34
+ # end
35
+ # end
36
+ #
37
+ # CAVEATS
38
+ # * The callback runs on the SFML audio thread; doing heavy work
39
+ # there will glitch the audio. Generate samples and return.
40
+ # * Ruby threads still need the GVL — your callback acquires it
41
+ # each invocation. Long Ruby work on the main thread can starve
42
+ # the audio thread and produce dropouts.
43
+ # * Always keep a reference to the SoundStream object (assign to a
44
+ # variable, store in an instance var). If the Ruby object is GC'd
45
+ # while CSFML is still calling callbacks, the process crashes.
46
+ class SoundStream
47
+ DEFAULT_CHUNK_FRAMES = 4096
48
+
49
+ def initialize(channel_count:, sample_rate:)
50
+ raise ArgumentError, "channel_count must be >= 1" if channel_count < 1
51
+ raise ArgumentError, "sample_rate must be > 0" if sample_rate < 1
52
+
53
+ # Hold strong refs so neither GC nor reload disposes the
54
+ # callbacks while CSFML is still calling them.
55
+ @get_data_cb = FFI::Function.new(:bool, [:pointer, :pointer]) do |chunk_ptr, _user|
56
+ _on_get_data_callback(chunk_ptr)
57
+ end
58
+ @seek_cb = FFI::Function.new(:void, [C::System::Time.by_value, :pointer]) do |time, _user|
59
+ on_seek(Time.from_native(time))
60
+ nil
61
+ end
62
+
63
+ ptr = C::Audio.sfSoundStream_create(
64
+ @get_data_cb, @seek_cb,
65
+ Integer(channel_count), Integer(sample_rate),
66
+ nil, 0,
67
+ nil,
68
+ )
69
+ raise Error, "sfSoundStream_create returned NULL" if ptr.null?
70
+
71
+ @handle = FFI::AutoPointer.new(ptr, C::Audio.method(:sfSoundStream_destroy))
72
+
73
+ # We re-use a single MemoryPointer for the sample buffer,
74
+ # growing it on demand. CSFML reads from the pointer between
75
+ # callbacks, then asks for the next chunk — by the time we
76
+ # overwrite, CSFML is done with the previous data.
77
+ @sample_buffer = nil
78
+ @sample_buffer_capacity = 0
79
+ end
80
+
81
+ # ---- Subclass hooks ------------------------------------------------
82
+
83
+ # Return an Array of Int16 PCM samples (interleaved if multi-channel),
84
+ # or `nil` to stop the stream. Default raises so subclasses must
85
+ # implement it.
86
+ def on_get_data
87
+ raise NoMethodError, "#{self.class} must override #on_get_data"
88
+ end
89
+
90
+ # Called when the user changes the playing offset. Default is a
91
+ # no-op — override if your stream tracks position internally
92
+ # (counters, file offsets, etc.).
93
+ def on_seek(_time); end
94
+
95
+ # ---- Public playback API ------------------------------------------
96
+
97
+ def play = (C::Audio.sfSoundStream_play(@handle); self)
98
+ def pause = (C::Audio.sfSoundStream_pause(@handle); self)
99
+ def stop = (C::Audio.sfSoundStream_stop(@handle); self)
100
+
101
+ def status = C::Audio::STATUSES[C::Audio.sfSoundStream_getStatus(@handle)]
102
+ def playing? = status == :playing
103
+ def paused? = status == :paused
104
+ def stopped? = status == :stopped
105
+
106
+ def channel_count = C::Audio.sfSoundStream_getChannelCount(@handle)
107
+ def sample_rate = C::Audio.sfSoundStream_getSampleRate(@handle)
108
+
109
+ # Cached on the Ruby side; some OpenAL backends (notably the
110
+ # headless null sink we get on Linux CI) don't reliably read
111
+ # the loop flag back through CSFML once it's been set.
112
+ def looping?
113
+ @looping == true
114
+ end
115
+
116
+ def looping=(value)
117
+ @looping = !!value
118
+ C::Audio.sfSoundStream_setLooping(@handle, @looping)
119
+ end
120
+
121
+ def volume = C::Audio.sfSoundStream_getVolume(@handle)
122
+
123
+ def volume=(value)
124
+ C::Audio.sfSoundStream_setVolume(@handle, value.to_f)
125
+ end
126
+
127
+ def pitch = C::Audio.sfSoundStream_getPitch(@handle)
128
+
129
+ def pitch=(value)
130
+ C::Audio.sfSoundStream_setPitch(@handle, value.to_f)
131
+ end
132
+
133
+ def playing_offset
134
+ Time.from_native(C::Audio.sfSoundStream_getPlayingOffset(@handle))
135
+ end
136
+
137
+ def playing_offset=(value)
138
+ t = value.is_a?(Time) ? value : Time.seconds(value.to_f)
139
+ C::Audio.sfSoundStream_setPlayingOffset(@handle, t.to_native)
140
+ end
141
+
142
+ def position
143
+ v = C::Audio.sfSoundStream_getPosition(@handle)
144
+ Vector3.new(v[:x], v[:y], v[:z])
145
+ end
146
+
147
+ def position=(value)
148
+ v = value.is_a?(Vector3) ? value : Vector3.new(*value)
149
+ packed = C::System::Vector3f.new
150
+ packed[:x] = v.x.to_f; packed[:y] = v.y.to_f; packed[:z] = v.z.to_f
151
+ C::Audio.sfSoundStream_setPosition(@handle, packed)
152
+ end
153
+
154
+ def attenuation = C::Audio.sfSoundStream_getAttenuation(@handle)
155
+
156
+ def attenuation=(value)
157
+ C::Audio.sfSoundStream_setAttenuation(@handle, value.to_f)
158
+ end
159
+
160
+ def min_distance = C::Audio.sfSoundStream_getMinDistance(@handle)
161
+
162
+ def min_distance=(value)
163
+ C::Audio.sfSoundStream_setMinDistance(@handle, value.to_f)
164
+ end
165
+
166
+ def relative_to_listener? = C::Audio.sfSoundStream_isRelativeToListener(@handle)
167
+
168
+ def relative_to_listener=(value)
169
+ C::Audio.sfSoundStream_setRelativeToListener(@handle, !!value)
170
+ end
171
+
172
+ attr_reader :handle # :nodoc:
173
+
174
+ private
175
+
176
+ # Bridge between CSFML's chunk struct and our user-facing
177
+ # `#on_get_data`. Runs on the audio thread.
178
+ def _on_get_data_callback(chunk_ptr)
179
+ samples = on_get_data
180
+ chunk = C::Audio::SoundStreamChunk.new(chunk_ptr)
181
+
182
+ if samples.nil?
183
+ chunk[:samples] = FFI::Pointer::NULL
184
+ chunk[:sample_count] = 0
185
+ return false
186
+ end
187
+
188
+ arr = samples.respond_to?(:to_a) ? samples.to_a : samples
189
+ n = arr.length
190
+
191
+ _grow_sample_buffer(n)
192
+ @sample_buffer.write_array_of_int16(arr) if n > 0
193
+
194
+ chunk[:samples] = @sample_buffer
195
+ chunk[:sample_count] = n
196
+ true
197
+ end
198
+
199
+ def _grow_sample_buffer(n)
200
+ return if @sample_buffer && @sample_buffer_capacity >= n
201
+
202
+ @sample_buffer_capacity = [n, DEFAULT_CHUNK_FRAMES].max
203
+ @sample_buffer = FFI::MemoryPointer.new(:int16, @sample_buffer_capacity)
204
+ end
205
+ end
206
+ end
data/lib/sfml/c/audio.rb CHANGED
@@ -12,6 +12,13 @@ module SFML
12
12
  # sfSoundStatus = { sfStopped, sfPaused, sfPlaying } in that order.
13
13
  STATUSES = %i[stopped paused playing].freeze
14
14
 
15
+ # Plain-old-data struct that mirrors sfSoundSourceCone.
16
+ class SoundSourceCone < FFI::Struct
17
+ layout :inner_angle, :float,
18
+ :outer_angle, :float,
19
+ :outer_gain, :float
20
+ end
21
+
15
22
  # ---- SoundBuffer ----
16
23
  attach_function :sfSoundBuffer_createFromFile, [:string], :sound_buffer_t
17
24
  attach_function :sfSoundBuffer_destroy, [:sound_buffer_t], :void
@@ -44,6 +51,19 @@ module SFML
44
51
  attach_function :sfSound_getAttenuation, [:sound_t], :float
45
52
  attach_function :sfSound_setRelativeToListener, [:sound_t, :bool], :void
46
53
  attach_function :sfSound_isRelativeToListener, [:sound_t], :bool
54
+ attach_function :sfSound_setPlayingOffset, [:sound_t, System::Time.by_value], :void
55
+ attach_function :sfSound_getPlayingOffset, [:sound_t], System::Time.by_value
56
+ attach_function :sfSound_setVelocity, [:sound_t, System::Vector3f.by_value], :void
57
+ attach_function :sfSound_getVelocity, [:sound_t], System::Vector3f.by_value
58
+ attach_function :sfSound_setDopplerFactor, [:sound_t, :float], :void
59
+ attach_function :sfSound_getDopplerFactor, [:sound_t], :float
60
+ attach_function :sfSound_setDirection, [:sound_t, System::Vector3f.by_value], :void
61
+ attach_function :sfSound_getDirection, [:sound_t], System::Vector3f.by_value
62
+ attach_function :sfSound_setCone, [:sound_t, SoundSourceCone.by_value], :void
63
+ attach_function :sfSound_getCone, [:sound_t], SoundSourceCone.by_value
64
+ # void(*)(const float* in, unsigned int* in_count, float* out, unsigned int* out_count, unsigned int channels, void* userData)
65
+ callback :sf_effect_processor, [:pointer, :pointer, :pointer, :pointer, :uint32, :pointer], :void
66
+ attach_function :sfSound_setEffectProcessor, [:sound_t, :sf_effect_processor, :pointer], :void
47
67
 
48
68
  # ---- Music ----
49
69
  attach_function :sfMusic_createFromFile, [:string], :music_t
@@ -68,6 +88,60 @@ module SFML
68
88
  attach_function :sfMusic_getAttenuation, [:music_t], :float
69
89
  attach_function :sfMusic_setRelativeToListener, [:music_t, :bool], :void
70
90
  attach_function :sfMusic_isRelativeToListener, [:music_t], :bool
91
+ attach_function :sfMusic_setPlayingOffset, [:music_t, System::Time.by_value], :void
92
+ attach_function :sfMusic_getPlayingOffset, [:music_t], System::Time.by_value
93
+ attach_function :sfMusic_setVelocity, [:music_t, System::Vector3f.by_value], :void
94
+ attach_function :sfMusic_getVelocity, [:music_t], System::Vector3f.by_value
95
+ attach_function :sfMusic_setDopplerFactor, [:music_t, :float], :void
96
+ attach_function :sfMusic_getDopplerFactor, [:music_t], :float
97
+ attach_function :sfMusic_setDirection, [:music_t, System::Vector3f.by_value], :void
98
+ attach_function :sfMusic_getDirection, [:music_t], System::Vector3f.by_value
99
+ attach_function :sfMusic_setCone, [:music_t, SoundSourceCone.by_value], :void
100
+ attach_function :sfMusic_getCone, [:music_t], SoundSourceCone.by_value
101
+ attach_function :sfMusic_setEffectProcessor, [:music_t, :sf_effect_processor, :pointer], :void
102
+
103
+ # ---- SoundStream ----
104
+ # Custom audio source — fills a chunk via a Ruby callback that
105
+ # CSFML invokes from the audio thread. Beware: that callback
106
+ # has to acquire the GVL to run any Ruby code, so don't do
107
+ # heavy work there. Generate samples and return.
108
+ typedef :pointer, :sound_stream_t
109
+
110
+ class SoundStreamChunk < FFI::Struct
111
+ layout :samples, :pointer,
112
+ :sample_count, :uint32
113
+ end
114
+
115
+ callback :sound_stream_get_data, [:pointer, :pointer], :bool
116
+ callback :sound_stream_seek, [System::Time.by_value, :pointer], :void
117
+
118
+ attach_function :sfSoundStream_create,
119
+ [:sound_stream_get_data, :sound_stream_seek,
120
+ :uint32, :uint32, :pointer, :size_t, :pointer],
121
+ :sound_stream_t
122
+ attach_function :sfSoundStream_destroy, [:sound_stream_t], :void
123
+ attach_function :sfSoundStream_play, [:sound_stream_t], :void
124
+ attach_function :sfSoundStream_pause, [:sound_stream_t], :void
125
+ attach_function :sfSoundStream_stop, [:sound_stream_t], :void
126
+ attach_function :sfSoundStream_getChannelCount, [:sound_stream_t], :uint32
127
+ attach_function :sfSoundStream_getSampleRate, [:sound_stream_t], :uint32
128
+ attach_function :sfSoundStream_getStatus, [:sound_stream_t], :int
129
+ attach_function :sfSoundStream_setPlayingOffset, [:sound_stream_t, System::Time.by_value], :void
130
+ attach_function :sfSoundStream_getPlayingOffset, [:sound_stream_t], System::Time.by_value
131
+ attach_function :sfSoundStream_setLooping, [:sound_stream_t, :bool], :void
132
+ attach_function :sfSoundStream_isLooping, [:sound_stream_t], :bool
133
+ attach_function :sfSoundStream_setVolume, [:sound_stream_t, :float], :void
134
+ attach_function :sfSoundStream_getVolume, [:sound_stream_t], :float
135
+ attach_function :sfSoundStream_setPitch, [:sound_stream_t, :float], :void
136
+ attach_function :sfSoundStream_getPitch, [:sound_stream_t], :float
137
+ attach_function :sfSoundStream_setPosition, [:sound_stream_t, System::Vector3f.by_value], :void
138
+ attach_function :sfSoundStream_getPosition, [:sound_stream_t], System::Vector3f.by_value
139
+ attach_function :sfSoundStream_setMinDistance, [:sound_stream_t, :float], :void
140
+ attach_function :sfSoundStream_getMinDistance, [:sound_stream_t], :float
141
+ attach_function :sfSoundStream_setAttenuation, [:sound_stream_t, :float], :void
142
+ attach_function :sfSoundStream_getAttenuation, [:sound_stream_t], :float
143
+ attach_function :sfSoundStream_setRelativeToListener, [:sound_stream_t, :bool], :void
144
+ attach_function :sfSoundStream_isRelativeToListener, [:sound_stream_t], :bool
71
145
 
72
146
  # ---- SoundBufferRecorder ----
73
147
  # The simple "record into a SoundBuffer" path. Raw sfSoundRecorder
@@ -101,6 +175,10 @@ module SFML
101
175
  attach_function :sfListener_getDirection, [], System::Vector3f.by_value
102
176
  attach_function :sfListener_setUpVector, [System::Vector3f.by_value], :void
103
177
  attach_function :sfListener_getUpVector, [], System::Vector3f.by_value
178
+ attach_function :sfListener_setVelocity, [System::Vector3f.by_value], :void
179
+ attach_function :sfListener_getVelocity, [], System::Vector3f.by_value
180
+ attach_function :sfListener_setCone, [SoundSourceCone.by_value], :void
181
+ attach_function :sfListener_getCone, [], SoundSourceCone.by_value
104
182
  end
105
183
  end
106
184
  end
@@ -94,8 +94,17 @@ module SFML
94
94
  attach_function :sfRenderWindow_setFramerateLimit, [:render_window_t, :uint32], :void
95
95
  attach_function :sfRenderWindow_display, [:render_window_t], :void
96
96
  attach_function :sfRenderWindow_clear, [:render_window_t, Color.by_value], :void
97
+ attach_function :sfRenderWindow_clearStencil, [:render_window_t, StencilValue.by_value], :void
98
+ attach_function :sfRenderWindow_clearColorAndStencil, [:render_window_t, Color.by_value, StencilValue.by_value], :void
97
99
  attach_function :sfRenderWindow_getSize, [:render_window_t], System::Vector2u.by_value
98
100
  attach_function :sfRenderWindow_setSize, [:render_window_t, System::Vector2u.by_value], :void
101
+ attach_function :sfRenderWindow_setIcon, [:render_window_t, System::Vector2u.by_value, :pointer], :void
102
+
103
+ typedef :pointer, :vertex_buffer_t
104
+ attach_function :sfRenderWindow_setMinimumSize, [:render_window_t, :pointer], :void
105
+ attach_function :sfRenderWindow_setMaximumSize, [:render_window_t, :pointer], :void
106
+ attach_function :sfRenderWindow_createFromHandle, [:pointer, :pointer], :render_window_t
107
+ attach_function :sfRenderWindow_getNativeHandle, [:render_window_t], :pointer
99
108
 
100
109
  typedef :pointer, :texture_t
101
110
  typedef :pointer, :render_texture_t
@@ -144,6 +153,10 @@ module SFML
144
153
  [:render_window_t, :text_t, :render_states_t], :void
145
154
  attach_function :sfRenderWindow_drawVertexArray,
146
155
  [:render_window_t, :vertex_array_t, :render_states_t], :void
156
+ attach_function :sfRenderWindow_drawVertexBuffer,
157
+ [:render_window_t, :vertex_buffer_t, :render_states_t], :void
158
+ attach_function :sfRenderWindow_drawVertexBufferRange,
159
+ [:render_window_t, :vertex_buffer_t, :uint32, :uint32, :render_states_t], :void
147
160
 
148
161
  # Mouse position queries relative to a render-window.
149
162
  attach_function :sfMouse_getPositionRenderWindow,
@@ -151,6 +164,10 @@ module SFML
151
164
  attach_function :sfMouse_setPositionRenderWindow,
152
165
  [System::Vector2i.by_value, :render_window_t], :void
153
166
 
167
+ # Touch position relative to a render-window.
168
+ attach_function :sfTouch_getPositionRenderWindow,
169
+ [:uint32, :render_window_t], System::Vector2i.by_value
170
+
154
171
  # ---- View ----
155
172
  attach_function :sfView_create, [], :view_t
156
173
  attach_function :sfView_createFromRect, [FloatRect.by_value], :view_t
@@ -206,6 +223,7 @@ module SFML
206
223
  attach_function :sfImage_copy, [:image_t], :image_t
207
224
  attach_function :sfImage_destroy, [:image_t], :void
208
225
  attach_function :sfImage_saveToFile, [:image_t, :string], :bool
226
+ attach_function :sfImage_saveToMemory, [:image_t, :pointer, :string], :bool
209
227
  attach_function :sfImage_getSize, [:image_t], System::Vector2u.by_value
210
228
  attach_function :sfImage_setPixel, [:image_t, System::Vector2u.by_value, Color.by_value], :void
211
229
  attach_function :sfImage_getPixel, [:image_t, System::Vector2u.by_value], Color.by_value
@@ -324,6 +342,9 @@ module SFML
324
342
  attach_function :sfRenderTexture_setActive, [:render_texture_t, :bool], :bool
325
343
  attach_function :sfRenderTexture_display, [:render_texture_t], :void
326
344
  attach_function :sfRenderTexture_clear, [:render_texture_t, Color.by_value], :void
345
+ attach_function :sfRenderTexture_clearStencil, [:render_texture_t, StencilValue.by_value], :void
346
+ attach_function :sfRenderTexture_clearColorAndStencil,
347
+ [:render_texture_t, Color.by_value, StencilValue.by_value], :void
327
348
  attach_function :sfRenderTexture_setView, [:render_texture_t, :view_t], :void
328
349
  attach_function :sfRenderTexture_getView, [:render_texture_t], :view_t
329
350
  attach_function :sfRenderTexture_getDefaultView, [:render_texture_t], :view_t
@@ -347,6 +368,9 @@ module SFML
347
368
  attach_function :sfRenderTexture_drawConvexShape, [:render_texture_t, :convex_shape_t, :render_states_t], :void
348
369
  attach_function :sfRenderTexture_drawText, [:render_texture_t, :text_t, :render_states_t], :void
349
370
  attach_function :sfRenderTexture_drawVertexArray, [:render_texture_t, :vertex_array_t, :render_states_t], :void
371
+ attach_function :sfRenderTexture_drawVertexBuffer, [:render_texture_t, :vertex_buffer_t, :render_states_t], :void
372
+ attach_function :sfRenderTexture_drawVertexBufferRange,
373
+ [:render_texture_t, :vertex_buffer_t, :uint32, :uint32, :render_states_t], :void
350
374
 
351
375
  # ---- Raw primitive drawing (without a VertexArray object) ----
352
376
  attach_function :sfRenderWindow_drawPrimitives,
@@ -374,6 +398,33 @@ module SFML
374
398
  attach_function :sfShader_setTextureUniform,[:shader_t, :string, :texture_t], :void
375
399
  attach_function :sfShader_setCurrentTextureUniform, [:shader_t, :string], :void
376
400
 
401
+ # ---- VertexBuffer (static GPU vertex buffer) ----
402
+ # USAGE order matches sfVertexBufferUsage in CSFML.
403
+ VERTEX_BUFFER_USAGES = %i[stream dynamic static].freeze
404
+
405
+ attach_function :sfVertexBuffer_create, [:uint32, :int, :int], :vertex_buffer_t
406
+ attach_function :sfVertexBuffer_copy, [:vertex_buffer_t], :vertex_buffer_t
407
+ attach_function :sfVertexBuffer_destroy, [:vertex_buffer_t], :void
408
+ attach_function :sfVertexBuffer_getVertexCount, [:vertex_buffer_t], :uint32
409
+ attach_function :sfVertexBuffer_update, [:vertex_buffer_t, :pointer, :uint32, :uint32], :bool
410
+ attach_function :sfVertexBuffer_updateFromVertexBuffer,
411
+ [:vertex_buffer_t, :vertex_buffer_t], :bool
412
+ attach_function :sfVertexBuffer_swap, [:vertex_buffer_t, :vertex_buffer_t], :void
413
+ attach_function :sfVertexBuffer_getNativeHandle,[:vertex_buffer_t], :uint32
414
+ attach_function :sfVertexBuffer_setPrimitiveType,[:vertex_buffer_t, :int], :void
415
+ attach_function :sfVertexBuffer_getPrimitiveType,[:vertex_buffer_t], :int
416
+ attach_function :sfVertexBuffer_setUsage, [:vertex_buffer_t, :int], :void
417
+ attach_function :sfVertexBuffer_getUsage, [:vertex_buffer_t], :int
418
+ attach_function :sfVertexBuffer_isAvailable, [], :bool
419
+
420
+ # Bulk array uniform setters. The array argument is a packed
421
+ # buffer of N elements (N×{1,2,3,4} floats); the length argument
422
+ # is the *element* count, not the float count.
423
+ attach_function :sfShader_setFloatUniformArray, [:shader_t, :string, :pointer, :size_t], :void
424
+ attach_function :sfShader_setVec2UniformArray, [:shader_t, :string, :pointer, :size_t], :void
425
+ attach_function :sfShader_setVec3UniformArray, [:shader_t, :string, :pointer, :size_t], :void
426
+ attach_function :sfShader_setVec4UniformArray, [:shader_t, :string, :pointer, :size_t], :void
427
+
377
428
  # ---- Font ----
378
429
  attach_function :sfFont_createFromFile, [:string], :font_t
379
430
  attach_function :sfFont_destroy, [:font_t], :void
@@ -74,6 +74,110 @@ module SFML
74
74
  attach_function :sfPacket_append, [:packet_t, :pointer, :size_t], :void
75
75
  attach_function :sfPacket_clear, [:packet_t], :void
76
76
  attach_function :sfPacket_getData, [:packet_t], :pointer
77
+
78
+ # ---- HTTP ----
79
+ typedef :pointer, :http_t
80
+ typedef :pointer, :http_request_t
81
+ typedef :pointer, :http_response_t
82
+
83
+ # Order matches sfHttpMethod in CSFML 3.
84
+ HTTP_METHODS = %i[get post head put delete].freeze
85
+
86
+ # CSFML returns sfHttpStatus as an int. Most are HTTP status
87
+ # codes; the four sfInvalid*/sfConnectionFailed are SFML-side
88
+ # transport errors above the HTTP status range.
89
+ attach_function :sfHttpRequest_create, [], :http_request_t
90
+ attach_function :sfHttpRequest_destroy, [:http_request_t], :void
91
+ attach_function :sfHttpRequest_setField, [:http_request_t, :string, :string], :void
92
+ attach_function :sfHttpRequest_setMethod, [:http_request_t, :int], :void
93
+ attach_function :sfHttpRequest_setUri, [:http_request_t, :string], :void
94
+ attach_function :sfHttpRequest_setHttpVersion, [:http_request_t, :uint32, :uint32], :void
95
+ attach_function :sfHttpRequest_setBody, [:http_request_t, :string], :void
96
+
97
+ attach_function :sfHttpResponse_destroy, [:http_response_t], :void
98
+ attach_function :sfHttpResponse_getField, [:http_response_t, :string], :string
99
+ attach_function :sfHttpResponse_getStatus, [:http_response_t], :int
100
+ attach_function :sfHttpResponse_getMajorVersion, [:http_response_t], :uint32
101
+ attach_function :sfHttpResponse_getMinorVersion, [:http_response_t], :uint32
102
+ attach_function :sfHttpResponse_getBody, [:http_response_t], :string
103
+
104
+ attach_function :sfHttp_create, [], :http_t
105
+ attach_function :sfHttp_destroy, [:http_t], :void
106
+ attach_function :sfHttp_setHost, [:http_t, :string, :uint16], :void
107
+ # The actual network round-trip — release the GVL so other Ruby
108
+ # threads (timers, audio, even an in-process test server) can run
109
+ # while CSFML is blocked on the socket.
110
+ attach_function :sfHttp_sendRequest,
111
+ [:http_t, :http_request_t, System::Time.by_value],
112
+ :http_response_t,
113
+ blocking: true
114
+
115
+ # ---- SocketSelector ----
116
+ typedef :pointer, :socket_selector_t
117
+
118
+ attach_function :sfSocketSelector_create, [], :socket_selector_t
119
+ attach_function :sfSocketSelector_copy, [:socket_selector_t], :socket_selector_t
120
+ attach_function :sfSocketSelector_destroy, [:socket_selector_t], :void
121
+ attach_function :sfSocketSelector_addTcpListener, [:socket_selector_t, :tcp_listener_t], :void
122
+ attach_function :sfSocketSelector_addTcpSocket, [:socket_selector_t, :tcp_socket_t], :void
123
+ attach_function :sfSocketSelector_addUdpSocket, [:socket_selector_t, :udp_socket_t], :void
124
+ attach_function :sfSocketSelector_removeTcpListener, [:socket_selector_t, :tcp_listener_t], :void
125
+ attach_function :sfSocketSelector_removeTcpSocket, [:socket_selector_t, :tcp_socket_t], :void
126
+ attach_function :sfSocketSelector_removeUdpSocket, [:socket_selector_t, :udp_socket_t], :void
127
+ attach_function :sfSocketSelector_clear, [:socket_selector_t], :void
128
+ attach_function :sfSocketSelector_wait,
129
+ [:socket_selector_t, System::Time.by_value], :bool, blocking: true
130
+ attach_function :sfSocketSelector_isTcpListenerReady, [:socket_selector_t, :tcp_listener_t], :bool
131
+ attach_function :sfSocketSelector_isTcpSocketReady, [:socket_selector_t, :tcp_socket_t], :bool
132
+ attach_function :sfSocketSelector_isUdpSocketReady, [:socket_selector_t, :udp_socket_t], :bool
133
+
134
+ # ---- FTP ----
135
+ typedef :pointer, :ftp_t
136
+ typedef :pointer, :ftp_response_t
137
+ typedef :pointer, :ftp_dir_response_t
138
+ typedef :pointer, :ftp_listing_response_t
139
+
140
+ # Order matches sfFtpTransferMode.
141
+ FTP_TRANSFER_MODES = %i[binary ascii ebcdic].freeze
142
+
143
+ attach_function :sfFtpResponse_destroy, [:ftp_response_t], :void
144
+ attach_function :sfFtpResponse_isOk, [:ftp_response_t], :bool
145
+ attach_function :sfFtpResponse_getStatus, [:ftp_response_t], :int
146
+ attach_function :sfFtpResponse_getMessage, [:ftp_response_t], :string
147
+
148
+ attach_function :sfFtpDirectoryResponse_destroy, [:ftp_dir_response_t], :void
149
+ attach_function :sfFtpDirectoryResponse_isOk, [:ftp_dir_response_t], :bool
150
+ attach_function :sfFtpDirectoryResponse_getStatus, [:ftp_dir_response_t], :int
151
+ attach_function :sfFtpDirectoryResponse_getMessage, [:ftp_dir_response_t], :string
152
+ attach_function :sfFtpDirectoryResponse_getDirectory, [:ftp_dir_response_t], :string
153
+
154
+ attach_function :sfFtpListingResponse_destroy, [:ftp_listing_response_t], :void
155
+ attach_function :sfFtpListingResponse_isOk, [:ftp_listing_response_t], :bool
156
+ attach_function :sfFtpListingResponse_getStatus, [:ftp_listing_response_t], :int
157
+ attach_function :sfFtpListingResponse_getMessage, [:ftp_listing_response_t], :string
158
+ attach_function :sfFtpListingResponse_getCount, [:ftp_listing_response_t], :size_t
159
+ attach_function :sfFtpListingResponse_getName, [:ftp_listing_response_t, :size_t], :string
160
+
161
+ attach_function :sfFtp_create, [], :ftp_t
162
+ attach_function :sfFtp_destroy, [:ftp_t], :void
163
+ attach_function :sfFtp_connect,
164
+ [:ftp_t, IpAddress.by_value, :uint16, System::Time.by_value],
165
+ :ftp_response_t, blocking: true
166
+ attach_function :sfFtp_loginAnonymous, [:ftp_t], :ftp_response_t, blocking: true
167
+ attach_function :sfFtp_login, [:ftp_t, :string, :string], :ftp_response_t, blocking: true
168
+ attach_function :sfFtp_disconnect, [:ftp_t], :ftp_response_t, blocking: true
169
+ attach_function :sfFtp_keepAlive, [:ftp_t], :ftp_response_t, blocking: true
170
+ attach_function :sfFtp_getWorkingDirectory, [:ftp_t], :ftp_dir_response_t, blocking: true
171
+ attach_function :sfFtp_getDirectoryListing, [:ftp_t, :string], :ftp_listing_response_t, blocking: true
172
+ attach_function :sfFtp_changeDirectory, [:ftp_t, :string], :ftp_response_t, blocking: true
173
+ attach_function :sfFtp_parentDirectory, [:ftp_t], :ftp_response_t, blocking: true
174
+ attach_function :sfFtp_createDirectory, [:ftp_t, :string], :ftp_dir_response_t, blocking: true
175
+ attach_function :sfFtp_deleteDirectory, [:ftp_t, :string], :ftp_response_t, blocking: true
176
+ attach_function :sfFtp_renameFile, [:ftp_t, :string, :string], :ftp_response_t, blocking: true
177
+ attach_function :sfFtp_deleteFile, [:ftp_t, :string], :ftp_response_t, blocking: true
178
+ attach_function :sfFtp_download, [:ftp_t, :string, :string, :int], :ftp_response_t, blocking: true
179
+ attach_function :sfFtp_upload, [:ftp_t, :string, :string, :int, :bool], :ftp_response_t, blocking: true
180
+ attach_function :sfFtp_sendCommand, [:ftp_t, :string, :string], :ftp_response_t, blocking: true
77
181
  end
78
182
  end
79
183
  end
data/lib/sfml/c/system.rb CHANGED
@@ -38,6 +38,16 @@ module SFML
38
38
  attach_function :sfClock_reset, [:clock_t], Time.by_value
39
39
 
40
40
  attach_function :sfSleep, [Time.by_value], :void
41
+
42
+ # sfBuffer — opaque growable byte buffer used by APIs like
43
+ # sfImage_saveToMemory to return variable-length binary data
44
+ # without forcing the caller to pre-size an output buffer.
45
+ typedef :pointer, :buffer_t
46
+
47
+ attach_function :sfBuffer_create, [], :buffer_t
48
+ attach_function :sfBuffer_destroy, [:buffer_t], :void
49
+ attach_function :sfBuffer_getSize, [:buffer_t], :size_t
50
+ attach_function :sfBuffer_getData, [:buffer_t], :pointer
41
51
  end
42
52
  end
43
53
  end
data/lib/sfml/c/window.rb CHANGED
@@ -104,6 +104,18 @@ module SFML
104
104
  :joystick_id, :uint32
105
105
  end
106
106
 
107
+ class TouchEvent < FFI::Struct
108
+ layout :type, :int,
109
+ :finger, :uint32,
110
+ :position, System::Vector2i
111
+ end
112
+
113
+ class SensorEvent < FFI::Struct
114
+ layout :type, :int,
115
+ :sensor, :int,
116
+ :value, System::Vector3f
117
+ end
118
+
107
119
  # sfEvent is a C union. The largest variant (KeyEvent on x86_64: 4+4+4+4
108
120
  # = 20 bytes) defines the union size; we allocate a buffer that big and
109
121
  # reinterpret per-type. We use a Struct (not Union) here because Ruby
@@ -156,6 +168,25 @@ module SFML
156
168
  attach_function :sfJoystick_getIdentification, [:uint32], JoystickIdentification.by_value
157
169
  attach_function :sfJoystick_update, [], :void
158
170
 
171
+ # ---- Touch ----
172
+ attach_function :sfTouch_isDown, [:uint32], :bool
173
+ attach_function :sfTouch_getPosition, [:uint32, :pointer], System::Vector2i.by_value
174
+
175
+ # ---- Sensor ----
176
+ # Order matches sfSensorType in CSFML 3.
177
+ SENSOR_TYPES = %i[
178
+ accelerometer
179
+ gyroscope
180
+ magnetometer
181
+ gravity
182
+ user_acceleration
183
+ orientation
184
+ ].freeze
185
+
186
+ attach_function :sfSensor_isAvailable, [:int], :bool
187
+ attach_function :sfSensor_setEnabled, [:int, :bool], :void
188
+ attach_function :sfSensor_getValue, [:int], System::Vector3f.by_value
189
+
159
190
  # ---- Bare Window (no rendering) ----
160
191
  # SFML 3 splits sf::Window (pure window + GL context) from
161
192
  # sf::RenderWindow (window + 2D batcher). The bare variant is
@@ -181,6 +212,17 @@ module SFML
181
212
  attach_function :sfWindow_requestFocus, [:raw_window_t], :void
182
213
  attach_function :sfWindow_hasFocus, [:raw_window_t], :bool
183
214
  attach_function :sfWindow_setActive, [:raw_window_t, :bool], :bool
215
+ attach_function :sfWindow_setIcon, [:raw_window_t, System::Vector2u.by_value, :pointer], :void
216
+ # NULL pointer clears the limit. Pass a Vector2u* to set it.
217
+ attach_function :sfWindow_setMinimumSize, [:raw_window_t, :pointer], :void
218
+ attach_function :sfWindow_setMaximumSize, [:raw_window_t, :pointer], :void
219
+
220
+ # Embed into an existing platform window. The first argument is
221
+ # an OS-specific native handle (HWND on Windows, NSView* on
222
+ # macOS, Window XID on X11). Pass NULL for the ContextSettings
223
+ # to use defaults.
224
+ attach_function :sfWindow_createFromHandle, [:pointer, :pointer], :raw_window_t
225
+ attach_function :sfWindow_getNativeHandle, [:raw_window_t], :pointer
184
226
  end
185
227
  end
186
228
  end
@@ -96,6 +96,29 @@ module SFML
96
96
  path
97
97
  end
98
98
 
99
+ # Encode the image as `format` ("png", "jpg", "bmp", or "tga") and
100
+ # return the encoded bytes as a Ruby String. Useful for sending
101
+ # screenshots over the network, generating data: URLs, piping into
102
+ # an image-processing library, etc., without touching the disk.
103
+ #
104
+ # png_bytes = img.save_to_memory("png")
105
+ # File.binwrite("out.png", png_bytes)
106
+ def save_to_memory(format)
107
+ buffer = C::System.sfBuffer_create
108
+ raise Error, "sfBuffer_create returned NULL" if buffer.null?
109
+
110
+ begin
111
+ ok = C::Graphics.sfImage_saveToMemory(@handle, buffer, format.to_s)
112
+ raise Error, "Could not encode image as #{format.inspect}" unless ok
113
+
114
+ size = C::System.sfBuffer_getSize(buffer)
115
+ data = C::System.sfBuffer_getData(buffer)
116
+ data.read_bytes(size)
117
+ ensure
118
+ C::System.sfBuffer_destroy(buffer)
119
+ end
120
+ end
121
+
99
122
  # Replace any pixel matching `color` with that colour at `alpha`
100
123
  # opacity — typical use is to turn a fixed background colour
101
124
  # transparent: img.mask_color!(SFML::Color.magenta, alpha: 0).
@@ -17,12 +17,13 @@ module SFML
17
17
  COORDINATE_TYPES = %i[normalized pixels].freeze
18
18
  COORDINATE_INDEX = COORDINATE_TYPES.each_with_index.to_h.freeze
19
19
 
20
- attr_reader :blend_mode, :texture, :shader, :coordinate_type
20
+ attr_reader :blend_mode, :stencil_mode, :texture, :shader, :coordinate_type
21
21
 
22
- def initialize(blend_mode: nil, texture: nil, shader: nil, coordinate_type: :normalized)
22
+ def initialize(blend_mode: nil, stencil_mode: nil, texture: nil, shader: nil, coordinate_type: :normalized)
23
23
  @blend_mode = blend_mode
24
+ @stencil_mode = stencil_mode
24
25
  @texture = texture
25
- @shader = shader # placeholder until SFML::Shader lands in the next P1 step
26
+ @shader = shader
26
27
  @coordinate_type = coordinate_type
27
28
  raise ArgumentError, "unknown coordinate_type: #{coordinate_type.inspect}" unless COORDINATE_INDEX.key?(coordinate_type)
28
29
  freeze
@@ -39,6 +40,7 @@ module SFML
39
40
  .then { |bytes| buffer.pointer.write_bytes(bytes) }
40
41
 
41
42
  @blend_mode&.populate(buffer[:blend_mode])
43
+ @stencil_mode&.populate(buffer[:stencil_mode])
42
44
  buffer[:coordinate_type] = COORDINATE_INDEX[@coordinate_type]
43
45
  buffer[:texture] = @texture ? @texture.handle : nil
44
46
  buffer[:shader] = @shader ? @shader.handle : nil