Archived
f4abfe6fdc
Belated rename commit for foundation/el-ui — was missed in the workspace-wide crates→vessels pass earlier today. Same structural intent as the rename in the other repos: 'crates' is the Rust word, 'vessel' is El's, and the directory rename is the marker that this slot holds an El buildable unit even if its current contents are still Rust pending port. Plus the El ports themselves — manifest.el + src/main.el per sub- vessel (el-aop, el-auth, el-config, el-i18n, el-identity, el-layout, el-platform, el-publish, el-secrets, el-services, el-style, el-ui- compiler). The ui-compiler is a stub: elc only emits C right now; generating browser-target JS/Wasm is the biggest open language gap and gets its own project. Until then, el-ui-compiler emits a JS module that throws elc.backend_missing so callers fail loudly. Cross-repo path dependencies in Cargo.toml updated to vessels/.
163 lines
4.6 KiB
Rust
163 lines
4.6 KiB
Rust
/// Layout constraints — what the parent is offering the child.
|
|
///
|
|
/// A parent passes a LayoutConstraints to each child during layout.
|
|
/// The child must produce a size within these constraints.
|
|
/// Constraints flow down; sizes flow back up.
|
|
|
|
/// Constraints passed from a parent to a child during layout.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct LayoutConstraints {
|
|
/// Minimum width the child must be (dp).
|
|
pub min_width: f32,
|
|
/// Maximum width available to the child (dp). f32::INFINITY = unbounded.
|
|
pub max_width: f32,
|
|
/// Minimum height the child must be (dp).
|
|
pub min_height: f32,
|
|
/// Maximum height available to the child (dp). f32::INFINITY = unbounded.
|
|
pub max_height: f32,
|
|
}
|
|
|
|
impl LayoutConstraints {
|
|
/// Unconstrained — the child can be any size.
|
|
pub fn unbounded() -> Self {
|
|
Self {
|
|
min_width: 0.0,
|
|
max_width: f32::INFINITY,
|
|
min_height: 0.0,
|
|
max_height: f32::INFINITY,
|
|
}
|
|
}
|
|
|
|
/// Constrained to a specific width, unconstrained height.
|
|
pub fn with_max_width(max_width: f32) -> Self {
|
|
Self {
|
|
min_width: 0.0,
|
|
max_width,
|
|
min_height: 0.0,
|
|
max_height: f32::INFINITY,
|
|
}
|
|
}
|
|
|
|
/// Constrained to an exact width and height.
|
|
pub fn tight(width: f32, height: f32) -> Self {
|
|
Self {
|
|
min_width: width,
|
|
max_width: width,
|
|
min_height: height,
|
|
max_height: height,
|
|
}
|
|
}
|
|
|
|
/// Return constraints loosened to allow any size up to the maximums.
|
|
pub fn loosen(&self) -> Self {
|
|
Self {
|
|
min_width: 0.0,
|
|
max_width: self.max_width,
|
|
min_height: 0.0,
|
|
max_height: self.max_height,
|
|
}
|
|
}
|
|
|
|
/// Deflate the constraints by padding amounts.
|
|
/// Useful when a parent applies its own padding before offering space to a child.
|
|
pub fn deflate(&self, horizontal: f32, vertical: f32) -> Self {
|
|
Self {
|
|
min_width: (self.min_width - horizontal).max(0.0),
|
|
max_width: (self.max_width - horizontal).max(0.0),
|
|
min_height: (self.min_height - vertical).max(0.0),
|
|
max_height: (self.max_height - vertical).max(0.0),
|
|
}
|
|
}
|
|
|
|
/// Is the width dimension bounded?
|
|
pub fn has_bounded_width(&self) -> bool {
|
|
self.max_width.is_finite()
|
|
}
|
|
|
|
/// Is the height dimension bounded?
|
|
pub fn has_bounded_height(&self) -> bool {
|
|
self.max_height.is_finite()
|
|
}
|
|
|
|
/// Clamp a proposed size to fit within these constraints.
|
|
pub fn clamp_size(&self, width: f32, height: f32) -> (f32, f32) {
|
|
(
|
|
width.clamp(self.min_width, self.max_width),
|
|
height.clamp(self.min_height, self.max_height),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// The size a child reports back to its parent after layout.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct Size {
|
|
pub width: f32,
|
|
pub height: f32,
|
|
}
|
|
|
|
impl Size {
|
|
pub fn new(width: f32, height: f32) -> Self {
|
|
Self { width, height }
|
|
}
|
|
|
|
pub fn zero() -> Self {
|
|
Self { width: 0.0, height: 0.0 }
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn unbounded_has_no_max() {
|
|
let c = LayoutConstraints::unbounded();
|
|
assert!(c.max_width.is_infinite());
|
|
assert!(c.max_height.is_infinite());
|
|
}
|
|
|
|
#[test]
|
|
fn tight_constraints() {
|
|
let c = LayoutConstraints::tight(100.0, 50.0);
|
|
assert_eq!(c.min_width, 100.0);
|
|
assert_eq!(c.max_width, 100.0);
|
|
}
|
|
|
|
#[test]
|
|
fn deflate_reduces_available_space() {
|
|
let c = LayoutConstraints::tight(200.0, 100.0);
|
|
let deflated = c.deflate(16.0, 8.0);
|
|
assert_eq!(deflated.max_width, 184.0);
|
|
assert_eq!(deflated.max_height, 92.0);
|
|
}
|
|
|
|
#[test]
|
|
fn deflate_does_not_go_negative() {
|
|
let c = LayoutConstraints::tight(10.0, 10.0);
|
|
let deflated = c.deflate(20.0, 20.0);
|
|
assert_eq!(deflated.max_width, 0.0);
|
|
assert_eq!(deflated.max_height, 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn clamp_size() {
|
|
let c = LayoutConstraints {
|
|
min_width: 50.0,
|
|
max_width: 200.0,
|
|
min_height: 30.0,
|
|
max_height: 100.0,
|
|
};
|
|
let (w, h) = c.clamp_size(250.0, 20.0);
|
|
assert_eq!(w, 200.0);
|
|
assert_eq!(h, 30.0);
|
|
}
|
|
|
|
#[test]
|
|
fn bounded_width_detection() {
|
|
let bounded = LayoutConstraints::with_max_width(400.0);
|
|
let unbounded = LayoutConstraints::unbounded();
|
|
assert!(bounded.has_bounded_width());
|
|
assert!(!unbounded.has_bounded_width());
|
|
}
|
|
}
|