Use standard desktop application menus
Some checks failed
Dependency security audit / rustsec (push) Has been cancelled
Some checks failed
Dependency security audit / rustsec (push) Has been cancelled
This commit is contained in:
37
Cargo.lock
generated
37
Cargo.lock
generated
@@ -4933,6 +4933,19 @@ dependencies = [
|
||||
"thiserror 2.0.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_aw"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b7f923642b024c415150b70ef84ee5c11626e3b0af73b954d817d573bcd0a4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"iced_core",
|
||||
"iced_fonts",
|
||||
"iced_widget",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.14.0"
|
||||
@@ -4962,6 +4975,29 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_fonts"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "214cff7c8499e328774216690e58e315a1a5f8f6fdd1035aed6298e62ffc4c1d"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
"iced_fonts_macros",
|
||||
"iced_widget",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_fonts_macros"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef5125e110cb19cd1910a28298661c98c5d9ab02eef43594968352940e8752e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.14.0"
|
||||
@@ -5430,6 +5466,7 @@ version = "1.0.0"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"iced",
|
||||
"iced_aw",
|
||||
"ironstorage",
|
||||
"muda",
|
||||
"rfd",
|
||||
|
||||
@@ -37,6 +37,7 @@ gix = { version = "0.86", default-features = false, features = ["blocking-http-t
|
||||
gix-config = "0.59"
|
||||
hmac = "0.12"
|
||||
iced = { version = "0.14", features = ["canvas", "tokio"] }
|
||||
iced_aw = { version = "0.14.1", default-features = false, features = ["menu"] }
|
||||
icns = "0.4"
|
||||
image = { version = "0.25", default-features = false, features = ["gif", "jpeg", "png"] }
|
||||
ironstorage = { path = "crates/storage" }
|
||||
|
||||
@@ -18,7 +18,7 @@ The current direct dependencies are:
|
||||
| [clap 4.6](https://crates.io/crates/clap/4.6.4), [clap_complete 4.6](https://crates.io/crates/clap_complete/4.6.9) | CLI parsing and in-process shell completion generation | MIT OR Apache-2.0 |
|
||||
| [crossterm 0.29](https://crates.io/crates/crossterm/0.29.0) | Terminal I/O | MIT |
|
||||
| [Ratatui 0.30](https://crates.io/crates/ratatui/0.30.2) | TUI | MIT |
|
||||
| [Iced 0.14](https://crates.io/crates/iced/0.14.0) | Desktop UI | MIT |
|
||||
| [Iced 0.14](https://crates.io/crates/iced/0.14.0), [iced_aw 0.14](https://crates.io/crates/iced_aw/0.14.1) | Desktop UI and standard in-window application menus | MIT |
|
||||
| [muda 0.19](https://crates.io/crates/muda/0.19.3) | Safe native macOS application menus and standard roles | MIT OR Apache-2.0 |
|
||||
| [rfd 0.17](https://crates.io/crates/rfd/0.17.2), [ashpd 0.13](https://crates.io/crates/ashpd/0.13.13) | Native macOS/Windows folder picker and Linux XDG portal folder picker | MIT |
|
||||
| [gix 0.86](https://crates.io/crates/gix/0.86.0), [gix-config 0.59](https://crates.io/crates/gix-config/0.59.0) | Embedded Git objects, index, references, fetch, and merge | MIT OR Apache-2.0 |
|
||||
@@ -59,7 +59,7 @@ decision.
|
||||
| Password generation | [`rand`](https://crates.io/crates/rand) | MIT OR Apache-2.0 | Preferred using the operating-system CSPRNG. |
|
||||
| TOTP and HOTP | [`hmac` 0.12](https://crates.io/crates/hmac/0.12.1), [`sha1` 0.10](https://crates.io/crates/sha1/0.10.7), [`sha2` 0.10](https://crates.io/crates/sha2/0.10.9), [`data-encoding` 2.11](https://crates.io/crates/data-encoding/2.11.1) | MIT or MIT OR Apache-2.0 | Selected for a small storage-owned implementation with RFC 4226/6238 vectors. Handled URIs retain exact bytes while decoded secrets zeroize; `totp-rs` rejects HOTP URIs and cannot cover all of `pass-otp`. |
|
||||
| Native desktop clipboard | [`arboard` 3.6](https://crates.io/crates/arboard/3.6.1) | MIT OR Apache-2.0 | Selected with image support disabled and Wayland data-control enabled. Storage owns timeout, cleanup, and newer-content race policy; the safe adapter provides macOS, Windows, X11, and Wayland text access without helper processes. |
|
||||
| Native macOS menu bar | [`muda` 0.19](https://crates.io/crates/muda/0.19.3) | MIT OR Apache-2.0 | Selected only on macOS. Its safe `NSApp` adapter supplies standard roles and accelerators; Linux and Windows use the same action registry through an in-window Iced menu bar, avoiding GTK and Win32 integration dependencies. |
|
||||
| Desktop menu bar | [`muda` 0.19](https://crates.io/crates/muda/0.19.3), [`iced_aw` 0.14](https://crates.io/crates/iced_aw/0.14.1) | MIT OR Apache-2.0; MIT | `muda` supplies native macOS roles and accelerators. Linux and Windows use `iced_aw`'s standard Iced menu bar over the same action registry, avoiding a separate GTK or Win32 window integration. |
|
||||
| Native folder picker | [`rfd` 0.17](https://crates.io/crates/rfd/0.17.2), [`ashpd` 0.13](https://crates.io/crates/ashpd/0.13.13) | MIT | `rfd` is selected without default features for safe native macOS and Windows panels. Linux uses `ashpd` directly over the XDG Desktop Portal so the application never takes `rfd`'s `zenity` subprocess fallback. |
|
||||
| CLI cancellation | [`ctrlc` 3.5](https://crates.io/crates/ctrlc/3.5.2) | MIT OR Apache-2.0 | Selected for cross-platform interruption of the blocking clipboard lease. Ctrl-C requests storage cleanup before the CLI returns cancellation. |
|
||||
| Hidden CLI input | [`rpassword` 7.5](https://crates.io/crates/rpassword/7.5.4) | Apache-2.0 | Selected for portable terminal input with echo disabled. The CLI immediately moves returned strings into storage-owned zeroizing OTP input objects; it does not own validation or confirmation policy. |
|
||||
|
||||
@@ -29,6 +29,7 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced_aw.workspace = true
|
||||
ironstorage = { workspace = true, features = ["ssh"] }
|
||||
tokio.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
@@ -31,6 +31,7 @@ use iced::{
|
||||
},
|
||||
window,
|
||||
};
|
||||
use iced_aw::menu::{Item, Menu, MenuBar};
|
||||
use ironstorage::{
|
||||
authentication::{
|
||||
AuthenticationClock, AuthenticationError, AuthenticationHandle, AuthenticationSession,
|
||||
@@ -98,7 +99,6 @@ struct RecipientSuccess {
|
||||
enum Message {
|
||||
Action(UiAction),
|
||||
FieldAction(EntryFieldId, UiAction),
|
||||
ToggleMenu(MenuGroup),
|
||||
PaletteQueryChanged(String),
|
||||
PaletteCancel,
|
||||
PaletteInvoke(UiAction),
|
||||
@@ -358,7 +358,6 @@ struct App {
|
||||
generation_form: Option<GenerateForm>,
|
||||
conflict: bool,
|
||||
status: String,
|
||||
open_menu: Option<MenuGroup>,
|
||||
utility: Option<UtilityView>,
|
||||
context_target: Option<TreeNodeId>,
|
||||
palette: CommandPalette,
|
||||
@@ -1064,7 +1063,6 @@ impl App {
|
||||
generation_form: None,
|
||||
conflict: false,
|
||||
status: "Loading shared configuration…".to_owned(),
|
||||
open_menu: None,
|
||||
utility: None,
|
||||
context_target: None,
|
||||
palette: CommandPalette::default(),
|
||||
@@ -1097,9 +1095,6 @@ impl App {
|
||||
}
|
||||
return self.invoke_action(action);
|
||||
}
|
||||
Message::ToggleMenu(group) => {
|
||||
self.open_menu = (self.open_menu != Some(group)).then_some(group);
|
||||
}
|
||||
Message::PaletteQueryChanged(query) => {
|
||||
self.palette.update_query(query);
|
||||
return iced::widget::operation::snap_to(
|
||||
@@ -2680,7 +2675,6 @@ impl App {
|
||||
}
|
||||
|
||||
fn invoke_action(&mut self, action: UiAction) -> Task<Message> {
|
||||
self.open_menu = None;
|
||||
if !action::enabled(action, self.action_context()) {
|
||||
if self.content_mode == ContentMode::Editor
|
||||
&& matches!(
|
||||
@@ -2870,7 +2864,6 @@ impl App {
|
||||
self.status = reason.to_owned();
|
||||
return Task::none();
|
||||
}
|
||||
self.open_menu = None;
|
||||
self.utility = None;
|
||||
self.palette.open();
|
||||
iced::widget::operation::focus(command_palette_input_id())
|
||||
@@ -4340,41 +4333,44 @@ fn platform_menu_bar(app: &App) -> Element<'_, Message> {
|
||||
if cfg!(target_os = "macos") {
|
||||
return container(row![]).height(Length::Fixed(0.0)).into();
|
||||
}
|
||||
let mut headers = row![].spacing(2).padding([0, 8]);
|
||||
for group in MenuGroup::ALL {
|
||||
headers = headers.push(
|
||||
button(group.label())
|
||||
.on_press(Message::ToggleMenu(group))
|
||||
.style(if app.open_menu == Some(group) {
|
||||
button::primary
|
||||
} else {
|
||||
button::text
|
||||
}),
|
||||
);
|
||||
}
|
||||
let mut menu = column![headers].spacing(4);
|
||||
if let Some(group) = app.open_menu {
|
||||
let context = app.action_context();
|
||||
let mut actions = row![].spacing(4).padding([6, 8]);
|
||||
for spec in action::actions_in(group) {
|
||||
let mut menus = Vec::new();
|
||||
for group in MenuGroup::ALL {
|
||||
let width = match group {
|
||||
MenuGroup::Window => 140.0,
|
||||
MenuGroup::Help => 180.0,
|
||||
_ => 240.0,
|
||||
};
|
||||
let items = action::actions_in(group)
|
||||
.map(|spec| {
|
||||
let label = action::shortcut_label(spec.action).map_or_else(
|
||||
|| spec.label.to_owned(),
|
||||
|key| format!("{} {key}", spec.label),
|
||||
);
|
||||
let item = button(text(label));
|
||||
actions = actions.push(if action::enabled(spec.action, context) {
|
||||
let item = button(
|
||||
text(label)
|
||||
.width(Length::Fill)
|
||||
.align_x(iced::alignment::Horizontal::Left),
|
||||
)
|
||||
.width(width)
|
||||
.padding([3, 8])
|
||||
.style(button::text);
|
||||
Item::new(if action::enabled(spec.action, context) {
|
||||
item.on_press(Message::Action(spec.action))
|
||||
} else {
|
||||
item
|
||||
});
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
menus.push(Item::with_menu(
|
||||
button(group.label()).padding([3, 8]).style(button::text),
|
||||
Menu::new(items).width(width),
|
||||
));
|
||||
}
|
||||
menu = menu.push(
|
||||
scrollable(actions).direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::default(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
container(menu).width(Length::Fill).into()
|
||||
MenuBar::new(menus)
|
||||
.close_on_item_click(true)
|
||||
.close_on_background_click(true)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn utility_title(utility: &UtilityView) -> &'static str {
|
||||
@@ -6734,7 +6730,6 @@ mod tests {
|
||||
generation_form: None,
|
||||
conflict: false,
|
||||
status: String::new(),
|
||||
open_menu: None,
|
||||
utility: None,
|
||||
context_target: None,
|
||||
palette: CommandPalette::default(),
|
||||
|
||||
Reference in New Issue
Block a user