Skip to main content

flux_tui/window/
mod.rs

1pub(crate) mod event_queue;
2pub(crate) mod handler;
3pub(crate) mod helper;
4
5use as_any::AsAny;
6use singlevec::SingleVec;
7use uid::IdU64;
8use vector2d::Vector2D;
9
10use crate::canvas::{BorderCanvas, Canvas};
11use crate::common::*;
12use std::cell::RefCell;
13use std::rc::*;
14
15pub use event_queue::*;
16pub use handler::WindowHandler;
17pub use helper::*;
18
19
20/// Defines border decoration
21#[derive(Clone, Copy, Debug, PartialEq, Default)]
22pub enum BorderKind {
23	#[default]
24	Solid,
25	Thick,
26	Dashed,
27}
28
29impl BorderKind {
30	/// Top Left, Top Right, Bottom Left, Bottom Right
31	pub const fn corner_style(&self) -> [Grapheme; 4] {
32		match self {
33			BorderKind::Solid => [
34				Grapheme::new_unchecked("┌", GlyphWidth::Half),
35				Grapheme::new_unchecked("┐", GlyphWidth::Half),
36				Grapheme::new_unchecked("└", GlyphWidth::Half),
37				Grapheme::new_unchecked("┘", GlyphWidth::Half),
38			],
39			BorderKind::Dashed => [
40				Grapheme::new_unchecked("╔", GlyphWidth::Half),
41				Grapheme::new_unchecked("╗", GlyphWidth::Half),
42				Grapheme::new_unchecked("╚", GlyphWidth::Half),
43				Grapheme::new_unchecked("╝", GlyphWidth::Half),
44			],
45			BorderKind::Thick => [
46				Grapheme::new_unchecked("┏", GlyphWidth::Half),
47				Grapheme::new_unchecked("┓", GlyphWidth::Half),
48				Grapheme::new_unchecked("┗", GlyphWidth::Half),
49				Grapheme::new_unchecked("┛", GlyphWidth::Half),
50			],
51		}
52	}
53
54	/// Default cross
55	pub const fn cross_style(&self) -> Grapheme {
56		match self {
57			BorderKind::Solid => Grapheme::new_unchecked("┼", GlyphWidth::Half),
58			BorderKind::Dashed => Grapheme::new_unchecked("╬", GlyphWidth::Half),
59			BorderKind::Thick => Grapheme::new_unchecked("╋", GlyphWidth::Half),
60		}
61	}
62
63	/// Non-connected side: Left, Right, Top, Bottom
64	pub const fn connector_style(&self) -> [Grapheme; 4] {
65		match self {
66			BorderKind::Solid => [
67				Grapheme::new_unchecked("├", GlyphWidth::Half),
68				Grapheme::new_unchecked("┤", GlyphWidth::Half),
69				Grapheme::new_unchecked("┬", GlyphWidth::Half),
70				Grapheme::new_unchecked("┴", GlyphWidth::Half),
71			],
72			BorderKind::Dashed => [
73				Grapheme::new_unchecked("╠", GlyphWidth::Half),
74				Grapheme::new_unchecked("╣", GlyphWidth::Half),
75				Grapheme::new_unchecked("╦", GlyphWidth::Half),
76				Grapheme::new_unchecked("╩", GlyphWidth::Half),
77			],
78			BorderKind::Thick => [
79				Grapheme::new_unchecked("┣", GlyphWidth::Half),
80				Grapheme::new_unchecked("┫", GlyphWidth::Half),
81				Grapheme::new_unchecked("┳", GlyphWidth::Half),
82				Grapheme::new_unchecked("┻", GlyphWidth::Half),
83			],
84		}
85	}
86
87	/// Horizontal line, Vertical line
88	pub const fn line_style(&self) -> [Grapheme; 2] {
89		match self {
90			BorderKind::Solid => [
91				Grapheme::new_unchecked("─", GlyphWidth::Half),
92				Grapheme::new_unchecked("│", GlyphWidth::Half),
93			],
94			BorderKind::Dashed => [
95				Grapheme::new_unchecked("═", GlyphWidth::Half),
96				Grapheme::new_unchecked("║", GlyphWidth::Half),
97			],
98			BorderKind::Thick => [
99				Grapheme::new_unchecked("━", GlyphWidth::Half),
100				Grapheme::new_unchecked("┃", GlyphWidth::Half),
101			],
102		}
103	}
104}
105
106/// The given [Window] is the instance which returned BorderStyle
107pub type CustomStyleFn = fn(&dyn Window, BorderCanvas);
108
109#[derive(Copy, Clone, Debug, Default)]
110pub enum BorderStyle {
111	#[default]
112	None,
113	Preset {
114		kind: BorderKind,
115		bg: Option<Color>,
116		fg: Option<Color>,
117	},
118	Custom(CustomStyleFn, Thickness),
119}
120
121impl PartialEq for BorderStyle {
122	fn eq(&self, other: &Self) -> bool {
123		match self {
124			Self::None => matches!(other, Self::None),
125			Self::Preset {
126				kind: kind0,
127				bg: bg0,
128				fg: fg0,
129			} => match other {
130				Self::Preset { kind, bg, fg } => kind0 == kind && bg0 == bg && fg0 == fg,
131				_ => false,
132			},
133			Self::Custom(_, _) => false,
134		}
135	}
136}
137
138impl BorderStyle {
139	pub const fn new(kind: BorderKind, bg: Option<Color>, fg: Option<Color>) -> Self {
140		Self::Preset { kind, bg, fg }
141	}
142
143	/// Use a custom callback to render the border
144	pub const fn custom(f: CustomStyleFn, thickness: Thickness) -> Self {
145		Self::Custom(f, thickness)
146	}
147
148	pub const fn thickness(&self) -> Thickness {
149		match *self {
150			BorderStyle::None => Thickness::from(0),
151			BorderStyle::Custom(_, tn) => tn,
152			_ => Thickness::from(1),
153		}
154	}
155
156	pub const fn get_borderkind(&self) -> Option<BorderKind> {
157		match self {
158			BorderStyle::Preset { kind, .. } => Some(*kind),
159			_ => None,
160		}
161	}
162}
163
164#[derive(Default, Clone, Copy, Debug, PartialEq)]
165pub struct Thickness {
166	pub top: TSize,
167	pub left: TSize,
168	pub right: TSize,
169	pub bottom: TSize,
170}
171
172impl Thickness {
173	pub const fn from(size: TSize) -> Self {
174		Self {
175			top: size,
176			left: size,
177			right: size,
178			bottom: size,
179		}
180	}
181
182	pub const fn new(top: TSize, left: TSize, right: TSize, bottom: TSize) -> Self {
183		Self {
184			top,
185			left,
186			right,
187			bottom,
188		}
189	}
190
191	pub const fn width(&self) -> TSize {
192		self.left + self.right
193	}
194
195	pub const fn height(&self) -> TSize {
196		self.top + self.bottom
197	}
198}
199
200impl From<CustomStyleFn> for BorderStyle {
201	fn from(value: CustomStyleFn) -> Self {
202		Self::Custom(value, Thickness::from(1))
203	}
204}
205
206impl From<BorderKind> for BorderStyle {
207	fn from(value: BorderKind) -> Self {
208		Self::Preset {
209			kind: value,
210			bg: None,
211			fg: None,
212		}
213	}
214}
215
216#[derive(Copy, Clone, Debug, Default)]
217pub struct RectsOverlapping {}
218
219pub struct SubWindowBuilder {
220	base_rect: Rect,
221	children: SingleVec<WindowRef>,
222	rects: SingleVec<Rect>,
223	overlapping: bool,
224}
225
226impl SubWindowBuilder {
227	pub(crate) fn from(base_rect: Rect) -> Self {
228		Self {
229			base_rect,
230			children: SingleVec::new(),
231			rects: SingleVec::new(),
232			overlapping: true,
233		}
234	}
235
236	/// Allows overlapping sub-windows
237	/// Enabled by default
238	pub fn enable_overlapping(&mut self) {
239		self.overlapping = true;
240	}
241
242	/// Disallows overlapping sub-windows
243	pub fn disable_overlapping(&mut self) {
244		self.overlapping = false;
245	}
246
247	/// Adds only a new children into the buffer
248	pub fn add_child(&mut self, child: WindowRef) {
249		self.children.push(child);
250	}
251
252	/// Returns [Err] when rects overlap (if overlapping is disabled)
253	pub fn add_rect(&mut self, rect: Rect) -> Result<(), RectsOverlapping> {
254		if self.check_overlapping(rect) {
255			return Err(RectsOverlapping::default());
256		}
257		self.rects.push(rect);
258		Ok(())
259	}
260
261	/// If `rect` is None then [Self::base_rect] will be used
262	/// Returns [Err] when rects overlap (if overlapping is disabled)
263	pub fn add_pair(
264		&mut self,
265		child: WindowRef,
266		rect: Option<Rect>,
267	) -> Result<(), RectsOverlapping> {
268		let rect = rect.unwrap_or(self.base_rect);
269		if self.check_overlapping(rect) {
270			return Err(RectsOverlapping::default());
271		}
272
273		self.children.push(child);
274		self.rects.push(rect);
275		Ok(())
276	}
277
278	#[inline(always)]
279	pub fn base_rect(&self) -> Rect {
280		self.base_rect
281	}
282
283	/// Builds children and their corresponding rects together
284	/// Children and their rects have to be in the same order
285	/// Returns `None` when the children and rects length are unequal
286	pub fn build(self) -> Option<SubWindows> {
287		if self.children.len() != self.rects.len() {
288			return None;
289		}
290
291		Some(SubWindows {
292			children: self.children,
293			rects: self.rects,
294		})
295	}
296
297	fn check_overlapping(&self, rect: Rect) -> bool {
298		if !self.overlapping {
299			for rt in self.rects.iter() {
300				if rt.overlaps(&rect) {
301					return true;
302				}
303			}
304		}
305		false
306	}
307}
308
309//Instead SingleVec<SubWindow>
310#[derive(Default)]
311pub struct SubWindows {
312	children: SingleVec<WindowRef>,
313	rects: SingleVec<Rect>,
314}
315
316impl SubWindows {
317	/// Returns the children/sub-windows of the current window
318	pub fn children(&self) -> &[WindowRef] {
319		&self.children
320	}
321
322	/// This returns the childrens associated rectangles
323	/// The rectangles define the inner area for rendering
324	pub fn rects(&self) -> &[Rect] {
325		&self.rects
326	}
327}
328
329pub type WindowRef = Rc<RefCell<dyn Window>>;
330pub type WindowWeakRef = Weak<RefCell<dyn Window>>;
331
332/// Main trait for any window
333pub trait Window: WindowLayout + HasWindowUID + AsAny {
334	/// The events are forwarded from [crossterm] with the only exception of Resize
335	/// which will have the same dimensions as the resulting canvas
336	/// If a event's handled attributed is set to true it will stop bubbling
337	/// otherwise it will keep bubbling until another Window marks it as handled
338	/// Except for:
339	/// - [Event::FocusGained] and [Event::FocusLost] indicate focus for the specific Window
340	/// - [Event::Resize] is broadcasted
341	fn handle_event(&mut self, event: &mut WindowEvent) {
342		let _ = event;
343	}
344
345	/// Returns all children windows (and layouting [Rect]s)
346	/// Note: When changing the children make sure to also adjust the focus
347	fn children(&mut self, builder: SubWindowBuilder) -> SubWindows {
348		let _ = builder;
349		SubWindows::default()
350	}
351
352	/// Allows [self] to draw into its own [Canvas]
353	/// The canvas size depends on other properties set and the terminal size itself
354	/// It is guarenteed that the size of the [Canvas] is atleast 1x1
355	fn render(&self, canvas: &mut Canvas);
356
357
358	/// Which child of the window has focus
359	/// Is there is None, then ´self´ has focus
360	fn focus(&self) -> Option<WindowRef> {
361		None
362	}
363
364	/// Is [self] enabled?
365	/// Indicated whether or not to handle input and if its focusable
366	/// When `false`, [Window] is still eligible to receive events
367	fn is_enabled(&self) -> bool {
368		true
369	}
370}
371
372
373/// Properties which define how the window is drawn
374pub trait WindowLayout {
375	/// Aligns [self] within its parent [Window]
376	fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
377		(HorizontalAlignment::default(), VerticalAlignment::default())
378	}
379
380	/// This is similar to desired_size but will not return anything and can mutably change [self]
381	/// This is executed *before* [WindowLayout::desired_size]
382	/// Only implement if actually necessary
383	fn desired_size_pre_hook(&mut self, available_size: TPoint) {
384		_ = available_size;
385	}
386
387	/// Parents call this to get the desired size of the child
388	/// This must not exceed `available_size` on any axis
389	/// When the result's product is equal to 0, this window wont be drawn
390	fn desired_size(&self, available_size: TPoint) -> TPoint {
391		available_size
392	}
393
394	fn margin(&self) -> Thickness {
395		Thickness::default()
396	}
397
398	fn border(&self) -> BorderStyle {
399		BorderStyle::default()
400	}
401
402	fn is_visible(&self) -> bool {
403		true
404	}
405}
406
407
408pub trait HasWindowUID {
409	/// Must be static and unique for each object implementing [Window].
410	/// Generate once per instance using [WindowUID::new].
411	fn uid(&self) -> WindowUID;
412}
413
414/// Helper struct for generating the UID
415#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
416pub struct WindowUID(u64);
417
418impl std::fmt::Display for WindowUID {
419	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420		write!(f, "{}", self.0)
421	}
422}
423
424impl Default for WindowUID {
425	fn default() -> Self {
426		Self::new()
427	}
428}
429
430impl WindowUID {
431	/// Generates a new [Window]'s UID
432	pub fn new() -> Self {
433		Self(IdU64::<Self>::new().get())
434	}
435}