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