forked from 1b-agent/opencadstudio-upstream
Standardize parametric constraints and interoperability
This commit is contained in:
1 parent
753df67ee4
commit
d738ebfcc6
41 files changed
+10038
-7847
No files matched your search
+96
-95
@@ -1,13 +1,11 @@
|
||||
//! Bottom status bar — Model/Layout tabs + OSNAP toggle + status info
|
||||
|
||||
pub mod status_menu;
|
||||
pub mod statusbar_config;
|
||||
pub mod statusbar_menu;
|
||||
pub mod status_menu;
|
||||
|
||||
use iced::widget::tooltip::Position as TipPos;
|
||||
use iced::widget::{
|
||||
button, column, container, mouse_area, row, text, text_input, tooltip,
|
||||
};
|
||||
use iced::widget::{button, column, container, mouse_area, row, text, text_input, tooltip};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
use iced_aw::ContextMenu;
|
||||
use std::sync::Arc;
|
||||
@@ -23,10 +21,10 @@ pub const LAYOUT_RENAME_INPUT_ID: &str = "layout_rename_input";
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::snap::Snapper;
|
||||
use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
||||
use crate::ui::statusbar::status_menu::Entry as StatusMenuEntry;
|
||||
use crate::ui::wrap_bar::WrapBar;
|
||||
use crate::t;
|
||||
use crate::ui::statusbar::status_menu::Entry as StatusMenuEntry;
|
||||
use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
||||
use crate::ui::wrap_bar::WrapBar;
|
||||
|
||||
/// Height of one status-bar row. Matches the drawing-tab strip above it so the
|
||||
/// three horizontal strips — tabs, status bar, command line — line up, and it
|
||||
@@ -125,16 +123,16 @@ impl StatusBar {
|
||||
// Which pills the user has chosen to show on the bar.
|
||||
config: &'a StatusBarConfig,
|
||||
menu_data: StatusMenuData<'a>,
|
||||
// Remaining degrees of freedom for the current sketch scope.
|
||||
// Remaining degrees of freedom for the current parametric scope.
|
||||
// `None` when the scope
|
||||
// has no `SketchConstraintSet` yet (nothing constrained), so the
|
||||
// has no `ParametricConstraintSet` yet (nothing constrained), so the
|
||||
// badge stays invisible until it's actually relevant.
|
||||
sketch_dof: Option<usize>,
|
||||
parametric_dof: Option<usize>,
|
||||
// Number of redundant/conflicting constraints in the current scope.
|
||||
// Zero hides
|
||||
// the pill entirely, so an ordinarily/fully-constrained drawing sees
|
||||
// no new clutter.
|
||||
sketch_conflicts: usize,
|
||||
parametric_conflicts: usize,
|
||||
// What is drawing the scene. Only a degraded verdict shows anything.
|
||||
gpu_status: &'a crate::scene::pipeline::GpuStatus,
|
||||
) -> Element<'a, Message> {
|
||||
@@ -157,9 +155,7 @@ impl StatusBar {
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::MENU, 16.0)
|
||||
};
|
||||
let menu_button = button(menu_icon)
|
||||
.style(button::subtle)
|
||||
.padding([4, 8]);
|
||||
let menu_button = button(menu_icon).style(button::subtle).padding([4, 8]);
|
||||
let menu_btn = if is_start {
|
||||
tip(
|
||||
menu_button.into(),
|
||||
@@ -270,9 +266,9 @@ impl StatusBar {
|
||||
);
|
||||
}
|
||||
// Not a `StatusPill` (so not user-hideable yet): invisible until the
|
||||
// current scope actually has a SketchConstraintSet, so a drawing
|
||||
// current scope actually has a ParametricConstraintSet, so a drawing
|
||||
// that never uses parametric constraints sees no new clutter.
|
||||
if let Some(dof) = sketch_dof {
|
||||
if let Some(dof) = parametric_dof {
|
||||
let pill = if dof == 0 {
|
||||
success_pill(crate::tf!("DOF: {dof}").into_owned())
|
||||
} else {
|
||||
@@ -284,21 +280,21 @@ impl StatusBar {
|
||||
if dof == 0 {
|
||||
t!("Fully constrained — no remaining degrees of freedom")
|
||||
} else {
|
||||
t!("Remaining degrees of freedom in the current sketch's persistent constraints")
|
||||
t!("Remaining degrees of freedom in the current parametric constraint scope")
|
||||
},
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
// Each click removes one flagged constraint.
|
||||
if sketch_conflicts > 0 {
|
||||
if parametric_conflicts > 0 {
|
||||
pills.push(
|
||||
tip(
|
||||
action_pill(
|
||||
crate::tf!("⚠ {sketch_conflicts} conflicting").into_owned(),
|
||||
Message::ResolveOneSketchConflict,
|
||||
crate::tf!("⚠ {parametric_conflicts} conflicting").into_owned(),
|
||||
Message::ResolveOneParametricConflict,
|
||||
),
|
||||
t!("One or more constraints in this sketch conflict or are redundant\nClick to remove one and re-solve"),
|
||||
t!("One or more parametric constraints conflict or are redundant\nClick to remove one and re-solve"),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
@@ -315,7 +311,11 @@ impl StatusBar {
|
||||
if vis(StatusPill::Lwt) {
|
||||
pills.push(
|
||||
tip(
|
||||
toggle_pill(crate::ui::icons::ST_LWT, lineweight_display, Message::ToggleLineweightDisplay),
|
||||
toggle_pill(
|
||||
crate::ui::icons::ST_LWT,
|
||||
lineweight_display,
|
||||
Message::ToggleLineweightDisplay,
|
||||
),
|
||||
t!("Show Lineweight\nLWDISPLAY"),
|
||||
)
|
||||
.into(),
|
||||
@@ -462,10 +462,7 @@ impl StatusBar {
|
||||
t!("Isolate Objects\nClick for Isolate / Hide / End"),
|
||||
tooltip_hidden,
|
||||
),
|
||||
crate::ui::popup::isolate_popup::menu_entries(
|
||||
has_selection,
|
||||
isolation_active,
|
||||
),
|
||||
crate::ui::popup::isolate_popup::menu_entries(has_selection, isolation_active),
|
||||
160.0,
|
||||
)
|
||||
.into(),
|
||||
@@ -474,7 +471,11 @@ impl StatusBar {
|
||||
if vis(StatusPill::QuickProps) {
|
||||
pills.push(
|
||||
tip(
|
||||
toggle_pill(crate::ui::icons::ST_QUICKPROPS, quick_properties, Message::ToggleQuickProperties),
|
||||
toggle_pill(
|
||||
crate::ui::icons::ST_QUICKPROPS,
|
||||
quick_properties,
|
||||
Message::ToggleQuickProperties,
|
||||
),
|
||||
t!("Quick Properties\nFloating panel on selection"),
|
||||
)
|
||||
.into(),
|
||||
@@ -504,7 +505,11 @@ impl StatusBar {
|
||||
if vis(StatusPill::SelCycle) {
|
||||
pills.push(
|
||||
tip(
|
||||
toggle_pill(crate::ui::icons::ST_SELCYCLE, selection_cycling, Message::ToggleSelectionCycling),
|
||||
toggle_pill(
|
||||
crate::ui::icons::ST_SELCYCLE,
|
||||
selection_cycling,
|
||||
Message::ToggleSelectionCycling,
|
||||
),
|
||||
t!("Selection Cycling\nRepeat-click to step through overlapping objects"),
|
||||
)
|
||||
.into(),
|
||||
@@ -522,7 +527,11 @@ impl StatusBar {
|
||||
if vis(StatusPill::CleanScreen) {
|
||||
pills.push(
|
||||
tip(
|
||||
toggle_pill(crate::ui::icons::ST_CLEANSCREEN, clean_screen, Message::ToggleCleanScreen),
|
||||
toggle_pill(
|
||||
crate::ui::icons::ST_CLEANSCREEN,
|
||||
clean_screen,
|
||||
Message::ToggleCleanScreen,
|
||||
),
|
||||
t!("Clean Screen\nHide ribbon and panels"),
|
||||
)
|
||||
.into(),
|
||||
@@ -607,13 +616,13 @@ impl StatusBar {
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
@@ -634,7 +643,12 @@ impl StatusBar {
|
||||
/// drawing set to architectural units read its coordinates in decimals, and one
|
||||
/// asking for two places got four. `format_length` is the same helper the
|
||||
/// properties panel formats through, so both now say a length the same way.
|
||||
fn format_coords(cursor: glam::DVec3, last: Option<glam::DVec3>, mode: i16, picking: bool) -> String {
|
||||
fn format_coords(
|
||||
cursor: glam::DVec3,
|
||||
last: Option<glam::DVec3>,
|
||||
mode: i16,
|
||||
picking: bool,
|
||||
) -> String {
|
||||
use crate::entities::common::format_length as len;
|
||||
let abs = |p: glam::DVec3| format!("{}, {}, {}", len(p.x), len(p.y), len(p.z));
|
||||
match mode {
|
||||
@@ -666,11 +680,14 @@ fn format_coords(cursor: glam::DVec3, last: Option<glam::DVec3>, mode: i16, pick
|
||||
// ── Customization handle ──────────────────────────────────────────────────
|
||||
|
||||
fn customize_btn() -> Element<'static, Message> {
|
||||
button(crate::ui::icons::themed_secondary(crate::ui::icons::MENU, 16.0))
|
||||
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||
.style(button::subtle)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
button(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::MENU,
|
||||
16.0,
|
||||
))
|
||||
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||
.style(button::subtle)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Tooltip helper ────────────────────────────────────────────────────────
|
||||
@@ -689,11 +706,7 @@ fn menu_tip<'a>(
|
||||
label: std::borrow::Cow<'static, str>,
|
||||
hidden: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let content = if hidden {
|
||||
content
|
||||
} else {
|
||||
tip(content, label)
|
||||
};
|
||||
let content = if hidden { content } else { tip(content, label) };
|
||||
|
||||
mouse_area(content)
|
||||
.on_exit(Message::StatusMenuTooltipHidden(false))
|
||||
@@ -763,21 +776,21 @@ fn split_pill<'a>(
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(if active {
|
||||
palette.primary.weak.color
|
||||
} else {
|
||||
palette.background.weakest.color
|
||||
})),
|
||||
border: Border {
|
||||
color: if active {
|
||||
palette.primary.base.color
|
||||
background: Some(Background::Color(if active {
|
||||
palette.primary.weak.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
palette.background.weakest.color
|
||||
})),
|
||||
border: Border {
|
||||
color: if active {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 6])
|
||||
@@ -880,8 +893,8 @@ fn osnap_btn<'a>(
|
||||
};
|
||||
let main = tip(
|
||||
mouse_area(snap_icon)
|
||||
.on_press(Message::ToggleSnapEnabled)
|
||||
.into(),
|
||||
.on_press(Message::ToggleSnapEnabled)
|
||||
.into(),
|
||||
t!("Object Snap: toggle on/off\nF3"),
|
||||
);
|
||||
|
||||
@@ -926,14 +939,10 @@ fn layout_tab_context_menu(name: String) -> Element<'static, Message> {
|
||||
.on_press(Message::LayoutDelete(name))
|
||||
.interaction(iced::mouse::Interaction::Pointer);
|
||||
|
||||
container(
|
||||
column![rename, delete]
|
||||
.spacing(0)
|
||||
.width(160),
|
||||
)
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 0])
|
||||
.into()
|
||||
container(column![rename, delete].spacing(0).width(160))
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 0])
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A layout tab button.
|
||||
@@ -1020,12 +1029,7 @@ fn space_tab<'a>(
|
||||
let report_key = format!("{report_key_prefix}:{label}");
|
||||
let tab = mouse_area(display).on_press(switch_msg);
|
||||
let tab: Element<'a, Message> = if has_context_menu {
|
||||
crate::ui::wrap_bar::ReorderTab::layout(
|
||||
label.clone(),
|
||||
reorderable_layouts,
|
||||
tab,
|
||||
)
|
||||
.into()
|
||||
crate::ui::wrap_bar::ReorderTab::layout(label.clone(), reorderable_layouts, tab).into()
|
||||
} else {
|
||||
tab.into()
|
||||
};
|
||||
@@ -1035,11 +1039,7 @@ fn space_tab<'a>(
|
||||
} else {
|
||||
tab
|
||||
};
|
||||
crate::ui::wrap_bar::PosReport::owned(
|
||||
report_key,
|
||||
tab,
|
||||
)
|
||||
.into()
|
||||
crate::ui::wrap_bar::PosReport::owned(report_key, tab).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1051,7 +1051,7 @@ fn space_tab<'a>(
|
||||
fn space_mode_btn(current_layout: &str, in_mspace: bool) -> Element<'static, Message> {
|
||||
let is_model_tab = current_layout == "Model";
|
||||
|
||||
// Labels and styling follow AutoCAD convention:
|
||||
// Labels and styling follow the conventional drafting layout:
|
||||
// PAPER = currently in paper-space editing
|
||||
// MODEL = currently in model-space editing (either the Model tab or MSPACE)
|
||||
let (label, active, on_press) = if is_model_tab {
|
||||
@@ -1089,9 +1089,9 @@ fn space_mode_btn(current_layout: &str, in_mspace: bool) -> Element<'static, Mes
|
||||
|
||||
fn status_pill(label: impl Into<String>) -> Element<'static, Message> {
|
||||
container(text(label.into()).size(12))
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A success-colored status pill used when no degrees of freedom remain.
|
||||
@@ -1123,7 +1123,11 @@ fn success_pill(label: impl Into<String>) -> Element<'static, Message> {
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.success.weak.color)),
|
||||
text_color: Some(palette.success.weak.text),
|
||||
border: Border { color: palette.success.base.color, width: 1.0, radius: 4.0.into() },
|
||||
border: Border {
|
||||
color: palette.success.base.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
@@ -1141,10 +1145,10 @@ fn popup_pill(label: impl Into<String>) -> Element<'static, Message> {
|
||||
fn action_pill(label: impl Into<String>, msg: Message) -> Element<'static, Message> {
|
||||
let label = label.into();
|
||||
button(text(label).size(12))
|
||||
.on_press(msg)
|
||||
.style(button::subtle)
|
||||
.padding([4, 7])
|
||||
.into()
|
||||
.on_press(msg)
|
||||
.style(button::subtle)
|
||||
.padding([4, 7])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Scale display ─────────────────────────────────────────────────────────
|
||||
@@ -1196,13 +1200,10 @@ fn active_scale_label(
|
||||
.iter()
|
||||
.find(|(_, anno_scale, vp_scale)| {
|
||||
if is_model {
|
||||
(annotation_scale - *anno_scale).abs()
|
||||
< 0.001 * annotation_scale.max(0.001)
|
||||
(annotation_scale - *anno_scale).abs() < 0.001 * annotation_scale.max(0.001)
|
||||
} else {
|
||||
viewport_scale
|
||||
.map(|current| {
|
||||
(current - *vp_scale).abs() < 0.001 * vp_scale.max(0.001)
|
||||
})
|
||||
.map(|current| (current - *vp_scale).abs() < 0.001 * vp_scale.max(0.001))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! Unlike the alias editor, each row also shows a live-computed resolved
|
||||
//! value or error (`preview`, below) — built fresh from the whole buffer on
|
||||
//! every render, not cached: the same "cheap enough to recompute from
|
||||
//! scratch" philosophy `ParameterTable`/`sketch_solve.rs` already use
|
||||
//! scratch" philosophy `ParameterTable`/`parametric_solve.rs` already use
|
||||
//! throughout this project, and it means the preview reflects circular/
|
||||
//! undefined-reference problems across rows immediately, before Apply.
|
||||
|
||||
@@ -393,7 +393,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn usage_lines_names_the_constraint_kind_and_its_entities() {
|
||||
use crate::scene::sketch_constraints::{ConstraintKind, SketchRef, SketchScope};
|
||||
use crate::scene::parametric_constraints::{
|
||||
ConstraintKind, ParametricRef, ParametricScope,
|
||||
};
|
||||
let mut scene = Scene::new();
|
||||
let line = scene.add_entity(acadrust::EntityType::Line(
|
||||
acadrust::entities::Line::from_points(
|
||||
@@ -402,10 +404,10 @@ mod tests {
|
||||
),
|
||||
));
|
||||
scene
|
||||
.sketch_constraint_set_mut(SketchScope::ModelSpace)
|
||||
.parametric_constraint_set_mut(ParametricScope::ModelSpace)
|
||||
.add(
|
||||
ConstraintKind::Distance,
|
||||
vec![SketchRef::point(line, 0), SketchRef::point(line, 1)],
|
||||
vec![ParametricRef::point(line, 0), ParametricRef::point(line, 1)],
|
||||
Some(crate::scene::named_parameters::DrivingValue::Named(
|
||||
"gap".to_string(),
|
||||
)),
|
||||
|
||||
@@ -145,7 +145,6 @@ impl<T> fmt::Display for Labelled<T> {
|
||||
pub fn view_window<'a>(
|
||||
default_save_format: &'a str,
|
||||
file_assoc_enabled: bool,
|
||||
write_dwg_native_constraints: bool,
|
||||
show_constraint_values: bool,
|
||||
ui_theme: &'a UiThemeConfig,
|
||||
theme_color_inputs: &'a [String; 6],
|
||||
@@ -352,22 +351,6 @@ pub fn view_window<'a>(
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(14),
|
||||
row![
|
||||
iced::widget::checkbox(write_dwg_native_constraints)
|
||||
.on_toggle(Message::WriteDwgNativeConstraintsChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Write native constraint objects on save")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!(
|
||||
"Saves sketch constraints as native drawing objects alongside this app's own format. \
|
||||
Off by default because it adds file size. Existing native objects stay synchronized."
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(14),
|
||||
row![
|
||||
iced::widget::checkbox(show_constraint_values)
|
||||
.on_toggle(Message::ShowConstraintValuesChanged)
|
||||
|
||||
Reference in new issue
Block a user