| author | |
| committer | |
| log | d92401a36cb3a706f3f80bd0b6aeb1f5cb789375 |
| tree | 287ee12af218f42c20e942aa6cdbddc4763f7504 |
| parent | 9367f7dcc5c2e41ce870e0579a703718aa2e69bd |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Options and Save As put OK last, Notebook Properties, Link and Customize Tags
first. Every Cancel and default pair now stands as the platform's own dialogs
place it, the default last on macOS and GNOME and first on Windows, the
default outlined in the accent as Enter's button. Password Protection takes
the keyboard's focus as it opens, and Save As's labels end in colons as the
other dialogs' do.
Assisted-by: claude-opus-5.511 files changed, 75 insertions(+), 45 deletions(-)
crates/snowbound/src/link.rs+2-2| ... | ... | @@ -211,8 +211,8 @@ impl State { |
| 211 | 211 | } |
| 212 | 212 | ui.close(); |
| 213 | 213 | crate::buttons(ui); |
| 214 | let ok = ui::button(ui, "ok", "OK").clicked || entered; | |
| 215 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 214 | let [cancel, ok] = ui::dialog_buttons(ui, "OK", true); | |
| 215 | let ok = ok || entered; | |
| 216 | 216 | ui.close(); |
| 217 | 217 | ui.close(); |
| 218 | 218 | if let Some((space, title)) = picked |
crates/snowbound/src/options.rs+2-2| ... | ... | @@ -674,8 +674,8 @@ impl State { |
| 674 | 674 | ..Spec::default() |
| 675 | 675 | }, |
| 676 | 676 | ); |
| 677 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 678 | let ok = ui::button(ui, "ok", "OK").clicked || entered; | |
| 677 | let [cancel, ok] = ui::dialog_buttons(ui, "OK", true); | |
| 678 | let ok = ok || entered; | |
| 679 | 679 | ui.close(); |
| 680 | 680 | ui.close(); |
| 681 | 681 | let name = options.user_name.trim(); |
crates/snowbound/src/print.rs+2-2| ... | ... | @@ -256,9 +256,9 @@ impl State { |
| 256 | 256 | } |
| 257 | 257 | }); |
| 258 | 258 | crate::buttons(ui); |
| 259 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 260 | 259 | let action = if dialog.export { "Export" } else { "Print" }; |
| 261 | let go = ui::button(ui, "go", action).clicked || entered; | |
| 260 | let [cancel, go] = ui::dialog_buttons(ui, action, true); | |
| 261 | let go = go || entered; | |
| 262 | 262 | ui.close(); |
| 263 | 263 | ui.close(); |
| 264 | 264 | if !go && !cancel { |
crates/snowbound/src/properties.rs+2-2| ... | ... | @@ -185,8 +185,8 @@ impl State { |
| 185 | 185 | crate::platform::reveal(&dialog.library.location); |
| 186 | 186 | } |
| 187 | 187 | crate::buttons(ui); |
| 188 | let ok = ui::button(ui, "ok", "OK").clicked || entered; | |
| 189 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 188 | let [cancel, ok] = ui::dialog_buttons(ui, "OK", true); | |
| 189 | let ok = ok || entered; | |
| 190 | 190 | ui.close(); |
| 191 | 191 | ui.close(); |
| 192 | 192 | let name = dialog.name.trim(); |
crates/snowbound/src/protection.rs+14-6| ... | ... | @@ -262,7 +262,6 @@ impl State { |
| 262 | 262 | dialog, |
| 263 | 263 | }); |
| 264 | 264 | self.ui.open_popup(id()); |
| 265 | self.ui.set_focus(None); | |
| 266 | 265 | } |
| 267 | 266 | |
| 268 | 267 | /// Locks every protected section unlocked this run, as Lock All and Ctrl+Alt+L do. |
| ... | ... | @@ -420,7 +419,8 @@ impl State { |
| 420 | 419 | }, |
| 421 | 420 | ); |
| 422 | 421 | }; |
| 423 | let mut buttons: &[&str] = &["Cancel", "OK"]; | |
| 422 | // Password Protection's own buttons; the others' are Cancel and OK. | |
| 423 | let mut buttons: &[&str] = &[]; | |
| 424 | 424 | match &mut asking.dialog { |
| 425 | 425 | Dialog::Unlock(value, wrong) => { |
| 426 | 426 | text( |
| ... | ... | @@ -502,12 +502,20 @@ impl State { |
| 502 | 502 | ..Spec::default() |
| 503 | 503 | }, |
| 504 | 504 | ); |
| 505 | let pressed = buttons | |
| 506 | .iter() | |
| 507 | .position(|label| ui::button(ui, *label, label).clicked); | |
| 505 | let pressed = if buttons.is_empty() { | |
| 506 | match ui::dialog_buttons(ui, "OK", true) { | |
| 507 | [true, _] => Some("Cancel"), | |
| 508 | [_, true] => Some("OK"), | |
| 509 | _ => None, | |
| 510 | } | |
| 511 | } else { | |
| 512 | (buttons.iter()) | |
| 513 | .find(|label| ui::button(ui, **label, label).clicked) | |
| 514 | .copied() | |
| 515 | }; | |
| 508 | 516 | ui.close(); |
| 509 | 517 | ui.close(); |
| 510 | let chosen = pressed.map(|at| buttons[at]).or(entered.then_some("OK")); | |
| 518 | let chosen = pressed.or(entered.then_some("OK")); | |
| 511 | 519 | let Asking { |
| 512 | 520 | library, |
| 513 | 521 | path, |
crates/snowbound/src/save_as.rs+3-3| ... | ... | @@ -134,8 +134,8 @@ impl State { |
| 134 | 134 | dialog.format = at; |
| 135 | 135 | } |
| 136 | 136 | crate::buttons(ui); |
| 137 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 138 | let go = ui::button(ui, "go", "Save As").clicked || entered; | |
| 137 | let [cancel, go] = ui::dialog_buttons(ui, "Save As", true); | |
| 138 | let go = go || entered; | |
| 139 | 139 | ui.close(); |
| 140 | 140 | ui.close(); |
| 141 | 141 | if !go && !cancel { |
| ... | ... | @@ -249,7 +249,7 @@ fn choose(ui: &mut ui::Ui, label: &str, names: &[&str], current: usize) -> Optio |
| 249 | 249 | "label", |
| 250 | 250 | Spec { |
| 251 | 251 | size: [px(110.0), px(row)], |
| 252 | text: Some(label), | |
| 252 | text: Some(&format!("{label}:")), | |
| 253 | 253 | ..Spec::default() |
| 254 | 254 | }, |
| 255 | 255 | ); |
crates/snowbound/src/server.rs+4-21| ... | ... | @@ -825,29 +825,12 @@ impl State { |
| 825 | 825 | ..Spec::default() |
| 826 | 826 | }, |
| 827 | 827 | ); |
| 828 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 829 | 828 | let waiting = matches!(connect.status, Status::Waiting(_)); |
| 830 | let go = match connect.step { | |
| 831 | Step::Browse if connect.opens().is_none() => { | |
| 832 | ui.leaf( | |
| 833 | "open", | |
| 834 | Spec { | |
| 835 | size: [ui::fit(), px(row)], | |
| 836 | text: Some("Open"), | |
| 837 | color: Some(theme.text_dim), | |
| 838 | fill: Some(theme.chip), | |
| 839 | radius: 4.0, | |
| 840 | pad: [theme.font_size * 0.75, 0.0], | |
| 841 | center: true, | |
| 842 | role: Some(Role::Button), | |
| 843 | ..Spec::default() | |
| 844 | }, | |
| 845 | ); | |
| 846 | false | |
| 847 | } | |
| 848 | Step::Browse => ui::button(ui, "open", "Open").clicked, | |
| 849 | _ => ui::button(ui, "connect", "Connect").clicked || entered, | |
| 829 | let [cancel, go] = match connect.step { | |
| 830 | Step::Browse => ui::dialog_buttons(ui, "Open", connect.opens().is_some()), | |
| 831 | _ => ui::dialog_buttons(ui, "Connect", true), | |
| 850 | 832 | }; |
| 833 | let go = go || entered && connect.step != Step::Browse; | |
| 851 | 834 | ui.close(); |
| 852 | 835 | ui.close(); |
| 853 | 836 | if cancel { |
crates/snowbound/src/tags.rs+3-4| ... | ... | @@ -525,8 +525,7 @@ impl State { |
| 525 | 525 | }); |
| 526 | 526 | ui.close(); |
| 527 | 527 | crate::buttons(ui); |
| 528 | let ok = button(ui, "ok", "OK", list.tags != self.tags); | |
| 529 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 528 | let [cancel, ok] = ui::dialog_buttons(ui, "OK", list.tags != self.tags); | |
| 530 | 529 | ui.close(); |
| 531 | 530 | if let Some(place) = list.selected { |
| 532 | 531 | if up { |
| ... | ... | @@ -870,8 +869,8 @@ fn tag_editor( |
| 870 | 869 | ); |
| 871 | 870 | crate::buttons(ui); |
| 872 | 871 | let named = !tag.label.trim().is_empty(); |
| 873 | let ok = button(ui, "ok", "OK", named) || entered && named; | |
| 874 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 872 | let [cancel, ok] = ui::dialog_buttons(ui, "OK", named); | |
| 873 | let ok = ok || entered && named; | |
| 875 | 874 | ui.close(); |
| 876 | 875 | ui.close(); |
| 877 | 876 | if ok { |
crates/snowbound/src/unpack.rs+2-2| ... | ... | @@ -168,8 +168,8 @@ impl State { |
| 168 | 168 | }, |
| 169 | 169 | ); |
| 170 | 170 | crate::buttons(ui); |
| 171 | let cancel = ui::button(ui, "cancel", "Cancel").clicked; | |
| 172 | let create = ui::button(ui, "create", "Create").clicked || entered; | |
| 171 | let [cancel, create] = ui::dialog_buttons(ui, "Create", true); | |
| 172 | let create = create || entered; | |
| 173 | 173 | ui.close(); |
| 174 | 174 | ui.close(); |
| 175 | 175 | if browse { |
crates/ui/src/lib.rs+1-1| ... | ... | @@ -16,7 +16,7 @@ pub use list::{List, Row, Rows, list}; |
| 16 | 16 | pub use theme::{Menu, PopupMotion, Section, Shades, Shadow, Theme}; |
| 17 | 17 | pub use widgets::{ |
| 18 | 18 | PaintedScroller, Scroller, ScrollerPart, Scrollers, badge, badge_width, button, check_box, |
| 19 | edit_key, edit_modifiers, password_field, scrollbar, text_field, | |
| 19 | dialog_buttons, edit_key, edit_modifiers, password_field, scrollbar, text_field, | |
| 20 | 20 | }; |
| 21 | 21 | |
| 22 | 22 | use draw::{ |
crates/ui/src/widgets.rs+40| ... | ... | @@ -32,6 +32,46 @@ pub fn button(ui: &mut Ui, part: impl Hash, text: &str) -> Signal { |
| 32 | 32 | ui.leaf(part, spec) |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | /// Cancel and a dialog's default button `text`, which takes no clicks while not `enabled`, | |
| 36 | /// in the platform's order: the default last on macOS and GNOME, first on Windows, as their | |
| 37 | /// own dialogs place them. The default wears the accent around it. Whether each was clicked, | |
| 38 | /// Cancel first. | |
| 39 | pub fn dialog_buttons(ui: &mut Ui, text: &str, enabled: bool) -> [bool; 2] { | |
| 40 | let default = |ui: &mut Ui| { | |
| 41 | let theme = &ui.theme; | |
| 42 | let spec = Spec { | |
| 43 | flags: if enabled { | |
| 44 | Flags::CLICKABLE | |
| 45 | } else { | |
| 46 | Flags::default() | |
| 47 | }, | |
| 48 | size: [fit(), px(theme.font_size * 2.0)], | |
| 49 | text: Some(text), | |
| 50 | color: (!enabled).then_some(theme.text_dim), | |
| 51 | fill: Some(theme.chip), | |
| 52 | hover_fill: enabled.then(|| theme.hover()), | |
| 53 | border: enabled.then_some(theme.accent), | |
| 54 | radius: 4.0, | |
| 55 | pad: [theme.font_size * 0.75, 0.0], | |
| 56 | center: true, | |
| 57 | role: Some(Role::Button), | |
| 58 | ..Spec::default() | |
| 59 | }; | |
| 60 | let clicked = ui.leaf("default", spec).clicked; | |
| 61 | if !enabled && let Some(node) = ui.access(ui.id("default")) { | |
| 62 | node.set_disabled(); | |
| 63 | } | |
| 64 | clicked | |
| 65 | }; | |
| 66 | let windows = edit::Platform::CURRENT == edit::Platform::Windows; | |
| 67 | let mut chosen = windows && default(ui); | |
| 68 | let cancel = button(ui, "cancel", "Cancel").clicked; | |
| 69 | if !windows { | |
| 70 | chosen = default(ui); | |
| 71 | } | |
| 72 | [cancel, chosen] | |
| 73 | } | |
| 74 | ||
| 35 | 75 | /// A short tag in the accent's colour, centred in a box `height` tall, as BETA marks a |
| 36 | 76 | /// feature still settling beside its name. |
| 37 | 77 | pub fn badge(ui: &mut Ui, part: impl Hash, text: &str, height: f32) { |