forked from 1b-agent/opencadstudio-upstream
Sheet set row menus: own fly-out menu widget
The row menu is a small overlay widget: a click on a submenu entry opens its fly-out instead of closing the menu, and the fly-out is placed beside the menu where it actually landed (right when it fits, otherwise left), so the menu never moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a3c79be278
commit
582e0980d9
4 files changed
+312
-45
No files matched your search
+1
-1
@@ -3041,7 +3041,7 @@ impl OpenCADStudio {
|
|||||||
auto_collapse,
|
auto_collapse,
|
||||||
),
|
),
|
||||||
crate::ui::dock::PanelId::SheetSetManager => {
|
crate::ui::dock::PanelId::SheetSetManager => {
|
||||||
crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse, side == crate::app::config::DockSide::Right)
|
crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let divider = dock_divider(id);
|
let divider = dock_divider(id);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub mod alias_editor;
|
|||||||
pub mod find_replace;
|
pub mod find_replace;
|
||||||
pub mod named_parameters;
|
pub mod named_parameters;
|
||||||
pub mod pc_manager;
|
pub mod pc_manager;
|
||||||
|
pub mod row_menu;
|
||||||
pub mod sheet_set;
|
pub mod sheet_set;
|
||||||
pub mod pdf_dialogs;
|
pub mod pdf_dialogs;
|
||||||
pub mod xref_attach;
|
pub mod xref_attach;
|
||||||
|
|||||||
@@ -0,0 +1,286 @@
|
|||||||
|
//! A right-click menu for palette rows with one level of fly-out submenus.
|
||||||
|
//!
|
||||||
|
//! The menu opens at the cursor (flipped at the window edges); an open fly-out
|
||||||
|
//! is laid out beside it — on the right when it fits, otherwise on the left —
|
||||||
|
//! so the menu itself never moves. Pressing a submenu entry keeps the menu open.
|
||||||
|
|
||||||
|
use iced::advanced::layout::{self, Layout};
|
||||||
|
use iced::advanced::widget::{self, tree, Tree, Widget};
|
||||||
|
use iced::advanced::{mouse, overlay, renderer, Shell};
|
||||||
|
use iced::{keyboard, Element, Event, Length, Point, Rectangle, Renderer, Size, Theme, Vector};
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
|
||||||
|
const FLYOUT_GAP: f32 = 2.0;
|
||||||
|
|
||||||
|
pub(crate) struct RowMenu<'a> {
|
||||||
|
underlay: Element<'a, Message>,
|
||||||
|
main: Element<'a, Message>,
|
||||||
|
/// Per entry of the menu's column: a submenu entry (pressing it keeps the menu open).
|
||||||
|
keep_open: Vec<bool>,
|
||||||
|
/// The open fly-out: its entry's index in the column, and its content.
|
||||||
|
flyout: Option<(usize, Element<'a, Message>)>,
|
||||||
|
/// Published when the menu closes with a fly-out open.
|
||||||
|
on_close: Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
show: bool,
|
||||||
|
position: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RowMenu<'a> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
underlay: impl Into<Element<'a, Message>>,
|
||||||
|
main: Element<'a, Message>,
|
||||||
|
keep_open: Vec<bool>,
|
||||||
|
flyout: Option<(usize, Element<'a, Message>)>,
|
||||||
|
on_close: Message,
|
||||||
|
) -> Self {
|
||||||
|
Self { underlay: underlay.into(), main, keep_open, flyout, on_close }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget<Message, Theme, Renderer> for RowMenu<'a> {
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
self.underlay.as_widget().size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||||
|
self.underlay.as_widget_mut().layout(&mut tree.children[0], renderer, limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.underlay.as_widget().draw(&tree.children[0], renderer, theme, style, layout, cursor, viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
tree::Tag::of::<State>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
tree::State::new(State::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
|
let mut refs: Vec<&mut Element<'a, Message>> = vec![&mut self.underlay, &mut self.main];
|
||||||
|
if let Some((_, f)) = &mut self.flyout {
|
||||||
|
refs.push(f);
|
||||||
|
}
|
||||||
|
tree.diff_children(&mut refs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: &Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &Renderer,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event {
|
||||||
|
if let Some(p) = cursor.position_over(layout.bounds()) {
|
||||||
|
let state: &mut State = tree.state.downcast_mut();
|
||||||
|
state.show = true;
|
||||||
|
state.position = p;
|
||||||
|
if self.flyout.is_some() {
|
||||||
|
shell.publish(self.on_close.clone());
|
||||||
|
}
|
||||||
|
shell.capture_event();
|
||||||
|
shell.request_redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.underlay
|
||||||
|
.as_widget_mut()
|
||||||
|
.update(&mut tree.children[0], event, layout, cursor, renderer, shell, viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.underlay.as_widget().mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn widget::Operation,
|
||||||
|
) {
|
||||||
|
self.underlay.as_widget_mut().operate(&mut tree.children[0], layout, renderer, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
tree: &'b mut Tree,
|
||||||
|
layout: Layout<'b>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
translation: Vector,
|
||||||
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||||
|
let state: &mut State = tree.state.downcast_mut();
|
||||||
|
if !state.show {
|
||||||
|
return self.underlay.as_widget_mut().overlay(&mut tree.children[0], layout, renderer, viewport, translation);
|
||||||
|
}
|
||||||
|
let position = state.position + translation;
|
||||||
|
let (_, trees) = tree.children.split_at_mut(1);
|
||||||
|
let (main_tree, fly_tree) = trees.split_at_mut(1);
|
||||||
|
Some(overlay::Element::new(Box::new(MenuOverlay {
|
||||||
|
state,
|
||||||
|
position,
|
||||||
|
main: &mut self.main,
|
||||||
|
main_tree: &mut main_tree[0],
|
||||||
|
keep_open: &self.keep_open,
|
||||||
|
flyout: self.flyout.as_mut().map(|(i, f)| (*i, f)).zip(fly_tree.first_mut()),
|
||||||
|
on_close: &self.on_close,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<RowMenu<'a>> for Element<'a, Message> {
|
||||||
|
fn from(w: RowMenu<'a>) -> Self {
|
||||||
|
Element::new(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MenuOverlay<'a, 'b> {
|
||||||
|
state: &'b mut State,
|
||||||
|
position: Point,
|
||||||
|
main: &'b mut Element<'a, Message>,
|
||||||
|
main_tree: &'b mut Tree,
|
||||||
|
keep_open: &'b [bool],
|
||||||
|
flyout: Option<((usize, &'b mut Element<'a, Message>), &'b mut Tree)>,
|
||||||
|
on_close: &'b Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The entries of a menu box (container › column › entries).
|
||||||
|
fn entries(main: Layout<'_>) -> impl Iterator<Item = Layout<'_>> {
|
||||||
|
main.children().next().into_iter().flat_map(|column| column.children())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuOverlay<'_, '_> {
|
||||||
|
fn close(&mut self, shell: &mut Shell<'_, Message>) {
|
||||||
|
self.state.show = false;
|
||||||
|
if self.flyout.is_some() {
|
||||||
|
shell.publish(self.on_close.clone());
|
||||||
|
}
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl overlay::Overlay<Message, Theme, Renderer> for MenuOverlay<'_, '_> {
|
||||||
|
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
|
||||||
|
let limits = layout::Limits::new(Size::ZERO, bounds);
|
||||||
|
let main = self.main.as_widget_mut().layout(self.main_tree, renderer, &limits);
|
||||||
|
let size = main.size();
|
||||||
|
// At the cursor, flipped to the other side at the window edges.
|
||||||
|
let mut p = self.position;
|
||||||
|
if p.x + size.width > bounds.width {
|
||||||
|
p.x = (p.x - size.width).max(0.0);
|
||||||
|
}
|
||||||
|
if p.y + size.height > bounds.height {
|
||||||
|
p.y = (p.y - size.height).max(0.0);
|
||||||
|
}
|
||||||
|
let main = main.move_to(p);
|
||||||
|
let mut nodes = Vec::with_capacity(2);
|
||||||
|
if let Some(((index, flyout), tree)) = self.flyout.as_mut() {
|
||||||
|
let node = flyout.as_widget_mut().layout(tree, renderer, &limits);
|
||||||
|
let fly = node.size();
|
||||||
|
// Its first item level with the entry (both boxes have the same inset).
|
||||||
|
let entry_y = main.children().first().and_then(|column| column.children().get(*index)).map_or(0.0, |e| e.bounds().y);
|
||||||
|
let y = (p.y + entry_y).min(bounds.height - fly.height).max(0.0);
|
||||||
|
// On the right when it fits, otherwise on the left.
|
||||||
|
let right = p.x + size.width + FLYOUT_GAP;
|
||||||
|
let x = if right + fly.width <= bounds.width { right } else { (p.x - fly.width - FLYOUT_GAP).max(0.0) };
|
||||||
|
nodes.push(main);
|
||||||
|
nodes.push(node.move_to(Point::new(x, y)));
|
||||||
|
} else {
|
||||||
|
nodes.push(main);
|
||||||
|
}
|
||||||
|
layout::Node::with_children(bounds, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor) {
|
||||||
|
let mut children = layout.children();
|
||||||
|
if let Some(main) = children.next() {
|
||||||
|
self.main.as_widget().draw(self.main_tree, renderer, theme, style, main, cursor, &layout.bounds());
|
||||||
|
}
|
||||||
|
if let (Some(((_, flyout), tree)), Some(fly)) = (self.flyout.as_ref(), children.next()) {
|
||||||
|
flyout.as_widget().draw(tree, renderer, theme, style, fly, cursor, &layout.bounds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
event: &Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &Renderer,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) {
|
||||||
|
let mut children = layout.children();
|
||||||
|
let Some(main) = children.next() else { return };
|
||||||
|
let fly = children.next();
|
||||||
|
let over = cursor.is_over(main.bounds()) || fly.is_some_and(|f| cursor.is_over(f.bounds()));
|
||||||
|
match event {
|
||||||
|
Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Escape), .. }) => {
|
||||||
|
self.close(shell);
|
||||||
|
shell.capture_event();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonPressed(_)) if !over => {
|
||||||
|
self.close(shell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let viewport = layout.bounds();
|
||||||
|
self.main.as_widget_mut().update(self.main_tree, event, main, cursor, renderer, shell, &viewport);
|
||||||
|
if let (Some(((_, flyout), tree)), Some(fly)) = (self.flyout.as_mut(), fly) {
|
||||||
|
flyout.as_widget_mut().update(tree, event, fly, cursor, renderer, shell, &viewport);
|
||||||
|
}
|
||||||
|
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) = event {
|
||||||
|
// A chosen entry closes the menu; a submenu entry opens its fly-out instead.
|
||||||
|
let on_submenu = entries(main)
|
||||||
|
.zip(self.keep_open.iter())
|
||||||
|
.any(|(e, keep)| *keep && cursor.is_over(e.bounds()));
|
||||||
|
if over && !on_submenu {
|
||||||
|
self.close(shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if over {
|
||||||
|
shell.capture_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(&self, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer) -> mouse::Interaction {
|
||||||
|
let mut children = layout.children();
|
||||||
|
let main = children.next().map(|m| {
|
||||||
|
self.main.as_widget().mouse_interaction(self.main_tree, m, cursor, &m.bounds(), renderer)
|
||||||
|
});
|
||||||
|
let fly = match (self.flyout.as_ref(), children.next()) {
|
||||||
|
(Some(((_, f), tree)), Some(l)) => Some(f.as_widget().mouse_interaction(tree, l, cursor, &l.bounds(), renderer)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
fly.filter(|i| *i != mouse::Interaction::default()).or(main).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-44
@@ -612,7 +612,7 @@ fn plain_row<'a>(
|
|||||||
fold: Option<(bool, Message)>,
|
fold: Option<(bool, Message)>,
|
||||||
double: Option<Message>,
|
double: Option<Message>,
|
||||||
menu: Vec<Mi>,
|
menu: Vec<Mi>,
|
||||||
open_sub: Option<(String, bool)>,
|
open_sub: Option<String>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let arrow: Element<'a, Message> = match fold {
|
let arrow: Element<'a, Message> = match fold {
|
||||||
Some((open, m)) => button(if open { crate::ui::icons::themed_arrow_down(10.0) } else { crate::ui::icons::themed_arrow_right(10.0) })
|
Some((open, m)) => button(if open { crate::ui::icons::themed_arrow_down(10.0) } else { crate::ui::icons::themed_arrow_right(10.0) })
|
||||||
@@ -637,13 +637,10 @@ fn plain_row<'a>(
|
|||||||
if menu.is_empty() {
|
if menu.is_empty() {
|
||||||
return area.into();
|
return area.into();
|
||||||
}
|
}
|
||||||
iced_aw::ContextMenu::new(area, move || menu_panel(&menu, open_sub.as_ref())).into()
|
let (main, keep_open, flyout) = menu_panel(&menu, open_sub.as_deref());
|
||||||
|
crate::ui::window::row_menu::RowMenu::new(area, main, keep_open, flyout, msg(SheetSetMsg::Submenu(None))).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Height of a menu entry and of a separator (with the column spacing).
|
|
||||||
const MENU_ENTRY_H: f32 = 27.0;
|
|
||||||
const MENU_SEP_H: f32 = 2.0;
|
|
||||||
|
|
||||||
fn menu_box<'b>(items: Vec<Element<'b, Message>>) -> Element<'b, Message> {
|
fn menu_box<'b>(items: Vec<Element<'b, Message>>) -> Element<'b, Message> {
|
||||||
container(iced::widget::Column::with_children(items).spacing(1))
|
container(iced::widget::Column::with_children(items).spacing(1))
|
||||||
.padding(4)
|
.padding(4)
|
||||||
@@ -656,48 +653,32 @@ fn menu_box<'b>(items: Vec<Element<'b, Message>>) -> Element<'b, Message> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A row menu; hovering a submenu entry opens its items beside it (`open`:
|
/// A row menu: the menu box, which of its entries open a fly-out, and the
|
||||||
/// key and whether the fly-out goes on the left).
|
/// open fly-out (its entry index and items). Hovering, pressing or right
|
||||||
fn menu_panel(menu: &[Mi], open: Option<&(String, bool)>) -> Element<'static, Message> {
|
/// pressing a submenu entry opens its fly-out.
|
||||||
|
fn menu_panel(menu: &[Mi], open: Option<&str>) -> (Element<'static, Message>, Vec<bool>, Option<(usize, Element<'static, Message>)>) {
|
||||||
let mut items: Vec<Element<'static, Message>> = Vec::new();
|
let mut items: Vec<Element<'static, Message>> = Vec::new();
|
||||||
let mut flyout: Option<(f32, Element<'static, Message>)> = None;
|
let mut keep_open = Vec::new();
|
||||||
let mut y = 0.0;
|
let mut flyout = None;
|
||||||
for item in menu {
|
for item in menu {
|
||||||
match item {
|
match item {
|
||||||
Mi::Sep => {
|
Mi::Sep => items.push(menu_separator()),
|
||||||
items.push(menu_separator());
|
|
||||||
y += MENU_SEP_H;
|
|
||||||
}
|
|
||||||
Mi::Entry(label, m) => {
|
Mi::Entry(label, m) => {
|
||||||
items.push(mouse_area(menu_entry(label.clone(), m.clone())).on_enter(msg(SheetSetMsg::Submenu(None))).into());
|
items.push(mouse_area(menu_entry(label.clone(), m.clone())).on_enter(msg(SheetSetMsg::Submenu(None))).into());
|
||||||
y += MENU_ENTRY_H;
|
|
||||||
}
|
}
|
||||||
Mi::Sub(label, key, sub) => {
|
Mi::Sub(label, key, sub) => {
|
||||||
let open_here = open.is_some_and(|(k, _)| k == key);
|
|
||||||
// Hover (or a right press) opens the fly-out; a click closes the menu.
|
|
||||||
let entry = menu_entry(format!("{label} ▸"), Some(msg(SheetSetMsg::Submenu(None))));
|
|
||||||
let open_msg = msg(SheetSetMsg::Submenu(Some(key.clone())));
|
let open_msg = msg(SheetSetMsg::Submenu(Some(key.clone())));
|
||||||
items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into());
|
let entry = menu_entry(format!("{label} ▸"), Some(open_msg.clone()));
|
||||||
if open_here {
|
if open == Some(key.as_str()) {
|
||||||
let subs = sub.iter().map(|(l, m)| menu_entry(l.clone(), m.clone())).collect();
|
let subs = sub.iter().map(|(l, m)| menu_entry(l.clone(), m.clone())).collect();
|
||||||
flyout = Some((y, menu_box(subs)));
|
flyout = Some((items.len(), menu_box(subs)));
|
||||||
}
|
}
|
||||||
y += MENU_ENTRY_H;
|
items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
keep_open.push(matches!(item, Mi::Sub(..)));
|
||||||
}
|
}
|
||||||
let main = menu_box(items);
|
(menu_box(items), keep_open, flyout)
|
||||||
match flyout {
|
|
||||||
Some((offset, sub)) => {
|
|
||||||
let sub = column![Space::new().height(Length::Fixed(offset)), sub];
|
|
||||||
// A menu opened from a right-docked palette sits left of the cursor
|
|
||||||
// (the overlay flips it at the window edge); its fly-out opens to
|
|
||||||
// the left so the menu itself does not move.
|
|
||||||
// ponytail: side from the dock, not the menu's actual position.
|
|
||||||
if open.is_some_and(|(_, left)| *left) { row![sub, main] } else { row![main, sub] }.spacing(2).into()
|
|
||||||
}
|
|
||||||
None => main,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A menu item: translated label and message (an empty label is a separator).
|
/// A menu item: translated label and message (an empty label is a separator).
|
||||||
@@ -782,7 +763,7 @@ fn push_tree<'a>(
|
|||||||
/// A sheet view row and its menu: Display, Rename & Renumber, Set category
|
/// A sheet view row and its menu: Display, Rename & Renumber, Set category
|
||||||
/// (the categories as sub-items), Place Callout Block (the set's callout
|
/// (the categories as sub-items), Place Callout Block (the set's callout
|
||||||
/// blocks as sub-items) and Place View Label Block.
|
/// blocks as sub-items) and Place View Label Block.
|
||||||
fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement, depth: u16, left: bool) -> Element<'a, Message> {
|
fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement, depth: u16) -> Element<'a, Message> {
|
||||||
let id = view.id().to_string();
|
let id = view.id().to_string();
|
||||||
let act = |a: ViewAction| Some(msg(SheetSetMsg::ViewMenu(id.clone(), a)));
|
let act = |a: ViewAction| Some(msg(SheetSetMsg::ViewMenu(id.clone(), a)));
|
||||||
let categories = db.view_categories().iter().map(|c| (c.prop("Name").unwrap_or("").to_string(), act(ViewAction::Category(c.id().to_string())))).collect();
|
let categories = db.view_categories().iter().map(|c| (c.prop("Name").unwrap_or("").to_string(), act(ViewAction::Category(c.id().to_string())))).collect();
|
||||||
@@ -795,11 +776,11 @@ fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement
|
|||||||
Mi::Sub(t!("Place Callout Block").into_owned(), format!("{id}|callout"), callouts),
|
Mi::Sub(t!("Place Callout Block").into_owned(), format!("{id}|callout"), callouts),
|
||||||
mi("Place View Label Block", act(ViewAction::Label)),
|
mi("Place View Label Block", act(ViewAction::Label)),
|
||||||
];
|
];
|
||||||
plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu, state.submenu.clone().map(|k| (k, left)))
|
plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu, state.submenu.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Sheet Views page: the set, then its view categories (or sheets) and their views.
|
/// The Sheet Views page: the set, then its view categories (or sheets) and their views.
|
||||||
fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bool) -> Vec<Element<'a, Message>> {
|
fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec<Element<'a, Message>> {
|
||||||
let new_category = || mi("New View Category...", Some(msg(SheetSetMsg::NewCategory)));
|
let new_category = || mi("New View Category...", Some(msg(SheetSetMsg::NewCategory)));
|
||||||
let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()], None)];
|
let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()], None)];
|
||||||
let views: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect();
|
let views: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect();
|
||||||
@@ -807,7 +788,7 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bo
|
|||||||
let named = db.view_categories();
|
let named = db.view_categories();
|
||||||
// Views of the unnamed (default) category sit directly under the set.
|
// Views of the unnamed (default) category sit directly under the set.
|
||||||
for v in views.iter().filter(|v| !SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))) {
|
for v in views.iter().filter(|v| !SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))) {
|
||||||
rows.push(view_row(state, db, v, 1, left));
|
rows.push(view_row(state, db, v, 1));
|
||||||
}
|
}
|
||||||
for c in named {
|
for c in named {
|
||||||
let id = c.id().to_string();
|
let id = c.id().to_string();
|
||||||
@@ -825,7 +806,7 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bo
|
|||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
for v in views.iter().filter(|v| SheetSetDatabase::view_category_of(v) == Some(id.as_str())) {
|
for v in views.iter().filter(|v| SheetSetDatabase::view_category_of(v) == Some(id.as_str())) {
|
||||||
rows.push(view_row(state, db, v, 2, left));
|
rows.push(view_row(state, db, v, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -836,7 +817,7 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bo
|
|||||||
}
|
}
|
||||||
rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, None, vec![], None));
|
rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, None, vec![], None));
|
||||||
for v in views {
|
for v in views {
|
||||||
rows.push(view_row(state, db, v, 2, left));
|
rows.push(view_row(state, db, v, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -919,8 +900,7 @@ fn heading(label: &str) -> Element<'static, Message> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `dock_right`: the palette is docked on the right (fly-outs open leftwards).
|
pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> Element<'a, Message> {
|
||||||
pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool, dock_right: bool) -> Element<'a, Message> {
|
|
||||||
let title_bar =
|
let title_bar =
|
||||||
crate::ui::dock::title_bar(PanelId::SheetSetManager, t!("Sheet Set Manager").into_owned(), auto_collapse);
|
crate::ui::dock::title_bar(PanelId::SheetSetManager, t!("Sheet Set Manager").into_owned(), auto_collapse);
|
||||||
let mut picks: Vec<Pick> =
|
let mut picks: Vec<Pick> =
|
||||||
@@ -986,7 +966,7 @@ pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool, doc
|
|||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db, dock_right)).spacing(1)).height(Fill)]
|
column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db)).spacing(1)).height(Fill)]
|
||||||
.spacing(6)
|
.spacing(6)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user