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