ad_editor/ui/
mod.rs

1//! The ad user interface
2use crate::{
3    config::Config,
4    editor::{Click, EditorMode, MiniBufferState},
5    input::Event,
6    key::Input,
7    term::CurShape,
8};
9use std::{
10    fmt,
11    sync::{Arc, RwLock, mpsc::Sender},
12};
13
14mod layout;
15mod tui;
16
17pub use layout::{Border, Layout, SCRATCH_ID};
18pub use tui::{GenericTui, Tui};
19
20/// Something that can be used as a user interface
21pub trait UserInterface {
22    /// Initialise the UI and start processing events.
23    ///
24    /// Called before entering the main editor event loop
25    fn init(&mut self, tx: Sender<Event>) -> (usize, usize);
26
27    /// Called when the editor event loop exits cleanly.
28    fn shutdown(&mut self);
29
30    /// Update internal state based on an Editor state change without rendering.
31    fn state_change(&mut self, change: StateChange);
32
33    /// Refresh the ui to display the current editor state.
34    fn refresh(
35        &mut self,
36        mode_name: &str,
37        layout: &mut Layout,
38        n_running: usize,
39        pending_keys: &[Input],
40        held_click: Option<&Click>,
41        mb: Option<MiniBufferState<'_>>,
42    );
43
44    /// Called when the editor mode changes and a new cursor shape is required
45    fn set_cursor_shape(&mut self, cur_shape: CurShape);
46}
47
48/// Sent by the Editor to a [UserInterface] when internal state has changed in such a way that
49/// a UI update _may_ be required.
50///
51/// In cases where the data is cheap to pass directly it is included, otherwise updates
52/// to the editor state can be requested through the `provide_buf_reqs` method.
53#[derive(Debug, Clone)]
54pub enum StateChange {
55    // /// The active buffer has been updated
56    // ActiveBuffer { id: usize },
57    // /// The given buffer has been closed
58    // BufferClosed { id: usize },
59    // /// A new buffer has been opened.
60    // /// If replace_active is true then the user wants to replace the active buffer
61    // /// with the id specified, if it is false then they want to open a new UI
62    // /// window to contain the buffer.
63    // BufferOpen { id: usize, replace_active: bool },
64    // /// The given buffer has had its contents modified in some way
65    // BufferModified { id: usize },
66    // /// The dot for the given buffer has been updated
67    // BufferDotUpdated { id: usize },
68    /// User level config has been modified in some way
69    ConfigUpdated,
70    // /// The tag for the given buffer has been updated
71    // TagModified { id: usize },
72    /// A new status message has been set
73    StatusMessage { msg: String },
74}
75
76#[allow(clippy::large_enum_variant)]
77pub(crate) enum Ui {
78    Headless,
79    Tui(Tui),
80    Boxed(Box<dyn UserInterface>),
81}
82
83impl fmt::Debug for Ui {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::Headless => f.debug_struct("Ui::Headless").finish(),
87            Self::Tui(_) => f.debug_struct("Ui::Tui").finish(),
88            Self::Boxed(_) => f.debug_struct("Ui::Boxed").finish(),
89        }
90    }
91}
92
93impl Ui {
94    pub(crate) fn new(mode: EditorMode, config: Arc<RwLock<Config>>) -> Self {
95        match mode {
96            EditorMode::Headless => Self::Headless,
97            EditorMode::Terminal => Self::Tui(Tui::new(config)),
98            EditorMode::Boxed(ui) => Self::Boxed(ui),
99        }
100    }
101}
102
103impl UserInterface for Ui {
104    fn init(&mut self, tx: Sender<Event>) -> (usize, usize) {
105        match self {
106            Self::Headless => (60, 80),
107            Self::Tui(tui) => tui.init(tx),
108            Self::Boxed(ui) => ui.init(tx),
109        }
110    }
111
112    fn shutdown(&mut self) {
113        match self {
114            Self::Headless => (),
115            Self::Tui(tui) => tui.shutdown(),
116            Self::Boxed(ui) => ui.shutdown(),
117        }
118    }
119
120    fn state_change(&mut self, change: StateChange) {
121        match self {
122            Self::Headless => (),
123            Self::Tui(tui) => tui.state_change(change),
124            Self::Boxed(ui) => ui.state_change(change),
125        }
126    }
127
128    fn refresh(
129        &mut self,
130        mode_name: &str,
131        layout: &mut Layout,
132        n_running: usize,
133        pending_keys: &[Input],
134        held_click: Option<&Click>,
135        mb: Option<MiniBufferState<'_>>,
136    ) {
137        match self {
138            Self::Headless => (),
139            Self::Tui(tui) => {
140                tui.refresh(mode_name, layout, n_running, pending_keys, held_click, mb)
141            }
142            Self::Boxed(ui) => {
143                ui.refresh(mode_name, layout, n_running, pending_keys, held_click, mb)
144            }
145        }
146    }
147
148    fn set_cursor_shape(&mut self, cur_shape: CurShape) {
149        match self {
150            Self::Headless => (),
151            Self::Tui(tui) => tui.set_cursor_shape(cur_shape),
152            Self::Boxed(ui) => ui.set_cursor_shape(cur_shape),
153        }
154    }
155}