flux-tui 0.4.5

Fast and lightweight Terminal UI drawing library
Documentation
mod button;
pub use button::*;

mod stackpanel;
pub use stackpanel::*;

mod textbox;
pub use textbox::*;

mod label;
pub use label::*;

mod scrollviewer;
pub use scrollviewer::*;

mod checkbox;
pub use checkbox::*;

mod combobox;
pub use combobox::*;

mod tabview;
pub use tabview::*;

mod numeric;
pub use numeric::*;

/*TODO: Next version
mod datagrid;
pub use datagrid::*;

mod listview;
pub use listview::*;
*/

mod dockpanel;
pub use dockpanel::*;

mod textblock;
pub use textblock::*;

use vector2d::Vector2D;

use crate::{common::*, window::*};
use std::num::NonZero;

/// Defines setter functions to the corresponding [Window] functions
pub trait Widget: WindowLayout + Default {
	fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment);

	fn set_visibility(&mut self, visibility: bool);

	fn set_width(&mut self, width: Size);

	fn set_height(&mut self, height: Size);

	fn set_width_constraint(&mut self, width: SizeConstraint);

	fn set_height_constraint(&mut self, height: SizeConstraint);

	fn set_margin(&mut self, margin: Thickness);

	fn set_border(&mut self, border: BorderStyle);

	/// Changing the IsEnabled state usually changes the highlight color
	fn set_enabled_state(&mut self, is_enabled: bool);
}

pub trait WidgetColors: Widget {
	fn set_disabled_color(&mut self, color: Option<Color>);

	fn set_base_fg_color(&mut self, color: Option<Color>);

	fn set_base_bg_color(&mut self, color: Option<Color>);
}


/// Exposes an interface for widgets containing an underlying collection
pub trait ItemCollection<T>: Widget {
	/// Type used to index the underlying collection
	type Index: Copy;

	/// Adds an item to the underlying collection
	fn add_item(&mut self, item: T);

	/// Removes an item from the underlying collection
	fn remove_item(&mut self, item: &T);

	/// Removes an item at the given index from the underlying collection
	/// Might panic if index is invalid
	fn remove_at(&mut self, index: Self::Index) -> T;

	/// Returns an item from the collection by its index
	fn get_item(&mut self, index: Self::Index) -> Option<&T>;

	/// Clears all items in the underlying collection
	fn clear_items(&mut self);

	/// Returns the number of items in the underlying collection
	fn items(&self) -> Self::Index;
}

#[derive(Default)]
pub struct ColorBase {
	pub disabled: Option<Color>,
	pub base_bg: Option<Color>,
	pub base_fg: Option<Color>,
}

impl ColorBase {}

/// Base struct usable for implementations of [Widget]
pub struct WidgetBase {
	pub colors: ColorBase,
	pub visibility: bool,
	pub size: Vector2D<Size>,
	pub constraints: Vector2D<SizeConstraint>,
	pub margin: Thickness,
	pub border: BorderStyle,
	pub alignment: (HorizontalAlignment, VerticalAlignment),
	pub enabled: bool,
	pub uid: WindowUID,
}

impl Default for WidgetBase {
	fn default() -> Self {
		Self {
			colors: ColorBase::default(),
			visibility: true,
			enabled: true,
			uid: WindowUID::new(),
			size: Vector2D::new(Size::default(), Size::default()),
			constraints: Default::default(),
			margin: Thickness::default(),
			border: BorderStyle::default(),
			alignment: (HorizontalAlignment::default(), VerticalAlignment::default()),
		}
	}
}

impl WidgetBase {
	/// Returns the color associated to the state
	pub const fn state_color(&self, is_enabled: bool) -> Option<Color> {
		match is_enabled {
			true => None,
			false => self.colors.disabled,
		}
	}

	pub const fn desired_size(&self, available_size: TPoint) -> TPoint {
		Vector2D::new(
			Self::get_size(available_size.x, self.constraints.x, self.size.x),
			Self::get_size(available_size.y, self.constraints.y, self.size.y),
		)
	}

	const fn get_size(available_size: TSize, constraint: SizeConstraint, size: Size) -> TSize {
		if available_size >= constraint.min.get() as TSize {
			match size {
				Size::Relative(Percent::DEFAULT) => constraint.max.get_size(available_size),
				v => v.get_size(available_size),
			}
		}
		else {
			TSize::MIN
		}
	}
}


#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Size {
	Fixed(NonZero<TSize>),
	Relative(Percent),
}

impl Default for Size {
	fn default() -> Self {
		Self::Relative(Percent::default())
	}
}

impl Size {
	/// This will return size relative to the given size
	/// If its a percentage it will just get multiplied
	/// If its a fixed size it will be equal or less than 'relative_to'
	pub const fn get_size(&self, relative_to: TSize) -> TSize {
		match *self {
			Size::Fixed(u) => {
				let size = u.get();
				if relative_to >= size {
					size
				}
				else {
					relative_to
				}
			}
			Size::Relative(p) => p.multiply(relative_to),
		}
	}
}

impl From<Percent> for Size {
	fn from(value: Percent) -> Self {
		Size::Relative(value)
	}
}


/// When setting the Layout together there's a strict order on which size is determined over the other.
/// Min size > Max size > (Preferred) size
#[derive(Debug, Copy, Clone)]
pub struct SizeConstraint {
	pub min: NonZero<TSize>,
	pub max: Size,
}

impl Default for SizeConstraint {
	fn default() -> Self {
		Self {
			min: NonZero::<TSize>::MIN,
			max: Size::Relative(Percent::default()),
		}
	}
}

pub struct DisplayValue<T: PartialEq> {
	value: T,
	display: String,
}

impl<T: PartialEq> Default for DisplayValue<T>
where T: Default
{
	fn default() -> Self {
		Self {
			value: T::default(),
			display: String::default(),
		}
	}
}

impl<T: PartialEq> DisplayValue<T> {
	pub fn new<S: ToString>(value: T, display: S) -> Self {
		Self {
			value,
			display: display.to_string(),
		}
	}

	pub fn from(value: T, display: String) -> Self {
		Self { value, display }
	}

	// Returns the stored value of the pair
	pub fn value(&self) -> &T {
		&self.value
	}

	// Returns the display value of the pair
	pub fn display(&self) -> &str {
		&self.display
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn size_fixed() {
		let value = NonZero::new(20).unwrap();
		let fixed = Size::Fixed(value);

		assert_eq!(fixed.get_size(25), value.get());
		assert_eq!(fixed.get_size(value.get()), value.get());
		assert_eq!(fixed.get_size(12), 12);
	}

	#[test]
	fn size_relative() {
		let value = 25;
		let rel = Size::Relative(Percent::from_int(25));

		assert_eq!(rel.get_size(100), value);
		assert_eq!(rel.get_size(200), value * 2);
		assert_eq!(rel.get_size(50), value / 2);
		assert_eq!(rel.get_size(0), 0);
	}
}