forked from 1b-agent/opencadstudio-upstream
feat(block-editor): add functional Visibility ribbon controls
Add the current-state selector, Visibility States dialog and mode/show/hide controls to the contextual ribbon, using shared icons, tooltips, modal UI and adaptive flyouts. Disable the controls until a Visibility parameter exists. Stage state creation, rename, deletion, current-state changes and reordering until OK. Support copied, show-all and hide-all new states in one undo step, preserve native graph metadata, and restore current-state identity on undo/redo. Implement nonsaved BVMODE and display-only reveal during BVSHOW selection. Cancelling selection leaves saved visibility flags and history unchanged. Keep the existing command-line state workflows available. Validation: 6 targeted tests; 277 block, 31 app dock, 35 ribbon and 12 history checks passed (overlapping filters). All 17 local DWG fixtures passed in-memory DWG/DXF preservation checks with unchanged original checksums.
This commit is contained in:
1 parent
308990e672
commit
daa26007f9
24 files changed
+1341
-34
No files matched your search
+11
-1
@@ -26,6 +26,7 @@ pub(crate) use draw_panel::tools as panel_tools;
|
||||
mod color_dropdown;
|
||||
mod context_tools;
|
||||
mod modify_panel;
|
||||
mod visibility_panel;
|
||||
pub use context_tools::{pdf_underlay_tools, point_cloud_tools, xref_tools, UnderlayContext};
|
||||
use widgets::{StyleContext, *};
|
||||
pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID};
|
||||
@@ -630,6 +631,7 @@ impl Ribbon {
|
||||
undo_count: usize,
|
||||
redo_count: usize,
|
||||
show_block_palette: bool,
|
||||
visibility: Option<crate::ui::window::block_visibility::RibbonContext>,
|
||||
) -> Element<'_, Message> {
|
||||
// ── Quick-access file commands + undo/redo, one merged flow ────────
|
||||
let lead = iced::widget::Row::with_children(vec![
|
||||
@@ -825,7 +827,15 @@ impl Ribbon {
|
||||
// button. See `CollapsePanels`.
|
||||
let ts = self.toggle_state(show_block_palette);
|
||||
let panels: Vec<Panel<'_>> = groups
|
||||
.map(|g| self.panel(g, ts, &style_ctx, &|_| Vec::new()))
|
||||
.map(|g| {
|
||||
if self.modules[effective_active].id() == "block_editor"
|
||||
&& g.title == "Visibility"
|
||||
{
|
||||
visibility_panel::panel(visibility.clone())
|
||||
} else {
|
||||
self.panel(g, ts, &style_ctx, &|_| Vec::new())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
CollapsePanels::new(panels, self.collapsed_open.clone(), TOOL_BAR_H)
|
||||
.report_height(self.tool_bar_h.clone())
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
//! The contextual visibility controls share the ribbon's buttons, tips and collapse layout.
|
||||
use iced::widget::{button, column, container, pick_list, row, text, tooltip};
|
||||
use iced::{Element, Fill, Theme};
|
||||
|
||||
use super::collapse::Panel;
|
||||
use super::widgets::{make_icon, tool_btn_style, LARGE_ICON, LARGE_W, SMALL_ICON, TOOL_BAR_H};
|
||||
use crate::app::Message;
|
||||
use crate::modules::IconKind;
|
||||
use crate::ui::window::block_visibility::{Action, RibbonContext};
|
||||
use crate::ui::ROW_H;
|
||||
|
||||
fn tool(
|
||||
id: &'static str,
|
||||
label: &'static str,
|
||||
icon: &'static [u8],
|
||||
large: bool,
|
||||
active: bool,
|
||||
message: Option<Message>,
|
||||
) -> Element<'static, Message> {
|
||||
let enabled = message.is_some();
|
||||
let size = if large { LARGE_ICON } else { SMALL_ICON };
|
||||
let image = if enabled {
|
||||
crate::ui::icons::semantic(icon, size)
|
||||
} else {
|
||||
crate::ui::icons::semantic_disabled(icon, size)
|
||||
};
|
||||
let face: Element<'static, Message> = if large {
|
||||
column![
|
||||
image,
|
||||
text(crate::t!(label).into_owned())
|
||||
.size(10)
|
||||
.align_x(iced::Center)
|
||||
]
|
||||
.align_x(iced::Center)
|
||||
.spacing(3)
|
||||
.into()
|
||||
} else {
|
||||
image
|
||||
};
|
||||
let mut control = button(face)
|
||||
.on_press_maybe(message)
|
||||
.style(move |theme: &Theme, status| {
|
||||
let mut style = tool_btn_style(theme, active, status);
|
||||
if !enabled {
|
||||
style.text_color = style.text_color.scale_alpha(0.42);
|
||||
style.background = None;
|
||||
}
|
||||
style
|
||||
})
|
||||
.padding([3, 5]);
|
||||
control = if large {
|
||||
control.width(LARGE_W + 8.0).height(3.0 * ROW_H)
|
||||
} else {
|
||||
control.width(ROW_H + 4.0).height(ROW_H)
|
||||
};
|
||||
tooltip(
|
||||
super::keytips::anchor(id, container(control).id(id)),
|
||||
super::tooltip_content(crate::t!(label).into_owned()),
|
||||
iced::widget::tooltip::Position::Right,
|
||||
)
|
||||
.gap(6)
|
||||
.delay(std::time::Duration::from_millis(400))
|
||||
.style(super::tooltip_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn view(context: Option<RibbonContext>, compact: bool) -> Element<'static, Message> {
|
||||
let target = context.as_ref().map(|context| context.target);
|
||||
let mode = context.as_ref().is_some_and(|context| context.mode);
|
||||
let manager = tool(
|
||||
"BVSTATE_MANAGER",
|
||||
"Visibility States",
|
||||
include_bytes!("../../../assets/icons/ui/list.svg"),
|
||||
!compact,
|
||||
false,
|
||||
target.map(|target| Message::BlockVisibility(Action::Open(target))),
|
||||
);
|
||||
let toggles = row![
|
||||
tool(
|
||||
"BVMODE_TOGGLE",
|
||||
"Visibility Mode",
|
||||
include_bytes!("../../../assets/icons/constrain/show_all.svg"),
|
||||
false,
|
||||
mode,
|
||||
target.map(|target| Message::BlockVisibility(Action::Mode(target)))
|
||||
),
|
||||
tool(
|
||||
"BVSHOW",
|
||||
"Make Visible",
|
||||
include_bytes!("../../../assets/icons/constrain/show.svg"),
|
||||
false,
|
||||
false,
|
||||
target.map(|_| Message::Command("BVSHOW".into()))
|
||||
),
|
||||
tool(
|
||||
"BVHIDE",
|
||||
"Make Invisible",
|
||||
include_bytes!("../../../assets/icons/constrain/hide_all.svg"),
|
||||
false,
|
||||
false,
|
||||
target.map(|_| Message::Command("BVHIDE".into()))
|
||||
),
|
||||
]
|
||||
.spacing(2);
|
||||
let names = context
|
||||
.as_ref()
|
||||
.map(|context| context.names.clone())
|
||||
.unwrap_or_default();
|
||||
let current = context.as_ref().and_then(|context| context.current.clone());
|
||||
let mut selector = pick_list(current, names, |name: &String| name.clone())
|
||||
.placeholder(crate::t!("Visibility state").into_owned())
|
||||
.text_size(11)
|
||||
.padding([3, 6])
|
||||
.width(Fill);
|
||||
if let Some(target) = target {
|
||||
selector =
|
||||
selector.on_select(move |name| Message::BlockVisibility(Action::Current(target, name)));
|
||||
}
|
||||
let selector = container(selector).id("BVSTATE_CURRENT").width(Fill);
|
||||
let controls: Element<'static, Message> = if compact {
|
||||
column![row![manager, toggles].spacing(3), selector]
|
||||
.spacing(4)
|
||||
.width(160)
|
||||
.height(Fill)
|
||||
.into()
|
||||
} else {
|
||||
row![manager, column![toggles, selector].spacing(6).width(170)]
|
||||
.spacing(4)
|
||||
.height(Fill)
|
||||
.into()
|
||||
};
|
||||
column![controls, text(crate::t!("Visibility").into_owned()).size(9)]
|
||||
.align_x(iced::Center)
|
||||
.padding([3, 4])
|
||||
.height(TOOL_BAR_H)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn collapsed(tight: bool) -> Element<'static, Message> {
|
||||
let icon = make_icon(
|
||||
IconKind::Svg(include_bytes!("../../../assets/icons/ui/list.svg")),
|
||||
if tight { SMALL_ICON } else { LARGE_ICON },
|
||||
);
|
||||
button(
|
||||
column![icon, text(crate::t!("Visibility ▾").into_owned()).size(10)]
|
||||
.align_x(iced::Center)
|
||||
.spacing(4),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonPanel("Visibility".into()))
|
||||
.style(|theme, status| tool_btn_style(theme, false, status))
|
||||
.padding([4, 6])
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(super) fn panel(context: Option<RibbonContext>) -> Panel<'static> {
|
||||
Panel {
|
||||
id: "Visibility".into(),
|
||||
elements: [
|
||||
view(context.clone(), false),
|
||||
view(context, true),
|
||||
collapsed(false),
|
||||
collapsed(true),
|
||||
],
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user