forked from 1b-agent/opencadstudio-upstream
feat(properties): refine quick properties
- Keep the floating panel near the selection and inside the viewport - Persist the toggle and hide the panel without a selection - Show only Material in 3D Visualization
This commit is contained in:
1 parent
5ad95d1d2e
commit
c9550b48f9
9 files changed
+75
-57
No files matched your search
@@ -352,6 +352,8 @@ pub(super) struct OpenCADStudio {
|
||||
clean_screen: bool,
|
||||
/// Quick Properties: show a compact floating property panel on selection.
|
||||
quick_properties: bool,
|
||||
/// Canvas-space cursor position where Quick Properties was last opened.
|
||||
quick_properties_anchor: Point,
|
||||
/// Selection cycling: clicking where objects overlap opens a list box
|
||||
/// to pick which one; the pick is added to the current selection.
|
||||
selection_cycling: bool,
|
||||
@@ -2869,6 +2871,7 @@ impl OpenCADStudio {
|
||||
otrack_active: None,
|
||||
clean_screen: false,
|
||||
quick_properties: false,
|
||||
quick_properties_anchor: Point::new(12.0, 12.0),
|
||||
selection_cycling: false,
|
||||
pick_add: true,
|
||||
pick_drag_rect: false,
|
||||
|
||||
@@ -147,6 +147,8 @@ pub struct UserSettings {
|
||||
/// PICKDRAG (#226): `false` (default) = press-drag draws the freeform
|
||||
/// lasso; `true` = press-drag draws a rectangle marquee instead.
|
||||
pub pick_drag_rect: bool,
|
||||
/// Show the floating Quick Properties panel when objects are selected.
|
||||
pub quick_properties: bool,
|
||||
/// Persisted viewport background colours (0–255 RGB); `None` = app default
|
||||
/// (dark grey model / off-white paper). Applied to every drawing tab on
|
||||
/// launch and to tabs opened later, so a chosen background survives restarts
|
||||
@@ -181,6 +183,7 @@ impl Default for UserSettings {
|
||||
default_save_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||
pick_add: true,
|
||||
pick_drag_rect: false,
|
||||
quick_properties: false,
|
||||
bg_color: None,
|
||||
paper_bg_color: None,
|
||||
language: crate::i18n::Language::default(),
|
||||
|
||||
@@ -287,6 +287,7 @@ impl OpenCADStudio {
|
||||
default_save_format: self.default_save_format.clone(),
|
||||
pick_add: self.pick_add,
|
||||
pick_drag_rect: self.pick_drag_rect,
|
||||
quick_properties: self.quick_properties,
|
||||
bg_color: self.default_bg_color.map(f4_to_u3),
|
||||
paper_bg_color: self.default_paper_bg_color.map(f4_to_u3),
|
||||
language: self.language,
|
||||
@@ -325,6 +326,7 @@ impl OpenCADStudio {
|
||||
crate::io::canonical_save_format(&s.default_save_format).to_string();
|
||||
self.pick_add = s.pick_add;
|
||||
self.pick_drag_rect = s.pick_drag_rect;
|
||||
self.quick_properties = s.quick_properties;
|
||||
self.default_bg_color = s.bg_color.map(u3_to_f4);
|
||||
self.default_paper_bg_color = s.paper_bg_color.map(u3_to_f4);
|
||||
if crate::i18n::set_language(s.language).is_ok() {
|
||||
|
||||
@@ -3216,6 +3216,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
Message::ToggleQuickProperties => {
|
||||
self.quick_properties ^= true;
|
||||
if self.quick_properties {
|
||||
self.quick_properties_anchor =
|
||||
self.tabs[self.active_tab].last_cursor_screen;
|
||||
}
|
||||
self.save_config();
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleSelectionCycling => {
|
||||
@@ -3226,12 +3231,19 @@ impl OpenCADStudio {
|
||||
}
|
||||
Message::CycleSelect(handle) => {
|
||||
// Add the picked object to the current selection (accumulate).
|
||||
let quick_properties_anchor = self
|
||||
.cycle_candidates
|
||||
.as_ref()
|
||||
.map(|(point, _)| *point);
|
||||
self.cycle_candidates = None;
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.set_hover_highlight(None);
|
||||
self.tabs[i].scene.select_entity(handle, false);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&[handle]);
|
||||
self.refresh_properties();
|
||||
if let Some(point) = quick_properties_anchor {
|
||||
self.quick_properties_anchor = point;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::CycleHover(handle) => {
|
||||
|
||||
@@ -3530,6 +3530,10 @@ impl OpenCADStudio {
|
||||
sel.left_dragging = false;
|
||||
}
|
||||
|
||||
if selection_just_completed {
|
||||
self.quick_properties_anchor = p_full;
|
||||
}
|
||||
|
||||
if is_gathering && selection_just_completed {
|
||||
let handles: Vec<Handle> = self.tabs[i]
|
||||
.scene
|
||||
|
||||
+19
-16
@@ -23,8 +23,8 @@ mod viewcube;
|
||||
|
||||
use controls::{dyn_component_value, viewport_controls};
|
||||
use overlay::{
|
||||
mtext_editor_overlay, position_canvas_overlay, qselect_overlay, text_inline_overlay,
|
||||
viewport_context_menu_overlay,
|
||||
mtext_editor_overlay, position_canvas_overlay, position_canvas_overlay_near_cursor,
|
||||
qselect_overlay, text_inline_overlay, viewport_context_menu_overlay,
|
||||
};
|
||||
use viewcube::{viewcube_nav_controls, viewcube_ucs_picker, UCS_PICKER_W};
|
||||
|
||||
@@ -1208,12 +1208,25 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// Quick Properties: compact floating property panel on selection,
|
||||
// anchored at the canvas top-left so it doesn't track the cursor.
|
||||
// Reserve the overlaid command line when placing cursor-anchored panels.
|
||||
let command_line_inset = if self.command_line.history_open {
|
||||
self.command_line.history_height.clamp(
|
||||
crate::ui::command_line::HISTORY_HEIGHT_MIN,
|
||||
crate::ui::command_line::history_max_height(self.win_size.1),
|
||||
) + 72.0
|
||||
} else {
|
||||
34.0
|
||||
};
|
||||
|
||||
// Quick Properties: stay near the selection cursor, flipping around
|
||||
// it as needed to remain inside the visible drawing area.
|
||||
if self.quick_properties && !tab.is_start {
|
||||
if let Some(panel) = tab.properties.quick_view() {
|
||||
viewport_stack = viewport_stack
|
||||
.push(position_canvas_overlay(iced::Point::new(12.0, 12.0), panel));
|
||||
viewport_stack = viewport_stack.push(position_canvas_overlay_near_cursor(
|
||||
self.quick_properties_anchor,
|
||||
command_line_inset,
|
||||
panel,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1353,16 +1366,6 @@ impl OpenCADStudio {
|
||||
.take(3)
|
||||
.cloned()
|
||||
.collect();
|
||||
// The command line overlays the bottom of the drawing stack.
|
||||
// Keep context menus above its interactive input/history area.
|
||||
let command_line_inset = if self.command_line.history_open {
|
||||
self.command_line.history_height.clamp(
|
||||
crate::ui::command_line::HISTORY_HEIGHT_MIN,
|
||||
crate::ui::command_line::history_max_height(self.win_size.1),
|
||||
) + 72.0
|
||||
} else {
|
||||
34.0
|
||||
};
|
||||
viewport_stack = viewport_stack.push(viewport_context_menu_overlay(
|
||||
p,
|
||||
command_line_inset,
|
||||
|
||||
+21
-4
@@ -752,6 +752,22 @@ fn position_canvas_overlay_clamped<'a>(
|
||||
content: iced::widget::opaque(panel),
|
||||
anchor,
|
||||
bottom_inset: bottom_inset.max(0.0),
|
||||
gap: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Keep a floating panel close to the cursor without letting it cover the
|
||||
/// pointer or leave the drawing's safe rectangle.
|
||||
pub(super) fn position_canvas_overlay_near_cursor<'a>(
|
||||
cursor: iced::Point,
|
||||
bottom_inset: f32,
|
||||
panel: Element<'a, Message>,
|
||||
) -> Element<'a, Message> {
|
||||
Element::new(ClampedPin {
|
||||
content: iced::widget::opaque(panel),
|
||||
anchor: cursor,
|
||||
bottom_inset: bottom_inset.max(0.0),
|
||||
gap: 12.0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -759,6 +775,7 @@ struct ClampedPin<'a> {
|
||||
content: Element<'a, Message>,
|
||||
anchor: iced::Point,
|
||||
bottom_inset: f32,
|
||||
gap: f32,
|
||||
}
|
||||
|
||||
impl Widget<Message, Theme, iced::Renderer> for ClampedPin<'_> {
|
||||
@@ -799,8 +816,8 @@ impl Widget<Message, Theme, iced::Renderer> for ClampedPin<'_> {
|
||||
let content = node.size();
|
||||
|
||||
let max_x = (max.width - MARGIN - content.width).max(MARGIN);
|
||||
let right = self.anchor.x;
|
||||
let left = self.anchor.x - content.width;
|
||||
let right = self.anchor.x + self.gap;
|
||||
let left = self.anchor.x - self.gap - content.width;
|
||||
let x = if right <= max_x {
|
||||
right.max(MARGIN)
|
||||
} else if left >= MARGIN {
|
||||
@@ -811,8 +828,8 @@ impl Widget<Message, Theme, iced::Renderer> for ClampedPin<'_> {
|
||||
|
||||
let max_y =
|
||||
(max.height - self.bottom_inset - MARGIN - content.height).max(MARGIN);
|
||||
let below = self.anchor.y;
|
||||
let above = self.anchor.y - content.height;
|
||||
let below = self.anchor.y + self.gap;
|
||||
let above = self.anchor.y - self.gap - content.height;
|
||||
let y = if below <= max_y {
|
||||
below.max(MARGIN)
|
||||
} else if above >= MARGIN {
|
||||
|
||||
Vendored
+5
-33
@@ -125,41 +125,13 @@ pub fn visualization_section(entity: &EntityType) -> Option<PropSection> {
|
||||
1 => "ByBlock",
|
||||
_ => "Custom",
|
||||
};
|
||||
let mut props = vec![Property {
|
||||
label: t!("Material").into_owned(),
|
||||
field: "material",
|
||||
value: PropValue::ReadOnly(material.into()),
|
||||
}];
|
||||
let handle_text = |handle: Option<acadrust::Handle>| {
|
||||
handle
|
||||
.filter(|handle| handle.is_valid())
|
||||
.map_or_else(|| "None".to_string(), |handle| format!("{:X}", handle.value()))
|
||||
};
|
||||
props.extend([
|
||||
Property {
|
||||
label: t!("Visual style").into_owned(),
|
||||
field: "visual_style",
|
||||
value: PropValue::ReadOnly(handle_text(common.full_visual_style_handle)),
|
||||
},
|
||||
Property {
|
||||
label: t!("Face style").into_owned(),
|
||||
field: "face_visual_style",
|
||||
value: PropValue::ReadOnly(handle_text(common.face_visual_style_handle)),
|
||||
},
|
||||
Property {
|
||||
label: t!("Edge style").into_owned(),
|
||||
field: "edge_visual_style",
|
||||
value: PropValue::ReadOnly(handle_text(common.edge_visual_style_handle)),
|
||||
},
|
||||
Property {
|
||||
label: t!("Shadow flags").into_owned(),
|
||||
field: "shadow_flags",
|
||||
value: PropValue::ReadOnly(common.shadow_flags.to_string()),
|
||||
},
|
||||
]);
|
||||
Some(PropSection {
|
||||
title: t!("3D Visualization").into_owned(),
|
||||
props,
|
||||
props: vec![Property {
|
||||
label: t!("Material").into_owned(),
|
||||
field: "material",
|
||||
value: PropValue::ReadOnly(material.into()),
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ impl PropertiesPanel {
|
||||
/// editable section rows as the docked panel, sized to its content.
|
||||
/// Returns `None` when nothing is selected.
|
||||
pub fn quick_view(&self) -> Option<Element<'_, Message>> {
|
||||
if self.sections.is_empty() {
|
||||
if self.source_handles.is_empty() || self.sections.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let title = container(
|
||||
@@ -527,13 +527,15 @@ impl PropertiesPanel {
|
||||
.width(Length::Fill)
|
||||
.padding([4, 10]);
|
||||
|
||||
let mut col = column![title].spacing(0);
|
||||
let mut sections = column![].spacing(0);
|
||||
for section in &self.sections {
|
||||
col = col.push(self.render_section(section));
|
||||
sections = sections.push(self.render_section(section));
|
||||
}
|
||||
|
||||
let content = scrollable(sections).height(Length::Shrink);
|
||||
|
||||
Some(
|
||||
container(col)
|
||||
container(column![title, content].spacing(0))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
|
||||
Reference in new issue
Block a user