1use ratatui::{
2 layout::{Constraint, Direction, Layout, Rect},
3 widgets::Table,
4};
5
6use crate::{
7 PickerItem, Selection, SelectionSet,
8 config::{
9 InputConfig, PreviewLayoutSetting, RenderConfig, ResultsConfig, TerminalLayoutSettings
10 },
11 nucleo::worker::Worker,
12 spawn::preview::PreviewerView,
13 tui::Tui,
14};
15
16mod input;
17mod picker;
18mod preview;
19mod results;
20pub use input::InputUI;
21pub use preview::PreviewUI;
22pub use results::ResultsUI;
23
24pub struct UI {
27 pub layout: Option<TerminalLayoutSettings>,
28 pub area: Rect,
29}
30
31impl UI {
32 pub fn new<'a, T: PickerItem, S: Selection, C, W: std::io::Write>(
33 mut config: RenderConfig,
34 matcher: &'a mut nucleo::Matcher,
35 worker: Worker<T, C>,
36 selection_set: SelectionSet<T, S>,
37 view: Option<PreviewerView>,
38 tui: &mut Tui<W>,
39 ) -> (Self, PickerUI<'a, T, S, C>, Option<PreviewUI>) {
40 if config.results.reverse.is_none() {
41 config.results.reverse = Some(
42 tui.is_fullscreen() && tui.area.y < tui.area.height / 2
43 );
44 }
45
46 let ui = Self {
47 layout: tui.layout().clone(),
48 area: tui.area.clone(),
49 };
50
51 let picker = PickerUI::new(config.results, config.input, 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
67pub struct PickerUI<'a, T: PickerItem, S: Selection, C> {
68 pub results: ResultsUI,
69 pub input: InputUI,
70 pub matcher: &'a mut nucleo::Matcher,
71 pub selections: SelectionSet<T, S>,
72 pub worker: Worker<T, C>,
73}
74
75impl<'a, T: PickerItem, S: Selection, C> PickerUI<'a, T, S, C> {
76 pub fn new(
77 results_config: ResultsConfig,
78 input_config: InputConfig,
79 matcher: &'a mut nucleo::Matcher,
80 worker: Worker<T, C>,
81 selections: SelectionSet<T, S>,
82 ) -> Self {
83 Self {
84 results: ResultsUI::new(results_config),
85 input: InputUI::new(input_config),
86 matcher,
87 selections,
88 worker,
89 }
90 }
91
92 pub fn layout(&self, area: Rect) -> [Rect; 3] {
93 let PickerUI { input, .. } = self;
94
95 let mut constraints = [
96 Constraint::Length(input.height()),
97 Constraint::Length(1),
98 Constraint::Fill(1),
99 ];
100
101 if self.reverse() {
102 constraints.reverse();
103 }
104
105 let chunks = Layout::default()
106 .direction(Direction::Vertical)
107 .constraints(constraints)
108 .split(area);
109
110 if self.reverse() {
111 [chunks[2], chunks[1], chunks[0]]
112 } else {
113 [chunks[0], chunks[1], chunks[2]]
114 }
115 }
116}
117
118impl<'a, T: PickerItem, O: Selection, C> PickerUI<'a, T, O, C> {
119 pub fn make_table(&mut self) -> Table<'_> {
120 self.results
121 .make_table(&mut self.worker, &mut self.selections, self.matcher)
122 }
123
124 pub fn update(&mut self) {
125 self.worker.find(&self.input.input);
126 }
127
128 pub fn reverse(&self) -> bool {
130 self.results.reverse()
131 }
132}
133
134impl PreviewLayoutSetting {
135 pub fn split(&self, area: Rect) -> [Rect; 2] {
136 use crate::config::Side;
137 use ratatui::layout::{Constraint, Direction, Layout};
138
139 let direction = match self.side {
140 Side::Left | Side::Right => Direction::Horizontal,
141 Side::Top | Side::Bottom => Direction::Vertical,
142 };
143
144 let side_first = matches!(self.side, Side::Left | Side::Top);
145
146 let total = if matches!(direction, Direction::Horizontal) {
147 area.width
148 } else {
149 area.height
150 };
151
152 let p = self.percentage.get();
153
154 let mut side_size = if p != 0 { total * p / 100 } else { 0 };
155
156 let min = if self.min < 0 {
157 total.saturating_sub((-self.min) as u16)
158 } else {
159 self.min as u16
160 };
161
162 let max = if self.max < 0 {
163 total.saturating_sub((-self.max) as u16)
164 } else {
165 self.max as u16
166 };
167
168 side_size = side_size.clamp(min, max);
169
170 let side_constraint = Constraint::Length(side_size.max(0));
171
172 let constraints = if side_first {
173 [side_constraint, Constraint::Min(0)]
174 } else {
175 [Constraint::Min(0), side_constraint]
176 };
177
178 let chunks = Layout::default()
179 .direction(direction)
180 .constraints(constraints)
181 .split(area);
182
183 if side_first {
184 [chunks[0], chunks[1]]
185 } else {
186 [chunks[1], chunks[0]]
187 }
188 }
189}