First commit. Version 0.1.0

This commit is contained in:
2026-05-10 09:58:23 +02:00
parent af105b7f7d
commit bbab5e238d
635 changed files with 53627 additions and 175 deletions

182
src/layout/stack.rs Normal file
View 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()
}