gdnative_core/export/property/
hint.rs

1//! Strongly typed property hints.
2
3use std::fmt::{self, Write};
4use std::ops::RangeInclusive;
5
6use crate::core_types::GodotString;
7use crate::core_types::VariantType;
8use crate::sys;
9
10use super::{Export, ExportInfo};
11
12/// Hints that an integer or float property should be within an inclusive range.
13///
14/// # Examples
15///
16/// Basic usage:
17///
18/// ```rust
19/// use gdnative_core::export::hint::RangeHint;
20///
21/// let hint: RangeHint<f64> = RangeHint::new(0.0, 20.0).or_greater();
22/// ```
23#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
24pub struct RangeHint<T> {
25    /// Minimal value, inclusive
26    pub min: T,
27    /// Maximal value, inclusive
28    pub max: T,
29    /// Optional step value for the slider
30    pub step: Option<T>,
31    /// Allow manual input above the `max` value
32    pub or_greater: bool,
33    /// Allow manual input below the `min` value
34    pub or_lesser: bool,
35}
36
37impl<T> RangeHint<T>
38where
39    T: fmt::Display,
40{
41    /// Creates a new `RangeHint`.
42    #[inline]
43    pub fn new(min: T, max: T) -> Self {
44        RangeHint {
45            min,
46            max,
47            step: None,
48            or_greater: false,
49            or_lesser: false,
50        }
51    }
52
53    /// Builder-style method that returns `self` with the given step.
54    #[inline]
55    pub fn with_step(mut self, step: T) -> Self {
56        self.step.replace(step);
57        self
58    }
59
60    /// Builder-style method that returns `self` with the `or_greater` hint.
61    #[inline]
62    pub fn or_greater(mut self) -> Self {
63        self.or_greater = true;
64        self
65    }
66
67    /// Builder-style method that returns `self` with the `or_lesser` hint.
68    #[inline]
69    pub fn or_lesser(mut self) -> Self {
70        self.or_lesser = true;
71        self
72    }
73
74    /// Formats the hint as a Godot hint string.
75    fn to_godot_hint_string(&self) -> GodotString {
76        let mut s = String::new();
77
78        write!(s, "{},{}", self.min, self.max).unwrap();
79        if let Some(step) = &self.step {
80            write!(s, ",{step}").unwrap();
81        }
82
83        if self.or_greater {
84            s.push_str(",or_greater");
85        }
86        if self.or_lesser {
87            s.push_str(",or_lesser");
88        }
89
90        s.into()
91    }
92}
93
94impl<T> From<RangeInclusive<T>> for RangeHint<T>
95where
96    T: fmt::Display,
97{
98    #[inline]
99    fn from(range: RangeInclusive<T>) -> Self {
100        let (min, max) = range.into_inner();
101        RangeHint::new(min, max)
102    }
103}
104
105/// Hints that an integer, float or string property is an enumerated value to pick in a list.
106///
107///
108/// # Examples
109///
110/// Basic usage:
111///
112/// ```rust
113/// use gdnative_core::export::hint::EnumHint;
114///
115/// let hint = EnumHint::new(vec!["Foo".into(), "Bar".into(), "Baz".into()]);
116/// ```
117#[derive(Clone, Eq, PartialEq, Debug, Default)]
118pub struct EnumHint {
119    values: Vec<String>,
120}
121
122impl EnumHint {
123    #[inline]
124    pub fn new(values: Vec<String>) -> Self {
125        EnumHint { values }
126    }
127
128    /// Formats the hint as a Godot hint string.
129    fn to_godot_hint_string(&self) -> GodotString {
130        let mut s = String::new();
131
132        let mut iter = self.values.iter();
133
134        if let Some(first) = iter.next() {
135            write!(s, "{first}").unwrap();
136        }
137
138        for rest in iter {
139            write!(s, ",{rest}").unwrap();
140        }
141
142        s.into()
143    }
144}
145
146/// Possible hints for integers.
147#[derive(Clone, Debug)]
148pub enum IntHint<T> {
149    /// Hints that an integer or float property should be within a range.
150    Range(RangeHint<T>),
151    /// Hints that an integer or float property should be within an exponential range.
152    ExpRange(RangeHint<T>),
153    /// Hints that an integer, float or string property is an enumerated value to pick in a list.
154    Enum(EnumHint),
155    /// Hints that an integer property is a bitmask with named bit flags.
156    Flags(EnumHint),
157    /// Hints that an integer property is a bitmask using the optionally named 2D render layers.
158    Layers2DRender,
159    /// Hints that an integer property is a bitmask using the optionally named 2D physics layers.
160    Layers2DPhysics,
161    /// Hints that an integer property is a bitmask using the optionally named 3D render layers.
162    Layers3DRender,
163    /// Hints that an integer property is a bitmask using the optionally named 3D physics layers.
164    Layers3DPhysics,
165}
166
167impl<T> IntHint<T>
168where
169    T: fmt::Display,
170{
171    #[inline]
172    pub fn export_info(self) -> ExportInfo {
173        use IntHint as IH;
174
175        let hint_kind = match &self {
176            IH::Range(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_RANGE,
177            IH::ExpRange(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_EXP_RANGE,
178            IH::Enum(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_ENUM,
179            IH::Flags(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_FLAGS,
180            IH::Layers2DRender => sys::godot_property_hint_GODOT_PROPERTY_HINT_LAYERS_2D_RENDER,
181            IH::Layers2DPhysics => sys::godot_property_hint_GODOT_PROPERTY_HINT_LAYERS_2D_PHYSICS,
182            IH::Layers3DRender => sys::godot_property_hint_GODOT_PROPERTY_HINT_LAYERS_3D_RENDER,
183            IH::Layers3DPhysics => sys::godot_property_hint_GODOT_PROPERTY_HINT_LAYERS_3D_PHYSICS,
184        };
185
186        let hint_string = match self {
187            IH::Range(range) | IH::ExpRange(range) => range.to_godot_hint_string(),
188            IH::Enum(e) | IH::Flags(e) => e.to_godot_hint_string(),
189            _ => GodotString::new(),
190        };
191
192        ExportInfo {
193            variant_type: VariantType::I64,
194            hint_kind,
195            hint_string,
196        }
197    }
198}
199
200impl<T> From<RangeHint<T>> for IntHint<T>
201where
202    T: fmt::Display,
203{
204    #[inline]
205    fn from(hint: RangeHint<T>) -> Self {
206        Self::Range(hint)
207    }
208}
209
210impl<T> From<RangeInclusive<T>> for IntHint<T>
211where
212    T: fmt::Display,
213{
214    #[inline]
215    fn from(range: RangeInclusive<T>) -> Self {
216        Self::Range(range.into())
217    }
218}
219
220impl<T> From<EnumHint> for IntHint<T> {
221    #[inline]
222    fn from(hint: EnumHint) -> Self {
223        Self::Enum(hint)
224    }
225}
226
227/// Hints that a float property should be edited via an exponential easing function.
228#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
229pub struct ExpEasingHint {
230    /// Flip the curve horizontally.
231    pub is_attenuation: bool,
232    /// Also include in/out easing.
233    pub is_in_out: bool,
234}
235
236impl ExpEasingHint {
237    #[inline]
238    pub fn new() -> Self {
239        Self::default()
240    }
241
242    /// Formats the hint as a Godot hint string.
243    fn to_godot_hint_string(self) -> GodotString {
244        let mut s = String::new();
245
246        if self.is_attenuation {
247            s.push_str("attenuation");
248        }
249
250        if self.is_in_out {
251            if self.is_attenuation {
252                s.push(',');
253            }
254            s.push_str("inout");
255        }
256
257        s.into()
258    }
259}
260
261/// Possible hints for floats.
262#[derive(Clone, Debug)]
263pub enum FloatHint<T> {
264    /// Hints that an integer or float property should be within a range.
265    Range(RangeHint<T>),
266    /// Hints that an integer or float property should be within an exponential range.
267    ExpRange(RangeHint<T>),
268    /// Hints that a float property should be edited via an exponential easing function.
269    ExpEasing(ExpEasingHint),
270}
271
272impl<T> FloatHint<T>
273where
274    T: fmt::Display,
275{
276    #[inline]
277    pub fn export_info(self) -> ExportInfo {
278        use FloatHint as FH;
279
280        let hint_kind = match &self {
281            FH::Range(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_RANGE,
282            FH::ExpRange(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_EXP_RANGE,
283            FH::ExpEasing(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_EXP_EASING,
284        };
285
286        let hint_string = match self {
287            FH::Range(range) | FH::ExpRange(range) => range.to_godot_hint_string(),
288            FH::ExpEasing(e) => e.to_godot_hint_string(),
289        };
290
291        ExportInfo {
292            variant_type: VariantType::F64,
293            hint_kind,
294            hint_string,
295        }
296    }
297}
298
299impl<T> From<RangeHint<T>> for FloatHint<T>
300where
301    T: fmt::Display,
302{
303    #[inline]
304    fn from(hint: RangeHint<T>) -> Self {
305        Self::Range(hint)
306    }
307}
308
309impl<T> From<RangeInclusive<T>> for FloatHint<T>
310where
311    T: fmt::Display,
312{
313    #[inline]
314    fn from(range: RangeInclusive<T>) -> Self {
315        Self::Range(range.into())
316    }
317}
318
319impl<T> From<ExpEasingHint> for FloatHint<T> {
320    #[inline]
321    fn from(hint: ExpEasingHint) -> Self {
322        Self::ExpEasing(hint)
323    }
324}
325
326/// Possible hints for strings.
327#[derive(Clone, Debug)]
328pub enum StringHint {
329    /// Hints that an integer, float or string property is an enumerated value to pick in a list.
330    Enum(EnumHint),
331    /// Hints that a string property is a path to a file.
332    File(EnumHint),
333    /// Hints that a string property is an absolute path to a file outside the project folder.
334    GlobalFile(EnumHint),
335    /// Hints that a string property is a path to a directory.
336    Dir,
337    /// Hints that a string property is an absolute path to a directory outside the project folder.
338    GlobalDir,
339    /// Hints that a string property is text with line breaks.
340    Multiline,
341    /// Hints that a string property should have a placeholder text visible on its input field, whenever the property is empty.
342    Placeholder { placeholder: String },
343}
344
345impl StringHint {
346    #[inline]
347    pub fn export_info(self) -> ExportInfo {
348        use StringHint as SH;
349
350        let hint_kind = match &self {
351            SH::Enum(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_ENUM,
352            SH::File(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_FILE,
353            SH::GlobalFile(_) => sys::godot_property_hint_GODOT_PROPERTY_HINT_GLOBAL_FILE,
354            SH::Dir => sys::godot_property_hint_GODOT_PROPERTY_HINT_DIR,
355            SH::GlobalDir => sys::godot_property_hint_GODOT_PROPERTY_HINT_GLOBAL_DIR,
356            SH::Multiline => sys::godot_property_hint_GODOT_PROPERTY_HINT_MULTILINE_TEXT,
357            SH::Placeholder { .. } => sys::godot_property_hint_GODOT_PROPERTY_HINT_PLACEHOLDER_TEXT,
358        };
359
360        let hint_string = match self {
361            SH::Enum(e) | SH::File(e) | SH::GlobalFile(e) => e.to_godot_hint_string(),
362            SH::Placeholder { placeholder } => placeholder.into(),
363            _ => GodotString::new(),
364        };
365
366        ExportInfo {
367            variant_type: VariantType::GodotString,
368            hint_kind,
369            hint_string,
370        }
371    }
372}
373
374/// Possible hints for `Color`.
375#[derive(Clone, Debug)]
376pub enum ColorHint {
377    /// Hints that a color property should be edited without changing its alpha component.
378    NoAlpha,
379}
380
381impl ColorHint {
382    #[inline]
383    pub fn export_info(self) -> ExportInfo {
384        ExportInfo {
385            variant_type: VariantType::Color,
386            hint_kind: match self {
387                ColorHint::NoAlpha => sys::godot_property_hint_GODOT_PROPERTY_HINT_COLOR_NO_ALPHA,
388            },
389            hint_string: GodotString::new(),
390        }
391    }
392}
393
394/// Array hints optionally with an element hint.
395#[derive(Debug, Default)]
396pub struct ArrayHint {
397    element_hint: Option<ExportInfo>,
398}
399
400impl ArrayHint {
401    /// Returns an `ArrayHint` without a element hint.
402    #[inline]
403    pub fn new() -> Self {
404        Self::default()
405    }
406
407    /// Returns an `ArrayHint` with an element hint for type `T`, but without a hint for
408    /// that type.
409    #[inline]
410    pub fn with_element<T: Export>() -> Self {
411        Self::with_maybe_element_hint::<T>(None)
412    }
413
414    /// Returns an `ArrayHint` with an element hint for type `T`, and a hint for that type.
415    #[inline]
416    pub fn with_element_hint<T: Export>(hint: T::Hint) -> Self {
417        Self::with_maybe_element_hint::<T>(Some(hint))
418    }
419
420    /// Returns an `ArrayHint` with an element hint for type `T`, and optionally a hint
421    /// for that type.
422    #[inline]
423    pub fn with_maybe_element_hint<T: Export>(hint: Option<T::Hint>) -> Self {
424        ArrayHint {
425            element_hint: Some(T::export_info(hint)),
426        }
427    }
428}
429
430impl ArrayHint {
431    #[inline]
432    pub fn export_info(self) -> ExportInfo {
433        if let Some(element_hint) = self.element_hint {
434            let hint_string = match (element_hint.variant_type, element_hint.hint_kind) {
435                // Special-cased because sub-hints seem to leave off the hint only if it's NONE,
436                // but Array will also do it on HINT_TYPE_STRING.
437                (
438                    VariantType::VariantArray,
439                    sys::godot_property_hint_GODOT_PROPERTY_HINT_TYPE_STRING,
440                ) => format!(
441                    "{}:{}",
442                    VariantType::VariantArray as u32,
443                    element_hint.hint_string
444                ),
445                (variant_type, sys::godot_property_hint_GODOT_PROPERTY_HINT_NONE) => {
446                    format!("{}:{}", variant_type as u32, element_hint.hint_string)
447                }
448                (variant_type, hint_type) => format!(
449                    "{}/{}:{}",
450                    variant_type as u32, hint_type, element_hint.hint_string
451                ),
452            }
453            .into();
454            ExportInfo {
455                variant_type: VariantType::VariantArray,
456                hint_kind: sys::godot_property_hint_GODOT_PROPERTY_HINT_TYPE_STRING,
457                hint_string,
458            }
459        } else {
460            ExportInfo {
461                variant_type: VariantType::VariantArray,
462                hint_kind: sys::godot_property_hint_GODOT_PROPERTY_HINT_NONE,
463                hint_string: GodotString::new(),
464            }
465        }
466    }
467}