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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module C
|
|
3
|
+
module Window
|
|
4
|
+
extend FFI::Library
|
|
5
|
+
|
|
6
|
+
ffi_lib LIB_CANDIDATES[:window]
|
|
7
|
+
|
|
8
|
+
class VideoMode < FFI::Struct
|
|
9
|
+
layout :size, System::Vector2u,
|
|
10
|
+
:bits_per_pixel, :uint32
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# sfStyle is a bitmask. sfWindowState is a plain enum.
|
|
14
|
+
module Style
|
|
15
|
+
NONE = 0
|
|
16
|
+
TITLEBAR = 1 << 0
|
|
17
|
+
RESIZE = 1 << 1
|
|
18
|
+
CLOSE = 1 << 2
|
|
19
|
+
DEFAULT = TITLEBAR | RESIZE | CLOSE
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
State = enum :window_state, [:windowed, :fullscreen]
|
|
23
|
+
|
|
24
|
+
# sfEventType — order MUST match CSFML/Window/Event.h. We expose this as
|
|
25
|
+
# an :int FFI type and translate to/from Ruby symbols in the high-level
|
|
26
|
+
# SFML::Event class. Order is load-bearing.
|
|
27
|
+
EVENT_TYPES = %i[
|
|
28
|
+
closed
|
|
29
|
+
resized
|
|
30
|
+
focus_lost
|
|
31
|
+
focus_gained
|
|
32
|
+
text_entered
|
|
33
|
+
key_pressed
|
|
34
|
+
key_released
|
|
35
|
+
mouse_wheel_scrolled
|
|
36
|
+
mouse_button_pressed
|
|
37
|
+
mouse_button_released
|
|
38
|
+
mouse_moved
|
|
39
|
+
mouse_moved_raw
|
|
40
|
+
mouse_entered
|
|
41
|
+
mouse_left
|
|
42
|
+
joystick_button_pressed
|
|
43
|
+
joystick_button_released
|
|
44
|
+
joystick_moved
|
|
45
|
+
joystick_connected
|
|
46
|
+
joystick_disconnected
|
|
47
|
+
touch_began
|
|
48
|
+
touch_moved
|
|
49
|
+
touch_ended
|
|
50
|
+
sensor_changed
|
|
51
|
+
].freeze
|
|
52
|
+
|
|
53
|
+
# Per-variant structs of the sfEvent union.
|
|
54
|
+
class KeyEvent < FFI::Struct
|
|
55
|
+
layout :type, :int,
|
|
56
|
+
:code, :int32,
|
|
57
|
+
:scancode, :int32,
|
|
58
|
+
:alt, :bool,
|
|
59
|
+
:control, :bool,
|
|
60
|
+
:shift, :bool,
|
|
61
|
+
:system, :bool
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class TextEvent < FFI::Struct
|
|
65
|
+
layout :type, :int, :unicode, :uint32
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class SizeEvent < FFI::Struct
|
|
69
|
+
layout :type, :int, :size, System::Vector2u
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class MouseMoveEvent < FFI::Struct
|
|
73
|
+
layout :type, :int, :position, System::Vector2i
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class MouseButtonEvent < FFI::Struct
|
|
77
|
+
layout :type, :int,
|
|
78
|
+
:button, :int,
|
|
79
|
+
:position, System::Vector2i
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class MouseWheelScrollEvent < FFI::Struct
|
|
83
|
+
layout :type, :int,
|
|
84
|
+
:wheel, :int,
|
|
85
|
+
:delta, :float,
|
|
86
|
+
:position, System::Vector2i
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class JoystickButtonEvent < FFI::Struct
|
|
90
|
+
layout :type, :int,
|
|
91
|
+
:joystick_id, :uint32,
|
|
92
|
+
:button, :uint32
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class JoystickMoveEvent < FFI::Struct
|
|
96
|
+
layout :type, :int,
|
|
97
|
+
:joystick_id, :uint32,
|
|
98
|
+
:axis, :int,
|
|
99
|
+
:position, :float
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class JoystickConnectEvent < FFI::Struct
|
|
103
|
+
layout :type, :int,
|
|
104
|
+
:joystick_id, :uint32
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# sfEvent is a C union. The largest variant (KeyEvent on x86_64: 4+4+4+4
|
|
108
|
+
# = 20 bytes) defines the union size; we allocate a buffer that big and
|
|
109
|
+
# reinterpret per-type. We use a Struct (not Union) here because Ruby
|
|
110
|
+
# FFI handles variable-tag unions awkwardly — the tag is the first int32
|
|
111
|
+
# in every variant, so we read it directly.
|
|
112
|
+
class Event < FFI::Struct
|
|
113
|
+
layout :type, :int, :_pad, [:uint8, 28]
|
|
114
|
+
|
|
115
|
+
def event_type
|
|
116
|
+
EVENT_TYPES[self[:type]]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
attach_function :sfVideoMode_getDesktopMode, [], VideoMode.by_value
|
|
121
|
+
|
|
122
|
+
attach_function :sfKeyboard_isKeyPressed, [:int], :bool
|
|
123
|
+
|
|
124
|
+
# Mouse: position queries here use a sfWindow*. Pass NULL to get
|
|
125
|
+
# desktop-relative coordinates. The render-window variants live in
|
|
126
|
+
# SFML::C::Graphics (different shared library).
|
|
127
|
+
attach_function :sfMouse_isButtonPressed, [:int], :bool
|
|
128
|
+
attach_function :sfMouse_getPosition, [:pointer], System::Vector2i.by_value
|
|
129
|
+
attach_function :sfMouse_setPosition, [System::Vector2i.by_value, :pointer], :void
|
|
130
|
+
|
|
131
|
+
# ---- Cursor ----
|
|
132
|
+
typedef :pointer, :cursor_t
|
|
133
|
+
|
|
134
|
+
attach_function :sfCursor_createFromPixels, [:pointer, System::Vector2u.by_value, System::Vector2u.by_value], :cursor_t
|
|
135
|
+
attach_function :sfCursor_createFromSystem, [:int], :cursor_t
|
|
136
|
+
attach_function :sfCursor_destroy, [:cursor_t], :void
|
|
137
|
+
|
|
138
|
+
# ---- Clipboard ----
|
|
139
|
+
attach_function :sfClipboard_getString, [], :string
|
|
140
|
+
attach_function :sfClipboard_setString, [:string], :void
|
|
141
|
+
attach_function :sfClipboard_getUnicodeString, [], :pointer
|
|
142
|
+
attach_function :sfClipboard_setUnicodeString, [:pointer], :void
|
|
143
|
+
|
|
144
|
+
# ---- Joystick ----
|
|
145
|
+
class JoystickIdentification < FFI::Struct
|
|
146
|
+
layout :name, :pointer, # const char*
|
|
147
|
+
:vendor_id, :uint32,
|
|
148
|
+
:product_id, :uint32
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
attach_function :sfJoystick_isConnected, [:uint32], :bool
|
|
152
|
+
attach_function :sfJoystick_getButtonCount, [:uint32], :uint32
|
|
153
|
+
attach_function :sfJoystick_hasAxis, [:uint32, :int], :bool
|
|
154
|
+
attach_function :sfJoystick_isButtonPressed, [:uint32, :uint32], :bool
|
|
155
|
+
attach_function :sfJoystick_getAxisPosition, [:uint32, :int], :float
|
|
156
|
+
attach_function :sfJoystick_getIdentification, [:uint32], JoystickIdentification.by_value
|
|
157
|
+
attach_function :sfJoystick_update, [], :void
|
|
158
|
+
|
|
159
|
+
# ---- Bare Window (no rendering) ----
|
|
160
|
+
# SFML 3 splits sf::Window (pure window + GL context) from
|
|
161
|
+
# sf::RenderWindow (window + 2D batcher). The bare variant is
|
|
162
|
+
# useful only when you're driving raw OpenGL yourself.
|
|
163
|
+
typedef :pointer, :raw_window_t
|
|
164
|
+
|
|
165
|
+
attach_function :sfWindow_create, [VideoMode.by_value, :string, :uint32, :int, :pointer], :raw_window_t
|
|
166
|
+
attach_function :sfWindow_destroy, [:raw_window_t], :void
|
|
167
|
+
attach_function :sfWindow_close, [:raw_window_t], :void
|
|
168
|
+
attach_function :sfWindow_isOpen, [:raw_window_t], :bool
|
|
169
|
+
attach_function :sfWindow_pollEvent, [:raw_window_t, :pointer], :bool
|
|
170
|
+
attach_function :sfWindow_waitEvent, [:raw_window_t, System::Time.by_value, :pointer], :bool
|
|
171
|
+
attach_function :sfWindow_display, [:raw_window_t], :void
|
|
172
|
+
attach_function :sfWindow_setVisible, [:raw_window_t, :bool], :void
|
|
173
|
+
attach_function :sfWindow_setTitle, [:raw_window_t, :string], :void
|
|
174
|
+
attach_function :sfWindow_getSize, [:raw_window_t], System::Vector2u.by_value
|
|
175
|
+
attach_function :sfWindow_setSize, [:raw_window_t, System::Vector2u.by_value], :void
|
|
176
|
+
attach_function :sfWindow_getPosition, [:raw_window_t], System::Vector2i.by_value
|
|
177
|
+
attach_function :sfWindow_setPosition, [:raw_window_t, System::Vector2i.by_value], :void
|
|
178
|
+
attach_function :sfWindow_setFramerateLimit, [:raw_window_t, :uint32], :void
|
|
179
|
+
attach_function :sfWindow_setVerticalSyncEnabled, [:raw_window_t, :bool], :void
|
|
180
|
+
attach_function :sfWindow_setKeyRepeatEnabled, [:raw_window_t, :bool], :void
|
|
181
|
+
attach_function :sfWindow_requestFocus, [:raw_window_t], :void
|
|
182
|
+
attach_function :sfWindow_hasFocus, [:raw_window_t], :bool
|
|
183
|
+
attach_function :sfWindow_setActive, [:raw_window_t, :bool], :bool
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/sfml/c.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "ffi"
|
|
2
|
+
|
|
3
|
+
module SFML
|
|
4
|
+
# Low-level FFI bindings to CSFML 3.x. One module per CSFML library.
|
|
5
|
+
#
|
|
6
|
+
# End users should not call into here directly. The high-level Ruby classes
|
|
7
|
+
# (SFML::Vector2, SFML::Clock, SFML::RenderWindow, ...) wrap these. This
|
|
8
|
+
# layer exists to be replaceable: when CSFML ships a new minor version, only
|
|
9
|
+
# the files under SFML::C should need to move.
|
|
10
|
+
module C
|
|
11
|
+
# Library name candidates per CSFML module. Each value is an Array passed
|
|
12
|
+
# as a single argument to ffi_lib — FFI then tries the names in order and
|
|
13
|
+
# uses the first one it can dlopen. (Splatting would tell FFI to load all
|
|
14
|
+
# of them at once, not as alternatives.)
|
|
15
|
+
#
|
|
16
|
+
# The bare name ("csfml-system") is what FFI auto-decorates per platform:
|
|
17
|
+
# Linux → libcsfml-system.so
|
|
18
|
+
# macOS → libcsfml-system.dylib
|
|
19
|
+
# Windows → csfml-system.dll
|
|
20
|
+
# The remaining entries are explicit fallbacks for systems where the
|
|
21
|
+
# unversioned symlink is missing (e.g. runtime-only installs without -dev).
|
|
22
|
+
LIB_CANDIDATES = {
|
|
23
|
+
system: ["csfml-system", "libcsfml-system.so.3.0", "libcsfml-system.3.0.0.dylib"],
|
|
24
|
+
window: ["csfml-window", "libcsfml-window.so.3.0", "libcsfml-window.3.0.0.dylib"],
|
|
25
|
+
graphics: ["csfml-graphics", "libcsfml-graphics.so.3.0", "libcsfml-graphics.3.0.0.dylib"],
|
|
26
|
+
audio: ["csfml-audio", "libcsfml-audio.so.3.0", "libcsfml-audio.3.0.0.dylib"],
|
|
27
|
+
network: ["csfml-network", "libcsfml-network.so.3.0", "libcsfml-network.3.0.0.dylib"],
|
|
28
|
+
}.freeze
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Version probe — runs before any binding modules attach their full
|
|
33
|
+
# function tables. We try to attach a single CSFML 3.0+ symbol; if it's
|
|
34
|
+
# missing the linked libcsfml is older (e.g. Ubuntu 22.04 / 24.04 ship
|
|
35
|
+
# CSFML 2.5 in their repos). Trip a clear SFML::LoadError now rather
|
|
36
|
+
# than letting users hit a cryptic FFI::NotFoundError mid-attach when
|
|
37
|
+
# they `require "sfml"` for the first time.
|
|
38
|
+
begin
|
|
39
|
+
probe = Module.new
|
|
40
|
+
probe.extend FFI::Library
|
|
41
|
+
probe.ffi_lib SFML::C::LIB_CANDIDATES[:system]
|
|
42
|
+
# sfClock_isRunning is part of the SFML 3.0 sf::Clock rewrite — not
|
|
43
|
+
# present in any 2.x release. Cheap and dependency-free to probe.
|
|
44
|
+
probe.attach_function :sfClock_isRunning, [:pointer], :bool
|
|
45
|
+
rescue FFI::NotFoundError
|
|
46
|
+
raise SFML::LoadError, <<~MSG.chomp
|
|
47
|
+
|
|
48
|
+
============================================================================
|
|
49
|
+
ruby-sfml requires CSFML #{SFML::CSFML_VERSION} or compatible.
|
|
50
|
+
|
|
51
|
+
The libcsfml-system on your system is older than 3.0 — it's missing
|
|
52
|
+
'sfClock_isRunning', which was added in the SFML 3.0 / CSFML 3.0
|
|
53
|
+
release (March 2025).
|
|
54
|
+
|
|
55
|
+
Fix:
|
|
56
|
+
Ubuntu 25.04+ / Debian: sudo apt install libcsfml-dev
|
|
57
|
+
Ubuntu 22.04 / 24.04: repo is too old, build from source:
|
|
58
|
+
https://github.com/SFML/CSFML/releases/tag/3.0.0
|
|
59
|
+
macOS (brew): brew upgrade csfml
|
|
60
|
+
Arch Linux: sudo pacman -S csfml
|
|
61
|
+
Windows: https://www.sfml-dev.org/download/csfml/
|
|
62
|
+
|
|
63
|
+
See also: https://github.com/SFML/CSFML/releases for the latest 3.x.
|
|
64
|
+
============================================================================
|
|
65
|
+
MSG
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
require "sfml/c/system"
|
|
69
|
+
require "sfml/c/window"
|
|
70
|
+
require "sfml/c/graphics"
|
|
71
|
+
require "sfml/c/audio"
|
|
72
|
+
require "sfml/c/network"
|
data/lib/sfml/game.rb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# How source pixels combine with destination pixels when drawing.
|
|
3
|
+
# Use the named constants for the common cases:
|
|
4
|
+
#
|
|
5
|
+
# window.draw(sprite, blend_mode: SFML::BlendMode::ADD)
|
|
6
|
+
#
|
|
7
|
+
# ADD — bright shapes accumulate (great for glow / lighting)
|
|
8
|
+
# MULTIPLY — darken (shadows, screen-style overlays)
|
|
9
|
+
# MIN / MAX — pick the darker / brighter pixel
|
|
10
|
+
# NONE — overwrite, ignoring the destination
|
|
11
|
+
# ALPHA — the default; src*α blended onto dst
|
|
12
|
+
#
|
|
13
|
+
# Build a custom mode with kwargs:
|
|
14
|
+
#
|
|
15
|
+
# SFML::BlendMode.new(
|
|
16
|
+
# color_src: :src_alpha, color_dst: :one, color_eq: :add,
|
|
17
|
+
# alpha_src: :one, alpha_dst: :one, alpha_eq: :add,
|
|
18
|
+
# )
|
|
19
|
+
class BlendMode
|
|
20
|
+
# Order matches sfBlendFactor in CSFML 3.
|
|
21
|
+
FACTORS = %i[
|
|
22
|
+
zero one src_color one_minus_src_color dst_color one_minus_dst_color
|
|
23
|
+
src_alpha one_minus_src_alpha dst_alpha one_minus_dst_alpha
|
|
24
|
+
].freeze
|
|
25
|
+
FACTOR_INDEX = FACTORS.each_with_index.to_h.freeze
|
|
26
|
+
|
|
27
|
+
EQUATIONS = %i[add subtract reverse_subtract min max].freeze
|
|
28
|
+
EQUATION_INDEX = EQUATIONS.each_with_index.to_h.freeze
|
|
29
|
+
|
|
30
|
+
attr_reader :color_src, :color_dst, :color_eq,
|
|
31
|
+
:alpha_src, :alpha_dst, :alpha_eq
|
|
32
|
+
|
|
33
|
+
def initialize(color_src:, color_dst:, color_eq:,
|
|
34
|
+
alpha_src:, alpha_dst:, alpha_eq:)
|
|
35
|
+
@color_src = _check_factor(color_src)
|
|
36
|
+
@color_dst = _check_factor(color_dst)
|
|
37
|
+
@color_eq = _check_equation(color_eq)
|
|
38
|
+
@alpha_src = _check_factor(alpha_src)
|
|
39
|
+
@alpha_dst = _check_factor(alpha_dst)
|
|
40
|
+
@alpha_eq = _check_equation(alpha_eq)
|
|
41
|
+
freeze
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ==(other)
|
|
45
|
+
other.is_a?(BlendMode) &&
|
|
46
|
+
color_src == other.color_src && color_dst == other.color_dst && color_eq == other.color_eq &&
|
|
47
|
+
alpha_src == other.alpha_src && alpha_dst == other.alpha_dst && alpha_eq == other.alpha_eq
|
|
48
|
+
end
|
|
49
|
+
alias eql? ==
|
|
50
|
+
def hash = [color_src, color_dst, color_eq, alpha_src, alpha_dst, alpha_eq].hash
|
|
51
|
+
|
|
52
|
+
def to_s
|
|
53
|
+
"BlendMode(color: #{color_src}/#{color_dst}/#{color_eq}, alpha: #{alpha_src}/#{alpha_dst}/#{alpha_eq})"
|
|
54
|
+
end
|
|
55
|
+
alias inspect to_s
|
|
56
|
+
|
|
57
|
+
# @!visibility private
|
|
58
|
+
# Write our six fields into a CSFML BlendMode struct (or a sub-struct
|
|
59
|
+
# of an existing RenderStates buffer).
|
|
60
|
+
def populate(struct)
|
|
61
|
+
struct[:color_src_factor] = FACTOR_INDEX[@color_src]
|
|
62
|
+
struct[:color_dst_factor] = FACTOR_INDEX[@color_dst]
|
|
63
|
+
struct[:color_equation] = EQUATION_INDEX[@color_eq]
|
|
64
|
+
struct[:alpha_src_factor] = FACTOR_INDEX[@alpha_src]
|
|
65
|
+
struct[:alpha_dst_factor] = FACTOR_INDEX[@alpha_dst]
|
|
66
|
+
struct[:alpha_equation] = EQUATION_INDEX[@alpha_eq]
|
|
67
|
+
struct
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @!visibility private
|
|
71
|
+
def self.from_native(struct)
|
|
72
|
+
new(
|
|
73
|
+
color_src: FACTORS[struct[:color_src_factor]],
|
|
74
|
+
color_dst: FACTORS[struct[:color_dst_factor]],
|
|
75
|
+
color_eq: EQUATIONS[struct[:color_equation]],
|
|
76
|
+
alpha_src: FACTORS[struct[:alpha_src_factor]],
|
|
77
|
+
alpha_dst: FACTORS[struct[:alpha_dst_factor]],
|
|
78
|
+
alpha_eq: EQUATIONS[struct[:alpha_equation]],
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Defined as private at the bottom of the file would arrive too late
|
|
83
|
+
# — the constants below call .new through .from_native, which goes
|
|
84
|
+
# through #initialize, which uses these checks.
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def _check_factor(name)
|
|
88
|
+
raise ArgumentError, "Unknown blend factor: #{name.inspect}" unless FACTOR_INDEX.key?(name)
|
|
89
|
+
name
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def _check_equation(name)
|
|
93
|
+
raise ArgumentError, "Unknown blend equation: #{name.inspect}" unless EQUATION_INDEX.key?(name)
|
|
94
|
+
name
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
public
|
|
98
|
+
|
|
99
|
+
# Built-in modes — read straight out of CSFML's global constants so
|
|
100
|
+
# we never have to hand-maintain the canonical factor lists.
|
|
101
|
+
ALPHA = from_native(C::Graphics.sfBlendAlpha)
|
|
102
|
+
ADD = from_native(C::Graphics.sfBlendAdd)
|
|
103
|
+
MULTIPLY = from_native(C::Graphics.sfBlendMultiply)
|
|
104
|
+
MIN = from_native(C::Graphics.sfBlendMin)
|
|
105
|
+
MAX = from_native(C::Graphics.sfBlendMax)
|
|
106
|
+
NONE = from_native(C::Graphics.sfBlendNone)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A filled circle (or regular polygon if you set point_count). Cheapest
|
|
3
|
+
# drawable that requires no asset file — perfect for placeholders.
|
|
4
|
+
#
|
|
5
|
+
# ball = SFML::CircleShape.new(
|
|
6
|
+
# radius: 20,
|
|
7
|
+
# position: [400, 300],
|
|
8
|
+
# fill_color: SFML::Color.red,
|
|
9
|
+
# )
|
|
10
|
+
# window.draw(ball)
|
|
11
|
+
class CircleShape
|
|
12
|
+
include Graphics::Transformable
|
|
13
|
+
CSFML_PREFIX = :sfCircleShape
|
|
14
|
+
|
|
15
|
+
def initialize(radius: 10.0, **opts)
|
|
16
|
+
ptr = C::Graphics.sfCircleShape_create
|
|
17
|
+
raise Error, "sfCircleShape_create returned NULL" if ptr.null?
|
|
18
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfCircleShape_destroy))
|
|
19
|
+
|
|
20
|
+
self.radius = radius
|
|
21
|
+
self.point_count = opts[:point_count] if opts[:point_count]
|
|
22
|
+
self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
|
|
23
|
+
self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
|
|
24
|
+
self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
|
|
25
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
26
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
27
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
28
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def radius = C::Graphics.sfCircleShape_getRadius(@handle)
|
|
32
|
+
|
|
33
|
+
def radius=(value)
|
|
34
|
+
C::Graphics.sfCircleShape_setRadius(@handle, value.to_f)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def point_count = C::Graphics.sfCircleShape_getPointCount(@handle)
|
|
38
|
+
|
|
39
|
+
def point_count=(n)
|
|
40
|
+
C::Graphics.sfCircleShape_setPointCount(@handle, Integer(n))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fill_color = Color.from_native(C::Graphics.sfCircleShape_getFillColor(@handle))
|
|
44
|
+
|
|
45
|
+
def fill_color=(c)
|
|
46
|
+
C::Graphics.sfCircleShape_setFillColor(@handle, c.to_native)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def outline_color = Color.from_native(C::Graphics.sfCircleShape_getOutlineColor(@handle))
|
|
50
|
+
|
|
51
|
+
def outline_color=(c)
|
|
52
|
+
C::Graphics.sfCircleShape_setOutlineColor(@handle, c.to_native)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def outline_thickness = C::Graphics.sfCircleShape_getOutlineThickness(@handle)
|
|
56
|
+
|
|
57
|
+
def outline_thickness=(t)
|
|
58
|
+
C::Graphics.sfCircleShape_setOutlineThickness(@handle, t.to_f)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
62
|
+
target._draw_native(:CircleShape, @handle, states_ptr)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
attr_reader :handle # :nodoc:
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# 32-bit RGBA color, immutable.
|
|
3
|
+
#
|
|
4
|
+
# SFML::Color.new(255, 100, 50) # opaque
|
|
5
|
+
# SFML::Color.new(255, 100, 50, 128) # half-transparent
|
|
6
|
+
# SFML::Color.rgb(255, 100, 50) # alias
|
|
7
|
+
# SFML::Color.rgba(255, 100, 50, 128)
|
|
8
|
+
# SFML::Color["#ff6432"] # hex (RGB or RRGGBB or RRGGBBAA)
|
|
9
|
+
# SFML::Color.cornflower_blue # named
|
|
10
|
+
class Color
|
|
11
|
+
attr_reader :r, :g, :b, :a
|
|
12
|
+
|
|
13
|
+
def initialize(r, g, b, a = 255)
|
|
14
|
+
@r = Integer(r)
|
|
15
|
+
@g = Integer(g)
|
|
16
|
+
@b = Integer(b)
|
|
17
|
+
@a = Integer(a)
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.rgb(r, g, b) = new(r, g, b, 255)
|
|
22
|
+
def self.rgba(r, g, b, a) = new(r, g, b, a)
|
|
23
|
+
|
|
24
|
+
def self.[](hex)
|
|
25
|
+
str = hex.to_s.delete_prefix("#")
|
|
26
|
+
case str.length
|
|
27
|
+
when 3
|
|
28
|
+
new(str[0].hex * 17, str[1].hex * 17, str[2].hex * 17, 255)
|
|
29
|
+
when 6
|
|
30
|
+
new(str[0..1].to_i(16), str[2..3].to_i(16), str[4..5].to_i(16), 255)
|
|
31
|
+
when 8
|
|
32
|
+
new(str[0..1].to_i(16), str[2..3].to_i(16), str[4..5].to_i(16), str[6..7].to_i(16))
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError, "Color hex must be #RGB, #RRGGBB, or #RRGGBBAA, got #{hex.inspect}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ==(other)
|
|
39
|
+
other.is_a?(Color) && @r == other.r && @g == other.g && @b == other.b && @a == other.a
|
|
40
|
+
end
|
|
41
|
+
alias eql? ==
|
|
42
|
+
def hash = [@r, @g, @b, @a].hash
|
|
43
|
+
|
|
44
|
+
def to_a = [@r, @g, @b, @a]
|
|
45
|
+
def to_h = { r: @r, g: @g, b: @b, a: @a }
|
|
46
|
+
def deconstruct = to_a
|
|
47
|
+
def deconstruct_keys(_keys) = to_h
|
|
48
|
+
|
|
49
|
+
def to_s = "Color(#{@r}, #{@g}, #{@b}, #{@a})"
|
|
50
|
+
alias inspect to_s
|
|
51
|
+
|
|
52
|
+
def to_native # :nodoc:
|
|
53
|
+
C::Graphics::Color.new.tap do |c|
|
|
54
|
+
c[:r] = @r; c[:g] = @g; c[:b] = @b; c[:a] = @a
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.from_native(struct) # :nodoc:
|
|
59
|
+
new(struct[:r], struct[:g], struct[:b], struct[:a])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Standard SFML colors.
|
|
63
|
+
BLACK = new(0, 0, 0)
|
|
64
|
+
WHITE = new(255, 255, 255)
|
|
65
|
+
RED = new(255, 0, 0)
|
|
66
|
+
GREEN = new(0, 255, 0)
|
|
67
|
+
BLUE = new(0, 0, 255)
|
|
68
|
+
YELLOW = new(255, 255, 0)
|
|
69
|
+
MAGENTA = new(255, 0, 255)
|
|
70
|
+
CYAN = new(0, 255, 255)
|
|
71
|
+
TRANSPARENT = new(0, 0, 0, 0)
|
|
72
|
+
|
|
73
|
+
# A nicer default than pure black for empty windows.
|
|
74
|
+
CORNFLOWER_BLUE = new(100, 149, 237)
|
|
75
|
+
|
|
76
|
+
class << self
|
|
77
|
+
def black = BLACK
|
|
78
|
+
def white = WHITE
|
|
79
|
+
def red = RED
|
|
80
|
+
def green = GREEN
|
|
81
|
+
def blue = BLUE
|
|
82
|
+
def yellow = YELLOW
|
|
83
|
+
def magenta = MAGENTA
|
|
84
|
+
def cyan = CYAN
|
|
85
|
+
def transparent = TRANSPARENT
|
|
86
|
+
def cornflower_blue = CORNFLOWER_BLUE
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|