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