Skip to main content

flux_tui/components/
mod.rs

1mod button;
2pub use button::*;
3
4mod stackpanel;
5pub use stackpanel::*;
6
7mod textbox;
8pub use textbox::*;
9
10mod label;
11pub use label::*;
12
13mod scrollviewer;
14pub use scrollviewer::*;
15
16mod checkbox;
17pub use checkbox::*;
18
19mod combobox;
20pub use combobox::*;
21
22mod tabview;
23pub use tabview::*;
24
25mod numeric;
26pub use numeric::*;
27
28mod datagrid;
29pub use datagrid::*;
30
31mod dockpanel;
32pub use dockpanel::*;
33
34mod textblock;
35pub use textblock::*;
36
37use arrayvec::ArrayVec;
38use vector2d::Vector2D;
39
40use crate::{common::*, window::*};
41use std::num::NonZero;
42
43/// Defines setter functions to the corresponding [Window] functions
44pub trait Widget: WindowLayout + Default {
45	fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment);
46
47	fn set_visibility(&mut self, visibility: bool);
48
49	fn set_width(&mut self, width: Size);
50
51	fn set_height(&mut self, height: Size);
52
53	fn set_width_constraint(&mut self, width: SizeConstraint);
54
55	fn set_height_constraint(&mut self, height: SizeConstraint);
56
57	fn set_margin(&mut self, margin: Thickness);
58
59	fn set_border(&mut self, border: BorderStyle);
60
61	/// Changing the IsEnabled state usually changes the highlight color
62	fn set_enabled_state(&mut self, is_enabled: bool);
63}
64
65pub trait WidgetColors: Widget {
66	fn set_disabled_color(&mut self, color: Option<Color>);
67
68	fn set_base_fg_color(&mut self, color: Option<Color>);
69
70	fn set_base_bg_color(&mut self, color: Option<Color>);
71}
72
73
74/// Exposes an interface for widgets containing an underlying collection
75pub trait ItemCollection<T>: Widget {
76	/// Type used to index the underlying collection
77	type Index: Copy;
78
79	/// Adds an item to the underlying collection
80	fn add_item(&mut self, item: T);
81
82	/// Removes an item from the underlying collection
83	fn remove_item(&mut self, item: &T);
84
85	/// Removes an item at the given index from the underlying collection
86	/// Might panic if index is invalid
87	fn remove_at(&mut self, index: Self::Index) -> T;
88
89	/// Returns an item from the collection by its index
90	fn get_item(&mut self, index: Self::Index) -> Option<&T>;
91
92	/// Clears all items in the underlying collection
93	fn clear_items(&mut self);
94
95	/// Returns the number of items in the underlying collection
96	fn items(&self) -> Self::Index;
97}
98
99#[derive(Default)]
100pub struct ColorBase {
101	pub disabled: Option<Color>,
102	pub base_bg: Option<Color>,
103	pub base_fg: Option<Color>,
104}
105
106impl ColorBase {}
107
108/// Base struct usable for implementations of [Widget]
109pub struct WidgetBase {
110	pub colors: ColorBase,
111	pub visibility: bool,
112	pub size: Vector2D<Size>,
113	pub constraints: Vector2D<SizeConstraint>,
114	pub margin: Thickness,
115	pub border: BorderStyle,
116	pub alignment: (HorizontalAlignment, VerticalAlignment),
117	pub enabled: bool,
118	pub uid: WindowUID,
119}
120
121impl Default for WidgetBase {
122	fn default() -> Self {
123		Self {
124			colors: ColorBase::default(),
125			visibility: true,
126			enabled: true,
127			uid: WindowUID::new(),
128			size: Vector2D::new(Size::default(), Size::default()),
129			constraints: Default::default(),
130			margin: Thickness::default(),
131			border: BorderStyle::default(),
132			alignment: (HorizontalAlignment::default(), VerticalAlignment::default()),
133		}
134	}
135}
136
137impl WidgetBase {
138	/// Returns the color associated to the state
139	pub const fn state_color(&self, is_enabled: bool) -> Option<Color> {
140		match is_enabled {
141			true => None,
142			false => self.colors.disabled,
143		}
144	}
145
146	pub fn desired_size(&self, available_size: TPoint) -> TPoint {
147		Vector2D::new(
148			Self::get_size(available_size.x, self.constraints.x, self.size.x),
149			Self::get_size(available_size.y, self.constraints.y, self.size.y),
150		)
151	}
152
153	fn get_size(available_size: TSize, constraint: SizeConstraint, size: Size) -> TSize {
154		if available_size >= constraint.min.get() as TSize {
155			match size {
156				Size::Relative(Percent::DEFAULT) => constraint.max.get_size(available_size),
157				v => v.get_size(available_size),
158			}
159		}
160		else {
161			TSize::MIN
162		}
163	}
164}
165
166
167#[derive(Debug, Clone, Copy, PartialEq)]
168pub enum Size {
169	Fixed(NonZero<TSize>),
170	Relative(Percent),
171}
172
173impl Default for Size {
174	fn default() -> Self {
175		Self::Relative(Percent::default())
176	}
177}
178
179impl Size {
180	/// This will return size relative to the given size
181	/// If its a percentage it will just get multiplied
182	/// If its a fixed size it will be equal or less than 'relative_to'
183	pub fn get_size(&self, relative_to: TSize) -> TSize {
184		match *self {
185			Size::Fixed(u) => {
186				let size = u.get();
187				if relative_to >= size {
188					size
189				}
190				else {
191					relative_to
192				}
193			}
194			Size::Relative(p) => p.multiply(relative_to),
195		}
196	}
197}
198
199impl From<Percent> for Size {
200	fn from(value: Percent) -> Self {
201		Size::Relative(value)
202	}
203}
204
205
206/// When setting the Layout together there's a strict order on which size is determined over the other.
207/// Min size > Max size > (Preferred) size
208#[derive(PartialEq, Debug, Copy, Clone)]
209pub struct SizeConstraint {
210	pub min: NonZero<TSize>,
211	pub max: Size,
212}
213
214impl Default for SizeConstraint {
215	fn default() -> Self {
216		Self {
217			min: NonZero::<TSize>::MIN,
218			max: Size::Relative(Percent::default()),
219		}
220	}
221}
222
223pub struct DisplayValue<T: PartialEq> {
224	value: T,
225	display: String,
226}
227
228impl<T: PartialEq> Default for DisplayValue<T>
229where T: Default
230{
231	fn default() -> Self {
232		Self {
233			value: T::default(),
234			display: String::default(),
235		}
236	}
237}
238
239impl<T: PartialEq> DisplayValue<T> {
240	pub fn new<S: ToString>(value: T, display: S) -> Self {
241		Self {
242			value,
243			display: display.to_string(),
244		}
245	}
246
247	pub fn from(value: T, display: String) -> Self {
248		Self { value, display }
249	}
250
251	// Returns the stored value of the pair
252	pub fn value(&self) -> &T {
253		&self.value
254	}
255
256	// Returns the display value of the pair
257	pub fn display(&self) -> &str {
258		&self.display
259	}
260}
261
262#[repr(usize)]
263#[derive(Debug, Clone, Copy, PartialEq, Default)]
264pub enum Radix {
265	Binary = 2,
266	Octal = 8,
267	#[default]
268	Decimal = 10,
269	Hexadecimal = 16,
270}
271
272impl Radix {
273	pub const LITERALS: [Grapheme; 16] = [
274		Grapheme::new_unchecked("0", GlyphWidth::Half),
275		Grapheme::new_unchecked("1", GlyphWidth::Half),
276		Grapheme::new_unchecked("2", GlyphWidth::Half),
277		Grapheme::new_unchecked("3", GlyphWidth::Half),
278		Grapheme::new_unchecked("4", GlyphWidth::Half),
279		Grapheme::new_unchecked("5", GlyphWidth::Half),
280		Grapheme::new_unchecked("6", GlyphWidth::Half),
281		Grapheme::new_unchecked("7", GlyphWidth::Half),
282		Grapheme::new_unchecked("8", GlyphWidth::Half),
283		Grapheme::new_unchecked("9", GlyphWidth::Half),
284		Grapheme::new_unchecked("A", GlyphWidth::Half),
285		Grapheme::new_unchecked("B", GlyphWidth::Half),
286		Grapheme::new_unchecked("C", GlyphWidth::Half),
287		Grapheme::new_unchecked("D", GlyphWidth::Half),
288		Grapheme::new_unchecked("E", GlyphWidth::Half),
289		Grapheme::new_unchecked("F", GlyphWidth::Half),
290	];
291	pub const NEGATIVE_INDICATOR: Grapheme = Grapheme::new_unchecked("-", GlyphWidth::Half);
292	pub const PREFIX_BINARY: [Grapheme; 2] = [
293		Grapheme::new_unchecked("0", GlyphWidth::Half),
294		Grapheme::new_unchecked("b", GlyphWidth::Half),
295	];
296	pub const PREFIX_OCTAL: [Grapheme; 2] = [
297		Grapheme::new_unchecked("0", GlyphWidth::Half),
298		Grapheme::new_unchecked("o", GlyphWidth::Half),
299	];
300	pub const PREFIX_HEX: [Grapheme; 2] = [
301		Grapheme::new_unchecked("0", GlyphWidth::Half),
302		Grapheme::new_unchecked("x", GlyphWidth::Half),
303	];
304
305	pub fn iter_float(&self, number: f32) -> RenderFloatIterator {
306		RenderFloatIterator::new(*self, number)
307	}
308
309	pub fn iter_integer(&self, number: i32) -> RenderIntegerIterator {
310		RenderIntegerIterator::new(*self, number)
311	}
312
313	pub fn first_literal(&self, number: usize) -> Grapheme {
314		Self::LITERALS[number % *self as usize]
315	}
316
317	pub const fn prefix_glyphs(&self) -> &[Grapheme] {
318		match *self {
319			Self::Binary => &Self::PREFIX_BINARY,
320			Self::Octal => &Self::PREFIX_OCTAL,
321			Self::Decimal => &[],
322			Self::Hexadecimal => &Self::PREFIX_HEX,
323		}
324	}
325}
326
327pub struct RenderFloatIterator {
328	dot: bool,
329	fract: f32,
330	nums: ArrayVec<u8, { f32::MANTISSA_DIGITS as usize }>,
331	f: f32,
332	r: Radix,
333	cnt: u8,
334}
335
336impl RenderFloatIterator {
337	const F_NAN: [Grapheme; 3] = [
338		Grapheme::new_unchecked("N", GlyphWidth::Half),
339		Grapheme::new_unchecked("a", GlyphWidth::Half),
340		Grapheme::new_unchecked("N", GlyphWidth::Half),
341	];
342	const F_INF: Grapheme = Grapheme::new_unchecked("∞", GlyphWidth::Half);
343	const F_DOT: Grapheme = Grapheme::new_unchecked(".", GlyphWidth::Half);
344
345	fn new(r: Radix, number: f32) -> Self {
346		let mut n = number.abs().trunc() as i32;
347		let mut vec = ArrayVec::new();
348		while n != 0 {
349			vec.push((n % r as i32) as u8);
350			n /= r as i32;
351		}
352		Self {
353			dot: false,
354			fract: number.fract(),
355			nums: vec,
356			f: number,
357			r,
358			cnt: 0,
359		}
360	}
361}
362
363impl Iterator for RenderFloatIterator {
364	type Item = Grapheme;
365
366	fn next(&mut self) -> Option<Self::Item> {
367		if self.f.is_nan() {
368			if self.cnt as usize > Self::F_NAN.len() - 1 {
369				None
370			}
371			else {
372				let v = Self::F_NAN[self.cnt as usize];
373				self.cnt += 1;
374				Some(v)
375			}
376		}
377		else if self.f.is_sign_negative() && self.f != 0.0 {
378			self.f = self.f.abs();
379			Some(Radix::NEGATIVE_INDICATOR)
380		}
381		else if self.f.is_infinite() {
382			if self.cnt != 0 {
383				None
384			}
385			else {
386				self.cnt = 1;
387				Some(Self::F_INF)
388			}
389		}
390		else if self.r != Radix::Decimal && (self.cnt as usize) < self.r.prefix_glyphs().len() {
391			let v = self.r.prefix_glyphs()[self.cnt as usize];
392			self.cnt += 1;
393			Some(v)
394		}
395		else {
396			if !self.nums.is_empty() {
397				self.nums.pop().map(|v| Radix::LITERALS[v as usize])
398			}
399			else if !self.dot {
400				self.dot = true;
401				Some(Self::F_DOT)
402			}
403			else if self.fract != 0.0 {
404				let rem = self.fract * self.r as usize as f32;
405				let v = Radix::LITERALS[rem.trunc() as usize];
406				self.fract = rem.fract();
407				Some(v)
408			}
409			else {
410				None
411			}
412		}
413	}
414}
415
416pub struct RenderIntegerIterator {
417	i: i32,
418	nums: ArrayVec<u8, { i32::BITS as usize }>,
419	r: Radix,
420	cnt: u8,
421}
422
423impl RenderIntegerIterator {
424	fn new(r: Radix, number: i32) -> Self {
425		let mut n = number.abs();
426		let mut vec = ArrayVec::new();
427		while n != 0 {
428			vec.push((n % r as i32) as u8);
429			n /= r as i32;
430		}
431		Self {
432			i: number,
433			r,
434			cnt: 0,
435			nums: vec,
436		}
437	}
438}
439
440impl Iterator for RenderIntegerIterator {
441	type Item = Grapheme;
442
443	fn next(&mut self) -> Option<Self::Item> {
444		if self.i.is_negative() {
445			self.i = self.i.abs();
446			Some(Radix::NEGATIVE_INDICATOR)
447		}
448		else if self.r != Radix::Decimal && (self.cnt as usize) < self.r.prefix_glyphs().len() {
449			let v = self.r.prefix_glyphs()[self.cnt as usize];
450			self.cnt += 1;
451			Some(v)
452		}
453		else {
454			self.nums.pop().map(|v| Radix::LITERALS[v as usize])
455		}
456	}
457}
458
459#[cfg(test)]
460mod tests {
461	use super::*;
462
463	#[test]
464	fn size_fixed() {
465		let value = NonZero::new(20).unwrap();
466		let fixed = Size::Fixed(value);
467
468		assert_eq!(fixed.get_size(25), value.get());
469		assert_eq!(fixed.get_size(value.get()), value.get());
470		assert_eq!(fixed.get_size(12), 12);
471	}
472
473	#[test]
474	fn size_relative() {
475		let value = 25;
476		let rel = Size::Relative(Percent::from_int(25));
477
478		assert_eq!(rel.get_size(100), value);
479		assert_eq!(rel.get_size(200), value * 2);
480		assert_eq!(rel.get_size(50), value / 2);
481		assert_eq!(rel.get_size(0), 0);
482	}
483}