Refactor Spacing into DynamicSpacing using proc macro (#20504)
Density tracking issue: #18078 This PR refactors our spacing system to use a more flexible and maintainable approach. We've replaced the static `Spacing` enum with a dynamically generated `DynamicSpacing` enum using a proc macro. Enum variants now use a `BaseXX` format, where XX = the pixel value @ default rem size and the default UI density. For example: `CustomSpacing::Base16` would return 16px at the default UI scale & density. I'd love to find another name other than `Base` that is clear (to avoid base_10, etc confusion), let me know if you have any ideas! Changes: - Introduced a new `derive_dynamic_spacing` proc macro to generate the `DynamicSpacing` enum - Updated all usages of `Spacing` to use the new `DynamicSpacing` - Removed the `custom_spacing` function, mapping previous usages to appropriate `DynamicSpacing` variants - Improved documentation and type safety for spacing values New usage example: ```rust .child( div() .flex() .flex_none() .m(DynamicSpacing::Base04.px(cx)) .size(DynamicSpacing::Base16.rems(cx)) .children(icon), ) ``` vs old usage example: ``` .child( div() .flex() .flex_none() .m(Spacing::Small.px(cx)) .size(custom_spacing(px(16.))) .children(icon), ) ``` Release Notes: - N/A
This commit is contained in:
parent
93ab6ad922
commit
94d8ead270
29 changed files with 292 additions and 191 deletions
163
crates/ui_macros/src/dynamic_spacing.rs
Normal file
163
crates/ui_macros/src/dynamic_spacing.rs
Normal file
|
@ -0,0 +1,163 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated, LitInt, Token,
|
||||
};
|
||||
|
||||
struct DynamicSpacingInput {
|
||||
values: Punctuated<DynamicSpacingValue, Token![,]>,
|
||||
}
|
||||
|
||||
// The input for the derive macro is a list of values.
|
||||
//
|
||||
// When a single value is provided, the standard spacing formula is
|
||||
// used to derive the of spacing values.
|
||||
//
|
||||
// When a tuple of three values is provided, the values are used as
|
||||
// the spacing values directly.
|
||||
enum DynamicSpacingValue {
|
||||
Single(LitInt),
|
||||
Tuple(LitInt, LitInt, LitInt),
|
||||
}
|
||||
|
||||
impl Parse for DynamicSpacingInput {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(DynamicSpacingInput {
|
||||
values: input.parse_terminated(DynamicSpacingValue::parse)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for DynamicSpacingValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(syn::token::Paren) {
|
||||
let content;
|
||||
syn::parenthesized!(content in input);
|
||||
let a: LitInt = content.parse()?;
|
||||
content.parse::<Token![,]>()?;
|
||||
let b: LitInt = content.parse()?;
|
||||
content.parse::<Token![,]>()?;
|
||||
let c: LitInt = content.parse()?;
|
||||
Ok(DynamicSpacingValue::Tuple(a, b, c))
|
||||
} else {
|
||||
Ok(DynamicSpacingValue::Single(input.parse()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Derives the spacing method for the `DynamicSpacing` enum.
|
||||
pub fn derive_spacing(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DynamicSpacingInput);
|
||||
|
||||
let spacing_ratios: Vec<_> = input
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let variant = match v {
|
||||
DynamicSpacingValue::Single(n) => {
|
||||
format_ident!("Base{:02}", n.base10_parse::<u32>().unwrap())
|
||||
}
|
||||
DynamicSpacingValue::Tuple(_, b, _) => {
|
||||
format_ident!("Base{:02}", b.base10_parse::<u32>().unwrap())
|
||||
}
|
||||
};
|
||||
match v {
|
||||
DynamicSpacingValue::Single(n) => {
|
||||
let n = n.base10_parse::<f32>().unwrap();
|
||||
quote! {
|
||||
DynamicSpacing::#variant => match ThemeSettings::get_global(cx).ui_density {
|
||||
UiDensity::Compact => (#n - 4.0).max(0.0) / BASE_REM_SIZE_IN_PX,
|
||||
UiDensity::Default => #n / BASE_REM_SIZE_IN_PX,
|
||||
UiDensity::Comfortable => (#n + 4.0) / BASE_REM_SIZE_IN_PX,
|
||||
}
|
||||
}
|
||||
}
|
||||
DynamicSpacingValue::Tuple(a, b, c) => {
|
||||
let a = a.base10_parse::<f32>().unwrap();
|
||||
let b = b.base10_parse::<f32>().unwrap();
|
||||
let c = c.base10_parse::<f32>().unwrap();
|
||||
quote! {
|
||||
DynamicSpacing::#variant => match ThemeSettings::get_global(cx).ui_density {
|
||||
UiDensity::Compact => #a / BASE_REM_SIZE_IN_PX,
|
||||
UiDensity::Default => #b / BASE_REM_SIZE_IN_PX,
|
||||
UiDensity::Comfortable => #c / BASE_REM_SIZE_IN_PX,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let variant_docs: Vec<_> = input
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let variant = match v {
|
||||
DynamicSpacingValue::Single(n) => format_ident!("Base{:02}", n.base10_parse::<u32>().unwrap()),
|
||||
DynamicSpacingValue::Tuple(_, b, _) => format_ident!("Base{:02}", b.base10_parse::<u32>().unwrap()),
|
||||
};
|
||||
match v {
|
||||
DynamicSpacingValue::Single(n) => {
|
||||
// When a single value is passed in, derive the compact and comfortable values.
|
||||
let n = n.base10_parse::<f32>().unwrap();
|
||||
let compact = (n - 4.0).max(0.0);
|
||||
let comfortable = n + 4.0;
|
||||
quote! {
|
||||
#[doc = concat!("@16px/rem: `", stringify!(#compact), "px`|`", stringify!(#n), "px`|`", stringify!(#comfortable), "px` - Scales with the user's rem size.")]
|
||||
#variant,
|
||||
}
|
||||
}
|
||||
DynamicSpacingValue::Tuple(a, b, c) => {
|
||||
let a = a.base10_parse::<f32>().unwrap();
|
||||
let b = b.base10_parse::<f32>().unwrap();
|
||||
let c = c.base10_parse::<f32>().unwrap();
|
||||
quote! {
|
||||
#[doc = concat!("@16px/rem: `", stringify!(#a), "px`|`", stringify!(#b), "px`|`", stringify!(#c), "px` - Scales with the user's rem size.")]
|
||||
#variant,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expanded = quote! {
|
||||
/// A dynamic spacing system that adjusts spacing based on
|
||||
/// [UiDensity].
|
||||
///
|
||||
/// The number following "Base" refers to the base pixel size
|
||||
/// at the default rem size and spacing settings.
|
||||
///
|
||||
/// When possible, [DynamicSpacing] should be used over manual
|
||||
/// or built-in spacing values in places dynamic spacing is needed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DynamicSpacing {
|
||||
#(
|
||||
#[doc = stringify!(#variant_docs)]
|
||||
#variant_docs
|
||||
)*
|
||||
}
|
||||
|
||||
impl DynamicSpacing {
|
||||
/// Returns the spacing ratio, should only be used internally.
|
||||
fn spacing_ratio(&self, cx: &WindowContext) -> f32 {
|
||||
const BASE_REM_SIZE_IN_PX: f32 = 16.0;
|
||||
match self {
|
||||
#(#spacing_ratios,)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the spacing value in rems.
|
||||
pub fn rems(&self, cx: &WindowContext) -> Rems {
|
||||
rems(self.spacing_ratio(cx))
|
||||
}
|
||||
|
||||
/// Returns the spacing value in pixels.
|
||||
pub fn px(&self, cx: &WindowContext) -> Pixels {
|
||||
let ui_font_size_f32: f32 = ThemeSettings::get_global(cx).ui_font_size.into();
|
||||
px(ui_font_size_f32 * self.spacing_ratio(cx))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod derive_path_str;
|
||||
mod dynamic_spacing;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
|
@ -51,3 +52,9 @@ pub fn path_str(_args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
// This attribute doesn't modify the input, it's just a marker
|
||||
input
|
||||
}
|
||||
|
||||
/// Generates the DynamicSpacing enum used for density-aware spacing in the UI.
|
||||
#[proc_macro]
|
||||
pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
|
||||
dynamic_spacing::derive_spacing(input)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue