diff --git a/crates/snowbound/src/link.rs b/crates/snowbound/src/link.rs index 283cf68b3236db587ecb5efb90862cc649f25ef9..513f84e3b5e333012f39be6869c624597c809baf 100644 --- a/crates/snowbound/src/link.rs +++ b/crates/snowbound/src/link.rs @@ -211,8 +211,8 @@ impl State { } ui.close(); crate::buttons(ui); - let ok = ui::button(ui, "ok", "OK").clicked || entered; - let cancel = ui::button(ui, "cancel", "Cancel").clicked; + let [cancel, ok] = ui::dialog_buttons(ui, "OK", true); + let ok = ok || entered; ui.close(); ui.close(); if let Some((space, title)) = picked diff --git a/crates/snowbound/src/options.rs b/crates/snowbound/src/options.rs index 533525d71b3d1bfdaa6fd16d43dad63a48ab3982..db3c2b2f16f5e1f53b6bad58d296ccc282b16f65 100644 --- a/crates/snowbound/src/options.rs +++ b/crates/snowbound/src/options.rs @@ -674,8 +674,8 @@ impl State { ..Spec::default() }, ); - let cancel = ui::button(ui, "cancel", "Cancel").clicked; - let ok = ui::button(ui, "ok", "OK").clicked || entered; + let [cancel, ok] = ui::dialog_buttons(ui, "OK", true); + let ok = ok || entered; ui.close(); ui.close(); let name = options.user_name.trim(); diff --git a/crates/snowbound/src/print.rs b/crates/snowbound/src/print.rs index c41daa8bac98c72967205ee167872382c8284621..7e4d5a5cd576fa7adfefa3d8448345d2b764d42b 100644 --- a/crates/snowbound/src/print.rs +++ b/crates/snowbound/src/print.rs @@ -256,9 +256,9 @@ impl State { } }); crate::buttons(ui); - let cancel = ui::button(ui, "cancel", "Cancel").clicked; let action = if dialog.export { "Export" } else { "Print" }; - let go = ui::button(ui, "go", action).clicked || entered; + let [cancel, go] = ui::dialog_buttons(ui, action, true); + let go = go || entered; ui.close(); ui.close(); if !go && !cancel { diff --git a/crates/snowbound/src/properties.rs b/crates/snowbound/src/properties.rs index da21d467243ba97d48b6338550e2ff111ced347c..de5a9e3aef14aef02fabafe7b179fb544ef098b4 100644 --- a/crates/snowbound/src/properties.rs +++ b/crates/snowbound/src/properties.rs @@ -185,8 +185,8 @@ impl State { crate::platform::reveal(&dialog.library.location); } crate::buttons(ui); - let ok = ui::button(ui, "ok", "OK").clicked || entered; - let cancel = ui::button(ui, "cancel", "Cancel").clicked; + let [cancel, ok] = ui::dialog_buttons(ui, "OK", true); + let ok = ok || entered; ui.close(); ui.close(); let name = dialog.name.trim(); diff --git a/crates/snowbound/src/protection.rs b/crates/snowbound/src/protection.rs index d0ab31f8de8f18ccd66cd023c56ce6921cead79e..065d12964a7a4c3ff68111ca15b6fd4bdf63de52 100644 --- a/crates/snowbound/src/protection.rs +++ b/crates/snowbound/src/protection.rs @@ -262,7 +262,6 @@ impl State { dialog, }); self.ui.open_popup(id()); - self.ui.set_focus(None); } /// Locks every protected section unlocked this run, as Lock All and Ctrl+Alt+L do. @@ -420,7 +419,8 @@ impl State { }, ); }; - let mut buttons: &[&str] = &["Cancel", "OK"]; + // Password Protection's own buttons; the others' are Cancel and OK. + let mut buttons: &[&str] = &[]; match &mut asking.dialog { Dialog::Unlock(value, wrong) => { text( @@ -502,12 +502,20 @@ impl State { ..Spec::default() }, ); - let pressed = buttons - .iter() - .position(|label| ui::button(ui, *label, label).clicked); + let pressed = if buttons.is_empty() { + match ui::dialog_buttons(ui, "OK", true) { + [true, _] => Some("Cancel"), + [_, true] => Some("OK"), + _ => None, + } + } else { + (buttons.iter()) + .find(|label| ui::button(ui, **label, label).clicked) + .copied() + }; ui.close(); ui.close(); - let chosen = pressed.map(|at| buttons[at]).or(entered.then_some("OK")); + let chosen = pressed.or(entered.then_some("OK")); let Asking { library, path, diff --git a/crates/snowbound/src/save_as.rs b/crates/snowbound/src/save_as.rs index 6e6d683d2813acf85b89aa42d4040be3768a3199..38b496f901dd0a8c3acb992e60d98d35275af865 100644 --- a/crates/snowbound/src/save_as.rs +++ b/crates/snowbound/src/save_as.rs @@ -134,8 +134,8 @@ impl State { dialog.format = at; } crate::buttons(ui); - let cancel = ui::button(ui, "cancel", "Cancel").clicked; - let go = ui::button(ui, "go", "Save As").clicked || entered; + let [cancel, go] = ui::dialog_buttons(ui, "Save As", true); + let go = go || entered; ui.close(); ui.close(); if !go && !cancel { @@ -249,7 +249,7 @@ fn choose(ui: &mut ui::Ui, label: &str, names: &[&str], current: usize) -> Optio "label", Spec { size: [px(110.0), px(row)], - text: Some(label), + text: Some(&format!("{label}:")), ..Spec::default() }, ); diff --git a/crates/snowbound/src/server.rs b/crates/snowbound/src/server.rs index 520b44a6e231999ec867957af8a5129257b39b5c..93cefc4be64f8bc7b6d42f12c6f295992dd8de82 100644 --- a/crates/snowbound/src/server.rs +++ b/crates/snowbound/src/server.rs @@ -825,29 +825,12 @@ impl State { ..Spec::default() }, ); - let cancel = ui::button(ui, "cancel", "Cancel").clicked; let waiting = matches!(connect.status, Status::Waiting(_)); - let go = match connect.step { - Step::Browse if connect.opens().is_none() => { - ui.leaf( - "open", - Spec { - size: [ui::fit(), px(row)], - text: Some("Open"), - color: Some(theme.text_dim), - fill: Some(theme.chip), - radius: 4.0, - pad: [theme.font_size * 0.75, 0.0], - center: true, - role: Some(Role::Button), - ..Spec::default() - }, - ); - false - } - Step::Browse => ui::button(ui, "open", "Open").clicked, - _ => ui::button(ui, "connect", "Connect").clicked || entered, + let [cancel, go] = match connect.step { + Step::Browse => ui::dialog_buttons(ui, "Open", connect.opens().is_some()), + _ => ui::dialog_buttons(ui, "Connect", true), }; + let go = go || entered && connect.step != Step::Browse; ui.close(); ui.close(); if cancel { diff --git a/crates/snowbound/src/tags.rs b/crates/snowbound/src/tags.rs index 73cdffddbe9c5ed81711f35a9718bd7fbd8c5dac..0cac6b187d23052b6bb093e92d007bdf603cfb4b 100644 --- a/crates/snowbound/src/tags.rs +++ b/crates/snowbound/src/tags.rs @@ -525,8 +525,7 @@ impl State { }); ui.close(); crate::buttons(ui); - let ok = button(ui, "ok", "OK", list.tags != self.tags); - let cancel = ui::button(ui, "cancel", "Cancel").clicked; + let [cancel, ok] = ui::dialog_buttons(ui, "OK", list.tags != self.tags); ui.close(); if let Some(place) = list.selected { if up { @@ -870,8 +869,8 @@ fn tag_editor( ); crate::buttons(ui); let named = !tag.label.trim().is_empty(); - let ok = button(ui, "ok", "OK", named) || entered && named; - let cancel = ui::button(ui, "cancel", "Cancel").clicked; + let [cancel, ok] = ui::dialog_buttons(ui, "OK", named); + let ok = ok || entered && named; ui.close(); ui.close(); if ok { diff --git a/crates/snowbound/src/unpack.rs b/crates/snowbound/src/unpack.rs index 2267d6d2048ceab9596a5ac7e52de25be80f0df2..42e58823cb6bea51dbc887b6cbacd9d01dcee9b2 100644 --- a/crates/snowbound/src/unpack.rs +++ b/crates/snowbound/src/unpack.rs @@ -168,8 +168,8 @@ impl State { }, ); crate::buttons(ui); - let cancel = ui::button(ui, "cancel", "Cancel").clicked; - let create = ui::button(ui, "create", "Create").clicked || entered; + let [cancel, create] = ui::dialog_buttons(ui, "Create", true); + let create = create || entered; ui.close(); ui.close(); if browse { diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index c5a839fdd2c2ebd69dbe390de7eb47422704c93a..ec8cfa0a3c5fcbf17eac16f102d85dfdd92074e4 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -16,7 +16,7 @@ pub use list::{List, Row, Rows, list}; pub use theme::{Menu, PopupMotion, Section, Shades, Shadow, Theme}; pub use widgets::{ PaintedScroller, Scroller, ScrollerPart, Scrollers, badge, badge_width, button, check_box, - edit_key, edit_modifiers, password_field, scrollbar, text_field, + dialog_buttons, edit_key, edit_modifiers, password_field, scrollbar, text_field, }; use draw::{ diff --git a/crates/ui/src/widgets.rs b/crates/ui/src/widgets.rs index 75c8a56b8fff87e5cb0f1950fcdc5890b39b0f13..3d4513ef662a5a786f1375a691d85805fa9ee655 100644 --- a/crates/ui/src/widgets.rs +++ b/crates/ui/src/widgets.rs @@ -32,6 +32,46 @@ pub fn button(ui: &mut Ui, part: impl Hash, text: &str) -> Signal { ui.leaf(part, spec) } +/// Cancel and a dialog's default button `text`, which takes no clicks while not `enabled`, +/// in the platform's order: the default last on macOS and GNOME, first on Windows, as their +/// own dialogs place them. The default wears the accent around it. Whether each was clicked, +/// Cancel first. +pub fn dialog_buttons(ui: &mut Ui, text: &str, enabled: bool) -> [bool; 2] { + let default = |ui: &mut Ui| { + let theme = &ui.theme; + let spec = Spec { + flags: if enabled { + Flags::CLICKABLE + } else { + Flags::default() + }, + size: [fit(), px(theme.font_size * 2.0)], + text: Some(text), + color: (!enabled).then_some(theme.text_dim), + fill: Some(theme.chip), + hover_fill: enabled.then(|| theme.hover()), + border: enabled.then_some(theme.accent), + radius: 4.0, + pad: [theme.font_size * 0.75, 0.0], + center: true, + role: Some(Role::Button), + ..Spec::default() + }; + let clicked = ui.leaf("default", spec).clicked; + if !enabled && let Some(node) = ui.access(ui.id("default")) { + node.set_disabled(); + } + clicked + }; + let windows = edit::Platform::CURRENT == edit::Platform::Windows; + let mut chosen = windows && default(ui); + let cancel = button(ui, "cancel", "Cancel").clicked; + if !windows { + chosen = default(ui); + } + [cancel, chosen] +} + /// A short tag in the accent's colour, centred in a box `height` tall, as BETA marks a /// feature still settling beside its name. pub fn badge(ui: &mut Ui, part: impl Hash, text: &str, height: f32) {