Skip to main content

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::{self, 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(
37                "default.WhichKeyDescriptions",
38                Form::mimic("default.WhichKey"),
39            );
40        });
41
42        let mut keys_builder = Text::builder();
43        let mut descs_builder = Text::builder();
44
45        let (title, descs) = mode::current_seq_descriptions(pa);
46        let title = title.cloned();
47        for desc in descs {
48            if let Some(fmt) = fmt.as_mut() {
49                if let Some((keys, desc)) = fmt(desc) {
50                    keys_builder.push(keys);
51                    descs_builder.push(desc);
52                }
53            } else if let Some(text) = desc.text
54                && !text.is_empty()
55            {
56                keys_builder.push(txt!("{}[colon.WhichKey]:", desc.keys.into_text()));
57                descs_builder.push(txt!("{text}"));
58            } else {
59                continue;
60            }
61
62            keys_builder.push('\n');
63            descs_builder.push('\n');
64        }
65
66        let keys = WhichKey(keys_builder.build_no_double_nl(), None);
67        let mut descs = WhichKeyDescriptions(descs_builder.build_no_double_nl(), None);
68
69        let handles: Vec<_> = context::windows()
70            .handles(pa)
71            .filter(|handle| {
72                handle.widget().is::<WhichKey>() || handle.widget().is::<WhichKeyDescriptions>()
73            })
74            .collect();
75        for handle in handles {
76            let _ = handle.close(pa);
77        }
78
79        if let Some(height) = specs.height.as_mut() {
80            *height = keys.text().end_point().line().min(*height as usize) as f32;
81        }
82
83        let keys_handle = context::current_buffer(pa)
84            .spawn_widget(pa, keys, specs)
85            .unwrap();
86        descs.1 = Some(keys_handle.clone());
87
88        let title = title.unwrap_or_else(|| txt!("{}", crate::state::mode_name().call(pa)));
89        if let Some(area) = keys_handle.area().write_as::<duat_term::Area>(pa) {
90            use duat_core::text::Spacer;
91
92            let mut frame = Frame {
93                left: true,
94                right: true,
95                above: true,
96                below: true,
97                ..Frame::default()
98            };
99            frame.set_text(Side::Above, move |_| {
100                txt!("{Spacer}[terminal.frame]┤[]{title}[terminal.frame]├{Spacer}")
101            });
102            area.set_frame(frame);
103        }
104
105        let (keys, area) = keys_handle.write_with_area(pa);
106        if let Ok(size) = area.size_of_text(keys.print_opts(), keys.text()) {
107            area.set_width(size.x + 1.0).unwrap();
108        }
109
110        let descs_handle = keys_handle.push_inner_widget(pa, descs, PushSpecs {
111            side: Side::Right,
112            ..Default::default()
113        });
114        keys_handle.write(pa).1 = Some(descs_handle.clone());
115
116        let (descs, area) = descs_handle.write_with_area(pa);
117        if let Ok(size) = area.size_of_text(descs.print_opts(), descs.text()) {
118            area.set_width(size.x).unwrap();
119        }
120
121        hook::add_once::<KeyTyped>({
122            let keys_handle = keys_handle.clone();
123            let descs_handle = descs_handle.clone();
124            move |pa, _| {
125                _ = keys_handle.close(pa);
126                _ = descs_handle.close(pa);
127            }
128        });
129        hook::add_once::<FocusChanged>(move |pa, _| {
130            _ = keys_handle.close(pa);
131            _ = descs_handle.close(pa);
132        });
133    }
134}
135
136impl Widget for WhichKey {
137    fn update(_: &mut Pass, _: &Handle<Self>) {}
138
139    fn needs_update(&self, _: &Pass) -> bool {
140        false
141    }
142
143    fn text(&self) -> &Text {
144        &self.0
145    }
146
147    fn text_mut(&mut self) -> TextMut<'_> {
148        self.0.as_mut()
149    }
150
151    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent) {
152        use MouseEventKind::{ScrollDown, ScrollUp};
153        match event.kind {
154            ScrollDown | ScrollUp => {
155                let (keys, area) = handle.write_with_area(pa);
156                let scroll = if let ScrollDown = event.kind { 3 } else { -3 };
157                area.scroll_ver(&keys.0, scroll, keys.print_opts());
158
159                let handle = keys.1.clone().unwrap();
160                let (descs, area) = handle.write_with_area(pa);
161                area.scroll_ver(&descs.0, scroll, descs.print_opts());
162            }
163            _ => {}
164        }
165    }
166}
167
168struct WhichKeyDescriptions(Text, Option<Handle<WhichKey>>);
169
170impl Widget for WhichKeyDescriptions {
171    fn update(_: &mut Pass, _: &Handle<Self>) {}
172
173    fn needs_update(&self, _: &Pass) -> bool {
174        false
175    }
176
177    fn text(&self) -> &Text {
178        &self.0
179    }
180
181    fn text_mut(&mut self) -> TextMut<'_> {
182        self.0.as_mut()
183    }
184
185    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent) {
186        match event.kind {
187            MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
188                let (descs, area) = handle.write_with_area(pa);
189                let scroll = if let MouseEventKind::ScrollDown = event.kind {
190                    3
191                } else {
192                    -3
193                };
194                area.scroll_ver(&descs.0, scroll, descs.print_opts());
195
196                let handle = descs.1.clone().unwrap();
197                let (keys, area) = handle.write_with_area(pa);
198                area.scroll_ver(&keys.0, scroll, keys.print_opts());
199            }
200            _ => {}
201        }
202    }
203}