1pub mod cmdline;
2pub mod grid;
3pub mod messages;
4pub mod options;
5pub mod window;
6
7use self::{
8 cmdline::Cmdline, grid::Grid, messages::Messages, options::GuiFont, window::WindowOffset,
9};
10use neophyte_linalg::{CellVec, PixelVec, Vec2};
11use neophyte_ui_event::{
12 hl_attr_define::Attributes, mode_info_set::ModeInfo, Chdir, CmdlineBlockAppend,
13 CmdlineBlockShow, CmdlinePos, DefaultColorsSet, Event, GridClear, GridCursorGoto, GridDestroy,
14 GridLine, GridResize, GridScroll, HlGroupSet, ModeChange, ModeInfoSet, MsgHistoryShow,
15 MsgRuler, MsgSetPos, MsgShowcmd, MsgShowmode, OptionSet, PopupmenuSelect, PopupmenuShow,
16 TablineUpdate, WinClose, WinExternalPos, WinFloatPos, WinHide, WinPos, WinViewport,
17};
18use std::{collections::HashMap, fmt::Debug};
19use window::{FloatingWindow, NormalWindow, Window};
20
21pub type HlId = u32;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct DrawItem {
25 pub grid: grid::Id,
26 pub z: Option<u32>,
27}
28
29impl DrawItem {
30 pub const fn new(grid: grid::Id, z: Option<u32>) -> Self {
31 Self { grid, z }
32 }
33}
34
35#[derive(Clone, Default)]
37pub struct Ui {
38 pub grids: Vec<Grid>,
40 pub draw_order: Vec<DrawItem>,
42 pub float_windows_start: usize,
44 pub cursor: CursorInfo,
46 pub mouse: bool,
48 pub highlights: Vec<Option<Attributes>>,
51 pub highlight_groups: HashMap<String, HlId>,
53 pub did_highlights_change: bool,
55 pub current_mode: u32,
57 pub modes: Vec<ModeInfo>,
59 pub guifont_update: Option<GuiFont>,
61 pub default_colors: DefaultColorsSet,
63 pub messages: Messages,
65 pub cmdline: Cmdline,
67 pub popupmenu: Option<PopupmenuShow>,
69 pub tabline: Option<TablineUpdate>,
71 pub did_flush: bool,
73 pub ignore_next_scroll: bool,
74}
75
76impl Ui {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn grid_index(&self, id: grid::Id) -> Result<usize, usize> {
84 self.grids.binary_search_by(|probe| probe.id.cmp(&id))
85 }
86
87 pub fn grid_mut(&mut self, id: grid::Id) -> Option<&mut Grid> {
89 self.grid_index(id)
90 .map(|i| self.grids.get_mut(i))
91 .ok()
92 .flatten()
93 }
94
95 pub fn grid(&self, id: grid::Id) -> Option<&Grid> {
97 self.grid_index(id)
98 .map(|i| self.grids.get(i))
99 .ok()
100 .flatten()
101 }
102
103 fn get_or_create_grid(&mut self, id: grid::Id) -> &mut Grid {
105 match self.grid_index(id) {
106 Ok(i) => &mut self.grids[i],
107 Err(i) => {
108 self.grids.insert(i, Grid::new(id));
109 self.show_normal(id);
110 &mut self.grids[i]
111 }
112 }
113 }
114
115 pub fn clear_dirty(&mut self) {
117 self.did_highlights_change = false;
118 self.did_flush = false;
119 self.guifont_update = None;
120 self.ignore_next_scroll = false;
121 self.messages.dirty = false;
122 for grid in self.grids.iter_mut() {
123 grid.clear_dirty();
124 }
125 }
126
127 pub fn process(&mut self, event: Event) {
129 match event {
130 Event::OptionSet(event) => match event {
131 OptionSet::Guifont(s) if !s.is_empty() => self.guifont_update = Some(s.into()),
132 _ => {}
133 },
134 Event::DefaultColorsSet(event) => {
135 self.did_highlights_change = true;
136 self.default_colors = event;
137 }
138 Event::HlAttrDefine(event) => {
139 self.did_highlights_change = true;
140 let i = event.id as usize;
141 if i > self.highlights.len() {
142 self.highlights.resize(i * 2, None);
143 }
144 self.highlights.insert(i, Some(event.rgb_attr));
145 }
146 Event::HlGroupSet(HlGroupSet { name, hl_id }) => {
147 self.did_highlights_change = true;
148 self.highlight_groups.insert(name, hl_id);
149 }
150 Event::ModeChange(ModeChange { mode_idx, mode: _ }) => self.current_mode = mode_idx,
151 Event::ModeInfoSet(ModeInfoSet {
152 cursor_style_enabled,
153 mode_info,
154 }) => {
155 self.cursor.style_enabled = cursor_style_enabled;
156 self.modes = mode_info;
157 }
158
159 Event::GridResize(GridResize {
160 grid,
161 width,
162 height,
163 }) => {
164 self.get_or_create_grid(grid)
165 .contents_mut()
166 .resize(CellVec(Vec2::new(width, height)));
167 }
168 Event::GridClear(GridClear { grid }) => self
169 .grid_mut(grid)
170 .expect("Tried to clear nonexistent grid")
171 .contents_mut()
172 .clear(),
173 Event::GridDestroy(GridDestroy { grid }) => self.delete_grid(grid),
174 Event::GridCursorGoto(GridCursorGoto { grid, row, column }) => {
175 self.cursor.pos = CellVec::new(column, row);
176 self.cursor.grid = grid;
177 }
178 Event::GridScroll(GridScroll {
179 grid,
180 top,
181 bot,
182 left,
183 right,
184 rows,
185 cols: _,
186 }) => {
187 self.grid_mut(grid)
188 .expect("Tried to scroll nonexistent grid")
189 .contents_mut()
190 .scroll(top, bot, left, right, rows);
191 }
192 Event::GridLine(GridLine {
193 grid,
194 row,
195 col_start,
196 cells,
197 }) => {
198 self.grid_mut(grid)
199 .expect("Tried to update a line of a nonexistent grid")
200 .contents_mut()
201 .grid_line(row, col_start, cells);
202 }
203
204 Event::WinPos(WinPos {
205 grid,
206 win: _,
207 start_row,
208 start_col,
209 width,
210 height,
211 }) => {
212 self.show_normal(grid);
213 *self
214 .grid_mut(grid)
215 .expect("Tried to update the position of a nonexistent grid")
216 .window_mut() = Window::Normal(NormalWindow {
217 start: CellVec(Vec2::new(start_col, start_row)),
218 size: CellVec(Vec2::new(width, height)),
219 });
220 }
221 Event::WinFloatPos(WinFloatPos {
222 grid,
223 win: _,
224 anchor,
225 anchor_grid,
226 anchor_row,
227 anchor_col,
228 focusable,
229 zindex,
230 }) => {
231 self.show_float(DrawItem::new(grid, zindex));
232 *self
233 .grid_mut(grid)
234 .expect("Tried to update the position of a nonexistent grid")
235 .window_mut() = Window::Floating(FloatingWindow {
236 anchor,
237 focusable,
238 anchor_grid,
239 anchor_pos: CellVec(Vec2::new(anchor_col, anchor_row)),
240 });
241 }
242 Event::WinExternalPos(WinExternalPos { grid, win: _ }) => {
243 *self
244 .grid_mut(grid)
245 .expect("Tried to update the position of a nonexistent grid")
246 .window_mut() = Window::External;
247 }
248 Event::WinHide(WinHide { grid }) => {
249 self.hide(grid);
250 }
251 Event::WinClose(WinClose { grid }) => {
252 self.hide(grid);
253 if let Some(grid) = self.grid_mut(grid) {
257 *grid.window_mut() = Window::None;
258 }
259 }
260 Event::WinViewport(WinViewport {
261 grid,
262 scroll_delta,
263 win: _,
264 topline: _,
265 botline: _,
266 curline: _,
267 curcol: _,
268 line_count: _,
269 }) => {
270 if !self.ignore_next_scroll {
271 self.grid_mut(grid)
272 .expect("Tried to update the viewport of a nonexistent grid")
273 .scroll_delta = scroll_delta;
274 }
275 }
276 Event::WinViewportMargins(_) | Event::WinExtmark(_) => {}
277
278 Event::PopupmenuShow(event) => self.popupmenu = Some(event),
279 Event::PopupmenuSelect(PopupmenuSelect { selected }) => {
280 if let Some(menu) = &mut self.popupmenu {
281 menu.selected = selected
282 }
283 }
284 Event::PopupmenuHide => self.popupmenu = None,
285
286 Event::CmdlineShow(event) => self.cmdline.show(event),
287 Event::CmdlinePos(CmdlinePos { pos, level: _ }) => self.cmdline.set_cursor_pos(pos),
288 Event::CmdlineBlockShow(CmdlineBlockShow { lines }) => self.cmdline.show_block(lines),
289 Event::CmdlineBlockAppend(CmdlineBlockAppend { line }) => {
290 self.cmdline.append_block(line)
291 }
292 Event::CmdlineSpecialChar(event) => self.cmdline.special(event),
293 Event::CmdlineHide => self.cmdline.hide(),
294 Event::CmdlineBlockHide => self.cmdline.hide_block(),
295
296 Event::MsgHistoryShow(MsgHistoryShow { entries }) => {
297 self.messages.history = entries;
298 self.messages.dirty = true;
299 }
300 Event::MsgRuler(MsgRuler { content }) => self.messages.ruler = content,
301 Event::MsgSetPos(MsgSetPos {
302 grid,
303 row,
304 scrolled: _,
305 sep_char: _,
306 }) => {
307 self.show_float(DrawItem::new(grid, Some(200)));
310 *self.get_or_create_grid(grid).window_mut() = Window::Messages { row };
311 }
312 Event::MsgShow(event) => {
313 self.messages.show(event);
314 self.messages.dirty = true;
315 }
316 Event::MsgShowmode(MsgShowmode { content }) => self.messages.showmode = content,
317 Event::MsgShowcmd(MsgShowcmd { content }) => self.messages.showcmd = content,
318 Event::MsgClear => {
319 self.messages.show.clear();
320 self.messages.dirty = true;
321 }
322 Event::MsgHistoryClear => {
323 self.messages.history.clear();
324 }
325
326 Event::TablineUpdate(event) => self.tabline = Some(event),
327 Event::Chdir(Chdir { path }) => match std::env::set_current_dir(path) {
328 Ok(_) => {}
329 Err(e) => log::error!("Failed to change directory: {e:?}"),
330 },
331
332 Event::MouseOn => self.mouse = true,
333 Event::MouseOff => self.mouse = false,
334 Event::BusyStart => self.cursor.enabled = false,
335 Event::BusyStop => self.cursor.enabled = true,
336 Event::Flush => self.did_flush = true,
337
338 Event::Suspend
339 | Event::SetTitle(_)
340 | Event::SetIcon(_)
341 | Event::UpdateMenu
342 | Event::Bell
343 | Event::VisualBell => {}
344 }
345 }
346
347 fn show_float(&mut self, draw_item: DrawItem) {
349 self.hide(draw_item.grid);
350 let z_of = |item: DrawItem| item.z.unwrap_or(50);
353 let z = z_of(draw_item);
354 let insert_position = self
355 .draw_order
356 .iter()
357 .enumerate()
358 .skip(self.float_windows_start)
359 .rev()
360 .find_map(|(i, item)| (z >= z_of(*item)).then_some(i + 1))
361 .unwrap_or(self.float_windows_start);
362 self.draw_order.insert(insert_position, draw_item);
363 }
364
365 fn show_normal(&mut self, grid: grid::Id) {
367 self.hide(grid);
368 self.draw_order
369 .insert(self.float_windows_start, DrawItem::new(grid, None));
370 self.float_windows_start += 1;
371 }
372
373 fn hide(&mut self, grid: grid::Id) {
375 if let Some(i) = self.draw_order.iter().position(|&r| r.grid == grid) {
376 self.draw_order.remove(i);
377 if i < self.float_windows_start {
378 self.float_windows_start -= 1;
379 }
380 }
381 }
382
383 fn delete_grid(&mut self, grid: grid::Id) {
385 if let Ok(i) = self.grids.binary_search_by(|probe| probe.id.cmp(&grid)) {
386 self.grids.remove(i);
387 }
388 self.hide(grid);
389 }
390
391 pub fn position(&self, grid: grid::Id) -> Option<CellVec<f32>> {
394 if grid == 1 {
395 return Some(CellVec::new(0., 0.));
396 }
397 if let Ok(index) = self.grid_index(grid) {
398 let grid = &self.grids[index];
399 if grid.window() == &Window::None {
400 return None;
401 }
402
403 let WindowOffset {
404 offset,
405 anchor_grid,
406 } = grid.window().offset(grid.contents().size);
407
408 let position = if let Some(anchor_grid) = anchor_grid {
409 self.position(anchor_grid)? + offset
410 } else {
411 offset
412 };
413
414 match grid.window() {
415 Window::Floating(_) => {
416 let base_grid_size = self.grids[0].contents().size;
417 let grid_max = position + grid.contents().size.cast_as();
418 let overflow = (grid_max - base_grid_size.cast_as()).map(|x| x.max(0.));
419 Some((position - overflow).map(|x| x.max(0.)))
420 }
421 _ => Some(position),
422 }
423 } else {
424 None
425 }
426 }
427
428 pub fn grid_under_cursor(
431 &self,
432 cursor: PixelVec<u32>,
433 cell_size: Vec2<u32>,
434 ) -> Option<GridUnderCursor> {
435 let cursor = cursor.cast_as::<f32>();
437 let cell_size = cell_size.cast_as::<f32>();
438 for &draw_item in self.draw_order.iter().rev() {
439 let grid = self.grid(draw_item.grid).unwrap();
440 let size: CellVec<f32> = grid.contents().size.cast_as();
441 let start = self.position(draw_item.grid)?.into_pixels(cell_size);
442 let end = start + size.into_pixels(cell_size);
443 if cursor.0.x > start.0.x
444 && cursor.0.y > start.0.y
445 && cursor.0.x < end.0.x
446 && cursor.0.y < end.0.y
447 {
448 let position = (cursor - start).into_cells(cell_size);
449 let position = position.cast_as::<i64>();
450 let Ok(position) = position.try_cast() else {
451 continue;
452 };
453 return Some(GridUnderCursor {
454 grid: draw_item.grid,
455 position,
456 });
457 }
458 }
459 None
460 }
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464pub struct GridUnderCursor {
465 pub grid: grid::Id,
467 pub position: CellVec<u32>,
469}
470
471#[derive(Debug, Copy, Clone)]
472pub struct CursorInfo {
473 pub pos: CellVec<u16>,
475 pub grid: grid::Id,
477 pub enabled: bool,
479 pub style_enabled: bool,
481}
482
483impl Default for CursorInfo {
484 fn default() -> Self {
485 Self {
486 pos: Default::default(),
487 grid: 1,
488 enabled: true,
489 style_enabled: false,
490 }
491 }
492}