ribir_core 0.4.0-alpha.63

A non-intrusive declarative GUI framework, to build modern native/wasm cross-platform applications.
Documentation
#[doc(hidden)]
pub use std::{
  any::{Any, TypeId},
  marker::PhantomData,
  ops::Deref,
};
use std::{cell::RefCell, convert::Infallible};

use ribir_algo::Rc;
use rxrust::observable::boxed::LocalBoxedObservableClone;
use widget_id::RenderQueryable;

pub(crate) use crate::widget_tree::*;
use crate::{context::*, prelude::*};

/// Defines how a type composes its user interface representation from its state
///
/// Implement this trait for types that need to create widget hierarchies based
/// on their internal state.
pub trait Compose {
  fn compose(state: impl StateWriter<Value = Self>) -> Widget<'static>
  where
    Self: Sized;
}

/// Core rendering interface for visual widgets
///
/// Implement this trait for widgets that need custom layout calculation,
/// painting logic, or hit testing behavior. The framework calls these methods
/// during different phases of the rendering pipeline.
pub trait Render: 'static {
  /// Measure the widget and its children to determine its own size.
  ///
  /// # Parameters
  /// - `clamp`: Size constraints from parent
  /// - `ctx`: Layout context and child management
  ///
  /// # Implementation Guide
  /// 1. **Constraint Handling**:
  ///    - Respect clamp.min/max boundaries
  /// 2. **Child Measurement**:
  ///    - Call `ctx.layout_child()` for each child to get their sizes
  /// 3. **Size Safety**:
  ///    - Never return infinite/NaN sizes
  ///    - Fall back to clamp limits for invalid calculations
  ///
  /// **Note**: Do NOT set child positions in this method. Use `place_children`
  /// for that.
  fn measure(&self, clamp: BoxClamp, ctx: &mut MeasureCtx) -> Size;

  /// Arrange children within the widget's bounds.
  ///
  /// # Parameters
  /// - `size`: The widget's own size (as returned by `measure`)
  /// - `ctx`: PlaceCtx for positioning children
  ///
  /// # Implementation Guide
  /// 1. Calculate child positions based on the widget's size and child sizes
  /// 2. Call `ctx.update_position(child, ...)` to set each child's position
  ///
  /// Default implementation does nothing (children will be in the top-left of
  /// the widget).
  fn place_children(&self, _size: Size, ctx: &mut PlaceCtx) {
    let (ctx, children) = ctx.split_children();
    for child in children {
      ctx.update_position(child, Point::zero());
    }
  }

  /// Custom painting implementation
  ///
  /// Use `PaintingCtx::painter` for drawing operations. Child widgets are
  /// painted automatically by the framework after parent painting completes.
  fn paint(&self, _: &mut PaintingCtx) {}

  /// Child size dependency flag
  ///
  /// Return `false` for fixed-size containers to optimize layout passes.
  /// Default implementation assumes child-dependent sizing.
  fn size_affected_by_child(&self) -> bool { true }

  /// Hit testing implementation
  ///
  /// # Parameters
  /// - `ctx`: Hit test context and helpers
  /// - `pos`: Test position in local coordinates
  ///
  /// Return `HitTest` with:
  /// - `hit`: Whether position intersects widget
  /// - `can_hit_child`: Whether to test child widgets
  fn hit_test(&self, ctx: &mut HitTestCtx, pos: Point) -> HitTest {
    let hit = ctx.box_hit_test(pos);
    HitTest { hit, can_hit_child: hit || self.size_affected_by_child() }
  }

  /// Dirty state propagation control
  ///
  /// Return `DirtyPhase::Layout` (default) to mark as dirty on modifications.
  /// Use `DirtyPhase::Visual` for paint-only updates.
  fn dirty_phase(&self) -> DirtyPhase { DirtyPhase::Layout }

  /// Custom coordinate transformation
  ///
  /// Return `Some(Transform)` to apply local-to-parent transformation.
  /// Used for widgets with custom positioning or transformation effects.
  fn get_transform(&self) -> Option<Transform> { None }

  /// Adjust the position assigned by the parent's `place_children` method.
  ///
  /// Override this to apply custom position adjustments (e.g., anchor-based
  /// positioning) after the parent has placed this widget. In most cases, the
  /// position should be determined by the parent's layout logic, and you should
  /// not modify it.
  fn adjust_position(&self, pos: Point, _ctx: &mut PlaceCtx) -> Point { pos }

  /// Returns the debug name of this widget for debugging tools.
  ///
  /// Default implementation returns the type name.
  #[cfg(feature = "debug")]
  fn debug_name(&self) -> std::borrow::Cow<'static, str> {
    use crate::debug_tool::resolve_debug_name;
    std::borrow::Cow::Borrowed(
      resolve_debug_name::<Self>().unwrap_or_else(|| std::any::type_name::<Self>()),
    )
  }

  /// Returns debug properties as a JSON value for debugging tools.
  ///
  /// Override this to expose widget-specific state (e.g., text content,
  /// colors). Default implementation returns `null`.
  #[cfg(feature = "debug")]
  fn debug_properties(&self) -> serde_json::Value { serde_json::Value::Null }
}

