ruby-sfml 3.0.0.1 → 3.0.0.2

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: 7f6e44279278680f06163024e2dd2f1eecbc8e8624960d589ec74e591849c9b3
4
- data.tar.gz: 23fc48fc06aa0f7e6807b681c2601784de0159d9429055617e87d1e7977d20fa
3
+ metadata.gz: 8136e14f0f8d0f777f7649e15a61e6017e386f7e80e023d5a2b8000191db9213
4
+ data.tar.gz: f6fed33adc435dcbe67777499d0aae14e58ae670a7a04fc94cb58b23805e8fc8
5
5
  SHA512:
6
- metadata.gz: d351b03bb583fb5bb7ddd469da17838d876d7f5c99cbdae5387c94dfbf933f7299362d70bc8360c71b1b5737c49622bb55795869f2bd49d1784aab910f16d32d
7
- data.tar.gz: d5b91cdb38ee758ca2fc76daf52f2aba4c6172b7d6798d51783caa677a10684d394881dca6e01e704e8db9d1be372ef38035de43e4f49b3bab26a9066d5728f5
6
+ metadata.gz: 62d6d8965915bfe6e95161ae8580a3b2d84eca6334a33387238073de069839dae4f08829f82f42042f43850899ed24329e3d49610ac6ec95092b53685ed49d2e
7
+ data.tar.gz: a4f6805ba848b00c623ae4f1b0029b97423f154058e472e3208c48873c9f2be229707aed0ac2934483256c03f048fc06bf3f6c9c8001712ab7e764d8aa23bba4
data/CHANGELOG.md CHANGED
@@ -8,6 +8,85 @@ ruby-sfml's own patch level.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [3.0.0.2] — 2026-05-09
12
+
13
+ ### Added
14
+ - **`SFML::App`** — subclass-friendly main loop. Removes the
15
+ boilerplate of window creation, event pumping, dt management,
16
+ and clear/display so a small app fits in a few methods.
17
+ - Class-level configuration DSL for `SFML::App`. Defaults that
18
+ used to be passed to `.new` can now be declared in the class
19
+ body and inherited:
20
+ ```ruby
21
+ class MyApp < SFML::App
22
+ width 800
23
+ height 600
24
+ title "Smooth"
25
+ framerate 120
26
+ antialiasing 4
27
+ background SFML::Color["#1a1a1a"]
28
+ end
29
+ ```
30
+ Per-instance kwargs to `.new` still override on a case-by-case
31
+ basis. Available macros: `width`, `height`, `title`, `framerate`,
32
+ `vsync`, `background`, `style`, `fullscreen`, `antialiasing`,
33
+ `context`.
34
+ - **`SFML::ContextSettings`** — configures the OpenGL context the
35
+ window backs onto: anti-aliasing level, depth/stencil bits, GL
36
+ version. The fifth argument to `sfRenderWindow_create` is no
37
+ longer `NULL` by default; pass `antialiasing: 4` (or
38
+ `context: SFML::ContextSettings.new(...)`) to
39
+ `RenderWindow.new` / `SFML::App.new` to turn on MSAA. Read back
40
+ what the driver actually gave you with
41
+ `window.context_settings`. Wraps `sfContextSettings` and
42
+ `sfRenderWindow_getSettings`.
43
+ - **`on_key`** — class-level keybinding DSL. Bind a key to an
44
+ instance method, a Proc, or a block; bindings inherit through
45
+ subclasses with later definitions shadowing earlier ones:
46
+ ```ruby
47
+ class MyApp < SFML::App
48
+ on_key :escape, :quit
49
+ on_key :f11, :toggle_fullscreen
50
+ on_key :p do |app| app.toggle_pause end
51
+ end
52
+ ```
53
+ Bindings live as the class's `key_handlers` Hash; the same DSL
54
+ is also available on `SFML::Scene`, where scene-level bindings
55
+ shadow app-level ones for the active scene.
56
+ - **`pause` / `resume` / `toggle_pause` / `paused?`** on
57
+ `SFML::App` — while paused, `update(dt)` is skipped but `draw`
58
+ continues, so a pause overlay can be drawn on top of a frozen
59
+ scene.
60
+ - **`on_resize(width, height)`** hook on `SFML::App` and
61
+ `SFML::Scene` — replaces the `case event in {type: :resized,
62
+ size: {x:, y:}}` boilerplate that used to live inside
63
+ `on_event`. Default forwards to the active scene.
64
+ - **`SFML::Scene`** — base class for stateful screens (menu,
65
+ gameplay, results screen, settings overlay, etc.). Lifecycle
66
+ hooks (`setup` / `update` / `draw` / `on_event` / `on_resize` /
67
+ `teardown`), its own `on_key` DSL, and a `switch_to(other)`
68
+ shortcut that delegates to the host app. The host app picks a
69
+ starting scene with the `initial_scene SomeScene` class macro;
70
+ `App.switch_to` tears down the previous scene before calling
71
+ `setup` on the new one.
72
+ - New example
73
+ [24_scenes](examples/24_scenes/scenes.rb) — title → play scene
74
+ pair built on `SFML::Scene` + `initial_scene`.
75
+ - New example
76
+ [04_app_class](examples/04_app_class/app_class.rb) — bouncing
77
+ ball on top of `SFML::App` with class-level config and
78
+ `on_key` bindings (replaces the old `04_game_class` example).
79
+
80
+ ### Changed
81
+ - `SFML::App._dispatch` no longer auto-quits on Esc. Apps that
82
+ want it bind it explicitly with `on_key :escape, :quit`. The
83
+ window-close button (`:closed`) still always quits.
84
+ - Audio specs (anything under `spec/sfml/audio/`) are now
85
+ auto-tagged `:audio` and skipped by default on macOS, where
86
+ CoreAudio + the CSFML OpenAL backend occasionally hang the
87
+ test group. Opt-in with `bundle exec rspec --tag audio`.
88
+ Linux runs the full suite as before.
89
+
11
90
  ## [3.0.0.1] — 2026-05-07
12
91
 
13
92
  ### Added
@@ -202,8 +281,8 @@ RBS signatures, hosted RDoc).
202
281
  `Net::HTTP` / `Net::FTP` — they're nicer)
203
282
 
204
283
  ### Helpers
