1use std::error::Error;
2use std::fmt::{Debug, Display, Formatter};
3use std::io::{BufWriter, Stdout};
4use std::num::NonZero;
5
6use arrayvec::ArrayString;
7use bitflags::bitflags;
8use crossterm::QueueableCommand;
9use crossterm::style::{self, Attribute};
10use unicode_segmentation::UnicodeSegmentation;
11use unicode_width::UnicodeWidthStr;
12use vector2d::Vector2D;
13
14pub use arrayvec_const as arrayvec;
15pub use as_any;
16pub use crossterm;
17pub use vector2d;
18
19pub use crossterm::event::Event;
20pub use crossterm::style::Color;
21
22pub type TSize = u16;
24pub type TPoint = Vector2D<TSize>;
26
27#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum CharDirection {
30 LeftRight,
32 RightLeft,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq)]
39pub enum Alignment {
40 LowerBound,
41 Center,
42 HigherBound,
43}
44
45impl From<Alignment> for HorizontalAlignment {
46 fn from(value: Alignment) -> Self {
47 match value {
48 Alignment::LowerBound => Self::Left,
49 Alignment::Center => Self::Center,
50 Alignment::HigherBound => Self::Right,
51 }
52 }
53}
54
55impl From<Alignment> for VerticalAlignment {
56 fn from(value: Alignment) -> Self {
57 match value {
58 Alignment::LowerBound => Self::Top,
59 Alignment::Center => Self::Center,
60 Alignment::HigherBound => Self::Bottom,
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Default)]
67pub enum HorizontalAlignment {
68 Left,
69 #[default]
70 Center,
71 Right,
72}
73
74impl From<HorizontalAlignment> for Alignment {
75 fn from(value: HorizontalAlignment) -> Self {
76 match value {
77 HorizontalAlignment::Left => Self::HigherBound,
78 HorizontalAlignment::Center => Self::Center,
79 HorizontalAlignment::Right => Self::HigherBound,
80 }
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Default)]
86pub enum VerticalAlignment {
87 Top,
88 #[default]
89 Center,
90 Bottom,
91}
92
93impl From<VerticalAlignment> for Alignment {
94 fn from(value: VerticalAlignment) -> Self {
95 match value {
96 VerticalAlignment::Top => Self::HigherBound,
97 VerticalAlignment::Center => Self::Center,
98 VerticalAlignment::Bottom => Self::HigherBound,
99 }
100 }
101}
102
103#[derive(Debug, Copy, Clone, PartialEq)]
105#[repr(usize)]
106pub enum Orientation {
107 Horizontal = 0,
108 Vertical,
109}
110
111impl Orientation {
112 const INV_MAP: [Orientation; 2] = [Self::Vertical, Self::Horizontal];
113
114 pub fn invert(&self) -> Self {
115 Self::INV_MAP[*self as usize]
116 }
117}
118
119#[derive(Debug, Default, Copy, Clone, PartialEq)]
120pub struct Line2D<T> {
121 pub a: Vector2D<T>,
122 pub b: Vector2D<T>,
123}
124
125impl Line2D<TSize> {
126 pub const fn new(x1: TSize, y1: TSize, x2: TSize, y2: TSize) -> Self {
127 Self {
128 a: Vector2D::new(x1, y1),
129 b: Vector2D::new(x2, y2),
130 }
131 }
132
133 pub const fn from(p1: TPoint, p2: TPoint) -> Self {
134 Self { a: p1, b: p2 }
135 }
136
137 pub const fn is_vertical(&self) -> bool {
138 self.a.x == self.b.x
139 }
140
141 pub const fn is_horizontal(&self) -> bool {
142 self.a.y == self.b.y
143 }
144
145 pub const fn is_ascending(&self) -> bool {
146 self.a.y < self.b.y
147 }
148
149 pub const fn is_descending(&self) -> bool {
150 self.a.y > self.b.y
151 }
152
153 pub const fn is_constant(&self) -> bool {
154 self.a.y == self.b.y
155 }
156
157 pub const fn iter_points(&self) -> PointIterator<Line2D<TSize>> {
159 PointIterator::<Line2D<TSize>>::new(*self)
160 }
161}
162
163pub struct PointIterator<T: Sized> {
164 line: T,
165 cursor: TSize,
166}
167
168impl Iterator for PointIterator<Line2D<TSize>> {
169 type Item = TPoint;
170
171 fn next(&mut self) -> Option<Self::Item> {
172 let mut ret = None;
173 if self.line.is_vertical() {
174 if self.cursor < self.line.b.y {
175 ret = Some(Vector2D::new(self.line.a.y, self.cursor));
176 self.cursor += 1;
177 }
178 }
179 else if self.cursor < self.line.b.x {
180 let div = self.line.b.x - self.line.a.x;
181 let mut m = TSize::MIN;
182 if div != TSize::MIN {
183 m = (self.line.b.y - self.line.a.y) / div;
184 }
185 let b = self.line.a.y - self.line.a.x * m;
186 ret = Some(Vector2D::new(self.cursor, m * self.cursor + b));
187
188 self.cursor += 1;
189 }
190 ret
191 }
192}
193
194impl PointIterator<Line2D<TSize>> {
195 const fn new(line: Line2D<TSize>) -> Self {
196 if line.is_vertical() {
197 Self {
198 line,
199 cursor: line.a.y,
200 }
201 }
202 else {
203 Self {
204 line,
205 cursor: line.a.x,
206 }
207 }
208 }
209}
210
211
212#[repr(transparent)]
214#[derive(Debug, Clone, Copy, PartialEq)]
215pub struct Percent(NonZero<u8>);
216
217impl Default for Percent {
218 fn default() -> Self {
219 Self::DEFAULT
220 }
221}
222
223impl Percent {
224 pub const MIN: Self = Self(NonZero::<u8>::MIN);
225 pub const MAX: Self = Self(NonZero::new(100).unwrap());
226 pub const DEFAULT: Self = Self::MAX;
227
228 pub const fn from_int(u: u8) -> Self {
230 if u == 0 {
231 Self::MIN
232 }
233 else if u >= 100 {
234 Self::MAX
235 }
236 else {
237 Self(NonZero::new(u).unwrap())
238 }
239 }
240
241 pub const fn from_float(f: f32) -> Self {
243 if !f.is_normal() {
244 return Self::DEFAULT;
245 }
246 let u = (f * 100.) as u8;
247 if u == 0 {
248 Self::MIN
249 }
250 else if u >= 100 {
251 Self::MAX
252 }
253 else {
254 Self(NonZero::new(u).unwrap())
255 }
256 }
257
258 pub const fn multiply(self, u: TSize) -> TSize {
260 (u * self.0.get() as TSize) / 100
261 }
262
263 pub const fn value(self) -> u8 {
265 self.0.get()
266 }
267}
268
269
270#[derive(Copy, Clone, Debug, PartialEq)]
272#[repr(usize)]
273pub enum GlyphWidth {
274 Half = 1,
275 Full = 2,
276}
277
278impl TryFrom<usize> for GlyphWidth {
279 type Error = GraphemeError;
280
281 fn try_from(value: usize) -> Result<Self, GraphemeError> {
282 match value {
283 1 => Ok(GlyphWidth::Half),
284 2 => Ok(GlyphWidth::Full),
285 _ => Err(GraphemeError::ConversionError),
286 }
287 }
288}
289
290impl Default for GlyphWidth {
291 fn default() -> Self {
292 Self::Half
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq)]
297pub struct Grapheme {
299 inner: ArrayString<{ Self::MAX_SIZE }>,
300 width: GlyphWidth,
301}
302
303impl Default for Grapheme {
304 fn default() -> Self {
305 Self::PLACEHOLDER
306 }
307}
308
309impl Grapheme {
310 pub const MAX_SIZE: usize = 16;
311 pub const PLACEHOLDER: Self = Self::new_unchecked(" ", GlyphWidth::Half);
312 pub const REPLACEMENT: Self = Self::new_unchecked("\u{FFFD}", GlyphWidth::Half);
313
314 pub(crate) const fn new_unchecked(grapheme: &'static str, width: GlyphWidth) -> Self {
318 if grapheme.len() > Self::MAX_SIZE {
319 panic!(stringify!(GraphemeError::GraphemeTooBig));
320 }
321 if grapheme.as_bytes()[0] < 32 {
322 panic!(stringify!(GraphemeError::InvalidGlyphWidth));
323 }
324 match ArrayString::from(grapheme) {
326 Ok(v) => Self { width, inner: v },
327 Err(_) => panic!("Error creating ArrayString"),
328 }
329 }
330
331 pub fn from(grapheme: &str) -> Result<Self, GraphemeError> {
338 if grapheme.len() > Self::MAX_SIZE {
339 return Err(GraphemeError::GraphemeTooBig);
340 }
341
342 let mut graphemes = grapheme.graphemes(true);
343 let _ = graphemes.next();
344 let g2 = graphemes.next();
345
346 if g2.is_some() {
347 return Err(GraphemeError::TooManyGraphemes);
348 }
349
350 let width = grapheme.width();
351 if grapheme.as_bytes()[0] < 32 || width == 0 {
352 return Err(GraphemeError::InvalidGlyphWidth);
353 }
354
355 Ok(Self {
356 inner: ArrayString::from(grapheme).unwrap(),
357 width: GlyphWidth::try_from(width).unwrap(),
358 })
359 }
360
361 #[inline(always)]
362 pub(crate) fn get_string(&self) -> ArrayString<{ Self::MAX_SIZE }> {
363 self.inner
364 }
365
366 #[inline(always)]
367 pub fn as_str(&self) -> &str {
368 &self.inner
369 }
370
371 #[inline(always)]
372 pub fn width(&self) -> GlyphWidth {
373 self.width
374 }
375}
376
377#[derive(Debug, Copy, Clone, PartialEq)]
378pub enum GraphemeError {
379 GraphemeTooBig,
380 TooManyGraphemes,
381 InvalidGlyphWidth,
382 ConversionError,
383}
384
385impl Display for GraphemeError {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 f.write_fmt(format_args!("{self:?}"))
388 }
389}
390
391impl Error for GraphemeError {}
392
393bitflags! {
394 #[derive(Debug, Copy, Clone, PartialEq, Default, PartialOrd, Hash)]
397 pub struct Style: u8{
398
399 const None = 0b0000_0000;
401 const ResetBefore = 0b0000_0001;
403 const ResetAfter = 0b0000_0010;
406 const Bold = 0b0000_0100;
408 const NoBold = 0b0000_1000;
410 const Underline = 0b0001_0000;
412 const NoUnderline = 0b0010_0000;
414 const Reverse = 0b0100_0000;
416 const NoReverse = 0b1000_0000;
418 }
419}
420
421impl Style {
422 const ATTRIBUTES: [Attribute; 8] = [
423 Attribute::Reset,
425 Attribute::Reset,
427 Attribute::Bold,
428 Attribute::NoBold,
429 Attribute::Underlined,
430 Attribute::NoUnderline,
431 Attribute::Reverse,
432 Attribute::NoReverse,
433 ];
434
435 #[inline(always)]
436 pub(crate) fn apply_pre_styles(
437 stdout: &mut BufWriter<Stdout>,
438 flags: Style,
439 ) -> Result<(), std::io::Error> {
440 for idx in [0, 2, 3, 4, 5, 6, 7] {
441 if flags.bits() & (1 << idx) != 0 {
442 stdout.queue(style::SetAttribute(Self::ATTRIBUTES[idx]))?;
443 }
444 }
445 Ok(())
446 }
447
448 #[inline(always)]
449 pub(crate) fn apply_post_styles(
450 stdout: &mut BufWriter<Stdout>,
451 flags: Style,
452 ) -> Result<(), std::io::Error> {
453 const INDEX: u8 = Style::ResetAfter.flag_index();
454 if flags.bits() & (1 << INDEX) != 0 {
455 stdout.queue(style::SetAttribute(Self::ATTRIBUTES[INDEX as usize]))?;
456 }
457 Ok(())
458 }
459
460 #[inline(always)]
461 const fn flag_index(self) -> u8 {
462 let mut n = u8::MIN;
463 while (self.bits() >> (n + 1)) != 0 {
464 n += 1;
465 }
466 n
467 }
468
469 #[inline(always)]
471 pub fn when(self, when: bool) -> Self {
472 Self::from_bits_retain(self.bits() * when as u8)
473 }
474}
475
476#[derive(Debug, Copy, Clone, PartialEq)]
478pub(crate) struct Glyph {
479 pub style: Style,
480 pub bg: Option<Color>,
481 pub fg: Option<Color>,
482 pub grapheme: ArrayString<{ Grapheme::MAX_SIZE }>,
483}
484
485impl Default for Glyph {
486 fn default() -> Self {
487 Self {
488 style: Style::default(),
489 bg: None,
490 fg: None,
491 grapheme: ArrayString::from(Grapheme::PLACEHOLDER.as_str()).unwrap(),
492 }
493 }
494}
495
496impl Glyph {
497 pub const NULL: &'static str = "\0";
498
499 pub fn nullify(&mut self) {
500 self.grapheme.clear();
501 self.grapheme.push_str(Self::NULL);
502 }
503
504 pub fn is_null(&self) -> bool {
505 self.grapheme.as_str() == Self::NULL
506 }
507}
508
509
510pub enum RectError {
511 HorizontalBorderExceeds(Rect, Rect),
512 VerticalBorderExceeds(Rect, Rect),
513}
514
515impl Debug for RectError {
516 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
517 match self {
518 Self::HorizontalBorderExceeds(rect, base) => write!(
519 f,
520 "Rect ({rect:?}) exceeds the base rect's ({base:?}) right border."
521 ),
522 Self::VerticalBorderExceeds(rect, base) => write!(
523 f,
524 "Rect ({rect:?}) exceeds the base rect's ({base:?}) right border."
525 ),
526 }
527 }
528}
529
530impl Display for RectError {
531 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
532 write!(f, "{self:?}")
533 }
534}
535
536#[derive(Debug, Copy, Clone, PartialEq)]
538pub struct Rect {
539 pub(crate) start: TPoint,
540 pub(crate) size: TPoint,
541}
542
543impl Rect {
544 pub const fn subrect(
547 &self,
548 x: TSize,
549 y: TSize,
550 width: TSize,
551 height: TSize,
552 ) -> Result<Rect, RectError> {
553 let abs = Rect {
554 start: Vector2D::new(self.start.x + x, self.start.y + y),
555 size: Vector2D::new(width, height),
556 };
557 let base_end = self.end();
558 let end = abs.end();
559 if end.x > base_end.x {
560 return Err(RectError::HorizontalBorderExceeds(*self, abs));
561 }
562 if end.y > base_end.y {
563 return Err(RectError::VerticalBorderExceeds(*self, abs));
564 }
565
566 Ok(abs)
567 }
568
569 pub fn subrect2(&self, offset: TPoint, size: TPoint) -> Result<Rect, RectError> {
571 let abs = Rect {
572 start: self.start + offset,
573 size,
574 };
575 let base_end = self.end();
576 let end = abs.end();
577 if end.x > base_end.x {
578 return Err(RectError::HorizontalBorderExceeds(*self, abs));
579 }
580 if end.y > base_end.y {
581 return Err(RectError::VerticalBorderExceeds(*self, abs));
582 }
583
584 Ok(abs)
585 }
586
587 pub(crate) fn from(width: TSize, height: TSize) -> Self {
589 Self {
590 start: Vector2D::new(TSize::MIN, TSize::MIN),
591 size: Vector2D::new(width, height),
592 }
593 }
594
595 pub const fn start(&self) -> TPoint {
597 self.start
598 }
599
600 pub const fn size(&self) -> TPoint {
602 self.size
603 }
604
605 pub const fn end(&self) -> TPoint {
607 Vector2D::new(self.start.x + self.size.x, self.start.y + self.size.y)
608 }
609
610 pub fn overlaps(&self, other: &Self) -> bool {
612 let end1 = self.end();
613 let end2 = other.end();
614 let x_overlap = self.start.x < end2.x
615 && end1.x > other.start.x
616 && self.start.y < end2.y
617 && end1.y > other.start.y;
618 let y_overlap = self.start.x < end2.x
619 && end1.x > other.start.x
620 && self.start.y > end2.y
621 && end1.y < other.start.y;
622 x_overlap || y_overlap
623 }
624
625 pub fn split(
631 &self,
632 orientation: Orientation,
633 ratio: Percent,
634 mut padding: TSize,
635 ) -> Result<SplitRect, RectError> {
636 let min_base_base = self.size[orientation.invert() as usize].saturating_sub(2);
637 if padding >= min_base_base {
638 padding = min_base_base;
639 }
640 eprintln!("padding: {padding}");
641
642 match orientation {
643 Orientation::Horizontal => {
644 let y = self.size().y - padding;
645 let subsize = (ratio.value() as TSize * y).div_ceil(100);
646 let padding_area = match padding {
647 0 => None,
648 v => Some(self.subrect(0, subsize, self.size().x, v)?),
649 };
650
651 Ok(SplitRect {
652 rects: (
653 self.subrect(0, 0, self.size().x, subsize)?,
654 self.subrect(0, subsize + padding, self.size().x, y - subsize)?,
655 ),
656 padding_area,
657 })
658 }
659 Orientation::Vertical => {
660 let x = self.size().x - padding;
661 let subsize = (ratio.value() as TSize * x).div_ceil(100);
662 let padding_area = match padding {
663 0 => None,
664 v => Some(self.subrect(subsize, 0, v, self.size().y)?),
665 };
666 Ok(SplitRect {
667 rects: (
668 self.subrect(0, 0, subsize, self.size().y)?,
669 self.subrect(subsize + padding, 0, x - subsize, self.size().y)?,
670 ),
671 padding_area,
672 })
673 }
674 }
675 }
676
677
678 pub const fn area(&self) -> TSize {
680 self.size.x * self.size.y
681 }
682}
683
684#[derive(Debug)]
685pub struct SplitRect {
686 pub rects: (Rect, Rect),
687 pub padding_area: Option<Rect>,
689}
690
691#[cfg(test)]
692mod test {
693 use super::*;
694
695 #[test]
696 fn grapheme_invalid_chars() {
697 for c in 0..32 {
698 assert_eq!(
699 Grapheme::from(&char::from_u32(c).unwrap().to_string()),
700 Err(GraphemeError::InvalidGlyphWidth)
701 );
702 }
703 }
704
705 #[test]
706 fn grapheme_check_count() {
707 assert_eq!(Grapheme::from("abc"), Err(GraphemeError::TooManyGraphemes));
708 assert_eq!(Grapheme::from("ʤĵ"), Err(GraphemeError::TooManyGraphemes));
709 }
710
711 #[test]
712 fn grapheme_check_size() {
713 assert_eq!(Grapheme::from("🤦🏻♂️"), Err(GraphemeError::GraphemeTooBig));
714 }
715
716 #[test]
717 fn percent_int() {
718 assert_eq!(Percent::from_int(0), Percent::from_int(1));
719 assert_eq!(Percent::from_int(101), Percent::from_int(100));
720 for v in 1..=100 {
721 assert_eq!(Percent::from_int(v).value(), v);
722 }
723 }
724
725 #[test]
726 fn percent_float() {
727 assert_eq!(Percent::from_float(0.001), Percent::from_int(1));
728 assert_eq!(Percent::from_float(1.1), Percent::from_int(100));
729 assert_eq!(Percent::from_float(f32::INFINITY), Percent::from_int(100));
730 let mut v = 0.01;
731 while v <= 1. {
732 assert_eq!(Percent::from_float(v).value(), (v * 100.) as u8);
733 v += 0.01;
734 }
735 }
736
737 mod line {
738 use super::*;
739
740 #[test]
741 fn check_ctor() {
742 assert_eq!(
743 Line2D::from(Vector2D::new(0, 4), Vector2D::new(2, 1)),
744 Line2D::new(0, 4, 2, 1)
745 );
746 }
747
748 #[test]
749 fn check_iter() {
750 let f1 = |x: TSize| 2 * x + 4;
751 let l1 = Line2D::new(0, 4, 16, 4);
752 let l2 = Line2D::new(2, 4, 2, 20);
753 let l3 = Line2D::new(0, f1(0), 12, f1(12));
754
755 for (v, exp) in l1.iter_points().zip(0..16) {
756 assert_eq!(v.x, exp);
757 }
758
759 for (v, exp) in l2.iter_points().zip(4..20) {
760 assert_eq!(v.y, exp);
761 }
762
763 for v in l3.iter_points() {
764 assert_eq!(v.y, f1(v.x));
765 }
766 }
767
768 #[test]
769 fn check_linear_properties() {
770 let l1 = Line2D::new(0, 4, 2, 1);
771 let l2 = Line2D::new(2, 1, 4, 5);
772 assert!(l1.is_descending());
773 assert!(l2.is_ascending());
774 assert!(!l1.is_vertical());
775 assert!(!l2.is_horizontal());
776
777 let l3 = Line2D::new(2, 4, 2, 12);
778 let l4 = Line2D::new(1, 2, 12, 2);
779 assert!(l3.is_vertical());
780 assert!(l4.is_horizontal());
781 assert!(l4.is_constant());
782 }
783 }
784
785 #[test]
786 fn rect_check() {
787 let rect = Rect::from(100, 100);
788 let subrect = rect.subrect(20, 20, 50, 50).unwrap();
789 assert_ne!(rect.start(), subrect.start());
790 assert_ne!(rect.end(), subrect.end());
791 assert_ne!(rect.area(), subrect.area());
792 assert!(subrect.overlaps(&rect));
793 assert!(!subrect.overlaps(&Rect::from(20, 20)));
794 assert!(subrect.overlaps(&Rect::from(21, 21)));
795 assert!(!subrect.overlaps(&Rect::from(20, 21)));
796 assert!(!subrect.overlaps(&Rect::from(21, 20)));
797 assert!(!subrect.overlaps(&rect.subrect(70, 70, 20, 20).unwrap()));
798 assert!(subrect.overlaps(&rect.subrect(69, 69, 20, 20).unwrap()));
799 assert!(!subrect.overlaps(&rect.subrect(70, 69, 20, 20).unwrap()));
800 assert!(!subrect.overlaps(&rect.subrect(69, 70, 20, 20).unwrap()));
801 let subsubrect = subrect.subrect(10, 10, 20, 20).unwrap();
802 assert_eq!(subsubrect.start(), subrect.start() + Vector2D::new(10, 10));
803 assert!(
804 subrect
805 .subrect(0, 0, subrect.size().x + 1, subrect.size().y + 1)
806 .is_err()
807 );
808 }
809
810 #[test]
811 fn rect_split_check() {
812 let rect = Rect::from(64, 64);
813 let p = Percent::from_int(20);
814 let split = rect.split(Orientation::Horizontal, p, 2).unwrap();
815 let expected_rect_0 = Rect {
816 start: Vector2D::new(0, 0),
817 size: Vector2D::new(64, 13),
818 };
819 let expected_rect_1 = Rect {
820 start: Vector2D::new(0, 15),
821 size: Vector2D::new(64, 49),
822 };
823 assert_eq!(split.rects, (expected_rect_0, expected_rect_1));
824
825 let p = Percent::from_int(40);
826 let split = rect.split(Orientation::Vertical, p, 3).unwrap();
827 let expected_rect_0 = Rect {
828 start: Vector2D::new(0, 0),
829 size: Vector2D::new(25, 64),
830 };
831 let expected_rect_1 = Rect {
832 start: Vector2D::new(28, 0),
833 size: Vector2D::new(36, 64),
834 };
835 assert_eq!(split.rects, (expected_rect_0, expected_rect_1));
836
837
838 let small_rect = Rect::from(4, 4);
839 let p = Percent::from_int(20);
840 let split = small_rect.split(Orientation::Horizontal, p, 2).unwrap();
841 let expected_rect_0 = Rect {
842 start: Vector2D::new(0, 0),
843 size: Vector2D::new(4, 1),
844 };
845 let expected_rect_1 = Rect {
846 start: Vector2D::new(0, 3),
847 size: Vector2D::new(4, 1),
848 };
849 assert_eq!(split.rects, (expected_rect_0, expected_rect_1));
850
851 let split = small_rect.split(Orientation::Horizontal, p, 4).unwrap();
852 assert_eq!(split.rects, (expected_rect_0, expected_rect_1));
853 }
854}