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,74 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Network
|
|
3
|
+
# A TCP client socket. Connect to a server, send/receive bytes,
|
|
4
|
+
# disconnect.
|
|
5
|
+
#
|
|
6
|
+
# sock = SFML::Network::TcpSocket.new
|
|
7
|
+
# case sock.connect(SFML::Network::IpAddress::LOCALHOST, port: 8080)
|
|
8
|
+
# when :done then ...
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# sock.send("hello")
|
|
12
|
+
# status, bytes = sock.receive(max: 1024)
|
|
13
|
+
#
|
|
14
|
+
# By default sockets are blocking; set `socket.blocking = false`
|
|
15
|
+
# for non-blocking polling, where send/receive may return :not_ready.
|
|
16
|
+
class TcpSocket
|
|
17
|
+
def initialize
|
|
18
|
+
ptr = C::Network.sfTcpSocket_create
|
|
19
|
+
raise Error, "sfTcpSocket_create returned NULL" if ptr.null?
|
|
20
|
+
@handle = FFI::AutoPointer.new(ptr, C::Network.method(:sfTcpSocket_destroy))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Open a connection. Returns one of the SOCKET_STATUSES symbols
|
|
24
|
+
# (:done, :not_ready, :disconnected, :error). `timeout` is a
|
|
25
|
+
# SFML::Time; SFML::Time.zero (default) blocks forever.
|
|
26
|
+
def connect(address, port:, timeout: SFML::Time.zero)
|
|
27
|
+
addr = address.is_a?(IpAddress) ? address : IpAddress.from_string(address)
|
|
28
|
+
code = C::Network.sfTcpSocket_connect(@handle, addr.struct, Integer(port), timeout.to_native)
|
|
29
|
+
C::Network::STATUSES[code]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def disconnect
|
|
33
|
+
C::Network.sfTcpSocket_disconnect(@handle)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def send(data)
|
|
38
|
+
bytes = data.to_s
|
|
39
|
+
buf = FFI::MemoryPointer.new(:uint8, bytes.bytesize)
|
|
40
|
+
buf.write_bytes(bytes)
|
|
41
|
+
code = C::Network.sfTcpSocket_send(@handle, buf, bytes.bytesize)
|
|
42
|
+
C::Network::STATUSES[code]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Read up to `max` bytes. Returns [status, data] — `data` is a
|
|
46
|
+
# binary String when status is :done, nil otherwise.
|
|
47
|
+
def receive(max: 4096)
|
|
48
|
+
buf = FFI::MemoryPointer.new(:uint8, Integer(max))
|
|
49
|
+
received = FFI::MemoryPointer.new(:size_t)
|
|
50
|
+
code = C::Network.sfTcpSocket_receive(@handle, buf, Integer(max), received)
|
|
51
|
+
status = C::Network::STATUSES[code]
|
|
52
|
+
|
|
53
|
+
return [status, nil] unless status == :done
|
|
54
|
+
n = received.read(:size_t)
|
|
55
|
+
[status, buf.read_bytes(n)]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def blocking? = C::Network.sfTcpSocket_isBlocking(@handle)
|
|
59
|
+
|
|
60
|
+
def blocking=(value)
|
|
61
|
+
C::Network.sfTcpSocket_setBlocking(@handle, value ? true : false)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def local_port = C::Network.sfTcpSocket_getLocalPort(@handle)
|
|
65
|
+
def remote_port = C::Network.sfTcpSocket_getRemotePort(@handle)
|
|
66
|
+
|
|
67
|
+
def remote_address
|
|
68
|
+
IpAddress.wrap(C::Network.sfTcpSocket_getRemoteAddress(@handle))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
attr_reader :handle # :nodoc:
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Network
|
|
3
|
+
# Connectionless UDP datagram socket. Bind a port to receive,
|
|
4
|
+
# send to (address, port). Stateless — every send/receive specifies
|
|
5
|
+
# the peer.
|
|
6
|
+
#
|
|
7
|
+
# sock = SFML::Network::UdpSocket.new
|
|
8
|
+
# sock.bind(port: 9999)
|
|
9
|
+
# sock.send("hello", to: SFML::Network::IpAddress::LOCALHOST, port: 9000)
|
|
10
|
+
# status, bytes, addr, port = sock.receive(max: 1024)
|
|
11
|
+
class UdpSocket
|
|
12
|
+
MAX_DATAGRAM_SIZE = C::Network.sfUdpSocket_maxDatagramSize
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
ptr = C::Network.sfUdpSocket_create
|
|
16
|
+
raise Error, "sfUdpSocket_create returned NULL" if ptr.null?
|
|
17
|
+
@handle = FFI::AutoPointer.new(ptr, C::Network.method(:sfUdpSocket_destroy))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def bind(port:, address: IpAddress::ANY)
|
|
21
|
+
addr = address.is_a?(IpAddress) ? address : IpAddress.from_string(address)
|
|
22
|
+
code = C::Network.sfUdpSocket_bind(@handle, Integer(port), addr.struct)
|
|
23
|
+
C::Network::STATUSES[code]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def unbind
|
|
27
|
+
C::Network.sfUdpSocket_unbind(@handle)
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def send(data, to:, port:)
|
|
32
|
+
bytes = data.to_s
|
|
33
|
+
addr = to.is_a?(IpAddress) ? to : IpAddress.from_string(to)
|
|
34
|
+
buf = FFI::MemoryPointer.new(:uint8, bytes.bytesize)
|
|
35
|
+
buf.write_bytes(bytes)
|
|
36
|
+
code = C::Network.sfUdpSocket_send(@handle, buf, bytes.bytesize, addr.struct, Integer(port))
|
|
37
|
+
C::Network::STATUSES[code]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns [status, data, sender_ip, sender_port].
|
|
41
|
+
# `data` is a binary String when status is :done, otherwise nil.
|
|
42
|
+
def receive(max: 1024)
|
|
43
|
+
buf = FFI::MemoryPointer.new(:uint8, Integer(max))
|
|
44
|
+
received_size = FFI::MemoryPointer.new(:size_t)
|
|
45
|
+
sender_addr = C::Network::IpAddress.new
|
|
46
|
+
sender_port = FFI::MemoryPointer.new(:uint16)
|
|
47
|
+
|
|
48
|
+
code = C::Network.sfUdpSocket_receive(
|
|
49
|
+
@handle, buf, Integer(max), received_size, sender_addr.pointer, sender_port,
|
|
50
|
+
)
|
|
51
|
+
status = C::Network::STATUSES[code]
|
|
52
|
+
|
|
53
|
+
return [status, nil, nil, nil] unless status == :done
|
|
54
|
+
n = received_size.read(:size_t)
|
|
55
|
+
sip = IpAddress.wrap(sender_addr)
|
|
56
|
+
sport = sender_port.read(:uint16)
|
|
57
|
+
[status, buf.read_bytes(n), sip, sport]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def blocking? = C::Network.sfUdpSocket_isBlocking(@handle)
|
|
61
|
+
|
|
62
|
+
def blocking=(value)
|
|
63
|
+
C::Network.sfUdpSocket_setBlocking(@handle, value ? true : false)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def local_port = C::Network.sfUdpSocket_getLocalPort(@handle)
|
|
67
|
+
|
|
68
|
+
attr_reader :handle # :nodoc:
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Monotonic high-resolution timer. Wraps sfClock from CSFML.
|
|
3
|
+
#
|
|
4
|
+
# clock = SFML::Clock.new
|
|
5
|
+
# # ... do work ...
|
|
6
|
+
# puts clock.elapsed_time.as_seconds
|
|
7
|
+
# clock.restart # returns the elapsed time and resets to zero
|
|
8
|
+
class Clock
|
|
9
|
+
def initialize
|
|
10
|
+
ptr = C::System.sfClock_create
|
|
11
|
+
raise Error, "sfClock_create returned NULL" if ptr.null?
|
|
12
|
+
@handle = FFI::AutoPointer.new(ptr, C::System.method(:sfClock_destroy))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def elapsed_time
|
|
16
|
+
Time.from_native(C::System.sfClock_getElapsedTime(@handle))
|
|
17
|
+
end
|
|
18
|
+
alias elapsed elapsed_time
|
|
19
|
+
|
|
20
|
+
def running?
|
|
21
|
+
C::System.sfClock_isRunning(@handle)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def start
|
|
25
|
+
C::System.sfClock_start(@handle)
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop
|
|
30
|
+
C::System.sfClock_stop(@handle)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def restart
|
|
35
|
+
Time.from_native(C::System.sfClock_restart(@handle))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset
|
|
39
|
+
Time.from_native(C::System.sfClock_reset(@handle))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attr_reader :handle # :nodoc:
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# An axis-aligned rectangle. Mirrors sfFloatRect / sfIntRect — those structs
|
|
3
|
+
# are just (position: Vector2, size: Vector2). We deliberately keep one
|
|
4
|
+
# Ruby class for both the float and int variants since pattern-matching
|
|
5
|
+
# `case bounds in {position: {x:, y:}, size: {x: w, y: h}}` is what the
|
|
6
|
+
# users reach for either way.
|
|
7
|
+
#
|
|
8
|
+
# r = SFML::Rect.new([10, 20], [100, 50])
|
|
9
|
+
# r.contains?([50, 30]) #=> true
|
|
10
|
+
# r.right #=> 110
|
|
11
|
+
#
|
|
12
|
+
# Used by Text#local_bounds, Text#global_bounds, Sprite#texture_rect, etc.
|
|
13
|
+
class Rect
|
|
14
|
+
attr_reader :position, :size
|
|
15
|
+
|
|
16
|
+
def initialize(position, size)
|
|
17
|
+
@position = position.is_a?(Vector2) ? position : Vector2.new(*position)
|
|
18
|
+
@size = size.is_a?(Vector2) ? size : Vector2.new(*size)
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def x = @position.x
|
|
23
|
+
def y = @position.y
|
|
24
|
+
def width = @size.x
|
|
25
|
+
def height = @size.y
|
|
26
|
+
alias left x
|
|
27
|
+
alias top y
|
|
28
|
+
def right = @position.x + @size.x
|
|
29
|
+
def bottom = @position.y + @size.y
|
|
30
|
+
|
|
31
|
+
def contains?(point)
|
|
32
|
+
px, py = point.is_a?(Vector2) ? [point.x, point.y] : [point[0], point[1]]
|
|
33
|
+
px >= left && px < right && py >= top && py < bottom
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def intersects?(other)
|
|
37
|
+
left < other.right &&
|
|
38
|
+
right > other.left &&
|
|
39
|
+
top < other.bottom &&
|
|
40
|
+
bottom > other.top
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ==(other)
|
|
44
|
+
other.is_a?(Rect) && @position == other.position && @size == other.size
|
|
45
|
+
end
|
|
46
|
+
alias eql? ==
|
|
47
|
+
def hash = [@position, @size].hash
|
|
48
|
+
|
|
49
|
+
def to_a = [x, y, width, height]
|
|
50
|
+
def to_h = { position: @position, size: @size }
|
|
51
|
+
def deconstruct = [x, y, width, height]
|
|
52
|
+
def deconstruct_keys(_keys) = { position: @position, size: @size }
|
|
53
|
+
|
|
54
|
+
def to_s = "Rect(x=#{x}, y=#{y}, w=#{width}, h=#{height})"
|
|
55
|
+
alias inspect to_s
|
|
56
|
+
|
|
57
|
+
def self.from_native(struct) # :nodoc:
|
|
58
|
+
new(
|
|
59
|
+
Vector2.new(struct[:position][:x], struct[:position][:y]),
|
|
60
|
+
Vector2.new(struct[:size][:x], struct[:size][:y]),
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Represents a time value. Stored internally as microseconds (int64), same
|
|
3
|
+
# as sfTime in CSFML. Immutable and comparable.
|
|
4
|
+
#
|
|
5
|
+
# SFML::Time.seconds(1.5) #=> 1_500_000 µs
|
|
6
|
+
# SFML::Time.milliseconds(500)
|
|
7
|
+
# SFML::Time.microseconds(42)
|
|
8
|
+
# SFML::Time.zero
|
|
9
|
+
class Time
|
|
10
|
+
include Comparable
|
|
11
|
+
|
|
12
|
+
attr_reader :microseconds
|
|
13
|
+
|
|
14
|
+
def self.seconds(value) = new((value * 1_000_000).to_i)
|
|
15
|
+
def self.milliseconds(value) = new(Integer(value) * 1_000)
|
|
16
|
+
def self.microseconds(value) = new(Integer(value))
|
|
17
|
+
def self.zero = new(0)
|
|
18
|
+
|
|
19
|
+
def initialize(microseconds)
|
|
20
|
+
@microseconds = Integer(microseconds)
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def as_seconds = @microseconds / 1_000_000.0
|
|
25
|
+
def as_milliseconds = @microseconds / 1_000
|
|
26
|
+
def as_microseconds = @microseconds
|
|
27
|
+
|
|
28
|
+
def +(other) = Time.new(@microseconds + other.microseconds)
|
|
29
|
+
def -(other) = Time.new(@microseconds - other.microseconds)
|
|
30
|
+
def -@ = Time.new(-@microseconds)
|
|
31
|
+
def <=>(other) = @microseconds <=> other.microseconds
|
|
32
|
+
|
|
33
|
+
def hash = @microseconds.hash
|
|
34
|
+
def eql?(other) = other.is_a?(Time) && @microseconds == other.microseconds
|
|
35
|
+
alias == eql?
|
|
36
|
+
|
|
37
|
+
def to_s = "#<SFML::Time #{as_seconds}s>"
|
|
38
|
+
alias inspect to_s
|
|
39
|
+
|
|
40
|
+
def self.from_native(struct) # :nodoc:
|
|
41
|
+
new(struct[:microseconds])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_native # :nodoc:
|
|
45
|
+
C::System::Time.new.tap { |t| t[:microseconds] = @microseconds }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# 2D vector with float components. Operator-friendly and pattern-matchable.
|
|
3
|
+
#
|
|
4
|
+
# v = SFML::Vector2[3, 4]
|
|
5
|
+
# v.length #=> 5.0
|
|
6
|
+
# v + Vector2[1, 1] #=> Vector2(4, 5)
|
|
7
|
+
# v * 2 #=> Vector2(6, 8)
|
|
8
|
+
# x, y = v # destructures via to_a
|
|
9
|
+
class Vector2
|
|
10
|
+
attr_reader :x, :y
|
|
11
|
+
|
|
12
|
+
def self.[](x, y) = new(x, y)
|
|
13
|
+
def self.zero = new(0, 0)
|
|
14
|
+
|
|
15
|
+
def initialize(x = 0, y = 0)
|
|
16
|
+
@x = x
|
|
17
|
+
@y = y
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def +(other) = Vector2.new(@x + other.x, @y + other.y)
|
|
22
|
+
def -(other) = Vector2.new(@x - other.x, @y - other.y)
|
|
23
|
+
def *(scalar) = Vector2.new(@x * scalar, @y * scalar)
|
|
24
|
+
def /(scalar) = Vector2.new(@x / scalar.to_f, @y / scalar.to_f)
|
|
25
|
+
def -@ = Vector2.new(-@x, -@y)
|
|
26
|
+
|
|
27
|
+
# Lets Ruby evaluate `2 * vec` as `vec * 2`. Without this, Numeric#*
|
|
28
|
+
# would raise TypeError because it doesn't know about Vector2.
|
|
29
|
+
def coerce(other)
|
|
30
|
+
raise TypeError, "Vector2 cannot coerce #{other.class}" unless other.is_a?(Numeric)
|
|
31
|
+
[self, other]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ==(other) = other.is_a?(Vector2) && @x == other.x && @y == other.y
|
|
35
|
+
alias eql? ==
|
|
36
|
+
def hash = [@x, @y].hash
|
|
37
|
+
|
|
38
|
+
def length = Math.sqrt(length_sq)
|
|
39
|
+
def length_sq = (@x * @x) + (@y * @y)
|
|
40
|
+
|
|
41
|
+
def normalize
|
|
42
|
+
len = length
|
|
43
|
+
return Vector2.zero if len.zero?
|
|
44
|
+
self / len
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def dot(other) = (@x * other.x) + (@y * other.y)
|
|
48
|
+
def cross(other) = (@x * other.y) - (@y * other.x)
|
|
49
|
+
|
|
50
|
+
def to_a = [@x, @y]
|
|
51
|
+
def to_h = { x: @x, y: @y }
|
|
52
|
+
def deconstruct = [@x, @y]
|
|
53
|
+
def deconstruct_keys(_keys) = { x: @x, y: @y }
|
|
54
|
+
|
|
55
|
+
def to_s = "Vector2(#{@x}, #{@y})"
|
|
56
|
+
alias inspect to_s
|
|
57
|
+
|
|
58
|
+
def self.from_native(struct) # :nodoc:
|
|
59
|
+
new(struct[:x], struct[:y])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_native_f # :nodoc:
|
|
63
|
+
C::System::Vector2f.new.tap { |v| v[:x] = @x; v[:y] = @y }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# 3D vector with float components. Mirrors Vector2's surface.
|
|
3
|
+
class Vector3
|
|
4
|
+
attr_reader :x, :y, :z
|
|
5
|
+
|
|
6
|
+
def self.[](x, y, z) = new(x, y, z)
|
|
7
|
+
def self.zero = new(0, 0, 0)
|
|
8
|
+
|
|
9
|
+
def initialize(x = 0, y = 0, z = 0)
|
|
10
|
+
@x = x
|
|
11
|
+
@y = y
|
|
12
|
+
@z = z
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def +(other) = Vector3.new(@x + other.x, @y + other.y, @z + other.z)
|
|
17
|
+
def -(other) = Vector3.new(@x - other.x, @y - other.y, @z - other.z)
|
|
18
|
+
def *(scalar) = Vector3.new(@x * scalar, @y * scalar, @z * scalar)
|
|
19
|
+
def /(scalar) = Vector3.new(@x / scalar.to_f, @y / scalar.to_f, @z / scalar.to_f)
|
|
20
|
+
def -@ = Vector3.new(-@x, -@y, -@z)
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(Vector3) && @x == other.x && @y == other.y && @z == other.z
|
|
24
|
+
end
|
|
25
|
+
alias eql? ==
|
|
26
|
+
def hash = [@x, @y, @z].hash
|
|
27
|
+
|
|
28
|
+
def length = Math.sqrt(length_sq)
|
|
29
|
+
def length_sq = (@x * @x) + (@y * @y) + (@z * @z)
|
|
30
|
+
|
|
31
|
+
def normalize
|
|
32
|
+
len = length
|
|
33
|
+
return Vector3.zero if len.zero?
|
|
34
|
+
self / len
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dot(other) = (@x * other.x) + (@y * other.y) + (@z * other.z)
|
|
38
|
+
|
|
39
|
+
def cross(other)
|
|
40
|
+
Vector3.new(
|
|
41
|
+
(@y * other.z) - (@z * other.y),
|
|
42
|
+
(@z * other.x) - (@x * other.z),
|
|
43
|
+
(@x * other.y) - (@y * other.x)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_a = [@x, @y, @z]
|
|
48
|
+
def to_h = { x: @x, y: @y, z: @z }
|
|
49
|
+
def deconstruct = [@x, @y, @z]
|
|
50
|
+
def deconstruct_keys(_keys) = { x: @x, y: @y, z: @z }
|
|
51
|
+
|
|
52
|
+
def to_s = "Vector3(#{@x}, #{@y}, #{@z})"
|
|
53
|
+
alias inspect to_s
|
|
54
|
+
|
|
55
|
+
def self.from_native(struct) # :nodoc:
|
|
56
|
+
new(struct[:x], struct[:y], struct[:z])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_native_f # :nodoc:
|
|
60
|
+
C::System::Vector3f.new.tap { |v| v[:x] = @x; v[:y] = @y; v[:z] = @z }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/sfml/version.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# The SFML/CSFML release this gem is built against. End users can
|
|
3
|
+
# check this at runtime to assert binding compatibility.
|
|
4
|
+
CSFML_VERSION = "3.0.0"
|
|
5
|
+
|
|
6
|
+
# Gem version. The first three segments mirror CSFML_VERSION exactly;
|
|
7
|
+
# the trailing fourth segment is our own patch number for fixes /
|
|
8
|
+
# additions made on top of the same upstream CSFML release.
|
|
9
|
+
#
|
|
10
|
+
# Examples on a hypothetical timeline:
|
|
11
|
+
#
|
|
12
|
+
# "3.0.0.0" — first cut against CSFML 3.0.0
|
|
13
|
+
# "3.0.0.1" — our bug fix, still on CSFML 3.0.0
|
|
14
|
+
# "3.0.0.2" — another patch
|
|
15
|
+
# "3.0.1.0" — CSFML 3.0.1 ships, we re-cut from upstream
|
|
16
|
+
# "3.0.1.1" — our patch on top of CSFML 3.0.1
|
|
17
|
+
# "3.1.0.0" — CSFML 3.1.0 ships, we add new bindings
|
|
18
|
+
VERSION = "3.0.0.0"
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# The system clipboard. UTF-8 in, UTF-8 out — even when the OS stores
|
|
3
|
+
# it as wide chars, we go through CSFML's `*UnicodeString` variants
|
|
4
|
+
# and convert in Ruby so non-ASCII text round-trips losslessly.
|
|
5
|
+
#
|
|
6
|
+
# SFML::Clipboard.text #=> "whatever was last copied"
|
|
7
|
+
# SFML::Clipboard.text = "пример"
|
|
8
|
+
#
|
|
9
|
+
# Useful inside text-input UIs (Ctrl+C / Ctrl+V handlers) and for
|
|
10
|
+
# quick "copy that error message" buttons in tools.
|
|
11
|
+
module Clipboard
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def text
|
|
15
|
+
ptr = C::Window.sfClipboard_getUnicodeString
|
|
16
|
+
return "" if ptr.null?
|
|
17
|
+
|
|
18
|
+
codepoints = []
|
|
19
|
+
offset = 0
|
|
20
|
+
loop do
|
|
21
|
+
cp = ptr.get_uint32(offset)
|
|
22
|
+
break if cp.zero?
|
|
23
|
+
codepoints << cp
|
|
24
|
+
offset += 4
|
|
25
|
+
end
|
|
26
|
+
codepoints.pack("U*")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def text=(value)
|
|
30
|
+
str = value.to_s.encode("UTF-8")
|
|
31
|
+
cps = str.unpack("U*")
|
|
32
|
+
buf = FFI::MemoryPointer.new(:uint32, cps.length + 1)
|
|
33
|
+
buf.write_array_of_uint32(cps + [0])
|
|
34
|
+
C::Window.sfClipboard_setUnicodeString(buf)
|
|
35
|
+
str
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A mouse cursor — either a built-in system shape, or a custom bitmap.
|
|
3
|
+
# Apply it with `window.cursor = cursor`.
|
|
4
|
+
#
|
|
5
|
+
# window.cursor = SFML::Cursor.system(:hand)
|
|
6
|
+
# window.cursor = SFML::Cursor.system(:not_allowed)
|
|
7
|
+
#
|
|
8
|
+
# pixels = "..." # 32×32 RGBA bytes
|
|
9
|
+
# window.cursor = SFML::Cursor.from_pixels(32, 32, pixels, hotspot: [16, 16])
|
|
10
|
+
#
|
|
11
|
+
# See SFML::Cursor::TYPES for the full list of system shapes.
|
|
12
|
+
class Cursor
|
|
13
|
+
# Order matches sfCursorType in CSFML/Window/Cursor.h.
|
|
14
|
+
TYPES = %i[
|
|
15
|
+
arrow arrow_wait wait text hand
|
|
16
|
+
size_horizontal size_vertical
|
|
17
|
+
size_top_left_bottom_right size_bottom_left_top_right
|
|
18
|
+
size_left size_right size_top size_bottom
|
|
19
|
+
size_top_left size_bottom_right size_bottom_left size_top_right
|
|
20
|
+
size_all cross help not_allowed
|
|
21
|
+
].freeze
|
|
22
|
+
TYPE_INDEX = TYPES.each_with_index.to_h.freeze
|
|
23
|
+
|
|
24
|
+
# Build a cursor matching one of the OS-supplied shapes. Some types
|
|
25
|
+
# (the directional `size_*` family beyond Horizontal/Vertical) only
|
|
26
|
+
# render distinct shapes on Linux — on macOS / Windows they fall
|
|
27
|
+
# back to the closest equivalent.
|
|
28
|
+
def self.system(type)
|
|
29
|
+
code = TYPE_INDEX.fetch(type) do
|
|
30
|
+
raise ArgumentError, "Unknown cursor type: #{type.inspect}. " \
|
|
31
|
+
"Expected one of: #{TYPES.inspect}"
|
|
32
|
+
end
|
|
33
|
+
ptr = C::Window.sfCursor_createFromSystem(code)
|
|
34
|
+
raise Error, "sfCursor_createFromSystem returned NULL for #{type.inspect}" if ptr.null?
|
|
35
|
+
_wrap(ptr)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Build a cursor from raw RGBA pixels. `pixels` must be exactly
|
|
39
|
+
# width*height*4 bytes. `hotspot:` is the [x, y] within the cursor
|
|
40
|
+
# image that maps to the actual click point (e.g. tip of an arrow).
|
|
41
|
+
def self.from_pixels(width, height, pixels, hotspot: [0, 0])
|
|
42
|
+
expected = Integer(width) * Integer(height) * 4
|
|
43
|
+
raise ArgumentError, "pixels must be #{expected} bytes, got #{pixels.bytesize}" if pixels.bytesize != expected
|
|
44
|
+
|
|
45
|
+
buf = FFI::MemoryPointer.new(:uint8, expected)
|
|
46
|
+
buf.write_bytes(pixels)
|
|
47
|
+
|
|
48
|
+
size = C::System::Vector2u.new
|
|
49
|
+
size[:x] = Integer(width); size[:y] = Integer(height)
|
|
50
|
+
|
|
51
|
+
hot = C::System::Vector2u.new
|
|
52
|
+
hot[:x] = Integer(hotspot[0]); hot[:y] = Integer(hotspot[1])
|
|
53
|
+
|
|
54
|
+
ptr = C::Window.sfCursor_createFromPixels(buf, size, hot)
|
|
55
|
+
raise Error, "sfCursor_createFromPixels returned NULL" if ptr.null?
|
|
56
|
+
_wrap(ptr)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
attr_reader :handle # :nodoc:
|
|
60
|
+
|
|
61
|
+
# @!visibility private
|
|
62
|
+
def self._wrap(ptr)
|
|
63
|
+
cursor = allocate
|
|
64
|
+
cursor.instance_variable_set(:@handle, FFI::AutoPointer.new(ptr, C::Window.method(:sfCursor_destroy)))
|
|
65
|
+
cursor
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A window event. Holds a type symbol and a Hash of type-specific data.
|
|
3
|
+
# Pattern-match it directly; that's the intended interface:
|
|
4
|
+
#
|
|
5
|
+
# case event
|
|
6
|
+
# in {type: :closed} then ...
|
|
7
|
+
# in {type: :key_pressed, code: :escape} then ...
|
|
8
|
+
# in {type: :resized, size:} then ...
|
|
9
|
+
# in {type: :mouse_moved, position: {x:, y:}} then ...
|
|
10
|
+
# in {type: :mouse_wheel_scrolled, delta:} then ...
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# Type-specific fields are also reachable as plain methods (`event.code`,
|
|
14
|
+
# `event.position`) for the cases when pattern matching is overkill.
|
|
15
|
+
class Event
|
|
16
|
+
# Order matches sfMouseButton in CSFML 3: left, right, middle,
|
|
17
|
+
# extra1, extra2. (CSFML 2 called the last two x_button1/x_button2,
|
|
18
|
+
# but SFML 3 dropped the X-button terminology.)
|
|
19
|
+
MOUSE_BUTTONS = %i[left right middle extra1 extra2].freeze
|
|
20
|
+
MOUSE_WHEELS = %i[vertical horizontal].freeze
|
|
21
|
+
|
|
22
|
+
attr_reader :type, :data
|
|
23
|
+
|
|
24
|
+
def initialize(type, data = {})
|
|
25
|
+
@type = type
|
|
26
|
+
@data = data.freeze
|
|
27
|
+
freeze
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def [](key) = @data[key]
|
|
31
|
+
def fetch(*args, &) = @data.fetch(*args, &)
|
|
32
|
+
|
|
33
|
+
def deconstruct_keys(_keys)
|
|
34
|
+
{ type: @type, **@data }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def respond_to_missing?(name, _private = false)
|
|
38
|
+
@data.key?(name) || super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def method_missing(name, *args)
|
|
42
|
+
return @data[name] if args.empty? && @data.key?(name)
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s = "#<SFML::Event #{@type} #{@data.inspect}>"
|
|
47
|
+
alias inspect to_s
|
|
48
|
+
|
|
49
|
+
# Reads the sfEvent union buffer, decodes the discriminator, and reads
|
|
50
|
+
# the type-specific variant. CSFML lays each variant out with the
|
|
51
|
+
# sfEventType as the first field, so we can re-interpret the same memory
|
|
52
|
+
# as the right struct.
|
|
53
|
+
def self.from_native(buffer)
|
|
54
|
+
type_index = buffer[:type]
|
|
55
|
+
type_sym = C::Window::EVENT_TYPES[type_index] || :unknown
|
|
56
|
+
|
|
57
|
+
data = decode(type_sym, buffer.to_ptr)
|
|
58
|
+
new(type_sym, data)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.decode(type_sym, ptr)
|
|
62
|
+
case type_sym
|
|
63
|
+
when :closed, :focus_lost, :focus_gained,
|
|
64
|
+
:mouse_entered, :mouse_left
|
|
65
|
+
EMPTY
|
|
66
|
+
|
|
67
|
+
when :resized
|
|
68
|
+
s = C::Window::SizeEvent.new(ptr)
|
|
69
|
+
{ size: Vector2.new(s[:size][:x], s[:size][:y]) }
|
|
70
|
+
|
|
71
|
+
when :key_pressed, :key_released
|
|
72
|
+
k = C::Window::KeyEvent.new(ptr)
|
|
73
|
+
{
|
|
74
|
+
code: Keyboard.code_to_symbol(k[:code]),
|
|
75
|
+
alt: k[:alt],
|
|
76
|
+
control: k[:control],
|
|
77
|
+
shift: k[:shift],
|
|
78
|
+
system: k[:system],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
when :text_entered
|
|
82
|
+
t = C::Window::TextEvent.new(ptr)
|
|
83
|
+
{ unicode: t[:unicode], char: [t[:unicode]].pack("U*") }
|
|
84
|
+
|
|
85
|
+
when :mouse_moved
|
|
86
|
+
m = C::Window::MouseMoveEvent.new(ptr)
|
|
87
|
+
{ position: Vector2.new(m[:position][:x], m[:position][:y]) }
|
|
88
|
+
|
|
89
|
+
when :mouse_moved_raw
|
|
90
|
+
m = C::Window::MouseMoveEvent.new(ptr)
|
|
91
|
+
# Raw event re-uses the same struct shape but the field is a delta.
|
|
92
|
+
{ delta: Vector2.new(m[:position][:x], m[:position][:y]) }
|
|
93
|
+
|
|
94
|
+
when :mouse_button_pressed, :mouse_button_released
|
|
95
|
+
b = C::Window::MouseButtonEvent.new(ptr)
|
|
96
|
+
{
|
|
97
|
+
button: MOUSE_BUTTONS[b[:button]] || :unknown,
|
|
98
|
+
position: Vector2.new(b[:position][:x], b[:position][:y]),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
when :mouse_wheel_scrolled
|
|
102
|
+
w = C::Window::MouseWheelScrollEvent.new(ptr)
|
|
103
|
+
{
|
|
104
|
+
wheel: MOUSE_WHEELS[w[:wheel]] || :unknown,
|
|
105
|
+
delta: w[:delta],
|
|
106
|
+
position: Vector2.new(w[:position][:x], w[:position][:y]),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
when :joystick_button_pressed, :joystick_button_released
|
|
110
|
+
b = C::Window::JoystickButtonEvent.new(ptr)
|
|
111
|
+
{ joystick_id: b[:joystick_id], button: b[:button] }
|
|
112
|
+
|
|
113
|
+
when :joystick_moved
|
|
114
|
+
m = C::Window::JoystickMoveEvent.new(ptr)
|
|
115
|
+
{
|
|
116
|
+
joystick_id: m[:joystick_id],
|
|
117
|
+
axis: Joystick::AXES[m[:axis]] || :unknown,
|
|
118
|
+
position: m[:position],
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
when :joystick_connected, :joystick_disconnected
|
|
122
|
+
c = C::Window::JoystickConnectEvent.new(ptr)
|
|
123
|
+
{ joystick_id: c[:joystick_id] }
|
|
124
|
+
|
|
125
|
+
else
|
|
126
|
+
EMPTY
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
EMPTY = {}.freeze
|
|
131
|
+
private_constant :EMPTY
|
|
132
|
+
end
|
|
133
|
+
end
|