diff --git a/godot-core/src/builtin/string/gstring.rs b/godot-core/src/builtin/string/gstring.rs index 96fc89cd2..424be89fd 100644 --- a/godot-core/src/builtin/string/gstring.rs +++ b/godot-core/src/builtin/string/gstring.rs @@ -13,7 +13,7 @@ use godot_ffi as sys; use sys::types::OpaqueString; use sys::{ffi_methods, interface_fn, GodotFfi}; -use crate::builtin::string::Encoding; +use crate::builtin::string::{pad_if_needed, Encoding}; use crate::builtin::{inner, NodePath, StringName, Variant}; use crate::meta::error::StringError; use crate::meta::AsArg; @@ -298,11 +298,13 @@ impl_shared_string_api! { impl fmt::Display for GString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for ch in self.chars() { - f.write_char(*ch)?; - } + pad_if_needed(f, |f| { + for ch in self.chars() { + f.write_char(*ch)?; + } - Ok(()) + Ok(()) + }) } } diff --git a/godot-core/src/builtin/string/mod.rs b/godot-core/src/builtin/string/mod.rs index 644487b80..2412e29ef 100644 --- a/godot-core/src/builtin/string/mod.rs +++ b/godot-core/src/builtin/string/mod.rs @@ -155,3 +155,48 @@ fn found_to_option(index: i64) -> Option { Some(index_usize) } } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Padding, alignment and precision support + +// Used by sub-modules of this module. +use standard_fmt::pad_if_needed; + +mod standard_fmt { + use std::fmt; + use std::fmt::Write; + + pub fn pad_if_needed(f: &mut fmt::Formatter<'_>, display_impl: F) -> fmt::Result + where + F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result, + { + let needs_format = f.width().is_some() || f.precision().is_some() || f.align().is_some(); + + // Early exit if no custom formatting is needed. + if !needs_format { + return display_impl(f); + } + + let ic = FmtInterceptor { display_impl }; + + let mut local_str = String::new(); + write!(&mut local_str, "{ic}")?; + f.pad(&local_str) + } + + struct FmtInterceptor + where + F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result, + { + display_impl: F, + } + + impl fmt::Display for FmtInterceptor + where + F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result, + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (self.display_impl)(f) + } + } +} diff --git a/godot-core/src/global/print.rs b/godot-core/src/global/print.rs index 4ddb36e76..51dd7b04b 100644 --- a/godot-core/src/global/print.rs +++ b/godot-core/src/global/print.rs @@ -50,6 +50,11 @@ macro_rules! inner_godot_msg { /// Pushes a warning message to Godot's built-in debugger and to the OS terminal. /// +/// # See also +/// [`godot_print!`](macro.godot_print.html) and [`godot_error!`](macro.godot_error.html). +/// +/// Related to the utility function [`global::push_warning()`](crate::global::push_warning). +/// /// _Godot equivalent: [`@GlobalScope.push_warning()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-push-warning)_. #[macro_export] macro_rules! godot_warn { @@ -60,6 +65,12 @@ macro_rules! godot_warn { /// Pushes an error message to Godot's built-in debugger and to the OS terminal. /// +/// # See also +/// [`godot_print!`](macro.godot_print.html) and [`godot_warn!`](macro.godot_warn.html). +/// For script errors (less relevant in Rust), use [`godot_script_error!`](macro.godot_script_error.html). +/// +/// Related to the utility function [`global::push_error()`][crate::global::push_error]. +/// /// _Godot equivalent: [`@GlobalScope.push_error()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-push-error)_. #[macro_export] macro_rules! godot_error { @@ -69,6 +80,13 @@ macro_rules! godot_error { } /// Logs a script error to Godot's built-in debugger and to the OS terminal. +/// +/// This is rarely needed in Rust; script errors are typically emitted by the GDScript parser. +/// +/// # See also +/// [`godot_error!`](macro.godot_error.html) for a general error message. +/// +/// #[macro_export] macro_rules! godot_script_error { ($fmt:literal $(, $args:expr)* $(,)?) => { @@ -78,6 +96,22 @@ macro_rules! godot_script_error { /// Prints to the Godot console. /// +/// Automatically appends a newline character at the end of the message. +/// +/// Used exactly like standard [`println!`]: +/// ```no_run +/// use godot::global::godot_print; +/// +/// let version = 4; +/// godot_print!("Hello, Godot {version}!"); +/// ``` +/// +/// # See also +/// [`godot_print_rich!`](macro.godot_print_rich.html) for a slower alternative that supports BBCode, color and URL tags. +/// To print Godot errors and warnings, use [`godot_error!`](macro.godot_error.html) and [`godot_warn!`](macro.godot_warn.html), respectively. +/// +/// This uses the underlying [`global::print()`][crate::global::print] function, which takes a variable-length slice of variants. +/// /// _Godot equivalent: [`@GlobalScope.print()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-print)_. #[macro_export] macro_rules! godot_print { @@ -92,7 +126,7 @@ macro_rules! godot_print { /// Prints to the Godot console. Supports BBCode, color and URL tags. /// -/// Slower than [`godot_print!`]. +/// Slower than [`godot_print!`](macro.godot_print_rich.html). /// /// _Godot equivalent: [`@GlobalScope.print_rich()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-print-rich)_. #[macro_export] diff --git a/itest/rust/src/builtin_tests/string/gstring_test.rs b/itest/rust/src/builtin_tests/string/gstring_test.rs index 33622556b..801f04fe5 100644 --- a/itest/rust/src/builtin_tests/string/gstring_test.rs +++ b/itest/rust/src/builtin_tests/string/gstring_test.rs @@ -279,6 +279,14 @@ crate::generate_string_bytes_and_cstr_tests!( ] ); +crate::generate_string_standard_fmt_tests!( + builtin: GString, + tests: [ + gstring_display, + gstring_standard_pad, + ] +); + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Helpers diff --git a/itest/rust/src/builtin_tests/string/node_path_test.rs b/itest/rust/src/builtin_tests/string/node_path_test.rs index 5a0923e4d..12e565e64 100644 --- a/itest/rust/src/builtin_tests/string/node_path_test.rs +++ b/itest/rust/src/builtin_tests/string/node_path_test.rs @@ -31,6 +31,7 @@ fn node_path_conversion() { assert_eq!(string, back); } + #[itest] fn node_path_equality() { let string = NodePath::from("some string"); @@ -126,3 +127,11 @@ fn node_path_get_subname() { assert_eq!(path.get_subname(2), "".into()); }) } + +crate::generate_string_standard_fmt_tests!( + builtin: NodePath, + tests: [ + node_path_display, + node_path_standard_pad, + ] +); diff --git a/itest/rust/src/builtin_tests/string/string_name_test.rs b/itest/rust/src/builtin_tests/string/string_name_test.rs index 1023eef6e..14b690ac6 100644 --- a/itest/rust/src/builtin_tests/string/string_name_test.rs +++ b/itest/rust/src/builtin_tests/string/string_name_test.rs @@ -175,3 +175,11 @@ crate::generate_string_bytes_and_cstr_tests!( string_name_from_cstr_utf8, ] ); + +crate::generate_string_standard_fmt_tests!( + builtin: StringName, + tests: [ + string_name_display, + string_name_standard_pad, + ] +); diff --git a/itest/rust/src/builtin_tests/string/string_test_macros.rs b/itest/rust/src/builtin_tests/string/string_test_macros.rs index 7773cf21e..38ba5412f 100644 --- a/itest/rust/src/builtin_tests/string/string_test_macros.rs +++ b/itest/rust/src/builtin_tests/string/string_test_macros.rs @@ -162,3 +162,35 @@ macro_rules! generate_string_bytes_and_cstr_tests { } }; } + +// Tests padding with the standard formatter. +#[macro_export] +macro_rules! generate_string_standard_fmt_tests { + ( + builtin: $T:ty, + tests: [ + $display:ident, + $standard_pad:ident, + ] + ) => { + #[itest] + fn $display() { + let s = <$T>::from("abcd"); + + assert_eq!(format!("{s}"), "abcd"); + } + + #[itest] + fn $standard_pad() { + let s = <$T>::from("abcd"); + + // Padding with spaces + alignment. + assert_eq!(format!("{s:<6}"), "abcd "); + assert_eq!(format!("{s:>6}"), " abcd"); + + // Precision. + assert_eq!(format!("{s:.2}"), "ab"); + assert_eq!(format!("{s:.3}"), "abc"); + } + }; +}