R5.5: ElyTable right-click menus, Excel-style; three rounds used, last fixes tested
Grid and header menus share ely_office::menu with ElyDoc. Each item's availability is the kernel's state for the exact command it runs (patch 0045). Patch 0046 lets a header select its row or column while a shape is selected or its text is edited: the view shell ends the drawing edit, keeps the typed text, leaves drawing mode, then marks. Ely no longer sends Esc first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
07c0b43542
commit
87886c8a8d
30 files changed
+1010
-106
No files matched your search
@@ -9,6 +9,7 @@ pub mod engine;
|
||||
pub mod files;
|
||||
mod find;
|
||||
pub mod lok;
|
||||
pub mod menu;
|
||||
mod open;
|
||||
mod recover;
|
||||
pub mod panel;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Ely's right-click menus, gated by the kernel's (DOC-02).
|
||||
|
||||
use std::io;
|
||||
|
||||
use ely_gpui_component::{
|
||||
menus::{ContextMenu, Menu, MenuItem},
|
||||
primitives::IconName,
|
||||
};
|
||||
use ely_lok::{LokView, menu::Offered};
|
||||
use gpui::{App, Entity, Pixels, Point};
|
||||
|
||||
/// One menu, asked for at a point in one tab.
|
||||
pub struct Offer<W> {
|
||||
pub tab: u64,
|
||||
pub what: W,
|
||||
/// Bumps for each menu, so each opens once.
|
||||
request: (u64, Point<Pixels>),
|
||||
}
|
||||
|
||||
impl<W> Offer<W> {
|
||||
/// The next menu after `last`.
|
||||
pub fn new(last: Option<&Offer<W>>, tab: u64, what: W, at: Point<Pixels>) -> Self {
|
||||
let number = last.map_or(1, |last| last.request.0 + 1);
|
||||
Offer { tab, what, request: (number, at) }
|
||||
}
|
||||
|
||||
/// Its host, opened at the point.
|
||||
pub fn host(&self, id: &'static str, menu: Menu) -> ContextMenu {
|
||||
ContextMenu::new(id, menu).manual(Some(self.request))
|
||||
}
|
||||
}
|
||||
|
||||
/// The view's last kernel menu and where it opens.
|
||||
pub fn take(view: &Entity<LokView>, cx: &mut App) -> Option<io::Result<(Offered, Point<Pixels>)>> {
|
||||
let (json, at) = view.update(cx, |view, _| view.take_menu())?;
|
||||
let offered = match ely_lok::menu::read(&json) {
|
||||
Ok(offered) => offered,
|
||||
Err(error) => return Some(Err(error)),
|
||||
};
|
||||
// Without a click it opens at the caret.
|
||||
let caret = || view.read(cx).cursor_bounds().map(|caret| caret.bottom_left());
|
||||
let Some(at) = at.or_else(caret) else {
|
||||
log::warn!("office: a menu with no click and no caret");
|
||||
return None;
|
||||
};
|
||||
log::info!("office: a {:?} menu", offered.target);
|
||||
Some(Ok((offered, at)))
|
||||
}
|
||||
|
||||
/// A menu row: its label key and its gating command.
|
||||
pub trait Pick: Copy + 'static {
|
||||
fn row(self) -> (&'static str, &'static str);
|
||||
}
|
||||
|
||||
/// A row, a submenu or a line.
|
||||
pub enum Entry<P: 'static> {
|
||||
Row(P),
|
||||
Sub(&'static str, &'static [P]),
|
||||
Line,
|
||||
}
|
||||
|
||||
/// Cut, copy and paste as one row of icons.
|
||||
pub fn clipboard<P: Pick>(menu: Menu, picks: [P; 3], item: impl Fn(P) -> MenuItem) -> Menu {
|
||||
let icons = [IconName::Scissors, IconName::Copy, IconName::Clipboard];
|
||||
let row = picks.into_iter().zip(icons).map(|(pick, icon)| item(pick).icon(icon));
|
||||
menu.buttons(row)
|
||||
}
|
||||
|
||||
/// Adds entries; a submenu is on when any row is.
|
||||
pub fn entries<P: Pick>(
|
||||
menu: Menu,
|
||||
entries: &[Entry<P>],
|
||||
cx: &App,
|
||||
on: impl Fn(P) -> bool,
|
||||
item: impl Fn(P) -> MenuItem,
|
||||
) -> Menu {
|
||||
entries.iter().fold(menu, |menu, entry| match entry {
|
||||
Entry::Row(pick) => menu.item(item(*pick)),
|
||||
Entry::Sub(key, picks) => {
|
||||
let sub = picks.iter().fold(Menu::new(), |sub, pick| sub.item(item(*pick)));
|
||||
let any = picks.iter().any(|pick| on(*pick));
|
||||
menu.item(MenuItem::submenu(crate::text(cx, key, &[]), sub).disabled(!any))
|
||||
}
|
||||
Entry::Line => menu.separator(),
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user