mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-13 11:54:50 +00:00
* Add craft-menu: Mac menu-bar layout and native-menu driver A platform-free menu model (built directly or from flat rows), the macOS layout (app menu, Window, Help, system-key clash resolution, translatable labels), and NativeMenu, which feeds a backend's key equivalents to egui as key presses, runs Minimize and craft-quit's Quit, and re-syncs only when the app's state changes. Generalised from PhotoCraft's native_menu.rs. * craft-menu: macOS menu bar through muda * craft-menu: clipboard keys, layout-assigned shortcuts, Hide Others and atomic rebuilds * craft-menu: menu key equivalents run while a text field has the keys * craft-menu: commands as CommandRef, item kinds, craft-keymap chords Items and rows run a craft_keymap::CommandRef and take a craft_keymap::Chord shortcut; crates/menu/src/chord.rs is gone. Native ids are the command's key, made unique with #2, #3 when a command is shown twice. Item.checked becomes ItemKind { Plain, Check, Radio } (radio shows as a check item on macOS). The built-in items' commands are Builtin::{Hide, HideOthers, Minimize, Quit}. MenuRole gains Edit, View and Other; tag_submenu finds its menu by role. While text editing, the keys left to the field are the clipboard keys and craft_keymap::text_editing_chords. Adds the Platforms, Document and Gate types (data only) and MenuBar::documents. * craft-menu: Gate::allows, the one rule for what may run from the menus * craft-menu: Windows, Linux and web layouts, documents in Window, keymap adapter layout(bar, Target, options) dispatches to mac_layout, desktop_layout or web_layout. Every layout drops what the target's Platforms leave out and lists MenuBar::documents as radio items last in the Window menu (ItemRole::Document is gone). Windows and Linux put About in Help, Settings in Edit as Preferences and Quit in File (Exit on Windows); the web layout has no Quit and takes the browser's own shortcuts off the items, reporting clashes. tidy drops empty submenus. apply_keymap sets shortcuts and toggle check items from a craft-keymap Keymap; apply_state sets enabled and checked per frame. * craft-menu: the egui-drawn menu bar for Windows, Linux and the web * craft-menu: gate, command hook and one muda handler for the native menu - NativeMenu::set_gate: app items greyed in place while modal or recording a shortcut; take_clicks drops clicks the gate forbids when they are taken. While recording, key equivalents reach egui as plain key presses. - NativeMenu::on_command: a hook called from the menu event handler for each allowed app click; take_clicks still returns every command. - Dispatch, shared between the driver and the backend's handler (Backend::attach replaces Backend::set_text_editing). - Only Cmd or Ctrl chords become key equivalents; other shortcuts are not shown natively (building an attributed title needs unsafe objc2 calls). - mac: one muda event handler per process, routed to the latest install; mac::install_app_menu ports craft-quit's minimal app menu onto it. - craft_quit::install_app_menu is deprecated in favor of craft_menu::mac::install_app_menu. * craft-menu: the bar takes Target, README section and NOTICE credit * craft-menu: bar tests beside the layout and native ones; only a click outside closes the menus * craft-menu: clippy (complex types, Copy clones, elided lifetimes) * craft-menu: keyboard access to the egui bar A tap on Alt alone or F10 focuses the bar, Alt+letter opens a menu and a letter in an open menu runs or opens the row with that mnemonic, as in a Windows menu bar. Mnemonics are assigned per level from the shown labels, underlined only while the keyboard drives the bar, and a label without an ASCII letter shows its English letter after it ("文件(F)") through the optional english hook. Preferred letters come from MenuBarUi::mnemonics. A Mac target has none, and a dialog or the shortcut recorder keeps the keys. Ported from PhotoCraft's menu_keys.rs and menu_keys_tests.rs. * craft-menu: one installer or the other; sort mnemonics before checking uniqueness * craft-menu: egui 0.37 * craft-menu: text fields keep their Edit items; web names by the browser's system; sync per frame; stable check kinds; handled hook clicks * craft-menu: sync reports a clash change on an unchanged bar
63 lines
2.6 KiB
Rust
63 lines
2.6 KiB
Rust
//! Shortcuts and checked states from the app's craft-keymap [`Keymap`], and the per-frame
|
|
//! enabled and checked update.
|
|
|
|
use craft_keymap::{CommandRef, Keymap};
|
|
|
|
use crate::model::{Builtin, ItemKind, MenuBar};
|
|
|
|
/// Give each item the shortcut its command has in `keymap`, the user's overrides included, and
|
|
/// make the items of toggle commands ([`craft_keymap::Bindable::is_toggle`]) check items.
|
|
///
|
|
/// The shortcut is the first single-press binding, one in the command's home scope first; a
|
|
/// double press can't be a menu shortcut. A command the keymap binds to nothing loses its
|
|
/// shortcut, and an item whose command the keymap doesn't know keeps its own. A toggle's check
|
|
/// item keeps its checked state; radio items stay radio items. Call it on the app's bar, before
|
|
/// the layout.
|
|
pub fn apply_keymap(bar: &mut MenuBar, keymap: &Keymap) {
|
|
bar.for_each_item_mut(|it| {
|
|
let Some(bindable) = keymap.bindable(&it.command) else { return };
|
|
let bindings = keymap.bindings(&it.command);
|
|
let single = || bindings.iter().filter(|b| !b.trigger.double);
|
|
let home = single().find(|b| *b.scope_or(&bindable.scope) == bindable.scope);
|
|
it.shortcut = home.or_else(|| single().next()).map(|b| b.trigger.chord);
|
|
if bindable.is_toggle && it.kind == ItemKind::Plain {
|
|
it.kind = ItemKind::Check(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// An item's state for the frame.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct ItemState {
|
|
pub enabled: bool,
|
|
/// Checked or selected; `None` leaves the item's [`ItemKind`] as it is.
|
|
pub checked: Option<bool>,
|
|
}
|
|
|
|
impl Default for ItemState {
|
|
/// Enabled, the kind left as it is.
|
|
fn default() -> ItemState {
|
|
ItemState { enabled: true, checked: None }
|
|
}
|
|
}
|
|
|
|
/// Set every item's enabled and checked state from `state`, called once per item of the app's
|
|
/// commands. `checked: Some(..)` makes a plain item a check item and sets a radio item's
|
|
/// selection. The built-in items and the open documents are left alone.
|
|
pub fn apply_state(bar: &mut MenuBar, mut state: impl FnMut(&CommandRef) -> ItemState) {
|
|
let documents: Vec<CommandRef> = bar.documents.iter().map(|d| d.command.clone()).collect();
|
|
bar.for_each_item_mut(|it| {
|
|
if Builtin::of(&it.command).is_some() || documents.contains(&it.command) {
|
|
return;
|
|
}
|
|
let s = state(&it.command);
|
|
it.enabled = s.enabled;
|
|
if let Some(on) = s.checked {
|
|
it.kind = match it.kind {
|
|
ItemKind::Radio(_) => ItemKind::Radio(on),
|
|
ItemKind::Plain | ItemKind::Check(_) => ItemKind::Check(on),
|
|
};
|
|
}
|
|
});
|
|
}
|