duat_base/widgets/
which_key.rs1use 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
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(
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}