forked from 1b-agent/opencadstudio-upstream
feat(block-editor): wire Visibility parameter placement
Milestone 1: enable Parameters > Visibility and add interactive BPARAMETER VISIBILITY placement using the existing native visibility parameter, grip and state engine. Bare BPARAMETER currently offers only Visibility; other parameter, action and set entries remain unavailable. Show the placed parameter in the editor and support cancel, local undo/ redo, save/discard and saved-edit undo. Bind placement to its document and definition, reject duplicates and invalid points, and protect existing authoring graphs from replacement. Keep visibility parameters and their grips aligned with geometry when a BEDIT UCS change reframes the definition, including parameter-only blocks. Validation: 9 targeted tests, 272 block checks and 58 dock checks passed (overlapping groups). All 17 local DWG fixtures passed in-memory DWG/DXF placement roundtrips and graph-preservation checks; originals unchanged.
This commit is contained in:
1 parent
c8750db7c9
commit
308990e672
14 files changed
+778
-7
No files matched your search
@@ -977,6 +977,7 @@ pub fn selection_overlay<'a>(
|
||||
constraint_glyph_selected: Arc<[bool]>,
|
||||
constraint_glyph_tooltip: Option<String>,
|
||||
constraint_cursor_badge: Option<String>,
|
||||
authoring_visibility: Option<(Point, String, iced::Rectangle)>,
|
||||
) -> Element<'a, Message> {
|
||||
canvas(SelectionCanvas {
|
||||
selection,
|
||||
@@ -1005,6 +1006,7 @@ pub fn selection_overlay<'a>(
|
||||
constraint_glyph_selected,
|
||||
constraint_glyph_tooltip,
|
||||
constraint_cursor_badge,
|
||||
authoring_visibility,
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
@@ -1081,6 +1083,7 @@ struct SelectionCanvas {
|
||||
/// Symbol shown beside the cursor while it targets an entity that already
|
||||
/// participates in an enabled geometric constraint.
|
||||
constraint_cursor_badge: Option<String>,
|
||||
authoring_visibility: Option<(Point, String, iced::Rectangle)>,
|
||||
}
|
||||
|
||||
/// The move gizmo: an arrow per axis (X red, Y green, Z blue) from the
|
||||
@@ -2303,6 +2306,25 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
frame.stroke(&b1, stroke.clone());
|
||||
frame.stroke(&b2, stroke);
|
||||
}
|
||||
if let Some((point, label, clip)) = &self.authoring_visibility {
|
||||
frame.with_clip(*clip, |frame| {
|
||||
let color = theme.palette().primary.base.color;
|
||||
let triangle = canvas::Path::new(|builder| {
|
||||
builder.move_to(Point::new(point.x - 5.0, point.y - 3.0));
|
||||
builder.line_to(Point::new(point.x + 5.0, point.y - 3.0));
|
||||
builder.line_to(Point::new(point.x, point.y + 5.0));
|
||||
builder.close();
|
||||
});
|
||||
frame.fill(&triangle, color);
|
||||
frame.fill_text(canvas::Text {
|
||||
content: label.clone(),
|
||||
position: Point::new(point.x + 10.0, point.y - 9.0),
|
||||
color,
|
||||
size: iced::Pixels(13.0),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
}
|
||||
// Draw + hover + tooltip all read the memoised entries' precomputed
|
||||
// `top_left`/`size` (one shared `hovered` lookup below) — no offset
|
||||
// recompute, no label clones on this path.
|
||||
|
||||
@@ -188,7 +188,7 @@ const PARAMETER_SETS: &[(&str, &[u8])] = &[
|
||||
];
|
||||
|
||||
/// Catalog rows remain inert until their authoring commands exist.
|
||||
fn catalog(tab: Tab) -> Element<'static, Message> {
|
||||
fn catalog(tab: Tab, visibility_error: Option<&'static str>) -> Element<'static, Message> {
|
||||
let (entries, unavailable) = match tab {
|
||||
Tab::Parameters => (
|
||||
PARAMETERS,
|
||||
@@ -202,7 +202,13 @@ fn catalog(tab: Tab) -> Element<'static, Message> {
|
||||
Tab::Constraints => return constraints(),
|
||||
};
|
||||
column(entries.iter().map(|(label, icon)| {
|
||||
let base = icons::semantic_disabled(icon, 28.0);
|
||||
let visibility = tab == Tab::Parameters && *label == "Visibility";
|
||||
let enabled = visibility && visibility_error.is_none();
|
||||
let base = if enabled {
|
||||
icons::semantic(icon, 28.0)
|
||||
} else {
|
||||
icons::semantic_disabled(icon, 28.0)
|
||||
};
|
||||
let icon = if tab != Tab::Parameters && *label != "Block Properties Table" {
|
||||
stack![
|
||||
base,
|
||||
@@ -219,7 +225,23 @@ fn catalog(tab: Tab) -> Element<'static, Message> {
|
||||
} else {
|
||||
base
|
||||
};
|
||||
tool_row(label, icon, None, &unavailable)
|
||||
let message = enabled.then(|| Message::RibbonToolClick {
|
||||
tool_id: "BPARAMETER_VISIBILITY".into(),
|
||||
event: crate::modules::ModuleEvent::Command("BPARAMETER VISIBILITY".into()),
|
||||
});
|
||||
let description = if visibility {
|
||||
crate::t!(visibility_error
|
||||
.unwrap_or("Place a visibility parameter, then use BVSTATE to manage its states."))
|
||||
.into_owned()
|
||||
} else {
|
||||
unavailable.to_string()
|
||||
};
|
||||
let entry = tool_row(label, icon, message, &description);
|
||||
if visibility {
|
||||
container(entry).id("authoring-tool-visibility").into()
|
||||
} else {
|
||||
entry
|
||||
}
|
||||
}))
|
||||
.spacing(2)
|
||||
.into()
|
||||
@@ -323,7 +345,12 @@ fn constraints() -> Element<'static, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view(selected_tab: Tab, width: f32, chrome: Chrome) -> Element<'static, Message> {
|
||||
pub fn view(
|
||||
selected_tab: Tab,
|
||||
width: f32,
|
||||
chrome: Chrome,
|
||||
visibility_error: Option<&'static str>,
|
||||
) -> Element<'static, Message> {
|
||||
let tabs = column(Tab::ALL.into_iter().map(|category| {
|
||||
let label = category.label();
|
||||
let selected = category == selected_tab;
|
||||
@@ -372,7 +399,9 @@ pub fn view(selected_tab: Tab, width: f32, chrome: Chrome) -> Element<'static, M
|
||||
chrome,
|
||||
),
|
||||
row![
|
||||
scrollable(catalog(selected_tab)).width(Fill).height(Fill),
|
||||
scrollable(catalog(selected_tab, visibility_error))
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
tabs
|
||||
]
|
||||
.spacing(4)
|
||||
|
||||
Reference in new issue
Block a user