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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8688630fa5286e1b5c28ed9c5cda8aa635a197fe74dc422859253f45f11f111a
|
|
4
|
+
data.tar.gz: bcb83c0e0667fcae607fa3d3b0ca32527d25dec96fd8726ae697b1b5340c68f9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d2a0d250ee6f2a7ca62d7da0305f3345bed8170be02f7700074ae1dbb76a24379687c13e64488ab6cbea5eed78821aa9554ecac6501a76ca16eb3febcbe8ca47
|
|
7
|
+
data.tar.gz: f9b7399e3e2b6168e217292573ef54a15a6217c4675f21f3718a4786787590fda2900d6795b68cd422ca1a5859962aa75d51371305f3ca1584914d5d4e0d13e2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. The format roughly follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the gem
|
|
5
|
+
versioning is [described in the README](README.md#versioning) — first
|
|
6
|
+
three segments mirror the targeted CSFML release, fourth segment is
|
|
7
|
+
ruby-sfml's own patch level.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [3.0.0.0] — initial release
|
|
12
|
+
|
|
13
|
+
First public cut. Targets **CSFML 3.0.0** (released March 2025) and
|
|
14
|
+
**Ruby ≥ 3.2**. API surface complete for the SFML 3.0 spec; some
|
|
15
|
+
engineering polish still pending (`gem build` end-to-end verification,
|
|
16
|
+
RBS signatures, hosted RDoc).
|
|
17
|
+
|
|
18
|
+
### System
|
|
19
|
+
- `Vector2`, `Vector3` — operator-friendly value classes with `coerce`
|
|
20
|
+
(`2 * vec`), pattern-match `deconstruct`, `length`, `normalize`,
|
|
21
|
+
`dot`/`cross`, conversion helpers
|
|
22
|
+
- `Rect` — single class for float / int rectangles with `contains?`,
|
|
23
|
+
`intersects?`, deconstruction
|
|
24
|
+
- `Time`, `Clock` — monotonic timer, immutable Time arithmetic
|
|
25
|
+
|
|
26
|
+
### Window
|
|
27
|
+
- `RenderWindow` — main 2D drawing surface
|
|
28
|
+
- `Window` — bare GL-only window for raw-OpenGL apps
|
|
29
|
+
- `Event` — pattern-matchable hash-like value (`case event in {type: :key_pressed, code: :escape}`)
|
|
30
|
+
- `Keyboard`, `Mouse`, `Joystick` — polling APIs with symbol-named keys / buttons / axes
|
|
31
|
+
- `Cursor` — 21 system cursor types + `from_pixels` for custom shapes
|
|
32
|
+
- `Clipboard` — UTF-8 in / UTF-8 out via the unicode-string CSFML path
|
|
33
|
+
- `VideoMode`
|
|
34
|
+
|
|
35
|
+
### Graphics
|
|
36
|
+
- `Color`, `Image`, `Texture`, `RenderTexture`
|
|
37
|
+
- `Sprite`, `CircleShape`, `RectangleShape`, `ConvexShape` — share a
|
|
38
|
+
`Transformable` mixin (position, rotation, scale, origin, move)
|
|
39
|
+
- `Vertex`, `VertexArray` — batched geometry; six primitive types
|
|
40
|
+
- `Font`, `Text` — UTF-8 strings, `local_bounds` / `global_bounds`,
|
|
41
|
+
Font.find searches common system paths plus a bundled DejaVu Sans
|
|
42
|
+
- `View` — 2D camera, including `from_rect` and viewport for split-screen / minimap
|
|
43
|
+
- `RenderStates`, `BlendMode` — full blend mode catalogue, kwargs
|
|
44
|
+
shortcut on `window.draw(thing, blend_mode: SFML::BlendMode::ADD)`
|
|
45
|
+
- `Shader` — load from file or memory, uniform setter that dispatches by
|
|
46
|
+
Ruby type (`shader[:time] = 1.5`, `shader[:tex] = my_texture`,
|
|
47
|
+
`shader[:tint] = SFML::Color.red`)
|
|
48
|
+
- `Transform` — standalone 4×3 matrix value class, chainable
|
|
49
|
+
- `RenderTarget#draw` accepts `texture:`, `blend_mode:`, `shader:`,
|
|
50
|
+
`coordinate_type:`, `render_states:` kwargs
|
|
51
|
+
- `RenderTarget#draw_primitives(vertices, type)` — one-shot batch
|
|
52
|
+
rendering without a `VertexArray` object
|
|
53
|
+
|
|
54
|
+
### Audio
|
|
55
|
+
- `SoundBuffer`, `Sound`, `Music`
|
|
56
|
+
- 3D positional audio on Sound and Music (`#position=`, `#attenuation=`,
|
|
57
|
+
`#min_distance=`, `#relative_to_listener=`)
|
|
58
|
+
- `Listener` — global "ear" with `position`, `direction`, `up_vector`,
|
|
59
|
+
`global_volume`
|
|
60
|
+
- `SoundBufferRecorder` + `SoundRecorder` static helpers — record audio
|
|
61
|
+
from the system mic into a SoundBuffer
|
|
62
|
+
|
|
63
|
+
### Network
|
|
64
|
+
- `Network::IpAddress` — value class with `from_string`, `from_bytes`,
|
|
65
|
+
`LOCALHOST` / `ANY` / `BROADCAST` constants, `local`, `public`
|
|
66
|
+
- `Network::TcpSocket`, `Network::TcpListener` — TCP client + server,
|
|
67
|
+
blocking and non-blocking modes
|
|
68
|
+
- `Network::UdpSocket` — connectionless datagrams
|
|
69
|
+
- `SocketStatus` returned as symbols (`:done`, `:not_ready`,
|
|
70
|
+
`:disconnected`, `:error`, `:partial`)
|
|
71
|
+
- Intentionally not wrapped: `Http`, `Ftp` (use Ruby stdlib's
|
|
72
|
+
`Net::HTTP` / `Net::FTP` — they're nicer)
|
|
73
|
+
|
|
74
|
+
### Helpers
|
|
75
|
+
- `Game` — subclass-friendly main loop with `setup` / `update(dt)` /
|
|
76
|
+
`draw` / `on_event` hooks. Auto-quit on Esc + close button.
|
|
77
|
+
- `Assets` — cached, search-path-driven loader.
|
|
78
|
+
`SFML::Assets.font("DejaVuSans")`, `.texture(name)`, `.sound(name)`,
|
|
79
|
+
`.music(name)`. Default search root is `<dir of $0>/assets/`.
|
|
80
|
+
- Bundled DejaVu Sans (Bitstream Vera license) — `SFML::Font.default`
|
|
81
|
+
works without a system font install
|
|
82
|
+
|
|
83
|
+
### Engineering
|
|
84
|
+
- Two-tier API: `SFML::C::*` thin FFI bindings, `SFML::*` idiomatic
|
|
85
|
+
Ruby on top. Most user-facing classes are ~50–150 LOC each
|
|
86
|
+
- `RenderTarget` mixin — `RenderWindow` and `RenderTexture` share
|
|
87
|
+
every drawing method through CSFML_PREFIX dispatch
|
|
88
|
+
- 287 RSpec examples, all hitting real CSFML
|
|
89
|
+
- 20 self-contained example folders under [examples/](examples/)
|
|
90
|
+
- CI matrix: Ubuntu + macOS × Ruby 3.2 / 3.3 / 3.4
|
|
91
|
+
- Linux CI builds CSFML 3 from source (cached) and runs specs under
|
|
92
|
+
`xvfb-run`
|
|
93
|
+
- `extconf.rb` checks every required `libcsfml-*` plus probes
|
|
94
|
+
`sfClock_isRunning` (CSFML 3.0+ only) — `gem install` aborts with a
|
|
95
|
+
helpful message on CSFML 2.x
|
|
96
|
+
- Same probe re-runs at `require "sfml"` time as a runtime sanity
|
|
97
|
+
check
|
|
98
|
+
- `at_exit` hook stops live `Sound`/`Music` and bypasses Ruby's
|
|
99
|
+
finalizer pass via `exit!` — eliminates a class of GL/OpenAL-teardown
|
|
100
|
+
segfaults that plagued every non-trivial example
|
|
101
|
+
- RDoc 7 (Aliki theme) generated via `rake rdoc`
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ruby-sfml contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# ruby-sfml
|
|
2
|
+
|
|
3
|
+
Modern, idiomatic Ruby bindings for [SFML 3.x](https://www.sfml-dev.org/) via [CSFML](https://github.com/SFML/CSFML) and [Ruby FFI](https://github.com/ffi/ffi).
|
|
4
|
+
|
|
5
|
+
> **Status:** the API surface is complete for SFML 3.0 — system, window, graphics, audio, network, plus the higher-level `Game` and `Assets` helpers. 287 RSpec examples, 20 runnable example folders. Some details (gem-build verification, RBS signatures, hosted docs) are still pending.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
The original [rbSFML](https://github.com/Groogy/rbSFML) is unmaintained and only works against SFML 2 and Ruby 2.2. `ruby-sfml` targets the current SFML 3.x line, modern Ruby (3.2+), and a Ruby-first API — blocks instead of polling loops, symbols instead of enums, operators on vectors, automatic resource cleanup via GC.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Ruby `>= 3.2`
|
|
14
|
+
- CSFML **3.0** or compatible 3.x at the system level
|
|
15
|
+
|
|
16
|
+
### Install CSFML
|
|
17
|
+
|
|
18
|
+
| OS | Command | Notes |
|
|
19
|
+
| ------------------------ | ---------------------------------------- | ----- |
|
|
20
|
+
| Ubuntu 25.04+ / Debian | `sudo apt install libcsfml-dev` | Ships CSFML 3 |
|
|
21
|
+
| Ubuntu 22.04 / 24.04 | repo too old (CSFML 2.5) | Build from [3.0.0 release](https://github.com/SFML/CSFML/releases/tag/3.0.0) |
|
|
22
|
+
| macOS (brew) | `brew install csfml` | Currently 3.x |
|
|
23
|
+
| Arch Linux | `sudo pacman -S csfml` | Currently 3.x |
|
|
24
|
+
| Windows | https://www.sfml-dev.org/download/csfml/ | Pick the 3.0 tarball |
|
|
25
|
+
|
|
26
|
+
ruby-sfml verifies the linked CSFML twice:
|
|
27
|
+
|
|
28
|
+
- **At `gem install`** — `extconf.rb` checks for the five `libcsfml-*` libraries plus a CSFML 3.0+ symbol (`sfClock_isRunning`). Aborts with a clear message if the system has CSFML 2.x.
|
|
29
|
+
- **At `require "sfml"`** — same probe runs as a runtime sanity check, in case libraries were swapped between install and use.
|
|
30
|
+
|
|
31
|
+
You'll see a useful error either way; nothing falls through to a cryptic CSFML segfault.
|
|
32
|
+
|
|
33
|
+
## A 12-line game
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "sfml"
|
|
37
|
+
|
|
38
|
+
class Hello < SFML::Game
|
|
39
|
+
def setup
|
|
40
|
+
@ball = SFML::CircleShape.new(radius: 30, fill_color: SFML::Color.white,
|
|
41
|
+
position: [200, 200])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update(dt) = @ball.move(60 * dt.as_seconds * SFML::Vector2[1, 0])
|
|
45
|
+
def draw = window.draw(@ball)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Hello.new(title: "Hello", background: SFML::Color.cornflower_blue).run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`SFML::Game` handles window creation, the main loop, event pumping, dt, and the Esc/close-button quit. Override `setup` / `update` / `draw` / `on_event`. Drop into the manual loop style any time you want full control.
|
|
52
|
+
|
|
53
|
+
## A 5-line manual loop
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
require "sfml"
|
|
57
|
+
|
|
58
|
+
window = SFML::RenderWindow.new(800, 600, "Hello", framerate: 60)
|
|
59
|
+
|
|
60
|
+
while window.open?
|
|
61
|
+
window.each_event do |event|
|
|
62
|
+
case event
|
|
63
|
+
in {type: :closed} then window.close
|
|
64
|
+
in {type: :key_pressed, code: :escape} then window.close
|
|
65
|
+
else # always include `else` — case/in raises on unmatched events.
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
window.clear(SFML::Color.cornflower_blue)
|
|
70
|
+
window.display
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Available modules
|
|
75
|
+
|
|
76
|
+
| Area | Classes |
|
|
77
|
+
| -------- | ------------------------------------------------------------ |
|
|
78
|
+
| System | `Vector2`, `Vector3`, `Rect`, `Time`, `Clock` |
|
|
79
|
+
| Window | `RenderWindow`, `Window` (bare, GL-only), `VideoMode`, `Event`, `Keyboard`, `Mouse`, `Joystick`, `Cursor`, `Clipboard` |
|
|
80
|
+
| Graphics | `Color`, `Image`, `Texture`, `RenderTexture`, `Sprite`, `CircleShape`, `RectangleShape`, `ConvexShape`, `Vertex`, `VertexArray`, `Font`, `Text`, `View`, `BlendMode`, `RenderStates`, `Shader`, `Transform` |
|
|
81
|
+
| Audio | `SoundBuffer`, `Sound`, `Music`, `Listener`, `SoundRecorder`, `SoundBufferRecorder` (3D positional audio supported on Sound and Music) |
|
|
82
|
+
| Helpers | `Assets` (search-path + cache), `Game` (lifecycle main loop) |
|
|
83
|
+
|
|
84
|
+
**Network**: `IpAddress`, `TcpSocket`, `TcpListener`, `UdpSocket` for stream / datagram networking.
|
|
85
|
+
|
|
86
|
+
## What's intentionally *not* wrapped
|
|
87
|
+
|
|
88
|
+
CSFML 3 has a few corners we deliberately don't expose. Each is either
|
|
89
|
+
(a) niche enough not to justify the surface area, (b) better served by
|
|
90
|
+
a Ruby standard library, or (c) requires patterns that don't translate
|
|
91
|
+
cleanly to FFI.
|
|
92
|
+
|
|
93
|
+
**Use Ruby stdlib instead**
|
|
94
|
+
- `sf::Http` — `Net::HTTP` is a better Ruby fit
|
|
95
|
+
- `sf::Ftp` — `Net::FTP` likewise
|
|
96
|
+
- `sf::SocketSelector` — `IO.select` or [Async](https://github.com/socketry/async)
|
|
97
|
+
|
|
98
|
+
**Callback-based APIs that fight FFI / the GVL**
|
|
99
|
+
- Raw `sf::SoundRecorder` (per-buffer callbacks on the audio thread) —
|
|
100
|
+
use `SFML::SoundBufferRecorder` for "record into memory, save on stop"
|
|
101
|
+
- `sf::SoundStream` (custom audio source via inheritance) — niche; if
|
|
102
|
+
you need it, generate samples to a file and play via `Music`
|
|
103
|
+
|
|
104
|
+
**Mobile / niche inputs** (SFML 3 itself treats these as experimental)
|
|
105
|
+
- `sf::Touch`, `sf::Sensor` (accelerometer, gyro, etc.)
|
|
106
|
+
|
|
107
|
+
**Advanced graphics features**
|
|
108
|
+
- `sf::VertexBuffer` (static GPU vertex buffer) — `VertexArray` covers
|
|
109
|
+
the common case; if you need static-mesh perf, open an issue
|
|
110
|
+
- Geometry shaders — only vertex and fragment stages on `SFML::Shader`
|
|
111
|
+
- `sf::Shader#setUniformArray` (bulk uniforms) — set elements one by one
|
|
112
|
+
- Stencil buffer ops (`clearStencil`, custom `StencilMode`) — accept
|
|
113
|
+
CSFML defaults
|
|
114
|
+
- `sf::Image#saveToMemory` — only `Image#save(path)` is wrapped
|
|
115
|
+
|
|
116
|
+
**Advanced audio features**
|
|
117
|
+
- Sound / Music cones, velocity, Doppler factor, custom DSP via
|
|
118
|
+
`setEffectProcessor` — basic 3D positional + attenuation is in;
|
|
119
|
+
the rest is rarely used in 2D gamedev
|
|
120
|
+
- `sf::Listener` cone — same reasoning
|
|
121
|
+
|
|
122
|
+
**Embedding / integration corners**
|
|
123
|
+
- `RenderWindow.createFromHandle` (embed in another framework's window)
|
|
124
|
+
- Custom `sf::InputStream` for loading assets from non-file sources
|
|
125
|
+
- Window icon, min/max size, native handle accessors on `SFML::Window`
|
|
126
|
+
|
|
127
|
+
**Other Ruby bindings worth knowing about**
|
|
128
|
+
- SFML 2.x is *not* covered. The previous-generation gem
|
|
129
|
+
[rbSFML](https://github.com/Groogy/rbSFML) targets SFML 2; it's
|
|
130
|
+
unmaintained and only works with Ruby ≤ 2.2.
|
|
131
|
+
|
|
132
|
+
If anything in the list above is blocking you, **open an issue** —
|
|
133
|
+
"niche" is just a default, not a closed door.
|
|
134
|
+
|
|
135
|
+
## Examples
|
|
136
|
+
|
|
137
|
+
Each example is a self-contained folder under [examples/](examples/),
|
|
138
|
+
numbered roughly in learning order. Assets each example needs sit next
|
|
139
|
+
to its script. Run from the gem root:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
bundle exec ruby examples/<NN_name>/<name>.rb
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| # | Example | What it shows |
|
|
146
|
+
| --- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
|
|
147
|
+
| 01 | [hello_window](examples/01_hello_window/hello_window.rb) | Empty window, manual event loop |
|
|
148
|
+
| 02 | [events_demo](examples/02_events_demo/events_demo.rb) | Pattern matching on input events |
|
|
149
|
+
| 03 | [bouncing_ball](examples/03_bouncing_ball/bouncing_ball.rb) | dt-based physics, `CircleShape` + `RectangleShape` |
|
|
150
|
+
| 04 | [game_class](examples/04_game_class/game_class.rb) | Same idea on top of `SFML::Game` |
|
|
151
|
+
| 05 | [mouse_demo](examples/05_mouse_demo/mouse_demo.rb) | Polling vs. events; paint with the mouse |
|
|
152
|
+
| 06 | [pong](examples/06_pong/pong.rb) | Two-player Pong with in-window score (`Text`) and bounce `Sound` |
|
|
153
|
+
| 07 | [scrolling_world](examples/07_scrolling_world/scrolling_world.rb) | `View` as a 2D camera: drag-pan, wheel-zoom around cursor, FPS HUD |
|
|
154
|
+
| 08 | [joystick_demo](examples/08_joystick_demo/joystick_demo.rb) | Live gamepad inspector (axes, buttons, connect/disconnect) |
|
|
155
|
+
| 09 | [image_viewer](examples/09_image_viewer/image_viewer.rb) | Load a PNG, mutate the `Image`, re-upload to `Texture` on a key |
|
|
156
|
+
| 10 | [pixel_paint](examples/10_pixel_paint/pixel_paint.rb) | Paint into a CPU `Image`, blit to GPU `Texture` each dirty frame |
|
|
157
|
+
| 11 | [particles](examples/11_particles/particles.rb) | Thousands of points in one draw call via `VertexArray` + `ConvexShape` ground |
|
|
158
|
+
| 12 | [render_texture](examples/12_render_texture/render_texture.rb) | Off-screen `RenderTexture` for trail / motion-blur effects |
|
|
159
|
+
| 13 | [tilemap](examples/13_tilemap/tilemap.rb) | Textured `VertexArray` tilemap + additive `BlendMode` torch |
|
|
160
|
+
| 14 | [shader_wave](examples/14_shader_wave/shader_wave.rb) | Pure GLSL fragment `Shader` — procedural ripple + plasma |
|
|
161
|
+
| 15 | [cursors_clipboard](examples/15_cursors_clipboard/cursors_clipboard.rb) | All 21 system `Cursor` shapes + `Clipboard` copy/paste |
|
|
162
|
+
| 16 | [spatial_audio](examples/16_spatial_audio/spatial_audio.rb) | 3D positional `Sound` + `Listener` — three drones around the cursor |
|
|
163
|
+
| 17 | [voice_memo](examples/17_voice_memo/voice_memo.rb) | Record from microphone via `SoundBufferRecorder`, save + play back |
|
|
164
|
+
| 18 | [draw_primitives](examples/18_draw_primitives/draw_primitives.rb) | Raw `draw_primitives` — line burst rebuilt every frame |
|
|
165
|
+
| 19 | [udp_loopback](examples/19_udp_loopback/udp_loopback.rb) | UDP send/receive on localhost via `Network::UdpSocket` |
|
|
166
|
+
| 20 | [bare_window](examples/20_bare_window/bare_window.rb) | `SFML::Window` (no 2D batcher) — events for raw-OpenGL apps |
|
|
167
|
+
|
|
168
|
+
## Idioms baked in
|
|
169
|
+
|
|
170
|
+
- **Symbols, not enums:** `Keyboard.key_pressed?(:escape)`, not `Keyboard::Key::Escape`.
|
|
171
|
+
- **Pattern matching for events:**
|
|
172
|
+
```ruby
|
|
173
|
+
case event
|
|
174
|
+
in {type: :key_pressed, code: :escape}
|
|
175
|
+
in {type: :resized, size: {x:, y:}}
|
|
176
|
+
in {type: :mouse_button_pressed, button: :left, position: {x:, y:}}
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
- **Vectors with operators:** `pos + velocity * dt`, `2 * vec`, `vec.length`, deconstruction in `case/in`.
|
|
180
|
+
- **Kwargs constructors:** `Sprite.new(texture, position: [0, 0], color: SFML::Color.red)`, `CircleShape.new(radius: 10, fill_color: ...)` — no setter chains.
|
|
181
|
+
- **Asset manager with cache:** `SFML::Assets.font("DejaVuSans")`, `SFML::Assets.sound("blip")` — load each thing once, refer by name.
|
|
182
|
+
- **GC-managed resources:** every CSFML pointer goes through `FFI::AutoPointer`, so `sfXxx_destroy` is called automatically.
|
|
183
|
+
|
|
184
|
+
## Versioning
|
|
185
|
+
|
|
186
|
+
The gem version is `MAJOR.MINOR.PATCH.GEM_PATCH` — the first three segments mirror the CSFML release the gem was built against; the fourth is our own patch level for fixes / additions on top of the same upstream:
|
|
187
|
+
|
|
188
|
+
| gem version | targets CSFML | meaning |
|
|
189
|
+
| ----------- | ------------- | ----------------------------------------------- |
|
|
190
|
+
| `3.0.0.0` | 3.0.0 | First cut against CSFML 3.0.0 |
|
|
191
|
+
| `3.0.0.1` | 3.0.0 | Bug fix on top of CSFML 3.0.0 |
|
|
192
|
+
| `3.0.1.0` | 3.0.1 | CSFML 3.0.1 ships, we re-cut |
|
|
193
|
+
| `3.1.0.0` | 3.1.0 | New CSFML minor — added bindings for new APIs |
|
|
194
|
+
|
|
195
|
+
`SFML::CSFML_VERSION` exposes the upstream string at runtime.
|
|
196
|
+
|
|
197
|
+
Bundler-pinning patterns:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
gem "ruby-sfml", "~> 3.0" # any 3.x.x.x — typical
|
|
201
|
+
gem "ruby-sfml", "~> 3.0.0" # only 3.0.0.x — hold across a CSFML minor
|
|
202
|
+
gem "ruby-sfml", "~> 3.0.0.0" # only our patches on CSFML 3.0.0 — paranoid pin
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Process exit
|
|
206
|
+
|
|
207
|
+
ruby-sfml installs a single `at_exit` hook that:
|
|
208
|
+
|
|
209
|
+
1. Stops every live `SFML::Sound` / `SFML::Music` so the audio thread quiets before anything is freed.
|
|
210
|
+
2. Calls `Kernel#exit!` with the appropriate status, bypassing Ruby's natural finalizer pass.
|
|
211
|
+
|
|
212
|
+
This is intentional. CSFML's GL context, font glyph atlases, and OpenAL state are reclaimed by the OS on process exit; running each `FFI::AutoPointer` finalizer in a non-deterministic order tends to crash inside libGL/libopenal. The OS doesn't care, and now neither do we.
|
|
213
|
+
|
|
214
|
+
The trade-off: any user `at_exit` hook registered **before** `require "sfml"` will be skipped. Hooks registered after the require run first (Ruby's at_exit is LIFO) and are unaffected. Put your `require` at the top of the file (the normal place for it) and there's nothing to think about.
|
|
215
|
+
|
|
216
|
+
## Architecture
|
|
217
|
+
|
|
218
|
+
Two layers. Users only touch the top one.
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
SFML::C # thin FFI wrapper around CSFML, 1:1 with the C API
|
|
222
|
+
SFML # idiomatic Ruby on top
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Each render target (RenderWindow + RenderTexture) includes a `Graphics::RenderTarget` mixin that dispatches `clear`, `display`, `draw`, `view=`, `map_pixel_to_coords` etc. through the includer's `CSFML_PREFIX`. Adding a new target (say a future `RenderImage`) is ~30 lines.
|
|
226
|
+
|
|
227
|
+
When SFML 3.1 / CSFML 3.1 ships, only the bottom layer typically needs to move.
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```sh
|
|
232
|
+
bundle install
|
|
233
|
+
bundle exec rspec # 287 examples
|
|
234
|
+
bundle exec rake rdoc # generate HTML docs in doc/ (Aliki theme via RDoc 7)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The spec suite hits real CSFML for everything that isn't pure Ruby — `Clock` reads the real monotonic clock, `Text#local_bounds` measures real glyphs, audio loads a WAV — so a green run also confirms the FFI bindings line up. `spec/fixtures/` holds the only assets the suite touches (a font and a tiny WAV) so tests are independent of `examples/`.
|
|
238
|
+
|
|
239
|
+
CI runs the full suite on Ubuntu and macOS × Ruby 3.2 / 3.3 / 3.4. Linux builds CSFML 3 from source (cached), then runs specs under `xvfb-run` so the headless runner has an X server for `RenderWindow`.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT. See [LICENSE.txt](LICENSE.txt).
|
|
244
|
+
|
|
245
|
+
The gem also bundles [DejaVu Sans](https://dejavu-fonts.github.io/) under its [permissive license](lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt) — used as the default font when you don't supply your own.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require "mkmf"
|
|
2
|
+
|
|
3
|
+
# This is not a real native extension. We use the extconf.rb hook only as a
|
|
4
|
+
# pre-flight check: if CSFML 3.x is not on the system, fail `gem install` here
|
|
5
|
+
# with a clear message instead of letting the user discover the problem at
|
|
6
|
+
# runtime.
|
|
7
|
+
|
|
8
|
+
REQUIRED_LIBS = %w[csfml-system csfml-window csfml-graphics csfml-audio csfml-network]
|
|
9
|
+
|
|
10
|
+
missing = REQUIRED_LIBS.reject { |lib| have_library(lib) }
|
|
11
|
+
|
|
12
|
+
unless missing.empty?
|
|
13
|
+
abort <<~MSG
|
|
14
|
+
|
|
15
|
+
============================================================================
|
|
16
|
+
ruby-sfml requires CSFML 3.x to be installed on your system.
|
|
17
|
+
|
|
18
|
+
Missing libraries: #{missing.join(', ')}
|
|
19
|
+
|
|
20
|
+
Install CSFML, then re-run `gem install ruby-sfml`:
|
|
21
|
+
|
|
22
|
+
Ubuntu / Debian: sudo apt install libcsfml-dev
|
|
23
|
+
macOS (brew): brew install csfml
|
|
24
|
+
Arch Linux: sudo pacman -S csfml
|
|
25
|
+
Windows: https://www.sfml-dev.org/download/csfml/
|
|
26
|
+
|
|
27
|
+
See https://github.com/m1kh41l/ruby-sfml#requirements for full instructions.
|
|
28
|
+
============================================================================
|
|
29
|
+
|
|
30
|
+
MSG
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# All libcsfml-* are present, but they might be 2.x (Ubuntu 22.04 / 24.04
|
|
34
|
+
# ship 2.5 in their repos). Probe a CSFML 3.0+ symbol — sfClock_isRunning
|
|
35
|
+
# is part of the SFML 3 sf::Clock rewrite and isn't in any 2.x release.
|
|
36
|
+
unless have_func("sfClock_isRunning")
|
|
37
|
+
abort <<~MSG
|
|
38
|
+
|
|
39
|
+
============================================================================
|
|
40
|
+
ruby-sfml requires CSFML 3.0 or newer.
|
|
41
|
+
|
|
42
|
+
The libcsfml on your system is older than 3.0 — sfClock_isRunning,
|
|
43
|
+
introduced in the SFML 3.0 / CSFML 3.0 release (March 2025), is not
|
|
44
|
+
exported by the linked library.
|
|
45
|
+
|
|
46
|
+
Upgrade options:
|
|
47
|
+
|
|
48
|
+
Ubuntu 25.04+ / Debian: sudo apt install libcsfml-dev
|
|
49
|
+
Ubuntu 22.04 / 24.04: repo is too old; build from source:
|
|
50
|
+
https://github.com/SFML/CSFML/releases/tag/3.0.0
|
|
51
|
+
macOS (brew): brew upgrade csfml
|
|
52
|
+
Arch Linux: sudo pacman -S csfml
|
|
53
|
+
|
|
54
|
+
Or grab a prebuilt 3.x release from
|
|
55
|
+
https://github.com/SFML/CSFML/releases.
|
|
56
|
+
============================================================================
|
|
57
|
+
|
|
58
|
+
MSG
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Emit a no-op Makefile so RubyGems considers the extension "built".
|
|
62
|
+
File.write("Makefile", <<~MAKE)
|
|
63
|
+
all:
|
|
64
|
+
\t@true
|
|
65
|
+
install:
|
|
66
|
+
\t@true
|
|
67
|
+
clean:
|
|
68
|
+
\t@true
|
|
69
|
+
MAKE
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
2
|
+
Upstream-Name: DejaVu fonts
|
|
3
|
+
Upstream-Author: Stepan Roh <src@users.sourceforge.net> (original author),
|
|
4
|
+
see /usr/share/doc/fonts-dejavu-core/AUTHORS for full list
|
|
5
|
+
Source: https://dejavu-fonts.github.io/
|
|
6
|
+
|
|
7
|
+
Files: *
|
|
8
|
+
Copyright: Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.
|
|
9
|
+
Bitstream Vera is a trademark of Bitstream, Inc.
|
|
10
|
+
DejaVu changes are in public domain.
|
|
11
|
+
License: bitstream-vera
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of the fonts accompanying this license ("Fonts") and associated
|
|
14
|
+
documentation files (the "Font Software"), to reproduce and distribute the
|
|
15
|
+
Font Software, including without limitation the rights to use, copy, merge,
|
|
16
|
+
publish, distribute, and/or sell copies of the Font Software, and to permit
|
|
17
|
+
persons to whom the Font Software is furnished to do so, subject to the
|
|
18
|
+
following conditions:
|
|
19
|
+
.
|
|
20
|
+
The above copyright and trademark notices and this permission notice shall
|
|
21
|
+
be included in all copies of one or more of the Font Software typefaces.
|
|
22
|
+
.
|
|
23
|
+
The Font Software may be modified, altered, or added to, and in particular
|
|
24
|
+
the designs of glyphs or characters in the Fonts may be modified and
|
|
25
|
+
additional glyphs or characters may be added to the Fonts, only if the fonts
|
|
26
|
+
are renamed to names not containing either the words "Bitstream" or the word
|
|
27
|
+
"Vera".
|
|
28
|
+
.
|
|
29
|
+
This License becomes null and void to the extent applicable to Fonts or Font
|
|
30
|
+
Software that has been modified and is distributed under the "Bitstream
|
|
31
|
+
Vera" names.
|
|
32
|
+
.
|
|
33
|
+
The Font Software may be sold as part of a larger software package but no
|
|
34
|
+
copy of one or more of the Font Software typefaces may be sold by itself.
|
|
35
|
+
.
|
|
36
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
37
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
|
38
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
|
39
|
+
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
|
40
|
+
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
|
|
41
|
+
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
|
|
42
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
43
|
+
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
|
|
44
|
+
FONT SOFTWARE.
|
|
45
|
+
.
|
|
46
|
+
Except as contained in this notice, the names of Gnome, the Gnome
|
|
47
|
+
Foundation, and Bitstream Inc., shall not be used in advertising or
|
|
48
|
+
otherwise to promote the sale, use or other dealings in this Font Software
|
|
49
|
+
without prior written authorization from the Gnome Foundation or Bitstream
|
|
50
|
+
Inc., respectively. For further information, contact: fonts at gnome dot
|
|
51
|
+
org.
|
|
52
|
+
|
|
53
|
+
Files: debian/*
|
|
54
|
+
Copyright: (C) 2005-2006 Peter Cernak <pce@users.sourceforge.net>
|
|
55
|
+
(C) 2006-2011 Davide Viti <zinosat@tiscali.it>
|
|
56
|
+
(C) 2011-2013 Christian Perrier <bubulle@debian.org>
|
|
57
|
+
(C) 2013 Fabian Greffrath <fabian+debian@greffrath.com>
|
|
58
|
+
License: GPL-2+
|
|
59
|
+
This program is free software; you can redistribute it
|
|
60
|
+
and/or modify it under the terms of the GNU General Public
|
|
61
|
+
License as published by the Free Software Foundation; either
|
|
62
|
+
version 2 of the License, or (at your option) any later
|
|
63
|
+
version.
|
|
64
|
+
.
|
|
65
|
+
This program is distributed in the hope that it will be
|
|
66
|
+
useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
67
|
+
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
68
|
+
PURPOSE. See the GNU General Public License for more
|
|
69
|
+
details.
|
|
70
|
+
.
|
|
71
|
+
You should have received a copy of the GNU General Public
|
|
72
|
+
License along with this package; if not, write to the Free
|
|
73
|
+
Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
74
|
+
Boston, MA 02110-1301 USA
|
|
75
|
+
.
|
|
76
|
+
On Debian systems, the full text of the GNU General Public
|
|
77
|
+
License version 2 can be found in the file
|
|
78
|
+
/usr/share/common-licenses/GPL-2'.
|
|
Binary file
|
data/lib/sfml/assets.rb
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Cached, search-path-driven asset loader. Use it to avoid repeating file
|
|
3
|
+
# paths and to load each asset exactly once.
|
|
4
|
+
#
|
|
5
|
+
# font = SFML::Assets.font("DejaVuSans")
|
|
6
|
+
# tex = SFML::Assets.texture("hero") # finds hero.png/.jpg/.bmp
|
|
7
|
+
# blip = SFML::Assets.sound("blip") # finds blip.wav/.ogg/...
|
|
8
|
+
# music = SFML::Assets.music("track") # NOT cached (stateful)
|
|
9
|
+
#
|
|
10
|
+
# By default the search root is `<dir of $0>/assets/`. Override:
|
|
11
|
+
#
|
|
12
|
+
# SFML::Assets.root = File.expand_path("data", __dir__)
|
|
13
|
+
# SFML::Assets.add_search_path("/usr/local/share/mygame")
|
|
14
|
+
#
|
|
15
|
+
# Cache survives until you call .clear or the process exits.
|
|
16
|
+
module Assets
|
|
17
|
+
TEXTURE_EXTS = %w[.png .jpg .jpeg .bmp .gif .tga].freeze
|
|
18
|
+
SOUND_EXTS = %w[.wav .ogg .flac .mp3].freeze
|
|
19
|
+
MUSIC_EXTS = SOUND_EXTS
|
|
20
|
+
FONT_EXTS = %w[.ttf .otf].freeze
|
|
21
|
+
|
|
22
|
+
class NotFound < SFML::Error; end
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def search_paths
|
|
26
|
+
@search_paths ||= [default_root]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def search_paths=(paths)
|
|
30
|
+
@search_paths = Array(paths).map { |p| File.expand_path(p) }
|
|
31
|
+
@cache&.clear
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def root=(path)
|
|
35
|
+
self.search_paths = [path]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_search_path(path)
|
|
39
|
+
search_paths << File.expand_path(path) unless search_paths.include?(File.expand_path(path))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Drop everything from the in-memory cache. New loads will re-read
|
|
43
|
+
# from disk and recreate textures, fonts, sound buffers.
|
|
44
|
+
def clear
|
|
45
|
+
@cache&.clear
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def font(name)
|
|
50
|
+
cache[[:font, name]] ||= load_font(name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def texture(name)
|
|
54
|
+
cache[[:texture, name]] ||= load_texture(name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def sound(name)
|
|
58
|
+
cache[[:sound, name]] ||= load_sound_buffer(name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Music is intentionally NOT cached — it owns a streaming position and
|
|
62
|
+
# play state, so each caller wants its own instance.
|
|
63
|
+
def music(name)
|
|
64
|
+
path = locate(name, MUSIC_EXTS) or raise NotFound,
|
|
65
|
+
"Music #{name.inspect} not found. Searched: #{search_paths.inspect}"
|
|
66
|
+
Music.load(path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def cache
|
|
72
|
+
@cache ||= {}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def default_root
|
|
76
|
+
File.expand_path("assets", File.dirname($PROGRAM_NAME || "."))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def load_font(name)
|
|
80
|
+
path = locate(name, FONT_EXTS)
|
|
81
|
+
return Font.load(path) if path
|
|
82
|
+
# Fall back to a system-font search — fonts are large and rarely
|
|
83
|
+
# shipped with games, so this often does the right thing.
|
|
84
|
+
Font.find(name) or raise NotFound,
|
|
85
|
+
"Font #{name.inspect} not found in #{search_paths.inspect} or system fonts"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def load_texture(name)
|
|
89
|
+
path = locate(name, TEXTURE_EXTS) or raise NotFound,
|
|
90
|
+
"Texture #{name.inspect} not found. Searched: #{search_paths.inspect}"
|
|
91
|
+
Texture.load(path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def load_sound_buffer(name)
|
|
95
|
+
path = locate(name, SOUND_EXTS) or raise NotFound,
|
|
96
|
+
"Sound #{name.inspect} not found. Searched: #{search_paths.inspect}"
|
|
97
|
+
SoundBuffer.load(path)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Finds an asset on disk. If `name` already has a matching extension,
|
|
101
|
+
# uses it as-is; otherwise tries each extension in order.
|
|
102
|
+
def locate(name, extensions)
|
|
103
|
+
name_str = name.to_s
|
|
104
|
+
candidates =
|
|
105
|
+
if extensions.any? { |ext| name_str.end_with?(ext) }
|
|
106
|
+
[name_str]
|
|
107
|
+
else
|
|
108
|
+
extensions.map { |ext| "#{name_str}#{ext}" }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
search_paths.each do |dir|
|
|
112
|
+
candidates.each do |cand|
|
|
113
|
+
full = File.join(dir, cand)
|
|
114
|
+
return full if File.file?(full)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|