tessera-components 0.0.0

Basic components for tessera-ui, using md3e design principles.
Documentation
//! Visual modifiers for opacity, clipping, backgrounds, and borders.
//!
//! ## Usage
//!
//! Add drawing effects like alpha, clipping, and shape-based backgrounds or
//! borders.

use tessera_ui::{
    Color, ComputedData, Constraint, DimensionValue, Dp, LayoutInput, LayoutOutput, LayoutSpec,
    MeasurementError, PxPosition, PxSize, RenderInput, tessera,
};

use crate::{
    pipelines::shape::command::ShapeCommand,
    shape_def::{ResolvedShape, Shape},
};

#[tessera]
pub(crate) fn modifier_alpha<F>(alpha: f32, child: F)
where
    F: FnOnce(),
{
    layout(AlphaLayout { alpha });

    child();
}

#[tessera]
pub(crate) fn modifier_clip_to_bounds<F>(child: F)
where
    F: FnOnce(),
{
    layout(ClipLayout);

    child();
}

fn shape_background_command(color: Color, shape: Shape, size: PxSize) -> ShapeCommand {
    match shape.resolve_for_size(size) {
        ResolvedShape::Rounded {
            corner_radii,
            corner_g2,
        } => ShapeCommand::Rect {
            color,
            corner_radii,
            corner_g2,
            shadow: None,
        },
        ResolvedShape::Ellipse => ShapeCommand::Ellipse {
            color,
            shadow: None,
        },
    }
}

fn shape_border_command(color: Color, width: Dp, shape: Shape, size: PxSize) -> ShapeCommand {
    let border_width = width.to_pixels_f32();
    match shape.resolve_for_size(size) {
        ResolvedShape::Rounded {
            corner_radii,
            corner_g2,
        } => ShapeCommand::OutlinedRect {
            color,
            corner_radii,
            corner_g2,
            shadow: None,
            border_width,
        },
        ResolvedShape::Ellipse => ShapeCommand::OutlinedEllipse {
            color,
            shadow: None,
            border_width,
        },
    }
}

#[tessera]
pub(crate) fn modifier_background<F>(color: Color, shape: Shape, child: F)
where
    F: FnOnce(),
{
    layout(BackgroundLayout { color, shape });

    child();
}

#[tessera]
fn modifier_border_overlay(width: Dp, color: Color, shape: Shape) {
    layout(BorderOverlayLayout {
        width,
        color,
        shape,
    });
}

#[tessera]
pub(crate) fn modifier_border<F>(width: Dp, color: Color, shape: Shape, child: F)
where
    F: FnOnce(),
{
    layout(BorderLayout);

    child();
    modifier_border_overlay(width, color, shape);
}

#[derive(Clone, Copy, PartialEq)]
struct AlphaLayout {
    alpha: f32,
}

impl LayoutSpec for AlphaLayout {
    fn measure(
        &self,
        input: &LayoutInput<'_>,
        output: &mut LayoutOutput<'_>,
    ) -> Result<ComputedData, MeasurementError> {
        let child_id = input
            .children_ids()
            .first()
            .copied()
            .expect("modifier_alpha expects exactly one child");
        let child_measurement = input.measure_child_in_parent_constraint(child_id)?;
        output.place_child(child_id, PxPosition::ZERO);

        Ok(child_measurement)
    }

    fn record(&self, input: &RenderInput<'_>) {
        let mut metadata = input.metadata_mut();
        metadata.opacity *= self.alpha;
    }
}

#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
struct ClipLayout;

impl LayoutSpec for ClipLayout {
    fn measure(
        &self,
        input: &LayoutInput<'_>,
        output: &mut LayoutOutput<'_>,
    ) -> Result<ComputedData, MeasurementError> {
        let child_id = input
            .children_ids()
            .first()
            .copied()
            .expect("modifier_clip_to_bounds expects exactly one child");
        let child_measurement = input.measure_child_in_parent_constraint(child_id)?;
        output.place_child(child_id, PxPosition::ZERO);
        Ok(child_measurement)
    }

    fn record(&self, input: &RenderInput<'_>) {
        input.metadata_mut().clips_children = true;
    }
}

#[derive(Clone, PartialEq)]
struct BackgroundLayout {
    color: Color,
    shape: Shape,
}

impl LayoutSpec for BackgroundLayout {
    fn measure(
        &self,
        input: &LayoutInput<'_>,
        output: &mut LayoutOutput<'_>,
    ) -> Result<ComputedData, MeasurementError> {
        let child_id = input
            .children_ids()
            .first()
            .copied()
            .expect("modifier_background expects exactly one child");
        let child_measurement = input.measure_child_in_parent_constraint(child_id)?;
        output.place_child(child_id, PxPosition::ZERO);
        Ok(child_measurement)
    }

    fn record(&self, input: &RenderInput<'_>) {
        let mut metadata = input.metadata_mut();
        let size = metadata
            .computed_data
            .expect("modifier_background must have computed size before record");
        metadata.push_draw_command(shape_background_command(
            self.color,
            self.shape,
            size.into(),
        ));
    }
}

#[derive(Clone, PartialEq)]
struct BorderOverlayLayout {
    width: Dp,
    color: Color,
    shape: Shape,
}

impl LayoutSpec for BorderOverlayLayout {
    fn measure(
        &self,
        input: &LayoutInput<'_>,
        _output: &mut LayoutOutput<'_>,
    ) -> Result<ComputedData, MeasurementError> {
        let size = ComputedData {
            width: input.parent_constraint().width().resolve(),
            height: input.parent_constraint().height().resolve(),
        };
        Ok(size)
    }

    fn record(&self, input: &RenderInput<'_>) {
        let mut metadata = input.metadata_mut();
        let size = metadata
            .computed_data
            .expect("modifier_border_overlay must have computed size before record");
        metadata.push_draw_command(shape_border_command(
            self.color,
            self.width,
            self.shape,
            size.into(),
        ));
    }
}

#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
struct BorderLayout;

impl LayoutSpec for BorderLayout {
    fn measure(
        &self,
        input: &LayoutInput<'_>,
        output: &mut LayoutOutput<'_>,
    ) -> Result<ComputedData, MeasurementError> {
        let content_id = input
            .children_ids()
            .first()
            .copied()
            .expect("modifier_border expects exactly two children");
        let overlay_id = input
            .children_ids()
            .get(1)
            .copied()
            .expect("modifier_border expects exactly two children");
        let child_measurement = input.measure_child_in_parent_constraint(content_id)?;
        output.place_child(content_id, PxPosition::ZERO);
        let overlay_constraint = Constraint::new(
            DimensionValue::Fixed(child_measurement.width),
            DimensionValue::Fixed(child_measurement.height),
        );
        let _ = input.measure_child(overlay_id, &overlay_constraint)?;
        output.place_child(overlay_id, PxPosition::ZERO);
        Ok(child_measurement)
    }
}