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