/// Result of a hit testing operation
///
/// Contains both the hit status and child hit testing policy:
/// - `hit`: True if the widget itself was hit
/// - `can_hit_child`: Whether to continue testing child widgets
pub struct HitTest {
  pub hit: bool,
  pub can_hit_child: bool,
}

/// Primary widget handle type
///
/// Contains either static content or dynamic generator function.
/// All widget composition operations eventually produce this type.
pub struct Widget<'w>(InnerWidget<'w>);

/// Internal widget representation
pub(crate) struct InnerWidget<'w>(Box<dyn FnOnce(&mut BuildCtx) -> WidgetId + 'w>);

/// Conversion interface for widget-like types
///
/// Automatically implemented for all types that can be converted to [`Widget`]
/// through the [`RInto`] trait system.
pub trait IntoWidget<'a, K> {
  fn into_widget(self) -> Widget<'a>;
}

/// Reusable widget generator
///
/// Contains a boxed closure that can produce new widget instances on demand.
#[derive(Clone)]
pub struct GenWidget(InnerGenWidget);
type InnerGenWidget = Rc<RefCell<Box<dyn FnMut() -> Widget<'static>>>>;

/// Single-use widget generator
///
/// Wraps a closure that produces a widget when called.
pub struct FnWidget<W, F: FnOnce() -> W>(pub(crate) F);
pub type BoxFnWidget<'w> = Box<dyn FnOnce() -> Widget<'w> + 'w>;

impl<W, F> FnWidget<W, F>
where
  F: FnOnce() -> W,
{
  pub fn new<'w, K>(f: F) -> Self
  where
    W: IntoWidget<'w, K>,
  {
    Self(f)
  }

  pub fn into_inner(self) -> F { self.0 }

  pub fn call(self) -> W { (self.0)() }

  pub fn boxed<'w, K>(self) -> BoxFnWidget<'w>
  where
    W: IntoWidget<'w, K> + 'w,
    F: 'w,
  {
    Box::new(move || self.call().into_widget())
  }
}

impl GenWidget {
  pub fn new<W, K>(mut f: impl FnMut() -> W + 'static) -> Self
  where
    W: IntoWidget<'static, K>,
  {
    Self(Rc::new(RefCell::new(Box::new(move || f().into_widget()))))
  }

  pub fn from_fn_widget<F, W, K>(f: FnWidget<W, F>) -> Self
  where
    F: FnMut() -> W + 'static,
    W: IntoWidget<'static, K>,
  {
    Self::new(f.into_inner())
  }

  pub fn gen_widget(&self) -> Widget<'static> { self.0.borrow_mut()() }
}

impl<W: ComposeChild<'static, Child = Option<C>>, C> Compose for W {
  fn compose(this: impl StateWriter<Value = Self>) -> Widget<'static> {
    ComposeChild::compose_child(this, None).attach_debug_name::<W>()
  }
}

impl<'w> Widget<'w> {
  /// Register build completion callback
  ///
  /// The provided closure receives the final [`WidgetId`] after this widget
  /// has been built.
  pub fn on_build(self, f: impl FnOnce(WidgetId) + 'w) -> Self {
    Widget::from_fn(move |ctx| {
      let id = self.call(ctx);
      f(id);
      id
    })
  }

