1mod input;
2mod preview;
3mod results;
4mod display;
5mod overlay;
6pub use display::DisplayUI;
7pub use input::InputUI;
8pub use preview::PreviewUI;
9pub use results::ResultsUI;
10pub use overlay::*;
11
12pub use ratatui::{
13 layout::{Constraint, Direction, Layout, Rect},
14 widgets::Table,
15 Frame
16}; use crate::{
19 SSS, Selection, Selector, config::{
20 DisplayConfig, InputConfig, PreviewLayoutSetting, RenderConfig, ResultsConfig, TerminalLayoutSettings, UiConfig
21 }, nucleo::Worker, preview::Preview, tui::Tui
22};
23pub struct UI {
25 pub layout: Option<TerminalLayoutSettings>,
26 pub area: Rect, pub config: UiConfig
28}
29
30impl UI {
31 pub fn new<'a, T: SSS, S: Selection, W: std::io::Write>(
32 mut config: RenderConfig,
33 matcher: &'a mut nucleo::Matcher,
34 worker: Worker<T>,
35 selection_set: Selector<T, S>,
36 view: Option<Preview>,
37 tui: &mut Tui<W>,
38 ) -> (Self, PickerUI<'a, T, S>, Option<PreviewUI>) {
39 if config.results.reverse.is_none() {
40 config.results.reverse = Some(
41 tui.is_fullscreen() && tui.area.y < tui.area.height / 2 );
43 }
44
45 let ui = Self {
46 layout: tui.config.layout.clone(),
47 area: tui.area,
48 config: config.ui
49 };
50
51 let picker = PickerUI::new(config.results, config.input, config.header, config.footer, matcher, worker, selection_set);
52
53 let preview = if let Some(view) = view {
54 Some(PreviewUI::new(view, config.preview))
55 } else {
56 None
57 };
58
59 (ui, picker, preview)
60 }
61
62 pub fn update_dimensions(&mut self, area: Rect) {
63 self.area = area;
64 }
65
66 pub fn make_ui(&self) -> ratatui::widgets::Block<'_> {
67 self.config.border.as_block()
68 }
69
70 pub fn inner_area(&self, area: &Rect) -> Rect {
71 Rect {
72 x: area.x + self.config.border.left(),
73 y: area.y + self.config.border.top(),
74 width: area.width.saturating_sub(self.config.border.width()),
75 height: area.height.saturating_sub(self.config.border.height()),
76 }
77 }
78}
79
80pub struct PickerUI<'a, T: SSS, S: Selection> {
81 pub results: ResultsUI,
82 pub input: InputUI,
83 pub header: DisplayUI,
84 pub footer: DisplayUI,
85 pub matcher: &'a mut nucleo::Matcher,
86 pub selections: Selector<T, S>,
87 pub worker: Worker<T>,
88}
89
90impl<'a, T: SSS, S: Selection> PickerUI<'a, T, S> {
91 pub fn new(
92 results_config: ResultsConfig,
93 input_config: InputConfig,
94 header_config: DisplayConfig,
95 footer_config: DisplayConfig,
96 matcher: &'a mut nucleo::Matcher,
97 worker: Worker<T>,
98 selections: Selector<T, S>,
99 ) -> Self {
100 Self {
101 results: ResultsUI::new(results_config),
102 input: InputUI::new(input_config),
103 header: DisplayUI::new(header_config),
104 footer: DisplayUI::new(footer_config),
105 matcher,
106 selections,
107 worker,
108 }
109 }
110
111 pub fn layout(&self, area: Rect) -> [Rect; 5] {
112 let PickerUI { input, header, footer, .. } = self;
113
114 let mut constraints = [
115 Constraint::Length(1 + input.config.border.height()), Constraint::Length(1), Constraint::Length(header.height()),
118 Constraint::Fill(1), Constraint::Length(footer.height()),
120 ];
121
122 if self.reverse() {
123 constraints.reverse();
124 }
125
126 let chunks = Layout::default()
127 .direction(Direction::Vertical)
128 .constraints(constraints)
129 .split(area);
130
131 if self.reverse() {
132 [chunks[4], chunks[3], chunks[2], chunks[1], chunks[0]]
133 } else {
134 [chunks[0], chunks[1], chunks[2], chunks[3], chunks[4]]
135 }
136 }
137}
138
139impl<'a, T: SSS, O: Selection> PickerUI<'a, T, O> {
140 pub fn make_table(&mut self) -> Table<'_> {
141 self.results
142 .make_table(&mut self.worker, &mut self.selections, self.matcher)
143 }
144
145 pub fn update(&mut self) {
146 self.worker.find(&self.input.input);
147 }
148
149 pub fn reverse(&self) -> bool {
151 self.results.reverse()
152 }
153}
154
155impl PreviewLayoutSetting {
156 pub fn split(&self, area: Rect) -> [Rect; 2] {
157 use crate::config::Side;
158 use ratatui::layout::{Constraint, Direction, Layout};
159
160 let direction = match self.side {
161 Side::Left | Side::Right => Direction::Horizontal,
162 Side::Top | Side::Bottom => Direction::Vertical,
163 };
164
165 let side_first = matches!(self.side, Side::Left | Side::Top);
166
167 let total = if matches!(direction, Direction::Horizontal) {
168 area.width
169 } else {
170 area.height
171 };
172
173 let p = self.percentage.inner();
174
175 let mut side_size = if p != 0 { total * p / 100 } else { 0 };
176
177 let min = if self.min < 0 {
178 total.saturating_sub((-self.min) as u16)
179 } else {
180 self.min as u16
181 };
182
183 let max = if self.max < 0 {
184 total.saturating_sub((-self.max) as u16)
185 } else {
186 self.max as u16
187 };
188
189 side_size = side_size.clamp(min, max);
190
191 let side_constraint = Constraint::Length(side_size);
192
193 let constraints = if side_first {
194 [side_constraint, Constraint::Min(0)]
195 } else {
196 [Constraint::Min(0), side_constraint]
197 };
198
199 let chunks = Layout::default()
200 .direction(direction)
201 .constraints(constraints)
202 .split(area);
203
204 if side_first {
205 [chunks[0], chunks[1]]
206 } else {
207 [chunks[1], chunks[0]]
208 }
209 }
210}