Files
ElyOffice/apps/ely-show/src/insert.rs
T
ZacharyZhang-NY d59159cea2 Fixes for M-02 to M-14, G-01 to G-14, K-02 to K-04, X-04 to X-14, not yet reviewed
Committed before review so Windows and WSL builds can start.
Each fix was tested and mutation-checked here; the codex review
of each item is still to come, so tasks/BUGS.md stays unticked.

M-02 is mid-review: round 1 findings are fixed (apps/ely-mark/src/tally.rs).
K-01 stays deferred. The ElyMark window test mark-outside.sh failed
once at its crash-recovery step in the last run; not yet looked into.
2026-10-10 02:29:19 -04:00

181 lines
6.6 KiB
Rust

//! The Insert and View menus (SHOW-03 to 06).
use ely_office::prelude::*;
use gpui::{App, Context, Div, InteractiveElement as _, Menu, MenuItem, actions};
use crate::deck::Deck;
actions!(
show,
[
InsertTextBox,
InsertRectangle,
InsertEllipse,
InsertTriangle,
InsertLine,
InsertArrow,
InsertConnector,
InsertPicture,
InsertMedia,
InsertTable,
InsertChart,
InsertRemark,
ShowRemarks,
SnapToGrid,
SnapToGuides,
AddHorizontalGuide,
AddVerticalGuide
]
);
/// Insert and View; View checks what is on.
pub fn menus((grid, guides, remarks): (bool, bool, bool), cx: &App) -> [Menu; 2] {
let label = |key: &str| Deck::text(cx, key, &[]);
let media = ely_platform::media::BUILT.then(|| MenuItem::action(label("show.insert.media"), InsertMedia));
let insert = [
MenuItem::action(label("show.insert.text"), InsertTextBox),
MenuItem::separator(),
MenuItem::action(label("show.insert.rectangle"), InsertRectangle),
MenuItem::action(label("show.insert.ellipse"), InsertEllipse),
MenuItem::action(label("show.insert.triangle"), InsertTriangle),
MenuItem::action(label("show.insert.line"), InsertLine),
MenuItem::action(label("show.insert.arrow"), InsertArrow),
MenuItem::action(label("show.insert.connector"), InsertConnector),
MenuItem::separator(),
MenuItem::action(label("show.insert.picture"), InsertPicture),
]
.into_iter()
.chain(media)
.chain([
MenuItem::action(label("show.insert.table"), InsertTable),
MenuItem::action(label("show.insert.chart"), InsertChart),
MenuItem::separator(),
MenuItem::action(label("show.insert.remark"), InsertRemark),
]);
let insert = Menu::new(label("menu.insert")).items(insert);
let view = Menu::new(label("menu.view")).items([
MenuItem::action(label("show.view.remarks"), ShowRemarks).checked(remarks),
MenuItem::separator(),
MenuItem::action(label("show.view.snap_grid"), SnapToGrid).checked(grid),
MenuItem::action(label("show.view.snap_guides"), SnapToGuides).checked(guides),
MenuItem::separator(),
MenuItem::action(label("show.view.guide_across"), AddHorizontalGuide),
MenuItem::action(label("show.view.guide_down"), AddVerticalGuide),
MenuItem::separator(),
]);
let view = view.items(ely_office::zoom_items(true, cx));
[insert, view]
}
/// Binds an action to an object command.
macro_rules! run {
($root:expr, $cx:expr, $action:ty, $id:literal, $values:expr) => {
$root.on_action(
$cx.listener(|deck, _: &$action, _, cx| deck.object_command($id, $values, cx)),
)
};
}
pub fn bind(root: Div, cx: &mut Context<Deck>) -> Div {
let root = run!(root, cx, InsertTextBox, "draw.text", &["true"]);
let root = run!(
root,
cx,
InsertRectangle,
"draw.shape",
&["rectangle", "true"]
);
let root = run!(root, cx, InsertEllipse, "draw.shape", &["ellipse", "true"]);
let root = run!(
root,
cx,
InsertTriangle,
"draw.shape",
&["isosceles-triangle", "true"]
);
let root = run!(root, cx, InsertLine, "draw.line", &[]);
let root = run!(root, cx, InsertArrow, "draw.arrow", &[]);
let root = run!(root, cx, InsertConnector, "draw.connector", &[]);
let root = run!(root, cx, SnapToGrid, "view.snap_grid", &[]);
let root = run!(root, cx, SnapToGuides, "view.snap_guides", &[]);
root.on_action(cx.listener(|deck, _: &InsertPicture, _, cx| deck.pick_picture(false, cx)))
.on_action(cx.listener(|deck, _: &InsertMedia, _, cx| deck.pick_media(cx)))
.on_action(cx.listener(|deck, _: &InsertTable, _, cx| deck.ask_table(cx)))
.on_action(cx.listener(|deck, _: &InsertChart, _, cx| deck.ask_chart(cx)))
.on_action(
cx.listener(|deck, _: &InsertRemark, window, cx| deck.ask_remark(None, window, cx)),
)
.on_action(cx.listener(|deck, _: &ShowRemarks, _, cx| deck.toggle_remarks(cx)))
.on_action(cx.listener(|deck, _: &AddHorizontalGuide, _, cx| deck.add_guide(false, cx)))
.on_action(cx.listener(|deck, _: &AddVerticalGuide, _, cx| deck.add_guide(true, cx)))
}
impl Deck {
/// Runs an object command; the next read shows the result.
pub fn object_command(&mut self, id: &str, values: &[&str], cx: &mut Context<Self>) {
let Some(tab) = self.core.active else {
return;
};
self.run_in(Some(tab), id, values, cx);
self.force_reread(tab, cx);
}
/// Inserts a picture, or replaces the selected one.
pub fn pick_picture(&mut self, replace: bool, cx: &mut Context<Self>) {
self.pick(false, cx, move |deck, path, cx| {
let url = ely_lok::file_url(&path);
let id = if replace {
"image.replace"
} else {
"image.insert"
};
deck.object_command(id, &[&url], cx)
});
}
/// Shows comments, or properties, in the right column.
fn toggle_remarks(&mut self, cx: &mut Context<Self>) {
self.remarks_open = !self.remarks_open;
self.snap_menus = None;
self.sync_menus(cx);
cx.notify();
}
/// A guide through the middle of the slide.
fn add_guide(&mut self, vertical: bool, cx: &mut Context<Self>) {
let Some(tab) = self.active_tab() else {
return;
};
let (w, h) = tab.extra.shown_size();
let (kind, x, y) = if vertical {
("1", w / 2, 0)
} else {
("0", 0, h / 2)
};
self.object_command("guide.add", &[kind, &x.to_string(), &y.to_string()], cx);
}
}
#[cfg(test)]
mod tests {
#[test]
fn insert_media_shows_only_where_it_plays() {
let cx = gpui::TestAppContext::single();
cx.update(ely_gpui_component::init_for_tests);
let (offered, total) = cx.update(|cx| {
ely_ui::settings::install_defaults(cx);
ely_ui::i18n::install(cx);
let media = crate::deck::Deck::text(cx, "show.insert.media", &[]);
let [insert, _] = super::menus((false, false, false), cx);
let names = insert.items.iter().filter_map(|item| match item {
gpui::MenuItem::Action { name, .. } => Some(name.clone()),
_ => None,
});
let names: Vec<_> = names.collect();
(names.iter().any(|name| *name == media), names.len())
});
assert_eq!(offered, ely_platform::media::BUILT);
assert!(total >= 11, "the rest stay: {total}");
}
}