forked from 1b-agent/opencadstudio-upstream
Merge remote-tracking branch 'origin/main' into codex/field-command
# Conflicts: # src/ui/window/attdef_dialog.rs
This commit is contained in:
commit
762cf1e7e4
68 files changed
+4665
-1048
No files matched your search
+52
-16
@@ -213,6 +213,18 @@ pub(crate) fn pick_box_aperture_px(value: i32) -> f32 {
|
||||
aperture.max(1.0)
|
||||
}
|
||||
|
||||
/// Which interactive navigation tool is armed. Each one owns the whole
|
||||
/// viewport and hides the CAD crosshair, but they do not share a cursor: the
|
||||
/// hand reads as "drag the sheet", which is wrong for a zoom or an orbit.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum NavCursor {
|
||||
#[default]
|
||||
None,
|
||||
Pan,
|
||||
Orbit,
|
||||
Zoom,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CrosshairOptions {
|
||||
pub size_percent: i32,
|
||||
@@ -852,7 +864,7 @@ pub fn selection_overlay<'a>(
|
||||
dividers: Vec<iced::Rectangle>,
|
||||
pane_move_rect: Option<iced::Rectangle>,
|
||||
pane_drop_rect: Option<iced::Rectangle>,
|
||||
pan_mode: bool,
|
||||
nav: NavCursor,
|
||||
suppressed: bool,
|
||||
hover_locked: bool,
|
||||
crosshair_bg: [f32; 4],
|
||||
@@ -882,7 +894,7 @@ pub fn selection_overlay<'a>(
|
||||
dividers,
|
||||
pane_move_rect,
|
||||
pane_drop_rect,
|
||||
pan_mode,
|
||||
nav,
|
||||
suppressed,
|
||||
hover_locked,
|
||||
crosshair_bg,
|
||||
@@ -936,9 +948,10 @@ struct SelectionCanvas {
|
||||
pane_move_rect: Option<iced::Rectangle>,
|
||||
/// The pane under the cursor during a pane move (drop target), highlighted.
|
||||
pane_drop_rect: Option<iced::Rectangle>,
|
||||
/// Interactive PAN mode: the crosshair is hidden and the cursor becomes a
|
||||
/// hand so the viewport reads as a draggable surface.
|
||||
pan_mode: bool,
|
||||
/// Which interactive navigation tool is armed. Any of them hides the
|
||||
/// crosshair — the viewport is being driven, not drawn on — and each
|
||||
/// picks its own cursor in `mouse_interaction`.
|
||||
nav: NavCursor,
|
||||
/// A ribbon dropdown (or similar overlay) is open over the viewport. The
|
||||
/// crosshair is not drawn and the OS cursor is shown normally so the panel
|
||||
/// is usable instead of the cursor vanishing over it. (#227)
|
||||
@@ -1197,13 +1210,30 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
if self.suppressed {
|
||||
return mouse::Interaction::default();
|
||||
}
|
||||
// PAN mode owns the whole viewport: an open hand when hovering, a
|
||||
// closed hand while dragging.
|
||||
if self.pan_mode && cursor.is_over(bounds) {
|
||||
return if self.selection.borrow().middle_down {
|
||||
mouse::Interaction::Grabbing
|
||||
} else {
|
||||
mouse::Interaction::Grab
|
||||
// An armed navigation tool owns the whole viewport, and the cursor
|
||||
// says which one: a hand to drag the sheet, four-way arrows to swing
|
||||
// the model, a magnifier that carries the sign of the zoom actually
|
||||
// under way. Hovering before the drag starts shows the neutral "+"
|
||||
// magnifier rather than guessing a direction.
|
||||
if self.nav != NavCursor::None && cursor.is_over(bounds) {
|
||||
let dragging = self.selection.borrow().middle_down;
|
||||
return match self.nav {
|
||||
NavCursor::Pan => {
|
||||
if dragging {
|
||||
mouse::Interaction::Grabbing
|
||||
} else {
|
||||
mouse::Interaction::Grab
|
||||
}
|
||||
}
|
||||
NavCursor::Orbit => mouse::Interaction::AllScroll,
|
||||
NavCursor::Zoom => {
|
||||
if dragging && self.selection.borrow().zoom_dir_out {
|
||||
mouse::Interaction::ZoomOut
|
||||
} else {
|
||||
mouse::Interaction::ZoomIn
|
||||
}
|
||||
}
|
||||
NavCursor::None => unreachable!("guarded above"),
|
||||
};
|
||||
}
|
||||
if self.show_viewcube {
|
||||
@@ -1367,8 +1397,14 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
);
|
||||
}
|
||||
|
||||
if let (Some(a), Some(b)) = (self.selection.borrow().box_anchor, self.selection.borrow().box_current) {
|
||||
draw_marquee(&mut frame, a, b, self.selection.borrow().box_crossing, theme, &self.selection_visual, self.crosshair_bg);
|
||||
// ZOOM Dynamic reuses `box_anchor` to remember where the drag began,
|
||||
// so the marquee would otherwise rubber-band across the screen while
|
||||
// the view zooms. Suppress the drawing, not the state — the anchor is
|
||||
// still re-projected as the camera moves.
|
||||
if self.nav != NavCursor::Zoom {
|
||||
if let (Some(a), Some(b)) = (self.selection.borrow().box_anchor, self.selection.borrow().box_current) {
|
||||
draw_marquee(&mut frame, a, b, self.selection.borrow().box_crossing, theme, &self.selection_visual, self.crosshair_bg);
|
||||
}
|
||||
}
|
||||
// Preview marquee for point-picked windows (STRETCH) — same look, no pick.
|
||||
if let Some((a, b, crossing)) = self.selection.borrow().preview_box {
|
||||
@@ -1826,10 +1862,10 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// arrow (see `mouse_interaction`); drawing the CAD crosshair on
|
||||
// top of it would double up the visual feedback.
|
||||
let over_divider = self.divider_under(cursor, bounds);
|
||||
// PAN mode replaces the crosshair with a hand cursor.
|
||||
// An armed navigation tool replaces the crosshair with its own cursor.
|
||||
if !over_viewcube
|
||||
&& !over_divider
|
||||
&& !self.pan_mode
|
||||
&& self.nav == NavCursor::None
|
||||
&& !self.suppressed
|
||||
&& self.crosshair.cursor_type == CursorType::Crosshair
|
||||
{
|
||||
|
||||
+14
-1
@@ -20,7 +20,7 @@ use crate::modules::{CadModule, IconKind, RibbonGroup, RibbonItem};
|
||||
use crate::plugin::all_ribbon_modules;
|
||||
use crate::ui::properties::{linetype_display_name, lw_options, LinetypeItem};
|
||||
|
||||
mod widgets;
|
||||
pub(crate) mod widgets;
|
||||
mod draw_panel;
|
||||
pub(crate) use draw_panel::tools as panel_tools;
|
||||
mod modify_panel;
|
||||
@@ -745,6 +745,19 @@ impl Ribbon {
|
||||
|
||||
// ── Dropdown overlay ──────────────────────────────────────────────────
|
||||
|
||||
/// Place `panel` (`w` wide) under dropdown `id`, closing on a click
|
||||
/// outside it.
|
||||
pub fn place_dropdown<'a>(
|
||||
&self,
|
||||
id: &str,
|
||||
panel: Element<'a, Message>,
|
||||
w: f32,
|
||||
win_w: f32,
|
||||
) -> Element<'a, Message> {
|
||||
let (align_right, h_pad, top) = self.dd_anchor(id, w, win_w);
|
||||
dropdown_backdrop(position_ribbon_dropdown(panel, align_right, h_pad, top))
|
||||
}
|
||||
|
||||
pub fn dropdown_overlay(
|
||||
&self,
|
||||
undo_labels: &[String],
|
||||
|
||||
@@ -446,7 +446,7 @@ pub(super) fn combo_btn_style(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn popup_row_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||
pub(crate) fn popup_row_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||
let palette = theme.palette();
|
||||
let pair = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
palette.background.weak
|
||||
@@ -460,7 +460,7 @@ pub(super) fn popup_row_style(theme: &Theme, status: button::Status) -> button::
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn popup_panel_style(theme: &Theme) -> container::Style {
|
||||
pub(crate) fn popup_panel_style(theme: &Theme) -> container::Style {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
|
||||
@@ -480,14 +480,16 @@ fn test_block_palette_contrast() {
|
||||
// a hardcoded foreground, these equality checks fail on every theme
|
||||
// whose surface text is not pure white.
|
||||
let (normal_bg, normal_fg) = block_card_colors(theme, false, BtnStatus::Active);
|
||||
// Cards sit on the dock's base surface, so they take the weak pair
|
||||
// to read as cards.
|
||||
assert_eq!(
|
||||
normal_bg, p.background.base.color,
|
||||
"Theme {:?} block card normal background drifted from base pair",
|
||||
normal_bg, p.background.weak.color,
|
||||
"Theme {:?} block card normal background drifted from weak pair",
|
||||
theme
|
||||
);
|
||||
assert_eq!(
|
||||
normal_fg, p.background.base.text,
|
||||
"Theme {:?} block card normal label must use base text (was hardcoded WHITE)",
|
||||
normal_fg, p.background.weak.text,
|
||||
"Theme {:?} block card normal label must use weak text (was hardcoded WHITE)",
|
||||
theme
|
||||
);
|
||||
|
||||
@@ -505,11 +507,11 @@ fn test_block_palette_contrast() {
|
||||
theme
|
||||
);
|
||||
|
||||
// Default-branch statuses (Active/Disabled/Focused/...) share the base pair.
|
||||
// Default-branch statuses (Active/Disabled/Focused/...) share the weak pair.
|
||||
let (_, disabled_fg) = block_card_colors(theme, false, BtnStatus::Disabled);
|
||||
assert_eq!(
|
||||
disabled_fg, p.background.base.text,
|
||||
"Theme {:?} block card default-state label must use base text",
|
||||
disabled_fg, p.background.weak.text,
|
||||
"Theme {:?} block card default-state label must use weak text",
|
||||
theme
|
||||
);
|
||||
|
||||
|
||||
@@ -125,8 +125,12 @@ fn input<'a>(value: &str, enabled: bool, on: fn(String) -> AttdefDialogMsg) -> E
|
||||
}
|
||||
|
||||
/// The small button beside a field that takes the value from the drawing.
|
||||
fn pick<'a>(glyph: &'static str, tip: String, message: Option<AttdefDialogMsg>) -> Element<'a, Message> {
|
||||
let b = button(text(glyph).size(13).align_x(iced::Center).width(Fill))
|
||||
fn pick<'a>(
|
||||
face: Element<'a, Message>,
|
||||
tip: String,
|
||||
message: Option<AttdefDialogMsg>,
|
||||
) -> Element<'a, Message> {
|
||||
let b = button(container(face).center_x(Fill))
|
||||
.style(button_style(false))
|
||||
.padding([4, 0])
|
||||
.width(Length::Fixed(28.0));
|
||||
@@ -155,7 +159,7 @@ fn error_band<'a>(error: &Option<String>) -> Option<Element<'a, Message>> {
|
||||
crate::ui::icons::semantic(ICON_WARN, 14.0),
|
||||
text(error).size(11),
|
||||
Space::new().width(Fill),
|
||||
button(text("×").size(12))
|
||||
button(crate::ui::icons::themed(crate::ui::icons::CLOSE, 10.0))
|
||||
.on_press(msg(AttdefDialogMsg::DismissError))
|
||||
.style(button_style(false))
|
||||
.padding([1, 6]),
|
||||
@@ -174,6 +178,11 @@ fn error_band<'a>(error: &Option<String>) -> Option<Element<'a, Message>> {
|
||||
)
|
||||
}
|
||||
|
||||
/// The face of the buttons that take a value from the drawing.
|
||||
fn screen_pick<'a>() -> Element<'a, Message> {
|
||||
crate::ui::icons::themed(crate::ui::icons::SNAP, 14.0)
|
||||
}
|
||||
|
||||
static ICON_WARN: &[u8] = include_bytes!("../../../assets/icons/ui/warning_triangle.svg");
|
||||
|
||||
fn footer<'a>(action: std::borrow::Cow<'static, str>, ok: AttdefDialogMsg) -> Element<'a, Message> {
|
||||
@@ -243,12 +252,12 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
let mut value = row![input(&state.default, true, AttdefDialogMsg::Default)].spacing(6);
|
||||
if multiline {
|
||||
value = value.push(pick(
|
||||
"…",
|
||||
text("...").size(13).into(),
|
||||
t!("Multiline editor").into_owned(),
|
||||
Some(AttdefDialogMsg::EditValue),
|
||||
));
|
||||
}
|
||||
value.push(pick("ƒ", t!("Insert field").into_owned(), Some(AttdefDialogMsg::InsertField)))
|
||||
value.push(pick(text("fx").size(12).into(), t!("Insert field").into_owned(), Some(AttdefDialogMsg::InsertField)))
|
||||
}
|
||||
.spacing(6)
|
||||
.into(),
|
||||
@@ -301,7 +310,7 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
t!("Text height").into_owned(),
|
||||
row![
|
||||
input(&state.height, size_on, AttdefDialogMsg::Height),
|
||||
pick("⌖", t!("Specify on-screen").into_owned(), size_on.then_some(AttdefDialogMsg::PickHeight)),
|
||||
pick(screen_pick(), t!("Specify on-screen").into_owned(), size_on.then_some(AttdefDialogMsg::PickHeight)),
|
||||
]
|
||||
.spacing(6)
|
||||
.into(),
|
||||
@@ -311,7 +320,7 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
t!("Rotation").into_owned(),
|
||||
row![
|
||||
input(&state.rotation, rot_on, AttdefDialogMsg::Rotation),
|
||||
pick("⌖", t!("Specify on-screen").into_owned(), rot_on.then_some(AttdefDialogMsg::PickRotation)),
|
||||
pick(screen_pick(), t!("Specify on-screen").into_owned(), rot_on.then_some(AttdefDialogMsg::PickRotation)),
|
||||
]
|
||||
.spacing(6)
|
||||
.into(),
|
||||
@@ -322,7 +331,7 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
row![
|
||||
input(&state.width, text_on && multiline, AttdefDialogMsg::Width),
|
||||
pick(
|
||||
"⌖",
|
||||
screen_pick(),
|
||||
t!("Specify on-screen").into_owned(),
|
||||
(text_on && multiline).then_some(AttdefDialogMsg::PickWidth)
|
||||
),
|
||||
@@ -380,7 +389,7 @@ pub fn view_edit<'a>(state: &'a AttdefEditState, sizing: crate::ui::modal::Modal
|
||||
true,
|
||||
AttdefDialogMsg::EditDefault
|
||||
),
|
||||
pick("ƒ", t!("Insert field").into_owned(), Some(AttdefDialogMsg::EditInsertField)),
|
||||
pick(text("fx").size(12).into(), t!("Insert field").into_owned(), Some(AttdefDialogMsg::EditInsertField)),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
|
||||
+741
-210
File diff suppressed because it is too large.
Load diff
@@ -55,7 +55,7 @@ pub struct AppPrefs {
|
||||
pub commandline_fade_ms: i32,
|
||||
/// ZOOMWHEEL: reverse the mouse-wheel zoom direction.
|
||||
pub zoom_wheel_reversed: bool,
|
||||
/// ZOOMFACTOR, 3..=100.
|
||||
/// ZOOMFACTOR, 3..=500.
|
||||
pub zoom_factor: i32,
|
||||
/// TEXTEDITMODE: TEXTEDIT keeps prompting for the next object.
|
||||
pub texteditmode: bool,
|
||||
@@ -1181,10 +1181,10 @@ pub fn view_window<'a>(
|
||||
Space::new().height(12),
|
||||
row![
|
||||
text(crate::t!("Zoom factor")).size(12).width(150),
|
||||
slider(3..=100, prefs.zoom_factor.clamp(3, 100), Message::ZoomFactorChanged)
|
||||
slider(3..=500, prefs.zoom_factor.clamp(3, 500), Message::ZoomFactorChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(prefs.zoom_factor.clamp(3, 100).to_string()).size(11).width(44),
|
||||
text(prefs.zoom_factor.clamp(3, 500).to_string()).size(11).width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
|
||||
@@ -251,7 +251,7 @@ pub(crate) fn card_style(theme: &Theme) -> container::Style {
|
||||
}
|
||||
}
|
||||
|
||||
fn well_style(theme: &Theme) -> container::Style {
|
||||
pub(crate) fn well_style(theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
border: Border {
|
||||
|
||||
Reference in new issue
Block a user