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::*;
mod datagrid;
pub use datagrid::*;
mod dockpanel;
pub use dockpanel::*;
mod textblock;
pub use textblock::*;
use arrayvec::ArrayVec;
use vector2d::Vector2D;
use crate::{common::*, window::*};
use std::num::NonZero;
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);
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>);
}
pub trait ItemCollection<T>: Widget {
type Index: Copy;
fn add_item(&mut self, item: T);
fn remove_item(&mut self, item: &T);
fn remove_at(&mut self, index: Self::Index) -> T;
fn get_item(&mut self, index: Self::Index) -> Option<&T>;
fn clear_items(&mut self);
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 {}
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 {
pub const fn state_color(&self, is_enabled: bool) -> Option<Color> {
match is_enabled {
true => None,
false => self.colors.disabled,
}
}
pub 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),
)
}
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 {
pub 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)
}
}
#[derive(PartialEq, 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 }
}
pub fn value(&self) -> &T {
&self.value
}
pub fn display(&self) -> &str {
&self.display
}
}
#[repr(usize)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Radix {
Binary = 2,
Octal = 8,
#[default]
Decimal = 10,
Hexadecimal = 16,
}
impl Radix {
pub const LITERALS: [Grapheme; 16] = [
Grapheme::new_unchecked("0", GlyphWidth::Half),
Grapheme::new_unchecked("1", GlyphWidth::Half),
Grapheme::new_unchecked("2", GlyphWidth::Half),
Grapheme::new_unchecked("3", GlyphWidth::Half),
Grapheme::new_unchecked("4", GlyphWidth::Half),
Grapheme::new_unchecked("5", GlyphWidth::Half),
Grapheme::new_unchecked("6", GlyphWidth::Half),
Grapheme::new_unchecked("7", GlyphWidth::Half),
Grapheme::new_unchecked("8", GlyphWidth::Half),
Grapheme::new_unchecked("9", GlyphWidth::Half),
Grapheme::new_unchecked("A", GlyphWidth::Half),
Grapheme::new_unchecked("B", GlyphWidth::Half),
Grapheme::new_unchecked("C", GlyphWidth::Half),
Grapheme::new_unchecked("D", GlyphWidth::Half),
Grapheme::new_unchecked("E", GlyphWidth::Half),
Grapheme::new_unchecked("F", GlyphWidth::Half),
];
pub const NEGATIVE_INDICATOR: Grapheme = Grapheme::new_unchecked("-", GlyphWidth::Half);
pub const PREFIX_BINARY: [Grapheme; 2] = [
Grapheme::new_unchecked("0", GlyphWidth::Half),
Grapheme::new_unchecked("b", GlyphWidth::Half),
];
pub const PREFIX_OCTAL: [Grapheme; 2] = [
Grapheme::new_unchecked("0", GlyphWidth::Half),
Grapheme::new_unchecked("o", GlyphWidth::Half),
];
pub const PREFIX_HEX: [Grapheme; 2] = [
Grapheme::new_unchecked("0", GlyphWidth::Half),
Grapheme::new_unchecked("x", GlyphWidth::Half),
];
pub fn iter_float(&self, number: f32) -> RenderFloatIterator {
RenderFloatIterator::new(*self, number)
}
pub fn iter_integer(&self, number: i32) -> RenderIntegerIterator {
RenderIntegerIterator::new(*self, number)
}
pub fn first_literal(&self, number: usize) -> Grapheme {
Self::LITERALS[number % *self as usize]
}
pub const fn prefix_glyphs(&self) -> &[Grapheme] {
match *self {
Self::Binary => &Self::PREFIX_BINARY,
Self::Octal => &Self::PREFIX_OCTAL,
Self::Decimal => &[],
Self::Hexadecimal => &Self::PREFIX_HEX,
}
}
}
pub struct RenderFloatIterator {
dot: bool,
fract: f32,
nums: ArrayVec<u8, { f32::MANTISSA_DIGITS as usize }>,
f: f32,
r: Radix,
cnt: u8,
}
impl RenderFloatIterator {
const F_NAN: [Grapheme; 3] = [
Grapheme::new_unchecked("N", GlyphWidth::Half),
Grapheme::new_unchecked("a", GlyphWidth::Half),
Grapheme::new_unchecked("N", GlyphWidth::Half),
];
const F_INF: Grapheme = Grapheme::new_unchecked("∞", GlyphWidth::Half);
const F_DOT: Grapheme = Grapheme::new_unchecked(".", GlyphWidth::Half);
fn new(r: Radix, number: f32) -> Self {
let mut n = number.abs().trunc() as i32;
let mut vec = ArrayVec::new();
while n != 0 {
vec.push((n % r as i32) as u8);
n /= r as i32;
}
Self {
dot: false,
fract: number.fract(),
nums: vec,
f: number,
r,
cnt: 0,
}
}
}
impl Iterator for RenderFloatIterator {
type Item = Grapheme;
fn next(&mut self) -> Option<Self::Item> {
if self.f.is_nan() {
if self.cnt as usize > Self::F_NAN.len() - 1 {
None
}
else {
let v = Self::F_NAN[self.cnt as usize];
self.cnt += 1;
Some(v)
}
}
else if self.f.is_sign_negative() && self.f != 0.0 {
self.f = self.f.abs();
Some(Radix::NEGATIVE_INDICATOR)
}
else if self.f.is_infinite() {
if self.cnt != 0 {
None
}
else {
self.cnt = 1;
Some(Self::F_INF)
}
}
else if self.r != Radix::Decimal && (self.cnt as usize) < self.r.prefix_glyphs().len() {
let v = self.r.prefix_glyphs()[self.cnt as usize];
self.cnt += 1;
Some(v)
}
else {
if !self.nums.is_empty() {
self.nums.pop().map(|v| Radix::LITERALS[v as usize])
}
else if !self.dot {
self.dot = true;
Some(Self::F_DOT)
}
else if self.fract != 0.0 {
let rem = self.fract * self.r as usize as f32;
let v = Radix::LITERALS[rem.trunc() as usize];
self.fract = rem.fract();
Some(v)
}
else {
None
}
}
}
}
pub struct RenderIntegerIterator {
i: i32,
nums: ArrayVec<u8, { i32::BITS as usize }>,
r: Radix,
cnt: u8,
}
impl RenderIntegerIterator {
fn new(r: Radix, number: i32) -> Self {
let mut n = number.abs();
let mut vec = ArrayVec::new();
while n != 0 {
vec.push((n % r as i32) as u8);
n /= r as i32;
}
Self {
i: number,
r,
cnt: 0,
nums: vec,
}
}
}
impl Iterator for RenderIntegerIterator {
type Item = Grapheme;
fn next(&mut self) -> Option<Self::Item> {
if self.i.is_negative() {
self.i = self.i.abs();
Some(Radix::NEGATIVE_INDICATOR)
}
else if self.r != Radix::Decimal && (self.cnt as usize) < self.r.prefix_glyphs().len() {
let v = self.r.prefix_glyphs()[self.cnt as usize];
self.cnt += 1;
Some(v)
}
else {
self.nums.pop().map(|v| Radix::LITERALS[v as usize])
}
}
}
#[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);
}
}