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,82 @@
1
+ module SFML
2
+ # An arbitrary convex polygon. Define its outline by listing points in
3
+ # order (clockwise or counter-clockwise — just stay consistent). The
4
+ # shape must remain convex — drawing a non-convex point set produces
5
+ # visual artifacts (CSFML doesn't validate).
6
+ #
7
+ # star = SFML::ConvexShape.new(
8
+ # points: [[60, 0], [80, 40], [120, 50], [90, 80],
9
+ # [100, 120], [60, 95], [20, 120], [30, 80],
10
+ # [0, 50], [40, 40]],
11
+ # fill_color: SFML::Color.yellow,
12
+ # )
13
+ # window.draw(star)
14
+ class ConvexShape
15
+ include Graphics::Transformable
16
+ CSFML_PREFIX = :sfConvexShape
17
+
18
+ def initialize(points: nil, **opts)
19
+ ptr = C::Graphics.sfConvexShape_create
20
+ raise Error, "sfConvexShape_create returned NULL" if ptr.null?
21
+ @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfConvexShape_destroy))
22
+
23
+ self.points = points if points
24
+ self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
25
+ self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
26
+ self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
27
+ self.position = opts[:position] if opts.key?(:position)
28
+ self.origin = opts[:origin] if opts.key?(:origin)
29
+ self.rotation = opts[:rotation] if opts.key?(:rotation)
30
+ self.scale = opts[:scale] if opts.key?(:scale)
31
+ end
32
+
33
+ def point_count = C::Graphics.sfConvexShape_getPointCount(@handle)
34
+
35
+ # Returns the polygon vertices as an Array of Vector2.
36
+ def points
37
+ n = point_count
38
+ (0...n).map do |i|
39
+ Vector2.from_native(C::Graphics.sfConvexShape_getPoint(@handle, i))
40
+ end
41
+ end
42
+
43
+ # Replace every point. Accepts a list of [x, y] arrays or Vector2s.
44
+ def points=(list)
45
+ C::Graphics.sfConvexShape_setPointCount(@handle, list.length)
46
+ list.each_with_index do |pt, i|
47
+ vec = pt.is_a?(Vector2) ? pt : Vector2.new(*pt)
48
+ C::Graphics.sfConvexShape_setPoint(@handle, i, vec.to_native_f)
49
+ end
50
+ end
51
+
52
+ # Mutate a single vertex by index.
53
+ def set_point(index, point)
54
+ vec = point.is_a?(Vector2) ? point : Vector2.new(*point)
55
+ C::Graphics.sfConvexShape_setPoint(@handle, Integer(index), vec.to_native_f)
56
+ end
57
+
58
+ def fill_color = Color.from_native(C::Graphics.sfConvexShape_getFillColor(@handle))
59
+
60
+ def fill_color=(c)
61
+ C::Graphics.sfConvexShape_setFillColor(@handle, c.to_native)
62
+ end
63
+
64
+ def outline_color = Color.from_native(C::Graphics.sfConvexShape_getOutlineColor(@handle))
65
+
66
+ def outline_color=(c)
67
+ C::Graphics.sfConvexShape_setOutlineColor(@handle, c.to_native)
68
+ end
69
+
70
+ def outline_thickness = C::Graphics.sfConvexShape_getOutlineThickness(@handle)
71
+
72
+ def outline_thickness=(t)
73
+ C::Graphics.sfConvexShape_setOutlineThickness(@handle, t.to_f)
74
+ end
75
+
76
+ def draw_on(target, states_ptr = nil) # :nodoc:
77
+ target._draw_native(:ConvexShape, @handle, states_ptr)
78
+ end
79
+
80
+ attr_reader :handle # :nodoc:
81
+ end
82
+ end
@@ -0,0 +1,67 @@
1
+ module SFML
2
+ # A typeface loaded from a TTF/OTF file.
3
+ #
4
+ # font = SFML::Font.default # bundled DejaVu Sans
5
+ # font = SFML::Font.load("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
6
+ # font = SFML::Font.find("DejaVuSans") # search common locations
7
+ class Font
8
+ SEARCH_PATHS = [
9
+ "/usr/share/fonts",
10
+ "/usr/local/share/fonts",
11
+ "/Library/Fonts",
12
+ "/System/Library/Fonts",
13
+ File.expand_path("~/Library/Fonts"),
14
+ "C:/Windows/Fonts",
15
+ ].freeze
16
+
17
+ # Path to the font ruby-sfml ships with: DejaVu Sans (Bitstream Vera
18
+ # license, redistributable). See lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt.
19
+ DEFAULT_PATH = File.expand_path("../assets/fonts/DejaVuSans.ttf", __dir__).freeze
20
+
21
+ def self.load(path)
22
+ ptr = C::Graphics.sfFont_createFromFile(path.to_s)
23
+ raise Error, "Could not load font from #{path.inspect}" if ptr.null?
24
+
25
+ font = allocate
26
+ font.send(:_take_ownership, ptr)
27
+ font
28
+ end
29
+
30
+ # The default font bundled with ruby-sfml. Use this when you don't
31
+ # care which typeface as long as you can render text — examples,
32
+ # debug HUDs, prototypes. Memoized so subsequent calls return the
33
+ # same Font instance.
34
+ def self.default
35
+ @default ||= load(DEFAULT_PATH)
36
+ end
37
+
38
+ # Look up a font on disk by basename (with or without extension). Useful
39
+ # for examples that should "just run" — production code should ship its
40
+ # own font files. Returns nil if nothing is found.
41
+ def self.find(name)
42
+ target = name.to_s.downcase.sub(/\.(ttf|otf)\z/, "")
43
+ SEARCH_PATHS.each do |dir|
44
+ next unless File.directory?(dir)
45
+ match = Dir.glob(File.join(dir, "**", "*.{ttf,otf}")).find do |path|
46
+ File.basename(path).downcase.sub(/\.(ttf|otf)\z/, "") == target
47
+ end
48
+ return load(match) if match
49
+ end
50
+ nil
51
+ end
52
+
53
+ def smooth? = C::Graphics.sfFont_isSmooth(@handle)
54
+
55
+ def smooth=(value)
56
+ C::Graphics.sfFont_setSmooth(@handle, !!value)
57
+ end
58
+
59
+ attr_reader :handle # :nodoc:
60
+
61
+ private
62
+
63
+ def _take_ownership(ptr)
64
+ @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfFont_destroy))
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,125 @@
1
+ module SFML
2
+ # CPU-side bitmap. Lives in main memory and supports per-pixel reads /
3
+ # writes — handy for procedural generation, screenshots, masks, and
4
+ # anything that needs to inspect or modify pixel data before upload.
5
+ #
6
+ # img = SFML::Image.new(800, 600, fill: SFML::Color.cornflower_blue)
7
+ # img = SFML::Image.load("assets/hero.png")
8
+ #
9
+ # img[10, 20] = SFML::Color.red
10
+ # img[10, 20] #=> Color(255, 0, 0, 255)
11
+ #
12
+ # img.flip_vertically
13
+ # img.save("out.png")
14
+ #
15
+ # Convert to a GPU-side texture for drawing:
16
+ #
17
+ # tex = SFML::Texture.from_image(img)
18
+ # sprite = SFML::Sprite.new(tex)
19
+ class Image
20
+ # Create a blank image of the given size. With `fill:` it's filled
21
+ # with that colour; without, with transparent black.
22
+ def initialize(width, height, fill: nil)
23
+ size = C::System::Vector2u.new
24
+ size[:x] = Integer(width)
25
+ size[:y] = Integer(height)
26
+
27
+ ptr = if fill
28
+ C::Graphics.sfImage_createFromColor(size, fill.to_native)
29
+ else
30
+ C::Graphics.sfImage_create(size)
31
+ end
32
+ raise Error, "sfImage_create returned NULL" if ptr.null?
33
+ _take_ownership(ptr)
34
+ end
35
+
36
+ def self.load(path)
37
+ ptr = C::Graphics.sfImage_createFromFile(path.to_s)
38
+ raise Error, "Could not load image from #{path.inspect}" if ptr.null?
39
+ img = allocate
40
+ img.send(:_take_ownership, ptr)
41
+ img
42
+ end
43
+
44
+ # Build an image from a raw RGBA byte string. `pixels` must be
45
+ # exactly width*height*4 bytes, row-major from the top-left.
46
+ def self.from_pixels(width, height, pixels)
47
+ expected = Integer(width) * Integer(height) * 4
48
+ raise ArgumentError, "expected #{expected} bytes, got #{pixels.bytesize}" if pixels.bytesize != expected
49
+
50
+ buf = FFI::MemoryPointer.new(:uint8, expected)
51
+ buf.write_bytes(pixels)
52
+
53
+ size = C::System::Vector2u.new
54
+ size[:x] = Integer(width)
55
+ size[:y] = Integer(height)
56
+ ptr = C::Graphics.sfImage_createFromPixels(size, buf)
57
+ raise Error, "sfImage_createFromPixels returned NULL" if ptr.null?
58
+
59
+ img = allocate
60
+ img.send(:_take_ownership, ptr)
61
+ img
62
+ end
63
+
64
+ def size
65
+ Vector2.from_native(C::Graphics.sfImage_getSize(@handle))
66
+ end
67
+
68
+ def width = size.x
69
+ def height = size.y
70
+
71
+ # Read the colour of a single pixel.
72
+ def [](x, y)
73
+ coord = C::System::Vector2u.new
74
+ coord[:x] = Integer(x); coord[:y] = Integer(y)
75
+ Color.from_native(C::Graphics.sfImage_getPixel(@handle, coord))
76
+ end
77
+
78
+ # Write a single pixel.
79
+ def []=(x, y, color)
80
+ coord = C::System::Vector2u.new
81
+ coord[:x] = Integer(x); coord[:y] = Integer(y)
82
+ C::Graphics.sfImage_setPixel(@handle, coord, color.to_native)
83
+ end
84
+
85
+ # Write the entire pixel buffer back as a Ruby String. Useful for
86
+ # piping to image-processing libraries or writing custom file formats.
87
+ # Format: width*height*4 bytes, RGBA, row-major from top-left.
88
+ def pixels
89
+ ptr = C::Graphics.sfImage_getPixelsPtr(@handle)
90
+ ptr.read_bytes(width * height * 4)
91
+ end
92
+
93
+ def save(path)
94
+ ok = C::Graphics.sfImage_saveToFile(@handle, path.to_s)
95
+ raise Error, "Could not save image to #{path.inspect}" unless ok
96
+ path
97
+ end
98
+
99
+ # Replace any pixel matching `color` with that colour at `alpha`
100
+ # opacity — typical use is to turn a fixed background colour
101
+ # transparent: img.mask_color!(SFML::Color.magenta, alpha: 0).
102
+ def mask_color!(color, alpha: 0)
103
+ C::Graphics.sfImage_createMaskFromColor(@handle, color.to_native, Integer(alpha))
104
+ self
105
+ end
106
+
107
+ def flip_horizontally
108
+ C::Graphics.sfImage_flipHorizontally(@handle)
109
+ self
110
+ end
111
+
112
+ def flip_vertically
113
+ C::Graphics.sfImage_flipVertically(@handle)
114
+ self
115
+ end
116
+
117
+ attr_reader :handle # :nodoc:
118
+
119
+ private
120
+
121
+ def _take_ownership(ptr)
122
+ @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfImage_destroy))
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,62 @@
1
+ module SFML
2
+ # An axis-aligned filled rectangle.
3
+ #
4
+ # wall = SFML::RectangleShape.new(
5
+ # size: [200, 40],
6
+ # position: [100, 500],
7
+ # fill_color: SFML::Color["#444"],
8
+ # )
9
+ # window.draw(wall)
10
+ class RectangleShape
11
+ include Graphics::Transformable
12
+ CSFML_PREFIX = :sfRectangleShape
13
+
14
+ def initialize(size:, **opts)
15
+ ptr = C::Graphics.sfRectangleShape_create
16
+ raise Error, "sfRectangleShape_create returned NULL" if ptr.null?
17
+ @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRectangleShape_destroy))
18
+
19
+ self.size = size
20
+ self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
21
+ self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
22
+ self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
23
+ self.position = opts[:position] if opts.key?(:position)
24
+ self.origin = opts[:origin] if opts.key?(:origin)
25
+ self.rotation = opts[:rotation] if opts.key?(:rotation)
26
+ self.scale = opts[:scale] if opts.key?(:scale)
27
+ end
28
+
29
+ def size
30
+ Vector2.from_native(C::Graphics.sfRectangleShape_getSize(@handle))
31
+ end
32
+
33
+ def size=(value)
34
+ vec = value.is_a?(Vector2) ? value : Vector2.new(*value)
35
+ C::Graphics.sfRectangleShape_setSize(@handle, vec.to_native_f)
36
+ end
37
+
38
+ def fill_color = Color.from_native(C::Graphics.sfRectangleShape_getFillColor(@handle))
39
+
40
+ def fill_color=(c)
41
+ C::Graphics.sfRectangleShape_setFillColor(@handle, c.to_native)
42
+ end
43
+
44
+ def outline_color = Color.from_native(C::Graphics.sfRectangleShape_getOutlineColor(@handle))
45
+
46
+ def outline_color=(c)
47
+ C::Graphics.sfRectangleShape_setOutlineColor(@handle, c.to_native)
48
+ end
49
+
50
+ def outline_thickness = C::Graphics.sfRectangleShape_getOutlineThickness(@handle)
51
+
52
+ def outline_thickness=(t)
53
+ C::Graphics.sfRectangleShape_setOutlineThickness(@handle, t.to_f)
54
+ end
55
+
56
+ def draw_on(target, states_ptr = nil) # :nodoc:
57
+ target._draw_native(:RectangleShape, @handle, states_ptr)
58
+ end
59
+
60
+ attr_reader :handle # :nodoc:
61
+ end
62
+ end
@@ -0,0 +1,56 @@
1
+ module SFML
2
+ # The state passed alongside a draw call: blend mode, texture, shader,
3
+ # transform, coordinate type. You rarely instantiate this directly —
4
+ # `window.draw(...)` accepts `blend_mode:`, `texture:`, etc. shortcuts
5
+ # that build a RenderStates internally. Construct one yourself when
6
+ # you need to keep the same combination across many draws:
7
+ #
8
+ # states = SFML::RenderStates.new(
9
+ # blend_mode: SFML::BlendMode::ADD,
10
+ # texture: glow_texture,
11
+ # )
12
+ # window.draw(va, render_states: states)
13
+ # window.draw(other, render_states: states)
14
+ #
15
+ # All fields default to the CSFML `sfRenderStates_default` value.
16
+ class RenderStates
17
+ COORDINATE_TYPES = %i[normalized pixels].freeze
18
+ COORDINATE_INDEX = COORDINATE_TYPES.each_with_index.to_h.freeze
19
+
20
+ attr_reader :blend_mode, :texture, :shader, :coordinate_type
21
+
22
+ def initialize(blend_mode: nil, texture: nil, shader: nil, coordinate_type: :normalized)
23
+ @blend_mode = blend_mode
24
+ @texture = texture
25
+ @shader = shader # placeholder until SFML::Shader lands in the next P1 step
26
+ @coordinate_type = coordinate_type
27
+ raise ArgumentError, "unknown coordinate_type: #{coordinate_type.inspect}" unless COORDINATE_INDEX.key?(coordinate_type)
28
+ freeze
29
+ end
30
+
31
+ # @!visibility private
32
+ # Allocate and populate a sfRenderStates buffer. Returns an
33
+ # FFI::MemoryPointer ready to pass as the `const sfRenderStates*`
34
+ # argument of a CSFML draw function.
35
+ def to_native_pointer
36
+ buffer = C::Graphics::RenderStates.new
37
+ # Start from the CSFML default so we get sane stencil + transform.
38
+ C::Graphics.sfRenderStates_default.pointer.read_bytes(C::Graphics::RenderStates.size)
39
+ .then { |bytes| buffer.pointer.write_bytes(bytes) }
40
+
41
+ @blend_mode&.populate(buffer[:blend_mode])
42
+ buffer[:coordinate_type] = COORDINATE_INDEX[@coordinate_type]
43
+ buffer[:texture] = @texture ? @texture.handle : nil
44
+ buffer[:shader] = @shader ? @shader.handle : nil
45
+ buffer.pointer
46
+ end
47
+
48
+ # Convenience: build a RenderStates from the same shortcut kwargs
49
+ # that RenderTarget#draw accepts. Returns nil if no opts given (so
50
+ # the draw call uses the CSFML default without allocating anything).
51
+ def self.from_draw_opts(opts)
52
+ return nil if opts.empty?
53
+ new(**opts)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,146 @@
1
+ module SFML
2
+ module Graphics
3
+ # Shared behaviour between SFML::RenderWindow and SFML::RenderTexture.
4
+ # The two CSFML APIs are near-mirrors of each other (sfRenderWindow_*
5
+ # vs sfRenderTexture_*), so the Ruby side dispatches by the includer's
6
+ # CSFML_PREFIX constant. Adding a new render target only takes:
7
+ #
8
+ # class NewTarget
9
+ # include Graphics::RenderTarget
10
+ # CSFML_PREFIX = :sfNewTarget
11
+ # ...
12
+ # end
13
+ #
14
+ # Drawables call `target._draw_native(:CircleShape, handle)` to dispatch
15
+ # through the right CSFML draw function for whichever target they're
16
+ # being rendered to.
17
+ module RenderTarget
18
+ def clear(color = Color::BLACK)
19
+ _csfml(:clear, @handle, color.to_native)
20
+ self
21
+ end
22
+
23
+ def display
24
+ _csfml(:display, @handle)
25
+ self
26
+ end
27
+
28
+ # Polymorphic draw: any drawable with a #draw_on(target, [states])
29
+ # method. Built-in drawables call back into target._draw_native.
30
+ #
31
+ # Pass shortcut kwargs to apply render states without instantiating
32
+ # SFML::RenderStates yourself:
33
+ #
34
+ # window.draw(va, texture: tile_texture)
35
+ # window.draw(glow, blend_mode: SFML::BlendMode::ADD)
36
+ # window.draw(thing, texture: tex, blend_mode: SFML::BlendMode::ADD)
37
+ #
38
+ # Or pass a pre-built object for re-use across calls:
39
+ #
40
+ # window.draw(thing, render_states: shared_states)
41
+ def draw(drawable, render_states: nil, **opts)
42
+ states = render_states || RenderStates.from_draw_opts(opts)
43
+ states_ptr = states&.to_native_pointer
44
+ drawable.draw_on(self, states_ptr)
45
+ self
46
+ end
47
+
48
+ # @!visibility private
49
+ # Invoke the right CSFML draw function for this target + drawable
50
+ # kind. `kind` is the suffix after `draw`: e.g. :CircleShape →
51
+ # sfRenderWindow_drawCircleShape on a window, sfRenderTexture_drawCircleShape
52
+ # on a texture. `states_ptr` may be nil for default render states.
53
+ def _draw_native(kind, drawable_handle, states_ptr = nil)
54
+ C::Graphics.public_send(
55
+ :"#{self.class::CSFML_PREFIX}_draw#{kind}",
56
+ @handle, drawable_handle, states_ptr,
57
+ )
58
+ end
59
+
60
+ # Draw a one-shot batch of vertices without allocating a
61
+ # SFML::VertexArray. Useful for tight inner loops (a few dozen
62
+ # primitives per frame, where the VertexArray's per-object
63
+ # bookkeeping is itself the cost).
64
+ #
65
+ # window.draw_primitives(
66
+ # [SFML::Vertex.new([0, 0], color: SFML::Color.red),
67
+ # SFML::Vertex.new([100, 0], color: SFML::Color.green),
68
+ # SFML::Vertex.new([50, 80], color: SFML::Color.blue)],
69
+ # :triangles,
70
+ # )
71
+ #
72
+ # Accepts the same render-states kwargs as #draw.
73
+ def draw_primitives(vertices, primitive_type = :points, render_states: nil, **opts)
74
+ type_code = VertexArray::PRIMITIVE_INDEX.fetch(primitive_type) do
75
+ raise ArgumentError, "Unknown primitive type: #{primitive_type.inspect}"
76
+ end
77
+
78
+ # Pack the vertex array into a contiguous buffer.
79
+ n = vertices.length
80
+ buf = FFI::MemoryPointer.new(C::Graphics::Vertex, n)
81
+ vertices.each_with_index do |v, i|
82
+ slot = C::Graphics::Vertex.new(buf + i * C::Graphics::Vertex.size)
83
+ slot[:position][:x] = v.position.x.to_f
84
+ slot[:position][:y] = v.position.y.to_f
85
+ slot[:color][:r] = v.color.r
86
+ slot[:color][:g] = v.color.g
87
+ slot[:color][:b] = v.color.b
88
+ slot[:color][:a] = v.color.a
89
+ slot[:tex_coords][:x] = v.tex_coords.x.to_f
90
+ slot[:tex_coords][:y] = v.tex_coords.y.to_f
91
+ end
92
+
93
+ states = render_states || RenderStates.from_draw_opts(opts)
94
+ states_ptr = states&.to_native_pointer
95
+
96
+ C::Graphics.public_send(
97
+ :"#{self.class::CSFML_PREFIX}_drawPrimitives",
98
+ @handle, buf, n, type_code, states_ptr,
99
+ )
100
+ self
101
+ end
102
+
103
+ def view=(value)
104
+ raise ArgumentError, "#{self.class}#view= requires a SFML::View" unless value.is_a?(View)
105
+ _csfml(:setView, @handle, value.handle)
106
+ @view = value
107
+ end
108
+
109
+ def view
110
+ View.from_borrowed(_csfml(:getView, @handle))
111
+ end
112
+
113
+ # The default 1:1 view that matches the target's pixel size.
114
+ # Memoised — see the comment in render_window.rb for why.
115
+ def default_view
116
+ @default_view ||= View.from_borrowed(_csfml(:getDefaultView, @handle))
117
+ end
118
+
119
+ def map_pixel_to_coords(pixel, view: nil)
120
+ vec = C::System::Vector2i.new
121
+ px, py = pixel.is_a?(Vector2) ? [pixel.x, pixel.y] : pixel
122
+ vec[:x] = Integer(px); vec[:y] = Integer(py)
123
+
124
+ v_handle = view ? view.handle : _csfml(:getView, @handle)
125
+ result = _csfml(:mapPixelToCoords, @handle, vec, v_handle)
126
+ Vector2.new(result[:x], result[:y])
127
+ end
128
+
129
+ def map_coords_to_pixel(coord, view: nil)
130
+ vec = C::System::Vector2f.new
131
+ cx, cy = coord.is_a?(Vector2) ? [coord.x, coord.y] : coord
132
+ vec[:x] = cx.to_f; vec[:y] = cy.to_f
133
+
134
+ v_handle = view ? view.handle : _csfml(:getView, @handle)
135
+ result = _csfml(:mapCoordsToPixel, @handle, vec, v_handle)
136
+ Vector2.new(result[:x], result[:y])
137
+ end
138
+
139
+ private
140
+
141
+ def _csfml(suffix, *args)
142
+ C::Graphics.public_send(:"#{self.class::CSFML_PREFIX}_#{suffix}", *args)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,72 @@
1
+ module SFML
2
+ # Off-screen rendering target. Anything you can draw on a RenderWindow
3
+ # — sprites, shapes, text, vertex arrays — you can also draw on a
4
+ # RenderTexture and then use its #texture as a Sprite source. Typical
5
+ # uses: minimaps, post-processing, motion-blur trails, custom UIs that
6
+ # composite multiple layers.
7
+ #
8
+ # rt = SFML::RenderTexture.new(400, 300)
9
+ # rt.clear(SFML::Color.cornflower_blue)
10
+ # rt.draw(sprite)
11
+ # rt.draw(text)
12
+ # rt.display
13
+ #
14
+ # sprite = SFML::Sprite.new(rt.texture)
15
+ # window.draw(sprite)
16
+ #
17
+ # Note: rt.texture returns a *borrowed* reference owned by the
18
+ # RenderTexture. Keep the RenderTexture alive for as long as anything
19
+ # uses its texture.
20
+ class RenderTexture
21
+ include Graphics::RenderTarget
22
+ CSFML_PREFIX = :sfRenderTexture
23
+
24
+ def initialize(width, height, smooth: false, repeated: false)
25
+ size = C::System::Vector2u.new
26
+ size[:x] = Integer(width)
27
+ size[:y] = Integer(height)
28
+
29
+ ptr = C::Graphics.sfRenderTexture_create(size, nil)
30
+ raise Error, "sfRenderTexture_create returned NULL" if ptr.null?
31
+ @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRenderTexture_destroy))
32
+
33
+ self.smooth = smooth
34
+ self.repeated = repeated
35
+ end
36
+
37
+ def size
38
+ v = C::Graphics.sfRenderTexture_getSize(@handle)
39
+ Vector2.new(v[:x], v[:y])
40
+ end
41
+
42
+ def smooth? = C::Graphics.sfRenderTexture_isSmooth(@handle)
43
+
44
+ def smooth=(value)
45
+ C::Graphics.sfRenderTexture_setSmooth(@handle, !!value)
46
+ end
47
+
48
+ def repeated? = C::Graphics.sfRenderTexture_isRepeated(@handle)
49
+
50
+ def repeated=(value)
51
+ C::Graphics.sfRenderTexture_setRepeated(@handle, !!value)
52
+ end
53
+
54
+ # The Texture this RenderTexture is rendering into. Borrowed — its
55
+ # lifetime is bounded by `self`. Memoised so repeated calls return
56
+ # the same Ruby wrapper.
57
+ def texture
58
+ @texture ||= begin
59
+ ptr = C::Graphics.sfRenderTexture_getTexture(@handle)
60
+ raise Error, "sfRenderTexture_getTexture returned NULL" if ptr.null?
61
+ # Borrowed — RenderTexture owns the underlying sf::Texture, so
62
+ # we wrap with a raw pointer (no AutoPointer / no destructor).
63
+ # Sprite.new(@texture) will still get a valid handle through it.
64
+ tex = Texture.allocate
65
+ tex.instance_variable_set(:@handle, ptr)
66
+ tex
67
+ end
68
+ end
69
+
70
+ attr_reader :handle # :nodoc:
71
+ end
72
+ end