//! 有限布局盒子的尺寸策略。区分 auto、固定值和 fill,并处理 min/max 对约束与最终尺寸的限制。 //! margin、border、padding 的空间分配由布局层结合本模块完成;场景世界范围不被当成无限布局盒子。 //! //! Native border-box sizing. An unbounded axis cannot resolve `fill`. use crate::{layout::Size, ui_bridge::MAX_DIMENSION}; use anyhow::Result; use endor_core::{Value, VmError}; #[derive(Debug, Clone, Copy, Default, PartialEq)] /// 一个布局轴的尺寸策略:随内容、固定值或占用有限可用空间。 pub enum Dimension { #[default] Auto, Fixed(f32), Fill, } impl Dimension { /// 把内部尺寸策略投影为相应的源属性表示。 pub fn value(self) -> Value { match self { Self::Auto => Value::Text("auto".into()), Self::Fill => Value::Text("fill".into()), Self::Fixed(value) => Value::Number((value as f64).into()), } } /// 把外部源码或属性表示转换为内部枚举/结构,并反馈不支持的输入。 pub fn parse(value: &Value) -> Result { match value { Value::Text(text) if text == "auto" => Ok(Self::Auto), Value::Text(text) if text == "fill" => Ok(Self::Fill), Value::Number(_) => Ok(Self::Fixed(crate::scroll::coordinate(value).and_then( |n| { if n >= 0.0 { Ok(n) } else { Err(VmError::InvalidArgument( "box dimensions must be nonnegative", )) } }, )?)), _ => Err(VmError::InvalidArgument( "box dimension expects auto, fill or a nonnegative number", )), } } } #[derive(Debug, Clone, Copy, PartialEq)] /// border-box 尺寸策略与双轴上下限,实际求解仍要结合父级约束。 pub struct Sizing { pub width: Dimension, pub height: Dimension, pub min_width: f32, pub min_height: f32, pub max_width: f32, pub max_height: f32, } impl Default for Sizing { /// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。 fn default() -> Self { Self { width: Dimension::Auto, height: Dimension::Auto, min_width: 0.0, min_height: 0.0, max_width: MAX_DIMENSION as f32, max_height: MAX_DIMENSION as f32, } } } impl Sizing { /// Constrain descendants using the actual finite box before wrapping text. /// 根据固定/auto/fill 与 min/max 规则细化父级提供的可用约束。 pub fn available(self, mut constraints: [f32; 4]) -> [f32; 4] { constraints[2] = constraints[2].min(self.max_width); constraints[3] = constraints[3].min(self.max_height); if let Dimension::Fixed(width) = self.width { constraints[2] = constraints[2].min(width.max(self.min_width)); } if let Dimension::Fixed(height) = self.height { constraints[3] = constraints[3].min(height.max(self.min_height)); } constraints[0] = constraints[0].min(constraints[2]); constraints[1] = constraints[1].min(constraints[3]); constraints } /// 结合内容期望尺寸、轴策略和父级约束求最终盒子尺寸;无界轴不能解析 fill。 pub fn resolve(self, intrinsic: Size, constraints: [f32; 4]) -> Result { /// 针对单个尺寸轴应用约束与边界规则,供双轴尺寸求解复用。 fn axis( policy: Dimension, intrinsic: f32, min: f32, max: f32, available: f32, ) -> Result { let value = match policy { Dimension::Auto => intrinsic, Dimension::Fixed(value) => value, Dimension::Fill => { if available >= MAX_DIMENSION as f32 { return Err(VmError::InvalidArgument( "fill requires a finite parent allocation on this axis", ) .into()); } available } }; Ok(value.clamp(min, max).min(available)) } Ok(Size { width: axis( self.width, intrinsic.width, self.min_width, self.max_width, constraints[2], )?, height: axis( self.height, intrinsic.height, self.min_height, self.max_height, constraints[3], )?, }) } }