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 {}