mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-11 13:10:54 +00:00
More keybindings in macOs modals with buttons
Closes https://github.com/zed-industries/community/issues/1095 by forcing the non-Cancel button to get a focus. Due to the way macOs handles buttons on modals, the focus gain had to be achieved via certain button addition order, rather than conventional "setFocus"-ish API, see the related comment for details. Co-authored-by: Antonio Scandurra <antonio@zed.dev>
This commit is contained in:
parent
4f6939732e
commit
1398a12062
1 changed files with 37 additions and 2 deletions
|
@ -699,6 +699,31 @@ impl platform::Window for Window {
|
||||||
msg: &str,
|
msg: &str,
|
||||||
answers: &[&str],
|
answers: &[&str],
|
||||||
) -> oneshot::Receiver<usize> {
|
) -> oneshot::Receiver<usize> {
|
||||||
|
// macOs applies overrides to modal window buttons after they are added.
|
||||||
|
// Two most important for this logic are:
|
||||||
|
// * Buttons with "Cancel" title will be displayed as the last buttons in the modal
|
||||||
|
// * Last button added to the modal via `addButtonWithTitle` stays focused
|
||||||
|
// * Focused buttons react on "space"/" " keypresses
|
||||||
|
// * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
|
||||||
|
//
|
||||||
|
// See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
|
||||||
|
// ```
|
||||||
|
// By default, the first button has a key equivalent of Return,
|
||||||
|
// any button with a title of “Cancel” has a key equivalent of Escape,
|
||||||
|
// and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// To avoid situations when the last element added is "Cancel" and it gets the focus
|
||||||
|
// (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
|
||||||
|
// last, so it gets focus and a Space shortcut.
|
||||||
|
// This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
|
||||||
|
let latest_non_cancel_label = answers
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.find(|(_, &label)| label != "Cancel")
|
||||||
|
.filter(|&(label_index, _)| label_index > 0);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let alert: id = msg_send![class!(NSAlert), alloc];
|
let alert: id = msg_send![class!(NSAlert), alloc];
|
||||||
let alert: id = msg_send![alert, init];
|
let alert: id = msg_send![alert, init];
|
||||||
|
@ -709,10 +734,20 @@ impl platform::Window for Window {
|
||||||
};
|
};
|
||||||
let _: () = msg_send![alert, setAlertStyle: alert_style];
|
let _: () = msg_send![alert, setAlertStyle: alert_style];
|
||||||
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
|
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
|
||||||
for (ix, answer) in answers.iter().enumerate() {
|
|
||||||
|
for (ix, answer) in answers
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
|
||||||
|
{
|
||||||
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
|
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
|
||||||
let _: () = msg_send![button, setTag: ix as NSInteger];
|
let _: () = msg_send![button, setTag: ix as NSInteger];
|
||||||
}
|
}
|
||||||
|
if let Some((ix, answer)) = latest_non_cancel_label {
|
||||||
|
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
|
||||||
|
let _: () = msg_send![button, setTag: ix as NSInteger];
|
||||||
|
}
|
||||||
|
|
||||||
let (done_tx, done_rx) = oneshot::channel();
|
let (done_tx, done_rx) = oneshot::channel();
|
||||||
let done_tx = Cell::new(Some(done_tx));
|
let done_tx = Cell::new(Some(done_tx));
|
||||||
let block = ConcreteBlock::new(move |answer: NSInteger| {
|
let block = ConcreteBlock::new(move |answer: NSInteger| {
|
||||||
|
@ -720,7 +755,7 @@ impl platform::Window for Window {
|
||||||
let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap());
|
let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let block = block.copy();
|
|
||||||
let native_window = self.0.borrow().native_window;
|
let native_window = self.0.borrow().native_window;
|
||||||
self.0
|
self.0
|
||||||
.borrow()
|
.borrow()
|
||||||
|
|
Loading…
Reference in a new issue