1use crate::Bind;
2use egui::epaint::StrokeKind;
3use egui::{
4 pos2, vec2, Event, Id, Key, KeyboardShortcut, ModifierNames, PointerButton, RichText, Sense,
5 TextStyle, Ui, Widget, WidgetInfo, WidgetText, WidgetType,
6};
7
8pub struct Keybind<'a, B: Bind> {
10 bind: &'a mut B,
11 reset: B,
12 text: &'a str,
13 id: Id,
14 reset_key: Option<Key>,
15 modifier_names: &'a ModifierNames<'a>,
16}
17
18impl<'a, B: Bind> Keybind<'a, B> {
19 pub fn new(bind: &'a mut B, id: impl Into<Id>) -> Self {
26 let prev_bind = bind.clone();
27 Self {
28 bind,
29 reset: prev_bind,
30 text: "",
31 id: id.into(),
32 reset_key: None,
33 modifier_names: &ModifierNames::NAMES,
34 }
35 }
36
37 pub fn with_text(mut self, text: &'a str) -> Self {
43 self.text = text;
44 self
45 }
46
47 pub fn with_bind(mut self, bind: &'a mut B) -> Self {
51 self.bind = bind;
52 self
53 }
54
55 pub fn with_id(mut self, id: impl Into<Id>) -> Self {
59 self.id = id.into();
60 self
61 }
62
63 pub fn with_reset_key(mut self, key: Option<Key>) -> Self {
68 self.reset_key = key;
69 self
70 }
71
72 pub fn with_reset(mut self, prev_bind: B) -> Self {
76 self.reset = prev_bind;
77 self
78 }
79
80 pub fn with_modifier_names(mut self, modifier_names: &'a ModifierNames<'a>) -> Self {
82 self.modifier_names = modifier_names;
83 self
84 }
85}
86
87fn get_expecting(ui: &Ui, id: Id) -> bool {
89 let expecting = ui.ctx().memory_mut(|memory| {
90 *memory
91 .data
92 .get_temp_mut_or_default::<bool>(ui.make_persistent_id(id))
93 });
94 expecting
95}
96
97fn set_expecting(ui: &Ui, id: Id, expecting: bool) {
99 ui.ctx().memory_mut(|memory| {
100 *memory
101 .data
102 .get_temp_mut_or_default(ui.make_persistent_id(id)) = expecting;
103 });
104}
105
106impl<B: Bind> Widget for Keybind<'_, B> {
107 fn ui(self, ui: &mut egui::Ui) -> egui::Response {
108 let text = self.bind.format(self.modifier_names, false);
109
110 let galley = WidgetText::RichText(RichText::new(text.clone()).into()).into_galley(
111 ui,
112 Some(egui::TextWrapMode::Extend),
113 0.0,
114 TextStyle::Button,
115 );
116
117 let size = ui.spacing().interact_size.max(galley.size());
118 let button_padding = ui.spacing().button_padding;
119 let mut widget_size = size + button_padding * vec2(2.0, 1.0);
120
121 let text_galley = if !self.text.is_empty() {
124 let galley = WidgetText::RichText(RichText::new(self.text).into()).into_galley(
125 ui,
126 None,
127 ui.available_width() - widget_size.x, TextStyle::Button,
129 );
130 Some(galley)
131 } else {
132 None
133 };
134
135 let custom_text_width = text_galley.clone().map_or(0.0, |text_galley| {
136 ui.spacing().icon_spacing + text_galley.size().x
137 });
138 widget_size.x += custom_text_width;
139
140 let (rect, mut response) = ui.allocate_exact_size(widget_size, Sense::click());
141
142 let mut hotkey_rect = rect;
144 *hotkey_rect.right_mut() -= custom_text_width;
145
146 let mut expecting = get_expecting(ui, self.id);
148 let prev_expecting = expecting;
149 if response.clicked() {
150 expecting = !expecting;
151 }
152
153 response.widget_info(|| {
156 WidgetInfo::selected(
157 WidgetType::Button,
158 expecting,
159 expecting,
160 if self.text.is_empty() {
161 text.clone() } else {
163 text.clone() + ". " + self.text
164 },
165 )
166 });
167
168 if expecting {
169 if response.clicked_elsewhere() {
170 expecting = false;
172 } else {
173 let kb = ui.input(|i| {
175 i.events.iter().find_map(|e| match e {
176 Event::Key {
177 key,
178 pressed: true,
179 modifiers,
180 repeat: false,
181 ..
182 } => Some((*key, *modifiers)),
183 _ => None,
184 })
185 });
186
187 let pointer = ui.input(|i| {
189 i.events.iter().find_map(|e| match e {
190 Event::PointerButton {
191 button,
192 pressed: true,
193 ..
194 } if *button != PointerButton::Primary
195 && *button != PointerButton::Secondary =>
196 {
197 Some(*button)
198 }
199 _ => None,
200 })
201 });
202
203 if kb.is_some() || pointer.is_some() {
205 self.bind
206 .set(kb.map(|kb| KeyboardShortcut::new(kb.1, kb.0)), pointer);
207 response.mark_changed();
208 expecting = false;
209 }
210 }
211
212 if let Some(reset_key) = self.reset_key {
213 if ui.input(|i| i.key_pressed(reset_key)) {
215 *self.bind = self.reset;
216 expecting = false;
217 response.mark_changed();
218 }
219 }
220 }
221
222 if ui.is_rect_visible(rect) {
224 let visuals = ui.style().interact_selectable(&response, expecting);
226 ui.painter().rect(
227 hotkey_rect.expand(visuals.expansion),
228 visuals.corner_radius,
229 visuals.bg_fill,
230 visuals.bg_stroke,
231 StrokeKind::Inside,
232 );
233
234 let mut text_pos = ui
236 .layout()
237 .align_size_within_rect(galley.size(), hotkey_rect.shrink2(button_padding))
238 .min;
239
240 if text_pos.x + galley.size().x + button_padding.x < hotkey_rect.right() {
242 text_pos.x += hotkey_rect.size().x / 2.0 - galley.size().x / 2.0 - button_padding.x;
243 }
244
245 ui.painter().galley(text_pos, galley, visuals.text_color());
247
248 if let Some(text_galley) = text_galley {
250 let text_pos = pos2(
251 hotkey_rect.right() + ui.spacing().icon_spacing,
252 hotkey_rect.center().y - 0.5 * text_galley.size().y,
253 );
254 ui.painter().galley(
255 text_pos,
256 text_galley,
257 ui.style().noninteractive().text_color(),
258 );
259 }
260 }
261
262 if prev_expecting != expecting {
263 set_expecting(ui, self.id, expecting);
264 }
265 response
266 }
267}