use std::fmt::Debug; use figures::units::UPx; use figures::{Fraction, IntoSigned, Point, Rect, ScreenScale, Size}; use crate::context::{AsEventContext, EventContext, LayoutContext}; use crate::styles::{Edges, FlexibleDimension}; use crate::value::{IntoValue, Value}; use crate::widget::{MakeWidget, RootBehavior, WidgetRef, WrappedLayout, WrapperWidget}; use crate::ConstraintLimit; /// A widget aligns its contents to its container's boundaries. #[derive(Debug)] pub struct Align { child: WidgetRef, edges: Value>, } impl Align { /// Returns a new spacing widget containing `widget`, surrounding it with /// `margin`. pub fn new(margin: impl IntoValue>, widget: impl MakeWidget) -> Self { Self { child: WidgetRef::new(widget), edges: margin.into_value(), } } /// Returns a new spacing widget that centers `widget` vertically and /// horizontally. pub fn centered(widget: impl MakeWidget) -> Self { Self::new(FlexibleDimension::Auto, widget) } /// Sets the left edge of alignment to 0 and returns self. #[must_use] pub fn align_left(mut self) -> Self { self.edges .map_mut(|mut edges| edges.left = FlexibleDimension::ZERO); self } /// Sets the top edge of alignment to 0 and returns self. #[must_use] pub fn align_top(mut self) -> Self { self.edges .map_mut(|mut edges| edges.top = FlexibleDimension::ZERO); self } /// Sets the bottom edge of alignment to 0 and returns self. #[must_use] pub fn align_bottom(mut self) -> Self { self.edges .map_mut(|mut edges| edges.bottom = FlexibleDimension::ZERO); self } /// Sets the right edge of alignment to 0 and returns self. #[must_use] pub fn align_right(mut self) -> Self { self.edges .map_mut(|mut edges| edges.right = FlexibleDimension::ZERO); self } /// Sets the left and right edges of alignment to 0 and returns self. #[must_use] pub fn fit_horizontally(mut self) -> Self { self.edges.map_mut(|mut edges| { edges.left = FlexibleDimension::ZERO; edges.right = FlexibleDimension::ZERO; }); self } /// Sets the top and bottom edges of alignment to 0 and returns self. #[must_use] pub fn fit_vertically(mut self) -> Self { self.edges.map_mut(|mut edges| { edges.top = FlexibleDimension::ZERO; edges.bottom = FlexibleDimension::ZERO; }); self } fn measure( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_>, ) -> Layout { let margin = self.edges.get(); let vertical = FrameInfo::new(context.gfx.scale(), margin.top, margin.bottom); let horizontal = FrameInfo::new(context.gfx.scale(), margin.left, margin.right); let content_available = Size::new( horizontal.child_constraint(available_space.width), vertical.child_constraint(available_space.height), ); let child = self.child.mounted(&mut context.as_event_context()); let content_size = context.for_other(&child).layout(content_available); let (left, right, width) = horizontal.measure(available_space.width, content_size.width); let (top, bottom, height) = vertical.measure(available_space.height, content_size.height); Layout { margin: Edges { left, top, right, bottom, }, content: Size::new(width, height), } } } struct FrameInfo { a: Option, b: Option, } impl FrameInfo { fn new(scale: Fraction, a: FlexibleDimension, b: FlexibleDimension) -> Self { let a = match a { FlexibleDimension::Auto => None, FlexibleDimension::Dimension(dimension) => Some(dimension.into_upx(scale)), }; let b = match b { FlexibleDimension::Auto => None, FlexibleDimension::Dimension(dimension) => Some(dimension.into_upx(scale)), }; Self { a, b } } fn child_constraint(&self, available: ConstraintLimit) -> ConstraintLimit { match (self.a, self.b) { (Some(a), Some(b)) => available - (a + b), // If we have at least one auto-measurement, force the constraint // into ClippedAfter mode to make the widget attempt to size the // content to fit. (Some(one), None) | (None, Some(one)) => { ConstraintLimit::SizeToFit(available.max() - one) } (None, None) => ConstraintLimit::SizeToFit(available.max()), } } fn measure(&self, available: ConstraintLimit, content: UPx) -> (UPx, UPx, UPx) { match available { ConstraintLimit::Fill(size) => { let size = size.max(content); let remaining = size.saturating_sub(content); let (a, b) = match (self.a, self.b) { (Some(a), Some(b)) => (a, b), (Some(a), None) => (a, remaining - a), (None, Some(b)) => (remaining - b, b), (None, None) => { let a = remaining / 2; let b = remaining - a; (a, b) } }; (a, b, size - a - b) } ConstraintLimit::SizeToFit(_) => ( self.a.unwrap_or_default(), self.b.unwrap_or_default(), content, ), } } } impl WrapperWidget for Align { fn child_mut(&mut self) -> &mut WidgetRef { &mut self.child } fn root_behavior(&mut self, _context: &mut EventContext<'_>) -> Option { Some(RootBehavior::Align) } fn layout_child( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_>, ) -> WrappedLayout { let layout = self.measure(available_space, context); WrappedLayout { child: Rect::new( Point::new(layout.margin.left, layout.margin.top).into_signed(), layout.content.into_signed(), ), size: layout.content + layout.margin.size(), } } } impl AsMut for Align { fn as_mut(&mut self) -> &mut WidgetRef { &mut self.child } } #[derive(Debug)] struct Layout { margin: Edges, content: Size, }