  /// Establish reactive dirtiness tracking
  pub fn dirty_on(
    self, upstream: LocalBoxedObservableClone<'static, ModifyInfo, Infallible>, dirty: DirtyPhase,
  ) -> Self {
    let track = TrackWidgetId::default();
    let id = track.track_id();

    let tree = BuildCtx::get_mut().tree_mut();
    let marker = tree.dirty_marker();
    let h = upstream
      .filter(|b| b.contains(ModifyEffect::FRAMEWORK))
      .subscribe(move |_| {
        if let Some(id) = id.get() {
          marker.mark(id, dirty);
        }
      })
      .unsubscribe_when_dropped();

    track
      .with_child(self)
      .into_widget()
      .attach_anonymous_data(h)
  }

  /// Attach anonymous data to a widget and user can't query it.
  pub fn attach_anonymous_data(self, data: impl Any) -> Self {
    self.on_build(|id| id.attach_anonymous_data(data, BuildCtx::get_mut().tree_mut()))
  }

  pub fn attach_data(self, data: Box<dyn Query>) -> Self {
    self.on_build(|id| id.attach_data(data, BuildCtx::get_mut().tree_mut()))
  }

  #[cfg(feature = "debug")]
  pub fn attach_debug_name<T: ?Sized>(self) -> Self {
    if let Some(name) = crate::debug_tool::resolve_debug_name::<T>() {
      self.attach_debug_name_value(name)
    } else {
      self
    }
  }

  #[cfg(not(feature = "debug"))]
  #[inline]
  pub fn attach_debug_name<T: ?Sized>(self) -> Self { self }

  #[cfg(feature = "debug")]
  pub fn attach_debug_name_value(self, name: impl Into<CowArc<str>>) -> Self {
    let name = name.into();
    if name.trim().is_empty() {
      self
    } else {
      self.attach_data(Box::new(Queryable(crate::debug_tool::OriginWidgetName(name))))
    }
  }

  /// Attach a state to a widget and try to unwrap it before attaching.
  ///
  /// User can query the state or its value type.
  pub fn try_unwrap_state_and_attach<D: Any>(
    self, data: impl StateWriter<Value = D> + 'static,
  ) -> Self {
    let data: Box<dyn Query> = match data.try_into_value() {
      Ok(data) => Box::new(Queryable(data)),
      Err(data) => Box::new(data),
    };
    self.attach_data(data)
  }

  pub(crate) fn from_render(r: Box<dyn RenderQueryable>) -> Widget<'static> {
    Widget::from_fn(|_| BuildCtx::get_mut().tree_mut().alloc_node(r))
  }

  /// Convert an ID back to a widget.
  ///
  /// # Note
  ///
  /// It's important to remember that we construct the tree lazily. In most
  /// cases, you should avoid using this method to create a widget unless you
  /// are certain that the entire logic is suitable for creating this widget
  /// from an ID.
  pub(crate) fn from_id(id: WidgetId) -> Widget<'static> { Widget::from_fn(move |_| id) }

  pub(crate) fn new(parent: Widget<'w>, children: Vec<Widget<'w>>) -> Widget<'w> {
    Widget::from_fn(move |ctx| ctx.build_parent(parent, children))
  }

  pub(crate) fn from_fn(f: impl FnOnce(&mut BuildCtx) -> WidgetId + 'w) -> Widget<'w> {
    Widget(InnerWidget(Box::new(f)))
  }

  pub(crate) fn call(self, ctx: &mut BuildCtx) -> WidgetId { (self.0.0)(ctx) }
}

impl From<GenWidget> for Widget<'static> {
  fn from(widget: GenWidget) -> Self { FnWidget::new(move || widget.gen_widget()).into_widget() }
}

// ----- Into Widget --------------

impl<'w, W, K> IntoWidget<'w, K> for W
where
  W: RInto<Widget<'w>, K>,
{
  fn into_widget(self) -> Widget<'w> { self.r_into() }
}