Files
galaxy/crates/ui_components/src/lib.rs
T

291 lines
9.5 KiB
Rust

pub mod button;
pub mod dialog;
pub mod keyboard_shortcut;
pub mod lightbox;
pub mod switch;
pub mod tooltip;
pub use keyboard_shortcut::KeyboardShortcut;
use galaxy_core::ui::appearance::Appearance;
use galaxyui::Element;
/// A reusable UI component that can be rendered with configurable parameters.
///
/// Components are designed to be long-lived and stored as fields in views rather than
/// created on every render. This is critical for components that maintain internal state
/// (such as mouse hover state via `MouseStateHandle`) - creating them fresh each render
/// will cause intra-frame state to be incorrect.
///
/// # Design Pattern
///
/// The component pattern separates:
/// - **Component struct**: Holds persistent state (mouse handles, tooltips, etc.)
/// - **Params struct**: Contains both required and optional rendering parameters
/// - **Options struct**: Contains only optional parameters with appearance-based defaults
///
/// This separation allows users to specify only what's necessary while getting sensible
/// defaults for everything else.
///
/// # Example
///
/// ```rust
/// use ui_components::{Component, Options, button};
/// use galaxy_core::ui::appearance::Appearance;
/// use galaxyui::prelude::*;
///
/// // Store component as a field in your view.
/// struct MyView {
/// my_button: button::Button,
/// }
///
/// impl MyView {
/// fn render_button(&self, appearance: &Appearance) -> Box<dyn galaxyui::Element> {
/// self.my_button.render(
/// appearance,
/// button::Params {
/// // Required: specify what the button displays.
/// content: button::Content::Label("Click me".into()),
/// theme: &button::themes::Primary,
/// // Optional: use defaults and override as needed.
/// options: button::Options {
/// disabled: false,
/// ..Options::default(appearance)
/// },
/// },
/// )
/// }
/// }
/// ```
///
/// # Implementing a New Component
///
/// ```rust
/// use ui_components::Component;
/// use galaxy_core::ui::appearance::Appearance;
/// use galaxyui::prelude::*;
///
/// // 1. Define the component struct with any persistent state.
/// #[derive(Default)]
/// pub struct MyComponent {
/// mouse_state: MouseStateHandle,
/// }
///
/// // 2. Define the params struct with required fields.
/// pub struct Params {
/// pub content: String, // Required parameter.
/// pub options: Options, // Optional parameters.
/// }
///
/// // 3. Define the options struct with optional fields.
/// pub struct Options {
/// pub disabled: bool,
/// pub size: f32,
/// }
///
/// // 4. Implement the traits.
/// impl ui_components::Params for Params {
/// type Options<'a> = Options;
/// }
///
/// impl ui_components::Options for Options {
/// fn default(appearance: &Appearance) -> Self {
/// Self {
/// disabled: false,
/// size: appearance.ui_font_size(),
/// }
/// }
/// }
///
/// impl Component for MyComponent {
/// type Params<'a> = Params;
///
/// fn render<'a>(&self, appearance: &Appearance, params: Self::Params<'a>) -> Box<dyn Element> {
/// // Render implementation.
/// # Empty::new().finish()
/// }
/// }
/// ```
pub trait Component: Default {
/// The set of parameters that control rendering.
///
/// This type should include both required parameters (fields that must always be
/// specified, like button content) and an `options` field of type `Self::Params::Options`
/// containing optional parameters.
type Params<'a>: Params;
/// Renders the component given the current application appearance and rendering parameters.
///
/// This method is called during the render phase to produce the element tree for this
/// component. The component can use its internal state (mouse handles, etc.) along with
/// the provided parameters to determine how to render.
fn render<'a>(&self, appearance: &Appearance, params: Self::Params<'a>) -> Box<dyn Element>;
}
/// The set of parameters that control rendering of a component.
///
/// This trait links a params struct to its corresponding options struct. The params struct
/// should contain:
/// - Required parameters as direct fields (e.g., button content, switch state)
/// - An `options: Self::Options` field for optional parameters
///
/// The lifetime parameter `'a` allows params and options to contain borrowed data, though
/// specific implementations may not use it.
///
/// # Example
///
/// ```rust
/// use ui_components::{Params, Options};
/// use galaxy_core::ui::appearance::Appearance;
///
/// pub struct MyParams {
/// pub content: String, // Required.
/// pub options: MyOptions, // Optional.
/// }
///
/// pub struct MyOptions;
///
/// impl Options for MyOptions {
/// fn default(_: &Appearance) -> Self { Self }
/// }
///
/// impl Params for MyParams {
/// type Options<'a> = MyOptions;
/// }
/// ```
pub trait Params {
/// The optional subset of parameters for this component.
///
/// This type should contain only optional configuration that has sensible defaults.
type Options<'a>: Options;
}
/// The optional subset of parameters that control rendering of a component.
///
/// Options provide appearance-based defaults for optional configuration, allowing users to
/// override only what they need. This trait requires implementing a `default` method that
/// computes appropriate defaults based on the current appearance (theme, font sizes, etc.).
///
/// # Design Philosophy
///
/// The distinction between required params and optional options allows for:
/// - **Compile-time safety**: Required parameters must be provided.
/// - **Convenience**: Optional parameters have sensible defaults.
/// - **Flexibility**: Defaults adapt to the current appearance/theme.
///
/// # Example
///
/// ```rust
/// use ui_components::{Options, MouseEventHandler};
/// use galaxy_core::ui::appearance::Appearance;
///
/// pub struct MyOptions {
/// pub disabled: bool,
/// pub font_size: f32,
/// pub on_click: Option<MouseEventHandler>,
/// }
///
/// impl Options for MyOptions {
/// fn default(appearance: &Appearance) -> Self {
/// Self {
/// disabled: false,
/// font_size: appearance.ui_font_size(),
/// on_click: None,
/// }
/// }
/// }
///
/// // Users can then use the struct update syntax to override specific options.
/// # fn example(appearance: &Appearance) {
/// let options = MyOptions {
/// disabled: true,
/// ..Options::default(&appearance)
/// };
/// # }
/// ```
pub trait Options {
/// Computes default values for optional parameters based on the current appearance.
///
/// This method should return sensible defaults that work well with the current theme,
/// font sizes, and other appearance settings. The appearance parameter allows defaults
/// to adapt to different visual contexts (light/dark theme, different font scales, etc.).
fn default(appearance: &Appearance) -> Self;
}
/// A trait representing anything that can be rendered to an element tree given an appearance.
///
/// This trait provides a common interface for both UI components and custom rendering closures.
/// It's primarily used to allow components to accept flexible rendering parameters - either
/// sub-components or inline rendering logic.
///
/// # Implementations
///
/// There are two main implementations:
///
/// 1. **Component tuples**: `(&'a T, T::Params<'a>)` where `T: Component`
/// - Allows passing a component reference with its params.
///
/// 2. **Closures**: Any `FnOnce(&Appearance) -> Box<dyn Element>`
/// - Allows inline rendering logic.
///
/// # Example
///
/// ```rust
/// use ui_components::{Renderable, Options};
/// use galaxy_core::ui::appearance::Appearance;
/// use galaxyui::prelude::*;
///
/// pub struct SwitchOptions<'a> {
/// pub disabled: bool,
/// // Accept either a component or a rendering closure for the label.
/// pub label: Option<Box<dyn Renderable<'a>>>,
/// }
///
/// impl ui_components::Options for SwitchOptions<'_> {
/// fn default(_: &Appearance) -> Self {
/// Self { disabled: false, label: None }
/// }
/// }
///
/// // Usage with a closure.
/// # fn example(appearance: &Appearance) {
/// let options = SwitchOptions {
/// label: Some(Box::new(|appearance: &Appearance| {
/// Text::new("My Label", appearance.ui_font_family(), appearance.ui_font_size())
/// .finish()
/// })),
/// ..Options::default(&appearance)
/// };
/// # }
/// ```
pub trait Renderable<'a> {
/// Renders this object into an element tree.
fn render(self: Box<Self>, appearance: &Appearance) -> Box<dyn Element>;
}
/// An implementation of [`Renderable`] for any [`UiComponent`] and its parameters.
impl<'a, T: Component> Renderable<'a> for (&'a T, T::Params<'a>) {
fn render(self: Box<Self>, appearance: &Appearance) -> Box<dyn Element> {
self.0.render(appearance, self.1)
}
}
/// An implementation of [`Renderable`] for any [`FnOnce`] that returns a [`Box<dyn Element>`].
impl<'a, T> Renderable<'a> for T
where
T: FnOnce(&Appearance) -> Box<dyn Element>,
{
fn render(self: Box<Self>, appearance: &Appearance) -> Box<dyn Element> {
self(appearance)
}
}
/// A function that handles mouse events.
pub type MouseEventHandler = Box<
dyn FnMut(
&mut galaxyui::EventContext,
&galaxyui::AppContext,
pathfinder_geometry::vector::Vector2F,
),
>;