205
- - `Game` — subclass-friendly main loop with `setup` / `update(dt)` /
206
- `draw` / `on_event` hooks. Auto-quit on Esc + close button.
284
+ - `App` — subclass-friendly main loop with `setup` / `update(dt)` /
285
+ `draw` / `on_event` hooks.
207
286
  - `Assets` — cached, search-path-driven loader.
208
287
  `SFML::Assets.font("DejaVuSans")`, `.texture(name)`, `.sound(name)`,
209
288
  `.music(name)`. Default search root is `<dir of $0>/assets/`.
data/README.md CHANGED
@@ -1,54 +1,89 @@
1
- # ruby-sfml
1
+ <h1>
2
+ <img src="ruby-sfml.png" alt="" height="48" align="absmiddle">
3
+ ruby-sfml
4
+ </h1>
2
5
 
3
6
  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
7
 
5
- > **Status:** the API surface is complete for SFML 3.0 — system, window, graphics (incl. stencil buffer + VBOs), audio (incl. 3D positional + custom DSP + procedural streams), network (incl. HTTP / FTP / socket selector), input (keyboard, mouse, joystick, touch, sensors), plus the higher-level `Game` and `Assets` helpers. 387 RSpec examples, 23 runnable example folders. RBS signatures are the main remaining engineering item.
8
+ [![gem version](https://img.shields.io/gem/v/ruby-sfml.svg)](https://rubygems.org/gems/ruby-sfml)
9
+ [![docs](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://www.rubydoc.info/gems/ruby-sfml)
10
+
11
+ > **Status:** API surface complete for SFML 3.0 — system, window, graphics (incl. stencil buffer + VBOs), audio (incl. 3D positional + custom DSP + procedural streams), network (incl. HTTP / FTP / socket selector), input (keyboard, mouse, joystick, touch, sensors), plus the higher-level `App` / `Scene` / `Assets` helpers. 410 RSpec examples, 24 runnable example folders.
6
12
 
7
13
  ## Why
8
14
 
9
15
  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
16
 
11
- ## Requirements
17
+ ## Installation
12
18
 
13
- - Ruby `>= 3.2`
14
- - CSFML **3.0** or compatible 3.x at the system level
19
+ ruby-sfml needs **Ruby 3.2** and **CSFML 3.0** (or compatible 3.x) on
20
+ the system. Install in two steps:
15
21
 
16
- ### Install CSFML
22
+ ### 1. Install dependencies (CSFML)
17
23
 
18
- | OS | Command | Notes |
19
- | ------------------------ | ---------------------------------------- | ----- |
20
- | Ubuntu 25.04+ / Debian | `sudo apt install libcsfml-dev` | Ships CSFML 3 |
24
+ | OS | Command | Notes |
25
+ | ------------------------ | ---------------------------------------- | ------------------------------------------------------------------------ |
26
+ | Ubuntu 25.04+ / Debian | `sudo apt install libcsfml-dev` | Ships CSFML 3 |
21
27
  | 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 |
28
+ | macOS (brew) | `brew install csfml` | Currently 3.x |
29
+ | Arch Linux | `sudo pacman -S csfml` | Currently 3.x |
30
+ | Windows | https://www.sfml-dev.org/download/csfml/ | Pick the 3.0 tarball |
31
+
32
+ ### 2. Install the gem
33
+
34
+ In a Bundler-managed project, add to your `Gemfile`:
35
+
36
+ ```ruby
37
+ gem "ruby-sfml", "~> 3.0"
38
+ ```
39
+
40
+ then `bundle install`.
41
+
42
+ Or drop it in directly:
43
+
44
+ ```sh
45
+ gem install ruby-sfml
46
+ ```
25
47
 
26
- ruby-sfml verifies the linked CSFML twice:
48
+ Hosted on [rubygems.org](https://rubygems.org/gems/ruby-sfml); HTML
49
+ docs auto-generate at
50
+ [rubydoc.info/gems/ruby-sfml](https://www.rubydoc.info/gems/ruby-sfml).
27
51
 
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.
52
+ ### How the CSFML check happens
30
53
 
31
- You'll see a useful error either way; nothing falls through to a cryptic CSFML segfault.
54
+ ruby-sfml verifies the linked CSFML in two places so a missing or
55
+ out-of-date library never falls through to a cryptic segfault:
32
56
 
33
- ## A 12-line game
57
+ - **At `gem install`** — `extconf.rb` checks for the five
58
+ `libcsfml-*` libraries plus a CSFML 3.0+ symbol
59
+ (`sfClock_isRunning`). Aborts with a clear message if the system
60
+ has CSFML 2.x.
61
+ - **At `require "sfml"`** — the same probe runs as a runtime sanity
62
+ check, in case libraries were swapped between install and use.
63
+
64
+ ## A 13-line app
34
65
 
35
66
  ```ruby
36
67
  require "sfml"
37
68
 
38
- class Hello < SFML::Game
69
+ class Hello < SFML::App
70
+ title "Hello"
71
+ background SFML::Color.cornflower_blue
72
+ antialiasing 4
73
+
39
74
  def setup
40
75
  @ball = SFML::CircleShape.new(radius: 30, fill_color: SFML::Color.white,
41
76
  position: [200, 200])
42
77
  end
43
78
 
44
- def update(dt) = @ball.move(60 * dt.as_seconds * SFML::Vector2[1, 0])
45
- def draw = window.draw(@ball)
79
+ def update(dt) = @ball.move(60 * dt.as_seconds * SFML::Vector2[1, 0])
80
+ def draw = window.draw(@ball)
46
81
  end
47
82
 
48
- Hello.new(title: "Hello", background: SFML::Color.cornflower_blue).run
83
+ Hello.new.run
49
84
  ```
50
85
 
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.
86
+ `SFML::App` handles window creation, the main loop, event pumping, dt, and the Esc/close-button quit. Override `setup` / `update` / `draw` / `on_event`. Class-level macros (`title`, `framerate`, `antialiasing`, `background`, ...) set per-class defaults; per-instance kwargs to `.new` still override on a case-by-case basis. Drop into the manual loop style any time you want full control.
52
87
 
53
88
  ## A 5-line manual loop
54
89
 
@@ -79,44 +114,36 @@ end
79
114
  | Window | `RenderWindow`, `Window` (bare, GL-only), `VideoMode`, `Event`, `Keyboard`, `Mouse`, `Joystick`, `Touch`, `Sensor`, `Cursor`, `Clipboard` |
80
115
  | Graphics | `Color`, `Image`, `Texture`, `RenderTexture`, `Sprite`, `CircleShape`, `RectangleShape`, `ConvexShape`, `Vertex`, `VertexArray`, `VertexBuffer`, `Font`, `Text`, `View`, `BlendMode`, `StencilMode`, `RenderStates`, `Shader`, `Transform` |
81
116
  | Audio | `SoundBuffer`, `Sound`, `Music`, `Listener`, `SoundCone`, `SoundStream`, `SoundRecorder`, `SoundBufferRecorder` (3D positional + cones + Doppler + custom DSP via `effect_processor=`) |
82
- | Helpers | `Assets` (search-path + cache), `Game` (lifecycle main loop) |
117
+ | Helpers | `Assets` (search-path + cache), `App` (lifecycle main loop with class-level config + `on_key` DSL + `pause` / `on_resize`), `Scene` (stateful screens + `switch_to` between them), `ContextSettings` (MSAA / GL version) |
83
118
 
84
119
  **Network**: `IpAddress`, `TcpSocket`, `TcpListener`, `UdpSocket`, `SocketSelector` for stream / datagram networking, plus the niche `Http` and `Ftp` clients (use Ruby's `Net::HTTP` / `Net::FTP` if you have the choice — these exist for parity with CSFML).
85
120
 
86
121
  ## What's intentionally *not* wrapped
87
122
 
88
- A handful of CSFML 3 corners deliberately stay out:
123
+ Three corners of CSFML 3 deliberately stay out:
89
124
 
90
- - **Geometry shaders** — CSFML doesn't expose them at all (only vertex
91
- and fragment stages); nothing for us to wrap.
125
+ - **Geometry shaders** — CSFML doesn't expose them at all (only
126
+ vertex and fragment stages); there's nothing on the C side to
127
+ wrap.
92
128
  - **Raw `sf::SoundRecorder`** (per-buffer callbacks on the audio
93
- thread) — use `SFML::SoundBufferRecorder` for the common "record
94
- into memory, save on stop" path. The raw callback variant fights
95
- the GVL hard.
129
+ thread) — fights the GVL too hard for what it gives. Use
130
+ `SFML::SoundBufferRecorder` for the common "record into memory,
131
+ save on stop" path.
96
132
  - **Custom `sf::InputStream`** for loading assets from non-file
97
- sources — Ruby has `IO`, just read into memory and use the
98
- byte-string constructors.
99
-
100
- **An aside on `Http` / `Ftp` / `SocketSelector`** — these *are*
101
- wrapped (matches CSFML for parity), but for any non-trivial use
102
- Ruby's stdlib `Net::HTTP` / `Net::FTP` and `IO.select` are better
103
- tools.
133
+ sources — Ruby's `IO` covers this. Read the bytes yourself and
134
+ feed them into the byte-string constructors.
104
135
 
105
- **An aside on `SoundStream` and `effect_processor=`** both *are*
106
- wrapped, but their callbacks run on the SFML audio thread and
107
- must reacquire the GVL each invocation. Fine for trivial DSP;
108
- expect glitches for anything heavier.
136
+ If anything else is missing or blocking you, **open an issue**.
109
137
 
110
- **Other Ruby bindings worth knowing about**
138
+ ## Other Ruby bindings worth knowing about
111
139
 
112
140
  - SFML 2.x is *not* covered. The previous-generation gem
113
141
  [rbSFML](https://github.com/Groogy/rbSFML) targets SFML 2; it's
114
142
  unmaintained and only works with Ruby ≤ 2.2.
115
- - **RubySFML3** (Andy P., 2026) — a thin FFI wrapper that exposes
116
- the CSFML C API directly. If you want raw bindings (no idiomatic
117
- Ruby layer, no auto-cleanup), check that project instead.
118
-
119
- If anything missing here is blocking you, **open an issue**.
143
+ - [RubySFML3](https://github.com/gAndy50/RubySFML3) — a thin FFI
144
+ wrapper that exposes the CSFML C API directly. If you want raw
145
+ bindings (no idiomatic Ruby layer, no auto-cleanup), check that
146
+ project instead.
120
147
 
121
148
  ## Examples
122
149
 
@@ -133,7 +160,7 @@ bundle exec ruby examples/<NN_name>/<name>.rb
133
160
  | 01 | [hello_window](examples/01_hello_window/hello_window.rb) | Empty window, manual event loop |
134
161
  | 02 | [events_demo](examples/02_events_demo/events_demo.rb) | Pattern matching on input events |
135
162
  | 03 | [bouncing_ball](examples/03_bouncing_ball/bouncing_ball.rb) | dt-based physics, `CircleShape` + `RectangleShape` |
136
- | 04 | [game_class](examples/04_game_class/game_class.rb) | Same idea on top of `SFML::Game` |
163
+ | 04 | [app_class](examples/04_app_class/app_class.rb) | Same idea on top of `SFML::App` |
137
164
  | 05 | [mouse_demo](examples/05_mouse_demo/mouse_demo.rb) | Polling vs. events; paint with the mouse |
138
165
  | 06 | [pong](examples/06_pong/pong.rb) | Two-player Pong with in-window score (`Text`) and bounce `Sound` |
139
166
  | 07 | [scrolling_world](examples/07_scrolling_world/scrolling_world.rb) | `View` as a 2D camera: drag-pan, wheel-zoom around cursor, FPS HUD |
@@ -219,7 +246,7 @@ When SFML 3.1 / CSFML 3.1 ships, only the bottom layer typically needs to move.
219
246
 
220
247
  ```sh
221
248
  bundle install
222
- bundle exec rspec # 287 examples
249
+ bundle exec rspec # 410 examples (all subsystems on Linux)
223
250
  bundle exec rake rdoc # generate HTML docs in doc/ (Aliki theme via RDoc 7)
224
251
  ```
225
252
 
@@ -227,6 +254,17 @@ The spec suite hits real CSFML for everything that isn't pure Ruby — `Clock` r
227
254
 
228
255
  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`.
229
256
 
257
+ ### Audio specs on macOS
258
+
259
+ CoreAudio + the CSFML OpenAL backend occasionally hang an audio test group on macOS. To keep a darwin run reliable, every spec under `spec/sfml/audio/` is auto-tagged `:audio` and **excluded by default on darwin**. Force them in when you do want to run them:
260
+
261
+ ```sh
262
+ bundle exec rspec --tag audio # only audio specs
263
+ bundle exec rspec spec/sfml/audio/sound_spec.rb --tag audio # one file
264
+ ```
265
+
266
+ On Linux the `:audio` filter doesn't fire — the whole suite runs by default.
267
+
230
268
  ## License
231
269
 
232
270
  MIT. See [LICENSE.txt](LICENSE.txt).
data/lib/sfml/app.rb ADDED
@@ -0,0 +1,257 @@
1
+ module SFML
2
+ # Subclass-friendly main loop. Removes the boilerplate of window
3
+ # creation, event pumping, clock management, and clear/display so
4
+ # a small app fits in a few methods.
5
+ #
6
+ # class MyApp < SFML::App
7
+ # antialiasing 4 # class-level config — applies to every instance
8
+ # framerate 120
9
+ # background SFML::Color.new(18, 20, 28)
10
+ #
11
+ # def setup
12
+ # @ball = SFML::CircleShape.new(radius: 30, position: [200, 200],
13
+ # fill_color: SFML::Color.white)
14
+ # end
15
+ #
16
+ # def update(dt)
17
+ # @ball.move([60 * dt.as_seconds, 30 * dt.as_seconds])
18
+ # end
19
+ #
20
+ # def draw
21
+ # window.draw(@ball)
22
+ # end
23
+ # end
24
+ #
25
+ # MyApp.new(title: "Hello").run
26
+ #
27
+ # The loop auto-handles :closed and :key_pressed/:escape by calling
28
+ # #quit; everything else is forwarded to #on_event. Override that
29
+ # to handle keys, mouse, etc.
30
+ #
31
+ # ## Configuration
32
+ #
33
+ # Every constructor kwarg is also a class-level macro: declare
34
+ # defaults with `antialiasing 4` etc. inside the class body, and
35
+ # `MyApp.new` picks them up. Per-instance kwargs still win on a
36
+ # case-by-case basis. Subclasses inherit their parent's settings
37
+ # — set one in a base class, override in a subclass.
38
+ class App
39
+ CONFIG_KEYS = %i[
40
+ width height title
41
+ framerate vsync background
42
+ style fullscreen
43
+ antialiasing context
44
+ ].freeze
45
+
46
+ class << self
47
+ CONFIG_KEYS.each do |key|
48
+ # Each macro doubles as a reader (no args) and a writer
49
+ # (one arg). When reading, walks up the inheritance chain so
50
+ # a subclass picks up the parent's value if it hasn't
51
+ # overridden it.
52
+ define_method(key) do |*args|
53
+ ivar = "@#{key}"
54
+ if args.empty?
55
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
56
+ return superclass.send(key) if superclass.respond_to?(key)
57
+
58
+ nil
59
+ else
60
+ instance_variable_set(ivar, args.first)
61
+ end
62
+ end
63
+ end
64
+
65
+ include Keybindings # provides `on_key` + `key_handlers`
66
+
67
+ # Set the scene class the app should switch into automatically
68
+ # at `setup` time. Inheritable: a subclass that doesn't set
69
+ # one inherits the parent's choice.
70
+ def initial_scene(klass = nil)
71
+ @initial_scene = klass if klass
72
+ return @initial_scene if instance_variable_defined?(:@initial_scene)
73
+ superclass.respond_to?(:initial_scene) ? superclass.initial_scene : nil
74
+ end
75
+ end
76
+
77
+ attr_reader :window
78
+ attr_accessor :background_color
79
+
80
+ # Per-instance kwargs override anything set at the class level.
81
+ # Anything not given here OR at the class level falls back to
82
+ # the hard-coded defaults below.
83
+ def initialize(**opts)
84
+ cfg = self.class
85
+
86
+ width = opts[:width] || cfg.width || 800
87
+ height = opts[:height] || cfg.height || 600
88
+ title = opts[:title] || cfg.title || cfg.name
89
+ framerate = opts[:framerate] || cfg.framerate || 60
90
+ vsync = opts.fetch(:vsync, cfg.vsync)
91
+ background = opts[:background] || cfg.background || Color::BLACK
92
+ style = opts.fetch(:style, cfg.style)
93
+ fullscreen = opts.fetch(:fullscreen, cfg.fullscreen)
94
+ fullscreen = false if fullscreen.nil?
95
+ antialiasing = opts.fetch(:antialiasing, cfg.antialiasing)
96
+ context = opts.fetch(:context, cfg.context)
97
+
98
+ window_opts = { framerate: framerate, fullscreen: fullscreen }
99
+ window_opts[:vsync] = vsync unless vsync.nil?
100
+ window_opts[:style] = style unless style.nil?
101
+ window_opts[:antialiasing] = antialiasing unless antialiasing.nil?
102
+ window_opts[:context] = context unless context.nil?
103
+
104
+ @window = RenderWindow.new(width, height, title, **window_opts)
105
+ @background_color = background
106
+ end
107
+
108
+ # Width and height shortcuts that always reflect the current
109
+ # window size — matters once the user is allowed to resize.
110
+ def width = @window.size.x
111
+ def height = @window.size.y
112
+
113
+ # Close the window. The main loop exits at the start of the
114
+ # next frame.
115
+ def quit
116
+ @window.close
117
+ self
118
+ end
119
+
120
+ # ---- Scenes ----
121
+ #
122
+ # `current_scene` is whatever the app last activated via
123
+ # `switch_to`. When non-nil, App's default `update` / `draw` /
124
+ # `on_event` / `on_resize` forward to it; key bindings on the
125
+ # scene class shadow ones on the app class while the scene is
126
+ # active.
127
+
128
+ attr_reader :current_scene
129
+
130
+ # Activate `scene_or_class`. Tears down the previous scene
131
+ # before calling `setup` on the new one. Accepts either:
132
+ # * a Scene subclass — instantiated with `self` as host
133
+ # * an existing Scene instance — used as-is
134
+ def switch_to(scene_or_class)
135
+ new_scene =
136
+ case scene_or_class
137
+ when Class then scene_or_class.new(self)
138
+ else scene_or_class
139
+ end
140
+ @current_scene&.teardown
141
+ @current_scene = new_scene
142
+ @current_scene&.setup
143
+ @current_scene
144
+ end
145
+
146
+ # ---- Pause / resume ----
147
+ #
148
+ # While paused, `update(dt)` is *not* called — the world stops
149
+ # advancing. `draw` keeps running so the window still updates
150
+ # (handy for pause overlays that need to be drawn over a
151
+ # frozen scene).
152
+
153
+ def pause = (@paused = true)
154
+ def resume = (@paused = false)
155
+ def toggle_pause = (@paused = !paused?)
156
+ def paused? = @paused == true
157
+
158
+ # The main entry point. Calls #setup once, then runs the
159
+ # per-frame loop until the window closes.
160
+ def run
161
+ setup
162
+ clock = Clock.new
163
+ while @window.open?
164
+ dt = clock.restart
165
+ @window.each_event { |event| _dispatch(event) }
166
+ update(dt) unless paused?
167
+ @window.clear(@background_color)
168
+ draw
169
+ @window.display
170
+ end
171
+ teardown
172
+ self
173
+ end
174
+
175
+ # ---- Override these in subclasses --------------------------------------
176
+ #
177
+ # Defaults forward to the current scene when one is active. The
178
+ # `initial_scene` class macro auto-instantiates a scene at
179
+ # `setup` time so subclasses with `initial_scene Foo` don't
180
+ # have to define `setup` themselves.
181
+
182
+ def setup
183
+ if (klass = self.class.initial_scene)
184
+ switch_to(klass)
185
+ end
186
+ end
187
+
188
+ def update(dt)
189
+ @current_scene&.update(dt)
190
+ end
191
+
192
+ def draw
193
+ @current_scene&.draw
194
+ end
195
+
196
+ # The framework consumes:
197
+ # * `:closed` — closes the window (always)
198
+ # * `:resized` — forwarded to `on_resize`
199
+ # * `:key_pressed` whose code matches a scene- or app-level
200
+ # `on_key` binding
201
+ # Everything else lands here. Override to handle game-specific
202
+ # input. By default forwards to the current scene's `on_event`.
203
+ def on_event(event)
204
+ @current_scene&.on_event(event)
205
+ end
206
+
207
+ # Default: forward to the current scene. Override to additionally
208
+ # do app-wide layout fixups; call `super` to keep the scene in
209
+ # the loop.
210
+ def on_resize(width, height)
211
+ @current_scene&.on_resize(width, height)
212
+ end
213
+
214
+ # Called once after the main loop exits. Tears down the active
215
+ # scene by default.
216
+ def teardown
217
+ @current_scene&.teardown
218
+ end
219
+
220
+ private
221
+
222
+ def _dispatch(event)
223
+ case event
224
+ in {type: :closed}
225
+ quit
226
+ in {type: :resized, size: {x:, y:}}
227
+ on_resize(x, y)
228
+ in {type: :key_pressed, code:}
229
+ # Scene-level bindings win over app-level (CSS-style cascade:
230
+ # the more-specific layer overrides the more-general one).
231
+ if @current_scene && (h = @current_scene.class.key_handlers[code])
232
+ _invoke_scene_key(h)
233
+ elsif (h = self.class.key_handlers[code])
234
+ _invoke_key_handler(h)
235
+ else
236
+ on_event(event)
237
+ end
238
+ else
239
+ on_event(event)
240
+ end
241
+ end
242
+
243
+ def _invoke_key_handler(handler)
244
+ case handler
245
+ when Symbol then send(handler)
246
+ when Proc then handler.call(self)
247
+ end
248
+ end
249
+
250
+ def _invoke_scene_key(handler)
251
+ case handler
252
+ when Symbol then @current_scene.send(handler)
253
+ when Proc then handler.call(@current_scene)
254
+ end
255
+ end
256
+ end
257
+ end
@@ -79,12 +79,15 @@ module SFML
79
79
 
80
80
  typedef :pointer, :render_window_t
81
81
 
82
- # See CSFML/Graphics/RenderWindow.h. We pass NULL for sfContextSettings
83
- # in the high-level wrapper; that matches SFML defaults.
82
+ # See CSFML/Graphics/RenderWindow.h. The fifth param is a
83
+ # `const sfContextSettings*` pass `nil` for SFML defaults
84
+ # or the pointer to a populated ContextSettings struct for
85
+ # MSAA / depth-buffer / GL-version tuning.
84
86
  attach_function :sfRenderWindow_create,
85
87
  [Window::VideoMode.by_value, :string, :uint32, :int, :pointer],
86
88
  :render_window_t
87
89
 
90
+ attach_function :sfRenderWindow_getSettings, [:render_window_t], Window::ContextSettings.by_value
88
91
  attach_function :sfRenderWindow_destroy, [:render_window_t], :void
89
92
  attach_function :sfRenderWindow_close, [:render_window_t], :void
90
93
  attach_function :sfRenderWindow_isOpen, [:render_window_t], :bool
data/lib/sfml/c/window.rb CHANGED
@@ -10,6 +10,36 @@ module SFML
10
10
  :bits_per_pixel, :uint32
11
11
  end
12
12
 
13
+ # sfContextSettings — passed to sfWindow_create / sfRenderWindow_create
14
+ # to configure the underlying OpenGL context. Field order and types
15
+ # match CSFML/Window/Window.h exactly:
16
+ #
17
+ # typedef struct {
18
+ # unsigned int depthBits;
19
+ # unsigned int stencilBits;
20
+ # unsigned int antiAliasingLevel;
21
+ # unsigned int majorVersion;
22
+ # unsigned int minorVersion;
23
+ # uint32_t attributeFlags;
24
+ # bool sRgbCapable;
25
+ # } sfContextSettings;
26
+ class ContextSettings < FFI::Struct
27
+ layout :depth_bits, :uint32,
28
+ :stencil_bits, :uint32,
29
+ :anti_aliasing_level,:uint32,
30
+ :major_version, :uint32,
31
+ :minor_version, :uint32,
32
+ :attribute_flags, :uint32,
33
+ :s_rgb_capable, :bool
34
+ end
35
+
36
+ # Context attribute bitmask values — match SFML's enum.
37
+ module ContextAttribute
38
+ DEFAULT = 0
39
+ CORE = 1 << 0
40
+ DEBUG = 1 << 2
41
+ end
42
+
13
43
  # sfStyle is a bitmask. sfWindowState is a plain enum.
14
44
  module Style
15
45
  NONE = 0
@@ -24,31 +24,51 @@ module SFML
24
24
  # The second form takes (video_mode, title, **opts) for full control.
25
25
  #
26
26
  # Options:
27
- # style: bitmask of SFML::C::Window::Style constants
28
- # fullscreen: true to use sfFullscreen state instead of sfWindowed
29
- # framerate: cap to N FPS via sfRenderWindow_setFramerateLimit
30
- # vsync: enable vertical sync
27
+ # style: bitmask of SFML::C::Window::Style constants
28
+ # fullscreen: true to use sfFullscreen state instead of sfWindowed
29
+ # framerate: cap to N FPS via sfRenderWindow_setFramerateLimit
30
+ # vsync: enable vertical sync
31
+ # antialiasing: shorthand — MSAA level (2 / 4 / 8). Same as
32
+ # passing `context: ContextSettings.new(antialiasing: N)`
33
+ # context: a SFML::ContextSettings for full GL-context control
31
34
  def initialize(*args, **opts)
32
35
  mode, title = parse_args(args)
33
36
  style = opts.fetch(:style, DEFAULT_STYLE)
34
37
  state = opts[:fullscreen] ? :fullscreen : :windowed
35
38
 
39
+ settings = _resolve_context_settings(opts)
40
+ # Hold a reference for the duration of the C call so the
41
+ # struct's memory survives until CSFML has copied it.
42
+ @ctx_struct = settings ? settings.to_native : nil
43
+ ctx_ptr = @ctx_struct ? @ctx_struct.to_ptr : nil
44
+
36
45
  ptr = C::Graphics.sfRenderWindow_create(
37
46
  mode.to_native,
38
47
  title.to_s,
39
48
  style,
40
49
  C::Window::State[state],
41
- nil,
50
+ ctx_ptr,
42
51
  )
43
52
  raise Error, "sfRenderWindow_create returned NULL" if ptr.null?
44
53
 
45
54
  @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRenderWindow_destroy))
46
55
  @event_buffer = C::Window::Event.new
56
+ @requested_context = settings
47
57
 
48
58
  self.framerate_limit = opts[:framerate] if opts[:framerate]
49
59
  self.vsync = opts[:vsync] unless opts[:vsync].nil?
50
60
  end
51
61
 
62
+ # The actual ContextSettings the driver gave us. May differ
63
+ # from what we requested — the driver picks the closest level
64
+ # of MSAA / GL version it supports.
65
+ def context_settings
66
+ ContextSettings.from_native(C::Graphics.sfRenderWindow_getSettings(@handle))
67
+ end
68
+
69
+ # What we asked for at creation time, if anything (otherwise nil).
70
+ attr_reader :requested_context
71
+
52
72
  def open?
53
73
  C::Graphics.sfRenderWindow_isOpen(@handle)
54
74
  end
@@ -182,6 +202,17 @@ module SFML
182
202
 
183
203
  private
184
204
 
205
+ def _resolve_context_settings(opts)
206
+ if opts[:context]
207
+ unless opts[:context].is_a?(ContextSettings)
208
+ raise ArgumentError, "context: must be a SFML::ContextSettings"
209
+ end
210
+ opts[:context]
211
+ elsif opts[:antialiasing]
212
+ ContextSettings.new(antialiasing: opts[:antialiasing])
213
+ end
214
+ end
215
+
185
216
  def _vec2u_or_nil(value)
186
217
  return nil if value.nil?
187
218
 
@@ -0,0 +1,28 @@
1
+ module SFML
2
+ # Class-level `on_key` DSL — shared by `SFML::App` and `SFML::Scene`.
3
+ # Each class that extends this module gets:
4
+ #
5
+ # * `on_key(code, :method_name)` — bind a key to an instance method
6
+ # * `on_key(code, ->(receiver) { ... })` — bind to a Proc
7
+ # * `on_key(code) { |receiver| ... }` — bind to a block
8
+ #
9
+ # Inheritance: a subclass's bindings layer on top of the parent's,
10
+ # so `class Sub < Parent; on_key :x, :foo; end` keeps the parent's
11
+ # other bindings while overriding (or adding) `:x`.
12
+ module Keybindings
13
+ def on_key(code, target = nil, &block)
14
+ @key_handlers ||= {}
15
+ handler = block || target
16
+ raise ArgumentError, "on_key needs a target Symbol/Proc or a block" unless handler
17
+ @key_handlers[code.to_sym] = handler
18
+ end
19
+
20
+ # `Hash{key_code => handler}` — own bindings layered over the
21
+ # parent's (own keys win).
22
+ def key_handlers
23
+ own = (@key_handlers ||= {})
24
+ parent = superclass.respond_to?(:key_handlers) ? superclass.key_handlers : {}
25
+ parent.merge(own)
26
+ end
27
+ end
28
+ end
data/lib/sfml/scene.rb ADDED
@@ -0,0 +1,82 @@
1
+ module SFML
2
+ # A chunk of game state with its own lifecycle — menu, gameplay,
3
+ # game-over, settings overlay, etc. The host `SFML::App` keeps a
4
+ # `current_scene` and forwards `update` / `draw` / `on_event` /
5
+ # `on_resize` to it. Switch between scenes with `app.switch_to`
6
+ # (or `switch_to` from inside a scene, which delegates).
7
+ #
8
+ # class TitleScene < SFML::Scene
9
+ # on_key :enter, :start_game
10
+ #
11
+ # def setup
12
+ # @gui = SFML::GUI::App.new(window: window, stylesheet: SHEET)
13
+ # # ... build UI ...
14
+ # end
15
+ #
16
+ # def update(dt) = @gui.update(dt)
17
+ # def draw = @gui.draw
18
+ #
19
+ # def start_game
20
+ # switch_to PlayScene
21
+ # end
22
+ # end
23
+ #
24
+ # class PlayScene < SFML::Scene
25
+ # # …
26
+ # end
27
+ #
28
+ # class MyApp < SFML::App
29
+ # initial_scene TitleScene # auto-switches to it on app setup
30
+ # end
31
+ #
32
+ # `SFML::Scene` carries its own `on_key` DSL — scene-level
33
+ # bindings shadow app-level bindings while the scene is active.
34
+ class Scene
35
+ class << self
36
+ include Keybindings
37
+ end
38
+
39
+ attr_reader :app
40
+
41
+ def initialize(app)
42
+ @app = app
43
+ end
44
+
45
+ # Convenience accessors that match `SFML::App`'s.
46
+ def window = @app.window
47
+ def width = @app.width
48
+ def height = @app.height
49
+
50
+ # Switch the host app to a new scene from inside this one.
51
+ # Accepts a Scene class (instantiated with `app`) or an already-
52
+ # constructed Scene instance.
53
+ def switch_to(scene_or_class)
54
+ @app.switch_to(scene_or_class)
55
+ end
56
+
57
+ # ---- Override these in subclasses --------------------------------------
58
+
59
+ # Called once when the host app activates this scene (replacing
60
+ # whatever scene was active before). Build per-scene state here.
61
+ def setup; end
62
+
63
+ # Called every frame the scene is active and the app isn't paused.
64
+ def update(_dt); end
65
+
66
+ # Called every frame after #update.
67
+ def draw; end
68
+
69
+ # Called for every event the framework didn't auto-handle (closed,
70
+ # resized, and on_key bindings). Override to handle game-specific
71
+ # input patterns.
72
+ def on_event(_event); end
73
+
74
+ # Window resize. Default: no-op.
75
+ def on_resize(_width, _height); end
76
+
77
+ # Called once when the host app switches away (or when the app
78
+ # loop exits while this scene is active). Free per-scene
79
+ # resources here.
80
+ def teardown; end
81
+ end
82
+ 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.1"
18
+ VERSION = "3.0.0.2"
19
19
  end
@@ -0,0 +1,121 @@
1
+ module SFML
2
+ # Configuration for the underlying OpenGL context that backs a
3
+ # `Window` / `RenderWindow`. Passed at window-creation time to
4
+ # request anti-aliasing, depth/stencil buffers, or a specific
5
+ # OpenGL version.
6
+ #
7
+ # settings = SFML::ContextSettings.new(antialiasing: 4)
8
+ # window = SFML::RenderWindow.new(800, 600, "Smooth", context: settings)
9
+ #
10
+ # Or use the `RenderWindow.new` shortcut directly:
11
+ #
12
+ # window = SFML::RenderWindow.new(800, 600, "Smooth", antialiasing: 4)
13
+ #
14
+ # Anti-aliasing is the most common knob — typical values are 0
15
+ # (off), 2, 4, or 8. The driver picks the closest level it
16
+ # actually supports; query `window.context_settings` after
17
+ # creation to see what you got.
18
+ class ContextSettings
19
+ DEFAULT_DEPTH_BITS = 0
20
+ DEFAULT_STENCIL_BITS = 0
21
+ DEFAULT_AA_LEVEL = 0
22
+ DEFAULT_MAJOR_VERSION = 1
23
+ DEFAULT_MINOR_VERSION = 1
24
+
25
+ ATTRIBUTE_FLAGS = {
26
+ default: C::Window::ContextAttribute::DEFAULT,
27
+ core: C::Window::ContextAttribute::CORE,
28
+ debug: C::Window::ContextAttribute::DEBUG,
29
+ }.freeze
30
+
31
+ attr_reader :depth_bits, :stencil_bits, :antialiasing,
32
+ :major_version, :minor_version, :attribute_flags,
33
+ :srgb_capable
34
+
35
+ def initialize(depth_bits: DEFAULT_DEPTH_BITS,
36
+ stencil_bits: DEFAULT_STENCIL_BITS,
37
+ antialiasing: DEFAULT_AA_LEVEL,
38
+ major_version: DEFAULT_MAJOR_VERSION,
39
+ minor_version: DEFAULT_MINOR_VERSION,
40
+ attributes: :default,
41
+ srgb_capable: false)
42
+ @depth_bits = Integer(depth_bits)
43
+ @stencil_bits = Integer(stencil_bits)
44
+ @antialiasing = Integer(antialiasing)
45
+ @major_version = Integer(major_version)
46
+ @minor_version = Integer(minor_version)
47
+ @attribute_flags = _attribute_mask(attributes)
48
+ @srgb_capable = !!srgb_capable
49
+ freeze
50
+ end
51
+
52
+ # Build the matching CSFML struct on the heap. Returned struct
53
+ # is owned by the caller (FFI::AutoPointer is *not* set — pass
54
+ # by reference into a window-creation call and let it copy).
55
+ def to_native
56
+ s = C::Window::ContextSettings.new
57
+ s[:depth_bits] = @depth_bits
58
+ s[:stencil_bits] = @stencil_bits
59
+ s[:anti_aliasing_level] = @antialiasing
60
+ s[:major_version] = @major_version
61
+ s[:minor_version] = @minor_version
62
+ s[:attribute_flags] = @attribute_flags
63
+ s[:s_rgb_capable] = @srgb_capable
64
+ s
65
+ end
66
+
67
+ def self.from_native(struct)
68
+ attrs = ATTRIBUTE_FLAGS.find { |_, v| v == struct[:attribute_flags] }&.first || :default
69
+ new(
70
+ depth_bits: struct[:depth_bits],
71
+ stencil_bits: struct[:stencil_bits],
72
+ antialiasing: struct[:anti_aliasing_level],
73
+ major_version: struct[:major_version],
74
+ minor_version: struct[:minor_version],
75
+ attributes: attrs,
76
+ srgb_capable: struct[:s_rgb_capable],
77
+ )
78
+ end
79
+
80
+ def ==(other)
81
+ other.is_a?(ContextSettings) &&
82
+ depth_bits == other.depth_bits &&
83
+ stencil_bits == other.stencil_bits &&
84
+ antialiasing == other.antialiasing &&
85
+ major_version == other.major_version &&
86
+ minor_version == other.minor_version &&
87
+ attribute_flags == other.attribute_flags &&
88
+ srgb_capable == other.srgb_capable
89
+ end
90
+ alias eql? ==
91
+
92
+ def hash = [@depth_bits, @stencil_bits, @antialiasing,
93
+ @major_version, @minor_version, @attribute_flags,
94
+ @srgb_capable].hash
95
+
96
+ def to_s
97
+ "ContextSettings(aa=#{@antialiasing}, depth=#{@depth_bits}, " \
98
+ "stencil=#{@stencil_bits}, gl=#{@major_version}.#{@minor_version})"
99
+ end
100
+ alias inspect to_s
101
+
102
+ private
103
+
104
+ def _attribute_mask(attrs)
105
+ case attrs
106
+ when Integer then attrs
107
+ when Symbol then ATTRIBUTE_FLAGS.fetch(attrs) do
108
+ raise ArgumentError, "unknown attribute :#{attrs}; one of #{ATTRIBUTE_FLAGS.keys}"
109
+ end
110
+ when Array
111
+ attrs.reduce(0) do |mask, name|
112
+ mask | ATTRIBUTE_FLAGS.fetch(name) do
113
+ raise ArgumentError, "unknown attribute :#{name}; one of #{ATTRIBUTE_FLAGS.keys}"
114
+ end
115
+ end
116
+ else
117
+ raise ArgumentError, "expected Symbol/Array/Integer, got #{attrs.class}"
118
+ end
119
+ end
120
+ end
121
+ end
data/lib/sfml.rb CHANGED
@@ -80,6 +80,7 @@ require "sfml/window/cursor"
80
80
  require "sfml/window/clipboard"
81
81
  require "sfml/window/video_mode"
82
82
  require "sfml/window/event"
83
+ require "sfml/window/context_settings"
83
84
  require "sfml/window/window"
84
85
  require "sfml/graphics/color"
85
86
  require "sfml/graphics/transformable"
@@ -120,4 +121,6 @@ require "sfml/network/socket_selector"
120
121
  require "sfml/network/http"
121
122
  require "sfml/network/ftp"
122
123
  require "sfml/assets"
123
- require "sfml/game"
124
+ require "sfml/keybindings"
125
+ require "sfml/scene"
126
+ require "sfml/app"
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.1
4
+ version: 3.0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mykhailo Melnyk
@@ -79,6 +79,7 @@ files:
79
79
  - README.md
80
80
  - ext/ruby-sfml/extconf.rb
81
81
  - lib/sfml.rb
82
+ - lib/sfml/app.rb
82
83
  - lib/sfml/assets.rb
83
84
  - lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt
84
85
  - lib/sfml/assets/fonts/DejaVuSans.ttf
@@ -97,7 +98,6 @@ files:
97
98
  - lib/sfml/c/network.rb
98
99
  - lib/sfml/c/system.rb
99
100
  - lib/sfml/c/window.rb
100
- - lib/sfml/game.rb
101
101
  - lib/sfml/graphics/blend_mode.rb
102
102
  - lib/sfml/graphics/circle_shape.rb
103
103
  - lib/sfml/graphics/color.rb
@@ -120,6 +120,7 @@ files:
120
120
  - lib/sfml/graphics/vertex_array.rb
121
121
  - lib/sfml/graphics/vertex_buffer.rb
122
122
  - lib/sfml/graphics/view.rb
123
+ - lib/sfml/keybindings.rb
123
124
  - lib/sfml/network/ftp.rb
124
125
  - lib/sfml/network/http.rb
125
126
  - lib/sfml/network/ip_address.rb
@@ -127,6 +128,7 @@ files:
127
128
  - lib/sfml/network/tcp_listener.rb
128
129
  - lib/sfml/network/tcp_socket.rb
129
130
  - lib/sfml/network/udp_socket.rb
131
+ - lib/sfml/scene.rb
130
132
  - lib/sfml/system/clock.rb
131
133
  - lib/sfml/system/rect.rb
132
134
  - lib/sfml/system/time.rb
@@ -134,6 +136,7 @@ files:
134
136
  - lib/sfml/system/vector3.rb
135
137
  - lib/sfml/version.rb
136
138
  - lib/sfml/window/clipboard.rb
139
+ - lib/sfml/window/context_settings.rb
137
140
  - lib/sfml/window/cursor.rb
138
141
  - lib/sfml/window/event.rb
139
142
  - lib/sfml/window/joystick.rb
data/lib/sfml/game.rb DELETED
@@ -1,101 +0,0 @@
1
- module SFML
2
- # Subclass-friendly main loop. Removes the boilerplate of window creation,
3
- # event pumping, clock management, and clear/display so a small game fits
4
- # in a few methods.
5
- #
6
- # class MyGame < SFML::Game
7
- # def setup
8
- # @ball = SFML::CircleShape.new(radius: 30, position: [200, 200],
9
- # fill_color: SFML::Color.white)
10
- # end
11
- #
12
- # def update(dt)
13
- # @ball.move([60 * dt.as_seconds, 30 * dt.as_seconds])
14
- # end
15
- #
16
- # def draw
17
- # window.draw(@ball)
18
- # end
19
- # end
20
- #
21
- # MyGame.new(title: "Hello").run
22
- #
23
- # The loop auto-handles :closed and :key_pressed/:escape by calling #quit;
24
- # everything else is forwarded to #on_event. Override that to handle keys,
25
- # mouse, etc.
26
- class Game
27
- attr_reader :window
28
- attr_accessor :background_color
29
-
30
- def initialize(
31
- width: 800, height: 600, title: nil,
32
- framerate: 60, vsync: nil,
33
- background: Color::BLACK,
34
- style: nil, fullscreen: false
35
- )
36
- window_opts = { framerate: framerate, fullscreen: fullscreen }
37
- window_opts[:vsync] = vsync unless vsync.nil?
38
- window_opts[:style] = style unless style.nil?
39
-
40
- @window = RenderWindow.new(width, height, title || self.class.name, **window_opts)
41
- @background_color = background
42
- end
43
-
44
- # Width and height shortcuts that always reflect the current window size,
45
- # which matters once the user is allowed to resize.
46
- def width = @window.size.x
47
- def height = @window.size.y
48
-
49
- # Close the window. The main loop exits at the start of the next frame.
50
- def quit
51
- @window.close
52
- self
53
- end
54
-
55
- # The main entry point. Calls #setup once, then runs the per-frame loop
56
- # until the window closes.
57
- def run
58
- setup
59
- clock = Clock.new
60
- while @window.open?
61
- dt = clock.restart
62
- @window.each_event { |event| _dispatch(event) }
63
- update(dt)
64
- @window.clear(@background_color)
65
- draw
66
- @window.display
67
- end
68
- teardown
69
- self
70
- end
71
-
72
- # ---- Override these in subclasses --------------------------------------
73
-
74
- # Called once, just before the first frame. Build initial state here.
75
- def setup; end
76
-
77
- # Called every frame. `dt` is a SFML::Time covering the previous frame.
78
- def update(dt); end
79
-
80
- # Called every frame after #update. Use window.draw(...) for any drawables.
81
- def draw; end
82
-
83
- # Called for events the framework didn't auto-handle (everything except
84
- # :closed and the Esc key). Call #quit from here to exit on a custom key.
85
- def on_event(event); end
86
-
87
- # Called once after the window closes. Useful for saving state. Default
88
- # is a no-op.
89
- def teardown; end
90
-
91
- private
92
-
93
- def _dispatch(event)
94
- case event
95
- in {type: :closed} then quit
96
- in {type: :key_pressed, code: :escape} then quit
97
- else on_event(event)
98
- end
99
- end
100
- end
101
- end