forked from 1b-agent/opencadstudio-upstream
feat(annotative): annotation-scale manager window
Replace the scale popup's inline delete/add affordances with a single "Manage scales..." row that opens a proper manager, matching the style managers: it reuses the shared style-manager palette and chrome (toolbar + list + editor frame), so it looks and behaves like the Text / Dim / MLeader managers. The manager lists the drawing's annotation scales (name + ratio, the current one checked) and edits them through a Name / Paper units / Drawing units editor: New adds a scale from the fields, Apply renames / re-ratios the selected one, Delete removes it (never the current scale), and Set Current makes the selected scale the model-space annotation scale. Backed by Scene::add_scale / remove_scale / edit_scale on ACAD_SCALELIST. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
b1eba180eb
commit
1071570f40
9 files changed
+427
-67
No files matched your search
+11
-43
@@ -38,15 +38,13 @@ pub fn scale_popup_overlay(
|
||||
} else {
|
||||
Message::SetViewportScale(vp_scale)
|
||||
};
|
||||
// Editing the drawing's scale list is a model-space concept; in paper
|
||||
// space the popup only picks a viewport scale, so no delete there.
|
||||
scale_row(label, active, is_model && !active, msg)
|
||||
scale_row(label, active, msg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Model space gets an "add scale" affordance at the bottom.
|
||||
// Model space gets a "Manage scales..." row that opens the scale manager.
|
||||
if is_model {
|
||||
rows.push(add_scale_row());
|
||||
rows.push(manage_row());
|
||||
}
|
||||
|
||||
let width = if is_model { 150.0 } else { 120.0 };
|
||||
@@ -69,21 +67,16 @@ pub fn scale_popup_overlay(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn scale_row(
|
||||
label: String,
|
||||
active: bool,
|
||||
deletable: bool,
|
||||
msg: Message,
|
||||
) -> Element<'static, Message> {
|
||||
fn scale_row(label: String, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
|
||||
let lbl = text(label.clone())
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
||||
|
||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||
|
||||
let select = button(content)
|
||||
button(content)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
@@ -93,32 +86,13 @@ fn scale_row(
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
.padding([4, 10]);
|
||||
|
||||
if deletable {
|
||||
let del = button(text("\u{00d7}").size(13).color(LABEL_OFF))
|
||||
.on_press(Message::ScaleListDelete(label))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: match status {
|
||||
button::Status::Hovered => DELETE_HOVER,
|
||||
_ => LABEL_OFF,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 8]);
|
||||
row![select, del].align_y(iced::Center).into()
|
||||
} else {
|
||||
select.into()
|
||||
}
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn add_scale_row() -> Element<'static, Message> {
|
||||
button(text("+ Add scale...").size(11).color(CHECK_COLOR))
|
||||
.on_press(Message::ScaleListAddPrompt)
|
||||
fn manage_row() -> Element<'static, Message> {
|
||||
button(text("Manage scales...").size(11).color(CHECK_COLOR))
|
||||
.on_press(Message::ScaleManagerOpen)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
@@ -157,12 +131,6 @@ const CHECK_COLOR: Color = Color {
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const DELETE_HOVER: Color = Color {
|
||||
r: 0.95,
|
||||
g: 0.45,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
|
||||
@@ -6,4 +6,5 @@ pub mod textstyle;
|
||||
pub mod tablestyle;
|
||||
pub mod point_style;
|
||||
pub mod style_list;
|
||||
pub mod scale_manager;
|
||||
pub mod style_manager;
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Annotation-scale manager — add / delete / edit the drawing's ACAD_SCALELIST.
|
||||
//!
|
||||
//! Shares the style managers' frame (toolbar + list + editor + palette) so it
|
||||
//! looks and behaves like Text / Dim / MLeader style managers, but scales are
|
||||
//! plain `Scale` objects rather than symbol-table styles, so it drives its own
|
||||
//! messages instead of the StyleKind machinery.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::style::style_manager::{hdivider, tb_button, vsep, BG, BORDER, DIM, LIST, TB, TEXT};
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const INPUT_BG: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const CURRENT_CHECK: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.82,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(INPUT_BG),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 0.45,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `scales` is (name, "paper:drawing" ratio label). The editor buffers hold the
|
||||
/// name / paper / drawing of the row being added or edited.
|
||||
pub fn view_window<'a, 'b>(
|
||||
scales: &'b [(String, String)],
|
||||
selected: &'b str,
|
||||
current: &'b str,
|
||||
name_buf: &'a str,
|
||||
paper_buf: &'a str,
|
||||
drawing_buf: &'a str,
|
||||
) -> Element<'a, Message> {
|
||||
// ── Toolbar: New / Delete | Set Current / Apply ───────────────────────
|
||||
let toolbar = container(
|
||||
row![
|
||||
tb_button("New", Message::ScaleManagerNew, false),
|
||||
tb_button("Delete", Message::ScaleManagerDelete, false),
|
||||
Space::new().width(Fill),
|
||||
tb_button("Set Current", Message::ScaleManagerSetCurrent, false),
|
||||
tb_button("Apply", Message::ScaleManagerApply, true),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
.padding([5, 8]);
|
||||
|
||||
// ── Left: scale list ──────────────────────────────────────────────────
|
||||
let rows: Vec<Element<'_, Message>> = scales
|
||||
.iter()
|
||||
.map(|(name, ratio)| {
|
||||
let is_sel = name.as_str() == selected;
|
||||
let is_cur = name.eq_ignore_ascii_case(current);
|
||||
let check = crate::ui::icons::check_cell(is_cur, CURRENT_CHECK);
|
||||
let label = row![
|
||||
check,
|
||||
text(name.clone()).size(11).color(TEXT).width(Fill),
|
||||
text(ratio.clone()).size(10).color(DIM),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
button(label)
|
||||
.on_press(Message::ScaleManagerSelect(name.clone()))
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(if is_sel {
|
||||
ACTIVE
|
||||
} else if matches!(st, button::Status::Hovered) {
|
||||
Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
})),
|
||||
text_color: TEXT,
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let list_panel = container(
|
||||
column![
|
||||
text("Scales").size(10).color(DIM),
|
||||
container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(2),
|
||||
]
|
||||
.spacing(4)
|
||||
.height(Fill),
|
||||
)
|
||||
.width(190)
|
||||
.height(Fill)
|
||||
.padding(iced::Padding {
|
||||
top: 12.0,
|
||||
right: 8.0,
|
||||
bottom: 12.0,
|
||||
left: 12.0,
|
||||
});
|
||||
|
||||
// ── Right: editor (name + paper:drawing) ──────────────────────────────
|
||||
let field = |label: &'static str, value: &str, on: fn(String) -> Message| {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(96),
|
||||
text_input("", value)
|
||||
.on_input(on)
|
||||
.style(field_style)
|
||||
.size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
]
|
||||
.align_y(iced::Center)
|
||||
.spacing(6)
|
||||
};
|
||||
|
||||
let editor = container(
|
||||
column![
|
||||
text("Scale").size(10).color(DIM),
|
||||
field("Name", name_buf, Message::ScaleManagerNameBuf),
|
||||
field("Paper units", paper_buf, Message::ScaleManagerPaperBuf),
|
||||
field("Drawing units", drawing_buf, Message::ScaleManagerDrawingBuf),
|
||||
Space::new().height(6),
|
||||
text("Type the ratio in the fields, then New to add or Apply to update the selected scale.")
|
||||
.size(10)
|
||||
.color(DIM),
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(12);
|
||||
|
||||
let body = row![list_panel, vsep(), editor].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
}
|
||||
@@ -12,31 +12,33 @@ use iced::widget::button::{Status, Style};
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const TB: Color = Color {
|
||||
// Shared palette — also reused by the scale manager so it matches the style
|
||||
// managers exactly ([[feedback_shared_infra]]).
|
||||
pub(crate) const TB: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
const BG: Color = Color {
|
||||
pub(crate) const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
pub(crate) const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
pub(crate) const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
pub(crate) const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
@@ -48,7 +50,7 @@ const ACCENT: Color = Color {
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const LIST: Color = Color {
|
||||
pub(crate) const LIST: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
@@ -170,7 +172,7 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
||||
|
||||
// ── Shared chrome ──────────────────────────────────────────────────────────
|
||||
|
||||
fn tb_button<'a>(label: &'a str, msg: Message, accent: bool) -> Element<'a, Message> {
|
||||
pub(crate) fn tb_button<'a>(label: &'a str, msg: Message, accent: bool) -> Element<'a, Message> {
|
||||
let pad = if accent { [4, 14] } else { [4, 10] };
|
||||
button(text(label).size(11))
|
||||
.on_press(msg)
|
||||
@@ -212,7 +214,7 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, Status) -> Style {
|
||||
}
|
||||
}
|
||||
|
||||
fn hdivider<'a>() -> Element<'a, Message> {
|
||||
pub(crate) fn hdivider<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
@@ -223,7 +225,7 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn vsep<'a>() -> Element<'a, Message> {
|
||||
pub(crate) fn vsep<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
|
||||
Reference in new issue
Block a user