duat_utils/widgets/
mod.rs

1//! Core [`Widget`]s for usage in Duat
2//!
3//! The widgets defined in this module are expected to be used in
4//! pretty much every `config` of Duat, since they are Duat
5//! equivalents to what is commonly found in other text editors.
6//!
7//!
8//!
9//! [`Widget`]: duat_core::ui::Widget
10//! [`Text`]: duat_core::text::Text
11//! [`File`]: duat_core::file::File
12//! [`OnFileOpen`]: duat_core::hook::OnFileOpen
13//! [`OnWindowOpen`]: duat_core::hook::OnWindowOpen
14//! [`Constraint`]: duat_core::ui::Constraint
15//! [`duat-term`]: https://docs.rs/duat-term/latest/duat_term/
16//! [`VertRule`]: https://docs.rs/duat-term/latest/duat_term/struct.VertRule.html
17use duat_core::{
18    context,
19    hook::{self, FocusedOn, UnfocusedFrom},
20    prelude::Pass,
21    ui::{BuilderDummy, RawArea, Ui, UiBuilder, Widget, WidgetAlias},
22};
23
24pub use self::{
25    line_numbers::{LineNumbers, LineNumbersOptions},
26    log_book::{LogBook, LogBookCfg},
27    notifications::{Notifications, NotificationsCfg},
28    prompt_line::{PromptLine, PromptLineCfg},
29    status_line::{State, StatusLine, StatusLineCfg, status},
30};
31
32mod line_numbers;
33mod log_book;
34mod notifications;
35mod prompt_line;
36mod status_line;
37
38/// A footer [`WidgetAlias`] consisting of a [`StatusLine`],
39/// [`PromptLine`] and [`Notifications`] combo
40///
41/// These are the default [`Widget`]s placed in the footer position of
42/// Duat. By default, they will be placed [around the window], but
43/// they can also be placed around individual [`File`]s:
44///
45/// ```rust
46/// use duat_core::{hook::OnFileOpen, prelude::*};
47/// use duat_utils::{
48///     state::*,
49///     widgets::{FooterWidgets, status},
50/// };
51///
52/// fn setup_generic_over_ui<U: Ui>() {
53///     hook::add::<OnFileOpen<U>, U>(|pa, builder| {
54///         builder.push(
55///             pa,
56///             FooterWidgets::new(status!(
57///                 "{file_fmt}{Spacer}{mode_fmt} {sels_fmt} {main_fmt}"
58///             )),
59///         );
60///     });
61/// }
62/// ```
63///
64/// This specific [`Widget`] configuration makes use of [hook]s in
65/// order to show the [`PromptLine`] only when it is in focus, showing
66/// [`Notifications`] whenever that is not the case. Like Vim/Neovim,
67/// it also takes up two lines on the screen, one for the
68/// [`StatusLine`] and one for the other two [`Widget`]s.
69///
70/// If you want a Kakoune-like one line footer, use
71/// the [`one_line`] method. You can also use the [`prompt`] and
72/// [`notifs`] methods in order to specify the [`PromptLine`] and
73/// [`Notifications`] [`Widget`]s, respectively.
74///
75/// Additionally, you can call the [`above`] method in order to place
76/// the footer as a header instead.
77///
78/// [around the window]: duat_core::hook::OnWindowOpen
79/// [`File`]: duat_core::hook::OnFileOpen
80/// [`one_line`]: FooterWidgets::one_line
81/// [`prompt`]: FooterWidgets::prompt
82/// [`notifs`]: FooterWidgets::notifs
83/// [`above`]: FooterWidgets::above
84#[derive(Default)]
85pub struct FooterWidgets<U: Ui> {
86    status_cfg: StatusLineCfg<U>,
87    prompt_cfg: PromptLineCfg<U>,
88    notifs_cfg: NotificationsCfg<U>,
89    is_one_line: bool,
90    is_above: bool,
91}
92
93impl<U: Ui> FooterWidgets<U> {
94    /// Returns a new [`FooterWidgets`], with a [`StatusLine`] and
95    /// default [`PromptLine`] and [`Notifications`]
96    ///
97    /// You can set those other two through the [`prompt`] and
98    /// [`notifs`] methods.
99    ///
100    /// [`prompt`]: FooterWidgets::prompt
101    /// [`notifs`]: FooterWidgets::notifs
102    pub fn new(status_cfg: StatusLineCfg<U>) -> Self {
103        Self {
104            status_cfg,
105            prompt_cfg: PromptLine::cfg(),
106            notifs_cfg: Notifications::cfg(),
107            is_one_line: false,
108            is_above: false,
109        }
110    }
111
112    /// Turns this footer into a Kakoune-like one liner, as opposed to
113    /// a Neovim-like two liner
114    pub fn one_line(self) -> Self {
115        Self { is_one_line: true, ..self }
116    }
117
118    /// Changes this footer to a header
119    pub fn above(self) -> Self {
120        Self { is_above: true, ..self }
121    }
122
123    /// Sets the [`PromptLine`] to be used
124    pub fn prompt(self, prompt_cfg: PromptLineCfg<U>) -> Self {
125        Self { prompt_cfg, ..self }
126    }
127
128    /// Sets the [`Notifications`] to be used
129    pub fn notifs(self, notifs_cfg: NotificationsCfg<U>) -> Self {
130        Self { notifs_cfg, ..self }
131    }
132}
133
134impl<U: Ui> WidgetAlias<U, FooterWidgetsDummy> for FooterWidgets<U> {
135    fn push(self, pa: &mut Pass, builder: &mut impl UiBuilder<U>) -> (U::Area, Option<U::Area>) {
136        let (child, parent) = if self.is_above {
137            builder.push_cfg(pa, self.status_cfg.above())
138        } else {
139            builder.push_cfg(pa, self.status_cfg.below())
140        };
141
142        let (prompt_area, footer_parent) = if self.is_one_line {
143            builder.push_cfg_to(pa, child, self.prompt_cfg.left_ratioed(3, 7).hidden())
144        } else {
145            builder.push_cfg_to(pa, child, self.prompt_cfg.below().hidden())
146        };
147
148        let (notifs_area, _) = builder.push_cfg_to(pa, prompt_area.clone(), self.notifs_cfg);
149
150        hook::add::<FocusedOn<PromptLine<U>, U>, U>({
151            let prompt_area = prompt_area.clone();
152            let notifs_area = notifs_area.clone();
153            move |pa, (_, new)| {
154                let handle = context::get_widget::<PromptLine<U>, U>(pa, &prompt_area).unwrap();
155                if new.ptr_eq(handle.widget()) {
156                    prompt_area.reveal().unwrap();
157                    notifs_area.hide().unwrap();
158                }
159            }
160        });
161
162        hook::add::<UnfocusedFrom<PromptLine<U>, U>, U>(move |pa, (old, _)| {
163            let handle = context::get_widget::<PromptLine<U>, U>(pa, &prompt_area).unwrap();
164            if old.ptr_eq(handle.widget()) {
165                prompt_area.hide().unwrap();
166                notifs_area.reveal().unwrap();
167            }
168        });
169
170        (footer_parent.unwrap(), parent)
171    }
172}
173
174#[doc(hidden)]
175pub struct FooterWidgetsDummy;
176
177impl BuilderDummy for FooterWidgetsDummy {}