1use std::{
71 io::{IsTerminal, Write},
72 mem::ManuallyDrop,
73 os::fd::{AsFd, FromRawFd, RawFd},
74};
75
76use unicode_segmentation::UnicodeSegmentation;
77use unicode_width::UnicodeWidthStr;
78
79use crate::{
80 event::KeyboardEnhancementFlags,
81 vt::{BufferWrite, Modifier, MoveCursor, MoveCursorRight, ScrollBufferDown, ScrollBufferUp},
82};
83
84pub mod event;
85mod sys;
86pub mod vt;
87pub mod widget;
88
89#[macro_export]
106macro_rules! splat {
107 ($out: expr, $($expr:expr),* $(,)?) => {{
108 use $crate::vt::BufferWrite;
109 let out: &mut Vec<u8> = $out;
110 $(
111 $expr.write_to_buffer(out);
112 )*
113 }};
114}
115
116#[derive(Clone, Copy, PartialEq, Eq)]
122pub struct Cell(u64);
123
124impl std::fmt::Debug for Cell {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("Cell")
127 .field("style", &self.style())
128 .field("text", &self.text())
129 .finish()
130 }
131}
132
133#[derive(Clone, Copy, PartialEq, Eq, Debug)]
135pub enum Alignment {
136 Left,
138 Center,
140 Right,
142}
143
144#[repr(u8)]
148#[derive(Copy, Clone)]
149pub enum Rel {
150 TopLeft,
152 TopCenter,
154 TopRight,
156 CenterLeft,
158 CenterCenter,
160 CenterRight,
162 BottomLeft,
164 BottomCenter,
166 BottomRight,
168}
169
170#[derive(Copy, Clone)]
184pub struct RelSet(u16);
185
186impl std::ops::BitOr for RelSet {
187 type Output = RelSet;
188
189 fn bitor(self, rhs: Self) -> Self::Output {
190 RelSet(self.0 | rhs.0)
191 }
192}
193impl std::ops::BitAnd for RelSet {
194 type Output = RelSet;
195
196 fn bitand(self, rhs: Self) -> Self::Output {
197 RelSet(self.0 & rhs.0)
198 }
199}
200
201impl RelSet {
202 pub const TOP: RelSet = RelSet::new(&[Rel::TopLeft, Rel::TopCenter, Rel::TopRight]);
204 pub const BOTTOM: RelSet = RelSet::new(&[Rel::BottomLeft, Rel::BottomCenter, Rel::BottomRight]);
206 pub const LEFT: RelSet = RelSet::new(&[Rel::TopLeft, Rel::CenterLeft, Rel::BottomLeft]);
208 pub const RIGHT: RelSet = RelSet::new(&[Rel::TopRight, Rel::CenterRight, Rel::BottomRight]);
210 pub const BOX: RelSet = RelSet(0b111_101_111);
212
213 pub const fn new(mut rels: &[Rel]) -> RelSet {
215 let mut bits = 0u16;
216 while let [rel, rest @ ..] = rels {
217 bits |= 1 << (*rel as u8);
218 rels = rest;
219 }
220 RelSet(bits)
221 }
222
223 pub const fn contains(self, rel: Rel) -> bool {
225 (self.0 & (1 << (rel as u8))) != 0
226 }
227
228 pub const fn is_empty(self) -> bool {
230 self.0 == 0
231 }
232}
233
234pub struct BoxStyle {
238 pub top_left: Cell,
240 pub top_right: Cell,
242 pub bottom_right: Cell,
244 pub bottom_left: Cell,
246 pub horizontal: Cell,
248 pub vertical: Cell,
250}
251impl BoxStyle {
252 pub const ASCII: BoxStyle = unsafe {
254 BoxStyle {
255 top_right: Cell::new_ascii(b'+', Style::DEFAULT),
256 top_left: Cell::new_ascii(b'+', Style::DEFAULT),
257 bottom_right: Cell::new_ascii(b'+', Style::DEFAULT),
258 bottom_left: Cell::new_ascii(b'+', Style::DEFAULT),
259 horizontal: Cell::new_ascii(b'-', Style::DEFAULT),
260 vertical: Cell::new_ascii(b'|', Style::DEFAULT),
261 }
262 };
263 pub const LIGHT: BoxStyle = BoxStyle {
265 top_right: Cell::new_const("┐", Style::DEFAULT),
266 top_left: Cell::new_const("┌", Style::DEFAULT),
267 bottom_right: Cell::new_const("┘", Style::DEFAULT),
268 bottom_left: Cell::new_const("└", Style::DEFAULT),
269 horizontal: Cell::new_const("─", Style::DEFAULT),
270 vertical: Cell::new_const("│", Style::DEFAULT),
271 };
272}
273
274impl BoxStyle {
275 pub fn render(&self, rect: Rect, buf: &mut DoubleBuffer) -> Rect {
279 self.render_partial(rect, buf, RelSet::BOX)
280 }
281
282 pub fn render_partial(&self, mut rect: Rect, buf: &mut DoubleBuffer, set: RelSet) -> Rect {
286 let Rect { x, y, w, h } = rect;
287 if w == 0 || h == 0 {
288 return rect;
289 }
290
291 if let Some(row) = buf.current.row_remaining_mut(x, y) {
292 if w >= 1 && set.contains(Rel::TopLeft) {
293 row[0] = self.top_left;
294 }
295 if set.contains(Rel::TopCenter) {
296 for cell in row
297 .iter_mut()
298 .take(w as usize)
299 .skip(1)
300 .take(w.saturating_sub(2) as usize)
301 {
302 *cell = self.horizontal;
303 }
304 }
305 if w >= 2 && set.contains(Rel::TopRight) {
306 row[w as usize - 1] = self.top_right;
307 }
308 }
309 if let Some(row) = buf.current.row_remaining_mut(x, y + h - 1) {
310 if w >= 1 && set.contains(Rel::BottomLeft) {
311 row[0] = self.bottom_left;
312 }
313 if set.contains(Rel::BottomCenter) {
314 for cell in row
315 .iter_mut()
316 .take(w as usize)
317 .skip(1)
318 .take(w.saturating_sub(2) as usize)
319 {
320 *cell = self.horizontal;
321 }
322 }
323 if w >= 2 && set.contains(Rel::BottomRight) {
324 row[w as usize - 1] = self.bottom_right;
325 }
326 }
327
328 for y in y + ((!(set & RelSet::TOP).is_empty()) as u16)
329 ..y + h - ((!(set & RelSet::BOTTOM).is_empty()) as u16)
330 {
331 if set.contains(Rel::CenterLeft)
332 && let Some(row) = buf.current.row_remaining_mut(x, y) {
333 row[0] = self.vertical;
334 }
335 if set.contains(Rel::CenterRight)
336 && let Some(row) = buf.current.row_remaining_mut(x + w - 1, y) {
337 row[0] = self.vertical;
338 }
339 }
340 if !(set & RelSet::TOP).is_empty() {
342 rect.y += 1;
343 rect.h = rect.h.saturating_sub(1);
344 }
345 if !(set & RelSet::BOTTOM).is_empty() {
346 rect.h = rect.h.saturating_sub(1);
347 }
348 if !(set & RelSet::LEFT).is_empty() {
349 rect.x += 1;
350 rect.w = rect.w.saturating_sub(1);
351 }
352 if !(set & RelSet::RIGHT).is_empty() {
353 rect.w = rect.w.saturating_sub(1);
354 }
355 rect
356 }
357}
358
359pub struct StyleDelta {
364 current: u32,
365 target: Style,
366}
367
368impl StyleDelta {
369 pub fn with_previous(self, style: Style) -> StyleDelta {
371 StyleDelta {
372 current: style.0,
373 target: self.target,
374 }
375 }
376}
377
378#[derive(Clone, Copy, PartialEq, Eq, Default)]
395pub struct Style(u32);
396
397impl std::fmt::Debug for Style {
398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399 f.debug_struct("Style")
400 .field("fg", &self.fg())
401 .field("bg", &self.bg())
402 .field("modifiers", &self.modifiers())
403 .finish()
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub struct Color(pub u8);
423
424#[allow(non_upper_case_globals)]
425impl Color {
426 pub const NavyBlue: Color = Color(17);
427 pub const DarkBlue: Color = Color(18);
428 pub const Blue3: Color = Color(19);
429 pub const Blue1: Color = Color(21);
430 pub const DarkGreen: Color = Color(22);
431 pub const DeepSkyBlue4: Color = Color(23);
432 pub const DodgerBlue3: Color = Color(26);
433 pub const DodgerBlue2: Color = Color(27);
434 pub const Green4: Color = Color(28);
435 pub const SpringGreen4: Color = Color(29);
436 pub const Turquoise4: Color = Color(30);
437 pub const DeepSkyBlue3: Color = Color(31);
438 pub const DodgerBlue1: Color = Color(33);
439 pub const Green3: Color = Color(34);
440 pub const DarkCyan: Color = Color(36);
441 pub const DeepSkyBlue2: Color = Color(38);
442 pub const DeepSkyBlue1: Color = Color(39);
443 pub const SpringGreen3: Color = Color(41);
444 pub const SpringGreen: Color = Color(42);
445 pub const Cyan3: Color = Color(43);
446 pub const DarkTurquoise: Color = Color(44);
447 pub const Turquoise2: Color = Color(45);
448 pub const Green1: Color = Color(46);
449 pub const SpringGreen2: Color = Color(47);
450 pub const SpringGreen1: Color = Color(48);
451 pub const MediumSpringGreen: Color = Color(49);
452 pub const Cyan2: Color = Color(50);
453 pub const Cyan1: Color = Color(51);
454 pub const DarkRed: Color = Color(52);
455 pub const DeepPink4: Color = Color(53);
456 pub const Purple3: Color = Color(56);
457 pub const BlueViolet: Color = Color(57);
458 pub const Orange4: Color = Color(58);
459 pub const MediumPurple4: Color = Color(60);
460 pub const SlateBlue3: Color = Color(61);
461 pub const RoyalBlue1: Color = Color(63);
462 pub const Chartreuse4: Color = Color(64);
463 pub const PaleTurquoise4: Color = Color(66);
464 pub const SteelBlue: Color = Color(67);
465 pub const SteelBlue3: Color = Color(68);
466 pub const CornflowerBlue: Color = Color(69);
467 pub const Chartreuse3: Color = Color(70);
468 pub const DarkSeaGreen4: Color = Color(71);
469 pub const CadetBlue: Color = Color(72);
470 pub const SkyBlue3: Color = Color(74);
471 pub const SteelBlue1: Color = Color(75);
472 pub const PaleGreen3: Color = Color(77);
473 pub const SeaGreen3: Color = Color(78);
474 pub const Aquamarine3: Color = Color(79);
475 pub const MediumTurquoise: Color = Color(80);
476 pub const Chartreuse2: Color = Color(82);
477 pub const Aquamarine1: Color = Color(86);
478 pub const DarkSlateGray2: Color = Color(87);
479 pub const DarkViolet: Color = Color(92);
480 pub const Purple: Color = Color(93);
481 pub const LightPink4: Color = Color(95);
482 pub const Plum4: Color = Color(96);
483 pub const SlateBlue1: Color = Color(99);
484 pub const Yellow4: Color = Color(100);
485 pub const Wheat4: Color = Color(101);
486 pub const LightSlateGrey: Color = Color(103);
487 pub const MediumPurple: Color = Color(104);
488 pub const LightSlateBlue: Color = Color(105);
489 pub const DarkOliveGreen3: Color = Color(107);
490 pub const DarkSeaGreen: Color = Color(108);
491 pub const Grey: [Color; 31] = [
492 Color(16),
493 Color(232),
494 Color(233),
495 Color(234),
496 Color(235),
497 Color(236),
498 Color(237),
499 Color(238),
500 Color(239),
501 Color(240),
502 Color(59),
503 Color(241),
504 Color(242),
505 Color(243),
506 Color(244),
507 Color(102),
508 Color(245),
509 Color(246),
510 Color(247),
511 Color(139),
512 Color(248),
513 Color(145),
514 Color(249),
515 Color(250),
516 Color(251),
517 Color(252),
518 Color(188),
519 Color(253),
520 Color(254),
521 Color(255),
522 Color(231),
523 ];
524 pub const SkyBlue2: Color = Color(111);
525 pub const DarkOliveGreen: Color = Color(113);
526 pub const DarkSeaGreen3: Color = Color(115);
527 pub const DarkSlateGray3: Color = Color(116);
528 pub const SkyBlue1: Color = Color(117);
529 pub const Chartreuse1: Color = Color(118);
530 pub const LightGreen: Color = Color(119);
531 pub const PaleGreen1: Color = Color(121);
532 pub const DarkSlateGray1: Color = Color(123);
533 pub const Red3: Color = Color(124);
534 pub const MediumVioletRed: Color = Color(126);
535 pub const Magenta3: Color = Color(127);
536 pub const DarkOrange3: Color = Color(130);
537 pub const IndianRed: Color = Color(131);
538 pub const HotPink3: Color = Color(132);
539 pub const MediumOrchid3: Color = Color(133);
540 pub const MediumOrchid: Color = Color(134);
541 pub const DarkGoldenrod: Color = Color(136);
542 pub const LightSalmon3: Color = Color(137);
543 pub const RosyBrown: Color = Color(138);
544 pub const Violet: Color = Color(140);
545 pub const MediumPurple1: Color = Color(141);
546 pub const Gold3: Color = Color(142);
547 pub const DarkKhaki: Color = Color(143);
548 pub const NavajoWhite3: Color = Color(144);
549 pub const LightSteelBlue3: Color = Color(146);
550 pub const LightSteelBlue: Color = Color(147);
551 pub const Yellow3: Color = Color(148);
552 pub const LightCyan3: Color = Color(152);
553 pub const LightSkyBlue1: Color = Color(153);
554 pub const GreenYellow: Color = Color(154);
555 pub const DarkOliveGreen2: Color = Color(155);
556 pub const DarkSeaGreen1: Color = Color(158);
557 pub const PaleTurquoise1: Color = Color(159);
558 pub const Magenta2: Color = Color(165);
559 pub const HotPink2: Color = Color(169);
560 pub const Orchid: Color = Color(170);
561 pub const MediumOrchid1: Color = Color(171);
562 pub const Orange3: Color = Color(172);
563 pub const LightPink3: Color = Color(174);
564 pub const Pink3: Color = Color(175);
565 pub const Plum3: Color = Color(176);
566 pub const LightGoldenrod3: Color = Color(179);
567 pub const Tan: Color = Color(180);
568 pub const MistyRose3: Color = Color(181);
569 pub const Thistle3: Color = Color(182);
570 pub const Plum2: Color = Color(183);
571 pub const Khaki3: Color = Color(185);
572 pub const LightYellow3: Color = Color(187);
573 pub const LightSteelBlue1: Color = Color(189);
574 pub const Yellow2: Color = Color(190);
575 pub const DarkOliveGreen1: Color = Color(191);
576 pub const LightSeaGreen: Color = Color(193);
577 pub const Honeydew: Color = Color(194);
578 pub const LightCyan1: Color = Color(195);
579 pub const Red1: Color = Color(196);
580 pub const DeepPink2: Color = Color(197);
581 pub const DeepPink1: Color = Color(198);
582 pub const Magenta1: Color = Color(201);
583 pub const OrangeRed1: Color = Color(202);
584 pub const NeonRed: Color = Color(203);
585 pub const HotPink: Color = Color(205);
586 pub const DarkOrange: Color = Color(208);
587 pub const Salmon: Color = Color(209);
588 pub const LightCoral: Color = Color(210);
589 pub const PaleVioletRed1: Color = Color(211);
590 pub const Orchid2: Color = Color(212);
591 pub const Orchid1: Color = Color(213);
592 pub const Orange1: Color = Color(214);
593 pub const SandyBrown: Color = Color(215);
594 pub const LightSalmon1: Color = Color(216);
595 pub const LightPink1: Color = Color(217);
596 pub const Pink1: Color = Color(218);
597 pub const Plum1: Color = Color(219);
598 pub const Gold1: Color = Color(220);
599 pub const LightGoldenrod2: Color = Color(221);
600 pub const NavajoWhite: Color = Color(223);
601 pub const MistyRose: Color = Color(224);
602 pub const Thistle: Color = Color(225);
603 pub const Yellow1: Color = Color(226);
604 pub const LightGoldenrod1: Color = Color(227);
605 pub const Khaki1: Color = Color(228);
606 pub const Wheat1: Color = Color(229);
607 pub const Cornsilk1: Color = Color(230);
608
609 pub const White: Color = Color(231);
610 pub const Black: Color = Color(16);
611}
612impl Color {
613 pub fn as_fg(self) -> Style {
615 Style::DEFAULT.with_fg(self)
616 }
617
618 pub fn as_bg(self) -> Style {
620 Style::DEFAULT.with_bg(self)
621 }
622
623 pub fn with_fg(self, fg: Color) -> Style {
625 Style::DEFAULT.with_fg(fg).with_bg(self)
626 }
627
628 pub fn with_bg(self, bg: Color) -> Style {
630 Style::DEFAULT.with_bg(bg).with_fg(self)
631 }
632}
633
634impl std::ops::BitOrAssign<Modifier> for Style {
635 fn bitor_assign(&mut self, rhs: Modifier) {
636 self.0 |= rhs.0 as u32;
637 }
638}
639
640impl std::ops::BitOr<Modifier> for Style {
641 type Output = Style;
642
643 fn bitor(self, rhs: Modifier) -> Self::Output {
644 Style(self.0 | rhs.0 as u32)
645 }
646}
647
648impl std::ops::BitOrAssign for Style {
649 fn bitor_assign(&mut self, rhs: Self) {
650 self.0 |= rhs.0;
651 }
652}
653
654impl std::ops::BitOr for Style {
655 type Output = Style;
656
657 fn bitor(self, rhs: Self) -> Self::Output {
658 Style(self.0 | rhs.0)
659 }
660}
661
662impl Style {
663 pub const DEFAULT: Style = Style(0);
665 pub(crate) const HAS_BG: u32 = 0b10_000;
666 pub(crate) const FG_MASK: u32 = 0xff00_0000;
667 pub(crate) const BG_MASK: u32 = 0x00ff_0000;
668 pub(crate) const MASK: u32 = 0xffff_fff8;
669 pub(crate) const HAS_FG: u32 = 0b01_000;
670
671 pub const fn delta(self) -> StyleDelta {
673 StyleDelta {
674 current: u32::MAX,
675 target: self,
676 }
677 }
678 pub const fn with_fg(self, color: Color) -> Style {
680 Style((self.0 & 0x00ff_ffff) | ((color.0 as u32) << 24) | 0b1_000)
681 }
682
683 pub const fn without_fg(self) -> Style {
685 Style(self.0 & !(Style::FG_MASK | Style::HAS_FG))
686 }
687
688 pub const fn without_bg(self) -> Style {
690 Style(self.0 & !(Style::BG_MASK | Style::HAS_BG))
691 }
692
693 pub const fn modifiers(self) -> Modifier {
695 Modifier(self.0 as u16 & Modifier::ALL.0)
696 }
697
698 pub const fn with_modifier(self, mods: Modifier) -> Style {
700 Style(self.0 | mods.0 as u32)
701 }
702
703 pub const fn without_modifier(self, mods: Modifier) -> Style {
705 Style(self.0 & !(mods.0 as u32))
706 }
707
708 pub const fn fg(self) -> Option<Color> {
710 if self.0 & 0b1_000 != 0 {
711 Some(Color(((self.0 >> 24) & 0xff) as u8))
712 } else {
713 None
714 }
715 }
716
717 pub const fn with_bg(self, color: Color) -> Style {
719 Style((self.0 & 0xff00_ffff) | ((color.0 as u32) << 16) | 0b10_000)
720 }
721
722 pub const fn bg(self) -> Option<Color> {
724 if self.0 & 0b10_000 != 0 {
725 Some(Color(((self.0 >> 16) & 0xff) as u8))
726 } else {
727 None
728 }
729 }
730}
731
732impl Cell {
741 const EMPTY: Cell = Cell(0);
742 const BLANK: Cell = unsafe { Cell::new_ascii(b' ', Style::DEFAULT) };
743
744 pub fn is_empty(self) -> bool {
746 self.0 <= 0xffff_ffff
747 }
748 #[inline]
749 fn text(&self) -> &str {
750 let len = (self.0 & 0b111) as usize;
751 unsafe {
752 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
753 ((&self.0 as *const u64) as *const u8).add(4),
754 len,
755 ))
756 }
757 }
758 fn new(text: &str, style: Style) -> Cell {
759 if text.len() > 4 {
760 panic!();
761 }
762 let mut initial = [0u8; 8];
763 for (i, ch) in text.as_bytes().iter().enumerate() {
764 initial[i + 4] = *ch;
765 }
766 Cell(u64::from_ne_bytes(initial) | (style.0 as u64) | text.len() as u64)
767 }
768 const fn new_const(text: &str, style: Style) -> Cell {
769 if text.len() > 4 {
770 panic!();
771 }
772 let mut initial = [0u8; 8];
773 let bytes = text.as_bytes();
774 let mut i = 0;
775 while i < bytes.len() {
776 initial[i + 4] = bytes[i];
777 i += 1;
778 }
779 Cell(u64::from_ne_bytes(initial) | (style.0 as u64) | text.len() as u64)
780 }
781
782 pub fn with_style_merged(&self, style: Style) -> Cell {
784 Cell(self.0 | style.0 as u64)
785 }
786 fn style(&self) -> Style {
787 Style((self.0 & 0xfffffff8) as u32)
788 }
789 const unsafe fn new_ascii(ch: u8, style: Style) -> Cell {
790 Cell(((ch as u64) << (4 * 8)) | (style.0 as u64) | 1)
791 }
792}
793
794pub struct Buffer {
800 pub(crate) cells: Box<[Cell]>,
801 pub(crate) width: u16,
802 pub(crate) height: u16,
803}
804
805#[derive(Clone, Copy, PartialEq, Eq, Debug)]
809pub struct Rect {
810 pub x: u16,
812 pub y: u16,
814 pub w: u16,
816 pub h: u16,
818}
819
820impl Rect {
821 pub const EMPTY: Rect = Rect {
823 x: 0,
824 y: 0,
825 w: 0,
826 h: 0,
827 };
828}
829
830pub trait SplitRule: std::ops::Neg<Output = Self> {
835 fn split_once(self, value: u16) -> (u16, u16);
837}
838
839#[derive(Clone, Copy, PartialEq, Eq, Default)]
841pub enum VAlign {
842 #[default]
844 Top,
845 Center,
847 Bottom,
849}
850
851#[derive(Clone, Copy, PartialEq, Eq, Default)]
853pub enum HAlign {
854 #[default]
856 Left,
857 Center,
859 Right,
861}
862
863#[derive(Default, Clone, Copy)]
865pub struct RenderProperties {
866 pub style: Style,
868 pub valign: VAlign,
870 pub halign: HAlign,
872 pub offset: u32,
874}
875
876pub trait RenderProperty {
880 fn apply(self, properties: &mut RenderProperties);
882}
883
884impl RenderProperty for Style {
885 fn apply(self, properties: &mut RenderProperties) {
886 properties.style = self;
887 }
888}
889impl RenderProperty for VAlign {
890 fn apply(self, properties: &mut RenderProperties) {
891 properties.valign = self;
892 }
893}
894impl RenderProperty for HAlign {
895 fn apply(self, properties: &mut RenderProperties) {
896 properties.halign = self;
897 }
898}
899impl RenderProperty for RenderProperties {
900 fn apply(self, properties: &mut RenderProperties) {
901 *properties = self;
902 }
903}
904
905pub struct DisplayRect {
910 rect: Rect,
911 properties: RenderProperties,
912}
913
914impl DisplayRect {
915 pub fn with(self, property: impl RenderProperty) -> DisplayRect {
917 let mut properties = self.properties;
918 property.apply(&mut properties);
919 DisplayRect {
920 rect: self.rect,
921 properties,
922 }
923 }
924
925 pub fn fill<'a>(self, buf: &mut DoubleBuffer) -> DisplayRect {
927 if self.rect.is_empty() {
928 return self;
929 }
930 buf.current.set_style(self.rect, self.properties.style);
931 self
932 }
933 pub fn skip(mut self, amount: u32) -> DisplayRect {
935 self.properties.offset = self.properties.offset.wrapping_add(amount);
936 self
937 }
938 pub fn fmt<'a>(self, buf: &mut DoubleBuffer, content: impl std::fmt::Display) -> DisplayRect {
940 if self.rect.is_empty() {
941 return self;
942 }
943 let start_buf = buf.buf.len();
944 write!(buf.buf, "{content}").unwrap();
945 let text = unsafe { std::str::from_utf8_unchecked(&buf.buf[start_buf..]) };
946 let rect = self.text_inner(&mut buf.current, text);
947 unsafe {
948 buf.buf.set_len(start_buf);
949 }
950 rect
951 }
952 pub fn text(self, buf: &mut DoubleBuffer, text: &str) -> DisplayRect {
954 self.text_inner(&mut buf.current, text)
955 }
956 fn text_inner(mut self, buf: &mut Buffer, text: &str) -> DisplayRect {
957 if self.rect.is_empty() {
958 return self;
959 }
960 match self.properties.halign {
961 HAlign::Left => {
962 let (nx, _ny) = buf.set_stringn(
963 self.rect.x + self.properties.offset as u16,
964 self.rect.y,
965 text,
966 self.rect.w as usize,
967 self.properties.style,
968 );
969 self.properties.offset = (nx - self.rect.x) as u32;
970 self
971 }
972 HAlign::Center => todo!(),
973 HAlign::Right => {
974 let text_width = UnicodeWidthStr::width(text);
975 let start_x = if text_width as u16 >= self.rect.w {
976 self.rect.x
977 } else {
978 self.rect.x + self.rect.w - text_width as u16
979 };
980 let (nx, _ny) = buf.set_stringn(
981 start_x,
982 self.rect.y,
983 text,
984 self.rect.w as usize,
985 self.properties.style,
986 );
987 self.properties.offset = (nx - self.rect.x) as u32;
988 self
989 }
990 }
991 }
992}
993
994impl Rect {
995 pub fn contains(&self, x: u16, y: u16) -> bool {
997 let x_delta = (x as u32).wrapping_sub(self.x as u32);
998 let y_delta = (y as u32).wrapping_sub(self.y as u32);
999 (x_delta < (self.w as u32)) & (y_delta < (self.h as u32))
1000 }
1001 pub fn is_empty(self) -> bool {
1003 self.w == 0 || self.h == 0
1004 }
1005
1006 #[must_use]
1008 pub fn display(self) -> DisplayRect {
1009 DisplayRect {
1010 rect: self,
1011 properties: RenderProperties::default(),
1012 }
1013 }
1014 #[must_use]
1016 pub fn with(self, property: impl RenderProperty) -> DisplayRect {
1017 let properties = {
1018 let mut props = RenderProperties::default();
1019 property.apply(&mut props);
1020 props
1021 };
1022 DisplayRect {
1023 rect: self,
1024 properties,
1025 }
1026 }
1027 pub fn left(self) -> u16 {
1029 self.x
1030 }
1031
1032 pub fn right(self) -> u16 {
1034 self.x.saturating_add(self.w)
1035 }
1036
1037 pub fn top(self) -> u16 {
1039 self.y
1040 }
1041
1042 pub fn bottom(self) -> u16 {
1044 self.y.saturating_add(self.h)
1045 }
1046
1047 pub fn v_split(&self, rule: impl SplitRule) -> (Self, Self) {
1051 let (h1, h2) = rule.split_once(self.h);
1052 (
1053 Rect { h: h1, ..*self },
1054 Rect {
1055 h: h2,
1056 y: self.y + h1,
1057 ..*self
1058 },
1059 )
1060 }
1061
1062 pub fn h_split(&self, rule: impl SplitRule) -> (Self, Self) {
1066 let (w1, w2) = rule.split_once(self.w);
1067 (
1068 Rect { w: w1, ..*self },
1069 Rect {
1070 w: w2,
1071 x: self.x + w1,
1072 ..*self
1073 },
1074 )
1075 }
1076 pub fn take_right(&mut self, rule: impl SplitRule) -> Self {
1078 let (rest, new) = self.h_split(rule.neg());
1079 *self = rest;
1080 new
1081 }
1082
1083 pub fn take_bottom(&mut self, rule: impl SplitRule) -> Self {
1085 let (rest, new) = self.v_split(rule.neg());
1086 *self = rest;
1087 new
1088 }
1089
1090 pub fn take_left(&mut self, rule: impl SplitRule) -> Self {
1092 let (rest, new) = self.h_split(rule);
1093 *self = new;
1094 rest
1095 }
1096
1097 pub fn take_top(&mut self, rule: impl SplitRule) -> Self {
1099 let (rest, new) = self.v_split(rule);
1100 *self = new;
1101 rest
1102 }
1103}
1104
1105impl SplitRule for i32 {
1106 fn split_once(self, value: u16) -> (u16, u16) {
1107 let value = value as u32;
1108 let c = self.unsigned_abs();
1109 let (mut a, mut b) = if value >= c {
1110 (c as u16, (value - c) as u16)
1111 } else {
1112 (value as u16, 0)
1113 };
1114 if self < 0 {
1115 std::mem::swap(&mut a, &mut b);
1116 }
1117 (a, b)
1118 }
1119}
1120
1121impl SplitRule for f64 {
1122 fn split_once(self, value: u16) -> (u16, u16) {
1123 assert!((-1.0..=1.0).contains(&self), "Invalid Split Ratio");
1124 let mut a = ((value as f64) * self.abs()) as u16;
1125 let mut b = value - a;
1126 if self < 0.0 {
1127 std::mem::swap(&mut a, &mut b);
1128 }
1129 (a, b)
1130 }
1131}
1132
1133pub(crate) fn write_style_diff_ignore_fg(
1134 current: Style,
1135 mut new: Style,
1136 buf: &mut Vec<u8>,
1137) -> Style {
1138 new.0 = (new.0 & !(Style::FG_MASK | Style::HAS_FG))
1139 | (current.0 & (Style::FG_MASK | Style::HAS_FG));
1140 write_style_diff(current, new, buf)
1141}
1142pub(crate) fn write_style_diff(current: Style, new: Style, buf: &mut Vec<u8>) -> Style {
1143 if current == new {
1144 return current;
1145 }
1146 let removed = (new.0 | current.0) ^ new.0;
1147 let clearing = removed & (Style::HAS_BG | Style::HAS_FG | Modifier::ALL.0 as u32) != 0;
1148 if clearing {
1149 vt::style(buf, new, true);
1150 return new;
1151 }
1152 let mut target = new;
1153 if current.fg() == target.fg() {
1154 target = target.without_fg();
1155 }
1156 if current.bg() == target.bg() {
1157 target = target.without_bg();
1158 }
1159 vt::style(buf, target, false);
1161 new
1180}
1181
1182impl Buffer {
1183 pub fn scroll_up(&mut self, amount: u16) {
1188 if amount == 0 || self.height == 0 {
1189 return;
1190 }
1191 let amount = amount.min(self.height);
1192 let total = self.cells.len();
1193 let shifted = (self.width as usize) * (amount as usize);
1194 self.cells.copy_within(shifted.., 0);
1195 self.cells[total - shifted..].fill(Cell::EMPTY);
1196 }
1197 pub fn scroll_down(&mut self, amount: u16) {
1202 if amount == 0 || self.height == 0 {
1203 return;
1204 }
1205 let amount = amount.min(self.height);
1206 let total = self.cells.len();
1207 let shifted = (self.width as usize) * (amount as usize);
1208 self.cells.copy_within(0..total - shifted, shifted);
1209 self.cells[0..shifted].fill(Cell::EMPTY);
1210 }
1211 pub fn width(&self) -> u16 {
1213 self.width
1214 }
1215
1216 pub fn height(&self) -> u16 {
1218 self.height
1219 }
1220
1221 pub fn new(width: u16, height: u16) -> Buffer {
1223 let cells = vec![Cell::EMPTY; width as usize * height as usize].into_boxed_slice();
1224 Buffer {
1225 cells,
1226 width,
1227 height,
1228 }
1229 }
1230 pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
1232 let index = (y as u32) * (self.width as u32) + (x as u32);
1233 self.cells.get_mut(index as usize)
1234 }
1235 pub fn row_remaining_mut(&mut self, x: u16, y: u16) -> Option<&mut [Cell]> {
1237 let base = (y as u32) * (self.width as u32);
1238 let start = base + (x as u32);
1239 let end = base + self.width as u32;
1241 self.cells.get_mut(start as usize..end as usize)
1242 }
1243
1244 fn render_diff(&mut self, buf: &mut Vec<u8>, old: &Buffer, y_offset: u16, blanking: bool) {
1245 if self.cells.len() != old.cells.len() {
1246 self.render(buf, y_offset);
1247 return;
1248 }
1249 vt::CLEAR_STYLE.write_to_buffer(buf);
1250 let mut current_style = Style::DEFAULT;
1251 let mut old_cells = old.cells.iter();
1252 for (y, row) in self.cells.chunks_exact(self.width as usize).enumerate() {
1253 let y = y as u16 + y_offset;
1254 let mut moved = false;
1255 let mut matching_count = 0;
1256 let mut new_cells = row.iter();
1257 let mut erased_cell: Option<Cell> = None;
1258 'next_cell: while let Some(new) = new_cells.next() {
1259 let mut new = *new;
1260 let Some(old) = erased_cell.or_else(|| old_cells.next().copied()) else {
1261 return;
1262 };
1263 if new == old {
1264 matching_count += 1;
1265 continue;
1266 }
1267 if new.is_empty() {
1268 let mut blank_overwrite = 1;
1269 let mut blank_kind = new;
1270 'continue_new: {
1271 loop {
1272 let Some(&new_k) = new_cells.next() else {
1273 break;
1275 };
1276 let Some(old_k) = erased_cell.or_else(|| old_cells.next().copied())
1277 else {
1278 break;
1280 };
1281 if old_k == new_k {
1282 if !moved {
1283 moved = true;
1284 MoveCursor(matching_count, y).write_to_buffer(buf);
1285 matching_count = 0;
1286 }
1287 if matching_count > 0 {
1288 MoveCursorRight(matching_count).write_to_buffer(buf);
1289 }
1291 current_style = write_style_diff_ignore_fg(
1292 current_style,
1293 blank_kind.style(),
1294 buf,
1295 );
1296 if !blanking || blank_overwrite < 50 {
1297 buf.extend(std::iter::repeat_n(b' ', blank_overwrite as usize));
1298 matching_count = 1;
1299 } else {
1300 buf.extend_from_slice(b"\x1b[K");
1301 let rem_old = new_cells.len();
1302 if rem_old > 0 && erased_cell.is_none() {
1303 old_cells.nth(rem_old - 1);
1304 }
1305 erased_cell = Some(blank_kind);
1306 matching_count = blank_overwrite;
1307 if new_k == blank_kind {
1308 matching_count += 1;
1309 continue 'next_cell;
1310 } else {
1311 new = new_k;
1312 break 'continue_new;
1313 }
1314 }
1315 continue 'next_cell;
1316 }
1317 if new_k == blank_kind {
1318 blank_overwrite += 1;
1319 continue;
1320 }
1321
1322 if !moved {
1323 moved = true;
1324 MoveCursor(matching_count, y).write_to_buffer(buf);
1325 matching_count = 0;
1326 }
1327 if matching_count > 0 {
1328 MoveCursorRight(matching_count).write_to_buffer(buf);
1329 matching_count = 0;
1330 }
1331
1332 current_style =
1333 write_style_diff_ignore_fg(current_style, blank_kind.style(), buf);
1334 if !blanking || blank_overwrite < 50 {
1335 buf.extend(std::iter::repeat_n(b' ', blank_overwrite as usize));
1336 } else {
1337 buf.extend_from_slice(b"\x1b[K");
1338 let rem_old = new_cells.len();
1339 if rem_old > 0 && erased_cell.is_none() {
1340 old_cells.nth(rem_old - 1);
1341 }
1342 erased_cell = Some(blank_kind);
1343 matching_count = blank_overwrite;
1344 }
1345
1346 if new_k.is_empty() {
1347 blank_kind = new_k;
1348 blank_overwrite = 1;
1349 continue;
1350 } else {
1351 new = new_k;
1352 break 'continue_new;
1353 }
1354 }
1355
1356 if !moved {
1357 MoveCursor(matching_count, y).write_to_buffer(buf);
1359 matching_count = 0;
1360 }
1361 if matching_count > 0 {
1362 MoveCursorRight(matching_count).write_to_buffer(buf);
1363 }
1365 current_style =
1366 write_style_diff_ignore_fg(current_style, blank_kind.style(), buf);
1367
1368 if blank_overwrite < 8 {
1369 for _ in 0..blank_overwrite {
1370 buf.push(b' ');
1371 }
1372 } else {
1373 buf.extend_from_slice(b"\x1b[K");
1374 }
1375
1376 break 'next_cell;
1377 }
1378 }
1379
1380 if !moved {
1381 moved = true;
1382 MoveCursor(matching_count, y).write_to_buffer(buf);
1383 matching_count = 0;
1384 }
1385 if matching_count > 0 {
1386 MoveCursorRight(matching_count).write_to_buffer(buf);
1387 matching_count = 0;
1388 }
1389 let text = new.text();
1390 if text.is_empty() {
1391 current_style = write_style_diff_ignore_fg(current_style, new.style(), buf);
1392 } else {
1393 current_style = write_style_diff(current_style, new.style(), buf);
1394 }
1395 if text.is_empty() {
1396 buf.push(b' ');
1397 } else {
1398 buf.extend_from_slice(text.as_bytes());
1399 }
1400 }
1401 }
1402 }
1403 fn render(&mut self, buf: &mut Vec<u8>, y_offset: u16) {
1404 if y_offset == 0 {
1405 vt::MOVE_CURSOR_TO_ORIGIN.write_to_buffer(buf);
1406 } else {
1407 MoveCursor(0, y_offset).write_to_buffer(buf);
1408 }
1409 vt::CLEAR_STYLE.write_to_buffer(buf);
1410 buf.extend_from_slice(vt::CLEAR_BELOW);
1411
1412 let mut current_style = Style::DEFAULT;
1413 for (y, row) in self.cells.chunks_exact(self.width as usize).enumerate() {
1414 let y = y as u16 + y_offset;
1415 let mut moved = false;
1416 let mut blank_extension = 0;
1417 for &cell in row.iter() {
1418 if cell == Cell::BLANK || cell == Cell::EMPTY {
1419 blank_extension += 1;
1420 continue;
1421 }
1422 if !moved {
1423 moved = true;
1424 MoveCursor(blank_extension, y).write_to_buffer(buf);
1425 blank_extension = 0;
1426 }
1427 if blank_extension > 0 {
1428 if blank_extension < 5 && current_style == Style::DEFAULT {
1429 for _ in 0..blank_extension {
1430 buf.push(b' ');
1431 }
1432 } else {
1433 MoveCursorRight(blank_extension).write_to_buffer(buf);
1434 }
1435 blank_extension = 0;
1436 }
1437 current_style = write_style_diff(current_style, cell.style(), buf);
1438 let text = cell.text();
1439 if text.is_empty() {
1440 buf.push(b' ');
1441 } else {
1442 buf.extend_from_slice(text.as_bytes());
1443 }
1444 }
1445 }
1446 vt::MOVE_CURSOR_TO_ORIGIN.write_to_buffer(buf);
1447 }
1448 pub fn set_style(&mut self, area: Rect, style: Style) {
1449 let Rect { x, y, w, h } = area;
1450 let mut keep_mask = !(Style::MASK as u64);
1451 let mut new_mask = style.0 as u64;
1452
1453 if style.fg().is_none() {
1454 keep_mask |= (Style::HAS_FG as u64) | Style::FG_MASK as u64;
1455 }
1456 if style.bg().is_none() {
1457 keep_mask |= (Style::HAS_BG as u64) | Style::BG_MASK as u64;
1458 }
1459 new_mask &= !keep_mask;
1460
1461 for y in y..y + h {
1462 if let Some(row) = self.row_remaining_mut(x, y) {
1463 for cell in row.iter_mut().take(w as usize) {
1464 cell.0 = (cell.0 & keep_mask) | new_mask;
1465 }
1466 }
1467 }
1468 }
1469 pub fn set_string(&mut self, x: u16, y: u16, string: &str, style: Style) -> (u16, u16) {
1473 self.set_stringn(x, y, string, usize::MAX, style)
1474 }
1475
1476 pub fn set_stringn(
1480 &mut self,
1481 x: u16,
1482 y: u16,
1483 string: &str,
1484 max_width: usize,
1485 style: Style,
1486 ) -> (u16, u16) {
1487 let mut remaining_width = (self.width.saturating_sub(x) as usize).min(max_width) as u16;
1488 let initial_remaining_width = remaining_width;
1489 let Some(target) = self.row_remaining_mut(x, y) else {
1490 return (x, y);
1491 };
1492 let graphemes = UnicodeSegmentation::graphemes(string, true)
1493 .filter(|symbol| !symbol.contains(char::is_control))
1494 .map(|symbol| (symbol, symbol.width() as u16))
1495 .filter(|(_symbol, width)| *width > 0)
1496 .map_while(|(symbol, width)| {
1497 remaining_width = remaining_width.checked_sub(width)?;
1498 Some((symbol, width))
1499 });
1500 let mut target_cells = target.iter_mut();
1501 for (symbol, width) in graphemes {
1502 if let Some(cell) = target_cells.next() {
1503 *cell = Cell::new(symbol, style);
1504 } else {
1505 return (x + initial_remaining_width, y);
1506 }
1507 for _ in 1..width {
1510 if let Some(cell) = target_cells.next() {
1511 *cell = Cell::EMPTY;
1512 } else {
1513 return (x + initial_remaining_width, y);
1514 }
1515 }
1516 }
1517 (x + (initial_remaining_width - remaining_width), y)
1518 }
1519}
1520
1521bitflags::bitflags! {
1522 #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
1527 pub struct TerminalFlags: u8 {
1528 const RAW_MODE = 0b0000_0001;
1530 const ALT_SCREEN = 0b0000_0010;
1532 const MOUSE_CAPTURE = 0b0000_0100;
1534 const HIDE_CURSOR = 0b0000_1000;
1536 const EXTENDED_KEYBOARD_INPUTS = 0b0001_0000;
1538 }
1539}
1540
1541pub struct Terminal {
1559 fd: std::mem::ManuallyDrop<std::fs::File>,
1560 termios: sys::Termios,
1561 flags: TerminalFlags,
1562}
1563
1564fn write_enable_terminal_flags(
1565 file: &mut std::fs::File,
1566 flags: TerminalFlags,
1567) -> std::io::Result<()> {
1568 let mut buffer = Vec::new();
1570
1571 if flags.contains(TerminalFlags::MOUSE_CAPTURE) {
1572 buffer.extend_from_slice(vt::ENABLE_NON_MOTION_MOUSE_EVENTS);
1573 }
1574 if flags.contains(TerminalFlags::HIDE_CURSOR) {
1575 buffer.extend_from_slice(vt::HIDE_CURSOR);
1576 }
1577
1578 if flags.contains(TerminalFlags::ALT_SCREEN) {
1579 buffer.extend_from_slice(vt::ENABLE_ALT_SCREEN);
1580 }
1581 if flags.contains(TerminalFlags::EXTENDED_KEYBOARD_INPUTS) {
1582 (KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
1583 | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS)
1584 .write_to_buffer(&mut buffer);
1585 }
1586
1587 file.write_all(&buffer)?;
1588 Ok(())
1589}
1590
1591fn write_disable_terminal_flags(
1592 file: &mut std::fs::File,
1593 flags: TerminalFlags,
1594) -> std::io::Result<()> {
1595 let mut buffer = Vec::new();
1597 if flags.contains(TerminalFlags::MOUSE_CAPTURE) {
1598 buffer.extend_from_slice(vt::DISABLE_NON_MOTION_MOUSE_EVENTS);
1599 }
1600 if flags.contains(TerminalFlags::HIDE_CURSOR) {
1601 buffer.extend_from_slice(vt::SHOW_CURSOR);
1602 }
1603 if flags.contains(TerminalFlags::ALT_SCREEN) {
1604 buffer.extend_from_slice(vt::DISABLE_ALT_SCREEN);
1605 }
1606 if flags.contains(TerminalFlags::EXTENDED_KEYBOARD_INPUTS) {
1607 buffer.extend_from_slice(vt::POP_KEYBOARD_ENABLEMENT);
1608 }
1609 file.write_all(&buffer)?;
1610 Ok(())
1611}
1612impl std::io::Write for Terminal {
1613 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1614 self.fd.write(buf)
1615 }
1616
1617 fn flush(&mut self) -> std::io::Result<()> {
1618 Ok(())
1619 }
1620}
1621
1622impl Terminal {
1623 pub fn is_raw(&self) -> bool {
1625 self.flags.contains(TerminalFlags::RAW_MODE)
1626 }
1627
1628 pub fn with_bypass<T>(&mut self, mut func: impl FnMut() -> T) -> std::io::Result<T> {
1632 write_disable_terminal_flags(&mut self.fd, self.flags)?;
1633 sys::attr::set_terminal_attr(self.fd.as_fd(), &self.termios)?;
1634 let t = func();
1636 if self.flags.contains(TerminalFlags::RAW_MODE) {
1637 let mut raw_term = self.termios;
1638 sys::attr::raw_terminal_attr(&mut raw_term);
1639 sys::attr::set_terminal_attr(self.fd.as_fd(), &raw_term)?;
1640 }
1641 write_enable_terminal_flags(&mut self.fd, self.flags)?;
1642 Ok(t)
1643 }
1644 pub fn new(fd: RawFd, flags: TerminalFlags) -> std::io::Result<Terminal> {
1650 let mut stdout = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(fd) });
1651 if !stdout.is_terminal() {
1652 return Err(std::io::Error::other(
1653 "Stdout is not a terminal",
1654 ));
1655 }
1656 let termios = sys::attr::get_terminal_attr(stdout.as_fd())?;
1657 if flags.contains(TerminalFlags::RAW_MODE) {
1658 let mut raw_term = termios;
1659 sys::attr::raw_terminal_attr(&mut raw_term);
1660 sys::attr::set_terminal_attr(stdout.as_fd(), &raw_term)?;
1661 }
1662 write_enable_terminal_flags(&mut stdout, flags)?;
1663 Ok(Terminal {
1664 fd: stdout,
1665 flags,
1666 termios,
1667 })
1668 }
1669 pub fn open(flags: TerminalFlags) -> std::io::Result<Terminal> {
1675 let mut stdout = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(1) });
1676 if !stdout.is_terminal() {
1677 return Err(std::io::Error::other(
1678 "Stdout is not a terminal",
1679 ));
1680 }
1681 let termios = sys::attr::get_terminal_attr(stdout.as_fd())?;
1682 if flags.contains(TerminalFlags::RAW_MODE) {
1683 let mut raw_term = termios;
1684 sys::attr::raw_terminal_attr(&mut raw_term);
1685 sys::attr::set_terminal_attr(stdout.as_fd(), &raw_term)?;
1686 }
1687 write_enable_terminal_flags(&mut stdout, flags)?;
1688 Ok(Terminal {
1689 fd: stdout,
1690 flags,
1691 termios,
1692 })
1693 }
1694 pub fn size(&self) -> std::io::Result<(u16, u16)> {
1696 sys::size::terminal_size_fd(&self.fd.as_fd())
1697 }
1698}
1699
1700impl Drop for Terminal {
1701 fn drop(&mut self) {
1702 let _ = write_disable_terminal_flags(&mut self.fd, self.flags);
1703 let _ = sys::attr::set_terminal_attr(self.fd.as_fd(), &self.termios);
1704 }
1705}
1706
1707pub struct DoubleBuffer {
1726 current: Buffer,
1727 previous: Buffer,
1728 pub buf: Vec<u8>,
1730 diffable: bool,
1731 blanking: bool,
1732 scroll: i16,
1733 #[doc(hidden)]
1734 pub y_offset: u16,
1735 epoch: u64,
1736}
1737
1738impl DoubleBuffer {
1739 pub fn rect(&self) -> Rect {
1741 Rect {
1742 x: 0,
1743 y: 0,
1744 w: self.current.width,
1745 h: self.current.height,
1746 }
1747 }
1748 pub fn epoch(&self) -> u64 {
1750 self.epoch
1751 }
1752
1753 pub fn width(&self) -> u16 {
1755 self.current.width
1756 }
1757
1758 pub fn height(&self) -> u16 {
1760 self.current.height
1761 }
1762 pub(crate) fn set_cell(&mut self, x: u16, y: u16, cell: Cell) {
1763 if let Some(target) = self.current.get_mut(x, y) {
1764 *target = cell;
1765 }
1766 }
1767 pub fn set_style(&mut self, area: Rect, style: Style) {
1769 self.current.set_style(area, style)
1770 }
1771
1772 pub fn set_string(&mut self, x: u16, y: u16, string: &str, style: Style) -> (u16, u16) {
1776 self.current.set_string(x, y, string, style)
1777 }
1778
1779 pub fn set_stringn(
1783 &mut self,
1784 x: u16,
1785 y: u16,
1786 string: &str,
1787 max_width: usize,
1788 style: Style,
1789 ) -> (u16, u16) {
1790 self.current.set_stringn(x, y, string, max_width, style)
1791 }
1792 pub fn new(width: u16, height: u16) -> DoubleBuffer {
1794 DoubleBuffer {
1795 current: Buffer::new(width, height),
1796 previous: Buffer::new(width, height),
1797 diffable: false,
1798 blanking: true,
1799 buf: Vec::with_capacity(16 * 1024),
1800 scroll: 0,
1801 epoch: 0,
1802 y_offset: 0,
1803 }
1804 }
1805
1806 pub fn reset(&mut self) {
1808 self.current.cells.fill(Cell::EMPTY);
1809 self.previous.cells.fill(Cell::EMPTY);
1810 self.diffable = false;
1811 self.epoch = self.epoch.wrapping_add(1);
1812 }
1813 pub fn resize(&mut self, width: u16, height: u16) {
1815 if self.current.width != width || self.current.height != height {
1816 self.current = Buffer::new(width, height);
1817 self.previous = Buffer::new(width, height);
1818 self.diffable = false;
1819 self.epoch = self.epoch.wrapping_add(1);
1820 }
1821 }
1822 pub fn last_write_size(&self) -> usize {
1824 self.buf.len()
1825 }
1826
1827 pub fn write_buffer(&self) -> &[u8] {
1829 &self.buf
1830 }
1831
1832 pub fn scroll(&mut self, amount: i16) {
1836 self.scroll += amount;
1837 }
1838 pub fn render_internal(&mut self) {
1842 if self.diffable {
1843 if self.y_offset == 0 {
1844 if self.scroll < 0 {
1845 vt::CLEAR_STYLE.write_to_buffer(&mut self.buf);
1846 ScrollBufferDown(-self.scroll as u16).write_to_buffer(&mut self.buf);
1847 self.previous.scroll_down(-self.scroll as u16);
1849 } else if self.scroll > 0 {
1850 vt::CLEAR_STYLE.write_to_buffer(&mut self.buf);
1851 ScrollBufferUp(self.scroll as u16).write_to_buffer(&mut self.buf);
1852 self.previous.scroll_up(self.scroll as u16);
1853 }
1854 }
1855 self.scroll = 0;
1856 self.current
1857 .render_diff(&mut self.buf, &self.previous, self.y_offset, self.blanking);
1858 } else {
1859 self.scroll = 0;
1860 self.current.render(&mut self.buf, self.y_offset);
1861 self.diffable = true;
1862 }
1863 std::mem::swap(&mut self.current, &mut self.previous);
1864 self.current.cells.fill(Cell::EMPTY);
1865 }
1866 pub fn render(&mut self, term: &mut Terminal) {
1868 self.render_internal();
1869 term.write_all(&self.buf).unwrap();
1870 self.buf.clear();
1871 }
1872 pub fn current(&mut self) -> &mut Buffer {
1874 &mut self.current
1875 }
1876}
1877
1878#[derive(Clone, PartialEq, Eq, Debug)]
1880pub enum BorderType {
1881 Ascii,
1883 Thin,
1885}
1886
1887impl std::default::Default for Block<'_> {
1888 fn default() -> Self {
1889 Self {
1890 title: Default::default(),
1891 title_alignment: Alignment::Center,
1892 borders: Default::default(),
1893 border_style: Default::default(),
1894 border_type: BorderType::Thin,
1895 style: Default::default(),
1896 }
1897 }
1898}
1899
1900impl Block<'_> {
1901 pub const LEFT: u8 = 0b0001;
1903 pub const RIGHT: u8 = 0b0010;
1905 pub const TOP: u8 = 0b0100;
1907 pub const BOTTOM: u8 = 0b1000;
1909 pub const ALL: u8 = Self::LEFT | Self::RIGHT | Self::TOP | Self::BOTTOM;
1911}
1912
1913#[derive(Clone, PartialEq, Eq, Debug)]
1930pub struct Block<'a> {
1931 pub title: Option<&'a str>,
1933 pub title_alignment: Alignment,
1935 pub borders: u8,
1937 pub border_style: Style,
1939 pub border_type: BorderType,
1941 pub style: Style,
1943}
1944
1945impl Block<'_> {
1946 pub fn render(&self, rect: Rect, buf: &mut Buffer) {
1948 let Rect { x, y, w, h } = rect;
1949 if w == 0 || h == 0 {
1950 return;
1951 }
1952 let box_style = match self.border_type {
1953 BorderType::Ascii => &BoxStyle::ASCII,
1954 BorderType::Thin => &BoxStyle::LIGHT,
1955 };
1956 if self.borders & Self::TOP != 0
1957 && let Some(row) = buf.row_remaining_mut(x, y) {
1958 if w >= 1 {
1959 row[0] = box_style.top_left;
1960 }
1961 for cell in row
1962 .iter_mut()
1963 .take(w as usize)
1964 .skip(1)
1965 .take(w.saturating_sub(2) as usize)
1966 {
1967 *cell = box_style.horizontal;
1968 }
1969 if w >= 2 {
1970 row[w as usize - 1] = box_style.top_right;
1971 }
1972 }
1973 if self.borders & Self::BOTTOM != 0
1974 && let Some(row) = buf.row_remaining_mut(x, y + h - 1) {
1975 if w >= 1 {
1976 row[0] = box_style.bottom_left;
1977 }
1978 for cell in row
1979 .iter_mut()
1980 .take(w as usize)
1981 .skip(1)
1982 .take(w.saturating_sub(2) as usize)
1983 {
1984 *cell = box_style.horizontal;
1985 }
1986 if w >= 2 {
1987 row[w as usize - 1] = box_style.bottom_right;
1988 }
1989 }
1990 for y in y + (self.borders & Self::TOP != 0) as u16
1991 ..y + h - (self.borders & Self::BOTTOM != 0) as u16
1992 {
1993 if self.borders & Self::LEFT != 0
1994 && let Some(row) = buf.row_remaining_mut(x, y) {
1995 row[0] = box_style.vertical;
1996 }
1997 if self.borders & Self::RIGHT != 0
1998 && let Some(row) = buf.row_remaining_mut(x + w - 1, y) {
1999 row[0] = box_style.vertical;
2000 }
2001 }
2002
2003 buf.set_style(rect, self.style);
2004 if let Some(title) = self.title {
2005 let title_len = title.width() as u16;
2006 if title_len + 2 < w {
2007 let title_x = match self.title_alignment {
2008 Alignment::Left => x + 1,
2009 Alignment::Center => x + (w - title_len) / 2,
2010 Alignment::Right => x + w - title_len - 1,
2011 };
2012 buf.set_stringn(title_x, y, title, (w - 2) as usize, self.border_style);
2013 if title_x > x + 1 {
2014 buf.set_stringn(x + 1, y, " ", 1, self.border_style);
2015 }
2016 if title_x + title_len < x + w - 1 {
2017 buf.set_stringn(title_x + title_len, y, " ", 1, self.border_style);
2018 }
2019 }
2020 }
2021 }
2022 pub fn inner(&self, rect: Rect) -> Rect {
2023 let mut x = rect.x;
2024 let mut y = rect.y;
2025 let mut w = rect.w;
2026 let mut h = rect.h;
2027 if self.borders & Self::LEFT != 0 {
2028 x = x.saturating_add(1);
2029 w = w.saturating_sub(1);
2030 }
2031 if self.borders & Self::RIGHT != 0 {
2032 w = w.saturating_sub(1);
2033 }
2034 if self.borders & Self::TOP != 0 {
2035 y = y.saturating_add(1);
2036 h = h.saturating_sub(1);
2037 }
2038 if self.borders & Self::BOTTOM != 0 {
2039 h = h.saturating_sub(1);
2040 }
2041 Rect { x, y, w, h }
2042 }
2043}
2044
2045#[cfg(test)]
2046mod test {
2047 use super::*;
2048
2049 #[test]
2050 fn style_encoding() {
2051 for fg in 0..=255u8 {
2052 let fg = Color(fg);
2053 let colored = Style::DEFAULT.with_fg(fg);
2054 assert_eq!(colored.fg().unwrap(), fg);
2055 for bg in 0..=255u8 {
2056 let bg = Color(bg);
2057 let colored = colored.with_bg(bg);
2058 assert_eq!(colored.bg().unwrap(), bg);
2059 assert_eq!(colored.fg().unwrap(), fg);
2060 }
2061 }
2062 }
2063
2064 pub struct BufferDiffCheck {
2065 db_a: DoubleBuffer,
2066 term_1: vt100::Parser,
2067 db_b: DoubleBuffer,
2068 term_2: vt100::Parser,
2069 step: u32,
2070 }
2071
2072 impl BufferDiffCheck {
2073 pub fn new(width: u16, height: u16) -> BufferDiffCheck {
2074 BufferDiffCheck {
2075 db_a: DoubleBuffer::new(width, height),
2076 term_1: vt100::Parser::new(height, width, 0),
2077 db_b: DoubleBuffer::new(width, height),
2078
2079 term_2: vt100::Parser::new(height, width, 0),
2080 step: 0,
2081 }
2082 }
2083 pub fn step(&mut self, fnx: impl Fn(Rect, &mut DoubleBuffer)) {
2084 self.step += 1;
2085 let rect = Rect {
2086 x: 0,
2087 y: 0,
2088 w: self.db_a.width(),
2089 h: self.db_a.height(),
2090 };
2091 fnx(rect, &mut self.db_a);
2092 self.db_a.render_internal();
2093 self.term_1.process(&self.db_a.buf);
2094
2095 fnx(rect, &mut self.db_b);
2096 self.db_b.render_internal();
2097 self.term_2.process(&self.db_b.buf);
2098
2099 let _ = std::fs::write(format!("/tmp/term_{}.bin", self.step), &self.db_b.buf);
2100
2101 let a = self.term_2.screen().contents();
2102 let b = self.term_1.screen().contents();
2103 for (a, b) in a.lines().zip(b.lines()) {
2104 assert_eq!(a, b);
2105 }
2106 }
2107 }
2108
2109 #[test]
2110 fn blanking_optimization() {
2111 let mut checker = BufferDiffCheck::new(200, 4);
2112 checker.db_b.blanking = false;
2113 checker.step(|mut rect, out| {
2114 for i in 0..4 {
2115 let (mut a, mut b) = rect.take_top(1).h_split(0.5);
2116 a.take_left(6)
2117 .with(Color::LightGoldenrod2.with_fg(Color::Black))
2118 .text(out, " Done ");
2119
2120 a.with(if i == 0 {
2121 Color::Blue1.with_fg(Color::Black)
2122 } else {
2123 Color(248).as_fg()
2124 })
2125 .fill(out)
2126 .skip(1)
2127 .text(out, "die R:0 S:0");
2128
2129 b.take_left(6)
2130 .with(Color::Aquamarine1.with_bg(Color::Grey[4]))
2131 .text(out, " Fail ");
2132 b.with(Color(248).as_fg())
2133 .fill(out)
2134 .skip(1)
2135 .text(out, "cargo run -- die")
2136 .skip(1)
2137 .with(HAlign::Right)
2138 .text(out, "100s ");
2139 }
2140 });
2141 checker.step(|mut rect, out| {
2142 for i in 0..4 {
2143 let (mut a, mut b) = rect.take_top(1).h_split(0.5);
2144 a.take_left(6)
2145 .with(Color::LightGoldenrod2.with_fg(Color::Black))
2146 .text(out, " Done ");
2147
2148 a.with(if i == 1 {
2149 Color::Blue1.with_fg(Color::Black)
2150 } else {
2151 Color(248).as_fg()
2152 })
2153 .fill(out)
2154 .skip(1)
2155 .text(out, "die R:0 S:0");
2156
2157 b.take_left(6)
2158 .with(Color::BlueViolet.with_bg(Color::Grey[4]))
2159 .text(out, " Done ");
2160 b.with(Color(248).as_fg())
2161 .fill(out)
2162 .skip(1)
2163 .text(out, "cargo info")
2164 .skip(1)
2165 .with(HAlign::Right)
2166 .text(out, "101s ");
2167 }
2168 });
2169 }
2170
2171 #[test]
2172 fn buffer_set_style() {
2173 let mut buffer = Buffer::new(8, 1);
2174 buffer.set_string(1, 0, "hello", Style::DEFAULT);
2175 buffer.set_style(
2176 Rect {
2177 x: 0,
2178 y: 0,
2179 w: 5,
2180 h: 1,
2181 },
2182 Color(1).as_bg(),
2183 );
2184 buffer.set_style(
2185 Rect {
2186 x: 2,
2187 y: 0,
2188 w: 6,
2189 h: 1,
2190 },
2191 Color(2).as_fg(),
2192 );
2193 buffer.set_style(
2194 Rect {
2195 x: 3,
2196 y: 0,
2197 w: 1,
2198 h: 1,
2199 },
2200 Style::DEFAULT.with_modifier(Modifier::BOLD),
2201 );
2202 let expected = [
2203 Cell::new("", Color(1).as_bg()),
2204 Cell::new("h", Color(1).as_bg()),
2205 Cell::new("e", Color(1).as_bg() | Color(2).as_fg()),
2206 Cell::new("l", Color(1).as_bg() | Color(2).as_fg() | Modifier::BOLD),
2207 Cell::new("l", Color(1).as_bg() | Color(2).as_fg()),
2208 Cell::new("o", Color(2).as_fg()),
2209 Cell::new("", Color(2).as_fg()),
2210 Cell::new("", Color(2).as_fg()),
2211 ];
2212 for (i, (got, expected)) in buffer.cells.iter().zip(expected.iter()).enumerate() {
2213 assert_eq!(got, expected, "Mismatch at cell {}", i);
2214 }
2215 }
2216}