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 <noreply@anthropic.com>
This commit is contained in:
parent
b1eba180eb
commit
1071570f40
9 changed files with 427 additions and 67 deletions
|
|
@ -659,6 +659,12 @@ pub(super) struct OpenCADStudio {
|
||||||
layout_manager_selected: String,
|
layout_manager_selected: String,
|
||||||
layout_manager_rename_buf: String,
|
layout_manager_rename_buf: String,
|
||||||
|
|
||||||
|
// ── Annotation-scale Manager ──────────────────────────────────────────
|
||||||
|
scale_manager_selected: String,
|
||||||
|
scale_manager_name_buf: String,
|
||||||
|
scale_manager_paper_buf: String,
|
||||||
|
scale_manager_drawing_buf: String,
|
||||||
|
|
||||||
// ── Plot Style Panel ──────────────────────────────────────────────────
|
// ── Plot Style Panel ──────────────────────────────────────────────────
|
||||||
/// Selected ACI index in the panel (1-255).
|
/// Selected ACI index in the panel (1-255).
|
||||||
plotstyle_panel_aci: u8,
|
plotstyle_panel_aci: u8,
|
||||||
|
|
@ -1106,6 +1112,7 @@ pub enum ModalKind {
|
||||||
AttributeEditor,
|
AttributeEditor,
|
||||||
LayerDeleteWarning,
|
LayerDeleteWarning,
|
||||||
Aliases,
|
Aliases,
|
||||||
|
ScaleManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies a DimStyle field that can be edited in the dialog.
|
/// Identifies a DimStyle field that can be edited in the dialog.
|
||||||
|
|
@ -1455,10 +1462,22 @@ pub enum Message {
|
||||||
ToggleScalePopup,
|
ToggleScalePopup,
|
||||||
/// Close the scale picker popup.
|
/// Close the scale picker popup.
|
||||||
CloseScalePopup,
|
CloseScalePopup,
|
||||||
/// Delete a named annotation scale from the drawing's ACAD_SCALELIST.
|
/// Open the annotation-scale manager (from the scale popup's Manage row).
|
||||||
ScaleListDelete(String),
|
ScaleManagerOpen,
|
||||||
/// Prime the command line to add a new annotation scale.
|
/// Select a scale row in the manager (loads it into the editor).
|
||||||
ScaleListAddPrompt,
|
ScaleManagerSelect(String),
|
||||||
|
/// Add a new scale from the editor fields.
|
||||||
|
ScaleManagerNew,
|
||||||
|
/// Delete the selected scale.
|
||||||
|
ScaleManagerDelete,
|
||||||
|
/// Set the selected scale as the current annotation scale.
|
||||||
|
ScaleManagerSetCurrent,
|
||||||
|
/// Apply the editor fields to the selected scale (rename / re-ratio).
|
||||||
|
ScaleManagerApply,
|
||||||
|
/// Annotation-scale manager editor field edits.
|
||||||
|
ScaleManagerNameBuf(String),
|
||||||
|
ScaleManagerPaperBuf(String),
|
||||||
|
ScaleManagerDrawingBuf(String),
|
||||||
/// Toggle the leftmost hamburger's Model/layout list dropdown.
|
/// Toggle the leftmost hamburger's Model/layout list dropdown.
|
||||||
ToggleLayoutList,
|
ToggleLayoutList,
|
||||||
/// Close the Model/layout list dropdown.
|
/// Close the Model/layout list dropdown.
|
||||||
|
|
@ -2234,6 +2253,10 @@ impl OpenCADStudio {
|
||||||
alias_editor_rows: Vec::new(),
|
alias_editor_rows: Vec::new(),
|
||||||
// Layout Manager
|
// Layout Manager
|
||||||
layout_manager_selected: "Model".to_string(),
|
layout_manager_selected: "Model".to_string(),
|
||||||
|
scale_manager_selected: String::new(),
|
||||||
|
scale_manager_name_buf: String::new(),
|
||||||
|
scale_manager_paper_buf: String::new(),
|
||||||
|
scale_manager_drawing_buf: String::new(),
|
||||||
layout_manager_rename_buf: String::new(),
|
layout_manager_rename_buf: String::new(),
|
||||||
plotstyle_panel_aci: 1,
|
plotstyle_panel_aci: 1,
|
||||||
ps_color_buf: String::new(),
|
ps_color_buf: String::new(),
|
||||||
|
|
|
||||||
|
|
@ -1647,29 +1647,97 @@ impl OpenCADStudio {
|
||||||
self.scale_popup_open = false;
|
self.scale_popup_open = false;
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ScaleListDelete(name) => {
|
Message::ScaleManagerOpen => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let current = self.tabs[i]
|
self.scale_popup_open = false;
|
||||||
|
let cur = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.document
|
.document
|
||||||
.header
|
.header
|
||||||
.current_annotation_scale
|
.current_annotation_scale
|
||||||
.clone();
|
.clone();
|
||||||
// Keep the popup open so several can be removed in a row; never
|
self.load_scale_editor(&cur);
|
||||||
// remove the current annotation scale out from under the drawing.
|
self.active_modal = Some(crate::app::ModalKind::ScaleManager);
|
||||||
if !name.eq_ignore_ascii_case(¤t) {
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerSelect(name) => {
|
||||||
|
self.load_scale_editor(&name);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerNameBuf(s) => {
|
||||||
|
self.scale_manager_name_buf = s;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerPaperBuf(s) => {
|
||||||
|
self.scale_manager_paper_buf = s;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerDrawingBuf(s) => {
|
||||||
|
self.scale_manager_drawing_buf = s;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerNew => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if let (name, Some(paper), Some(drawing)) = self.scale_editor_values() {
|
||||||
|
if !name.is_empty() && paper > 0.0 && drawing > 0.0 {
|
||||||
|
self.push_undo_snapshot(i, "SCALELISTEDIT");
|
||||||
|
if self.tabs[i].scene.add_scale(&name, paper, drawing) {
|
||||||
|
self.scale_manager_selected = name;
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerApply => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let old = self.scale_manager_selected.clone();
|
||||||
|
if !old.is_empty() {
|
||||||
|
if let (name, Some(paper), Some(drawing)) = self.scale_editor_values() {
|
||||||
|
if !name.is_empty() && paper > 0.0 && drawing > 0.0 {
|
||||||
|
self.push_undo_snapshot(i, "SCALELISTEDIT");
|
||||||
|
if self.tabs[i].scene.edit_scale(&old, &name, paper, drawing) {
|
||||||
|
self.scale_manager_selected = name;
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ScaleManagerDelete => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let sel = self.scale_manager_selected.clone();
|
||||||
|
let cur = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.current_annotation_scale
|
||||||
|
.clone();
|
||||||
|
if !sel.is_empty() && !sel.eq_ignore_ascii_case(&cur) {
|
||||||
self.push_undo_snapshot(i, "SCALELISTEDIT");
|
self.push_undo_snapshot(i, "SCALELISTEDIT");
|
||||||
if self.tabs[i].scene.remove_scale(&name) {
|
if self.tabs[i].scene.remove_scale(&sel) {
|
||||||
|
self.scale_manager_selected.clear();
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ScaleListAddPrompt => {
|
Message::ScaleManagerSetCurrent => {
|
||||||
// Close the popup and prime the command line; the user types the
|
let i = self.active_tab;
|
||||||
// ratio (e.g. 1:50) and commits it via SCALELISTEDIT ADD.
|
let sel = self.scale_manager_selected.clone();
|
||||||
self.scale_popup_open = false;
|
if let Some((_, anno, _)) = self
|
||||||
self.command_line.input = "SCALELISTEDIT ADD ".to_string();
|
.tabs[i]
|
||||||
|
.scene
|
||||||
|
.scale_list()
|
||||||
|
.into_iter()
|
||||||
|
.find(|(n, _, _)| n.eq_ignore_ascii_case(&sel))
|
||||||
|
{
|
||||||
|
self.tabs[i].scene.annotation_scale = anno;
|
||||||
|
self.tabs[i].scene.document.header.current_annotation_scale = sel;
|
||||||
|
self.tabs[i].scene.bump_geometry();
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ToggleLayoutList => {
|
Message::ToggleLayoutList => {
|
||||||
|
|
@ -3953,4 +4021,31 @@ impl OpenCADStudio {
|
||||||
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
|
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load a named scale into the scale-manager editor buffers (name + the
|
||||||
|
/// paper / drawing units); blank ratios when the scale isn't found.
|
||||||
|
fn load_scale_editor(&mut self, name: &str) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
self.scale_manager_selected = name.to_string();
|
||||||
|
self.scale_manager_name_buf = name.to_string();
|
||||||
|
match self.tabs[i].scene.scale_paper_drawing(name) {
|
||||||
|
Some((p, d)) => {
|
||||||
|
self.scale_manager_paper_buf = format!("{p}");
|
||||||
|
self.scale_manager_drawing_buf = format!("{d}");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.scale_manager_paper_buf.clear();
|
||||||
|
self.scale_manager_drawing_buf.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scale-manager editor's (name, paper?, drawing?) parsed from buffers.
|
||||||
|
fn scale_editor_values(&self) -> (String, Option<f64>, Option<f64>) {
|
||||||
|
(
|
||||||
|
self.scale_manager_name_buf.trim().to_string(),
|
||||||
|
self.scale_manager_paper_buf.trim().parse::<f64>().ok(),
|
||||||
|
self.scale_manager_drawing_buf.trim().parse::<f64>().ok(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1573,6 +1573,7 @@ impl OpenCADStudio {
|
||||||
AttributeEditor => (640, 500),
|
AttributeEditor => (640, 500),
|
||||||
LayerDeleteWarning => (440, 200),
|
LayerDeleteWarning => (440, 200),
|
||||||
Aliases => (480, 520),
|
Aliases => (480, 520),
|
||||||
|
ScaleManager => (520, 360),
|
||||||
};
|
};
|
||||||
Some((w as f32 + EXTRA_W, h as f32 + EXTRA_H))
|
Some((w as f32 + EXTRA_W, h as f32 + EXTRA_H))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,35 @@ impl OpenCADStudio {
|
||||||
320,
|
320,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
super::super::ModalKind::ScaleManager => {
|
||||||
|
let tab = &self.tabs[self.active_tab];
|
||||||
|
let scales: Vec<(String, String)> = tab
|
||||||
|
.scene
|
||||||
|
.scale_list()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, _, _)| {
|
||||||
|
let ratio = tab
|
||||||
|
.scene
|
||||||
|
.scale_paper_drawing(&name)
|
||||||
|
.map(|(p, d)| format!("{p}:{d}"))
|
||||||
|
.unwrap_or_default();
|
||||||
|
(name, ratio)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let current = tab.scene.document.header.current_annotation_scale.clone();
|
||||||
|
sized(
|
||||||
|
crate::ui::style::scale_manager::view_window(
|
||||||
|
&scales,
|
||||||
|
&self.scale_manager_selected,
|
||||||
|
¤t,
|
||||||
|
&self.scale_manager_name_buf,
|
||||||
|
&self.scale_manager_paper_buf,
|
||||||
|
&self.scale_manager_drawing_buf,
|
||||||
|
),
|
||||||
|
520,
|
||||||
|
360,
|
||||||
|
)
|
||||||
|
}
|
||||||
super::super::ModalKind::Plotstyle => sized(
|
super::super::ModalKind::Plotstyle => sized(
|
||||||
crate::ui::style::plotstyle::view_window(
|
crate::ui::style::plotstyle::view_window(
|
||||||
self.active_plot_style.as_ref(),
|
self.active_plot_style.as_ref(),
|
||||||
|
|
|
||||||
|
|
@ -1945,6 +1945,57 @@ impl Scene {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rename `old_name` and/or change its paper:drawing ratio. Returns `false`
|
||||||
|
/// if the scale is missing or the new name collides with another scale.
|
||||||
|
pub fn edit_scale(&mut self, old_name: &str, new_name: &str, paper: f64, drawing: f64) -> bool {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let root_h = self.document.header.named_objects_dict_handle;
|
||||||
|
let Some(scalelist_h) = crate::scene::annotative::as_dict(&self.document, root_h)
|
||||||
|
.and_then(|d| d.get("ACAD_SCALELIST"))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(sh) = crate::scene::annotative::as_dict(&self.document, scalelist_h).and_then(
|
||||||
|
|d| {
|
||||||
|
d.entries
|
||||||
|
.iter()
|
||||||
|
.find(|(n, _)| n.eq_ignore_ascii_case(old_name))
|
||||||
|
.map(|(_, h)| *h)
|
||||||
|
},
|
||||||
|
) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// A new name that collides with a *different* scale is rejected.
|
||||||
|
if !new_name.eq_ignore_ascii_case(old_name)
|
||||||
|
&& crate::scene::annotative::as_dict(&self.document, scalelist_h)
|
||||||
|
.is_some_and(|d| d.entries.iter().any(|(n, _)| n.eq_ignore_ascii_case(new_name)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(ObjectType::Scale(s)) = self.document.objects.get_mut(&sh) {
|
||||||
|
s.name = new_name.to_string();
|
||||||
|
s.paper_units = paper;
|
||||||
|
s.drawing_units = drawing;
|
||||||
|
s.is_unit_scale = (paper - drawing).abs() < 1e-10;
|
||||||
|
}
|
||||||
|
if let Some(ObjectType::Dictionary(sl)) = self.document.objects.get_mut(&scalelist_h) {
|
||||||
|
if let Some(e) = sl.entries.iter_mut().find(|(n, _)| n.eq_ignore_ascii_case(old_name)) {
|
||||||
|
e.0 = new_name.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The (paper_units, drawing_units) of a named scale, for the editor.
|
||||||
|
pub fn scale_paper_drawing(&self, name: &str) -> Option<(f64, f64)> {
|
||||||
|
self.document.objects.values().find_map(|o| match o {
|
||||||
|
ObjectType::Scale(s) if !s.is_temporary && s.name.eq_ignore_ascii_case(name) => {
|
||||||
|
Some((s.paper_units, s.drawing_units))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// List of user viewports in the current layout: (handle, label, frozen_layer_handles).
|
/// List of user viewports in the current layout: (handle, label, frozen_layer_handles).
|
||||||
pub fn viewport_list(&self) -> Vec<(acadrust::Handle, String, Vec<acadrust::Handle>)> {
|
pub fn viewport_list(&self) -> Vec<(acadrust::Handle, String, Vec<acadrust::Handle>)> {
|
||||||
if self.current_layout == "Model" {
|
if self.current_layout == "Model" {
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,13 @@ pub fn scale_popup_overlay(
|
||||||
} else {
|
} else {
|
||||||
Message::SetViewportScale(vp_scale)
|
Message::SetViewportScale(vp_scale)
|
||||||
};
|
};
|
||||||
// Editing the drawing's scale list is a model-space concept; in paper
|
scale_row(label, active, msg)
|
||||||
// space the popup only picks a viewport scale, so no delete there.
|
|
||||||
scale_row(label, active, is_model && !active, msg)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.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 {
|
if is_model {
|
||||||
rows.push(add_scale_row());
|
rows.push(manage_row());
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = if is_model { 150.0 } else { 120.0 };
|
let width = if is_model { 150.0 } else { 120.0 };
|
||||||
|
|
@ -69,21 +67,16 @@ pub fn scale_popup_overlay(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scale_row(
|
fn scale_row(label: String, active: bool, msg: Message) -> Element<'static, Message> {
|
||||||
label: String,
|
|
||||||
active: bool,
|
|
||||||
deletable: bool,
|
|
||||||
msg: Message,
|
|
||||||
) -> Element<'static, Message> {
|
|
||||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||||
|
|
||||||
let lbl = text(label.clone())
|
let lbl = text(label)
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
.color(if active { LABEL_ON } else { LABEL_OFF });
|
||||||
|
|
||||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||||
|
|
||||||
let select = button(content)
|
button(content)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|_: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: Some(Background::Color(match status {
|
||||||
|
|
@ -93,32 +86,13 @@ fn scale_row(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10]);
|
.padding([4, 10])
|
||||||
|
.into()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_scale_row() -> Element<'static, Message> {
|
fn manage_row() -> Element<'static, Message> {
|
||||||
button(text("+ Add scale...").size(11).color(CHECK_COLOR))
|
button(text("Manage scales...").size(11).color(CHECK_COLOR))
|
||||||
.on_press(Message::ScaleListAddPrompt)
|
.on_press(Message::ScaleManagerOpen)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|_: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: Some(Background::Color(match status {
|
||||||
button::Status::Hovered => ROW_HOVER,
|
button::Status::Hovered => ROW_HOVER,
|
||||||
|
|
@ -157,12 +131,6 @@ const CHECK_COLOR: Color = Color {
|
||||||
b: 1.00,
|
b: 1.00,
|
||||||
a: 1.0,
|
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 {
|
const LABEL_ON: Color = Color {
|
||||||
r: 0.92,
|
r: 0.92,
|
||||||
g: 0.92,
|
g: 0.92,
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ pub mod textstyle;
|
||||||
pub mod tablestyle;
|
pub mod tablestyle;
|
||||||
pub mod point_style;
|
pub mod point_style;
|
||||||
pub mod style_list;
|
pub mod style_list;
|
||||||
|
pub mod scale_manager;
|
||||||
pub mod style_manager;
|
pub mod style_manager;
|
||||||
|
|
|
||||||
190
src/ui/style/scale_manager.rs
Normal file
190
src/ui/style/scale_manager.rs
Normal file
|
|
@ -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::widget::{button, column, container, row, scrollable, text, Space};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
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,
|
r: 0.13,
|
||||||
g: 0.13,
|
g: 0.13,
|
||||||
b: 0.13,
|
b: 0.13,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const BG: Color = Color {
|
pub(crate) const BG: Color = Color {
|
||||||
r: 0.15,
|
r: 0.15,
|
||||||
g: 0.15,
|
g: 0.15,
|
||||||
b: 0.15,
|
b: 0.15,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const BORDER: Color = Color {
|
pub(crate) const BORDER: Color = Color {
|
||||||
r: 0.35,
|
r: 0.35,
|
||||||
g: 0.35,
|
g: 0.35,
|
||||||
b: 0.35,
|
b: 0.35,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const TEXT: Color = Color {
|
pub(crate) const TEXT: Color = Color {
|
||||||
r: 0.88,
|
r: 0.88,
|
||||||
g: 0.88,
|
g: 0.88,
|
||||||
b: 0.88,
|
b: 0.88,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const DIM: Color = Color {
|
pub(crate) const DIM: Color = Color {
|
||||||
r: 0.55,
|
r: 0.55,
|
||||||
g: 0.55,
|
g: 0.55,
|
||||||
b: 0.55,
|
b: 0.55,
|
||||||
|
|
@ -48,7 +50,7 @@ const ACCENT: Color = Color {
|
||||||
b: 0.85,
|
b: 0.85,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const LIST: Color = Color {
|
pub(crate) const LIST: Color = Color {
|
||||||
r: 0.12,
|
r: 0.12,
|
||||||
g: 0.12,
|
g: 0.12,
|
||||||
b: 0.12,
|
b: 0.12,
|
||||||
|
|
@ -170,7 +172,7 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
||||||
|
|
||||||
// ── Shared chrome ──────────────────────────────────────────────────────────
|
// ── 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] };
|
let pad = if accent { [4, 14] } else { [4, 10] };
|
||||||
button(text(label).size(11))
|
button(text(label).size(11))
|
||||||
.on_press(msg)
|
.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))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
|
|
@ -223,7 +225,7 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vsep<'a>() -> Element<'a, Message> {
|
pub(crate) fn vsep<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(1).height(Fill))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue