1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
use crate::core::text::WeakFont;
use crate::ffi;
use std::cell::RefCell;
use std::ffi::CString;
thread_local! {
/// Holds the current tooltip string. `GuiSetTooltip` stores the raw pointer and
/// dereferences it on later control draws, so the owning `CString` must outlive
/// the call — it lives here until the next `gui_set_tooltip` on this thread.
static TOOLTIP: RefCell<Option<CString>> = const { RefCell::new(None) };
}
/// raygui global state, style, font and tooltip controls. Implemented for the
/// draw-handle types (call during drawing) and for [`crate::RaylibHandle`] (call during
/// setup).
pub trait RaylibGuiState {
/// Enable gui controls (global state)
#[inline]
fn gui_enable(&mut self) {
unsafe { ffi::GuiEnable() }
}
/// Disable gui controls (global state)
#[inline]
fn gui_disable(&mut self) {
unsafe { ffi::GuiDisable() }
}
/// Lock gui controls (global state)
#[inline]
fn gui_lock(&mut self) {
unsafe { ffi::GuiLock() }
}
/// Unlock gui controls (global state)
#[inline]
fn gui_unlock(&mut self) {
unsafe { ffi::GuiUnlock() }
}
/// Check if gui is locked (global state)
#[inline]
fn gui_is_locked(&mut self) -> bool {
unsafe { ffi::GuiIsLocked() }
}
/// Set gui controls alpha (global state); `alpha` is clamped 0.0..=1.0 by raygui.
#[inline]
fn gui_set_alpha(&mut self, alpha: f32) {
unsafe { ffi::GuiSetAlpha(alpha) }
}
/// Set gui state (global state)
#[inline]
fn gui_set_state(&mut self, state: crate::consts::GuiState) {
unsafe { ffi::GuiSetState(state as i32) }
}
/// Get gui state (global state)
#[inline]
fn gui_get_state(&self) -> crate::consts::GuiState {
use crate::consts::GuiState;
// raygui returns one of the GuiState discriminants; map explicitly rather
// than transmuting an arbitrary i32 into the enum.
match unsafe { ffi::GuiGetState() } {
x if x == GuiState::STATE_NORMAL as i32 => GuiState::STATE_NORMAL,
x if x == GuiState::STATE_FOCUSED as i32 => GuiState::STATE_FOCUSED,
x if x == GuiState::STATE_PRESSED as i32 => GuiState::STATE_PRESSED,
x if x == GuiState::STATE_DISABLED as i32 => GuiState::STATE_DISABLED,
_ => GuiState::STATE_NORMAL,
}
}
/// Set gui custom font (global state)
#[inline]
fn gui_set_font(&mut self, font: impl AsRef<ffi::Font>) {
unsafe { ffi::GuiSetFont(*font.as_ref()) }
}
/// Get gui custom font (global state)
#[inline]
fn gui_get_font(&mut self) -> WeakFont {
unsafe { WeakFont(ffi::GuiGetFont()) }
}
/// Set one style property
#[inline]
fn gui_set_style(
&mut self,
control: crate::consts::GuiControl,
property: impl GuiProperty,
value: i32,
) {
unsafe { ffi::GuiSetStyle(control as i32, property.as_i32(), value) }
}
/// Get one style property
#[inline]
fn gui_get_style(&self, control: crate::consts::GuiControl, property: impl GuiProperty) -> i32 {
unsafe { ffi::GuiGetStyle(control as i32, property.as_i32()) }
}
/// Load style file (.rgs)
#[inline]
fn gui_load_style(&mut self, filename: impl AsRef<str>) {
// CString used here (not scratch): GuiLoadStyle is not per-frame and may
// retain the pointer's contents during parsing; a fresh allocation is correct.
let c = CString::new(filename.as_ref()).unwrap();
unsafe { ffi::GuiLoadStyle(c.as_ptr()) }
}
/// Load style default over global style
#[inline]
fn gui_load_style_default(&mut self) {
unsafe { ffi::GuiLoadStyleDefault() }
}
/// Load a binary `.rgs` style file from an in-memory buffer. Mirrors
/// [`Self::gui_load_style`] (path-based) for embedded / network-loaded
/// styles.
///
/// raygui only supports the binary `.rgs` format from memory (not the
/// text format). Returns [`crate::core::error::LoadStyleFromMemoryError::LengthOverflow`] if
/// `data.len()` exceeds `i32::MAX`. raygui itself does not signal style-
/// parse failures — the same silent-failure semantics as
/// [`Self::gui_load_style`] apply.
#[inline]
fn gui_load_style_from_memory(
&mut self,
data: &[u8],
) -> Result<(), crate::core::error::LoadStyleFromMemoryError> {
let len = i32::try_from(data.len()).map_err(|_| {
crate::core::error::LoadStyleFromMemoryError::LengthOverflow(data.len())
})?;
// SAFETY: data lives for the duration of the call; raygui memcpys out
// of the buffer synchronously.
unsafe { ffi::GuiLoadStyleFromMemory(data.as_ptr(), len) };
Ok(())
}
/// Enable gui tooltips (global state)
#[inline]
fn gui_enable_tooltip(&mut self) {
unsafe { ffi::GuiEnableTooltip() }
}
/// Disable gui tooltips (global state)
#[inline]
fn gui_disable_tooltip(&mut self) {
unsafe { ffi::GuiDisableTooltip() }
}
/// Set tooltip string. The string is retained until the next call, because
/// raygui stores the pointer and reads it on later control draws.
#[inline]
fn gui_set_tooltip(&mut self, tooltip: impl AsRef<str>) {
let c = CString::new(tooltip.as_ref()).unwrap_or_default();
TOOLTIP.with(|cell| {
let mut slot = cell.borrow_mut();
*slot = Some(c);
let ptr = slot.as_ref().unwrap().as_ptr();
// SAFETY: GuiSetTooltip stores `ptr` and dereferences it during later
// control draws; the owning CString is retained in TOOLTIP, so the
// pointer stays valid until the next gui_set_tooltip on this thread.
unsafe { ffi::GuiSetTooltip(ptr) };
});
}
}
#[diagnostic::on_unimplemented(
message = "{Self} is not a gui property, or does not implement the GuiProperty trait.",
note = "As of Raylib 5.5, raygui functions that once took \"property enum as i32\" now just take the enum."
)]
/// Trait for types that can be used as raygui style property identifiers.
pub trait GuiProperty {
/// Convert this property to its `i32` discriminant for the raygui C API.
// The convention `as_i32(self)` matches the original API; properties are
// cheap `Copy` enums so consuming `self` is intentional.
#[allow(clippy::wrong_self_convention)]
fn as_i32(self) -> i32;
}
impl GuiProperty for crate::consts::GuiControlProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiDefaultProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiCheckBoxProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiColorPickerProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiComboBoxProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiDropdownBoxProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiListViewProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiProgressBarProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiScrollBarProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiSliderProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiValueBoxProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
impl GuiProperty for crate::consts::GuiToggleProperty {
fn as_i32(self) -> i32 {
self as i32
}
}
#[cfg(test)]
mod load_style_from_memory_tests {
use crate::core::error::LoadStyleFromMemoryError;
// Pure unit test on the i32 boundary. The actual FFI call needs a live
// RaylibHandle (which would force the test under software_renderer), but
// the LengthOverflow path returns before reaching FFI, so we exercise
// the boundary at the conversion layer directly.
#[test]
fn length_overflow_at_i32_max_plus_one() {
let oversize: usize = i32::MAX as usize + 1;
let err = i32::try_from(oversize)
.map_err(|_| LoadStyleFromMemoryError::LengthOverflow(oversize))
.unwrap_err();
assert!(matches!(err, LoadStyleFromMemoryError::LengthOverflow(n) if n == oversize));
}
#[test]
fn length_overflow_at_i32_max_is_ok() {
// i32::MAX as usize must NOT trigger overflow.
let at_max: usize = i32::MAX as usize;
let result =
i32::try_from(at_max).map_err(|_| LoadStyleFromMemoryError::LengthOverflow(at_max));
assert!(result.is_ok());
}
}