ltk/layout/
stack.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// 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 )>,
	/// When `true`, [`preferred_size`](Self::preferred_size) reports the max of
	/// children's intrinsic widths and heights instead of `(max_width, tallest)`.
	pub fit_content: bool,
}

impl<Msg: Clone> Stack<Msg>
{
	/// Create an empty stack.
	pub fn new() -> Self
	{
		Self { children: Vec::new(), fit_content: false }
	}

	/// Enable [`Self::fit_content`].
	pub fn fit_content( mut self ) -> Self
	{
		self.fit_content = true;
		self
	}

	/// 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
	}

	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
	{
		if self.fit_content
		{
			let content_w = self.children.iter()
				.map( |( c, _, _, _, _, _ )| match c
				{
					Element::Spacer( s )      => s.resolved_width( canvas ).unwrap_or( 0.0 ),
					Element::Separator( _ )   => 0.0,
					Element::Scroll( _ )      => 0.0,
					Element::ProgressBar( _ ) => 0.0,
					Element::Slider( _ )      => 0.0,
					Element::TextEdit( t )    => if t.fixed_width.is_some()
					{
						t.preferred_size( max_width, canvas ).0
					} else { 0.0 },
					other => other.preferred_size( max_width, canvas ).0,
				} )
				.fold( 0.0_f32, f32::max );
			let max_h = self.children.iter()
				.map( |( c, _, _, _, _, _ )| c.preferred_size( max_width, canvas ).1 )
				.fold( 0.0_f32, f32::max );
			return ( content_w.min( max_width ), max_h );
		}

		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(),
			fit_content: self.fit_content,
		}
	}
}

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()
}