First commit. Version 0.1.0
This commit is contained in:
182
src/layout/stack.rs
Normal file
182
src/layout/stack.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use crate::types::Rect;
|
||||
use crate::render::Canvas;
|
||||
use crate::widget::Element;
|
||||
|
||||
/// Horizontal alignment of a child within a [`Stack`] rect.
|
||||
#[ derive( Debug, Clone, Copy, PartialEq ) ]
|
||||
pub enum HAlign
|
||||
{
|
||||
/// Align to the left edge.
|
||||
Start,
|
||||
/// Center horizontally.
|
||||
Center,
|
||||
/// Align to the right edge.
|
||||
End,
|
||||
/// Stretch to fill the full width.
|
||||
Fill,
|
||||
}
|
||||
|
||||
/// Vertical alignment of a child within a [`Stack`] rect.
|
||||
#[ derive( Debug, Clone, Copy, PartialEq ) ]
|
||||
pub enum VAlign
|
||||
{
|
||||
/// Align to the top edge.
|
||||
Top,
|
||||
/// Center vertically.
|
||||
Center,
|
||||
/// Align to the bottom edge.
|
||||
Bottom,
|
||||
/// Stretch to fill the full height.
|
||||
Fill,
|
||||
}
|
||||
|
||||
/// A layout that draws all its children stacked on top of each other.
|
||||
/// Each child can be positioned within the Stack rect via [`HAlign`]/[`VAlign`].
|
||||
///
|
||||
/// Useful for overlaying a foreground widget on top of a background image:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::sync::Arc;
|
||||
/// # use ltk::{ column, img_widget, stack, text, Element, HAlign, VAlign };
|
||||
/// # #[ derive( Clone ) ] enum Msg {}
|
||||
/// # fn _ex( bg_rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> Element<Msg> {
|
||||
/// stack()
|
||||
/// .push( img_widget( bg_rgba, w, h ) )
|
||||
/// .push_aligned( column().push( text( "Bottom right" ) ), HAlign::End, VAlign::Bottom )
|
||||
/// .into()
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct Stack<Msg: Clone>
|
||||
{
|
||||
/// Children with their alignment, margin, and extra `(x, y)` translation
|
||||
/// applied after alignment. Drawn in order — last child is on top.
|
||||
pub children: Vec<( Element<Msg>, HAlign, VAlign, f32, f32, f32 )>,
|
||||
}
|
||||
|
||||
impl<Msg: Clone> Stack<Msg>
|
||||
{
|
||||
/// Create an empty stack.
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self { children: Vec::new() }
|
||||
}
|
||||
|
||||
/// Append a child that fills the entire Stack rect (Android FrameLayout default).
|
||||
pub fn push( self, e: impl Into<Element<Msg>> ) -> Self
|
||||
{
|
||||
self.push_aligned_margin( e, HAlign::Fill, VAlign::Fill, 0.0 )
|
||||
}
|
||||
|
||||
/// Append a child with explicit horizontal and vertical alignment.
|
||||
pub fn push_aligned(
|
||||
self,
|
||||
e: impl Into<Element<Msg>>,
|
||||
h_align: HAlign,
|
||||
v_align: VAlign,
|
||||
) -> Self
|
||||
{
|
||||
self.push_aligned_margin( e, h_align, v_align, 0.0 )
|
||||
}
|
||||
|
||||
/// Append a child with alignment and a uniform margin (inset from the Stack edges).
|
||||
pub fn push_aligned_margin(
|
||||
mut self,
|
||||
e: impl Into<Element<Msg>>,
|
||||
h_align: HAlign,
|
||||
v_align: VAlign,
|
||||
margin: f32,
|
||||
) -> Self
|
||||
{
|
||||
self.children.push( ( e.into(), h_align, v_align, margin, 0.0, 0.0 ) );
|
||||
self
|
||||
}
|
||||
|
||||
/// Append a child with alignment plus an extra `(x, y)` translation in
|
||||
/// logical pixels. Useful when a child needs to shift outside the normal
|
||||
/// alignment grid without giving up the margin or alignment shorthand.
|
||||
/// Positive `x` / `y` move the child right / down.
|
||||
pub fn push_translated(
|
||||
mut self,
|
||||
e: impl Into<Element<Msg>>,
|
||||
h_align: HAlign,
|
||||
v_align: VAlign,
|
||||
offset_x: f32,
|
||||
offset_y: f32,
|
||||
) -> Self
|
||||
{
|
||||
self.children.push( ( e.into(), h_align, v_align, 0.0, offset_x, offset_y ) );
|
||||
self
|
||||
}
|
||||
|
||||
/// Return the preferred `(width, height)` — the maximum height among children.
|
||||
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
|
||||
{
|
||||
let max_h = self.children.iter()
|
||||
.map( |( c, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 )
|
||||
.fold( 0.0_f32, f32::max );
|
||||
( max_width, max_h )
|
||||
}
|
||||
|
||||
/// Return `(rect, child_index)` pairs, computing each child's rect from its alignment.
|
||||
pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
|
||||
{
|
||||
self.children.iter().enumerate().map( |( i, ( child, h_align, v_align, margin, ox, oy ) )|
|
||||
{
|
||||
let inner_w = ( rect.width - margin * 2.0 ).max( 0.0 );
|
||||
let inner_h = ( rect.height - margin * 2.0 ).max( 0.0 );
|
||||
let ( pref_w, pref_h ) = child.preferred_size( inner_w, canvas );
|
||||
|
||||
let ( x, width ) = match h_align
|
||||
{
|
||||
HAlign::Start => ( rect.x + margin, pref_w ),
|
||||
HAlign::Center => ( rect.x + ( rect.width - pref_w ) / 2.0, pref_w ),
|
||||
HAlign::End => ( rect.x + rect.width - pref_w - margin, pref_w ),
|
||||
HAlign::Fill => ( rect.x + margin, inner_w ),
|
||||
};
|
||||
|
||||
let ( y, height ) = match v_align
|
||||
{
|
||||
VAlign::Top => ( rect.y + margin, pref_h ),
|
||||
VAlign::Center => ( rect.y + ( rect.height - pref_h ) / 2.0, pref_h ),
|
||||
VAlign::Bottom => ( rect.y + rect.height - pref_h - margin, pref_h ),
|
||||
VAlign::Fill => ( rect.y + margin, inner_h ),
|
||||
};
|
||||
|
||||
( Rect { x: x + ox, y: y + oy, width, height }, i )
|
||||
} ).collect()
|
||||
}
|
||||
|
||||
/// No-op — children are drawn directly by the event loop during layout.
|
||||
pub fn draw( &self, _canvas: &mut Canvas, _rect: Rect, _focused: bool ) {}
|
||||
|
||||
pub( crate ) fn map_msg<U>( self, f: &crate::widget::MapFn<Msg, U> ) -> Stack<U>
|
||||
where
|
||||
U: Clone + 'static,
|
||||
Msg: 'static,
|
||||
{
|
||||
Stack
|
||||
{
|
||||
children: self.children.into_iter()
|
||||
.map( |( child, ha, va, margin, ox, oy )|
|
||||
( child.map_arc( f ), ha, va, margin, ox, oy ) )
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Msg: Clone> Default for Stack<Msg>
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty [`Stack`].
|
||||
pub fn stack<Msg: Clone>() -> Stack<Msg>
|
||||
{
|
||||
Stack::new()
|
||||
}
|
||||
Reference in New Issue
Block a user