Some more layout features, align/stretch.
This commit is contained in:
@@ -41,7 +41,7 @@ fn CounterApp(title: &'static str) -> impl IntoView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
column(gap = 16.0, padding = 24.0, text_style = text_style) {
|
column(gap = 16.0, padding = 24.0, align_items = AlignItems::Center, text_style = text_style) {
|
||||||
text(role = TextRole::Heading(1), size = 32.0, weight = FontWeight::Semibold, color = Color::rgba(0, 0, 0, 255)) {
|
text(role = TextRole::Heading(1), size = 32.0, weight = FontWeight::Semibold, color = Color::rgba(0, 0, 0, 255)) {
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,7 @@ fn CounterApp(title: &'static str) -> impl IntoView {
|
|||||||
block(
|
block(
|
||||||
padding = 16.0,
|
padding = 16.0,
|
||||||
gap = 8.0,
|
gap = 8.0,
|
||||||
|
align_self = AlignItems::Stretch,
|
||||||
background = Color::rgba(255, 255, 255, 200),
|
background = Color::rgba(255, 255, 255, 200),
|
||||||
border_radius = 12.0,
|
border_radius = 12.0,
|
||||||
border = Border::new(2.0, Color::rgba(0, 0, 0, 120)),
|
border = Border::new(2.0, Color::rgba(0, 0, 0, 120)),
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ pub mod prelude {
|
|||||||
view,
|
view,
|
||||||
};
|
};
|
||||||
pub use ruin_ui::{
|
pub use ruin_ui::{
|
||||||
Border, Color, CursorIcon, Edges, Element, ElementId, InteractionTree, PointerButton,
|
AlignItems, Border, Color, CursorIcon, Edges, Element, ElementId, InteractionTree,
|
||||||
PointerEventKind, RoutedPointerEvent, RoutedPointerEventKind, ScrollbarStyle,
|
PointerButton, PointerEventKind, RoutedPointerEvent, RoutedPointerEventKind,
|
||||||
TextFontFamily, TextStyle, TextWrap, UiSize,
|
ScrollbarStyle, TextFontFamily, TextStyle, TextWrap, UiSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use ruin_ui::{Color, Element, ElementId};
|
use ruin_ui::{AlignItems, Color, Element, ElementId};
|
||||||
|
|
||||||
use crate::context::allocate_element_id;
|
use crate::context::allocate_element_id;
|
||||||
use crate::converters::{IntoBorder, IntoEdges, IntoShadow};
|
use crate::converters::{IntoBorder, IntoEdges, IntoShadow};
|
||||||
@@ -82,6 +82,16 @@ impl ContainerProps {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn align_items(mut self, align: AlignItems) -> Self {
|
||||||
|
self.element = self.element.align_items(align);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_self(mut self, align: AlignItems) -> Self {
|
||||||
|
self.element = self.element.align_self(align);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn shadow(mut self, shadow: impl IntoShadow) -> Self {
|
pub fn shadow(mut self, shadow: impl IntoShadow) -> Self {
|
||||||
self.element = self.element.shadow(shadow.into_shadow());
|
self.element = self.element.shadow(shadow.into_shadow());
|
||||||
self
|
self
|
||||||
@@ -118,6 +128,16 @@ pub fn block() -> ContainerBuilder {
|
|||||||
impl ContainerBuilder {
|
impl ContainerBuilder {
|
||||||
impl_props_methods!();
|
impl_props_methods!();
|
||||||
|
|
||||||
|
pub fn align_items(mut self, align: AlignItems) -> Self {
|
||||||
|
self.props = self.props.align_items(align);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_self(mut self, align: AlignItems) -> Self {
|
||||||
|
self.props = self.props.align_self(align);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn shadow(mut self, shadow: impl IntoShadow) -> Self {
|
pub fn shadow(mut self, shadow: impl IntoShadow) -> Self {
|
||||||
self.props = self.props.shadow(shadow);
|
self.props = self.props.shadow(shadow);
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::scene::{
|
|||||||
UiSize,
|
UiSize,
|
||||||
};
|
};
|
||||||
use crate::text::TextSystem;
|
use crate::text::TextSystem;
|
||||||
use crate::tree::{CursorIcon, Edges, Element, ElementId, FlexDirection, ImageNode, Style};
|
use crate::tree::{AlignItems, CursorIcon, Edges, Element, ElementId, FlexDirection, ImageNode, Style};
|
||||||
|
|
||||||
pub fn layout_scene(version: u64, logical_size: UiSize, root: &Element) -> SceneSnapshot {
|
pub fn layout_scene(version: u64, logical_size: UiSize, root: &Element) -> SceneSnapshot {
|
||||||
let mut text_system = TextSystem::new();
|
let mut text_system = TextSystem::new();
|
||||||
@@ -793,7 +793,11 @@ fn point_in_rounded_rect(point: crate::scene::Point, rect: Rect, radius: f32) ->
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct MeasuredChild {
|
struct MeasuredChild {
|
||||||
|
/// Offered cross-axis size (full available_cross for stretch; explicit value otherwise).
|
||||||
cross: f32,
|
cross: f32,
|
||||||
|
/// Natural cross-axis size: set when parent uses non-stretch alignment, child has no
|
||||||
|
/// explicit cross size, and the child is not flex. `None` means use `cross`.
|
||||||
|
natural_cross: Option<f32>,
|
||||||
main: f32,
|
main: f32,
|
||||||
is_flex: bool,
|
is_flex: bool,
|
||||||
}
|
}
|
||||||
@@ -870,6 +874,31 @@ fn layout_container_children(
|
|||||||
intrinsic.max(min_main_outer)
|
intrinsic.max(min_main_outer)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// For non-stretch alignment, measure the child's natural cross size so it is not
|
||||||
|
// forcibly stretched to fill the container's cross axis. Only done for non-flex
|
||||||
|
// children with no explicit cross-axis style, since flex main size is unknown here.
|
||||||
|
// align_self on the child overrides the parent's align_items for this child.
|
||||||
|
let effective_align = child.style.align_self.unwrap_or(element.style.align_items);
|
||||||
|
let natural_cross =
|
||||||
|
if effective_align != AlignItems::Stretch
|
||||||
|
&& !is_flex
|
||||||
|
&& child_cross_size(child, element.style.direction).is_none()
|
||||||
|
{
|
||||||
|
let offered = match element.style.direction {
|
||||||
|
FlexDirection::Row => UiSize::new(measured_main, available_cross),
|
||||||
|
FlexDirection::Column => UiSize::new(available_cross, measured_main),
|
||||||
|
};
|
||||||
|
let natural =
|
||||||
|
intrinsic_size(child, offered, text_system, perf_stats, layout_cache);
|
||||||
|
Some(
|
||||||
|
cross_axis_size(natural, element.style.direction)
|
||||||
|
.max(min_cross_outer)
|
||||||
|
.min(available_cross.max(min_cross_outer)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if is_flex {
|
if is_flex {
|
||||||
flex_total += child_flex_weight(child);
|
flex_total += child_flex_weight(child);
|
||||||
} else {
|
} else {
|
||||||
@@ -877,6 +906,7 @@ fn layout_container_children(
|
|||||||
}
|
}
|
||||||
measured_children.push(MeasuredChild {
|
measured_children.push(MeasuredChild {
|
||||||
cross,
|
cross,
|
||||||
|
natural_cross,
|
||||||
main: measured_main,
|
main: measured_main,
|
||||||
is_flex,
|
is_flex,
|
||||||
});
|
});
|
||||||
@@ -902,12 +932,24 @@ fn layout_container_children(
|
|||||||
} else {
|
} else {
|
||||||
measured.main
|
measured.main
|
||||||
};
|
};
|
||||||
|
// For non-stretch alignment, use the child's natural cross size; otherwise stretch.
|
||||||
|
// align_self on the child overrides the parent's align_items for this child.
|
||||||
|
let effective_align = child.style.align_self.unwrap_or(element.style.align_items);
|
||||||
|
let actual_cross = measured.natural_cross.unwrap_or(measured.cross);
|
||||||
|
let cross_origin = {
|
||||||
|
let base = cross_axis_origin(content, element.style.direction);
|
||||||
|
match effective_align {
|
||||||
|
AlignItems::Stretch | AlignItems::Start => base,
|
||||||
|
AlignItems::Center => base + (available_cross - actual_cross) * 0.5,
|
||||||
|
AlignItems::End => base + available_cross - actual_cross,
|
||||||
|
}
|
||||||
|
};
|
||||||
let child_rect = child_rect(
|
let child_rect = child_rect(
|
||||||
content,
|
cross_origin,
|
||||||
element.style.direction,
|
element.style.direction,
|
||||||
cursor,
|
cursor,
|
||||||
child_main.max(0.0),
|
child_main.max(0.0),
|
||||||
measured.cross,
|
actual_cross,
|
||||||
);
|
);
|
||||||
children.push(layout_element(
|
children.push(layout_element(
|
||||||
child,
|
child,
|
||||||
@@ -1865,16 +1907,23 @@ fn main_axis_origin(rect: Rect, direction: FlexDirection) -> f32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cross_axis_origin(content: Rect, direction: FlexDirection) -> f32 {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row => content.origin.y,
|
||||||
|
FlexDirection::Column => content.origin.x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn child_rect(
|
fn child_rect(
|
||||||
content: Rect,
|
cross_origin: f32,
|
||||||
direction: FlexDirection,
|
direction: FlexDirection,
|
||||||
main_origin: f32,
|
main_origin: f32,
|
||||||
main_size: f32,
|
main_size: f32,
|
||||||
cross_size: f32,
|
cross_size: f32,
|
||||||
) -> Rect {
|
) -> Rect {
|
||||||
match direction {
|
match direction {
|
||||||
FlexDirection::Row => Rect::new(main_origin, content.origin.y, main_size, cross_size),
|
FlexDirection::Row => Rect::new(main_origin, cross_origin, main_size, cross_size),
|
||||||
FlexDirection::Column => Rect::new(content.origin.x, main_origin, cross_size, main_size),
|
FlexDirection::Column => Rect::new(cross_origin, main_origin, cross_size, main_size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ pub use text::{
|
|||||||
TextStyle, TextSystem, TextWrap,
|
TextStyle, TextSystem, TextWrap,
|
||||||
};
|
};
|
||||||
pub use tree::{
|
pub use tree::{
|
||||||
Border, BoxShadow, BoxShadowKind, CornerRadius, CursorIcon, Edges, Element, ElementId,
|
AlignItems, Border, BoxShadow, BoxShadowKind, CornerRadius, CursorIcon, Edges, Element,
|
||||||
FlexDirection, ScrollbarStyle, Style,
|
ElementId, FlexDirection, ScrollbarStyle, Style,
|
||||||
};
|
};
|
||||||
pub use window::{
|
pub use window::{
|
||||||
DecorationMode, WindowConfigured, WindowId, WindowLifecycle, WindowSpec, WindowUpdate,
|
DecorationMode, WindowConfigured, WindowId, WindowLifecycle, WindowSpec, WindowUpdate,
|
||||||
|
|||||||
@@ -23,6 +23,15 @@ pub enum FlexDirection {
|
|||||||
Column,
|
Column,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub enum AlignItems {
|
||||||
|
#[default]
|
||||||
|
Stretch,
|
||||||
|
Start,
|
||||||
|
Center,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum CursorIcon {
|
pub enum CursorIcon {
|
||||||
Default,
|
Default,
|
||||||
@@ -180,6 +189,9 @@ impl Default for ScrollbarStyle {
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
pub direction: FlexDirection,
|
pub direction: FlexDirection,
|
||||||
|
pub align_items: AlignItems,
|
||||||
|
/// Per-element override of the parent container's `align_items`. `None` means inherit.
|
||||||
|
pub align_self: Option<AlignItems>,
|
||||||
pub width: Option<f32>,
|
pub width: Option<f32>,
|
||||||
pub height: Option<f32>,
|
pub height: Option<f32>,
|
||||||
pub min_width: Option<f32>,
|
pub min_width: Option<f32>,
|
||||||
@@ -200,6 +212,8 @@ impl Default for Style {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
direction: FlexDirection::Column,
|
direction: FlexDirection::Column,
|
||||||
|
align_items: AlignItems::Stretch,
|
||||||
|
align_self: None,
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
min_width: None,
|
min_width: None,
|
||||||
@@ -400,6 +414,16 @@ impl Element {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn align_items(mut self, align: AlignItems) -> Self {
|
||||||
|
self.style.align_items = align;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_self(mut self, align: AlignItems) -> Self {
|
||||||
|
self.style.align_self = Some(align);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cursor(mut self, cursor: CursorIcon) -> Self {
|
pub fn cursor(mut self, cursor: CursorIcon) -> Self {
|
||||||
self.style.cursor = Some(cursor);
|
self.style.cursor = Some(cursor);
|
||||||
self
|
self
|
||||||
@@ -545,9 +569,17 @@ impl Hash for ScrollbarStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for AlignItems {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
(*self as u8).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hash for Style {
|
impl Hash for Style {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.direction.hash(state);
|
self.direction.hash(state);
|
||||||
|
self.align_items.hash(state);
|
||||||
|
self.align_self.hash(state);
|
||||||
self.width.map(f32::to_bits).hash(state);
|
self.width.map(f32::to_bits).hash(state);
|
||||||
self.height.map(f32::to_bits).hash(state);
|
self.height.map(f32::to_bits).hash(state);
|
||||||
self.min_width.map(f32::to_bits).hash(state);
|
self.min_width.map(f32::to_bits).hash(state);
|
||||||
|
|||||||
Reference in New Issue
Block a user