duat_base/widgets/
which_key.rs1use 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
19pub struct WhichKey(Text, Option<Handle<WhichKeyDescriptions>>);
23
24impl WhichKey {
25 #[allow(clippy::type_complexity)] 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}