matchmaker/ui/
preview.rs

1use log::error;
2use ratatui::{
3    layout::Rect,
4    widgets::{Paragraph, Wrap},
5};
6
7use crate::{
8    config::{PreviewConfig, PreviewLayoutSetting},
9    preview::Preview,
10};
11
12#[derive(Debug)]
13pub struct PreviewUI {
14    pub view: Preview,
15    config: PreviewConfig,
16    pub layout_idx: usize,
17    pub area: Rect,
18    pub offset: u16,
19}
20
21impl PreviewUI {
22    pub fn new(view: Preview, config: PreviewConfig) -> Self {
23        Self {
24            view,
25            config,
26            layout_idx: 0,
27            offset: 0,
28            area: Rect::default(),
29        }
30    }
31    pub fn update_dimensions(&mut self, area: &Rect) {
32        let mut height = area.height;
33        height -= self.config.border.height();
34        self.area.height = height;
35
36        let mut width = area.width;
37        width -= self.config.border.width();
38        self.area.width = width;
39    }
40
41    // -------- Layout -----------
42    // None if not show
43    pub fn layout(&self) -> Option<&PreviewLayoutSetting> {
44        if !self.config.show || self.config.layout.is_empty() {
45            None
46        } else {
47            let ret = &self.config.layout[self.layout_idx].layout;
48            if ret.max == 0 { None } else { Some(ret) }
49        }
50    }
51    pub fn command(&self) -> &str {
52        if self.config.layout.is_empty() {
53            ""
54        } else {
55            self.config.layout[self.layout_idx].command.as_str()
56        }
57    }
58    pub fn cycle_layout(&mut self) {
59        self.layout_idx = (self.layout_idx + 1) % self.config.layout.len()
60    }
61    pub fn set_idx(&mut self, idx: u8) -> bool {
62        let idx = idx as usize;
63        if idx <= self.config.layout.len() {
64            let changed = self.layout_idx != idx;
65            self.layout_idx = idx;
66            changed
67        } else {
68            error!("Layout idx {idx} out of bounds, ignoring.");
69            false
70        }
71    }
72
73    // ----- config ---------
74    pub fn is_show(&self) -> bool {
75        self.layout().is_some()
76    }
77    // cheap show toggle + change tracking
78    pub fn show<const SHOW: bool>(&mut self) -> bool {
79        let previous = self.config.show;
80        self.config.show = SHOW;
81        previous != SHOW
82    }
83    pub fn toggle_show(&mut self) {
84        self.config.show = !self.config.show;
85    }
86
87    pub fn wrap(&mut self, wrap: bool) {
88        self.config.wrap = wrap;
89    }
90    pub fn is_wrap(&self) -> bool {
91        self.config.wrap
92    }
93
94    // ----- actions --------
95    pub fn up(&mut self, n: u16) {
96        if self.offset >= n {
97            self.offset -= n;
98        } else if self.config.scroll_wrap {
99            let total_lines = self.view.len() as u16;
100            self.offset = total_lines.saturating_sub(n - self.offset);
101        } else {
102            self.offset = 0;
103        }
104    }
105    pub fn down(&mut self, n: u16) {
106        let total_lines = self.view.len() as u16;
107
108        if self.offset + n > total_lines {
109            if self.config.scroll_wrap {
110                self.offset = 0;
111            } else {
112                self.offset = total_lines;
113            }
114        } else {
115            self.offset += n;
116        }
117    }
118
119    pub fn make_preview(&self) -> Paragraph<'_> {
120        let results = self.view.results();
121        let height = self.area.height as usize;
122        let offset = self.offset as usize;
123
124        // todo: can we avoid cloning?
125        let visible_lines: Vec<_> = results.iter().skip(offset).take(height).cloned().collect();
126
127        let mut preview = Paragraph::new(visible_lines);
128        preview = preview.block(self.config.border.as_block());
129        if self.config.wrap {
130            preview = preview.wrap(Wrap { trim: true });
131        }
132        preview
133    }
134}