duat_base/widgets/
which_key.rs

1//! A `Widget` to show available bindings and remaps
2//!
3//! This widget is automatically displayed when deemed necessary.
4//! Normally, this is done on some modes, and when typing remapped key
5//! sequences, be them maps or aliases.
6use std::sync::Once;
7
8use duat_core::{
9    context::{self, Handle},
10    data::Pass,
11    form,
12    hook::{self, FocusChanged, KeyTyped},
13    mode::{self, Description, MouseEvent, MouseEventKind},
14    text::{Text, TextMut, txt},
15    ui::{DynSpawnSpecs, PushSpecs, Side, Widget},
16};
17use duat_term::Frame;
18
19/// A [`Widget`] to display what [keys] will do
20///
21/// [keys]: mode::KeyEvent
22pub struct WhichKey(Text, Option<Handle<WhichKeyDescriptions>>);
23
24impl WhichKey {
25    /// Open the `WhichKey` widget
26    ///
27    /// You can optionally pass an
28    #[allow(clippy::type_complexity)] // ??? where?
29    pub fn open(
30        pa: &mut Pass,
31        mut fmt: Option<Box<dyn FnMut(Description) -> Option<(Text, Text)>>>,
32        mut specs: DynSpawnSpecs,
33    ) {
34        static ONCE: Once = Once::new();
35        ONCE.call_once(|| {
36            form::set("default.WhichKeyDescriptions", "default.WhichKey");
37        });
38
39        let mut keys_builder = Text::builder();
40        let mut descs_builder = Text::builder();
41
42        let (title, descs) = mode::current_seq_descriptions(pa);
43        let title = title.cloned();
44        for desc in descs {
45            if let Some(fmt) = fmt.as_mut() {
46                if let Some((keys, desc)) = fmt(desc) {
47                    keys_builder.push(keys);
48                    descs_builder.push(desc);
49                }
50            } else if let Some(text) = desc.text
51                && !text.is_empty()
52            {
53                keys_builder.push(txt!("{}[colon.WhichKey]:", desc.keys.into_text()));
54                descs_builder.push(txt!("{text}"));
55            } else {
56                continue;
57            }
58
59            keys_builder.push('\n');
60            descs_builder.push('\n');
61        }
62
63        let keys = WhichKey(keys_builder.build_no_double_nl(), None);
64        let mut descs = WhichKeyDescriptions(descs_builder.build_no_double_nl(), None);
65
66        let handles: Vec<_> = context::windows()
67            .handles(pa)
68            .filter(|handle| {
69                handle.widget().is::<WhichKey>() || handle.widget().is::<WhichKeyDescriptions>()
70            })
71            .collect();
72        for handle in handles {
73            let _ = handle.close(pa);
74        }
75
76        if let Some(height) = specs.height.as_mut() {
77            *height = keys.text().len().line().min(*height as usize) as f32;
78        }
79
80        let keys_handle = context::current_buffer(pa)
81            .spawn_widget(pa, keys, specs)
82            .unwrap();
83        descs.1 = Some(keys_handle.clone());
84
85        let title = title.unwrap_or_else(|| txt!("{}", crate::state::mode_name().call(pa)));
86        if let Some(area) = keys_handle.area().write_as::<duat_term::Area>(pa) {
87            use duat_core::text::Spacer;
88
89            let mut frame = Frame {
90                left: true,
91                right: true,
92                above: true,
93                below: true,
94                ..Frame::default()
95            };
96            frame.set_text(Side::Above, move |_| {
97                txt!("{Spacer}[terminal.frame]┤[]{title}[terminal.frame]├{Spacer}")
98            });
99            area.set_frame(frame);
100        }
101
102        let (keys, area) = keys_handle.write_with_area(pa);
103        if let Ok(size) = area.size_of_text(keys.get_print_opts(), keys.text()) {
104            area.set_width(size.x + 1.0).unwrap();
105        }
106
107        let descs_handle = keys_handle.push_inner_widget(pa, descs, PushSpecs {
108            side: Side::Right,
109            ..Default::default()
110        });
111        keys_handle.write(pa).1 = Some(descs_handle.clone());
112
113        let (descs, area) = descs_handle.write_with_area(pa);
114        if let Ok(size) = area.size_of_text(descs.get_print_opts(), descs.text()) {
115            area.set_width(size.x).unwrap();
116        }
117
118        hook::add::<KeyTyped>({
119            let keys_handle = keys_handle.clone();
120            let descs_handle = descs_handle.clone();
121            move |pa, _| {
122                _ = keys_handle.close(pa);
123                _ = descs_handle.close(pa);
124            }
125        })
126        .once();
127        hook::add::<FocusChanged>(move |pa, _| {
128            _ = keys_handle.close(pa);
129            _ = descs_handle.close(pa);
130        })
131        .once();
132    }
133}
134
135impl Widget for WhichKey {
136    fn update(_: &mut Pass, _: &Handle<Self>) {}
137
138    fn needs_update(&self, _: &Pass) -> bool {
139        false
140    }
141
142    fn text(&self) -> &Text {
143        &self.0
144    }
145
146    fn text_mut(&mut self) -> TextMut<'_> {
147        self.0.as_mut()
148    }
149
150    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent) {
151        use MouseEventKind::{ScrollDown, ScrollUp};
152        match event.kind {
153            ScrollDown | ScrollUp => {
154                let (keys, area) = handle.write_with_area(pa);
155                let scroll = if let ScrollDown = event.kind { 3 } else { -3 };
156                area.scroll_ver(&keys.0, scroll, keys.get_print_opts());
157
158                let handle = keys.1.clone().unwrap();
159                let (descs, area) = handle.write_with_area(pa);
160                area.scroll_ver(&descs.0, scroll, descs.get_print_opts());
161            }
162            _ => {}
163        }
164    }
165}
166
167struct WhichKeyDescriptions(Text, Option<Handle<WhichKey>>);
168
169impl Widget for WhichKeyDescriptions {
170    fn update(_: &mut Pass, _: &Handle<Self>) {}
171
172    fn needs_update(&self, _: &Pass) -> bool {
173        false
174    }
175
176    fn text(&self) -> &Text {
177        &self.0
178    }
179
180    fn text_mut(&mut self) -> TextMut<'_> {
181        self.0.as_mut()
182    }
183
184    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent) {
185        match event.kind {
186            MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
187                let (descs, area) = handle.write_with_area(pa);
188                let scroll = if let MouseEventKind::ScrollDown = event.kind {
189                    3
190                } else {
191                    -3
192                };
193                area.scroll_ver(&descs.0, scroll, descs.get_print_opts());
194
195                let handle = descs.1.clone().unwrap();
196                let (keys, area) = handle.write_with_area(pa);
197                area.scroll_ver(&keys.0, scroll, keys.get_print_opts());
198            }
199            _ => {}
200        }
201    }
202}