tuigui/
lib.rs

1macro_rules! include {
2    ($module: ident) => {
3        mod $module;
4        pub use $module::*;
5    };
6}
7
8include!(backend);
9include!(widget);
10include!(animation);
11include!(shader);
12include!(content_processor);
13include!(style);
14
15mod type_aliases;
16use type_aliases::*;
17
18pub mod preludes;
19pub mod backends;
20pub mod content_processors;
21pub mod widgets;
22pub mod animations;
23pub mod shaders;
24pub mod event;
25
26use std::io;
27
28#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
29/// Simple enum that dictates what is at a specific location
30pub enum Content {
31    /// Styled character
32    Styled(char, Style),
33    /// Nothing (clear terminal area)
34    Clear,
35}
36
37#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
38/// Context configuration, this contains config options for different
39/// things regarding the context
40pub struct ContextConfig {
41    /// Delay between each frame draw (doesn't block logic loop).
42    /// This option doesn't block, because it is simply a condition
43    /// on whether or not the context should draw, there is no thread
44    /// sleeping going on here
45    pub frame_delay: Option<std::time::Duration>,
46    /// Clear the content buffer before every frame draw
47    pub clear_buffer: bool,
48    /// Specify a custom size for the context to use as the screen size
49    pub custom_size: Option<Size>,
50    /// If true, don't print at absolute values like (0, 0).
51    /// Instead, print relative to where the cursor started
52    pub relative_printing: bool,
53    /// Use damaged area calculation to limit how much of the terminal is re-drawn
54    /// (only re-draw what actually changed) (recommended to always keep this set to 'true')
55    pub damaged_only: bool,
56    /// Clear any pasted text from the event state after a draw call
57    pub clear_paste: bool,
58    /// Allow screen tearing (why would you do this!?)
59    pub allow_screen_tearing: bool,
60}
61
62impl Default for ContextConfig {
63    fn default() -> Self {
64        Self {
65            frame_delay: None,
66            clear_buffer: true,
67            custom_size: None,
68            relative_printing: false,
69            damaged_only: true,
70            clear_paste: true,
71            allow_screen_tearing: false,
72        }
73    }
74}
75
76#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
77/// These are things that should be set and then never touched again.
78/// These things relate to setting up and restoring the terminal environment.
79pub struct ContextSetupConfig {
80    /// Enable raw mode
81    pub raw_mode: bool,
82    /// Use alternate screen
83    pub alt_screen: bool,
84    /// Hide the cursor
85    pub hide_cursor: bool,
86    /// Capture the mouse
87    pub capture_mouse: bool,
88}
89
90impl Default for ContextSetupConfig {
91    fn default() -> Self {
92        Self {
93            raw_mode: true,
94            alt_screen: true,
95            hide_cursor: true,
96            capture_mouse: false,
97        }
98    }
99}
100
101#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
102pub struct DrawSummary {
103    /// The amount of time taken to draw the frame
104    pub duration: std::time::Duration,
105    /// Number of terminal characters altered/printed
106    pub count: usize,
107    /// Index of the drawn buffer
108    pub drawn_buffer: usize,
109}
110
111#[derive(Debug, Clone)]
112/// The context, this handles everything.
113/// It handles drawing the frames, it stores the root
114/// widget, it calls backend commands, etc...
115pub struct Context<CT: ContentProcessorOutput, B: Backend<CT>, C: ContentProcessor<CT>, R: Widget> {
116    pub config: ContextConfig,
117    pub backend: B,
118    pub content_processor: C,
119    pub root: R,
120    pub event_state: event::EventState,
121    last_event_state: Option<event::EventState>,
122    filler: Content,
123    refresh_all: bool,
124    setup_config: ContextSetupConfig,
125    has_drawn_before: bool,
126    last_draw: Option<std::time::Instant>,
127    last_size: Option<Size>,
128    buffers: [Buffer; 2],
129    front_buffer: usize,
130    _phantom: std::marker::PhantomData<CT>,
131}
132
133impl<CT: ContentProcessorOutput, B: Backend<CT>, C: ContentProcessor<CT>, R: Widget> Context<CT, B, C, R> {
134    /// Create a new context
135    pub fn new(config: ContextConfig, setup_config: ContextSetupConfig, backend: B, content_processor: C, root: R) -> Self {
136        Self {
137            config,
138            backend,
139            content_processor,
140            root,
141            setup_config,
142            has_drawn_before: false,
143            last_draw: None,
144            last_size: None,
145            buffers: [
146                Buffer::new(),
147                Buffer::new(),
148            ],
149            front_buffer: 0,
150            filler: Content::Clear,
151            refresh_all: true,
152            event_state: event::EventState::new(),
153            last_event_state: None,
154            _phantom: std::marker::PhantomData,
155        }
156    }
157
158    /// Set up the context (call before logic loop)
159    pub fn setup(&mut self) -> Result<(), io::Error> {
160        self.set_state(true)?;
161
162        Ok(())
163    }
164
165    /// Set up the context (call after logic loop)
166    pub fn cleanup(&mut self) -> Result<(), io::Error> {
167        self.set_state(false)?;
168
169        if self.setup_config.alt_screen == false {
170            self.backend.clear(ClearType::FromCursorDown)?;
171        }
172
173        Ok(())
174    }
175
176    fn set_state(&mut self, enable: bool) -> Result<(), io::Error> {
177        if self.setup_config.capture_mouse {
178            self.backend.capture_mouse(enable)?;
179        }
180
181        if self.setup_config.alt_screen {
182            self.backend.alt_screen(enable)?;
183        }
184
185        if self.setup_config.raw_mode {
186            self.backend.raw_mode(enable)?;
187        }
188
189        if self.setup_config.hide_cursor {
190            self.backend.show_cursor(!enable)?;
191        }
192
193        Ok(())
194    }
195
196    /// Obtain the terminal's previous size
197    pub fn last_size(&self) -> Option<Size> {
198        return self.last_size;
199    }
200
201    /// Obtain the time that the context's previous draw happened
202    pub fn last_draw(&self) -> Option<std::time::Instant> {
203        return self.last_draw;
204    }
205
206    /// The duration since the last frame draw
207    pub fn duration_since_last_draw(&self) -> Option<std::time::Duration> {
208        match self.last_draw() {
209            Some(s) => Some(std::time::Instant::now() - s),
210            None => None,
211        }
212    }
213
214    #[inline(always)]
215    fn should_draw(&self) -> bool {
216        let mut should_draw = true;
217
218        if let Some(frame_delay) = self.config.frame_delay {
219            match self.duration_since_last_draw() {
220                Some(s) => {
221                    if s < frame_delay {
222                        should_draw = false;
223                    }
224                },
225                None => (),
226            };
227        }
228
229        return should_draw;
230    }
231
232    #[inline(always)]
233    fn front_buffer_index(&self) -> usize {
234        return self.front_buffer;
235    }
236
237    #[inline(always)]
238    fn back_buffer_index(&self) -> usize {
239        return match self.front_buffer_index() {
240            0 => 1,
241            1 => 0,
242            _ => unreachable!(),
243        }
244    }
245
246    #[inline(always)]
247    fn swap_buffers(&mut self) {
248        self.front_buffer = self.back_buffer_index();
249    }
250
251    /// Draw the context (this should be called after every update)
252    ///
253    /// This function returns a summary if the draw actually took place
254    /// sometimes drawing a frame is skipped
255    /// (that is how non-blocking frame delay is implemented)
256    pub fn draw(&mut self) -> Result<Option<DrawSummary>, io::Error> {
257        let draw_start_time = std::time::Instant::now();
258
259        let mut draw_count: usize = 0;
260
261        if self.should_draw() == false {
262            return Ok(None);
263        }
264
265        self.swap_buffers();
266
267        let front_index = self.front_buffer_index();
268        let back_index = self.back_buffer_index();
269
270        if self.config.clear_buffer {
271            self.buffers[front_index].clear();
272        }
273
274        let offset = match self.config.relative_printing {
275            true => self.backend.cursor_position()?,
276            false => Position::new(0, 0),
277        };
278
279        let size = match self.config.custom_size {
280            Some(s) => s,
281            None => self.backend.terminal_size()?,
282        } - Size::new(offset.col as u16, offset.row as u16);
283
284        let mut canvas = Canvas::new(
285            Transform::new(
286                Position::zero(),
287                size,
288            ),
289            0,
290            &mut self.buffers[front_index],
291            self.filler.clone(),
292        );
293
294        canvas.transform.size = self.root.widget_info().size_info.correct_size(canvas.transform.size);
295
296        self.root.draw(&mut canvas, Some(&self.event_state_frame()));
297
298        if size != self.last_size.unwrap_or(size) {
299            self.refresh_all = true;
300        }
301
302        if self.config.allow_screen_tearing == false {
303            self.backend.begin_sync_update()?;
304        }
305
306        for row in 0..size.rows {
307            for col in 0..size.cols {
308                let position = Position::new(col as i16, row as i16);
309                let draw_position = position + offset;
310
311                let back_content = self.buffers[back_index].get(position);
312                let front_content = self.buffers[front_index].get(position);
313
314                let should_draw: bool = self.refresh_all
315                    || self.config.damaged_only == false
316                    || front_content != back_content;
317
318                if should_draw {
319                    self.backend.set_cursor_pos(draw_position)?;
320
321                    let content: Content = match self.buffers[front_index].get(position) {
322                        Some(s) => s.clone(),
323                        None => self.filler.clone(),
324                    };
325
326                    self.print(&content, &mut draw_count)?;
327                }
328            }
329        }
330
331        if self.config.allow_screen_tearing == false {
332            self.backend.end_sync_update()?;
333        }
334
335        self.backend.set_cursor_pos(offset)?;
336
337        self.backend.flush()?;
338
339        // Set all the prev stuff.
340        self.last_draw = Some(std::time::Instant::now());
341        self.last_size = Some(size);
342        self.last_event_state = Some(self.event_state.clone());
343
344        // Untick stuff.
345        self.refresh_all = false;
346
347        if self.config.clear_paste {
348            self.event_state.terminal.paste = None;
349        }
350
351        // Very last stuff.
352        self.has_drawn_before = true;
353        let draw_duration = std::time::Instant::now() - draw_start_time;
354        Ok(Some(DrawSummary {
355            duration: draw_duration,
356            count: draw_count,
357            drawn_buffer: front_index,
358        }))
359    }
360
361    #[inline(always)]
362    pub fn event_state_frame(&self) -> event::EventStateFrame {
363        return self.event_state.calculate_frame(self.last_event_state.clone().unwrap_or(self.event_state.clone()));
364    }
365
366    #[inline(always)]
367    pub fn get_last_event_state(&self) -> Option<event::EventState> {
368        return self.last_event_state.clone();
369    }
370
371    #[inline(always)]
372    fn print(&mut self, content: &Content, draw_count: &mut usize) -> Result<(), io::Error> {
373        self.backend.print(match content {
374            Content::Clear => CT::clear_output(),
375            Content::Styled(character, style) => {
376                self.content_processor.process(*character, style)
377            },
378        })?;
379
380        *draw_count += 1;
381
382        Ok(())
383    }
384
385    #[inline(always)]
386    /// This is the content that will be used in place
387    /// of empty spots not drawn over by a canvas
388    /// (the background content essentially)
389    pub fn set_filler(&mut self, content: Content) {
390        if self.filler != content {
391            self.refresh_all = true;
392        }
393
394        self.filler = content;
395    }
396
397    #[inline(always)]
398    pub fn get_filler(&self) -> &Content {
399        return &self.filler;
400    }
401}
402
403#[derive(Debug, Clone, Eq, PartialEq)]
404struct Buffer {
405    map: HashMap<Position, Content>,
406}
407
408impl Buffer {
409    pub fn new() -> Self {
410        Self {
411            map: HashMap::new(),
412        }
413    }
414
415    #[inline(always)]
416    pub fn set(&mut self, position: Position, content: Option<Content>) {
417        match content {
418            Some(s) => self.map.insert(position, s),
419            None => self.map.remove(&position),
420        };
421    }
422
423    #[inline(always)]
424    pub fn get(&self, position: Position) -> Option<&Content> {
425        return self.map.get(&position);
426    }
427
428    #[inline(always)]
429    pub fn clear(&mut self) {
430        self.map = HashMap::new(); // This is faster than .clear() for some reason.
431    }
432}
433
434/// A canvas is how a widget displays contents
435/// you can create a mutable canvas with a certain size and position
436/// relative to the parent, and then tell a widget to draw to that canvas
437/// via a mutable reference
438pub struct Canvas {
439    /// Transform with modifications
440    pub transform: Transform,
441    /// Pointer to the buffer
442    buffer_pointer: *mut Buffer,
443    /// All the positions of the content that were set by this canvas
444    set_by_canvas: HashMap<Position, ()>,
445    /// Transform without modifications
446    transform_original: Transform,
447    /// Canvas hierarchy depth (0 == root)
448    depth: u32,
449    /// Filler content
450    filler: Content,
451}
452
453impl Canvas {
454    /// Create a new canvas
455    fn new(
456        transform: Transform,
457        depth: u32,
458        buffer_pointer: *mut Buffer,
459        filler: Content,
460    ) -> Self {
461        return Self {
462            transform,
463            buffer_pointer,
464            set_by_canvas: HashMap::new(),
465            transform_original: transform,
466            depth,
467            filler,
468        };
469    }
470
471    #[inline(always)]
472    /// Create new child canvas (transform argument is offset)
473    pub fn new_child(&self, transform: Transform) -> Self {
474        return Self::new(
475            transform.offset_by(self.transform.position),
476            self.depth + 1,
477            self.buffer_pointer,
478            self.filler.clone(),
479        );
480    }
481
482    #[inline(always)]
483    /// Create new child canvas with the same transform as the parent
484    pub fn new_copy_child(&self) -> Self {
485        return self.new_child(Transform::new(Position::zero(), self.transform.size));
486    }
487
488    #[inline(always)]
489    /// Get canvas hierarchy depth (root == 0)
490    pub fn depth(&self) -> u32 {
491        return self.depth;
492    }
493
494    #[inline(always)]
495    /// Returns 'true' if the canvas is the root canvas (hierarchy depth of 0)
496    pub fn is_root(&self) -> bool {
497        return self.depth == 0;
498    }
499
500    #[inline(always)]
501    /// Animate canvas
502    pub fn animate<A: Animation>(
503        &mut self,
504        animation: &mut A,
505        animation_data: &AnimationData,
506        custom_original: Option<Transform>,
507    ) {
508        self.animate_with_offset(animation, animation_data, custom_original, 0.0);
509    }
510
511    #[inline(always)]
512    /// Animate canvas (with extra offset option)
513    pub fn animate_with_offset<A: Animation>(
514        &mut self,
515        animation: &mut A,
516        animation_data: &AnimationData,
517        custom_original: Option<Transform>,
518        offset: f64,
519    ) {
520        let original = custom_original.unwrap_or(self.original_transform());
521
522        self.transform = animate(animation, animation_data, original, offset);
523    }
524
525    #[inline(always)]
526    /// Original transform (canvas transform without modifications)
527    pub fn original_transform(&self) -> Transform {
528        return self.transform_original;
529    }
530
531    #[inline(always)]
532    /// Returns 'true' if the canvas is actually visible,
533    /// the canvas is not visible when either the rows or columns
534    /// in the canvas size are 0
535    pub fn is_visible(&self) -> bool {
536        if self.transform.size.cols == 0 || self.transform.size.rows == 0 {
537            return false;
538        }
539
540        return true;
541    }
542
543    #[inline(always)]
544    /// Returns 'true' if the content at that position relative
545    /// to the canvas has been changed by the canvas
546    pub fn changed_at(&self, position: Position) -> bool {
547        return !(self.set_by_canvas.get(&position) == None);
548    }
549
550    #[inline(always)]
551    /// Get content at a specified position on the canvas
552    pub fn get(&self, position: Position) -> Option<&Content> {
553        if self.transform.zero_position().contains_point(position) == false {
554            return None;
555        }
556
557        let real_position = self.transform.position + position;
558
559        let buffer = unsafe { &*self.buffer_pointer };
560
561        return buffer.get(real_position);
562    }
563
564    #[inline(always)]
565    /// Set content at a specified position on the canvas
566    pub fn set(&mut self, position: Position, content: Option<Content>) {
567        if self.transform.zero_position().contains_point(position) == false {
568            return;
569        }
570
571        let real_position = self.transform.position + position;
572        let mut content = content;
573
574        let buffer = unsafe { &mut *self.buffer_pointer };
575
576        if let Some(Content::Styled(_, ref mut style)) = content {
577            let behind_content = buffer.get(real_position).unwrap_or(&self.filler);
578
579            *style = overlay_transparent(style, Some(behind_content), Some(&self.filler));
580        }
581
582        buffer.set(real_position, content);
583        self.set_by_canvas.insert(position, ());
584    }
585}
586
587#[inline(always)]
588fn overlay_transparent(front: &Style, back: Option<&Content>, filler: Option<&Content>) -> Style {
589    let mut style = front.clone();
590
591    macro_rules! style_apply {
592        ($fg_bg: ident, $opposite_fg_bg: ident, $pull_layer: expr, $back: ident) => {
593            if let Some(content) = $back {
594                match content {
595                    Content::Styled(_, b_style) => {
596                        style.$fg_bg = match $pull_layer {
597                            StylePullLayer::Foreground => b_style.fg,
598                            StylePullLayer::Background => b_style.bg,
599                            StylePullLayer::Same => b_style.$fg_bg,
600                            StylePullLayer::Any | StylePullLayer::AnyColor => {
601                                let color_only = $pull_layer == StylePullLayer::AnyColor;
602
603                                if (b_style.$fg_bg.is_final() && color_only == false)
604                                || (b_style.$fg_bg.is_color() && color_only) {
605                                    b_style.$fg_bg
606                                } else {
607                                    if (b_style.$opposite_fg_bg.is_final() && color_only == false)
608                                    || (b_style.$opposite_fg_bg.is_color() && color_only) {
609                                        b_style.$opposite_fg_bg
610                                    } else {
611                                        b_style.$fg_bg
612                                    }
613                                }
614                            },
615                        };
616                    },
617                    Content::Clear => style.$fg_bg = StyleGround::Clear,
618                };
619            }
620        };
621    }
622
623    macro_rules! apply {
624        ($fg_bg: ident, $opposite_fg_bg: ident) => {
625            if style.$fg_bg.is_final() == false {
626                if let StyleGround::Filler(pull_layer) = style.$fg_bg {
627                    style_apply!($fg_bg, $opposite_fg_bg, pull_layer, filler);
628                }
629
630                else if let StyleGround::Transparent(pull_layer) = style.$fg_bg {
631                    style_apply!($fg_bg, $opposite_fg_bg, pull_layer, back);
632                }
633            }
634        };
635    }
636
637    apply!(fg, bg);
638    apply!(bg, fg);
639
640    return style;
641}
642
643pub fn compute_refresh_area(
644    damaged: Option<Transform>,
645    prev_damaged: Option<Transform>,
646    has_drawn_before: bool,
647    full_size: Size,
648) -> Option<Transform> {
649    let last_damaged: Option<Transform> = match (prev_damaged, has_drawn_before) {
650        (None, false) => Some(Transform::new(Position::zero(), full_size)),
651        (l_d, _) => l_d,
652    };
653
654    return match (damaged, last_damaged) {
655        (Some(damaged), Some(last_damaged)) => Some(damaged.combined_area(last_damaged)),
656        (Some(damaged), None) => Some(damaged),
657        (None, Some(last_damaged)) => Some(last_damaged),
658        (None, None) => None,
659    };
660}
661
662#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
663/// Terminal transform for an area
664pub struct Transform {
665    pub position: Position,
666    pub size: Size,
667}
668
669impl Transform {
670    pub fn new(position: Position, size: Size) -> Self {
671        Self {
672            position,
673            size,
674        }
675    }
676
677    #[inline(always)]
678    pub fn zero() -> Self {
679        Self::new(Position::zero(), Size::zero())
680    }
681
682    #[inline(always)]
683    /// Return a copy of the transform with the position set to (0, 0)
684    pub fn zero_position(&self) -> Self {
685        return Self::new(Position::zero(), self.size);
686    }
687
688    #[inline(always)]
689    /// Return a copy of the transform with the position offset by the specified position
690    pub fn offset_by(&self, offset: Position) -> Self {
691        return Self::new(self.position + offset, self.size);
692    }
693
694    #[inline(always)]
695    /// Returns 'true' if the given position lies somewhere inside the transform as an area
696    ///
697    /// # Examples:
698    /// ```rust
699    /// use tuigui::{ Transform, Position, Size };
700    ///
701    /// fn main() {
702    ///     let area = Transform::new(Position::new(12, 8), Size::new(10, 6));
703    ///     let points = [ // (position, inside)
704    ///         (Position::new(12, 8), true),
705    ///         (Position::new(21, 13), true),
706    ///         (Position::new(22, 13), false),
707    ///         (Position::new(15, 10), true),
708    ///     ];
709    ///
710    ///     for i in points {
711    ///         assert_eq!(area.contains_point(i.0), i.1);
712    ///     }
713    /// }
714    /// ```
715    pub fn contains_point(&self, position: Position) -> bool {
716        if position.col >= self.position.col
717        && position.row >= self.position.row {
718            if position.col < self.size.cols as i16 + self.position.col
719            && position.row < self.size.rows as i16 + self.position.row {
720                return true;
721            }
722        }
723
724        return false;
725    }
726
727    #[inline(always)]
728    /// Same as combining for total area, but treating the position
729    /// as a transform of size (1, 1)
730    ///
731    /// # Examples:
732    /// ```rust
733    /// use tuigui::{ Transform, Position, Size };
734    ///
735    /// fn main() {
736    ///     let area = Transform::new(Position::new(12, 8), Size::new(10, 6));
737    ///     let expanded = area.expand_to_position(Position::new(4, 5));
738    ///
739    ///     assert_eq!(expanded, Transform::new(Position::new(4, 5), Size::new(18, 9)));
740    /// }
741    /// ```
742    pub fn expand_to_position(&self, position: Position) -> Self {
743        return self.combined_area(Self::new(position, Size::new(1, 1)));
744    }
745
746    #[inline(always)]
747    /// Return the total rectangular area of 2 transforms
748    ///
749    /// # Examples:
750    /// ```rust
751    /// use tuigui::{ Transform, Position, Size };
752    ///
753    /// fn main() {
754    ///     let area_1 = Transform::new(Position::new(2, 4), Size::new(7, 5));
755    ///     let area_2 = Transform::new(Position::new(10, 11), Size::new(2, 3));
756    ///
757    ///     let total_area = area_1.combined_area(area_2);
758    ///
759    ///     assert_eq!(total_area, Transform::new(Position::new(2, 4), Size::new(10, 10)));
760    /// }
761    /// ```
762    pub fn combined_area(&self, other: Self) -> Self {
763        let mut total = *self;
764
765        if other.position.row < total.position.row {
766            total.size.rows += (total.position.row - other.position.row) as u16;
767            total.position.row = other.position.row;
768        }
769
770        if other.position.col < total.position.col {
771            total.size.cols += (total.position.col - other.position.col) as u16;
772            total.position.col = other.position.col;
773        }
774
775        if other.size.rows as i16 + other.position.row >= total.size.rows as i16 + total.position.row {
776            total.size.rows = ((other.size.rows as i16 + other.position.row) - total.position.row) as u16;
777        }
778
779        if other.size.cols as i16 + other.position.col >= total.size.cols as i16 + total.position.col {
780            total.size.cols = ((other.size.cols as i16 + other.position.col) - total.position.col) as u16;
781        }
782
783        return total;
784    }
785
786    #[inline(always)]
787    pub fn apply_lerp(&self, b: Self, t: f64, f: fn(f64, f64, f64) -> f64) -> Self {
788        return Self::new(
789            Position {
790                col: f(self.position.col as f64, b.position.col as f64, t) as i16,
791                row: f(self.position.row as f64, b.position.row as f64, t) as i16,
792            },
793            Size {
794                cols: f(self.size.cols as f64, b.size.cols as f64, t) as u16,
795                rows: f(self.size.rows as f64, b.size.rows as f64, t) as u16,
796            },
797        );
798    }
799
800    #[inline(always)]
801    pub fn apply_quadratic_bezier(&self, control: Self, b: Self, t: f64, f: fn(f64, f64, f64, f64) -> f64) -> Self {
802        return Self::new(
803            Position {
804                col: f(self.position.col as f64, control.position.col as f64, b.position.col as f64, t) as i16,
805                row: f(self.position.row as f64, control.position.row as f64, b.position.row as f64, t) as i16,
806            },
807            Size {
808                cols: f(self.size.cols as f64, control.size.cols as f64, b.size.cols as f64, t) as u16,
809                rows: f(self.size.rows as f64, control.size.rows as f64, b.size.rows as f64, t) as u16,
810            },
811        );
812    }
813
814    #[inline(always)]
815    pub fn apply_cubic_bezier(&self, a_control: Self, b: Self, b_control: Self, t: f64, f: fn(f64, f64, f64, f64, f64) -> f64) -> Self {
816        return Self::new(
817            Position {
818                col: f(self.position.col as f64, a_control.position.col as f64, b.position.col as f64, b_control.position.col as f64, t) as i16,
819                row: f(self.position.row as f64, a_control.position.row as f64, b.position.row as f64, b_control.position.row as f64, t) as i16,
820            },
821            Size {
822                cols: f(self.size.cols as f64, a_control.size.cols as f64, b.size.cols as f64, b_control.size.cols as f64, t) as u16,
823                rows: f(self.size.rows as f64, a_control.size.rows as f64, b.size.rows as f64, b_control.size.rows as f64, t) as u16,
824            },
825        );
826    }
827}
828
829#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
830/// Size of an area in the terminal
831pub struct Size {
832    pub cols: u16,
833    pub rows: u16,
834}
835
836impl Size {
837    pub fn new(cols: u16, rows: u16) -> Self {
838        Self {
839            cols,
840            rows,
841        }
842    }
843
844    #[inline(always)]
845    pub fn zero() -> Self {
846        Self::new(0, 0)
847    }
848
849    #[inline(always)]
850    pub fn same(cols_rows: u16) -> Self {
851        Self::new(cols_rows, cols_rows)
852    }
853
854    #[inline(always)]
855    /// Get the total area of a size (cols * rows)
856    pub fn area(&self) -> u32 {
857        return (self.cols as u32) * (self.rows as u32);
858    }
859}
860
861#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
862/// Position in the terminal where (col: 0, row: 0) is the top left
863pub struct Position {
864    pub col: i16,
865    pub row: i16,
866}
867
868impl Position {
869    pub fn new(col: i16, row: i16) -> Self {
870        Self {
871            col,
872            row,
873        }
874    }
875
876    #[inline(always)]
877    pub fn zero() -> Self {
878        Self::new(0, 0)
879    }
880
881    #[inline(always)]
882    pub fn same(col_row: i16) -> Self {
883        Self::new(col_row, col_row)
884    }
885}
886
887macro_rules! op_impl_core {
888    ($op: ident, $func: ident, $subfunc: ident) => {
889        impl std::ops::$op for Size {
890            type Output = Self;
891
892            fn $func(self, rhs: Self) -> Self::Output {
893                Self {
894                    cols: self.cols.$subfunc(rhs.cols),
895                    rows: self.rows.$subfunc(rhs.rows),
896                }
897            }
898        }
899
900        impl std::ops::$op for Position {
901            type Output = Self;
902
903            fn $func(self, rhs: Self) -> Self::Output {
904                Self {
905                    col: self.col.$subfunc(rhs.col),
906                    row: self.row.$subfunc(rhs.row),
907                }
908            }
909        }
910
911        impl std::ops::$op for Transform {
912            type Output = Self;
913
914            fn $func(self, rhs: Self) -> Self::Output {
915                Self {
916                    position: self.position.$func(rhs.position),
917                    size: self.size.$func(rhs.size),
918                }
919            }
920        }
921    };
922}
923
924macro_rules! op_impl {
925    ($op: ident, $func: ident) => {
926        op_impl_core!($op, $func, $func);
927    };
928    ($op: ident, $func: ident, $subfunc: ident) => {
929        op_impl_core!($op, $func, $subfunc);
930    };
931}
932
933op_impl!(Add, add, saturating_add);
934op_impl!(Sub, sub, saturating_sub);
935op_impl!(Mul, mul, saturating_mul);
936op_impl!(Div, div, saturating_div);
937op_impl!(Rem, rem);
938op_impl!(Shl, shl);
939op_impl!(Shr, shr);