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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +101 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +245 -0
  5. data/ext/ruby-sfml/extconf.rb +69 -0
  6. data/lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt +78 -0
  7. data/lib/sfml/assets/fonts/DejaVuSans.ttf +0 -0
  8. data/lib/sfml/assets.rb +121 -0
  9. data/lib/sfml/audio/listener.rb +55 -0
  10. data/lib/sfml/audio/music.rb +88 -0
  11. data/lib/sfml/audio/sound.rb +102 -0
  12. data/lib/sfml/audio/sound_buffer.rb +38 -0
  13. data/lib/sfml/audio/sound_buffer_recorder.rb +71 -0
  14. data/lib/sfml/audio/sound_recorder.rb +30 -0
  15. data/lib/sfml/c/audio.rb +106 -0
  16. data/lib/sfml/c/graphics.rb +425 -0
  17. data/lib/sfml/c/network.rb +79 -0
  18. data/lib/sfml/c/system.rb +43 -0
  19. data/lib/sfml/c/window.rb +186 -0
  20. data/lib/sfml/c.rb +72 -0
  21. data/lib/sfml/game.rb +101 -0
  22. data/lib/sfml/graphics/blend_mode.rb +108 -0
  23. data/lib/sfml/graphics/circle_shape.rb +67 -0
  24. data/lib/sfml/graphics/color.rb +89 -0
  25. data/lib/sfml/graphics/convex_shape.rb +82 -0
  26. data/lib/sfml/graphics/font.rb +67 -0
  27. data/lib/sfml/graphics/image.rb +125 -0
  28. data/lib/sfml/graphics/rectangle_shape.rb +62 -0
  29. data/lib/sfml/graphics/render_states.rb +56 -0
  30. data/lib/sfml/graphics/render_target.rb +146 -0
  31. data/lib/sfml/graphics/render_texture.rb +72 -0
  32. data/lib/sfml/graphics/render_window.rb +154 -0
  33. data/lib/sfml/graphics/shader.rb +132 -0
  34. data/lib/sfml/graphics/sprite.rb +75 -0
  35. data/lib/sfml/graphics/text.rb +144 -0
  36. data/lib/sfml/graphics/texture.rb +79 -0
  37. data/lib/sfml/graphics/transform.rb +150 -0
  38. data/lib/sfml/graphics/transformable.rb +74 -0
  39. data/lib/sfml/graphics/vertex.rb +53 -0
  40. data/lib/sfml/graphics/vertex_array.rb +114 -0
  41. data/lib/sfml/graphics/view.rb +126 -0
  42. data/lib/sfml/network/ip_address.rb +67 -0
  43. data/lib/sfml/network/tcp_listener.rb +61 -0
  44. data/lib/sfml/network/tcp_socket.rb +74 -0
  45. data/lib/sfml/network/udp_socket.rb +71 -0
  46. data/lib/sfml/system/clock.rb +44 -0
  47. data/lib/sfml/system/rect.rb +64 -0
  48. data/lib/sfml/system/time.rb +48 -0
  49. data/lib/sfml/system/vector2.rb +66 -0
  50. data/lib/sfml/system/vector3.rb +63 -0
  51. data/lib/sfml/version.rb +19 -0
  52. data/lib/sfml/window/clipboard.rb +38 -0
  53. data/lib/sfml/window/cursor.rb +68 -0
  54. data/lib/sfml/window/event.rb +133 -0
  55. data/lib/sfml/window/joystick.rb +90 -0
  56. data/lib/sfml/window/keyboard.rb +60 -0
  57. data/lib/sfml/window/mouse.rb +71 -0
  58. data/lib/sfml/window/video_mode.rb +37 -0
  59. data/lib/sfml/window/window.rb +149 -0
  60. data/lib/sfml.rb +98 -0
  61. data/ruby-sfml.gemspec +38 -0
  62. 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