forked from 1b-agent/opencadstudio-upstream
style: apply cargo fmt across workspace
This commit is contained in:
1 parent
85710e5454
commit
9cf9ff2c5c
531 files changed
+57740
-33906
No files matched your search
+7
-10
@@ -13,9 +13,7 @@ fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
|
||||
fn surface_style(theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weakest.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weakest.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
@@ -53,7 +51,10 @@ fn info_row<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
container(
|
||||
row![
|
||||
text(label).size(11).style(muted_style).width(Length::Fixed(110.0)),
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(110.0)),
|
||||
text(value).size(13).width(Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
@@ -169,9 +170,7 @@ fn build_label() -> std::borrow::Cow<'static, str> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_window(
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'static, Message> {
|
||||
pub fn view_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Message> {
|
||||
// The hero carries the full build identity (`2026.36+194.gef189d77`);
|
||||
// the cards split it into the release version and the build details.
|
||||
let version = format!("v{}", env!("OCS_FULL_VERSION"));
|
||||
@@ -260,9 +259,7 @@ pub fn view_window(
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
//! unsaved-changes guard on close.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use crate::ui::style::common::muted_style;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Element, Length, Theme};
|
||||
use crate::t;
|
||||
|
||||
/// Which column of an alias row a text edit targets.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -37,18 +37,16 @@ fn danger_input_style(
|
||||
let danger = theme.palette().danger.base;
|
||||
iced::widget::text_input::Style {
|
||||
background: Background::Color(theme.palette().danger.weak.color),
|
||||
border: iced::border::rounded(4)
|
||||
.color(danger.color)
|
||||
.width(
|
||||
if matches!(
|
||||
status,
|
||||
iced::widget::text_input::Status::Focused { is_hovered: _ }
|
||||
) {
|
||||
1.5
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
),
|
||||
border: iced::border::rounded(4).color(danger.color).width(
|
||||
if matches!(
|
||||
status,
|
||||
iced::widget::text_input::Status::Focused { is_hovered: _ }
|
||||
) {
|
||||
1.5
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
),
|
||||
icon: danger.color,
|
||||
placeholder: danger.color.scale_alpha(0.7),
|
||||
value: danger.text,
|
||||
@@ -77,7 +75,12 @@ pub fn view_window<'a>(
|
||||
// Right gutter reserved so the scrollbar has its own lane and never sits on
|
||||
// top of the row delete (✕) buttons. Applied to both the header and the
|
||||
// scrollable rows so the columns stay aligned.
|
||||
let gutter = iced::Padding { top: 0.0, right: GUTTER, bottom: 0.0, left: 0.0 };
|
||||
let gutter = iced::Padding {
|
||||
top: 0.0,
|
||||
right: GUTTER,
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
};
|
||||
|
||||
let head = container(
|
||||
row![
|
||||
@@ -94,7 +97,11 @@ pub fn view_window<'a>(
|
||||
let alias_key = alias.trim().to_uppercase();
|
||||
let duplicate = !alias_key.is_empty() && duplicate_aliases.contains(alias_key.as_str());
|
||||
let alias_box = text_input(t!("alias").as_ref(), alias)
|
||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Alias, value: v })
|
||||
.on_input(move |v| Message::AliasEditorInput {
|
||||
idx,
|
||||
field: AliasField::Alias,
|
||||
value: v,
|
||||
})
|
||||
.size(13)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(120.0));
|
||||
@@ -103,10 +110,13 @@ pub fn view_window<'a>(
|
||||
} else {
|
||||
alias_box
|
||||
};
|
||||
let unknown_command =
|
||||
!cmd.trim().is_empty() && unknown_commands.contains(cmd.trim());
|
||||
let unknown_command = !cmd.trim().is_empty() && unknown_commands.contains(cmd.trim());
|
||||
let cmd_box = text_input(t!("command").as_ref(), cmd)
|
||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Command, value: v })
|
||||
.on_input(move |v| Message::AliasEditorInput {
|
||||
idx,
|
||||
field: AliasField::Command,
|
||||
value: v,
|
||||
})
|
||||
.size(13)
|
||||
.padding([3, 6])
|
||||
.width(sizing.width);
|
||||
@@ -261,9 +271,15 @@ pub fn view_window<'a>(
|
||||
Space::new().height(6),
|
||||
conflict_banner,
|
||||
Space::new().height(4),
|
||||
row![stats, add_area, reset_area, Space::new().width(sizing.width), apply_area]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
stats,
|
||||
add_area,
|
||||
reset_area,
|
||||
Space::new().width(sizing.width),
|
||||
apply_area
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(sizing.width)
|
||||
@@ -273,9 +289,7 @@ pub fn view_window<'a>(
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -290,12 +304,7 @@ pub fn view_window<'a>(
|
||||
.height(Length::Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.strongest
|
||||
.color
|
||||
.scale_alpha(0.55),
|
||||
theme.palette().background.strongest.color.scale_alpha(0.55),
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -323,9 +332,7 @@ pub fn view_window<'a>(
|
||||
.padding(16)
|
||||
.width(Length::Fixed(320.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
border: iced::Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
|
||||
+648
-123
@@ -8,7 +8,9 @@ use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
|
||||
use codec::types::Handle;
|
||||
use iced::widget::{button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Border, Element, Fill, Length, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
@@ -348,14 +350,46 @@ impl Default for DataExtractionState {
|
||||
selection_handles: Vec::new(),
|
||||
objects: Vec::new(),
|
||||
properties: vec![
|
||||
ExtractionProperty { key: "type".into(), name: "Type".into(), category: "General".into(), checked: true },
|
||||
ExtractionProperty { key: "handle".into(), name: "Handle".into(), category: "General".into(), checked: true },
|
||||
ExtractionProperty { key: "layer".into(), name: "Layer".into(), category: "General".into(), checked: true },
|
||||
ExtractionProperty { key: "color".into(), name: "Color".into(), category: "General".into(), checked: true },
|
||||
ExtractionProperty { key: "linetype".into(), name: "Linetype".into(), category: "General".into(), checked: true },
|
||||
ExtractionProperty { key: "details".into(), name: "Details".into(), category: "Geometry".into(), checked: true },
|
||||
ExtractionProperty {
|
||||
key: "type".into(),
|
||||
name: "Type".into(),
|
||||
category: "General".into(),
|
||||
checked: true,
|
||||
},
|
||||
ExtractionProperty {
|
||||
key: "handle".into(),
|
||||
name: "Handle".into(),
|
||||
category: "General".into(),
|
||||
checked: true,
|
||||
},
|
||||
ExtractionProperty {
|
||||
key: "layer".into(),
|
||||
name: "Layer".into(),
|
||||
category: "General".into(),
|
||||
checked: true,
|
||||
},
|
||||
ExtractionProperty {
|
||||
key: "color".into(),
|
||||
name: "Color".into(),
|
||||
category: "General".into(),
|
||||
checked: true,
|
||||
},
|
||||
ExtractionProperty {
|
||||
key: "linetype".into(),
|
||||
name: "Linetype".into(),
|
||||
category: "General".into(),
|
||||
checked: true,
|
||||
},
|
||||
ExtractionProperty {
|
||||
key: "details".into(),
|
||||
name: "Details".into(),
|
||||
category: "Geometry".into(),
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
categories: ["General".to_string(), "Geometry".to_string()].into_iter().collect(),
|
||||
categories: ["General".to_string(), "Geometry".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
combine_identical: false,
|
||||
show_count: true,
|
||||
show_name: true,
|
||||
@@ -402,15 +436,32 @@ fn group<'a>(title: impl Into<String>, body: Element<'a, Message>) -> Element<'a
|
||||
.padding(9)
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
border: Border { width: 1.0, radius: 4.0.into(), color: theme.palette().background.strong.color },
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
color: theme.palette().background.strong.color,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn labeled_input<'a>(label: impl Into<String>, value: &'a str, msg: fn(String) -> Message) -> Element<'a, Message> {
|
||||
row![text(label.into()).size(11).style(muted).width(118), text_input("", value).on_input(msg).padding([4, 7]).size(12).width(Fill)]
|
||||
.spacing(8).align_y(iced::Center).into()
|
||||
fn labeled_input<'a>(
|
||||
label: impl Into<String>,
|
||||
value: &'a str,
|
||||
msg: fn(String) -> Message,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label.into()).size(11).style(muted).width(118),
|
||||
text_input("", value)
|
||||
.on_input(msg)
|
||||
.padding([4, 7])
|
||||
.size(12)
|
||||
.width(Fill)
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn cell_style_picker<'a>(
|
||||
@@ -422,7 +473,11 @@ fn cell_style_picker<'a>(
|
||||
text(label.into()).size(11).style(muted).width(118),
|
||||
pick_list(
|
||||
Some(selected.to_string()),
|
||||
vec!["Title".to_string(), "Header".to_string(), "Data".to_string()],
|
||||
vec![
|
||||
"Title".to_string(),
|
||||
"Header".to_string(),
|
||||
"Data".to_string()
|
||||
],
|
||||
|value| value.clone(),
|
||||
)
|
||||
.on_select(msg)
|
||||
@@ -435,114 +490,340 @@ fn cell_style_picker<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn radio_button<'a>(selected: bool, label: impl Into<String>, msg: Message) -> Element<'a, Message> {
|
||||
fn radio_button<'a>(
|
||||
selected: bool,
|
||||
label: impl Into<String>,
|
||||
msg: Message,
|
||||
) -> Element<'a, Message> {
|
||||
form_radio(label, true, selected.then_some(true), move |_| msg.clone()).into()
|
||||
}
|
||||
|
||||
fn preview_grid<'a>(rows: &'a [Vec<String>]) -> Element<'a, Message> {
|
||||
if rows.is_empty() {
|
||||
return text(t!("No preview available.")).size(11).style(muted).into();
|
||||
return text(t!("No preview available."))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.into();
|
||||
}
|
||||
let lines = rows.iter().take(8).map(|row| row.join(" | ")).collect::<Vec<_>>().join("\n");
|
||||
container(text(lines).size(11)).padding(6).width(Fill).into()
|
||||
let lines = rows
|
||||
.iter()
|
||||
.take(8)
|
||||
.map(|row| row.join(" | "))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
container(text(lines).size(11))
|
||||
.padding(6)
|
||||
.width(Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn table_insert_view(state: &TableInsertState, sizing: crate::ui::modal::ModalSizing) -> Element<'_, Message> {
|
||||
let style = pick_list(Some(state.style.clone()), state.styles.clone(), |value| value.clone()).on_select(Message::TableInsertStyle).padding([3, 6]).text_size(12).width(Fill);
|
||||
pub fn table_insert_view(
|
||||
state: &TableInsertState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'_, Message> {
|
||||
let style = pick_list(Some(state.style.clone()), state.styles.clone(), |value| {
|
||||
value.clone()
|
||||
})
|
||||
.on_select(Message::TableInsertStyle)
|
||||
.padding([3, 6])
|
||||
.text_size(12)
|
||||
.width(Fill);
|
||||
let source = column![
|
||||
radio_button(state.source == TableSource::Empty, t!("Start from empty table"), Message::TableInsertField(TableInsertField::Source(TableSource::Empty))),
|
||||
radio_button(state.source == TableSource::DataLink, t!("Start from data link"), Message::TableInsertField(TableInsertField::Source(TableSource::DataLink))),
|
||||
radio_button(state.source == TableSource::DataExtraction, t!("Start from data extraction"), Message::TableInsertField(TableInsertField::Source(TableSource::DataExtraction))),
|
||||
].spacing(1);
|
||||
radio_button(
|
||||
state.source == TableSource::Empty,
|
||||
t!("Start from empty table"),
|
||||
Message::TableInsertField(TableInsertField::Source(TableSource::Empty))
|
||||
),
|
||||
radio_button(
|
||||
state.source == TableSource::DataLink,
|
||||
t!("Start from data link"),
|
||||
Message::TableInsertField(TableInsertField::Source(TableSource::DataLink))
|
||||
),
|
||||
radio_button(
|
||||
state.source == TableSource::DataExtraction,
|
||||
t!("Start from data extraction"),
|
||||
Message::TableInsertField(TableInsertField::Source(TableSource::DataExtraction))
|
||||
),
|
||||
]
|
||||
.spacing(1);
|
||||
let link_picker: Element<'_, Message> = if state.source == TableSource::DataLink {
|
||||
row![
|
||||
text(t!("Data link")).size(11).style(muted).width(118),
|
||||
pick_list(state.link.clone(), state.links.clone(), |value| value.to_string()).on_select(|value| Message::TableInsertField(TableInsertField::Link(value))).padding([3, 6]).text_size(12).width(Fill),
|
||||
button(text(t!("Manage…")).size(11)).on_press(Message::DataLinkManagerOpen).padding([4, 8]),
|
||||
].spacing(8).align_y(iced::Center).into()
|
||||
} else { Space::new().height(0).into() };
|
||||
pick_list(state.link.clone(), state.links.clone(), |value| value
|
||||
.to_string())
|
||||
.on_select(|value| Message::TableInsertField(TableInsertField::Link(value)))
|
||||
.padding([3, 6])
|
||||
.text_size(12)
|
||||
.width(Fill),
|
||||
button(text(t!("Manage…")).size(11))
|
||||
.on_press(Message::DataLinkManagerOpen)
|
||||
.padding([4, 8]),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
};
|
||||
let insertion = column![
|
||||
radio_button(state.insertion == TableInsertion::Point, t!("Specify insertion point"), Message::TableInsertField(TableInsertField::Insertion(TableInsertion::Point))),
|
||||
radio_button(state.insertion == TableInsertion::Window, t!("Specify window"), Message::TableInsertField(TableInsertField::Insertion(TableInsertion::Window))),
|
||||
radio_button(
|
||||
state.insertion == TableInsertion::Point,
|
||||
t!("Specify insertion point"),
|
||||
Message::TableInsertField(TableInsertField::Insertion(TableInsertion::Point))
|
||||
),
|
||||
radio_button(
|
||||
state.insertion == TableInsertion::Window,
|
||||
t!("Specify window"),
|
||||
Message::TableInsertField(TableInsertField::Insertion(TableInsertion::Window))
|
||||
),
|
||||
];
|
||||
let settings = column![
|
||||
labeled_input(t!("Columns"), &state.columns, |v| Message::TableInsertField(TableInsertField::Columns(v))),
|
||||
labeled_input(t!("Column width"), &state.column_width, |v| Message::TableInsertField(TableInsertField::ColumnWidth(v))),
|
||||
labeled_input(t!("Data rows"), &state.data_rows, |v| Message::TableInsertField(TableInsertField::DataRows(v))),
|
||||
labeled_input(t!("Row height"), &state.row_height, |v| Message::TableInsertField(TableInsertField::RowHeight(v))),
|
||||
].spacing(5);
|
||||
labeled_input(
|
||||
t!("Columns"),
|
||||
&state.columns,
|
||||
|v| Message::TableInsertField(TableInsertField::Columns(v))
|
||||
),
|
||||
labeled_input(t!("Column width"), &state.column_width, |v| {
|
||||
Message::TableInsertField(TableInsertField::ColumnWidth(v))
|
||||
}),
|
||||
labeled_input(t!("Data rows"), &state.data_rows, |v| {
|
||||
Message::TableInsertField(TableInsertField::DataRows(v))
|
||||
}),
|
||||
labeled_input(t!("Row height"), &state.row_height, |v| {
|
||||
Message::TableInsertField(TableInsertField::RowHeight(v))
|
||||
}),
|
||||
]
|
||||
.spacing(5);
|
||||
let cell_styles = column![
|
||||
cell_style_picker(t!("First row"), &state.first_row_style, |v| Message::TableInsertField(TableInsertField::FirstRowStyle(v))),
|
||||
cell_style_picker(t!("Second row"), &state.second_row_style, |v| Message::TableInsertField(TableInsertField::SecondRowStyle(v))),
|
||||
cell_style_picker(t!("All other rows"), &state.other_row_style, |v| Message::TableInsertField(TableInsertField::OtherRowStyle(v))),
|
||||
].spacing(5);
|
||||
cell_style_picker(t!("First row"), &state.first_row_style, |v| {
|
||||
Message::TableInsertField(TableInsertField::FirstRowStyle(v))
|
||||
}),
|
||||
cell_style_picker(t!("Second row"), &state.second_row_style, |v| {
|
||||
Message::TableInsertField(TableInsertField::SecondRowStyle(v))
|
||||
}),
|
||||
cell_style_picker(t!("All other rows"), &state.other_row_style, |v| {
|
||||
Message::TableInsertField(TableInsertField::OtherRowStyle(v))
|
||||
}),
|
||||
]
|
||||
.spacing(5);
|
||||
column![
|
||||
group(t!("Table style"), row![style].into()),
|
||||
group(t!("Insert options"), column![source, link_picker].spacing(4).into()),
|
||||
row![group(t!("Insertion behavior"), insertion.into()), group(t!("Column & row settings"), settings.into())].spacing(8),
|
||||
group(
|
||||
t!("Insert options"),
|
||||
column![source, link_picker].spacing(4).into()
|
||||
),
|
||||
row![
|
||||
group(t!("Insertion behavior"), insertion.into()),
|
||||
group(t!("Column & row settings"), settings.into())
|
||||
]
|
||||
.spacing(8),
|
||||
group(t!("Set cell styles"), cell_styles.into()),
|
||||
checkbox(state.preview).label(t!("Preview")).on_toggle(|v| Message::TableInsertField(TableInsertField::Preview(v))).size(14),
|
||||
checkbox(state.preview)
|
||||
.label(t!("Preview"))
|
||||
.on_toggle(|v| Message::TableInsertField(TableInsertField::Preview(v)))
|
||||
.size(14),
|
||||
text(&state.error).size(11),
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
dialog_button(t!("Cancel"), Message::CloseModal, false),
|
||||
dialog_button(t!("OK"), Message::TableInsertApply, true),
|
||||
].spacing(8),
|
||||
].spacing(8).padding(10).width(sizing.width).height(sizing.height).into()
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(8)
|
||||
.padding(10)
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn data_link_view(state: &DataLinkManagerState, sizing: crate::ui::modal::ModalSizing) -> Element<'_, Message> {
|
||||
pub fn data_link_view(
|
||||
state: &DataLinkManagerState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'_, Message> {
|
||||
let mut links = column![text(t!("Spreadsheet Links")).size(11).style(muted)].spacing(2);
|
||||
links = links.push(button(text(t!("Create a New Data Link")).size(12)).on_press(Message::DataLinkNew).padding([5, 7]).style(button::text).width(Fill));
|
||||
links = links.push(
|
||||
button(text(t!("Create a New Data Link")).size(12))
|
||||
.on_press(Message::DataLinkNew)
|
||||
.padding([5, 7])
|
||||
.style(button::text)
|
||||
.width(Fill),
|
||||
);
|
||||
for link in &state.links {
|
||||
let selected = state.selected.as_ref().is_some_and(|v| v.handle == link.handle);
|
||||
let label = if selected { format!("● {link}") } else { format!(" {link}") };
|
||||
links = links.push(button(text(label).size(12)).on_press(Message::DataLinkSelect(link.handle)).padding([5, 7]).style(button::text).width(Fill));
|
||||
let selected = state
|
||||
.selected
|
||||
.as_ref()
|
||||
.is_some_and(|v| v.handle == link.handle);
|
||||
let label = if selected {
|
||||
format!("● {link}")
|
||||
} else {
|
||||
format!(" {link}")
|
||||
};
|
||||
links = links.push(
|
||||
button(text(label).size(12))
|
||||
.on_press(Message::DataLinkSelect(link.handle))
|
||||
.padding([5, 7])
|
||||
.style(button::text)
|
||||
.width(Fill),
|
||||
);
|
||||
}
|
||||
let tree = container(scrollable(links).height(Fill)).padding(6).width(Length::Fixed(220.0)).height(Fill);
|
||||
let tree = container(scrollable(links).height(Fill))
|
||||
.padding(6)
|
||||
.width(Length::Fixed(220.0))
|
||||
.height(Fill);
|
||||
let right: Element<'_, Message> = if state.editing {
|
||||
let mut editor = column![
|
||||
labeled_input(t!("Data link name"), &state.name, |v| Message::DataLinkField(DataLinkField::Name(v))),
|
||||
row![text(t!("File")).size(11).style(muted).width(118), text_input("", &state.path).on_input(|v| Message::DataLinkField(DataLinkField::Path(v))).padding([4, 7]).size(12).width(Fill), button(text("…")).on_press(Message::DataLinkBrowse).padding([4, 9])].spacing(6).align_y(iced::Center),
|
||||
row![text(t!("Path type")).size(11).style(muted).width(118), pick_list(Some(state.path_type), vec![LinkPathType::Full, LinkPathType::Relative, LinkPathType::FileName], |value| value.to_string()).on_select(|value| Message::DataLinkField(DataLinkField::PathType(value))).width(Fill).padding([3, 6])].spacing(8),
|
||||
row![text(t!("Link option")).size(11).style(muted).width(118), pick_list(Some(state.range_kind), vec![LinkRange::EntireSheet, LinkRange::NamedRange, LinkRange::CellRange], |value| value.to_string()).on_select(|value| Message::DataLinkField(DataLinkField::RangeKind(value))).width(Fill).padding([3, 6])].spacing(8),
|
||||
].spacing(6);
|
||||
labeled_input(t!("Data link name"), &state.name, |v| {
|
||||
Message::DataLinkField(DataLinkField::Name(v))
|
||||
}),
|
||||
row![
|
||||
text(t!("File")).size(11).style(muted).width(118),
|
||||
text_input("", &state.path)
|
||||
.on_input(|v| Message::DataLinkField(DataLinkField::Path(v)))
|
||||
.padding([4, 7])
|
||||
.size(12)
|
||||
.width(Fill),
|
||||
button(text("…"))
|
||||
.on_press(Message::DataLinkBrowse)
|
||||
.padding([4, 9])
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text(t!("Path type")).size(11).style(muted).width(118),
|
||||
pick_list(
|
||||
Some(state.path_type),
|
||||
vec![
|
||||
LinkPathType::Full,
|
||||
LinkPathType::Relative,
|
||||
LinkPathType::FileName
|
||||
],
|
||||
|value| value.to_string()
|
||||
)
|
||||
.on_select(|value| Message::DataLinkField(DataLinkField::PathType(value)))
|
||||
.width(Fill)
|
||||
.padding([3, 6])
|
||||
]
|
||||
.spacing(8),
|
||||
row![
|
||||
text(t!("Link option")).size(11).style(muted).width(118),
|
||||
pick_list(
|
||||
Some(state.range_kind),
|
||||
vec![
|
||||
LinkRange::EntireSheet,
|
||||
LinkRange::NamedRange,
|
||||
LinkRange::CellRange
|
||||
],
|
||||
|value| value.to_string()
|
||||
)
|
||||
.on_select(|value| Message::DataLinkField(DataLinkField::RangeKind(value)))
|
||||
.width(Fill)
|
||||
.padding([3, 6])
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(6);
|
||||
if !state.sheets.is_empty() && state.range_kind != LinkRange::NamedRange {
|
||||
editor = editor.push(row![
|
||||
text(t!("Sheet")).size(11).style(muted).width(118),
|
||||
pick_list(Some(state.sheet.clone()), state.sheets.clone(), |value| value.clone())
|
||||
editor = editor.push(
|
||||
row![
|
||||
text(t!("Sheet")).size(11).style(muted).width(118),
|
||||
pick_list(Some(state.sheet.clone()), state.sheets.clone(), |value| {
|
||||
value.clone()
|
||||
})
|
||||
.on_select(|value| Message::DataLinkField(DataLinkField::Sheet(value)))
|
||||
.width(Fill)
|
||||
.padding([3, 6]),
|
||||
].spacing(8));
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
}
|
||||
if state.range_kind == LinkRange::NamedRange && !state.named_ranges.is_empty() {
|
||||
editor = editor.push(row![
|
||||
text(t!("Named range")).size(11).style(muted).width(118),
|
||||
pick_list(Some(state.range.clone()), state.named_ranges.clone(), |value| value.clone())
|
||||
editor = editor.push(
|
||||
row![
|
||||
text(t!("Named range")).size(11).style(muted).width(118),
|
||||
pick_list(
|
||||
Some(state.range.clone()),
|
||||
state.named_ranges.clone(),
|
||||
|value| value.clone()
|
||||
)
|
||||
.on_select(|value| Message::DataLinkField(DataLinkField::Range(value)))
|
||||
.width(Fill)
|
||||
.padding([3, 6]),
|
||||
].spacing(8));
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
} else if state.range_kind != LinkRange::EntireSheet {
|
||||
editor = editor.push(labeled_input(t!("Range"), &state.range, |v| Message::DataLinkField(DataLinkField::Range(v))));
|
||||
editor = editor.push(labeled_input(t!("Range"), &state.range, |v| {
|
||||
Message::DataLinkField(DataLinkField::Range(v))
|
||||
}));
|
||||
}
|
||||
editor = editor.push(checkbox(state.more_options).label(t!("More options")).on_toggle(|v| Message::DataLinkField(DataLinkField::MoreOptions(v))).size(14));
|
||||
editor = editor.push(
|
||||
checkbox(state.more_options)
|
||||
.label(t!("More options"))
|
||||
.on_toggle(|v| Message::DataLinkField(DataLinkField::MoreOptions(v)))
|
||||
.size(14),
|
||||
);
|
||||
if state.more_options {
|
||||
editor = editor
|
||||
.push(checkbox(state.allow_write).label(t!("Allow writing to source file")).on_toggle(|v| Message::DataLinkField(DataLinkField::AllowWrite(v))).size(14))
|
||||
.push(checkbox(state.use_source_formatting).label(t!("Use source formatting")).on_toggle(|v| Message::DataLinkField(DataLinkField::UseSourceFormatting(v))).size(14))
|
||||
.push(checkbox(state.update_source_formatting).label(t!("Keep table updated to source formatting")).on_toggle(|v| Message::DataLinkField(DataLinkField::UpdateSourceFormatting(v))).size(14));
|
||||
.push(
|
||||
checkbox(state.allow_write)
|
||||
.label(t!("Allow writing to source file"))
|
||||
.on_toggle(|v| Message::DataLinkField(DataLinkField::AllowWrite(v)))
|
||||
.size(14),
|
||||
)
|
||||
.push(
|
||||
checkbox(state.use_source_formatting)
|
||||
.label(t!("Use source formatting"))
|
||||
.on_toggle(|v| {
|
||||
Message::DataLinkField(DataLinkField::UseSourceFormatting(v))
|
||||
})
|
||||
.size(14),
|
||||
)
|
||||
.push(
|
||||
checkbox(state.update_source_formatting)
|
||||
.label(t!("Keep table updated to source formatting"))
|
||||
.on_toggle(|v| {
|
||||
Message::DataLinkField(DataLinkField::UpdateSourceFormatting(v))
|
||||
})
|
||||
.size(14),
|
||||
);
|
||||
}
|
||||
editor = editor.push(checkbox(state.insert_table).label(t!("Insert table after saving link")).on_toggle(|v| Message::DataLinkField(DataLinkField::InsertTable(v))).size(14));
|
||||
editor = editor.push(
|
||||
checkbox(state.insert_table)
|
||||
.label(t!("Insert table after saving link"))
|
||||
.on_toggle(|v| Message::DataLinkField(DataLinkField::InsertTable(v)))
|
||||
.size(14),
|
||||
);
|
||||
group(t!("Link settings"), editor.into())
|
||||
} else if let Some(link) = &state.selected {
|
||||
column![
|
||||
group(t!("Details"), column![text(format!("{}: {}", t!("Name"), link.name)).size(11), text(format!("{}: {}", t!("File"), link.path)).size(11), text(if link.valid { t!("Status: Linked") } else { t!("Status: Source not found") }).size(11)].spacing(5).into()),
|
||||
group(
|
||||
t!("Details"),
|
||||
column![
|
||||
text(format!("{}: {}", t!("Name"), link.name)).size(11),
|
||||
text(format!("{}: {}", t!("File"), link.path)).size(11),
|
||||
text(if link.valid {
|
||||
t!("Status: Linked")
|
||||
} else {
|
||||
t!("Status: Source not found")
|
||||
})
|
||||
.size(11)
|
||||
]
|
||||
.spacing(5)
|
||||
.into()
|
||||
),
|
||||
group(t!("Preview"), preview_grid(&state.preview)),
|
||||
].spacing(8).into()
|
||||
]
|
||||
.spacing(8)
|
||||
.into()
|
||||
} else {
|
||||
group(t!("Details"), text(t!("Select a data link or create a new one.")).size(11).style(muted).into())
|
||||
group(
|
||||
t!("Details"),
|
||||
text(t!("Select a data link or create a new one."))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.into(),
|
||||
)
|
||||
};
|
||||
let actions = if state.editing {
|
||||
row![
|
||||
@@ -553,14 +834,36 @@ pub fn data_link_view(state: &DataLinkManagerState, sizing: crate::ui::modal::Mo
|
||||
.spacing(8)
|
||||
} else {
|
||||
let enabled = state.selected.is_some();
|
||||
let edit = dialog_button_styled_opt(t!("Edit"), enabled.then_some(Message::DataLinkEdit), button::secondary);
|
||||
let delete = dialog_button_styled_opt(t!("Delete"), enabled.then_some(Message::DataLinkDelete), button::secondary);
|
||||
let insert = dialog_button_styled_opt(t!("Insert Table"), enabled.then_some(Message::DataLinkInsert), button::primary);
|
||||
let edit = dialog_button_styled_opt(
|
||||
t!("Edit"),
|
||||
enabled.then_some(Message::DataLinkEdit),
|
||||
button::secondary,
|
||||
);
|
||||
let delete = dialog_button_styled_opt(
|
||||
t!("Delete"),
|
||||
enabled.then_some(Message::DataLinkDelete),
|
||||
button::secondary,
|
||||
);
|
||||
let insert = dialog_button_styled_opt(
|
||||
t!("Insert Table"),
|
||||
enabled.then_some(Message::DataLinkInsert),
|
||||
button::primary,
|
||||
);
|
||||
let close = dialog_button(t!("Close"), Message::DataLinkClose, false);
|
||||
row![edit, delete, Space::new().width(Fill), insert, close].spacing(8)
|
||||
};
|
||||
column![row![tree, container(right).width(Fill).height(Fill)].spacing(8).height(Fill), text(&state.status).size(11), actions]
|
||||
.spacing(7).padding(10).width(sizing.width).height(sizing.height).into()
|
||||
column![
|
||||
row![tree, container(right).width(Fill).height(Fill)]
|
||||
.spacing(8)
|
||||
.height(Fill),
|
||||
text(&state.status).size(11),
|
||||
actions
|
||||
]
|
||||
.spacing(7)
|
||||
.padding(10)
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn wizard_nav(state: &DataExtractionState) -> iced::widget::Row<'_, Message> {
|
||||
@@ -576,45 +879,132 @@ fn wizard_nav(state: &DataExtractionState) -> iced::widget::Row<'_, Message> {
|
||||
} else {
|
||||
t!("Next")
|
||||
},
|
||||
state.error.is_empty().then_some(if state.page == ExtractionPage::Finish {
|
||||
Message::DataExtractionFinish
|
||||
} else {
|
||||
Message::DataExtractionNext
|
||||
}),
|
||||
state
|
||||
.error
|
||||
.is_empty()
|
||||
.then_some(if state.page == ExtractionPage::Finish {
|
||||
Message::DataExtractionFinish
|
||||
} else {
|
||||
Message::DataExtractionNext
|
||||
}),
|
||||
button::primary,
|
||||
);
|
||||
row![Space::new().width(Fill), back, cancel, next].spacing(8)
|
||||
}
|
||||
|
||||
pub fn data_extraction_view(state: &DataExtractionState, sizing: crate::ui::modal::ModalSizing) -> Element<'_, Message> {
|
||||
pub fn data_extraction_view(
|
||||
state: &DataExtractionState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'_, Message> {
|
||||
let add_folder = button(text(t!("Add folder…")).size(12)).padding([5, 10]);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let add_folder = add_folder.on_press(Message::DataExtractionAddFolder);
|
||||
|
||||
let body: Element<'_, Message> = match state.page {
|
||||
ExtractionPage::Begin => group(t!("Begin"), column![
|
||||
radio_button(state.begin == ExtractionBegin::New, t!("Create a new data extraction"), Message::DataExtractionField(DataExtractionField::Begin(ExtractionBegin::New))),
|
||||
radio_button(state.begin == ExtractionBegin::Template, t!("Use a previous extraction as a template"), Message::DataExtractionField(DataExtractionField::Begin(ExtractionBegin::Template))),
|
||||
radio_button(state.begin == ExtractionBegin::Edit, t!("Edit an existing data extraction"), Message::DataExtractionField(DataExtractionField::Begin(ExtractionBegin::Edit))),
|
||||
row![text(t!("Extraction settings")).size(11).style(muted).width(118), text_input("", &state.settings_path).on_input(|v| Message::DataExtractionField(DataExtractionField::SettingsPath(v))).padding([4, 7]).size(12).width(Fill), button(text("…")).on_press(Message::DataExtractionBrowseSettings).padding([4, 9])].spacing(6).align_y(iced::Center),
|
||||
].spacing(5).into()),
|
||||
ExtractionPage::Source => group(t!("Define data source"), column![
|
||||
radio_button(state.source == ExtractionSource::CurrentDrawing, t!("Current drawing"), Message::DataExtractionField(DataExtractionField::Source(ExtractionSource::CurrentDrawing))),
|
||||
radio_button(state.source == ExtractionSource::CurrentSelection, t!("Select objects in the current drawing"), Message::DataExtractionField(DataExtractionField::Source(ExtractionSource::CurrentSelection))),
|
||||
radio_button(state.source == ExtractionSource::DrawingsAndFolders, t!("Drawings / folders"), Message::DataExtractionField(DataExtractionField::Source(ExtractionSource::DrawingsAndFolders))),
|
||||
checkbox(state.include_current).label(t!("Include current drawing")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::IncludeCurrent(v))).size(14),
|
||||
checkbox(state.include_subfolders).label(t!("Include subfolders")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::IncludeSubfolders(v))).size(14),
|
||||
row![
|
||||
button(text(t!("Add drawings…")).size(12)).on_press(Message::DataExtractionAddDrawings).padding([5, 10]),
|
||||
add_folder,
|
||||
button(text(t!("Clear")).size(12)).on_press(Message::DataExtractionClearSources).padding([5, 10]),
|
||||
].spacing(6),
|
||||
text(state.source_files.join("\n")).size(10),
|
||||
].spacing(6).into()),
|
||||
ExtractionPage::Begin => group(
|
||||
t!("Begin"),
|
||||
column![
|
||||
radio_button(
|
||||
state.begin == ExtractionBegin::New,
|
||||
t!("Create a new data extraction"),
|
||||
Message::DataExtractionField(DataExtractionField::Begin(ExtractionBegin::New))
|
||||
),
|
||||
radio_button(
|
||||
state.begin == ExtractionBegin::Template,
|
||||
t!("Use a previous extraction as a template"),
|
||||
Message::DataExtractionField(DataExtractionField::Begin(
|
||||
ExtractionBegin::Template
|
||||
))
|
||||
),
|
||||
radio_button(
|
||||
state.begin == ExtractionBegin::Edit,
|
||||
t!("Edit an existing data extraction"),
|
||||
Message::DataExtractionField(DataExtractionField::Begin(ExtractionBegin::Edit))
|
||||
),
|
||||
row![
|
||||
text(t!("Extraction settings"))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.width(118),
|
||||
text_input("", &state.settings_path)
|
||||
.on_input(|v| Message::DataExtractionField(
|
||||
DataExtractionField::SettingsPath(v)
|
||||
))
|
||||
.padding([4, 7])
|
||||
.size(12)
|
||||
.width(Fill),
|
||||
button(text("…"))
|
||||
.on_press(Message::DataExtractionBrowseSettings)
|
||||
.padding([4, 9])
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(5)
|
||||
.into(),
|
||||
),
|
||||
ExtractionPage::Source => group(
|
||||
t!("Define data source"),
|
||||
column![
|
||||
radio_button(
|
||||
state.source == ExtractionSource::CurrentDrawing,
|
||||
t!("Current drawing"),
|
||||
Message::DataExtractionField(DataExtractionField::Source(
|
||||
ExtractionSource::CurrentDrawing
|
||||
))
|
||||
),
|
||||
radio_button(
|
||||
state.source == ExtractionSource::CurrentSelection,
|
||||
t!("Select objects in the current drawing"),
|
||||
Message::DataExtractionField(DataExtractionField::Source(
|
||||
ExtractionSource::CurrentSelection
|
||||
))
|
||||
),
|
||||
radio_button(
|
||||
state.source == ExtractionSource::DrawingsAndFolders,
|
||||
t!("Drawings / folders"),
|
||||
Message::DataExtractionField(DataExtractionField::Source(
|
||||
ExtractionSource::DrawingsAndFolders
|
||||
))
|
||||
),
|
||||
checkbox(state.include_current)
|
||||
.label(t!("Include current drawing"))
|
||||
.on_toggle(|v| Message::DataExtractionField(
|
||||
DataExtractionField::IncludeCurrent(v)
|
||||
))
|
||||
.size(14),
|
||||
checkbox(state.include_subfolders)
|
||||
.label(t!("Include subfolders"))
|
||||
.on_toggle(|v| Message::DataExtractionField(
|
||||
DataExtractionField::IncludeSubfolders(v)
|
||||
))
|
||||
.size(14),
|
||||
row![
|
||||
button(text(t!("Add drawings…")).size(12))
|
||||
.on_press(Message::DataExtractionAddDrawings)
|
||||
.padding([5, 10]),
|
||||
add_folder,
|
||||
button(text(t!("Clear")).size(12))
|
||||
.on_press(Message::DataExtractionClearSources)
|
||||
.padding([5, 10]),
|
||||
]
|
||||
.spacing(6),
|
||||
text(state.source_files.join("\n")).size(10),
|
||||
]
|
||||
.spacing(6)
|
||||
.into(),
|
||||
),
|
||||
ExtractionPage::Objects => {
|
||||
let mut list = column![].spacing(3);
|
||||
for (i, item) in state.objects.iter().enumerate() {
|
||||
list = list.push(checkbox(item.checked).label(format!("{} ({})", item.name, item.count)).on_toggle(move |v| Message::DataExtractionField(DataExtractionField::Object(i, v))).size(14));
|
||||
list = list.push(
|
||||
checkbox(item.checked)
|
||||
.label(format!("{} ({})", item.name, item.count))
|
||||
.on_toggle(move |v| {
|
||||
Message::DataExtractionField(DataExtractionField::Object(i, v))
|
||||
})
|
||||
.size(14),
|
||||
);
|
||||
}
|
||||
group(t!("Select objects"), scrollable(list).height(Fill).into())
|
||||
}
|
||||
@@ -623,34 +1013,169 @@ pub fn data_extraction_view(state: &DataExtractionState, sizing: crate::ui::moda
|
||||
for category in ["General", "Geometry"] {
|
||||
let checked = state.categories.contains(category);
|
||||
let key = category.to_string();
|
||||
categories = categories.push(checkbox(checked).label(category).on_toggle(move |v| Message::DataExtractionField(DataExtractionField::Category(key.clone(), v))).size(14));
|
||||
categories = categories.push(
|
||||
checkbox(checked)
|
||||
.label(category)
|
||||
.on_toggle(move |v| {
|
||||
Message::DataExtractionField(DataExtractionField::Category(
|
||||
key.clone(),
|
||||
v,
|
||||
))
|
||||
})
|
||||
.size(14),
|
||||
);
|
||||
}
|
||||
let mut list = column![categories].spacing(4);
|
||||
for (i, item) in state.properties.iter().enumerate().filter(|(_, p)| state.categories.contains(&p.category)) {
|
||||
list = list.push(checkbox(item.checked).label(format!("{} · {}", item.name, item.category)).on_toggle(move |v| Message::DataExtractionField(DataExtractionField::Property(i, v))).size(14));
|
||||
for (i, item) in state
|
||||
.properties
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, p)| state.categories.contains(&p.category))
|
||||
{
|
||||
list = list.push(
|
||||
checkbox(item.checked)
|
||||
.label(format!("{} · {}", item.name, item.category))
|
||||
.on_toggle(move |v| {
|
||||
Message::DataExtractionField(DataExtractionField::Property(i, v))
|
||||
})
|
||||
.size(14),
|
||||
);
|
||||
}
|
||||
group(t!("Select properties"), scrollable(list).height(Fill).into())
|
||||
group(
|
||||
t!("Select properties"),
|
||||
scrollable(list).height(Fill).into(),
|
||||
)
|
||||
}
|
||||
ExtractionPage::Refine => group(t!("Refine data"), column![
|
||||
row![checkbox(state.combine_identical).label(t!("Combine identical rows")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::CombineIdentical(v))).size(14), checkbox(state.show_count).label(t!("Show count column")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::ShowCount(v))).size(14), checkbox(state.show_name).label(t!("Show name column")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::ShowName(v))).size(14)].spacing(12),
|
||||
scrollable(preview_grid(&state.preview)).height(Fill),
|
||||
].spacing(8).into()),
|
||||
ExtractionPage::Output => group(t!("Choose output"), column![
|
||||
checkbox(state.output_table).label(t!("Insert data extraction table into drawing")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::OutputTable(v))).size(14),
|
||||
checkbox(state.output_file).label(t!("Output data to external file")).on_toggle(|v| Message::DataExtractionField(DataExtractionField::OutputFile(v))).size(14),
|
||||
row![text(t!("Output file")).size(11).style(muted).width(118), text_input("", &state.output_path).on_input(|v| Message::DataExtractionField(DataExtractionField::OutputPath(v))).padding([4, 7]).size(12).width(Fill), button(text("…")).on_press(Message::DataExtractionBrowseOutput).padding([4, 9])].spacing(6).align_y(iced::Center),
|
||||
].spacing(7).into()),
|
||||
ExtractionPage::TableStyle => group(t!("Table style"), column![
|
||||
row![text(t!("Style")).size(11).style(muted).width(118), pick_list(Some(state.table_style.clone()), state.table_styles.clone(), |value| value.clone()).on_select(|value| Message::DataExtractionField(DataExtractionField::TableStyle(value))).padding([3, 6]).width(Fill)].spacing(8),
|
||||
labeled_input(t!("Title"), &state.table_title, |v| Message::DataExtractionField(DataExtractionField::TableTitle(v))),
|
||||
preview_grid(&state.preview),
|
||||
].spacing(7).into()),
|
||||
ExtractionPage::Finish => group(t!("Finish"), column![text(t!("The selected drawing data is ready to be extracted.")).size(12), text(format!("{}: {}", t!("Rows"), state.preview.len().saturating_sub(1))).size(11), text(format!("{}: {}{}", t!("Output"), if state.output_table { t!("Drawing table") } else { "".into() }, if state.output_file { format!(" {}", t!("External file")) } else { String::new() })).size(11)].spacing(7).into()),
|
||||
ExtractionPage::Refine => group(
|
||||
t!("Refine data"),
|
||||
column![
|
||||
row![
|
||||
checkbox(state.combine_identical)
|
||||
.label(t!("Combine identical rows"))
|
||||
.on_toggle(|v| Message::DataExtractionField(
|
||||
DataExtractionField::CombineIdentical(v)
|
||||
))
|
||||
.size(14),
|
||||
checkbox(state.show_count)
|
||||
.label(t!("Show count column"))
|
||||
.on_toggle(|v| Message::DataExtractionField(
|
||||
DataExtractionField::ShowCount(v)
|
||||
))
|
||||
.size(14),
|
||||
checkbox(state.show_name)
|
||||
.label(t!("Show name column"))
|
||||
.on_toggle(
|
||||
|v| Message::DataExtractionField(DataExtractionField::ShowName(v))
|
||||
)
|
||||
.size(14)
|
||||
]
|
||||
.spacing(12),
|
||||
scrollable(preview_grid(&state.preview)).height(Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.into(),
|
||||
),
|
||||
ExtractionPage::Output => group(
|
||||
t!("Choose output"),
|
||||
column![
|
||||
checkbox(state.output_table)
|
||||
.label(t!("Insert data extraction table into drawing"))
|
||||
.on_toggle(
|
||||
|v| Message::DataExtractionField(DataExtractionField::OutputTable(v))
|
||||
)
|
||||
.size(14),
|
||||
checkbox(state.output_file)
|
||||
.label(t!("Output data to external file"))
|
||||
.on_toggle(|v| Message::DataExtractionField(DataExtractionField::OutputFile(v)))
|
||||
.size(14),
|
||||
row![
|
||||
text(t!("Output file")).size(11).style(muted).width(118),
|
||||
text_input("", &state.output_path)
|
||||
.on_input(|v| Message::DataExtractionField(
|
||||
DataExtractionField::OutputPath(v)
|
||||
))
|
||||
.padding([4, 7])
|
||||
.size(12)
|
||||
.width(Fill),
|
||||
button(text("…"))
|
||||
.on_press(Message::DataExtractionBrowseOutput)
|
||||
.padding([4, 9])
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(7)
|
||||
.into(),
|
||||
),
|
||||
ExtractionPage::TableStyle => group(
|
||||
t!("Table style"),
|
||||
column![
|
||||
row![
|
||||
text(t!("Style")).size(11).style(muted).width(118),
|
||||
pick_list(
|
||||
Some(state.table_style.clone()),
|
||||
state.table_styles.clone(),
|
||||
|value| value.clone()
|
||||
)
|
||||
.on_select(|value| Message::DataExtractionField(
|
||||
DataExtractionField::TableStyle(value)
|
||||
))
|
||||
.padding([3, 6])
|
||||
.width(Fill)
|
||||
]
|
||||
.spacing(8),
|
||||
labeled_input(t!("Title"), &state.table_title, |v| {
|
||||
Message::DataExtractionField(DataExtractionField::TableTitle(v))
|
||||
}),
|
||||
preview_grid(&state.preview),
|
||||
]
|
||||
.spacing(7)
|
||||
.into(),
|
||||
),
|
||||
ExtractionPage::Finish => group(
|
||||
t!("Finish"),
|
||||
column![
|
||||
text(t!("The selected drawing data is ready to be extracted.")).size(12),
|
||||
text(format!(
|
||||
"{}: {}",
|
||||
t!("Rows"),
|
||||
state.preview.len().saturating_sub(1)
|
||||
))
|
||||
.size(11),
|
||||
text(format!(
|
||||
"{}: {}{}",
|
||||
t!("Output"),
|
||||
if state.output_table {
|
||||
t!("Drawing table")
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
if state.output_file {
|
||||
format!(" {}", t!("External file"))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
))
|
||||
.size(11)
|
||||
]
|
||||
.spacing(7)
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
column![
|
||||
text(format!("{} {}/8", t!("Data Extraction"), state.page.number())).size(13),
|
||||
text(format!(
|
||||
"{} {}/8",
|
||||
t!("Data Extraction"),
|
||||
state.page.number()
|
||||
))
|
||||
.size(13),
|
||||
container(body).height(Fill).width(Fill),
|
||||
text(&state.error).size(11),
|
||||
wizard_nav(state),
|
||||
].spacing(8).padding(10).width(sizing.width).height(sizing.height).into()
|
||||
]
|
||||
.spacing(8)
|
||||
.padding(10)
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
}
|
||||
+177
-50
@@ -103,21 +103,39 @@ fn msg(m: AttdefDialogMsg) -> Message {
|
||||
}
|
||||
|
||||
fn card<'a>(title: String, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
container(column![text(title.to_uppercase()).size(10).style(accent_text), content.into()].spacing(8))
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
container(
|
||||
column![
|
||||
text(title.to_uppercase()).size(10).style(accent_text),
|
||||
content.into()
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn labelled<'a>(label: String, field: Element<'a, Message>, enabled: bool) -> Element<'a, Message> {
|
||||
let label = text(label).size(12).width(Length::Fixed(118.0));
|
||||
let label = if enabled { label } else { label.style(muted_style) };
|
||||
let label = if enabled {
|
||||
label
|
||||
} else {
|
||||
label.style(muted_style)
|
||||
};
|
||||
row![label, field].spacing(8).align_y(iced::Center).into()
|
||||
}
|
||||
|
||||
fn input<'a>(value: &str, enabled: bool, on: fn(String) -> AttdefDialogMsg) -> Element<'a, Message> {
|
||||
let mut field = text_input("", value).size(12).padding([5, 8]).width(Fill).style(field_style);
|
||||
fn input<'a>(
|
||||
value: &str,
|
||||
enabled: bool,
|
||||
on: fn(String) -> AttdefDialogMsg,
|
||||
) -> Element<'a, Message> {
|
||||
let mut field = text_input("", value)
|
||||
.size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.style(field_style);
|
||||
if enabled {
|
||||
field = field.on_input(move |v| msg(on(v)));
|
||||
}
|
||||
@@ -138,7 +156,12 @@ fn pick<'a>(
|
||||
Some(m) => b.on_press(msg(m)),
|
||||
None => b,
|
||||
};
|
||||
iced::widget::tooltip(b, text(tip).size(11), iced::widget::tooltip::Position::Bottom).into()
|
||||
iced::widget::tooltip(
|
||||
b,
|
||||
text(tip).size(11),
|
||||
iced::widget::tooltip::Position::Bottom,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn mode<'a>(label: String, on: bool, bit: u8) -> Element<'a, Message> {
|
||||
@@ -170,8 +193,13 @@ fn error_band<'a>(error: &Option<String>) -> Option<Element<'a, Message>> {
|
||||
.padding([6, 10])
|
||||
.width(Fill)
|
||||
.style(|theme: &iced::Theme| container::Style {
|
||||
background: Some(iced::Background::Color(theme.palette().danger.base.color.scale_alpha(0.18))),
|
||||
border: iced::Border { radius: 4.0.into(), ..Default::default() },
|
||||
background: Some(iced::Background::Color(
|
||||
theme.palette().danger.base.color.scale_alpha(0.18),
|
||||
)),
|
||||
border: iced::Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into(),
|
||||
@@ -200,30 +228,61 @@ fn footer<'a>(action: std::borrow::Cow<'static, str>, ok: AttdefDialogMsg) -> El
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSizing) -> Element<'a, Message> {
|
||||
pub fn view<'a>(
|
||||
state: &'a AttdefDialogState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let f = state.aflags;
|
||||
let constant = f & AFLAG_CONSTANT != 0;
|
||||
let multiline = f & AFLAG_MULTILINE != 0;
|
||||
let modes = card(
|
||||
t!("Mode").into_owned(),
|
||||
column![
|
||||
mode(t!("Invisible").into_owned(), f & AFLAG_INVISIBLE != 0, AFLAG_INVISIBLE),
|
||||
mode(
|
||||
t!("Invisible").into_owned(),
|
||||
f & AFLAG_INVISIBLE != 0,
|
||||
AFLAG_INVISIBLE
|
||||
),
|
||||
mode(t!("Constant").into_owned(), constant, AFLAG_CONSTANT),
|
||||
mode(t!("Verify").into_owned(), f & AFLAG_VERIFY != 0, AFLAG_VERIFY),
|
||||
mode(t!("Preset").into_owned(), f & AFLAG_PRESET != 0, AFLAG_PRESET),
|
||||
mode(t!("Lock position").into_owned(), f & AFLAG_LOCK != 0, AFLAG_LOCK),
|
||||
mode(t!("Multiple lines").into_owned(), multiline, AFLAG_MULTILINE),
|
||||
mode(
|
||||
t!("Verify").into_owned(),
|
||||
f & AFLAG_VERIFY != 0,
|
||||
AFLAG_VERIFY
|
||||
),
|
||||
mode(
|
||||
t!("Preset").into_owned(),
|
||||
f & AFLAG_PRESET != 0,
|
||||
AFLAG_PRESET
|
||||
),
|
||||
mode(
|
||||
t!("Lock position").into_owned(),
|
||||
f & AFLAG_LOCK != 0,
|
||||
AFLAG_LOCK
|
||||
),
|
||||
mode(
|
||||
t!("Multiple lines").into_owned(),
|
||||
multiline,
|
||||
AFLAG_MULTILINE
|
||||
),
|
||||
]
|
||||
.spacing(7),
|
||||
);
|
||||
let coords_on = !state.on_screen && !state.align_below;
|
||||
let coord = |i: usize, axis: &'static str| -> Element<'a, Message> {
|
||||
let mut field = text_input("", &state.coords[i]).size(12).padding([5, 8]).width(Fill).style(field_style);
|
||||
let mut field = text_input("", &state.coords[i])
|
||||
.size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.style(field_style);
|
||||
if coords_on {
|
||||
field = field.on_input(move |v| msg(AttdefDialogMsg::Coord(i, v)));
|
||||
}
|
||||
let label = text(axis).size(12).width(Length::Fixed(14.0));
|
||||
let label = if coords_on { label } else { label.style(muted_style) };
|
||||
let label = if coords_on {
|
||||
label
|
||||
} else {
|
||||
label.style(muted_style)
|
||||
};
|
||||
row![label, field].spacing(8).align_y(iced::Center).into()
|
||||
};
|
||||
let mut on_screen = checkbox(state.on_screen)
|
||||
@@ -240,7 +299,11 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
let attribute = card(
|
||||
t!("Attribute").into_owned(),
|
||||
column![
|
||||
labelled(t!("Tag").into_owned(), input(&state.tag, true, AttdefDialogMsg::Tag), true),
|
||||
labelled(
|
||||
t!("Tag").into_owned(),
|
||||
input(&state.tag, true, AttdefDialogMsg::Tag),
|
||||
true
|
||||
),
|
||||
labelled(
|
||||
t!("Prompt").into_owned(),
|
||||
input(&state.prompt, !constant, AttdefDialogMsg::Prompt),
|
||||
@@ -249,7 +312,8 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
labelled(
|
||||
if constant { t!("Value") } else { t!("Default") }.into_owned(),
|
||||
{
|
||||
let mut value = row![input(&state.default, true, AttdefDialogMsg::Default)].spacing(6);
|
||||
let mut value =
|
||||
row![input(&state.default, true, AttdefDialogMsg::Default)].spacing(6);
|
||||
if multiline {
|
||||
value = value.push(pick(
|
||||
text("...").size(13).into(),
|
||||
@@ -257,7 +321,11 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
Some(AttdefDialogMsg::EditValue),
|
||||
));
|
||||
}
|
||||
value.push(pick(text("fx").size(12).into(), 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(),
|
||||
@@ -279,22 +347,36 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
.width(Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text(JustifyChoice(state.justify).to_string()).size(12).style(muted_style))
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
container(
|
||||
text(JustifyChoice(state.justify).to_string())
|
||||
.size(12)
|
||||
.style(muted_style),
|
||||
)
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
};
|
||||
let style: Element<'a, Message> = if text_on {
|
||||
pick_list(Some(state.style.clone()), state.styles.clone(), |s: &String| s.clone())
|
||||
.on_select(|s| msg(AttdefDialogMsg::Style(s)))
|
||||
.text_size(12)
|
||||
pick_list(
|
||||
Some(state.style.clone()),
|
||||
state.styles.clone(),
|
||||
|s: &String| s.clone(),
|
||||
)
|
||||
.on_select(|s| msg(AttdefDialogMsg::Style(s)))
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text(state.style.clone()).size(12).style(muted_style))
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text(state.style.clone()).size(12).style(muted_style)).padding([5, 8]).width(Fill).into()
|
||||
};
|
||||
let mut annotative = checkbox(state.annotative).label(t!("Annotative").into_owned()).text_size(12).size(14);
|
||||
let mut annotative = checkbox(state.annotative)
|
||||
.label(t!("Annotative").into_owned())
|
||||
.text_size(12)
|
||||
.size(14);
|
||||
if text_on {
|
||||
annotative = annotative.on_toggle(|v| msg(AttdefDialogMsg::Annotative(v)));
|
||||
}
|
||||
@@ -303,14 +385,22 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
let settings = card(
|
||||
t!("Text Settings").into_owned(),
|
||||
column![
|
||||
labelled(t!("Justification").into_owned(), justify, text_on && !multiline),
|
||||
labelled(
|
||||
t!("Justification").into_owned(),
|
||||
justify,
|
||||
text_on && !multiline
|
||||
),
|
||||
labelled(t!("Text style").into_owned(), style, text_on),
|
||||
row![Space::new().width(Length::Fixed(126.0)), annotative],
|
||||
labelled(
|
||||
t!("Text height").into_owned(),
|
||||
row![
|
||||
input(&state.height, size_on, AttdefDialogMsg::Height),
|
||||
pick(screen_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(),
|
||||
@@ -320,7 +410,11 @@ 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(screen_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(),
|
||||
@@ -351,7 +445,9 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
align = align.on_toggle(|v| msg(AttdefDialogMsg::AlignBelow(v)));
|
||||
}
|
||||
let body = row![
|
||||
column![modes, insertion].spacing(10).width(Length::Fixed(200.0)),
|
||||
column![modes, insertion]
|
||||
.spacing(10)
|
||||
.width(Length::Fixed(200.0)),
|
||||
column![attribute, settings].spacing(10).width(Fill),
|
||||
]
|
||||
.spacing(10);
|
||||
@@ -367,29 +463,60 @@ pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSiz
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view_edit<'a>(state: &'a AttdefEditState, sizing: crate::ui::modal::ModalSizing) -> Element<'a, Message> {
|
||||
let field = |label: String, value: &'a str, enabled: bool, on: fn(String) -> AttdefDialogMsg| {
|
||||
let mut input = text_input("", value).size(12).padding([5, 8]).width(Fill).style(field_style);
|
||||
if enabled {
|
||||
input = input.on_input(move |v| msg(on(v)));
|
||||
}
|
||||
let label = text(label).size(12).width(Length::Fixed(80.0));
|
||||
let label = if enabled { label } else { label.style(muted_style) };
|
||||
row![label, input].spacing(8).align_y(iced::Center)
|
||||
};
|
||||
pub fn view_edit<'a>(
|
||||
state: &'a AttdefEditState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let field =
|
||||
|label: String, value: &'a str, enabled: bool, on: fn(String) -> AttdefDialogMsg| {
|
||||
let mut input = text_input("", value)
|
||||
.size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.style(field_style);
|
||||
if enabled {
|
||||
input = input.on_input(move |v| msg(on(v)));
|
||||
}
|
||||
let label = text(label).size(12).width(Length::Fixed(80.0));
|
||||
let label = if enabled {
|
||||
label
|
||||
} else {
|
||||
label.style(muted_style)
|
||||
};
|
||||
row![label, input].spacing(8).align_y(iced::Center)
|
||||
};
|
||||
let content = card(
|
||||
t!("Attribute Definition").into_owned(),
|
||||
column![
|
||||
field(t!("Tag").into_owned(), &state.tag, true, AttdefDialogMsg::EditTag),
|
||||
field(t!("Prompt").into_owned(), &state.prompt, !state.constant, AttdefDialogMsg::EditPrompt),
|
||||
field(
|
||||
t!("Tag").into_owned(),
|
||||
&state.tag,
|
||||
true,
|
||||
AttdefDialogMsg::EditTag
|
||||
),
|
||||
field(
|
||||
t!("Prompt").into_owned(),
|
||||
&state.prompt,
|
||||
!state.constant,
|
||||
AttdefDialogMsg::EditPrompt
|
||||
),
|
||||
row![
|
||||
field(
|
||||
if state.constant { t!("Value") } else { t!("Default") }.into_owned(),
|
||||
if state.constant {
|
||||
t!("Value")
|
||||
} else {
|
||||
t!("Default")
|
||||
}
|
||||
.into_owned(),
|
||||
&state.default,
|
||||
true,
|
||||
AttdefDialogMsg::EditDefault
|
||||
),
|
||||
pick(text("fx").size(12).into(), 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),
|
||||
|
||||
@@ -14,13 +14,11 @@
|
||||
//! edits, undo and repaint live in the update handler (`Message::AttrEditorApply`).
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use codec::entities::{HorizontalAlignment, VerticalAlignment};
|
||||
use codec::types::{Color as AcadColor, LineWeight};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Element, Length, Theme};
|
||||
use crate::t;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
@@ -67,21 +65,77 @@ pub struct AttrRow {
|
||||
/// standard text-justification list. The stored labels are the English source
|
||||
/// texts; `justify_label` / `justify_from_label` translate both directions.
|
||||
pub const JUSTIFY: &[(&str, HorizontalAlignment, VerticalAlignment)] = &[
|
||||
("Left", HorizontalAlignment::Left, VerticalAlignment::Baseline),
|
||||
("Center", HorizontalAlignment::Center, VerticalAlignment::Baseline),
|
||||
("Right", HorizontalAlignment::Right, VerticalAlignment::Baseline),
|
||||
("Aligned", HorizontalAlignment::Aligned, VerticalAlignment::Baseline),
|
||||
("Middle", HorizontalAlignment::Middle, VerticalAlignment::Baseline),
|
||||
(
|
||||
"Left",
|
||||
HorizontalAlignment::Left,
|
||||
VerticalAlignment::Baseline,
|
||||
),
|
||||
(
|
||||
"Center",
|
||||
HorizontalAlignment::Center,
|
||||
VerticalAlignment::Baseline,
|
||||
),
|
||||
(
|
||||
"Right",
|
||||
HorizontalAlignment::Right,
|
||||
VerticalAlignment::Baseline,
|
||||
),
|
||||
(
|
||||
"Aligned",
|
||||
HorizontalAlignment::Aligned,
|
||||
VerticalAlignment::Baseline,
|
||||
),
|
||||
(
|
||||
"Middle",
|
||||
HorizontalAlignment::Middle,
|
||||
VerticalAlignment::Baseline,
|
||||
),
|
||||
("Fit", HorizontalAlignment::Fit, VerticalAlignment::Baseline),
|
||||
("Top Left", HorizontalAlignment::Left, VerticalAlignment::Top),
|
||||
("Top Center", HorizontalAlignment::Center, VerticalAlignment::Top),
|
||||
("Top Right", HorizontalAlignment::Right, VerticalAlignment::Top),
|
||||
("Middle Left", HorizontalAlignment::Left, VerticalAlignment::Middle),
|
||||
("Middle Center", HorizontalAlignment::Center, VerticalAlignment::Middle),
|
||||
("Middle Right", HorizontalAlignment::Right, VerticalAlignment::Middle),
|
||||
("Bottom Left", HorizontalAlignment::Left, VerticalAlignment::Bottom),
|
||||
("Bottom Center", HorizontalAlignment::Center, VerticalAlignment::Bottom),
|
||||
("Bottom Right", HorizontalAlignment::Right, VerticalAlignment::Bottom),
|
||||
(
|
||||
"Top Left",
|
||||
HorizontalAlignment::Left,
|
||||
VerticalAlignment::Top,
|
||||
),
|
||||
(
|
||||
"Top Center",
|
||||
HorizontalAlignment::Center,
|
||||
VerticalAlignment::Top,
|
||||
),
|
||||
(
|
||||
"Top Right",
|
||||
HorizontalAlignment::Right,
|
||||
VerticalAlignment::Top,
|
||||
),
|
||||
(
|
||||
"Middle Left",
|
||||
HorizontalAlignment::Left,
|
||||
VerticalAlignment::Middle,
|
||||
),
|
||||
(
|
||||
"Middle Center",
|
||||
HorizontalAlignment::Center,
|
||||
VerticalAlignment::Middle,
|
||||
),
|
||||
(
|
||||
"Middle Right",
|
||||
HorizontalAlignment::Right,
|
||||
VerticalAlignment::Middle,
|
||||
),
|
||||
(
|
||||
"Bottom Left",
|
||||
HorizontalAlignment::Left,
|
||||
VerticalAlignment::Bottom,
|
||||
),
|
||||
(
|
||||
"Bottom Center",
|
||||
HorizontalAlignment::Center,
|
||||
VerticalAlignment::Bottom,
|
||||
),
|
||||
(
|
||||
"Bottom Right",
|
||||
HorizontalAlignment::Right,
|
||||
VerticalAlignment::Bottom,
|
||||
),
|
||||
];
|
||||
|
||||
/// The justification label for an (h, v) pair (defaults to "Left"). Kept in
|
||||
@@ -149,7 +203,11 @@ fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||
border: Border {
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
@@ -236,7 +294,10 @@ fn localized_pick_field<'a>(
|
||||
on_select: impl Fn(String) -> Message + 'a,
|
||||
width: Length,
|
||||
) -> Element<'a, Message> {
|
||||
let options = options.into_iter().map(LocalizedChoice::new).collect::<Vec<_>>();
|
||||
let options = options
|
||||
.into_iter()
|
||||
.map(LocalizedChoice::new)
|
||||
.collect::<Vec<_>>();
|
||||
let selected = selected.map(LocalizedChoice::new);
|
||||
let pl = iced::widget::pick_list(selected, options, |value| value.to_string())
|
||||
.on_select(move |choice: LocalizedChoice| on_select(choice.raw))
|
||||
@@ -246,7 +307,11 @@ fn localized_pick_field<'a>(
|
||||
field_row(label, pl.into(), width)
|
||||
}
|
||||
|
||||
fn tab_button<'a>(label: Cow<'static, str>, this: AttrTab, active: AttrTab) -> Element<'a, Message> {
|
||||
fn tab_button<'a>(
|
||||
label: Cow<'static, str>,
|
||||
this: AttrTab,
|
||||
active: AttrTab,
|
||||
) -> Element<'a, Message> {
|
||||
let is_active = this == active;
|
||||
button(text(label).size(11))
|
||||
.padding([4, 12])
|
||||
@@ -261,14 +326,14 @@ fn tab_button<'a>(label: Cow<'static, str>, this: AttrTab, active: AttrTab) -> E
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -298,16 +363,16 @@ pub fn view_window<'a>(
|
||||
.style(button::primary);
|
||||
let toolbar = container(
|
||||
row![
|
||||
text(t!("Block: %{block}", block = block)).size(12).style(muted_style),
|
||||
text(t!("Block: %{block}", block = block))
|
||||
.size(12)
|
||||
.style(muted_style),
|
||||
Space::new().width(width),
|
||||
apply,
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weak.color
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(width)
|
||||
@@ -321,22 +386,23 @@ pub fn view_window<'a>(
|
||||
.spacing(2);
|
||||
|
||||
let body: Element<'_, Message> = if rows.is_empty() {
|
||||
text(t!("This block has no attributes.")).size(13).style(muted_style).into()
|
||||
text(t!("This block has no attributes."))
|
||||
.size(13)
|
||||
.style(muted_style)
|
||||
.into()
|
||||
} else {
|
||||
match tab {
|
||||
AttrTab::Attribute => attribute_tab(rows, selected, width, height),
|
||||
AttrTab::TextOptions => {
|
||||
text_options_tab(&rows[selected.min(rows.len() - 1)], styles, width, height)
|
||||
}
|
||||
AttrTab::Properties => {
|
||||
properties_tab(
|
||||
&rows[selected.min(rows.len() - 1)],
|
||||
layers,
|
||||
linetypes,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
}
|
||||
AttrTab::Properties => properties_tab(
|
||||
&rows[selected.min(rows.len() - 1)],
|
||||
layers,
|
||||
linetypes,
|
||||
width,
|
||||
height,
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -347,9 +413,7 @@ pub fn view_window<'a>(
|
||||
|
||||
container(column![toolbar, hdivider(width), content])
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(width)
|
||||
@@ -441,16 +505,39 @@ fn text_options_tab<'a>(
|
||||
let justify_sel = Some(justify_label(r.h_align, r.v_align).to_string());
|
||||
|
||||
column![
|
||||
pick_field(t!("Text Style"), styles, style_sel, |s| {
|
||||
Message::AttrEditorTextStyle(s)
|
||||
}, width),
|
||||
localized_pick_field(t!("Justification"), justify_opts, justify_sel, |s| {
|
||||
Message::AttrEditorJustify(s)
|
||||
}, width),
|
||||
pick_field(
|
||||
t!("Text Style"),
|
||||
styles,
|
||||
style_sel,
|
||||
|s| { Message::AttrEditorTextStyle(s) },
|
||||
width
|
||||
),
|
||||
localized_pick_field(
|
||||
t!("Justification"),
|
||||
justify_opts,
|
||||
justify_sel,
|
||||
|s| { Message::AttrEditorJustify(s) },
|
||||
width
|
||||
),
|
||||
edit_field(t!("Height"), &r.height, Message::AttrEditorHeight, width),
|
||||
edit_field(t!("Rotation"), &r.rotation, Message::AttrEditorRotation, width),
|
||||
edit_field(t!("Width Factor"), &r.width_factor, Message::AttrEditorWidth, width),
|
||||
edit_field(t!("Oblique Angle"), &r.oblique, Message::AttrEditorOblique, width),
|
||||
edit_field(
|
||||
t!("Rotation"),
|
||||
&r.rotation,
|
||||
Message::AttrEditorRotation,
|
||||
width
|
||||
),
|
||||
edit_field(
|
||||
t!("Width Factor"),
|
||||
&r.width_factor,
|
||||
Message::AttrEditorWidth,
|
||||
width
|
||||
),
|
||||
edit_field(
|
||||
t!("Oblique Angle"),
|
||||
&r.oblique,
|
||||
Message::AttrEditorOblique,
|
||||
width
|
||||
),
|
||||
field_row(
|
||||
Cow::Borrowed(""),
|
||||
checkbox(r.backwards)
|
||||
@@ -505,17 +592,33 @@ fn properties_tab<'a>(
|
||||
let lw_opts = lw_options();
|
||||
let lw_sel = LwItem(r.line_weight);
|
||||
let lw = iced::widget::pick_list(Some(lw_sel), lw_opts, |value| value.to_string())
|
||||
.on_select(|it: LwItem| Message::AttrEditorLineweight(it.0))
|
||||
.text_size(13)
|
||||
.padding([3, 6])
|
||||
.width(width);
|
||||
.on_select(|it: LwItem| Message::AttrEditorLineweight(it.0))
|
||||
.text_size(13)
|
||||
.padding([3, 6])
|
||||
.width(width);
|
||||
|
||||
column![
|
||||
pick_field(t!("Layer"), layers, layer_sel, Message::AttrEditorLayer, width),
|
||||
localized_pick_field(t!("Linetype"), linetypes, lt_sel, Message::AttrEditorLinetype, width),
|
||||
localized_pick_field(t!("Color"), color_opts, color_sel, |s| {
|
||||
Message::AttrEditorColor(s)
|
||||
}, width),
|
||||
pick_field(
|
||||
t!("Layer"),
|
||||
layers,
|
||||
layer_sel,
|
||||
Message::AttrEditorLayer,
|
||||
width
|
||||
),
|
||||
localized_pick_field(
|
||||
t!("Linetype"),
|
||||
linetypes,
|
||||
lt_sel,
|
||||
Message::AttrEditorLinetype,
|
||||
width
|
||||
),
|
||||
localized_pick_field(
|
||||
t!("Color"),
|
||||
color_opts,
|
||||
color_sel,
|
||||
|s| { Message::AttrEditorColor(s) },
|
||||
width
|
||||
),
|
||||
field_row(t!("Lineweight"), lw.into(), width),
|
||||
]
|
||||
.spacing(8)
|
||||
|
||||
@@ -77,10 +77,14 @@ pub fn view_window<'a>(
|
||||
|
||||
let conditions = column![
|
||||
checkbox(settings.tangent_must_share_point)
|
||||
.label(crate::t!("Tangent objects must share an intersection point"))
|
||||
.label(crate::t!(
|
||||
"Tangent objects must share an intersection point"
|
||||
))
|
||||
.on_toggle(|_| Message::AutoConstrainToggleTangentPoint),
|
||||
checkbox(settings.perpendicular_must_intersect)
|
||||
.label(crate::t!("Perpendicular objects must share an intersection point"))
|
||||
.label(crate::t!(
|
||||
"Perpendicular objects must share an intersection point"
|
||||
))
|
||||
.on_toggle(|_| Message::AutoConstrainTogglePerpendicularIntersection),
|
||||
]
|
||||
.spacing(8);
|
||||
|
||||
+315
-98
@@ -245,9 +245,8 @@ impl BlockPalette {
|
||||
/// The items of the current tab that pass the filter.
|
||||
pub fn items(&self) -> Vec<Item> {
|
||||
let pattern = self.search.trim();
|
||||
let pass = |name: &str| {
|
||||
pattern.is_empty() || crate::io::xref_model::wildcard_match(name, pattern)
|
||||
};
|
||||
let pass =
|
||||
|name: &str| pattern.is_empty() || crate::io::xref_model::wildcard_match(name, pattern);
|
||||
match self.tab {
|
||||
Tab::Current => self
|
||||
.blocks
|
||||
@@ -271,13 +270,21 @@ impl BlockPalette {
|
||||
.library_entries
|
||||
.iter()
|
||||
.filter(|e| e.folder || pass(&display_name(&e.path)))
|
||||
.map(|e| if e.folder { Item::Folder(e.path.clone()) } else { Item::File(e.path.clone()) })
|
||||
.map(|e| {
|
||||
if e.folder {
|
||||
Item::Folder(e.path.clone())
|
||||
} else {
|
||||
Item::File(e.path.clone())
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_entry(&self, name: &str) -> Option<&BlockEntry> {
|
||||
self.blocks.iter().find(|b| b.name.eq_ignore_ascii_case(name))
|
||||
self.blocks
|
||||
.iter()
|
||||
.find(|b| b.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +294,9 @@ pub fn short(v: f64) -> String {
|
||||
}
|
||||
|
||||
fn display_name(path: &std::path::Path) -> String {
|
||||
path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default()
|
||||
path.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Canvas program that draws a block's wires fit-to-box inside its bounds.
|
||||
@@ -310,8 +319,12 @@ impl<'a> Program<Message> for BlockPreviewCanvas<'a> {
|
||||
let pad = 4.0;
|
||||
let inner_w = (bounds.width - 2.0 * pad).max(1.0);
|
||||
let inner_h = (bounds.height - 2.0 * pad).max(1.0);
|
||||
let (mut minx, mut miny, mut maxx, mut maxy) =
|
||||
(f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
|
||||
let (mut minx, mut miny, mut maxx, mut maxy) = (
|
||||
f32::INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
);
|
||||
let mut any = false;
|
||||
for w in self.wires {
|
||||
for p in &w.points {
|
||||
@@ -325,8 +338,19 @@ impl<'a> Program<Message> for BlockPreviewCanvas<'a> {
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
let rect = Path::rectangle(iced::Point::new(pad, pad), iced::Size::new(inner_w, inner_h));
|
||||
frame.fill(&rect, Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 });
|
||||
let rect = Path::rectangle(
|
||||
iced::Point::new(pad, pad),
|
||||
iced::Size::new(inner_w, inner_h),
|
||||
);
|
||||
frame.fill(
|
||||
&rect,
|
||||
Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
},
|
||||
);
|
||||
return vec![frame.into_geometry()];
|
||||
}
|
||||
let span_x = (maxx - minx).max(1e-6);
|
||||
@@ -334,9 +358,15 @@ impl<'a> Program<Message> for BlockPreviewCanvas<'a> {
|
||||
let scale = (inner_w / span_x).min(inner_h / span_y);
|
||||
let ox = (bounds.width - span_x * scale) * 0.5;
|
||||
let oy = (bounds.height - span_y * scale) * 0.5;
|
||||
let map = |x: f32, y: f32| iced::Point::new(ox + (x - minx) * scale, oy + (maxy - y) * scale);
|
||||
let map =
|
||||
|x: f32, y: f32| iced::Point::new(ox + (x - minx) * scale, oy + (maxy - y) * scale);
|
||||
for w in self.wires {
|
||||
let col = Color { r: w.color[0], g: w.color[1], b: w.color[2], a: 0.55 };
|
||||
let col = Color {
|
||||
r: w.color[0],
|
||||
g: w.color[1],
|
||||
b: w.color[2],
|
||||
a: 0.55,
|
||||
};
|
||||
for tri in w.fill_tris.chunks(3) {
|
||||
if tri.len() == 3 {
|
||||
let path = Path::new(|p| {
|
||||
@@ -350,7 +380,12 @@ impl<'a> Program<Message> for BlockPreviewCanvas<'a> {
|
||||
}
|
||||
}
|
||||
for w in self.wires {
|
||||
let col = Color { r: w.color[0], g: w.color[1], b: w.color[2], a: 1.0 };
|
||||
let col = Color {
|
||||
r: w.color[0],
|
||||
g: w.color[1],
|
||||
b: w.color[2],
|
||||
a: 1.0,
|
||||
};
|
||||
let width = if w.line_weight_px > 1.5 { 2.0 } else { 1.0 };
|
||||
let mut run: Vec<iced::Point> = Vec::new();
|
||||
let flush = |frame: &mut Frame, run: &mut Vec<iced::Point>| {
|
||||
@@ -380,7 +415,10 @@ impl<'a> Program<Message> for BlockPreviewCanvas<'a> {
|
||||
|
||||
/// A block preview (the palette's canvas) for other dialogs.
|
||||
pub(crate) fn preview_canvas<'a>(wires: &'a [WireModel], height: f32) -> Element<'a, Message> {
|
||||
canvas(BlockPreviewCanvas { wires }).width(Fill).height(Length::Fixed(height)).into()
|
||||
canvas(BlockPreviewCanvas { wires })
|
||||
.width(Fill)
|
||||
.height(Length::Fixed(height))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn pm(m: BlockPaletteMsg) -> Message {
|
||||
@@ -403,14 +441,23 @@ fn thumb<'a>(palette: &'a BlockPalette, item: &Item, size: f32) -> Element<'a, M
|
||||
let file_thumb = |path: &str| palette.file_thumbs.get(path).cloned().flatten();
|
||||
match item {
|
||||
Item::Current(name) => match wires(name) {
|
||||
Some(w) => canvas(BlockPreviewCanvas { wires: w }).width(Fill).height(Length::Fixed(size)).into(),
|
||||
Some(w) => canvas(BlockPreviewCanvas { wires: w })
|
||||
.width(Fill)
|
||||
.height(Length::Fixed(size))
|
||||
.into(),
|
||||
None => blank(),
|
||||
},
|
||||
Item::Ref(r) => {
|
||||
if !r.drawing {
|
||||
let seen = palette.ref_wires.get(&format!("{}|{}", r.source, r.name.to_ascii_uppercase()));
|
||||
let seen =
|
||||
palette
|
||||
.ref_wires
|
||||
.get(&format!("{}|{}", r.source, r.name.to_ascii_uppercase()));
|
||||
if let Some(w) = seen.map(|w| &w[..]).or_else(|| wires(&r.name)) {
|
||||
return canvas(BlockPreviewCanvas { wires: w }).width(Fill).height(Length::Fixed(size)).into();
|
||||
return canvas(BlockPreviewCanvas { wires: w })
|
||||
.width(Fill)
|
||||
.height(Length::Fixed(size))
|
||||
.into();
|
||||
}
|
||||
}
|
||||
match file_thumb(&r.source) {
|
||||
@@ -419,15 +466,23 @@ fn thumb<'a>(palette: &'a BlockPalette, item: &Item, size: f32) -> Element<'a, M
|
||||
}
|
||||
}
|
||||
Item::File(path) => {
|
||||
match palette.library_entries.iter().find(|e| &e.path == path).and_then(|e| e.thumb.clone()) {
|
||||
match palette
|
||||
.library_entries
|
||||
.iter()
|
||||
.find(|e| &e.path == path)
|
||||
.and_then(|e| e.thumb.clone())
|
||||
{
|
||||
Some(h) => image(h).width(Fill).height(Length::Fixed(size)).into(),
|
||||
None => blank(),
|
||||
}
|
||||
}
|
||||
Item::Folder(_) => container(crate::ui::icons::themed_secondary(crate::ui::icons::FOLDER_OPEN, size * 0.6))
|
||||
.center_x(Fill)
|
||||
.center_y(Length::Fixed(size))
|
||||
.into(),
|
||||
Item::Folder(_) => container(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::FOLDER_OPEN,
|
||||
size * 0.6,
|
||||
))
|
||||
.center_x(Fill)
|
||||
.center_y(Length::Fixed(size))
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,15 +501,24 @@ fn item_menu(tab: Tab, item: Item, in_drawing: bool, external: bool) -> Element<
|
||||
s
|
||||
});
|
||||
if enabled {
|
||||
b.on_press(pm(BlockPaletteMsg::Menu(item.clone(), action))).into()
|
||||
b.on_press(pm(BlockPaletteMsg::Menu(item.clone(), action)))
|
||||
.into()
|
||||
} else {
|
||||
b.into()
|
||||
}
|
||||
};
|
||||
let favorite = if tab == Tab::Favorites {
|
||||
entry(crate::t!("Remove from Favorites").into_owned(), MenuAction::Unfavorite, true)
|
||||
entry(
|
||||
crate::t!("Remove from Favorites").into_owned(),
|
||||
MenuAction::Unfavorite,
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
entry(crate::t!("Add to Favorites").into_owned(), MenuAction::Favorite, !matches!(item, Item::Folder(_)))
|
||||
entry(
|
||||
crate::t!("Add to Favorites").into_owned(),
|
||||
MenuAction::Favorite,
|
||||
!matches!(item, Item::Folder(_)),
|
||||
)
|
||||
};
|
||||
let remove_label = match tab {
|
||||
Tab::Recent | Tab::Favorites => crate::t!("Remove from List"),
|
||||
@@ -467,15 +531,34 @@ fn item_menu(tab: Tab, item: Item, in_drawing: bool, external: bool) -> Element<
|
||||
};
|
||||
container(
|
||||
column![
|
||||
entry(crate::t!("Insert").into_owned(), MenuAction::Insert, !matches!(item, Item::Folder(_))),
|
||||
entry(crate::t!("Redefine").into_owned(), MenuAction::Redefine, external),
|
||||
entry(
|
||||
crate::t!("Insert").into_owned(),
|
||||
MenuAction::Insert,
|
||||
!matches!(item, Item::Folder(_))
|
||||
),
|
||||
entry(
|
||||
crate::t!("Redefine").into_owned(),
|
||||
MenuAction::Redefine,
|
||||
external
|
||||
),
|
||||
favorite,
|
||||
container(Space::new()).height(1).width(Fill).style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
}),
|
||||
entry(crate::t!("Edit Block").into_owned(), MenuAction::Edit, in_drawing),
|
||||
entry(remove_label.into_owned(), MenuAction::Remove, remove_enabled),
|
||||
container(Space::new())
|
||||
.height(1)
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
}),
|
||||
entry(
|
||||
crate::t!("Edit Block").into_owned(),
|
||||
MenuAction::Edit,
|
||||
in_drawing
|
||||
),
|
||||
entry(
|
||||
remove_label.into_owned(),
|
||||
MenuAction::Remove,
|
||||
remove_enabled
|
||||
),
|
||||
]
|
||||
.spacing(1),
|
||||
)
|
||||
@@ -496,11 +579,13 @@ fn item_menu(tab: Tab, item: Item, in_drawing: bool, external: bool) -> Element<
|
||||
/// One item as a card / row, with press, release and the context menu.
|
||||
fn item_view<'a>(palette: &'a BlockPalette, item: Item) -> Element<'a, Message> {
|
||||
let name = item.name();
|
||||
let placing = matches!(&item, Item::Current(n) if palette.placing.as_deref() == Some(n.as_str()));
|
||||
let placing =
|
||||
matches!(&item, Item::Current(n) if palette.placing.as_deref() == Some(n.as_str()));
|
||||
let in_drawing = palette.current_entry(&name).is_some()
|
||||
&& !matches!(&item, Item::Ref(r) if r.drawing)
|
||||
&& !matches!(item, Item::File(_) | Item::Folder(_));
|
||||
let external = matches!(&item, Item::Ref(r) if !r.source.is_empty()) || matches!(item, Item::File(_));
|
||||
let external =
|
||||
matches!(&item, Item::Ref(r) if !r.source.is_empty()) || matches!(item, Item::File(_));
|
||||
let body: Element<'a, Message> = match palette.view.grid() {
|
||||
Some((_, size)) => column![
|
||||
thumb(palette, &item, size),
|
||||
@@ -524,7 +609,8 @@ fn item_view<'a>(palette: &'a BlockPalette, item: Item) -> Element<'a, Message>
|
||||
let (ann, dynamic) = palette
|
||||
.current_entry(&name)
|
||||
.map_or((false, false), |e| (e.annotative, e.dynamic));
|
||||
let yes_no = |v: bool| if v { crate::t!("Yes") } else { crate::t!("No") }.into_owned();
|
||||
let yes_no =
|
||||
|v: bool| if v { crate::t!("Yes") } else { crate::t!("No") }.into_owned();
|
||||
row![
|
||||
container(thumb(palette, &item, 16.0)).width(Length::Fixed(40.0)),
|
||||
text(name.clone()).size(11).width(Fill),
|
||||
@@ -560,7 +646,10 @@ fn item_view<'a>(palette: &'a BlockPalette, item: Item) -> Element<'a, Message>
|
||||
.on_release(pm(BlockPaletteMsg::Release(item.clone())))
|
||||
.interaction(iced::mouse::Interaction::Pointer);
|
||||
let tab = palette.tab;
|
||||
iced_aw::ContextMenu::new(area, move || item_menu(tab, item.clone(), in_drawing, external)).into()
|
||||
iced_aw::ContextMenu::new(area, move || {
|
||||
item_menu(tab, item.clone(), in_drawing, external)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn icon_button<'a>(icon: IconKind, tip: &str, msg: BlockPaletteMsg) -> Element<'a, Message> {
|
||||
@@ -582,7 +671,11 @@ fn options_card<'a>(palette: &'a BlockPalette) -> Element<'a, Message> {
|
||||
.into()
|
||||
};
|
||||
let num = |value: &'a str, f: fn(String) -> OptionMsg, enabled: bool| -> Element<'a, Message> {
|
||||
let mut input = text_input("", value).size(11).padding([3, 6]).width(Length::Fixed(48.0)).style(field_style);
|
||||
let mut input = text_input("", value)
|
||||
.size(11)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(48.0))
|
||||
.style(field_style);
|
||||
if enabled {
|
||||
input = input.on_input(move |v| pm(BlockPaletteMsg::Option(f(v))));
|
||||
}
|
||||
@@ -590,7 +683,9 @@ fn options_card<'a>(palette: &'a BlockPalette) -> Element<'a, Message> {
|
||||
};
|
||||
let header = mouse_area(
|
||||
row![
|
||||
text(crate::t!("Options").to_uppercase()).size(10).style(accent_text),
|
||||
text(crate::t!("Options").to_uppercase())
|
||||
.size(10)
|
||||
.style(accent_text),
|
||||
Space::new().width(Fill),
|
||||
if palette.options_collapsed {
|
||||
crate::ui::icons::themed_arrow_right(12.0)
|
||||
@@ -604,24 +699,39 @@ fn options_card<'a>(palette: &'a BlockPalette) -> Element<'a, Message> {
|
||||
.interaction(iced::mouse::Interaction::Pointer);
|
||||
let mut content = column![header].spacing(6);
|
||||
if !palette.options_collapsed {
|
||||
let scale_kinds = vec![crate::t!("Scale").into_owned(), crate::t!("Uniform Scale").into_owned()];
|
||||
let scale_kinds = vec![
|
||||
crate::t!("Scale").into_owned(),
|
||||
crate::t!("Uniform Scale").into_owned(),
|
||||
];
|
||||
let current_kind = scale_kinds[o.uniform as usize].clone();
|
||||
let uniform_label = scale_kinds[1].clone();
|
||||
let kind = pick_list(Some(current_kind), scale_kinds, |v: &String| v.clone())
|
||||
.on_select(move |v: String| pm(BlockPaletteMsg::Option(OptionMsg::Uniform(v == uniform_label))))
|
||||
.text_size(11)
|
||||
.padding([3, 6]);
|
||||
.on_select(move |v: String| {
|
||||
pm(BlockPaletteMsg::Option(OptionMsg::Uniform(
|
||||
v == uniform_label,
|
||||
)))
|
||||
})
|
||||
.text_size(11)
|
||||
.padding([3, 6]);
|
||||
let fields_on = !o.scale;
|
||||
let mut scale_row = row![
|
||||
checkbox(o.scale).size(14).on_toggle(|v| pm(BlockPaletteMsg::Option(OptionMsg::Scale(v)))),
|
||||
checkbox(o.scale)
|
||||
.size(14)
|
||||
.on_toggle(|v| pm(BlockPaletteMsg::Option(OptionMsg::Scale(v)))),
|
||||
kind,
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
// The scale values sit on their own line under the switch.
|
||||
let mut xyz_row = row![Space::new().width(Length::Fixed(20.0))].spacing(6).align_y(iced::Center);
|
||||
let mut xyz_row = row![Space::new().width(Length::Fixed(20.0))]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
if o.scale {
|
||||
scale_row = scale_row.push(text(crate::t!("specified on screen")).size(11).style(muted_style));
|
||||
scale_row = scale_row.push(
|
||||
text(crate::t!("specified on screen"))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
);
|
||||
} else if o.uniform {
|
||||
xyz_row = xyz_row.push(num(&palette.option_text[0], OptionMsg::X, fields_on));
|
||||
} else {
|
||||
@@ -633,34 +743,69 @@ fn options_card<'a>(palette: &'a BlockPalette) -> Element<'a, Message> {
|
||||
.push(text("Z").size(11))
|
||||
.push(num(&palette.option_text[2], OptionMsg::Z, fields_on));
|
||||
}
|
||||
let mut rot_row = row![check(crate::t!("Rotation").into_owned(), o.rotation, OptionMsg::Rotation), Space::new().width(Fill)]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
let mut rot_row = row![
|
||||
check(
|
||||
crate::t!("Rotation").into_owned(),
|
||||
o.rotation,
|
||||
OptionMsg::Rotation
|
||||
),
|
||||
Space::new().width(Fill)
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
if o.rotation {
|
||||
rot_row = rot_row.push(text(crate::t!("specified on screen")).size(11).style(muted_style));
|
||||
rot_row = rot_row.push(
|
||||
text(crate::t!("specified on screen"))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
);
|
||||
} else {
|
||||
rot_row = rot_row
|
||||
.push(num(&palette.option_text[3], OptionMsg::Angle, true))
|
||||
.push(text(crate::t!("Angle")).size(11));
|
||||
}
|
||||
content = content
|
||||
.push(check(crate::t!("Insertion Point").into_owned(), o.insertion_point, OptionMsg::InsertionPoint))
|
||||
.push(check(
|
||||
crate::t!("Insertion Point").into_owned(),
|
||||
o.insertion_point,
|
||||
OptionMsg::InsertionPoint,
|
||||
))
|
||||
.push(scale_row);
|
||||
if !o.scale {
|
||||
content = content.push(xyz_row);
|
||||
}
|
||||
content = content
|
||||
.push(rot_row)
|
||||
.push(check(crate::t!("Auto-Placement").into_owned(), o.auto_placement, OptionMsg::AutoPlacement))
|
||||
.push(check(crate::t!("Repeat Placement").into_owned(), o.repeat, OptionMsg::Repeat))
|
||||
.push(check(crate::t!("Explode").into_owned(), o.explode, OptionMsg::Explode));
|
||||
.push(check(
|
||||
crate::t!("Auto-Placement").into_owned(),
|
||||
o.auto_placement,
|
||||
OptionMsg::AutoPlacement,
|
||||
))
|
||||
.push(check(
|
||||
crate::t!("Repeat Placement").into_owned(),
|
||||
o.repeat,
|
||||
OptionMsg::Repeat,
|
||||
))
|
||||
.push(check(
|
||||
crate::t!("Explode").into_owned(),
|
||||
o.explode,
|
||||
OptionMsg::Explode,
|
||||
));
|
||||
}
|
||||
container(content).padding([8, 10]).width(Fill).style(card_style).into()
|
||||
container(content)
|
||||
.padding([8, 10])
|
||||
.width(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Build the docked panel element from the palette state.
|
||||
pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<'_, Message> {
|
||||
let title_bar = crate::ui::dock::title_bar(PanelId::BlockPalette, crate::t!("Blocks").into_owned(), auto_collapse);
|
||||
let title_bar = crate::ui::dock::title_bar(
|
||||
PanelId::BlockPalette,
|
||||
crate::t!("Blocks").into_owned(),
|
||||
auto_collapse,
|
||||
);
|
||||
|
||||
let tabs = {
|
||||
let buttons = [Tab::Current, Tab::Recent, Tab::Favorites, Tab::Libraries].map(|t| {
|
||||
@@ -682,12 +827,19 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
|
||||
.padding([4, 8])
|
||||
.size(12)
|
||||
.style(field_style);
|
||||
let view_pick = pick_list(Some(palette.view.label()), ViewMode::ALL.map(|v| v.label()).to_vec(), |v: &String| v.clone())
|
||||
.on_select(|label: String| {
|
||||
pm(BlockPaletteMsg::View(
|
||||
ViewMode::ALL.into_iter().find(|v| v.label() == label).unwrap_or_default(),
|
||||
))
|
||||
})
|
||||
let view_pick = pick_list(
|
||||
Some(palette.view.label()),
|
||||
ViewMode::ALL.map(|v| v.label()).to_vec(),
|
||||
|v: &String| v.clone(),
|
||||
)
|
||||
.on_select(|label: String| {
|
||||
pm(BlockPaletteMsg::View(
|
||||
ViewMode::ALL
|
||||
.into_iter()
|
||||
.find(|v| v.label() == label)
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
})
|
||||
.text_size(11)
|
||||
.width(Length::Fixed(118.0))
|
||||
.padding([3, 6]);
|
||||
@@ -714,15 +866,21 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
|
||||
.text_size(11)
|
||||
.padding([3, 6])
|
||||
.width(Fill),
|
||||
button(crate::ui::icons::themed(crate::ui::icons::FOLDER_OPEN, 14.0))
|
||||
.on_press(pm(BlockPaletteMsg::LibraryBrowse))
|
||||
.style(button_style(false))
|
||||
.padding([3, 8]),
|
||||
button(crate::ui::icons::themed(
|
||||
crate::ui::icons::FOLDER_OPEN,
|
||||
14.0
|
||||
))
|
||||
.on_press(pm(BlockPaletteMsg::LibraryBrowse))
|
||||
.style(button_style(false))
|
||||
.padding([3, 8]),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
let in_sub = palette.library_dir.as_ref().is_some_and(|d| {
|
||||
palette.libraries.first().is_some_and(|root| d != &PathBuf::from(root))
|
||||
palette
|
||||
.libraries
|
||||
.first()
|
||||
.is_some_and(|root| d != &PathBuf::from(root))
|
||||
});
|
||||
if in_sub {
|
||||
lib_row = lib_row.push(
|
||||
@@ -739,19 +897,36 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
|
||||
Ok(rel) => rel.display().to_string(),
|
||||
Err(_) => dir.display().to_string(),
|
||||
};
|
||||
top = top.push(text(format!("{} {shown}", crate::t!("Path:"))).size(10).style(accent_text));
|
||||
top = top.push(
|
||||
text(format!("{} {shown}", crate::t!("Path:")))
|
||||
.size(10)
|
||||
.style(accent_text),
|
||||
);
|
||||
}
|
||||
}
|
||||
top = top.push(text(palette.tab.heading().to_uppercase()).size(10).style(accent_text));
|
||||
top = top.push(
|
||||
text(palette.tab.heading().to_uppercase())
|
||||
.size(10)
|
||||
.style(accent_text),
|
||||
);
|
||||
|
||||
let items = palette.items();
|
||||
let body: Element<'_, Message> = if items.is_empty() {
|
||||
let msg = match palette.tab {
|
||||
Tab::Current if palette.cached_names.is_empty() => crate::t!("No blocks in this drawing"),
|
||||
Tab::Libraries if palette.libraries.is_empty() => crate::t!("Choose a library folder with …"),
|
||||
Tab::Current if palette.cached_names.is_empty() => {
|
||||
crate::t!("No blocks in this drawing")
|
||||
}
|
||||
Tab::Libraries if palette.libraries.is_empty() => {
|
||||
crate::t!("Choose a library folder with …")
|
||||
}
|
||||
_ => crate::t!("No matches"),
|
||||
};
|
||||
container(text(msg).size(12).style(muted_style)).center_x(Fill).center_y(Fill).width(Fill).height(Fill).into()
|
||||
container(text(msg).size(12).style(muted_style))
|
||||
.center_x(Fill)
|
||||
.center_y(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
} else {
|
||||
let mut col = column![].spacing(4);
|
||||
match palette.view.grid() {
|
||||
@@ -783,9 +958,18 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
|
||||
if palette.view == ViewMode::Details {
|
||||
col = col.push(
|
||||
row![
|
||||
text(crate::t!("Icon")).size(11).style(muted_style).width(Length::Fixed(40.0)),
|
||||
text(crate::t!("Name")).size(11).style(muted_style).width(Fill),
|
||||
text(crate::t!("Annotative / Dynamic")).size(11).style(muted_style).width(Length::Fixed(140.0)),
|
||||
text(crate::t!("Icon"))
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(40.0)),
|
||||
text(crate::t!("Name"))
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Fill),
|
||||
text(crate::t!("Annotative / Dynamic"))
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(140.0)),
|
||||
]
|
||||
.spacing(4),
|
||||
);
|
||||
@@ -795,13 +979,21 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
|
||||
}
|
||||
}
|
||||
}
|
||||
scrollable(container(col).padding(iced::Padding { top: 0.0, right: 8.0, bottom: 6.0, left: 0.0 }))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
scrollable(container(col).padding(iced::Padding {
|
||||
top: 0.0,
|
||||
right: 8.0,
|
||||
bottom: 6.0,
|
||||
left: 0.0,
|
||||
}))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
};
|
||||
|
||||
crate::ui::dock::frame(column![title_bar, top, body, options_card(palette)].spacing(6), width)
|
||||
crate::ui::dock::frame(
|
||||
column![title_bar, top, body, options_card(palette)].spacing(6),
|
||||
width,
|
||||
)
|
||||
}
|
||||
|
||||
/// Width of the ribbon's block gallery.
|
||||
@@ -819,7 +1011,11 @@ pub fn gallery(palette: &BlockPalette) -> Element<'_, Message> {
|
||||
let mut r = row![].spacing(4).width(Fill);
|
||||
for block in chunk {
|
||||
let card = column![
|
||||
canvas(BlockPreviewCanvas { wires: &block.wires }).width(Fill).height(Length::Fixed(48.0)),
|
||||
canvas(BlockPreviewCanvas {
|
||||
wires: &block.wires
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Length::Fixed(48.0)),
|
||||
text(crate::ui::text_util::elide(&block.name, 10))
|
||||
.size(10)
|
||||
.width(Fill)
|
||||
@@ -841,14 +1037,23 @@ pub fn gallery(palette: &BlockPalette) -> Element<'_, Message> {
|
||||
grid = grid.push(r);
|
||||
}
|
||||
let blocks: Element<'_, Message> = if palette.blocks.is_empty() {
|
||||
container(text(crate::t!("No blocks in this drawing")).size(11).style(muted_style))
|
||||
.padding(10)
|
||||
.center_x(Fill)
|
||||
.into()
|
||||
container(
|
||||
text(crate::t!("No blocks in this drawing"))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
)
|
||||
.padding(10)
|
||||
.center_x(Fill)
|
||||
.into()
|
||||
} else {
|
||||
scrollable(container(grid).padding(iced::Padding { top: 0.0, right: 8.0, bottom: 0.0, left: 0.0 }))
|
||||
.height(Length::Shrink)
|
||||
.into()
|
||||
scrollable(container(grid).padding(iced::Padding {
|
||||
top: 0.0,
|
||||
right: 8.0,
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
}))
|
||||
.height(Length::Shrink)
|
||||
.into()
|
||||
};
|
||||
let link = |label: std::borrow::Cow<'static, str>, tab: &str| -> Element<'_, Message> {
|
||||
button(text(label.into_owned()).size(11))
|
||||
@@ -860,12 +1065,19 @@ pub fn gallery(palette: &BlockPalette) -> Element<'_, Message> {
|
||||
};
|
||||
container(
|
||||
column![
|
||||
text(crate::t!("Current Drawing Blocks").to_uppercase()).size(10).style(accent_text),
|
||||
container(blocks).height(Length::Fixed(((palette.blocks.len().div_ceil(4)) as f32 * 79.0).clamp(36.0, 260.0))),
|
||||
container(Space::new()).height(1).width(Fill).style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
}),
|
||||
text(crate::t!("Current Drawing Blocks").to_uppercase())
|
||||
.size(10)
|
||||
.style(accent_text),
|
||||
container(blocks).height(Length::Fixed(
|
||||
((palette.blocks.len().div_ceil(4)) as f32 * 79.0).clamp(36.0, 260.0)
|
||||
)),
|
||||
container(Space::new())
|
||||
.height(1)
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
}),
|
||||
link(crate::t!("Recent Blocks..."), "RECENT"),
|
||||
link(crate::t!("Favorite Blocks..."), "FAVORITES"),
|
||||
link(crate::t!("Blocks from Libraries..."), "LIBRARIES"),
|
||||
@@ -880,15 +1092,20 @@ pub fn gallery(palette: &BlockPalette) -> Element<'_, Message> {
|
||||
|
||||
/// Theme-aware foreground/background pair for a block card (normal,
|
||||
/// hovered/pressed, or the block being placed).
|
||||
pub(crate) fn block_card_colors(theme: &Theme, is_placing: bool, status: button::Status) -> (Color, Color) {
|
||||
pub(crate) fn block_card_colors(
|
||||
theme: &Theme,
|
||||
is_placing: bool,
|
||||
status: button::Status,
|
||||
) -> (Color, Color) {
|
||||
let palette = theme.palette();
|
||||
if is_placing {
|
||||
(palette.primary.base.color, palette.primary.base.text)
|
||||
} else {
|
||||
match status {
|
||||
button::Status::Hovered | button::Status::Pressed => {
|
||||
(palette.background.strong.color, palette.background.strong.text)
|
||||
}
|
||||
button::Status::Hovered | button::Status::Pressed => (
|
||||
palette.background.strong.color,
|
||||
palette.background.strong.text,
|
||||
),
|
||||
_ => (palette.background.weak.color, palette.background.weak.text),
|
||||
}
|
||||
}
|
||||
|
||||
+20
-12
@@ -54,11 +54,10 @@ fn bodies(document: &CadDocument) -> Vec<(Handle, String)> {
|
||||
.filter(|entity| matches!(entity, EntityType::Solid3D(_)))
|
||||
.map(|entity| {
|
||||
let handle = entity.common().handle;
|
||||
let label = crate::scene::model::solid_history::primitive_property_operation(
|
||||
document, handle,
|
||||
)
|
||||
.as_ref()
|
||||
.map_or("Solid", operation_label);
|
||||
let label =
|
||||
crate::scene::model::solid_history::primitive_property_operation(document, handle)
|
||||
.as_ref()
|
||||
.map_or("Solid", operation_label);
|
||||
(handle, format!("{label} {:X}", handle.value()))
|
||||
})
|
||||
.collect()
|
||||
@@ -87,10 +86,14 @@ fn entry_row(label: String, command: String) -> Element<'static, Message> {
|
||||
|
||||
/// A row that states something rather than doing something.
|
||||
fn note_row(label: String) -> Element<'static, Message> {
|
||||
container(text(label).size(11).style(crate::ui::style::common::muted_style))
|
||||
.width(Fill)
|
||||
.padding([3, 10])
|
||||
.into()
|
||||
container(
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(crate::ui::style::common::muted_style),
|
||||
)
|
||||
.width(Fill)
|
||||
.padding([3, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
@@ -99,8 +102,11 @@ pub fn view<'a>(
|
||||
width: f32,
|
||||
auto_collapse: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let title_bar =
|
||||
crate::ui::dock::title_bar(PanelId::Browser, crate::t!("Browser").into_owned(), auto_collapse);
|
||||
let title_bar = crate::ui::dock::title_bar(
|
||||
PanelId::Browser,
|
||||
crate::t!("Browser").into_owned(),
|
||||
auto_collapse,
|
||||
);
|
||||
|
||||
let mut tree = column![].spacing(1);
|
||||
|
||||
@@ -123,7 +129,9 @@ pub fn view<'a>(
|
||||
}
|
||||
|
||||
let bodies = bodies(document);
|
||||
tree = tree.push(section_header(crate::tf!("Bodies ({})", bodies.len()).into_owned()));
|
||||
tree = tree.push(section_header(
|
||||
crate::tf!("Bodies ({})", bodies.len()).into_owned(),
|
||||
));
|
||||
if bodies.is_empty() {
|
||||
tree = tree.push(note_row(crate::t!("No solids yet").into_owned()));
|
||||
} else {
|
||||
|
||||
+257
-68
@@ -9,7 +9,10 @@ use crate::ui::style::common::muted_style;
|
||||
use crate::ui::style::form::{button_style, field_style};
|
||||
use codec::count::{BlockInstance, CountKey};
|
||||
use codec::{CadDocument, Handle};
|
||||
use iced::widget::{button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, tooltip, Space};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, tooltip,
|
||||
Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
|
||||
const BLOCK_ICON: &[u8] = include_bytes!("../../../assets/icons/blocks/block.svg");
|
||||
@@ -73,7 +76,11 @@ impl CountMode {
|
||||
/// The key the match options build from the reference instance.
|
||||
pub fn key(&self, instances: &[BlockInstance]) -> CountKey {
|
||||
match &self.target {
|
||||
Some(CountTarget::Block { reference: Some(r), matching, .. }) => instances
|
||||
Some(CountTarget::Block {
|
||||
reference: Some(r),
|
||||
matching,
|
||||
..
|
||||
}) => instances
|
||||
.iter()
|
||||
.find(|i| i.handle == *r)
|
||||
.map(|i| CountKey::of(i, matching[0], matching[1], matching[2]))
|
||||
@@ -102,7 +109,11 @@ impl CountMode {
|
||||
pub fn compute(&self, doc: &CadDocument) -> (CountResult, usize) {
|
||||
if let Some(CountTarget::Group(handles)) = &self.target {
|
||||
let groups = codec::count::group_matches(doc, handles, self.area.as_deref());
|
||||
let result = CountResult { counted: groups.concat(), errors: Vec::new(), overlapped: Vec::new() };
|
||||
let result = CountResult {
|
||||
counted: groups.concat(),
|
||||
errors: Vec::new(),
|
||||
overlapped: Vec::new(),
|
||||
};
|
||||
return (result, groups.len());
|
||||
}
|
||||
let result = self.block_result(doc);
|
||||
@@ -118,9 +129,23 @@ impl CountMode {
|
||||
let key = self.key(&instances);
|
||||
let mine = |i: &&BlockInstance| i.name.eq_ignore_ascii_case(name) && key.matches(i);
|
||||
CountResult {
|
||||
counted: instances.iter().filter(mine).filter(|i| i.duplicate_of.is_none()).map(|i| i.handle).collect(),
|
||||
errors: instances.iter().filter(mine).filter(|i| i.duplicate_of.is_some()).map(|i| i.handle).collect(),
|
||||
overlapped: instances.iter().filter(mine).filter_map(|i| i.duplicate_of).collect(),
|
||||
counted: instances
|
||||
.iter()
|
||||
.filter(mine)
|
||||
.filter(|i| i.duplicate_of.is_none())
|
||||
.map(|i| i.handle)
|
||||
.collect(),
|
||||
errors: instances
|
||||
.iter()
|
||||
.filter(mine)
|
||||
.filter(|i| i.duplicate_of.is_some())
|
||||
.map(|i| i.handle)
|
||||
.collect(),
|
||||
overlapped: instances
|
||||
.iter()
|
||||
.filter(mine)
|
||||
.filter_map(|i| i.duplicate_of)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +185,14 @@ pub struct CountPalette {
|
||||
/// on with all of model space (kept with the user settings).
|
||||
pub invalid_choice: u8,
|
||||
/// The list's references for (document tab id, geometry epoch, area).
|
||||
pub list_cache: std::cell::RefCell<Option<(u64, u64, Option<Vec<[f64; 2]>>, std::rc::Rc<Vec<BlockInstance>>)>>,
|
||||
pub list_cache: std::cell::RefCell<
|
||||
Option<(
|
||||
u64,
|
||||
u64,
|
||||
Option<Vec<[f64; 2]>>,
|
||||
std::rc::Rc<Vec<BlockInstance>>,
|
||||
)>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Default for CountPalette {
|
||||
@@ -187,7 +219,10 @@ impl Default for CountPalette {
|
||||
impl CountPalette {
|
||||
pub fn expansion(&self, name: &str) -> Option<[bool; 3]> {
|
||||
let key = name.to_ascii_uppercase();
|
||||
self.expanded.iter().find(|(n, _)| *n == key).map(|(_, e)| *e)
|
||||
self.expanded
|
||||
.iter()
|
||||
.find(|(n, _)| *n == key)
|
||||
.map(|(_, e)| *e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,10 +286,18 @@ pub fn rows(instances: &[BlockInstance], palette: &CountPalette) -> Vec<Row> {
|
||||
let mut rows: Vec<Row> = codec::count::block_counts(instances)
|
||||
.into_iter()
|
||||
.filter(|c| query.is_empty() || c.name.to_lowercase().contains(&query))
|
||||
.map(|c| Row { count: c.counted.len(), errors: c.duplicates.len(), name: c.name })
|
||||
.map(|c| Row {
|
||||
count: c.counted.len(),
|
||||
errors: c.duplicates.len(),
|
||||
name: c.name,
|
||||
})
|
||||
.collect();
|
||||
if palette.by_count {
|
||||
rows.sort_by(|a, b| a.count.cmp(&b.count).then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase())));
|
||||
rows.sort_by(|a, b| {
|
||||
a.count
|
||||
.cmp(&b.count)
|
||||
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
});
|
||||
}
|
||||
if palette.descending {
|
||||
rows.reverse();
|
||||
@@ -283,27 +326,50 @@ fn row_menu(name: String, key: CountKey, expandable: bool) -> Element<'static, M
|
||||
.into()
|
||||
};
|
||||
let mut items = column![
|
||||
entry(crate::t!("Review Count Details").into_owned(), RowAction::Review),
|
||||
entry(crate::t!("Insert Count Field").into_owned(), RowAction::Field),
|
||||
entry(
|
||||
crate::t!("Review Count Details").into_owned(),
|
||||
RowAction::Review
|
||||
),
|
||||
entry(
|
||||
crate::t!("Insert Count Field").into_owned(),
|
||||
RowAction::Field
|
||||
),
|
||||
]
|
||||
.spacing(1);
|
||||
if expandable {
|
||||
items = items
|
||||
.push(container(Space::new()).height(1).width(Fill).style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
}))
|
||||
.push(container(text(crate::t!("EXPAND BY")).size(10).style(muted_style)).padding([3, 12]))
|
||||
.push(
|
||||
container(Space::new())
|
||||
.height(1)
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.push(
|
||||
container(text(crate::t!("EXPAND BY")).size(10).style(muted_style))
|
||||
.padding([3, 12]),
|
||||
)
|
||||
.push(entry(crate::t!("Layer").into_owned(), RowAction::Expand(0)))
|
||||
.push(entry(crate::t!("Scale").into_owned(), RowAction::Expand(1)))
|
||||
.push(entry(crate::t!("Mirror State").into_owned(), RowAction::Expand(2)));
|
||||
.push(entry(
|
||||
crate::t!("Mirror State").into_owned(),
|
||||
RowAction::Expand(2),
|
||||
));
|
||||
}
|
||||
container(items)
|
||||
.padding(4)
|
||||
.width(Length::Fixed(200.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() },
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -319,16 +385,25 @@ fn list_row<'a>(
|
||||
depth: u16,
|
||||
expanded: Option<bool>,
|
||||
) -> Element<'a, Message> {
|
||||
let lead: Element<'a, Message> = if let Some(checked) = palette.table.as_ref().filter(|_| depth == 0) {
|
||||
let on = checked.contains(&name.to_ascii_uppercase());
|
||||
let n = name.to_string();
|
||||
checkbox(on).size(14).on_toggle(move |v| msg(CountMsg::TableCheck(n.clone(), v))).into()
|
||||
} else if expanded == Some(true) {
|
||||
container(crate::ui::icons::themed(crate::ui::icons::MINUS, 12.0)).width(Length::Fixed(14.0)).into()
|
||||
} else {
|
||||
Space::new().width(Length::Fixed(14.0)).into()
|
||||
};
|
||||
let mut cells = row![Space::new().width(Length::Fixed(f32::from(depth) * 18.0)), lead];
|
||||
let lead: Element<'a, Message> =
|
||||
if let Some(checked) = palette.table.as_ref().filter(|_| depth == 0) {
|
||||
let on = checked.contains(&name.to_ascii_uppercase());
|
||||
let n = name.to_string();
|
||||
checkbox(on)
|
||||
.size(14)
|
||||
.on_toggle(move |v| msg(CountMsg::TableCheck(n.clone(), v)))
|
||||
.into()
|
||||
} else if expanded == Some(true) {
|
||||
container(crate::ui::icons::themed(crate::ui::icons::MINUS, 12.0))
|
||||
.width(Length::Fixed(14.0))
|
||||
.into()
|
||||
} else {
|
||||
Space::new().width(Length::Fixed(14.0)).into()
|
||||
};
|
||||
let mut cells = row![
|
||||
Space::new().width(Length::Fixed(f32::from(depth) * 18.0)),
|
||||
lead
|
||||
];
|
||||
if depth == 0 {
|
||||
cells = cells.push(small_icon(BLOCK_ICON, 14.0));
|
||||
}
|
||||
@@ -341,7 +416,9 @@ fn list_row<'a>(
|
||||
Some(n) => Element::from(text(n.to_string()).size(12)),
|
||||
None => crate::ui::icons::themed(crate::ui::icons::MORE, 12.0),
|
||||
});
|
||||
let body = container(cells.spacing(6).align_y(iced::Center)).width(Fill).padding([4, 6]);
|
||||
let body = container(cells.spacing(6).align_y(iced::Center))
|
||||
.width(Fill)
|
||||
.padding([4, 6]);
|
||||
let area = mouse_area(body)
|
||||
.on_press(msg(CountMsg::Open(name.to_string(), key.clone())))
|
||||
.interaction(iced::mouse::Interaction::Pointer);
|
||||
@@ -349,7 +426,12 @@ fn list_row<'a>(
|
||||
iced_aw::ContextMenu::new(area, move || row_menu(n.clone(), key.clone(), expandable)).into()
|
||||
}
|
||||
|
||||
fn header_button<'a>(label: String, active: bool, descending: bool, m: CountMsg) -> Element<'a, Message> {
|
||||
fn header_button<'a>(
|
||||
label: String,
|
||||
active: bool,
|
||||
descending: bool,
|
||||
m: CountMsg,
|
||||
) -> Element<'a, Message> {
|
||||
let mut content = row![text(label).size(11)].spacing(3).align_y(iced::Center);
|
||||
if active {
|
||||
content = content.push(crate::ui::icons::themed_arrow_toggle(!descending, 8.0));
|
||||
@@ -379,9 +461,19 @@ fn list_view<'a>(palette: &'a CountPalette, instances: &[BlockInstance]) -> Elem
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
let header = row![
|
||||
header_button(crate::t!("Name").into_owned(), !palette.by_count, palette.descending, CountMsg::Sort(false)),
|
||||
header_button(
|
||||
crate::t!("Name").into_owned(),
|
||||
!palette.by_count,
|
||||
palette.descending,
|
||||
CountMsg::Sort(false)
|
||||
),
|
||||
Space::new().width(Fill),
|
||||
header_button(crate::t!("Count").into_owned(), palette.by_count, palette.descending, CountMsg::Sort(true)),
|
||||
header_button(
|
||||
crate::t!("Count").into_owned(),
|
||||
palette.by_count,
|
||||
palette.descending,
|
||||
CountMsg::Sort(true)
|
||||
),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
let mut list = column![].spacing(1);
|
||||
@@ -401,7 +493,16 @@ fn list_view<'a>(palette: &'a CountPalette, instances: &[BlockInstance]) -> Elem
|
||||
if let Some([l, s, m]) = expansion {
|
||||
for (key, n) in codec::count::expanded_counts(instances, &r.name, l, s, m) {
|
||||
let label = key.label();
|
||||
list = list.push(list_row(palette, &r.name, key, label, Some(n), false, 1, None));
|
||||
list = list.push(list_row(
|
||||
palette,
|
||||
&r.name,
|
||||
key,
|
||||
label,
|
||||
Some(n),
|
||||
false,
|
||||
1,
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,13 +517,20 @@ fn list_view<'a>(palette: &'a CountPalette, instances: &[BlockInstance]) -> Elem
|
||||
.into(),
|
||||
Some(checked) => {
|
||||
let all = rows(instances, palette);
|
||||
let every = !all.is_empty() && all.iter().all(|r| checked.contains(&r.name.to_ascii_uppercase()));
|
||||
let mut insert = button(text(crate::t!("Insert")).size(12)).style(button_style(true)).padding([5, 12]);
|
||||
let every = !all.is_empty()
|
||||
&& all
|
||||
.iter()
|
||||
.all(|r| checked.contains(&r.name.to_ascii_uppercase()));
|
||||
let mut insert = button(text(crate::t!("Insert")).size(12))
|
||||
.style(button_style(true))
|
||||
.padding([5, 12]);
|
||||
if !checked.is_empty() {
|
||||
insert = insert.on_press(msg(CountMsg::TableInsert));
|
||||
}
|
||||
row![
|
||||
checkbox(every).size(14).on_toggle(|v| msg(CountMsg::TableAll(v))),
|
||||
checkbox(every)
|
||||
.size(14)
|
||||
.on_toggle(|v| msg(CountMsg::TableAll(v))),
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("Cancel")).size(12))
|
||||
.on_press(msg(CountMsg::TableCancel))
|
||||
@@ -450,18 +558,32 @@ fn list_view<'a>(palette: &'a CountPalette, instances: &[BlockInstance]) -> Elem
|
||||
fn card(theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Count mode in the palette: back to the list, the counted block with the
|
||||
/// match options, and the error report.
|
||||
fn mode_view<'a>(palette: &'a CountPalette, mode: &'a CountMode, doc: &CadDocument) -> Element<'a, Message> {
|
||||
let back = button(row![crate::ui::icons::themed_arrow_left(10.0), text(crate::t!("Back to List")).size(12)].spacing(4).align_y(iced::Center))
|
||||
.on_press(msg(CountMsg::Back))
|
||||
.style(button::text)
|
||||
.padding([2, 2]);
|
||||
fn mode_view<'a>(
|
||||
palette: &'a CountPalette,
|
||||
mode: &'a CountMode,
|
||||
doc: &CadDocument,
|
||||
) -> Element<'a, Message> {
|
||||
let back = button(
|
||||
row![
|
||||
crate::ui::icons::themed_arrow_left(10.0),
|
||||
text(crate::t!("Back to List")).size(12)
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(msg(CountMsg::Back))
|
||||
.style(button::text)
|
||||
.padding([2, 2]);
|
||||
let result = mode.result(doc);
|
||||
let mut body = column![back].spacing(8);
|
||||
if let Some(name) = mode.target_name() {
|
||||
@@ -469,7 +591,8 @@ fn mode_view<'a>(palette: &'a CountPalette, mode: &'a CountMode, doc: &CadDocume
|
||||
row![
|
||||
crate::ui::icons::themed_success(CHECK_ICON, 12.0),
|
||||
text(match mode.target {
|
||||
Some(CountTarget::Group(_)) => crate::tf!("Geometries: {}", mode.count(doc)).into_owned(),
|
||||
Some(CountTarget::Group(_)) =>
|
||||
crate::tf!("Geometries: {}", mode.count(doc)).into_owned(),
|
||||
_ => format!("{name}: {}", mode.count(doc)),
|
||||
})
|
||||
.size(12)
|
||||
@@ -482,8 +605,13 @@ fn mode_view<'a>(palette: &'a CountPalette, mode: &'a CountMode, doc: &CadDocume
|
||||
.style(button::text)
|
||||
.padding(0)]
|
||||
.spacing(6);
|
||||
if let (false, Some(CountTarget::Block { matching, .. })) = (palette.details_closed, &mode.target) {
|
||||
for (k, label) in ["Match Layer", "Match Scale", "Match Mirror State"].iter().enumerate() {
|
||||
if let (false, Some(CountTarget::Block { matching, .. })) =
|
||||
(palette.details_closed, &mode.target)
|
||||
{
|
||||
for (k, label) in ["Match Layer", "Match Scale", "Match Mirror State"]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
details = details.push(
|
||||
checkbox(matching[k])
|
||||
.label(crate::t!(*label).into_owned())
|
||||
@@ -499,7 +627,9 @@ fn mode_view<'a>(palette: &'a CountPalette, mode: &'a CountMode, doc: &CadDocume
|
||||
let mut report = column![button(
|
||||
row![
|
||||
crate::ui::icons::themed_warning(WARNING_ICON, 12.0),
|
||||
text(crate::tf!("Count Error Report: {}", result.errors.len())).size(12).width(Fill),
|
||||
text(crate::tf!("Count Error Report: {}", result.errors.len()))
|
||||
.size(12)
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
@@ -532,7 +662,11 @@ pub fn view<'a>(
|
||||
width: f32,
|
||||
auto_collapse: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let title_bar = crate::ui::dock::title_bar(PanelId::Count, crate::t!("Count").into_owned(), auto_collapse);
|
||||
let title_bar = crate::ui::dock::title_bar(
|
||||
PanelId::Count,
|
||||
crate::t!("Count").into_owned(),
|
||||
auto_collapse,
|
||||
);
|
||||
let body = match mode {
|
||||
Some(mode) if mode.target.is_some() => mode_view(palette, mode, doc),
|
||||
_ => {
|
||||
@@ -542,7 +676,8 @@ pub fn view<'a>(
|
||||
let instances = match cache.as_ref() {
|
||||
Some((d, e, a, list)) if (*d, *e) == key && *a == area => list.clone(),
|
||||
_ => {
|
||||
let list = std::rc::Rc::new(codec::count::block_instances(doc, area.as_deref()));
|
||||
let list =
|
||||
std::rc::Rc::new(codec::count::block_instances(doc, area.as_deref()));
|
||||
*cache = Some((key.0, key.1, area, list.clone()));
|
||||
list
|
||||
}
|
||||
@@ -553,12 +688,18 @@ pub fn view<'a>(
|
||||
crate::ui::dock::frame(column![title_bar, body].spacing(6), width)
|
||||
}
|
||||
|
||||
fn bar_button<'a>(icon: Element<'a, Message>, tip: String, m: Option<CountMsg>) -> Element<'a, Message> {
|
||||
fn bar_button<'a>(
|
||||
icon: Element<'a, Message>,
|
||||
tip: String,
|
||||
m: Option<CountMsg>,
|
||||
) -> Element<'a, Message> {
|
||||
let mut b = button(icon).style(button::text).padding([3, 5]);
|
||||
if let Some(m) = m {
|
||||
b = b.on_press(msg(m));
|
||||
}
|
||||
tooltip(b, text(tip).size(10), tooltip::Position::Bottom).gap(4).into()
|
||||
tooltip(b, text(tip).size(10), tooltip::Position::Bottom)
|
||||
.gap(4)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The floating toolbar over the drawing in count mode: the count, the
|
||||
@@ -566,7 +707,11 @@ fn bar_button<'a>(icon: Element<'a, Message>, tip: String, m: Option<CountMsg>)
|
||||
pub fn toolbar<'a>(mode: &CountMode, doc: &CadDocument) -> Element<'a, Message> {
|
||||
let has_target = mode.target.is_some();
|
||||
let result = mode.result(doc);
|
||||
let count = if has_target { mode.count(doc).to_string() } else { "-".to_string() };
|
||||
let count = if has_target {
|
||||
mode.count(doc).to_string()
|
||||
} else {
|
||||
"-".to_string()
|
||||
};
|
||||
let status: Element<'a, Message> = if !result.errors.is_empty() {
|
||||
crate::ui::icons::themed_warning(WARNING_ICON, 14.0)
|
||||
} else if has_target {
|
||||
@@ -585,21 +730,55 @@ pub fn toolbar<'a>(mode: &CountMode, doc: &CadDocument) -> Element<'a, Message>
|
||||
let bar = row![
|
||||
text(crate::tf!("Count: {}", count)).size(13),
|
||||
status,
|
||||
bar_button(crate::ui::icons::themed_arrow_left(12.0), crate::t!("Previous").into_owned(), on(CountMsg::Prev)),
|
||||
bar_button(crate::ui::icons::themed_arrow_right(12.0), crate::t!("Next").into_owned(), on(CountMsg::Next)),
|
||||
bar_button(crate::ui::icons::themed_success(AREA_ICON, 16.0), crate::t!("Count in a specified area").into_owned(), Some(CountMsg::Area)),
|
||||
bar_button(crate::ui::icons::themed_success(SELECT_ICON, 16.0), crate::t!("Select objects").into_owned(), Some(CountMsg::Select)),
|
||||
bar_button(icon(TABLE_ICON), crate::t!("Insert Count Field").into_owned(), on(CountMsg::Field)),
|
||||
bar_button(crate::ui::icons::themed_secondary(crate::ui::icons::CLOSE, 12.0), crate::t!("Close").into_owned(), Some(CountMsg::Close)),
|
||||
bar_button(
|
||||
crate::ui::icons::themed_arrow_left(12.0),
|
||||
crate::t!("Previous").into_owned(),
|
||||
on(CountMsg::Prev)
|
||||
),
|
||||
bar_button(
|
||||
crate::ui::icons::themed_arrow_right(12.0),
|
||||
crate::t!("Next").into_owned(),
|
||||
on(CountMsg::Next)
|
||||
),
|
||||
bar_button(
|
||||
crate::ui::icons::themed_success(AREA_ICON, 16.0),
|
||||
crate::t!("Count in a specified area").into_owned(),
|
||||
Some(CountMsg::Area)
|
||||
),
|
||||
bar_button(
|
||||
crate::ui::icons::themed_success(SELECT_ICON, 16.0),
|
||||
crate::t!("Select objects").into_owned(),
|
||||
Some(CountMsg::Select)
|
||||
),
|
||||
bar_button(
|
||||
icon(TABLE_ICON),
|
||||
crate::t!("Insert Count Field").into_owned(),
|
||||
on(CountMsg::Field)
|
||||
),
|
||||
bar_button(
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::CLOSE, 12.0),
|
||||
crate::t!("Close").into_owned(),
|
||||
Some(CountMsg::Close)
|
||||
),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
let bar = container(bar).padding([4, 10]).style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
});
|
||||
container(iced::widget::opaque(bar)).width(Fill).align_x(iced::Center).padding([10, 0]).into()
|
||||
let bar = container(bar)
|
||||
.padding([4, 10])
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
container(iced::widget::opaque(bar))
|
||||
.width(Fill)
|
||||
.align_x(iced::Center)
|
||||
.padding([10, 0])
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The blue frame around the drawing area in count mode.
|
||||
@@ -608,7 +787,11 @@ pub fn border<'a>() -> Element<'a, Message> {
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
border: Border { color: Color::from_rgb(0.18, 0.52, 0.95), width: 2.0, radius: 0.0.into() },
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.18, 0.52, 0.95),
|
||||
width: 2.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -619,14 +802,20 @@ pub fn aci_rgba(index: i16) -> [f32; 4] {
|
||||
crate::scene::convert::tess_util::aci_to_rgba(&codec::types::Color::from_index(index))
|
||||
}
|
||||
|
||||
|
||||
/// The Invalid Area dialog, shown when the count area's boundary is gone.
|
||||
pub fn invalid_area_view<'a>(palette: &CountPalette, sizing: crate::ui::modal::ModalSizing) -> Element<'a, Message> {
|
||||
pub fn invalid_area_view<'a>(
|
||||
palette: &CountPalette,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let action = |title: String, detail: String, m: CountMsg| -> Element<'a, Message> {
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::themed_primary(crate::ui::icons::ARROW_LONG_RIGHT, 16.0),
|
||||
column![text(title).size(13), text(detail).size(11).style(muted_style)].spacing(2),
|
||||
column![
|
||||
text(title).size(13),
|
||||
text(detail).size(11).style(muted_style)
|
||||
]
|
||||
.spacing(2),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
|
||||
@@ -7,9 +7,7 @@ use iced::{Background, Border, Element, Fill, Theme};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Active tab in the Drafting Settings dialog.
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum DraftingSettingsTab {
|
||||
#[default]
|
||||
@@ -116,12 +114,17 @@ pub fn parse_grid_major(s: &str) -> Option<u32> {
|
||||
}
|
||||
|
||||
/// Helper for grouped sub-panels with a light border and header title.
|
||||
fn group<'a>(title: impl Into<String>, body: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
fn group<'a>(
|
||||
title: impl Into<String>,
|
||||
body: impl Into<Element<'a, Message>>,
|
||||
) -> Element<'a, Message> {
|
||||
container(
|
||||
column![
|
||||
text(title.into()).size(11).style(|theme: &Theme| text::Style {
|
||||
color: Some(theme.palette().background.strongest.text.scale_alpha(0.75)),
|
||||
}),
|
||||
text(title.into())
|
||||
.size(11)
|
||||
.style(|theme: &Theme| text::Style {
|
||||
color: Some(theme.palette().background.strongest.text.scale_alpha(0.75)),
|
||||
}),
|
||||
body.into(),
|
||||
]
|
||||
.spacing(6),
|
||||
@@ -167,10 +170,9 @@ pub fn view_window<'a>(
|
||||
let palette = theme.palette();
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.primary.strong,
|
||||
(
|
||||
false,
|
||||
button::Status::Hovered | button::Status::Pressed,
|
||||
) => palette.background.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
palette.background.strong
|
||||
}
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
@@ -194,12 +196,27 @@ pub fn view_window<'a>(
|
||||
|
||||
let tab_row = row![
|
||||
tab_button(DraftingSettingsTab::SnapAndGrid, crate::t!("Snap and Grid")),
|
||||
tab_button(DraftingSettingsTab::PolarTracking, crate::t!("Polar Tracking")),
|
||||
tab_button(
|
||||
DraftingSettingsTab::PolarTracking,
|
||||
crate::t!("Polar Tracking")
|
||||
),
|
||||
tab_button(DraftingSettingsTab::ObjectSnap, crate::t!("Object Snap")),
|
||||
tab_button(DraftingSettingsTab::ObjectSnap3D, crate::t!("3D Object Snap")),
|
||||
tab_button(DraftingSettingsTab::DynamicInput, crate::t!("Dynamic Input")),
|
||||
tab_button(DraftingSettingsTab::QuickProperties, crate::t!("Quick Properties")),
|
||||
tab_button(DraftingSettingsTab::SelectionCycling, crate::t!("Selection Cycling")),
|
||||
tab_button(
|
||||
DraftingSettingsTab::ObjectSnap3D,
|
||||
crate::t!("3D Object Snap")
|
||||
),
|
||||
tab_button(
|
||||
DraftingSettingsTab::DynamicInput,
|
||||
crate::t!("Dynamic Input")
|
||||
),
|
||||
tab_button(
|
||||
DraftingSettingsTab::QuickProperties,
|
||||
crate::t!("Quick Properties")
|
||||
),
|
||||
tab_button(
|
||||
DraftingSettingsTab::SelectionCycling,
|
||||
crate::t!("Selection Cycling")
|
||||
),
|
||||
]
|
||||
.spacing(2)
|
||||
.width(Fill)
|
||||
@@ -209,9 +226,7 @@ pub fn view_window<'a>(
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.neutral.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -287,7 +302,11 @@ pub fn view_window<'a>(
|
||||
text(crate::t!("F5 cycles Left, Top, and Right.")).size(10.5),
|
||||
Space::new().height(4),
|
||||
row![
|
||||
text(crate::t!("Rotation: %{angle}°", angle = state.snap_angle_deg)).size(11),
|
||||
text(crate::t!(
|
||||
"Rotation: %{angle}°",
|
||||
angle = state.snap_angle_deg
|
||||
))
|
||||
.size(11),
|
||||
button(text(crate::t!("Reset rotation")).size(10))
|
||||
.on_press(Message::DraftingSettingsResetRotation)
|
||||
.style(button::secondary)
|
||||
@@ -488,8 +507,7 @@ pub fn view_window<'a>(
|
||||
);
|
||||
|
||||
column![
|
||||
row![osnap_on_toggle, Space::new().width(24), otrack_on_toggle]
|
||||
.align_y(iced::Center),
|
||||
row![osnap_on_toggle, Space::new().width(24), otrack_on_toggle].align_y(iced::Center),
|
||||
action_buttons,
|
||||
Space::new().height(4),
|
||||
snap_modes_group,
|
||||
@@ -559,15 +577,15 @@ pub fn view_window<'a>(
|
||||
|
||||
let prompt_group = group(
|
||||
crate::t!("Dynamic Prompts"),
|
||||
column![
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Show command prompting and command input near crosshairs"))
|
||||
.size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
column![row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!(
|
||||
"Show command prompting and command input near crosshairs"
|
||||
))
|
||||
.size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
@@ -608,13 +626,9 @@ pub fn view_window<'a>(
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![
|
||||
quick_props_toggle,
|
||||
Space::new().height(4),
|
||||
palette_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
column![quick_props_toggle, Space::new().height(4), palette_group,]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Tab 7: Selection Cycling ─────────────────────────────────────────
|
||||
@@ -644,13 +658,9 @@ pub fn view_window<'a>(
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![
|
||||
sel_cycling_toggle,
|
||||
Space::new().height(4),
|
||||
cycling_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
column![sel_cycling_toggle, Space::new().height(4), cycling_group,]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Active Tab Content ───────────────────────────────────────────────
|
||||
@@ -719,4 +729,3 @@ pub fn view_window<'a>(
|
||||
Message::DraftingSettingsCloseKeep,
|
||||
)
|
||||
}
|
||||
|
||||
+481
-120
@@ -2,7 +2,9 @@
|
||||
//! dialogs): a category and field list on the left, the chosen field's
|
||||
//! format on the right, a live preview and the field expression below.
|
||||
|
||||
use iced::widget::{button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Element, Fill, Length};
|
||||
|
||||
use crate::app::Message;
|
||||
@@ -26,17 +28,49 @@ pub enum FieldTarget {
|
||||
/// application evaluates).
|
||||
pub const CATEGORIES: &[(&str, &[&str])] = &[
|
||||
("All", &[]),
|
||||
("Date & Time", &["CreateDate", "Date", "PlotDate", "SaveDate"]),
|
||||
(
|
||||
"Date & Time",
|
||||
&["CreateDate", "Date", "PlotDate", "SaveDate"],
|
||||
),
|
||||
(
|
||||
"Document",
|
||||
&["Author", "Comments", "Filename", "Filesize", "HyperlinkBase", "Keywords", "LastSavedBy", "Subject", "Title"],
|
||||
&[
|
||||
"Author",
|
||||
"Comments",
|
||||
"Filename",
|
||||
"Filesize",
|
||||
"HyperlinkBase",
|
||||
"Keywords",
|
||||
"LastSavedBy",
|
||||
"Subject",
|
||||
"Title",
|
||||
],
|
||||
),
|
||||
("Linked", &["Hyperlink"]),
|
||||
("Objects", &["BlockPlaceholder", "Count", "CountInArea", "Formula", "NamedObject", "Object"]),
|
||||
(
|
||||
"Objects",
|
||||
&[
|
||||
"BlockPlaceholder",
|
||||
"Count",
|
||||
"CountInArea",
|
||||
"Formula",
|
||||
"NamedObject",
|
||||
"Object",
|
||||
],
|
||||
),
|
||||
("Other", &["DieselExpression", "SystemVariable"]),
|
||||
(
|
||||
"Plot",
|
||||
&["DeviceName", "Login", "PageSetupName", "PaperSize", "PlotDate", "PlotOrientation", "PlotScale", "PlotStyleTable"],
|
||||
&[
|
||||
"DeviceName",
|
||||
"Login",
|
||||
"PageSetupName",
|
||||
"PaperSize",
|
||||
"PlotDate",
|
||||
"PlotOrientation",
|
||||
"PlotScale",
|
||||
"PlotStyleTable",
|
||||
],
|
||||
),
|
||||
("SheetSet", SHEET_SET_FIELDS),
|
||||
];
|
||||
@@ -92,24 +126,40 @@ fn sheet_set_code(name: &str, custom: &str) -> String {
|
||||
|
||||
/// The navigation tree of a SheetSet (set, subsets, sheets) or SheetView
|
||||
/// (set, view categories) field: depth, component id, label, is a sheet.
|
||||
pub fn nav_nodes(db: &codec::sheet_set::SheetSetDatabase, views: bool) -> Vec<(u16, String, String, bool)> {
|
||||
pub fn nav_nodes(
|
||||
db: &codec::sheet_set::SheetSetDatabase,
|
||||
views: bool,
|
||||
) -> Vec<(u16, String, String, bool)> {
|
||||
use codec::sheet_set::{number_and_title, ComponentKind, Element as SsElement};
|
||||
let set = db.sheet_set();
|
||||
let mut out = vec![(0, set.id().to_string(), db.name().to_string(), false)];
|
||||
if views {
|
||||
// Views of the unnamed (default) category sit under the set, then each
|
||||
// named category with its views.
|
||||
let all: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect();
|
||||
let all: Vec<&SsElement> = db
|
||||
.sheets()
|
||||
.into_iter()
|
||||
.flat_map(|s| db.sheet_views(s))
|
||||
.collect();
|
||||
let named: Vec<&SsElement> = db.view_categories();
|
||||
let in_named = |v: &SsElement| {
|
||||
codec::sheet_set::SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))
|
||||
codec::sheet_set::SheetSetDatabase::view_category_of(v)
|
||||
.is_some_and(|c| named.iter().any(|n| n.id() == c))
|
||||
};
|
||||
for v in all.iter().filter(|v| !in_named(v)) {
|
||||
out.push((1, v.id().to_string(), number_and_title(v), true));
|
||||
}
|
||||
for c in &named {
|
||||
out.push((1, c.id().to_string(), c.prop("Name").unwrap_or("").to_string(), false));
|
||||
for v in all.iter().filter(|v| codec::sheet_set::SheetSetDatabase::view_category_of(v) == Some(c.id())) {
|
||||
out.push((
|
||||
1,
|
||||
c.id().to_string(),
|
||||
c.prop("Name").unwrap_or("").to_string(),
|
||||
false,
|
||||
));
|
||||
for v in all
|
||||
.iter()
|
||||
.filter(|v| codec::sheet_set::SheetSetDatabase::view_category_of(v) == Some(c.id()))
|
||||
{
|
||||
out.push((2, v.id().to_string(), number_and_title(v), true));
|
||||
}
|
||||
}
|
||||
@@ -119,10 +169,17 @@ pub fn nav_nodes(db: &codec::sheet_set::SheetSetDatabase, views: bool) -> Vec<(u
|
||||
for c in &el.children {
|
||||
match ComponentKind::of(c) {
|
||||
Some(ComponentKind::Subset) => {
|
||||
out.push((depth, c.id().to_string(), c.prop("Name").unwrap_or("").to_string(), false));
|
||||
out.push((
|
||||
depth,
|
||||
c.id().to_string(),
|
||||
c.prop("Name").unwrap_or("").to_string(),
|
||||
false,
|
||||
));
|
||||
go(c, depth + 1, out);
|
||||
}
|
||||
Some(ComponentKind::Sheet) => out.push((depth, c.id().to_string(), number_and_title(c), true)),
|
||||
Some(ComponentKind::Sheet) => {
|
||||
out.push((depth, c.id().to_string(), number_and_title(c), true))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -132,14 +189,27 @@ pub fn nav_nodes(db: &codec::sheet_set::SheetSetDatabase, views: bool) -> Vec<(u
|
||||
}
|
||||
|
||||
/// The properties a navigation node offers: list label, code property, version.
|
||||
pub fn nav_props(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Vec<(String, String, &'static str)> {
|
||||
use codec::sheet_set::{custom_properties, ComponentKind, CUSTOM_SHEET_PROP, CUSTOM_SHEET_SET_PROP};
|
||||
let Some(el) = db.find(id) else { return Vec::new() };
|
||||
pub fn nav_props(
|
||||
db: &codec::sheet_set::SheetSetDatabase,
|
||||
id: &str,
|
||||
) -> Vec<(String, String, &'static str)> {
|
||||
use codec::sheet_set::{
|
||||
custom_properties, ComponentKind, CUSTOM_SHEET_PROP, CUSTOM_SHEET_SET_PROP,
|
||||
};
|
||||
let Some(el) = db.find(id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let fixed = |list: &[(&str, &str, &'static str)]| -> Vec<(String, String, &'static str)> {
|
||||
list.iter().map(|(l, p, v)| (l.to_string(), p.to_string(), *v)).collect()
|
||||
list.iter()
|
||||
.map(|(l, p, v)| (l.to_string(), p.to_string(), *v))
|
||||
.collect()
|
||||
};
|
||||
let custom = |flag: i32| -> Vec<(String, String, &'static str)> {
|
||||
custom_properties(db.sheet_set()).into_iter().filter(|(_, _, f)| f & flag != 0).map(|(n, _, _)| (n.clone(), n, "")).collect()
|
||||
custom_properties(db.sheet_set())
|
||||
.into_iter()
|
||||
.filter(|(_, _, f)| f & flag != 0)
|
||||
.map(|(n, _, _)| (n.clone(), n, ""))
|
||||
.collect()
|
||||
};
|
||||
match ComponentKind::of(el) {
|
||||
Some(ComponentKind::SheetSet) => {
|
||||
@@ -180,13 +250,24 @@ pub fn nav_props(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Vec<(Stri
|
||||
}
|
||||
|
||||
/// The sheet drawing, named view and layout a sheet or sheet view node links to.
|
||||
pub fn nav_link(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Option<(String, String, String)> {
|
||||
pub fn nav_link(
|
||||
db: &codec::sheet_set::SheetSetDatabase,
|
||||
id: &str,
|
||||
) -> Option<(String, String, String)> {
|
||||
let el = db.find(id)?;
|
||||
let (sheet, view) = if el.name == "AcSmSheetView" {
|
||||
let views = db.parent_of(id)?;
|
||||
let sheet = db.parent_of(views.id())?;
|
||||
(sheet.id().to_string(), el.named("NamedView").and_then(|v| v.prop("Name")).unwrap_or("").to_string())
|
||||
} else if codec::sheet_set::ComponentKind::of(el) == Some(codec::sheet_set::ComponentKind::Sheet) {
|
||||
(
|
||||
sheet.id().to_string(),
|
||||
el.named("NamedView")
|
||||
.and_then(|v| v.prop("Name"))
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
)
|
||||
} else if codec::sheet_set::ComponentKind::of(el)
|
||||
== Some(codec::sheet_set::ComponentKind::Sheet)
|
||||
{
|
||||
(id.to_string(), String::new())
|
||||
} else {
|
||||
return None;
|
||||
@@ -199,8 +280,13 @@ impl FieldDialogState {
|
||||
/// `\AcSm Database("…").SheetSet("…")[.Component("…")].Property` of the
|
||||
/// picked node, with the text case and, on a sheet, the hyperlink to it.
|
||||
fn navigation_code(&self) -> String {
|
||||
let Some(db) = self.ss_sets.get(self.ss_set) else { return "\\AcSm".into() };
|
||||
let Some((_, prop, version)) = nav_props(db, &self.ss_node).into_iter().find(|(l, _, _)| *l == self.ss_prop) else {
|
||||
let Some(db) = self.ss_sets.get(self.ss_set) else {
|
||||
return "\\AcSm".into();
|
||||
};
|
||||
let Some((_, prop, version)) = nav_props(db, &self.ss_node)
|
||||
.into_iter()
|
||||
.find(|(l, _, _)| *l == self.ss_prop)
|
||||
else {
|
||||
return "\\AcSm".into();
|
||||
};
|
||||
let set = db.sheet_set().id().to_string();
|
||||
@@ -210,7 +296,13 @@ impl FieldDialogState {
|
||||
let version = if scale {
|
||||
codec::fields::PLOT_SCALE_FORMATS
|
||||
.get(self.ss_scale_format)
|
||||
.map_or("", |(_, c)| if c.starts_with("\\AcVar.16.2") { ".16.2" } else { "" })
|
||||
.map_or("", |(_, c)| {
|
||||
if c.starts_with("\\AcVar.16.2") {
|
||||
".16.2"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
})
|
||||
} else {
|
||||
version
|
||||
};
|
||||
@@ -269,25 +361,67 @@ pub fn viewport_scale_picture(k: usize) -> String {
|
||||
/// The fields of a category; "All" lists every field once, sorted.
|
||||
pub fn fields_of(category: usize) -> Vec<&'static str> {
|
||||
if category == 0 {
|
||||
let mut all: Vec<&'static str> = CATEGORIES.iter().flat_map(|(_, f)| f.iter().copied()).collect();
|
||||
let mut all: Vec<&'static str> = CATEGORIES
|
||||
.iter()
|
||||
.flat_map(|(_, f)| f.iter().copied())
|
||||
.collect();
|
||||
all.sort_unstable_by_key(|s| s.to_ascii_lowercase());
|
||||
all.dedup();
|
||||
return all;
|
||||
}
|
||||
CATEGORIES.get(category).map(|(_, f)| f.to_vec()).unwrap_or_default()
|
||||
CATEGORIES
|
||||
.get(category)
|
||||
.map(|(_, f)| f.to_vec())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The date formats the dialog lists, in the reference order.
|
||||
pub const DATE_FORMATS: &[&str] = &[
|
||||
"M/d/yyyy", "dddd, MMMM dd, yyyy", "MMMM d, yyyy", "M/d/yy", "yyyy-MM-dd", "d-MMM-yy",
|
||||
"M.d.yyyy", "MMM. d, yy", "d MMMM yyyy", "dd.MM.yyyy", "dd/MM/yyyy", "yyyy/MM/dd",
|
||||
"yyyy-M-d", "MMMM yy", "MMM-yy", "M/d/yyyy h:mm tt", "M/d/yyyy h:mm:ss tt", "h:mm tt",
|
||||
"h:mm:ss tt", "HH:mm", "HH:mm:ss", "%#x", "%#c", "%x", "%c", "%X",
|
||||
"M/d/yyyy",
|
||||
"dddd, MMMM dd, yyyy",
|
||||
"MMMM d, yyyy",
|
||||
"M/d/yy",
|
||||
"yyyy-MM-dd",
|
||||
"d-MMM-yy",
|
||||
"M.d.yyyy",
|
||||
"MMM. d, yy",
|
||||
"d MMMM yyyy",
|
||||
"dd.MM.yyyy",
|
||||
"dd/MM/yyyy",
|
||||
"yyyy/MM/dd",
|
||||
"yyyy-M-d",
|
||||
"MMMM yy",
|
||||
"MMM-yy",
|
||||
"M/d/yyyy h:mm tt",
|
||||
"M/d/yyyy h:mm:ss tt",
|
||||
"h:mm tt",
|
||||
"h:mm:ss tt",
|
||||
"HH:mm",
|
||||
"HH:mm:ss",
|
||||
"%#x",
|
||||
"%#c",
|
||||
"%x",
|
||||
"%c",
|
||||
"%X",
|
||||
];
|
||||
|
||||
pub const TEXT_CASES: &[&str] = &["(none)", "Uppercase", "Lowercase", "First capital", "Title case"];
|
||||
pub const TEXT_CASES: &[&str] = &[
|
||||
"(none)",
|
||||
"Uppercase",
|
||||
"Lowercase",
|
||||
"First capital",
|
||||
"Title case",
|
||||
];
|
||||
pub const SIZE_UNITS: &[&str] = &["Bytes", "Kilobytes", "Megabytes"];
|
||||
pub const NAMED_TYPES: &[&str] = &["Block", "Dimstyle", "Layer", "Linetype", "Tablestyle", "Textstyle", "View"];
|
||||
pub const NAMED_TYPES: &[&str] = &[
|
||||
"Block",
|
||||
"Dimstyle",
|
||||
"Layer",
|
||||
"Linetype",
|
||||
"Tablestyle",
|
||||
"Textstyle",
|
||||
"View",
|
||||
];
|
||||
|
||||
/// How a field is formatted, by its name.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -316,8 +450,18 @@ pub enum FieldKind {
|
||||
}
|
||||
|
||||
/// Formula precisions: the current one, then 0 to 8 decimals.
|
||||
pub const FORMULA_PRECISIONS: &[&str] =
|
||||
&["Current precision", "0", "0.0", "0.00", "0.000", "0.0000", "0.00000", "0.000000", "0.0000000", "0.00000000"];
|
||||
pub const FORMULA_PRECISIONS: &[&str] = &[
|
||||
"Current precision",
|
||||
"0",
|
||||
"0.0",
|
||||
"0.00",
|
||||
"0.000",
|
||||
"0.0000",
|
||||
"0.00000",
|
||||
"0.000000",
|
||||
"0.0000000",
|
||||
"0.00000000",
|
||||
];
|
||||
|
||||
pub fn kind_of(name: &str) -> FieldKind {
|
||||
match name {
|
||||
@@ -450,10 +594,18 @@ impl FieldDialogState {
|
||||
n => format!("{base} \\f \"%tc{n}\""),
|
||||
};
|
||||
match kind_of(self.name) {
|
||||
FieldKind::Date => (format!("\\AcVar {} \\f \"{}\"", self.name, self.date_format), vec![]),
|
||||
FieldKind::Date => (
|
||||
format!("\\AcVar {} \\f \"{}\"", self.name, self.date_format),
|
||||
vec![],
|
||||
),
|
||||
FieldKind::Text => (case(format!("\\AcVar {}", self.name)), vec![]),
|
||||
FieldKind::Filename => {
|
||||
let bits = self.file_parts + if self.file_extension && self.file_parts & 2 != 0 { 4 } else { 0 };
|
||||
let bits = self.file_parts
|
||||
+ if self.file_extension && self.file_parts & 2 != 0 {
|
||||
4
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// The text case goes first in the same format: "%tc1%fn6".
|
||||
let case = match self.text_case {
|
||||
0 => String::new(),
|
||||
@@ -470,7 +622,9 @@ impl FieldDialogState {
|
||||
vec![],
|
||||
),
|
||||
FieldKind::Formula => {
|
||||
let format = codec::fields::FORMULA_FORMATS.get(self.formula_format).map_or("", |f| f.1);
|
||||
let format = codec::fields::FORMULA_FORMATS
|
||||
.get(self.formula_format)
|
||||
.map_or("", |f| f.1);
|
||||
let precision = match self.formula_precision {
|
||||
0 => String::new(),
|
||||
n => format!("%pr{}", n - 1),
|
||||
@@ -483,7 +637,11 @@ impl FieldDialogState {
|
||||
}
|
||||
}
|
||||
FieldKind::Hyperlink => (
|
||||
format!("\\AcVar \\href \"{}##{}#0\"", self.hyperlink_url.trim(), self.hyperlink_text),
|
||||
format!(
|
||||
"\\AcVar \\href \"{}##{}#0\"",
|
||||
self.hyperlink_url.trim(),
|
||||
self.hyperlink_text
|
||||
),
|
||||
vec![],
|
||||
),
|
||||
FieldKind::PlotScale => (
|
||||
@@ -493,22 +651,39 @@ impl FieldDialogState {
|
||||
vec![],
|
||||
),
|
||||
FieldKind::Count => {
|
||||
let evaluator = if self.name == "CountInArea" { "AcCount2" } else { "AcCount" };
|
||||
let evaluator = if self.name == "CountInArea" {
|
||||
"AcCount2"
|
||||
} else {
|
||||
"AcCount"
|
||||
};
|
||||
match self.count_expression.trim() {
|
||||
"" => (format!("\\{evaluator}"), vec![]),
|
||||
json => (format!("\\{evaluator} {json}"), vec![]),
|
||||
}
|
||||
}
|
||||
FieldKind::SheetSet | FieldKind::SheetSetCustom => (case(sheet_set_code(self.name, &self.ss_custom)), vec![]),
|
||||
FieldKind::SheetSet | FieldKind::SheetSetCustom => {
|
||||
(case(sheet_set_code(self.name, &self.ss_custom)), vec![])
|
||||
}
|
||||
FieldKind::SheetSetNavigation => (self.navigation_code(), vec![]),
|
||||
FieldKind::SheetSetPlaceholder => {
|
||||
let (label, target, version) = PLACEHOLDER_TYPES[self.ss_placeholder.min(PLACEHOLDER_TYPES.len() - 1)];
|
||||
let target = if label == "Custom" { format!("{target}{}", self.ss_custom) } else { target.to_string() };
|
||||
let (label, target, version) =
|
||||
PLACEHOLDER_TYPES[self.ss_placeholder.min(PLACEHOLDER_TYPES.len() - 1)];
|
||||
let target = if label == "Custom" {
|
||||
format!("{target}{}", self.ss_custom)
|
||||
} else {
|
||||
target.to_string()
|
||||
};
|
||||
if label == "ViewportScale" {
|
||||
// The version follows the format, as for PlotScale.
|
||||
let version = codec::fields::PLOT_SCALE_FORMATS
|
||||
.get(self.ss_scale_format)
|
||||
.map_or("", |(_, code)| if code.starts_with("\\AcVar.16.2") { ".16.2" } else { "" });
|
||||
.map_or("", |(_, code)| {
|
||||
if code.starts_with("\\AcVar.16.2") {
|
||||
".16.2"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
});
|
||||
let base = format!("\\AcSm{version} {target}");
|
||||
match viewport_scale_picture(self.ss_scale_format) {
|
||||
f if f.is_empty() => (base, vec![]),
|
||||
@@ -519,7 +694,8 @@ impl FieldDialogState {
|
||||
}
|
||||
}
|
||||
FieldKind::BlockPlaceholder => {
|
||||
let (_, property, format) = codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[self.placeholder_property];
|
||||
let (_, property, format) =
|
||||
codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[self.placeholder_property];
|
||||
// Text properties take the chosen case; the others keep their own format.
|
||||
let format = match (format, self.text_case) {
|
||||
("%tc4", 0) => String::new(),
|
||||
@@ -536,13 +712,17 @@ impl FieldDialogState {
|
||||
FieldKind::SystemVariable => (format!("\\AcVar {}", self.sysvar), vec![]),
|
||||
FieldKind::Diesel => (format!("\\AcDiesel {}", self.diesel), vec![]),
|
||||
FieldKind::NamedObject => match self.named.and_then(|i| self.named_names.get(i)) {
|
||||
Some((_, handle)) => (case("\\AcObjProp Object(%<\\_ObjIdx 0>%).Name".into()), vec![*handle]),
|
||||
Some((_, handle)) => (
|
||||
case("\\AcObjProp Object(%<\\_ObjIdx 0>%).Name".into()),
|
||||
vec![*handle],
|
||||
),
|
||||
None => ("\\AcObjProp".into(), vec![]),
|
||||
},
|
||||
FieldKind::Object => match (&self.object, self.object_prop) {
|
||||
(Some((handle, _)), Some(prop)) => {
|
||||
(format!("\\AcObjProp Object(%<\\_ObjIdx 0>%).{prop}"), vec![*handle])
|
||||
}
|
||||
(Some((handle, _)), Some(prop)) => (
|
||||
format!("\\AcObjProp Object(%<\\_ObjIdx 0>%).{prop}"),
|
||||
vec![*handle],
|
||||
),
|
||||
_ => ("\\AcObjProp".into(), vec![]),
|
||||
},
|
||||
}
|
||||
@@ -572,12 +752,17 @@ fn indexed_object_ids(formula: &str) -> (String, Vec<codec::Handle>) {
|
||||
while let Some(start) = rest.find("%<\\_ObjId ") {
|
||||
let after = &rest[start + "%<\\_ObjId ".len()..];
|
||||
let Some(end) = after.find(">%") else { break };
|
||||
let Ok(value) = u64::from_str_radix(after[..end].trim(), 16) else { break };
|
||||
let Ok(value) = u64::from_str_radix(after[..end].trim(), 16) else {
|
||||
break;
|
||||
};
|
||||
let handle = codec::Handle::new(value);
|
||||
let index = objects.iter().position(|h| *h == handle).unwrap_or_else(|| {
|
||||
objects.push(handle);
|
||||
objects.len() - 1
|
||||
});
|
||||
let index = objects
|
||||
.iter()
|
||||
.position(|h| *h == handle)
|
||||
.unwrap_or_else(|| {
|
||||
objects.push(handle);
|
||||
objects.len() - 1
|
||||
});
|
||||
out.push_str(&rest[..start]);
|
||||
out.push_str(&format!("%<\\_ObjIdx {index}>%"));
|
||||
rest = &after[end + 2..];
|
||||
@@ -656,11 +841,17 @@ fn msg(m: FieldDialogMsg) -> Message {
|
||||
}
|
||||
|
||||
fn card<'a>(title: String, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
container(column![text(title.to_uppercase()).size(10).style(accent_text), content.into()].spacing(8))
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
container(
|
||||
column![
|
||||
text(title.to_uppercase()).size(10).style(accent_text),
|
||||
content.into()
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A selectable list: one row per item, the chosen one highlighted.
|
||||
@@ -673,11 +864,13 @@ fn list<'a>(rows: Vec<(String, bool, FieldDialogMsg)>, height: f32) -> Element<'
|
||||
.width(Fill)
|
||||
.into()
|
||||
});
|
||||
container(scrollable(iced::widget::Column::with_children(rows.collect::<Vec<Element<'a, Message>>>()).spacing(1)))
|
||||
.height(Length::Fixed(height))
|
||||
.padding(2)
|
||||
.style(|theme: &iced::Theme| field_style(theme, text_input::Status::Active).into_container())
|
||||
.into()
|
||||
container(scrollable(
|
||||
iced::widget::Column::with_children(rows.collect::<Vec<Element<'a, Message>>>()).spacing(1),
|
||||
))
|
||||
.height(Length::Fixed(height))
|
||||
.padding(2)
|
||||
.style(|theme: &iced::Theme| field_style(theme, text_input::Status::Active).into_container())
|
||||
.into()
|
||||
}
|
||||
|
||||
trait IntoContainer {
|
||||
@@ -707,7 +900,13 @@ fn case_list<'a>(state: &FieldDialogState) -> Element<'a, Message> {
|
||||
TEXT_CASES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| (t!(*c).into_owned(), state.text_case == i, FieldDialogMsg::TextCase(i)))
|
||||
.map(|(i, c)| {
|
||||
(
|
||||
t!(*c).into_owned(),
|
||||
state.text_case == i,
|
||||
FieldDialogMsg::TextCase(i),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
130.0,
|
||||
)
|
||||
@@ -732,14 +931,20 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
examples
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, e)| (e.clone(), DATE_FORMATS.get(i) == Some(&state.date_format.as_str()), FieldDialogMsg::DateExample(i)))
|
||||
.map(|(i, e)| (
|
||||
e.clone(),
|
||||
DATE_FORMATS.get(i) == Some(&state.date_format.as_str()),
|
||||
FieldDialogMsg::DateExample(i)
|
||||
))
|
||||
.collect(),
|
||||
220.0,
|
||||
),
|
||||
]
|
||||
.spacing(8)
|
||||
.into(),
|
||||
FieldKind::Text => column![text(t!("Format")).size(12), case_list(state)].spacing(8).into(),
|
||||
FieldKind::Text => column![text(t!("Format")).size(12), case_list(state)]
|
||||
.spacing(8)
|
||||
.into(),
|
||||
FieldKind::Filename => {
|
||||
let part = |label: &str, bits: u8| {
|
||||
checkbox(state.file_parts == bits)
|
||||
@@ -772,7 +977,11 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
SIZE_UNITS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, u)| (t!(*u).into_owned(), state.size_unit == i, FieldDialogMsg::SizeUnit(i)))
|
||||
.map(|(i, u)| (
|
||||
t!(*u).into_owned(),
|
||||
state.size_unit == i,
|
||||
FieldDialogMsg::SizeUnit(i)
|
||||
))
|
||||
.collect(),
|
||||
90.0,
|
||||
),
|
||||
@@ -784,7 +993,11 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
list(
|
||||
crate::entities::field::SYSVARS
|
||||
.iter()
|
||||
.map(|v| (v.to_string(), state.sysvar == *v, FieldDialogMsg::SysVar(v.to_string())))
|
||||
.map(|v| (
|
||||
v.to_string(),
|
||||
state.sysvar == *v,
|
||||
FieldDialogMsg::SysVar(v.to_string())
|
||||
))
|
||||
.collect(),
|
||||
250.0,
|
||||
),
|
||||
@@ -803,10 +1016,19 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.into(),
|
||||
FieldKind::NamedObject => column![
|
||||
row![
|
||||
text(t!("Named object type")).size(12).width(Length::Fixed(120.0)),
|
||||
text(t!("Named object type"))
|
||||
.size(12)
|
||||
.width(Length::Fixed(120.0)),
|
||||
pick_list(
|
||||
Some(Pick(state.named_type, t!(NAMED_TYPES[state.named_type]).into_owned())),
|
||||
NAMED_TYPES.iter().enumerate().map(|(i, n)| Pick(i, t!(*n).into_owned())).collect::<Vec<_>>(),
|
||||
Some(Pick(
|
||||
state.named_type,
|
||||
t!(NAMED_TYPES[state.named_type]).into_owned()
|
||||
)),
|
||||
NAMED_TYPES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| Pick(i, t!(*n).into_owned()))
|
||||
.collect::<Vec<_>>(),
|
||||
|p: &Pick| p.1.clone(),
|
||||
)
|
||||
.on_select(|p| msg(FieldDialogMsg::NamedType(p.0)))
|
||||
@@ -822,7 +1044,11 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.named_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (n, _))| (n.clone(), state.named == Some(i), FieldDialogMsg::Named(i)))
|
||||
.map(|(i, (n, _))| (
|
||||
n.clone(),
|
||||
state.named == Some(i),
|
||||
FieldDialogMsg::Named(i)
|
||||
))
|
||||
.collect(),
|
||||
150.0,
|
||||
),
|
||||
@@ -837,16 +1063,23 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.enumerate()
|
||||
.map(|(i, (n, _))| Pick(i, t!(*n).into_owned()))
|
||||
.collect();
|
||||
let precisions: Vec<Pick> =
|
||||
FORMULA_PRECISIONS.iter().enumerate().map(|(i, n)| Pick(i, t!(*n).into_owned())).collect();
|
||||
let precisions: Vec<Pick> = FORMULA_PRECISIONS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| Pick(i, t!(*n).into_owned()))
|
||||
.collect();
|
||||
column![
|
||||
row![
|
||||
text(t!("Format")).size(12).width(Length::Fixed(80.0)),
|
||||
pick_list(Some(formats[state.formula_format].clone()), formats.clone(), |p: &Pick| p.1.clone())
|
||||
.on_select(|p| msg(FieldDialogMsg::FormulaFormat(p.0)))
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
pick_list(
|
||||
Some(formats[state.formula_format].clone()),
|
||||
formats.clone(),
|
||||
|p: &Pick| p.1.clone()
|
||||
)
|
||||
.on_select(|p| msg(FieldDialogMsg::FormulaFormat(p.0)))
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -870,7 +1103,9 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
}
|
||||
FieldKind::Hyperlink => column![
|
||||
row![
|
||||
text(t!("Text to display")).size(12).width(Length::Fixed(120.0)),
|
||||
text(t!("Text to display"))
|
||||
.size(12)
|
||||
.width(Length::Fixed(120.0)),
|
||||
text_input("", &state.hyperlink_text)
|
||||
.size(12)
|
||||
.padding([5, 8])
|
||||
@@ -898,13 +1133,19 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.spacing(8)
|
||||
.into(),
|
||||
FieldKind::BlockPlaceholder => match &state.placeholder_block {
|
||||
None => column![text(t!("Only accessible in the block editor")).size(12).style(muted_style)].into(),
|
||||
None => column![text(t!("Only accessible in the block editor"))
|
||||
.size(12)
|
||||
.style(muted_style)]
|
||||
.into(),
|
||||
Some(block) => {
|
||||
let (_, _, format) = codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[state.placeholder_property];
|
||||
let (_, _, format) =
|
||||
codec::fields::BLOCK_PLACEHOLDER_PROPERTIES[state.placeholder_property];
|
||||
let mut panel = column![
|
||||
row![
|
||||
text(t!("Block name")).size(12).width(Length::Fixed(110.0)),
|
||||
container(text(block.clone()).size(12).style(muted_style)).padding([5, 8]).width(Fill),
|
||||
container(text(block.clone()).size(12).style(muted_style))
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -914,7 +1155,11 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (label, _, _))| {
|
||||
(t!(*label).into_owned(), state.placeholder_property == i, FieldDialogMsg::PlaceholderProperty(i))
|
||||
(
|
||||
t!(*label).into_owned(),
|
||||
state.placeholder_property == i,
|
||||
FieldDialogMsg::PlaceholderProperty(i),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
150.0,
|
||||
@@ -922,7 +1167,9 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
]
|
||||
.spacing(8);
|
||||
if format == "%tc4" {
|
||||
panel = panel.push(text(t!("Format")).size(12)).push(case_list(state));
|
||||
panel = panel
|
||||
.push(text(t!("Format")).size(12))
|
||||
.push(case_list(state));
|
||||
}
|
||||
panel.into()
|
||||
}
|
||||
@@ -933,7 +1180,11 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
codec::fields::PLOT_SCALE_FORMATS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (n, _))| (t!(*n).into_owned(), state.plot_scale == i, FieldDialogMsg::PlotScale(i)))
|
||||
.map(|(i, (n, _))| (
|
||||
t!(*n).into_owned(),
|
||||
state.plot_scale == i,
|
||||
FieldDialogMsg::PlotScale(i)
|
||||
))
|
||||
.collect(),
|
||||
170.0,
|
||||
),
|
||||
@@ -941,9 +1192,15 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.spacing(8)
|
||||
.into(),
|
||||
FieldKind::Count => column![].into(),
|
||||
FieldKind::SheetSet => column![text(t!("Format")).size(12), case_list(state)].spacing(8).into(),
|
||||
FieldKind::SheetSet => column![text(t!("Format")).size(12), case_list(state)]
|
||||
.spacing(8)
|
||||
.into(),
|
||||
FieldKind::SheetSetCustom => {
|
||||
let names = if state.name == "CurrentSheetCustom" { &state.ss_sheet_custom } else { &state.ss_set_custom };
|
||||
let names = if state.name == "CurrentSheetCustom" {
|
||||
&state.ss_sheet_custom
|
||||
} else {
|
||||
&state.ss_set_custom
|
||||
};
|
||||
column![
|
||||
text(t!("Format")).size(12),
|
||||
case_list(state),
|
||||
@@ -967,7 +1224,13 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
PLACEHOLDER_TYPES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (n, _, _))| (n.to_string(), state.ss_placeholder == i, FieldDialogMsg::SsPlaceholder(i)))
|
||||
.map(|(i, (n, _, _))| {
|
||||
(
|
||||
n.to_string(),
|
||||
state.ss_placeholder == i,
|
||||
FieldDialogMsg::SsPlaceholder(i),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
200.0,
|
||||
);
|
||||
@@ -976,14 +1239,26 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
codec::fields::PLOT_SCALE_FORMATS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (n, _))| (t!(*n).into_owned(), state.ss_scale_format == i, FieldDialogMsg::SsScaleFormat(i)))
|
||||
.map(|(i, (n, _))| {
|
||||
(
|
||||
t!(*n).into_owned(),
|
||||
state.ss_scale_format == i,
|
||||
FieldDialogMsg::SsScaleFormat(i),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
170.0,
|
||||
)
|
||||
} else {
|
||||
case_list(state)
|
||||
};
|
||||
let mut col = column![text(t!("Placeholder type")).size(12), types, text(t!("Format")).size(12), format].spacing(8);
|
||||
let mut col = column![
|
||||
text(t!("Placeholder type")).size(12),
|
||||
types,
|
||||
text(t!("Format")).size(12),
|
||||
format
|
||||
]
|
||||
.spacing(8);
|
||||
if kind == "Custom" {
|
||||
col = col.push(text(t!("Custom property name")).size(12)).push(
|
||||
pick_list(
|
||||
@@ -1000,7 +1275,12 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
col.into()
|
||||
}
|
||||
FieldKind::SheetSetNavigation => {
|
||||
let picks: Vec<Pick> = state.ss_sets.iter().enumerate().map(|(i, d)| Pick(i, d.name().to_string())).collect();
|
||||
let picks: Vec<Pick> = state
|
||||
.ss_sets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, d)| Pick(i, d.name().to_string()))
|
||||
.collect();
|
||||
let current = picks.get(state.ss_set).cloned();
|
||||
let set_row = row![
|
||||
pick_list(current, picks, |p: &Pick| p.1.clone())
|
||||
@@ -1008,27 +1288,49 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
button(text("...").size(12)).on_press(msg(FieldDialogMsg::SsBrowse)).style(button_style(false)).padding([5, 10]),
|
||||
button(text("...").size(12))
|
||||
.on_press(msg(FieldDialogMsg::SsBrowse))
|
||||
.style(button_style(false))
|
||||
.padding([5, 10]),
|
||||
]
|
||||
.spacing(6);
|
||||
let db = state.ss_sets.get(state.ss_set);
|
||||
let nodes = db.map(|d| nav_nodes(d, state.name == "SheetView")).unwrap_or_default();
|
||||
let nodes = db
|
||||
.map(|d| nav_nodes(d, state.name == "SheetView"))
|
||||
.unwrap_or_default();
|
||||
let tree = list(
|
||||
nodes
|
||||
.iter()
|
||||
.map(|(depth, id, label, _)| {
|
||||
(format!("{}{label}", " ".repeat(usize::from(*depth))), *id == state.ss_node, FieldDialogMsg::SsNode(id.clone()))
|
||||
(
|
||||
format!("{}{label}", " ".repeat(usize::from(*depth))),
|
||||
*id == state.ss_node,
|
||||
FieldDialogMsg::SsNode(id.clone()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
118.0,
|
||||
);
|
||||
let props = db.map(|d| nav_props(d, &state.ss_node)).unwrap_or_default();
|
||||
let prop_list = list(
|
||||
props.iter().map(|(l, _, _)| (l.clone(), *l == state.ss_prop, FieldDialogMsg::SsProp(l.clone()))).collect(),
|
||||
props
|
||||
.iter()
|
||||
.map(|(l, _, _)| {
|
||||
(
|
||||
l.clone(),
|
||||
*l == state.ss_prop,
|
||||
FieldDialogMsg::SsProp(l.clone()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
92.0,
|
||||
);
|
||||
let linkable = db.is_some_and(|d| nav_link(d, &state.ss_node).is_some()) && state.ss_prop != "ViewportScale";
|
||||
let mut href = checkbox(state.ss_href && linkable).label(t!("Associate hyperlink").into_owned()).text_size(12).size(14);
|
||||
let linkable = db.is_some_and(|d| nav_link(d, &state.ss_node).is_some())
|
||||
&& state.ss_prop != "ViewportScale";
|
||||
let mut href = checkbox(state.ss_href && linkable)
|
||||
.label(t!("Associate hyperlink").into_owned())
|
||||
.text_size(12)
|
||||
.size(14);
|
||||
if linkable {
|
||||
href = href.on_toggle(|v| msg(FieldDialogMsg::SsHref(v)));
|
||||
}
|
||||
@@ -1037,7 +1339,13 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
codec::fields::PLOT_SCALE_FORMATS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (n, _))| (t!(*n).into_owned(), state.ss_scale_format == i, FieldDialogMsg::SsScaleFormat(i)))
|
||||
.map(|(i, (n, _))| {
|
||||
(
|
||||
t!(*n).into_owned(),
|
||||
state.ss_scale_format == i,
|
||||
FieldDialogMsg::SsScaleFormat(i),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
120.0,
|
||||
)
|
||||
@@ -1062,7 +1370,15 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
row![
|
||||
text(t!("Object type")).size(12).width(Length::Fixed(96.0)),
|
||||
container(
|
||||
text(state.object.as_ref().map(|(_, t)| t.clone()).unwrap_or_default()).size(12).style(muted_style)
|
||||
text(
|
||||
state
|
||||
.object
|
||||
.as_ref()
|
||||
.map(|(_, t)| t.clone())
|
||||
.unwrap_or_default()
|
||||
)
|
||||
.size(12)
|
||||
.style(muted_style)
|
||||
)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
@@ -1078,7 +1394,11 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> {
|
||||
state
|
||||
.object_props
|
||||
.iter()
|
||||
.map(|p| (t!(*p).into_owned(), state.object_prop == Some(*p), FieldDialogMsg::ObjectProp(p)))
|
||||
.map(|p| (
|
||||
t!(*p).into_owned(),
|
||||
state.object_prop == Some(*p),
|
||||
FieldDialogMsg::ObjectProp(p)
|
||||
))
|
||||
.collect(),
|
||||
180.0,
|
||||
),
|
||||
@@ -1092,17 +1412,25 @@ pub fn view<'a>(
|
||||
state: &'a FieldDialogState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let categories: Vec<Pick> = CATEGORIES.iter().enumerate().map(|(i, (c, _))| Pick(i, t!(*c).into_owned())).collect();
|
||||
let categories: Vec<Pick> = CATEGORIES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (c, _))| Pick(i, t!(*c).into_owned()))
|
||||
.collect();
|
||||
let left = card(
|
||||
t!("Field").into_owned(),
|
||||
column![
|
||||
row![
|
||||
text(t!("Category")).size(12).width(Length::Fixed(70.0)),
|
||||
pick_list(Some(categories[state.category].clone()), categories.clone(), |p: &Pick| p.1.clone())
|
||||
.on_select(|p| msg(FieldDialogMsg::Category(p.0)))
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
pick_list(
|
||||
Some(categories[state.category].clone()),
|
||||
categories.clone(),
|
||||
|p: &Pick| p.1.clone()
|
||||
)
|
||||
.on_select(|p| msg(FieldDialogMsg::Category(p.0)))
|
||||
.text_size(12)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -1118,9 +1446,16 @@ pub fn view<'a>(
|
||||
);
|
||||
let preview = card(
|
||||
t!("Preview").into_owned(),
|
||||
container(text(if state.preview.is_empty() { "----".to_string() } else { state.preview.clone() }).size(15))
|
||||
.padding([6, 8])
|
||||
.width(Fill),
|
||||
container(
|
||||
text(if state.preview.is_empty() {
|
||||
"----".to_string()
|
||||
} else {
|
||||
state.preview.clone()
|
||||
})
|
||||
.size(15),
|
||||
)
|
||||
.padding([6, 8])
|
||||
.width(Fill),
|
||||
);
|
||||
let right = match kind_of(state.name) {
|
||||
FieldKind::Formula => column![
|
||||
@@ -1152,7 +1487,11 @@ pub fn view<'a>(
|
||||
preview,
|
||||
]
|
||||
.spacing(10),
|
||||
FieldKind::Hyperlink => column![card(t!("Hyperlink").into_owned(), format_panel(state)), preview].spacing(10),
|
||||
FieldKind::Hyperlink => column![
|
||||
card(t!("Hyperlink").into_owned(), format_panel(state)),
|
||||
preview
|
||||
]
|
||||
.spacing(10),
|
||||
FieldKind::Count => column![
|
||||
card(
|
||||
t!("Expression").into_owned(),
|
||||
@@ -1180,27 +1519,49 @@ pub fn view<'a>(
|
||||
preview,
|
||||
]
|
||||
.spacing(10),
|
||||
FieldKind::BlockPlaceholder => column![card(t!("Block placeholder").into_owned(), format_panel(state)), preview].spacing(10),
|
||||
FieldKind::SheetSetNavigation => column![card(t!("Sheet set").into_owned(), format_panel(state)), preview].spacing(10),
|
||||
_ => column![card(t!("Format").into_owned(), format_panel(state)), preview].spacing(10),
|
||||
FieldKind::BlockPlaceholder => column![
|
||||
card(t!("Block placeholder").into_owned(), format_panel(state)),
|
||||
preview
|
||||
]
|
||||
.spacing(10),
|
||||
FieldKind::SheetSetNavigation => column![
|
||||
card(t!("Sheet set").into_owned(), format_panel(state)),
|
||||
preview
|
||||
]
|
||||
.spacing(10),
|
||||
_ => column![
|
||||
card(t!("Format").into_owned(), format_panel(state)),
|
||||
preview
|
||||
]
|
||||
.spacing(10),
|
||||
};
|
||||
let (code, _) = state.code();
|
||||
let expression = column![
|
||||
text(t!("Field expression")).size(12),
|
||||
container(text(format!("%<{code}>%")).size(12).font(iced::Font::MONOSPACE).style(muted_style))
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.style(|theme: &iced::Theme| field_style(theme, text_input::Status::Disabled).into_container()),
|
||||
container(
|
||||
text(format!("%<{code}>%"))
|
||||
.size(12)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.style(muted_style)
|
||||
)
|
||||
.padding([5, 8])
|
||||
.width(Fill)
|
||||
.style(
|
||||
|theme: &iced::Theme| field_style(theme, text_input::Status::Disabled).into_container()
|
||||
),
|
||||
]
|
||||
.spacing(4);
|
||||
let mut insert = button(text(t!("Insert")).size(12))
|
||||
.style(button_style(true))
|
||||
.padding([6, 16]);
|
||||
.style(button_style(true))
|
||||
.padding([6, 16]);
|
||||
if state.complete() {
|
||||
insert = insert.on_press(msg(FieldDialogMsg::Ok));
|
||||
}
|
||||
let footer = row![
|
||||
button(text("?").size(12)).on_press(msg(FieldDialogMsg::Help)).style(button_style(false)).padding([5, 11]),
|
||||
button(text("?").size(12))
|
||||
.on_press(msg(FieldDialogMsg::Help))
|
||||
.style(button_style(false))
|
||||
.padding([5, 11]),
|
||||
Space::new().width(Fill),
|
||||
dialog_button(t!("Cancel"), Message::CloseModal, false),
|
||||
insert,
|
||||
|
||||
@@ -43,8 +43,10 @@ pub fn view_window<'a>(
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
text(t!("Searches Text, MText, Attribute Definitions, and block attribute values."))
|
||||
.size(11),
|
||||
text(t!(
|
||||
"Searches Text, MText, Attribute Definitions, and block attribute values."
|
||||
))
|
||||
.size(11),
|
||||
text(status).size(11),
|
||||
row![
|
||||
Space::new().width(field_width),
|
||||
|
||||
@@ -152,9 +152,7 @@ fn remove_escape(input: &str, code: &str) -> Option<String> {
|
||||
break;
|
||||
};
|
||||
let end = start + 1 + relative_end;
|
||||
if crate::entities::tolerance::symbol_font_switch(&input[start + 1..end])
|
||||
== Some(target)
|
||||
{
|
||||
if crate::entities::tolerance::symbol_font_switch(&input[start + 1..end]) == Some(target) {
|
||||
let mut remaining = String::with_capacity(input.len() - (end + 1 - start));
|
||||
remaining.push_str(&input[..start]);
|
||||
remaining.push_str(&input[end + 1..]);
|
||||
@@ -314,8 +312,14 @@ impl State {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.symbol.is_empty()
|
||||
|| !self.symbol_tail.is_empty()
|
||||
|| self.tolerances.iter().any(|entry| !entry.value.trim().is_empty())
|
||||
|| self.datums.iter().any(|entry| !entry.value.trim().is_empty())
|
||||
|| self
|
||||
.tolerances
|
||||
.iter()
|
||||
.any(|entry| !entry.value.trim().is_empty())
|
||||
|| self
|
||||
.datums
|
||||
.iter()
|
||||
.any(|entry| !entry.value.trim().is_empty())
|
||||
|| !self.projected_height.trim().is_empty()
|
||||
|| !self.datum_identifier.trim().is_empty()
|
||||
|| self.extra_rows.iter().any(|row| !row.trim().is_empty())
|
||||
@@ -332,7 +336,10 @@ impl State {
|
||||
&& self.symbol.is_empty()
|
||||
&& self.symbol_tail.is_empty()
|
||||
&& self.tolerances[1] == ToleranceEntry::default()
|
||||
&& self.datums.iter().all(|entry| *entry == DatumEntry::default());
|
||||
&& self
|
||||
.datums
|
||||
.iter()
|
||||
.all(|entry| *entry == DatumEntry::default());
|
||||
let frame = if only_plain_value {
|
||||
self.tolerances[0].value.clone()
|
||||
} else {
|
||||
@@ -407,10 +414,7 @@ fn material_picker<'a>(index: usize, datum: bool, code: &str) -> Element<'a, Mes
|
||||
iced::widget::pick_list(selected, options, |choice| choice.to_string())
|
||||
.on_select(move |choice| {
|
||||
if datum {
|
||||
Message::ToleranceDialogField(Field::DatumMaterial(
|
||||
index,
|
||||
choice.code.to_string(),
|
||||
))
|
||||
Message::ToleranceDialogField(Field::DatumMaterial(index, choice.code.to_string()))
|
||||
} else {
|
||||
Message::ToleranceDialogField(Field::ToleranceMaterial(
|
||||
index,
|
||||
@@ -442,60 +446,64 @@ pub fn view_window<'a>(
|
||||
.into(),
|
||||
);
|
||||
|
||||
let tolerance_rows = state.tolerances.iter().enumerate().fold(
|
||||
column![].spacing(6),
|
||||
|column, (index, entry)| {
|
||||
column.push(
|
||||
row![
|
||||
text(format!("{} {}", t!("Tolerance"), index + 1))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(82.0)),
|
||||
checkbox(entry.diameter)
|
||||
.on_toggle(move |value| Message::ToleranceDialogToggle(
|
||||
Toggle::Diameter(index, value)
|
||||
))
|
||||
.size(14),
|
||||
text(t!("Diameter")).size(11).width(Length::Fixed(62.0)),
|
||||
text_input(EMPTY, &entry.value)
|
||||
.on_input(move |value| Message::ToleranceDialogField(
|
||||
Field::ToleranceValue(index, value)
|
||||
))
|
||||
.size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(125.0)),
|
||||
material_picker(index, false, &entry.material),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
},
|
||||
);
|
||||
let tolerance_rows =
|
||||
state
|
||||
.tolerances
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(column![].spacing(6), |column, (index, entry)| {
|
||||
column.push(
|
||||
row![
|
||||
text(format!("{} {}", t!("Tolerance"), index + 1))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(82.0)),
|
||||
checkbox(entry.diameter)
|
||||
.on_toggle(move |value| Message::ToleranceDialogToggle(
|
||||
Toggle::Diameter(index, value)
|
||||
))
|
||||
.size(14),
|
||||
text(t!("Diameter")).size(11).width(Length::Fixed(62.0)),
|
||||
text_input(EMPTY, &entry.value)
|
||||
.on_input(move |value| Message::ToleranceDialogField(
|
||||
Field::ToleranceValue(index, value)
|
||||
))
|
||||
.size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(125.0)),
|
||||
material_picker(index, false, &entry.material),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
});
|
||||
let tolerances = panel(t!("Tolerance values").into_owned(), tolerance_rows.into());
|
||||
|
||||
let datum_rows = state.datums.iter().enumerate().fold(
|
||||
column![].spacing(6),
|
||||
|column, (index, entry)| {
|
||||
column.push(
|
||||
row![
|
||||
text(format!("{} {}", t!("Datum"), index + 1))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(82.0)),
|
||||
text_input(EMPTY, &entry.value)
|
||||
.on_input(move |value| Message::ToleranceDialogField(
|
||||
Field::DatumValue(index, value)
|
||||
))
|
||||
.size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(212.0)),
|
||||
material_picker(index, true, &entry.material),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
},
|
||||
);
|
||||
let datum_rows =
|
||||
state
|
||||
.datums
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(column![].spacing(6), |column, (index, entry)| {
|
||||
column.push(
|
||||
row![
|
||||
text(format!("{} {}", t!("Datum"), index + 1))
|
||||
.size(11)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(82.0)),
|
||||
text_input(EMPTY, &entry.value)
|
||||
.on_input(move |value| Message::ToleranceDialogField(
|
||||
Field::DatumValue(index, value)
|
||||
))
|
||||
.size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(212.0)),
|
||||
material_picker(index, true, &entry.material),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
});
|
||||
let datums = panel(t!("Datum references").into_owned(), datum_rows.into());
|
||||
|
||||
let additions = panel(
|
||||
@@ -512,9 +520,7 @@ pub fn view_window<'a>(
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(125.0)),
|
||||
checkbox(state.projected_zone)
|
||||
.on_toggle(|value| Message::ToleranceDialogToggle(
|
||||
Toggle::ProjectedZone(value)
|
||||
))
|
||||
.on_toggle(|value| Message::ToleranceDialogToggle(Toggle::ProjectedZone(value)))
|
||||
.size(14),
|
||||
text(t!("Projected tolerance zone")).size(11),
|
||||
]
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
//! Layer State Manager — native DWG/DXF named layer-state UI.
|
||||
|
||||
use crate::app::{LayerStateLayerFlag, LayerStateProperty, Message};
|
||||
use crate::t;
|
||||
use crate::ui::properties::{lw_options, LwItem};
|
||||
use codec::{LayerState, LayerStateMask};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use crate::t;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
@@ -39,14 +37,7 @@ fn transparency_options(current: Option<codec::types::Transparency>) -> Vec<Tran
|
||||
|
||||
fn muted(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.65),
|
||||
),
|
||||
color: Some(theme.palette().background.base.text.scale_alpha(0.65)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +66,7 @@ fn divider<'a>(width: Length) -> Element<'a, Message> {
|
||||
.width(width)
|
||||
.height(1)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.neutral.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
@@ -233,9 +222,11 @@ pub fn view_window<'a>(
|
||||
} else {
|
||||
column![
|
||||
text(t!("New layer state")).size(13),
|
||||
text(t!("Save captures the current settings of every layer in the drawing."))
|
||||
.size(11)
|
||||
.style(muted),
|
||||
text(t!(
|
||||
"Save captures the current settings of every layer in the drawing."
|
||||
))
|
||||
.size(11)
|
||||
.style(muted),
|
||||
]
|
||||
.spacing(7)
|
||||
};
|
||||
@@ -371,26 +362,57 @@ fn bool_cell<'a>(
|
||||
fn editor_header<'a>() -> Element<'a, Message> {
|
||||
container(
|
||||
row![
|
||||
text(t!("Layer")).size(10).style(muted).width(Length::Fixed(170.0)),
|
||||
text(t!("On")).size(10).style(muted).width(Length::Fixed(44.0)),
|
||||
text(t!("Freeze")).size(10).style(muted).width(Length::Fixed(54.0)),
|
||||
text(t!("Lock")).size(10).style(muted).width(Length::Fixed(44.0)),
|
||||
text(t!("Plot")).size(10).style(muted).width(Length::Fixed(44.0)),
|
||||
text(t!("New VP")).size(10).style(muted).width(Length::Fixed(54.0)),
|
||||
text(t!("Color")).size(10).style(muted).width(Length::Fixed(135.0)),
|
||||
text(t!("Linetype")).size(10).style(muted).width(Length::Fixed(150.0)),
|
||||
text(t!("Lineweight")).size(10).style(muted).width(Length::Fixed(115.0)),
|
||||
text(t!("Plot style")).size(10).style(muted).width(Length::Fixed(135.0)),
|
||||
text(t!("Transparency")).size(10).style(muted).width(Length::Fixed(105.0)),
|
||||
text(t!("Layer"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(170.0)),
|
||||
text(t!("On"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(44.0)),
|
||||
text(t!("Freeze"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(54.0)),
|
||||
text(t!("Lock"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(44.0)),
|
||||
text(t!("Plot"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(44.0)),
|
||||
text(t!("New VP"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(54.0)),
|
||||
text(t!("Color"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(135.0)),
|
||||
text(t!("Linetype"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(150.0)),
|
||||
text(t!("Lineweight"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(115.0)),
|
||||
text(t!("Plot style"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(135.0)),
|
||||
text(t!("Transparency"))
|
||||
.size(10)
|
||||
.style(muted)
|
||||
.width(Length::Fixed(105.0)),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.padding([5, 8])
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weak.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
@@ -420,10 +442,7 @@ fn editor_layer_row<'a>(
|
||||
},
|
||||
move |color| Message::LayerStateEditorLayerColor(index, color),
|
||||
Message::LayerStateEditorLayerColorToggle(index),
|
||||
Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::LayerState(index),
|
||||
layer.color,
|
||||
),
|
||||
Message::OpenColorWindow(crate::app::ColorPickTarget::LayerState(index), layer.color),
|
||||
);
|
||||
|
||||
container(
|
||||
@@ -442,26 +461,18 @@ fn editor_layer_row<'a>(
|
||||
54.0
|
||||
),
|
||||
container(color).width(Length::Fixed(135.0)),
|
||||
iced::widget::pick_list(
|
||||
current_linetype,
|
||||
linetypes,
|
||||
|value| value.to_string(),
|
||||
)
|
||||
.on_select(move |value| Message::LayerStateEditorLayerLinetype(index, value))
|
||||
.text_size(11)
|
||||
.padding([3, 5])
|
||||
.width(Length::Fixed(150.0)),
|
||||
iced::widget::pick_list(
|
||||
current_lineweight,
|
||||
lw_options(),
|
||||
|value| value.to_string(),
|
||||
)
|
||||
.on_select(move |item: LwItem| {
|
||||
Message::LayerStateEditorLayerLineweight(index, item.0)
|
||||
})
|
||||
.text_size(11)
|
||||
.padding([3, 5])
|
||||
.width(Length::Fixed(115.0)),
|
||||
iced::widget::pick_list(current_linetype, linetypes, |value| value.to_string(),)
|
||||
.on_select(move |value| Message::LayerStateEditorLayerLinetype(index, value))
|
||||
.text_size(11)
|
||||
.padding([3, 5])
|
||||
.width(Length::Fixed(150.0)),
|
||||
iced::widget::pick_list(current_lineweight, lw_options(), |value| value.to_string(),)
|
||||
.on_select(move |item: LwItem| {
|
||||
Message::LayerStateEditorLayerLineweight(index, item.0)
|
||||
})
|
||||
.text_size(11)
|
||||
.padding([3, 5])
|
||||
.width(Length::Fixed(115.0)),
|
||||
text_input(t!("Default").as_ref(), &layer.plot_style)
|
||||
.on_input(move |value| Message::LayerStateEditorLayerPlotStyle(index, value))
|
||||
.size(11)
|
||||
@@ -482,9 +493,8 @@ fn editor_layer_row<'a>(
|
||||
)
|
||||
.padding([3, 8])
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: (index % 2 == 1).then_some(Background::Color(
|
||||
theme.palette().background.weak.color,
|
||||
)),
|
||||
background: (index % 2 == 1)
|
||||
.then_some(Background::Color(theme.palette().background.weak.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -566,11 +576,9 @@ pub fn view_editor<'a>(
|
||||
.style(muted),
|
||||
Space::new().width(sizing.width),
|
||||
text(t!("Current layer")).size(10).style(muted),
|
||||
iced::widget::pick_list(
|
||||
Some(state.current_layer.clone()),
|
||||
layer_names,
|
||||
|value| value.to_string(),
|
||||
)
|
||||
iced::widget::pick_list(Some(state.current_layer.clone()), layer_names, |value| {
|
||||
value.to_string()
|
||||
},)
|
||||
.on_select(Message::LayerStateEditorCurrentLayer)
|
||||
.text_size(11)
|
||||
.padding([3, 6])
|
||||
@@ -579,7 +587,9 @@ pub fn view_editor<'a>(
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
divider(sizing.width),
|
||||
text(t!("Properties restored by this state")).size(10).style(muted),
|
||||
text(t!("Properties restored by this state"))
|
||||
.size(10)
|
||||
.style(muted),
|
||||
mask_controls,
|
||||
row![
|
||||
text(t!("Saved layer values")).size(12),
|
||||
@@ -606,9 +616,11 @@ pub fn view_editor<'a>(
|
||||
..Default::default()
|
||||
}),
|
||||
row![
|
||||
text(t!("Changes affect the saved state only; the drawing is unchanged until Restore."))
|
||||
.size(10)
|
||||
.style(muted),
|
||||
text(t!(
|
||||
"Changes affect the saved state only; the drawing is unchanged until Restore."
|
||||
))
|
||||
.size(10)
|
||||
.style(muted),
|
||||
Space::new().width(sizing.width),
|
||||
button(text(t!("Cancel")).size(11))
|
||||
.on_press(Message::LayerStateEditorCancel)
|
||||
|
||||
@@ -150,9 +150,7 @@ pub fn view_window<'a>(
|
||||
.on_press_maybe(can_map.then_some(Message::LayerTranslatorMap))
|
||||
.padding([4, 10]),
|
||||
button(text(t!("Map same")).size(12))
|
||||
.on_press_maybe(
|
||||
(!state.targets.is_empty()).then_some(Message::LayerTranslatorMapSame)
|
||||
)
|
||||
.on_press_maybe((!state.targets.is_empty()).then_some(Message::LayerTranslatorMapSame))
|
||||
.padding([4, 10]),
|
||||
Space::new().width(Fill),
|
||||
loaded,
|
||||
|
||||
+166
-128
@@ -1,6 +1,7 @@
|
||||
//! Layer Manager — floating window.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use crate::ui::properties::{lw_options, LinetypeItem, LwItem};
|
||||
use crate::ui::style::common::muted_style;
|
||||
use crate::ui::style::row_button::{cell_button_style, header_button_style};
|
||||
@@ -15,7 +16,6 @@ use iced::widget::{
|
||||
};
|
||||
use iced::Padding;
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use crate::t;
|
||||
use std::borrow::Cow;
|
||||
|
||||
// ── Per-viewport column descriptor ───────────────────────────────────────────
|
||||
@@ -241,8 +241,7 @@ impl LayerPanel {
|
||||
.collect();
|
||||
|
||||
// Re-resolve the selection against the rebuilt rows by name.
|
||||
self.selected = anchor_name
|
||||
.and_then(|n| self.layers.iter().position(|l| l.name == n));
|
||||
self.selected = anchor_name.and_then(|n| self.layers.iter().position(|l| l.name == n));
|
||||
self.selected_multi = multi_names
|
||||
.iter()
|
||||
.filter_map(|n| self.layers.iter().position(|l| l.name == *n))
|
||||
@@ -294,9 +293,7 @@ impl LayerPanel {
|
||||
LayerSortCol::Lock => a.locked.cmp(&b.locked),
|
||||
LayerSortCol::Plot => a.plottable.cmp(&b.plottable),
|
||||
LayerSortCol::Color => color_sort_key(a.color).cmp(&color_sort_key(b.color)),
|
||||
LayerSortCol::Linetype => {
|
||||
a.linetype.to_lowercase().cmp(&b.linetype.to_lowercase())
|
||||
}
|
||||
LayerSortCol::Linetype => a.linetype.to_lowercase().cmp(&b.linetype.to_lowercase()),
|
||||
LayerSortCol::Lineweight => a.lineweight.value().cmp(&b.lineweight.value()),
|
||||
LayerSortCol::Transparency => a.transparency.cmp(&b.transparency),
|
||||
};
|
||||
@@ -335,9 +332,7 @@ impl LayerPanel {
|
||||
self.layers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, layer)| {
|
||||
filter.is_empty() || layer.name.to_lowercase().contains(&filter)
|
||||
})
|
||||
.filter(|(_, layer)| filter.is_empty() || layer.name.to_lowercase().contains(&filter))
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
}
|
||||
@@ -395,9 +390,7 @@ impl LayerPanel {
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weak.color
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(sizing.width)
|
||||
@@ -408,27 +401,69 @@ impl LayerPanel {
|
||||
let sa = self.sort_asc;
|
||||
let mut header_row = row![
|
||||
text(t!("Status")).size(10).style(muted_style).width(50),
|
||||
sortable_header(t!("Name"), LayerSortCol::Name, Length::Fixed(name_col_w), sc, sa),
|
||||
sortable_header(
|
||||
t!("Name"),
|
||||
LayerSortCol::Name,
|
||||
Length::Fixed(name_col_w),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
// Draggable divider: adjusts the Name column width (#359).
|
||||
iced::widget::mouse_area(
|
||||
container(iced::widget::Space::new().width(2).height(14)).style(
|
||||
|theme: &Theme| container::Style {
|
||||
container(iced::widget::Space::new().width(2).height(14)).style(|theme: &Theme| {
|
||||
container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.neutral.color
|
||||
theme.palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
}
|
||||
},),
|
||||
)
|
||||
.on_press(Message::LayerNameColGrab)
|
||||
.interaction(iced::mouse::Interaction::ResizingHorizontally),
|
||||
sortable_header(t!("On"), LayerSortCol::On, Length::Fixed(COL_ICON), sc, sa),
|
||||
sortable_header(t!("Freeze"), LayerSortCol::Freeze, Length::Fixed(COL_ICON), sc, sa),
|
||||
sortable_header(t!("Lock"), LayerSortCol::Lock, Length::Fixed(COL_ICON), sc, sa),
|
||||
sortable_header(t!("Plot"), LayerSortCol::Plot, Length::Fixed(COL_ICON), sc, sa),
|
||||
sortable_header(t!("Color"), LayerSortCol::Color, Length::Fixed(COL_COLOR), sc, sa),
|
||||
sortable_header(t!("Linetype"), LayerSortCol::Linetype, Length::Fixed(COL_LT), sc, sa),
|
||||
sortable_header(t!("Lineweight"), LayerSortCol::Lineweight, Length::Fixed(COL_LW), sc, sa),
|
||||
sortable_header(
|
||||
t!("Freeze"),
|
||||
LayerSortCol::Freeze,
|
||||
Length::Fixed(COL_ICON),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
sortable_header(
|
||||
t!("Lock"),
|
||||
LayerSortCol::Lock,
|
||||
Length::Fixed(COL_ICON),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
sortable_header(
|
||||
t!("Plot"),
|
||||
LayerSortCol::Plot,
|
||||
Length::Fixed(COL_ICON),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
sortable_header(
|
||||
t!("Color"),
|
||||
LayerSortCol::Color,
|
||||
Length::Fixed(COL_COLOR),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
sortable_header(
|
||||
t!("Linetype"),
|
||||
LayerSortCol::Linetype,
|
||||
Length::Fixed(COL_LT),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
sortable_header(
|
||||
t!("Lineweight"),
|
||||
LayerSortCol::Lineweight,
|
||||
Length::Fixed(COL_LW),
|
||||
sc,
|
||||
sa
|
||||
),
|
||||
sortable_header(
|
||||
t!("Transparency"),
|
||||
LayerSortCol::Transparency,
|
||||
@@ -454,13 +489,13 @@ impl LayerPanel {
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
@@ -497,7 +532,6 @@ impl LayerPanel {
|
||||
&self.vp_cols,
|
||||
name_col_w,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
let table = scrollable(rows_col)
|
||||
@@ -507,9 +541,7 @@ impl LayerPanel {
|
||||
// ── Full-window frame ─────────────────────────────────────────────
|
||||
container(column![toolbar, col_header, table].spacing(0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(sizing.width)
|
||||
@@ -561,25 +593,24 @@ fn sortable_header<'a>(
|
||||
|
||||
// ── Toolbar buttons ───────────────────────────────────────────────────────
|
||||
|
||||
fn toolbar_btn<'a>(icon: &'static [u8], label: Cow<'static, str>, msg: Message) -> Element<'a, Message> {
|
||||
fn toolbar_btn<'a>(
|
||||
icon: &'static [u8],
|
||||
label: Cow<'static, str>,
|
||||
msg: Message,
|
||||
) -> Element<'a, Message> {
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::themed(icon, 12.0),
|
||||
text(label).size(11),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
row![crate::ui::icons::themed(icon, 12.0), text(label).size(11),]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(msg)
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => {
|
||||
palette.background.strong
|
||||
}
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.strong,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
@@ -588,10 +619,10 @@ fn toolbar_btn<'a>(icon: &'static [u8], label: Cow<'static, str>, msg: Message)
|
||||
},
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
})
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn toolbar_btn_cond<'a>(
|
||||
@@ -610,11 +641,11 @@ fn toolbar_btn_cond<'a>(
|
||||
if enabled {
|
||||
text(label).size(11)
|
||||
} else {
|
||||
text(label).size(11).style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(
|
||||
theme.palette().background.base.text.scale_alpha(0.42)
|
||||
),
|
||||
})
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().background.base.text.scale_alpha(0.42)),
|
||||
})
|
||||
},
|
||||
]
|
||||
.spacing(5)
|
||||
@@ -627,18 +658,18 @@ fn toolbar_btn_cond<'a>(
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
},
|
||||
text_color: if enabled {
|
||||
pair.text
|
||||
} else {
|
||||
pair.text.scale_alpha(0.42)
|
||||
},
|
||||
..Default::default()
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
},
|
||||
text_color: if enabled {
|
||||
pair.text
|
||||
} else {
|
||||
pair.text.scale_alpha(0.42)
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 10]);
|
||||
@@ -663,14 +694,14 @@ fn name_tip<'a>(name: &'a str) -> Element<'a, Message> {
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -694,16 +725,16 @@ fn layer_row<'a>(
|
||||
let lname = layer.name.clone();
|
||||
let svg_btn = |bytes: &'static [u8], on_press: Message| -> Element<'a, Message> {
|
||||
button(crate::ui::icons::semantic(bytes, ICON_SZ))
|
||||
.on_press(on_press)
|
||||
.style(cell_button_style(is_selected, index))
|
||||
.padding(Padding {
|
||||
top: COMBO_PAD_V,
|
||||
bottom: COMBO_PAD_V,
|
||||
left: 4.0,
|
||||
right: 4.0,
|
||||
})
|
||||
.height(Length::Fixed(ROW_H))
|
||||
.into()
|
||||
.on_press(on_press)
|
||||
.style(cell_button_style(is_selected, index))
|
||||
.padding(Padding {
|
||||
top: COMBO_PAD_V,
|
||||
bottom: COMBO_PAD_V,
|
||||
left: 4.0,
|
||||
right: 4.0,
|
||||
})
|
||||
.height(Length::Fixed(ROW_H))
|
||||
.into()
|
||||
};
|
||||
|
||||
let vis_svg = crate::ui::icons::layer_visible(layer.visible);
|
||||
@@ -757,24 +788,27 @@ fn layer_row<'a>(
|
||||
} else {
|
||||
// ~6 px per glyph at the 10 px row font — track the column width.
|
||||
let name_budget = ((name_col_w / 6.0) as usize).max(8);
|
||||
let name_btn = button(
|
||||
text(crate::ui::text_util::elide(&layer.name, name_budget))
|
||||
.size(FONT_SZ),
|
||||
)
|
||||
.on_press(Message::LayerRenameStart(lname.clone()))
|
||||
.style(cell_button_style(is_selected, index))
|
||||
.padding(Padding {
|
||||
top: COMBO_PAD_V,
|
||||
bottom: COMBO_PAD_V,
|
||||
left: 4.0,
|
||||
right: 4.0,
|
||||
})
|
||||
.height(Length::Fixed(ROW_H))
|
||||
.width(Length::Fixed(name_col_w));
|
||||
let name_btn =
|
||||
button(text(crate::ui::text_util::elide(&layer.name, name_budget)).size(FONT_SZ))
|
||||
.on_press(Message::LayerRenameStart(lname.clone()))
|
||||
.style(cell_button_style(is_selected, index))
|
||||
.padding(Padding {
|
||||
top: COMBO_PAD_V,
|
||||
bottom: COMBO_PAD_V,
|
||||
left: 4.0,
|
||||
right: 4.0,
|
||||
})
|
||||
.height(Length::Fixed(ROW_H))
|
||||
.width(Length::Fixed(name_col_w));
|
||||
// When the name is truncated, reveal the full text on hover so the
|
||||
// user can still read it without widening the column.
|
||||
if layer.name.chars().count() > name_budget {
|
||||
tooltip(name_btn, name_tip(&layer.name), tooltip::Position::FollowCursor).into()
|
||||
tooltip(
|
||||
name_btn,
|
||||
name_tip(&layer.name),
|
||||
tooltip::Position::FollowCursor,
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
name_btn.into()
|
||||
}
|
||||
@@ -783,24 +817,25 @@ fn layer_row<'a>(
|
||||
// Color cell — looks like a combo_box input; click opens swatch dropdown below row.
|
||||
// Shared colour selector. Layers carry a concrete colour (no ByLayer /
|
||||
// ByBlock); true colours stay RGB instead of being collapsed to ACI 7.
|
||||
let color_cell: Element<'_, Message> = container(crate::ui::color_select::color_selector_with_name(
|
||||
layer.color,
|
||||
layer.color_name.as_deref(),
|
||||
color_picker_open,
|
||||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: false,
|
||||
by_block: false,
|
||||
..Default::default()
|
||||
},
|
||||
Message::LayerColorSet,
|
||||
Message::LayerColorPickerToggle(lname.clone()),
|
||||
Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::Layer(lname.clone()),
|
||||
let color_cell: Element<'_, Message> =
|
||||
container(crate::ui::color_select::color_selector_with_name(
|
||||
layer.color,
|
||||
),
|
||||
))
|
||||
.width(Length::Fixed(COL_COLOR))
|
||||
.into();
|
||||
layer.color_name.as_deref(),
|
||||
color_picker_open,
|
||||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: false,
|
||||
by_block: false,
|
||||
..Default::default()
|
||||
},
|
||||
Message::LayerColorSet,
|
||||
Message::LayerColorPickerToggle(lname.clone()),
|
||||
Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::Layer(lname.clone()),
|
||||
layer.color,
|
||||
),
|
||||
))
|
||||
.width(Length::Fixed(COL_COLOR))
|
||||
.into();
|
||||
|
||||
// Linetype cell — uses LinetypeItem (with ASCII art) same as Properties panel
|
||||
let cur_lt_item = LinetypeItem {
|
||||
@@ -837,9 +872,12 @@ fn layer_row<'a>(
|
||||
// Lineweight cell
|
||||
let cur_lw_item = LwItem(layer.lineweight);
|
||||
let lw_cell: Element<'_, Message> = if let Some(state) = lw_combo_state {
|
||||
combo_box(state, t!("lineweight").as_ref(), Some(&cur_lw_item), |item: LwItem| {
|
||||
Message::LayerLineweightSet(item.0)
|
||||
})
|
||||
combo_box(
|
||||
state,
|
||||
t!("lineweight").as_ref(),
|
||||
Some(&cur_lw_item),
|
||||
|item: LwItem| Message::LayerLineweightSet(item.0),
|
||||
)
|
||||
.size(FONT_SZ)
|
||||
.padding(Padding {
|
||||
top: COMBO_PAD_V,
|
||||
@@ -931,9 +969,9 @@ fn layer_row<'a>(
|
||||
palette.background.weak
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
..Default::default()
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(Padding {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! Layout Manager window — fills the entire OS window.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use crate::ui::style::common::muted_style;
|
||||
use crate::ui::style::form::{hdivider, vsep};
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Element, Theme};
|
||||
use crate::t;
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
@@ -91,9 +91,7 @@ pub fn view_window<'a>(
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weakest.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weakest.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(sizing.width)
|
||||
@@ -200,9 +198,7 @@ pub fn view_window<'a>(
|
||||
|
||||
container(column![toolbar, hdivider(sizing.width), body].spacing(0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(sizing.width)
|
||||
|
||||
+26
-26
@@ -1,36 +1,36 @@
|
||||
pub mod about;
|
||||
pub mod alias_editor;
|
||||
pub mod annotation_data;
|
||||
pub mod attdef_dialog;
|
||||
pub mod field_dialog;
|
||||
pub mod attribute_editor;
|
||||
pub mod auto_constrain_settings;
|
||||
pub mod block_definition;
|
||||
pub mod block_palette;
|
||||
pub mod browser;
|
||||
pub mod layout_manager;
|
||||
pub mod layer_state_manager;
|
||||
pub mod drawing_units;
|
||||
pub mod geometric_tolerance;
|
||||
pub mod drafting_settings;
|
||||
pub mod auto_constrain_settings;
|
||||
pub mod layer_translator;
|
||||
pub mod plot;
|
||||
pub mod print_all;
|
||||
pub mod plugin_manager;
|
||||
pub mod shortcuts;
|
||||
pub mod layers;
|
||||
pub mod update_notice;
|
||||
pub mod open_progress;
|
||||
pub mod missing_fonts;
|
||||
pub mod recovery;
|
||||
pub mod options;
|
||||
pub mod attribute_editor;
|
||||
pub mod annotation_data;
|
||||
pub mod alias_editor;
|
||||
pub mod find_replace;
|
||||
pub mod named_parameters;
|
||||
pub mod pc_manager;
|
||||
pub mod count_palette;
|
||||
pub mod sheet_set;
|
||||
pub mod drafting_settings;
|
||||
pub mod drawing_units;
|
||||
pub mod field_dialog;
|
||||
pub mod find_replace;
|
||||
pub mod geometric_tolerance;
|
||||
pub mod layer_state_manager;
|
||||
pub mod layer_translator;
|
||||
pub mod layers;
|
||||
pub mod layout_manager;
|
||||
pub mod missing_fonts;
|
||||
pub mod named_parameters;
|
||||
pub mod open_progress;
|
||||
pub mod options;
|
||||
pub mod pc_manager;
|
||||
pub mod pdf_dialogs;
|
||||
pub mod plot;
|
||||
pub mod plugin_manager;
|
||||
pub mod print_all;
|
||||
pub mod recovery;
|
||||
pub mod sheet_set;
|
||||
pub mod shortcuts;
|
||||
pub mod update_notice;
|
||||
pub mod wblock;
|
||||
pub mod xref_attach;
|
||||
pub mod xref_help;
|
||||
pub mod xref_manager;
|
||||
pub mod wblock;
|
||||
@@ -392,12 +392,10 @@ mod tests {
|
||||
ConstraintKind, ParametricRef, ParametricScope,
|
||||
};
|
||||
let mut scene = Scene::new();
|
||||
let line = scene.add_entity(codec::EntityType::Line(
|
||||
codec::entities::Line::from_points(
|
||||
codec::types::Vector3::new(0.0, 0.0, 0.0),
|
||||
codec::types::Vector3::new(10.0, 0.0, 0.0),
|
||||
),
|
||||
));
|
||||
let line = scene.add_entity(codec::EntityType::Line(codec::entities::Line::from_points(
|
||||
codec::types::Vector3::new(0.0, 0.0, 0.0),
|
||||
codec::types::Vector3::new(10.0, 0.0, 0.0),
|
||||
)));
|
||||
scene
|
||||
.parametric_constraint_set_mut(ParametricScope::ModelSpace)
|
||||
.add(
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
//! Displays the file name, size, current phase, measured progress, and a
|
||||
//! Cancel button.
|
||||
|
||||
use crate::t;
|
||||
use iced::time::Instant;
|
||||
use iced::widget::{button, column, container, row, stack, text, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use crate::t;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@@ -62,17 +62,15 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
.width(Length::Fixed(fill_width))
|
||||
.height(Length::Fixed(BAR_TRACK_HEIGHT)),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().primary.base.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().primary.base.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
|
||||
let bar_value: Element<'_, Message> = row![
|
||||
bar_fill,
|
||||
@@ -84,17 +82,19 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
|
||||
let bar_track: Element<'_, Message> = container(
|
||||
stack![
|
||||
container(Space::new().width(Length::Fixed(BAR_TRACK_WIDTH)).height(Length::Fixed(BAR_TRACK_HEIGHT)))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.strong.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
container(
|
||||
Space::new()
|
||||
.width(Length::Fixed(BAR_TRACK_WIDTH))
|
||||
.height(Length::Fixed(BAR_TRACK_HEIGHT))
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.strong.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
bar_value,
|
||||
]
|
||||
.width(Length::Fixed(BAR_TRACK_WIDTH))
|
||||
@@ -120,10 +120,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
phase_label(phase),
|
||||
basis_points as f32 / 100.0
|
||||
))
|
||||
.size(12)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().primary.base.color),
|
||||
});
|
||||
.size(12)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().primary.base.color),
|
||||
});
|
||||
|
||||
let cancel_btn: Element<'_, Message> = button(text(t!("Cancel")).size(12))
|
||||
.on_press(Message::OpenCancel)
|
||||
@@ -142,13 +142,13 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -156,7 +156,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||
let backdrop: Element<'_, Message> = container(Space::new().width(Fill).height(Fill))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.strong.color.scale_alpha(0.72)
|
||||
theme.palette().background.strong.color.scale_alpha(0.72),
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
+145
-120
@@ -6,9 +6,7 @@ use crate::app::settings;
|
||||
use crate::app::settings::{CursorType, RightClickMode};
|
||||
use crate::app::Message;
|
||||
use crate::ui::style::form::{dialog_button, dialog_button_styled_opt};
|
||||
use iced::widget::{
|
||||
button, column, container, row, scrollable, slider, text, text_input, Space,
|
||||
};
|
||||
use iced::widget::{button, column, container, row, scrollable, slider, text, text_input, Space};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
use std::fmt;
|
||||
|
||||
@@ -23,9 +21,7 @@ const DIALOG_HEIGHT: f32 = 620.0;
|
||||
///
|
||||
/// Persisted: with nine pages, reopening on General every time means hunting
|
||||
/// for the one you were last in.
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum OptionsTab {
|
||||
#[default]
|
||||
@@ -298,7 +294,10 @@ pub fn view_window<'a>(
|
||||
|
||||
let palette = ui_theme.palette.to_iced();
|
||||
let colors = [
|
||||
(crate::tr!("options", "color-background"), palette.background),
|
||||
(
|
||||
crate::tr!("options", "color-background"),
|
||||
palette.background,
|
||||
),
|
||||
(crate::tr!("options", "color-text"), palette.text),
|
||||
(crate::tr!("options", "color-primary"), palette.primary),
|
||||
(crate::tr!("options", "color-success"), palette.success),
|
||||
@@ -351,12 +350,12 @@ pub fn view_window<'a>(
|
||||
text(crate::tr!("options", "language-section")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::tr!("options", "language-label")).size(12).width(150),
|
||||
iced::widget::pick_list(
|
||||
selected_language,
|
||||
language_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
text(crate::tr!("options", "language-label"))
|
||||
.size(12)
|
||||
.width(150),
|
||||
iced::widget::pick_list(selected_language, language_options, |choice| choice
|
||||
.label
|
||||
.clone(),)
|
||||
.on_select(|choice| Message::LanguageChanged(choice.value))
|
||||
.width(sizing.width),
|
||||
]
|
||||
@@ -366,7 +365,9 @@ pub fn view_window<'a>(
|
||||
text(crate::t!("Applications")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Installed plugins and their sources")).size(12).width(Fill),
|
||||
text(crate::t!("Installed plugins and their sources"))
|
||||
.size(12)
|
||||
.width(Fill),
|
||||
button(text(crate::t!("Plugins…")).size(11))
|
||||
.on_press(Message::PluginManagerOpen)
|
||||
.padding([4, 10])
|
||||
@@ -421,7 +422,6 @@ pub fn view_window<'a>(
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
|
||||
// Saving preferences, gathered onto one page. The format and the file
|
||||
// association were on General, which had become a page of three unrelated
|
||||
// controls; the autosave interval and the backup toggle have existed since
|
||||
@@ -431,7 +431,9 @@ pub fn view_window<'a>(
|
||||
Space::new().height(10),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::tr!("options", "default-save-format-label")).size(12).width(150),
|
||||
text(crate::tr!("options", "default-save-format-label"))
|
||||
.size(12)
|
||||
.width(150),
|
||||
iced::widget::pick_list(
|
||||
selected_format,
|
||||
// Drawings, not the template entry (the list's last).
|
||||
@@ -445,15 +447,14 @@ pub fn view_window<'a>(
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(8),
|
||||
text(crate::tr!("options", "default-save-format-help"))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(14),
|
||||
row![
|
||||
iced::widget::checkbox(file_assoc_enabled)
|
||||
.on_toggle(Message::FileAssocChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Open .dwg and .dxf files with Open CAD Studio"))
|
||||
.size(12),
|
||||
text(crate::t!("Open .dwg and .dxf files with Open CAD Studio")).size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -468,7 +469,10 @@ pub fn view_window<'a>(
|
||||
iced::widget::checkbox(show_constraint_values)
|
||||
.on_toggle(Message::ShowConstraintValuesChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Show values and parameter names on constraint markers")).size(12),
|
||||
text(crate::t!(
|
||||
"Show values and parameter names on constraint markers"
|
||||
))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -485,9 +489,13 @@ pub fn view_window<'a>(
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Automatic save")).size(12).width(150),
|
||||
slider(0..=120, prefs.savetime_min.clamp(0, 120), Message::SaveTimeChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
slider(
|
||||
0..=120,
|
||||
prefs.savetime_min.clamp(0, 120),
|
||||
Message::SaveTimeChanged
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(if prefs.savetime_min <= 0 {
|
||||
crate::t!("Off").into_owned()
|
||||
} else {
|
||||
@@ -509,7 +517,10 @@ pub fn view_window<'a>(
|
||||
iced::widget::checkbox(prefs.backup_on_save)
|
||||
.on_toggle(Message::BackupOnSaveChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Keep a .bak copy when overwriting a drawing (ISAVEBAK)")).size(12),
|
||||
text(crate::t!(
|
||||
"Keep a .bak copy when overwriting a drawing (ISAVEBAK)"
|
||||
))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -518,40 +529,42 @@ pub fn view_window<'a>(
|
||||
.width(sizing.width);
|
||||
|
||||
let crosshair_rgb = crosshair_color.unwrap_or([255, 255, 255]);
|
||||
let crosshair_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
crosshair_rgb[0],
|
||||
crosshair_rgb[1],
|
||||
crosshair_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let crosshair_swatch =
|
||||
container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
crosshair_rgb[0],
|
||||
crosshair_rgb[1],
|
||||
crosshair_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let command_rgb = command_text_color.unwrap_or([220, 220, 220]);
|
||||
let command_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
command_rgb[0],
|
||||
command_rgb[1],
|
||||
command_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let command_swatch =
|
||||
container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
command_rgb[0],
|
||||
command_rgb[1],
|
||||
command_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let snap_rgb = snap_marker_color.unwrap_or([255, 230, 26]);
|
||||
let snap_swatch = container(Space::new())
|
||||
@@ -571,11 +584,17 @@ pub fn view_window<'a>(
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let model_bg_rgb = model_space.custom_bg.unwrap_or(crate::app::config::CLASSIC_CAD_DARK_BG);
|
||||
let model_bg_rgb = model_space
|
||||
.custom_bg
|
||||
.unwrap_or(crate::app::config::CLASSIC_CAD_DARK_BG);
|
||||
|
||||
let paper_bg_rgb = model_space.custom_paper_bg.unwrap_or(crate::app::config::DEFAULT_PAPER_BG);
|
||||
let paper_bg_rgb = model_space
|
||||
.custom_paper_bg
|
||||
.unwrap_or(crate::app::config::DEFAULT_PAPER_BG);
|
||||
|
||||
let desk_bg_rgb = model_space.custom_desk_bg.unwrap_or(crate::app::config::DEFAULT_DESK_BG);
|
||||
let desk_bg_rgb = model_space
|
||||
.custom_desk_bg
|
||||
.unwrap_or(crate::app::config::DEFAULT_DESK_BG);
|
||||
|
||||
let mode_options = crate::app::config::ModelSpaceMode::ALL
|
||||
.into_iter()
|
||||
@@ -664,21 +683,19 @@ pub fn view_window<'a>(
|
||||
text(crate::tr!("options", "theme-section")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::tr!("options", "theme-label")).size(12).width(150),
|
||||
iced::widget::pick_list(
|
||||
selected_theme,
|
||||
theme_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::OptionsThemeChanged(choice.value))
|
||||
.width(sizing.width),
|
||||
text(crate::tr!("options", "theme-label"))
|
||||
.size(12)
|
||||
.width(150),
|
||||
iced::widget::pick_list(selected_theme, theme_options, |choice| choice.label.clone(),)
|
||||
.on_select(|choice| Message::OptionsThemeChanged(choice.value))
|
||||
.width(sizing.width),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(8),
|
||||
text(crate::tr!("options", "theme-help"))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(12),
|
||||
color_controls,
|
||||
Space::new().height(16),
|
||||
@@ -688,7 +705,9 @@ pub fn view_window<'a>(
|
||||
text(crate::t!("Interface scale")).size(12).width(150),
|
||||
iced::widget::pick_list(
|
||||
Some(format!("{}%", prefs.ui_scale_percent)),
|
||||
[75u16, 100, 125, 150, 175, 200].map(|p| format!("{p}%")).to_vec(),
|
||||
[75u16, 100, 125, 150, 175, 200]
|
||||
.map(|p| format!("{p}%"))
|
||||
.to_vec(),
|
||||
|label: &String| label.clone(),
|
||||
)
|
||||
.on_select(|label: String| {
|
||||
@@ -711,13 +730,9 @@ pub fn view_window<'a>(
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Canvas mode")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
selected_mode,
|
||||
mode_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::ModelSpaceModeChanged(choice.value))
|
||||
.width(Fill),
|
||||
iced::widget::pick_list(selected_mode, mode_options, |choice| choice.label.clone(),)
|
||||
.on_select(|choice| Message::ModelSpaceModeChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
@@ -1245,16 +1260,13 @@ pub fn view_window<'a>(
|
||||
.find(|choice| choice.value == prefs.annotation_auto_scale.max(0))
|
||||
.cloned();
|
||||
|
||||
let qdim_options = [
|
||||
(0u8, "Endpoints"),
|
||||
(1, "Intersections"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(value, label)| Labelled {
|
||||
value,
|
||||
label: crate::t!(label).into_owned(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let qdim_options = [(0u8, "Endpoints"), (1, "Intersections")]
|
||||
.into_iter()
|
||||
.map(|(value, label)| Labelled {
|
||||
value,
|
||||
label: crate::t!(label).into_owned(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_qdim = qdim_options
|
||||
.iter()
|
||||
.find(|choice| choice.value == prefs.qdim_snap_priority)
|
||||
@@ -1453,7 +1465,6 @@ pub fn view_window<'a>(
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
|
||||
// Snap modes, grid and object snap stay in the Drafting Settings dialog.
|
||||
// These two have no home: Drafting Settings shows the rotation angle but
|
||||
// offers only Reset, and the polar increment lives solely in a status-bar
|
||||
@@ -1497,7 +1508,9 @@ pub fn view_window<'a>(
|
||||
.width(sizing.width),
|
||||
Space::new().height(14),
|
||||
row![
|
||||
text(crate::t!("Polar tracking increment")).size(12).width(150),
|
||||
text(crate::t!("Polar tracking increment"))
|
||||
.size(12)
|
||||
.width(150),
|
||||
iced::widget::pick_list(Some(selected_polar), polar_options, |choice| {
|
||||
choice.label.clone()
|
||||
})
|
||||
@@ -1524,7 +1537,6 @@ pub fn view_window<'a>(
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
|
||||
let surface_type_options = [
|
||||
(5i16, "Quadratic B-spline"),
|
||||
(6, "Cubic B-spline"),
|
||||
@@ -1541,17 +1553,13 @@ pub fn view_window<'a>(
|
||||
.find(|choice| choice.value == drawing_prefs.surface_type)
|
||||
.cloned();
|
||||
|
||||
let show_history_options = [
|
||||
(0i16, "Never"),
|
||||
(1, "As set per solid"),
|
||||
(2, "Always"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(value, label)| Labelled {
|
||||
value,
|
||||
label: crate::t!(label).into_owned(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let show_history_options = [(0i16, "Never"), (1, "As set per solid"), (2, "Always")]
|
||||
.into_iter()
|
||||
.map(|(value, label)| Labelled {
|
||||
value,
|
||||
label: crate::t!(label).into_owned(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_show_history = show_history_options
|
||||
.iter()
|
||||
.find(|choice| choice.value == drawing_prefs.show_solid_history.clamp(0, 2))
|
||||
@@ -1584,7 +1592,10 @@ pub fn view_window<'a>(
|
||||
iced::widget::checkbox(prefs.ucs_icon_at_origin)
|
||||
.on_toggle(Message::UcsIconAtOriginChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Place the UCS icon at the origin (UCSICON ORigin)")).size(12),
|
||||
text(crate::t!(
|
||||
"Place the UCS icon at the origin (UCSICON ORigin)"
|
||||
))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -1698,8 +1709,10 @@ pub fn view_window<'a>(
|
||||
iced::widget::checkbox(drawing_prefs.record_solid_history)
|
||||
.on_toggle(Message::SolidHistChanged)
|
||||
.size(15),
|
||||
text(crate::t!("Record the history of composite solids (SOLIDHIST)"))
|
||||
.size(12),
|
||||
text(crate::t!(
|
||||
"Record the history of composite solids (SOLIDHIST)"
|
||||
))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
@@ -1723,13 +1736,14 @@ pub fn view_window<'a>(
|
||||
|
||||
let modeling = modeling.spacing(0).width(sizing.width);
|
||||
|
||||
|
||||
// The application has no support-file search path, so this page does not
|
||||
// pretend to offer one. It shows where things actually live and opens the
|
||||
// folder — which is the question people are really asking when they go
|
||||
// looking for a Files page.
|
||||
let folder_row = |label: std::borrow::Cow<'a, str>, path: Option<String>| {
|
||||
let shown = path.clone().unwrap_or_else(|| crate::t!("Not available").into_owned());
|
||||
let shown = path
|
||||
.clone()
|
||||
.unwrap_or_else(|| crate::t!("Not available").into_owned());
|
||||
let mut open = button(text(crate::t!("Open folder")).size(11))
|
||||
.padding([4, 10])
|
||||
.style(button::secondary);
|
||||
@@ -1737,12 +1751,9 @@ pub fn view_window<'a>(
|
||||
open = open.on_press(Message::OpenFolder(path));
|
||||
}
|
||||
row![
|
||||
column![
|
||||
text(label).size(12),
|
||||
text(shown).size(11),
|
||||
]
|
||||
.spacing(2)
|
||||
.width(Fill),
|
||||
column![text(label).size(12), text(shown).size(11),]
|
||||
.spacing(2)
|
||||
.width(Fill),
|
||||
open,
|
||||
]
|
||||
.spacing(10)
|
||||
@@ -1807,7 +1818,11 @@ pub fn view_window<'a>(
|
||||
.on_press(Message::OptionsTabChanged(tab))
|
||||
.padding([7, 12])
|
||||
.width(Fill)
|
||||
.style(if selected { button::primary } else { button::text })
|
||||
.style(if selected {
|
||||
button::primary
|
||||
} else {
|
||||
button::text
|
||||
})
|
||||
};
|
||||
let tabs = column![
|
||||
tab_button(crate::t!("General"), OptionsTab::General),
|
||||
@@ -1822,9 +1837,7 @@ pub fn view_window<'a>(
|
||||
// The web build has no backend to choose, so it has no Graphics page.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let tabs = tabs.push(tab_button(crate::t!("Graphics"), OptionsTab::Graphics));
|
||||
let tabs = tabs
|
||||
.spacing(2)
|
||||
.width(iced::Length::Fixed(TAB_RAIL_WIDTH));
|
||||
let tabs = tabs.spacing(2).width(iced::Length::Fixed(TAB_RAIL_WIDTH));
|
||||
|
||||
let pane = column![
|
||||
// Keep the scrollbar in its own lane instead of floating over the
|
||||
@@ -1844,10 +1857,22 @@ pub fn view_window<'a>(
|
||||
let main = container(body)
|
||||
.style(container::rounded_box)
|
||||
.padding([16, 18])
|
||||
.width(if intrinsic { iced::Length::Fixed(DIALOG_WIDTH) } else { sizing.width })
|
||||
.height(if intrinsic { iced::Length::Fixed(DIALOG_HEIGHT) } else { sizing.height });
|
||||
.width(if intrinsic {
|
||||
iced::Length::Fixed(DIALOG_WIDTH)
|
||||
} else {
|
||||
sizing.width
|
||||
})
|
||||
.height(if intrinsic {
|
||||
iced::Length::Fixed(DIALOG_HEIGHT)
|
||||
} else {
|
||||
sizing.height
|
||||
});
|
||||
if !close_confirm {
|
||||
return main.into();
|
||||
}
|
||||
crate::ui::modal::discard_guard(main, Message::OptionsCloseDiscard, Message::OptionsCloseKeep)
|
||||
crate::ui::modal::discard_guard(
|
||||
main,
|
||||
Message::OptionsCloseDiscard,
|
||||
Message::OptionsCloseKeep,
|
||||
)
|
||||
}
|
||||
@@ -75,9 +75,12 @@ pub(crate) fn view(prefs: GraphicsPrefs, active: ActiveGpu) -> Element<'static,
|
||||
Space::new().height(20),
|
||||
text(crate::t!("Next launch")).size(13),
|
||||
Space::new().height(8),
|
||||
row![text(crate::t!("Graphics backend")).size(12).width(150), backend]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text(crate::t!("Graphics backend")).size(12).width(150),
|
||||
backend
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(12),
|
||||
row![
|
||||
checkbox(prefs.compat_renderer)
|
||||
|
||||
+88
-26
@@ -8,7 +8,10 @@ use crate::scene::model::point_cloud::{self, UNASSIGNED_OFF};
|
||||
use crate::ui::dock::PanelId;
|
||||
use codec::entities::ExtendedEntityData;
|
||||
use codec::{CadDocument, EntityType, Handle};
|
||||
use iced::widget::{button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, tooltip, Space};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, tooltip,
|
||||
Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -52,7 +55,9 @@ impl Row {
|
||||
"cloud" => Some(Self::Cloud),
|
||||
"unassigned" => Some(Self::Unassigned),
|
||||
"scans" => Some(Self::Scans),
|
||||
_ => text.strip_prefix("scan:").map(|name| Self::Scan(name.to_string())),
|
||||
_ => text
|
||||
.strip_prefix("scan:")
|
||||
.map(|name| Self::Scan(name.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,18 +95,30 @@ pub fn clouds(document: &CadDocument, selected: &[Handle]) -> Vec<Cloud> {
|
||||
let common = entity.common();
|
||||
let scans = point_cloud::resolve_source(document, data)
|
||||
.and_then(|path| point_cloud::load(&path))
|
||||
.map(|cloud| cloud.scan_ranges.iter().map(|s| (s.name.clone(), s.id.clone())).collect())
|
||||
.map(|cloud| {
|
||||
cloud
|
||||
.scan_ranges
|
||||
.iter()
|
||||
.map(|s| (s.name.clone(), s.id.clone()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Some(Cloud {
|
||||
handle: common.handle,
|
||||
name: if data.name.is_empty() { format!("{:X}", common.handle.value()) } else { data.name.clone() },
|
||||
name: if data.name.is_empty() {
|
||||
format!("{:X}", common.handle.value())
|
||||
} else {
|
||||
data.name.clone()
|
||||
},
|
||||
hidden: point_cloud::hidden(data),
|
||||
scans,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if all.iter().any(|cloud| selected.contains(&cloud.handle)) {
|
||||
all.into_iter().filter(|cloud| selected.contains(&cloud.handle)).collect()
|
||||
all.into_iter()
|
||||
.filter(|cloud| selected.contains(&cloud.handle))
|
||||
.collect()
|
||||
} else {
|
||||
all
|
||||
}
|
||||
@@ -155,7 +172,10 @@ pub fn toggled(hidden: &[String], scans: &[(String, String)], row: &Row) -> Vec<
|
||||
|
||||
/// Keys of the rows that fold: each cloud and its Scans.
|
||||
pub fn folding_keys(clouds: &[Cloud]) -> Vec<String> {
|
||||
clouds.iter().flat_map(|c| [key(c.handle), format!("{}/scans", key(c.handle))]).collect()
|
||||
clouds
|
||||
.iter()
|
||||
.flat_map(|c| [key(c.handle), format!("{}/scans", key(c.handle))])
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn key(handle: Handle) -> String {
|
||||
@@ -191,23 +211,39 @@ fn tree_row<'a>(
|
||||
};
|
||||
let enabled = switch.is_some();
|
||||
let label = text(label).size(12).width(Fill);
|
||||
let label = if enabled { label } else { label.style(crate::ui::style::common::muted_style) };
|
||||
let label = if enabled {
|
||||
label
|
||||
} else {
|
||||
label.style(crate::ui::style::common::muted_style)
|
||||
};
|
||||
let switch: Element<'a, Message> = match switch {
|
||||
// Mixed: a dash in the box, as the reference's indeterminate state.
|
||||
Some((Switch::Mixed, message)) => button(crate::ui::icons::themed_primary(crate::ui::icons::MINUS, 10.0))
|
||||
.on_press(message)
|
||||
.style(button::secondary)
|
||||
.padding(1)
|
||||
.width(Length::Fixed(16.0))
|
||||
.height(Length::Fixed(16.0))
|
||||
Some((Switch::Mixed, message)) => button(crate::ui::icons::themed_primary(
|
||||
crate::ui::icons::MINUS,
|
||||
10.0,
|
||||
))
|
||||
.on_press(message)
|
||||
.style(button::secondary)
|
||||
.padding(1)
|
||||
.width(Length::Fixed(16.0))
|
||||
.height(Length::Fixed(16.0))
|
||||
.into(),
|
||||
Some((on, message)) => checkbox(on == Switch::On)
|
||||
.on_toggle(move |_| message.clone())
|
||||
.size(14)
|
||||
.into(),
|
||||
Some((on, message)) => checkbox(on == Switch::On).on_toggle(move |_| message.clone()).size(14).into(),
|
||||
None => checkbox(false).size(14).into(),
|
||||
};
|
||||
let selected = state.selected.as_deref() == Some(key.as_str());
|
||||
let cells = row![Space::new().width(Length::Fixed(f32::from(depth) * 16.0)), arrow, icon, label, switch]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
let cells = row![
|
||||
Space::new().width(Length::Fixed(f32::from(depth) * 16.0)),
|
||||
arrow,
|
||||
icon,
|
||||
label,
|
||||
switch
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
mouse_area(
|
||||
container(cells)
|
||||
.width(Fill)
|
||||
@@ -222,12 +258,18 @@ fn tree_row<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn fold_button<'a>(icon: &'static [u8], tip: String, message: PcManagerMsg) -> Element<'a, Message> {
|
||||
fn fold_button<'a>(
|
||||
icon: &'static [u8],
|
||||
tip: String,
|
||||
message: PcManagerMsg,
|
||||
) -> Element<'a, Message> {
|
||||
let b = button(crate::ui::icons::themed_secondary(icon, 12.0))
|
||||
.on_press(msg(message))
|
||||
.style(button::subtle)
|
||||
.padding([2, 4]);
|
||||
tooltip(b, text(tip).size(10), tooltip::Position::Bottom).gap(4).into()
|
||||
tooltip(b, text(tip).size(10), tooltip::Position::Bottom)
|
||||
.gap(4)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
@@ -237,8 +279,11 @@ pub fn view<'a>(
|
||||
width: f32,
|
||||
auto_collapse: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let title_bar =
|
||||
crate::ui::dock::title_bar(PanelId::PointCloudManager, crate::t!("Point Cloud Manager").into_owned(), auto_collapse);
|
||||
let title_bar = crate::ui::dock::title_bar(
|
||||
PanelId::PointCloudManager,
|
||||
crate::t!("Point Cloud Manager").into_owned(),
|
||||
auto_collapse,
|
||||
);
|
||||
let query = state.search.trim().to_lowercase();
|
||||
let matches = |label: &str| query.is_empty() || label.to_lowercase().contains(&query);
|
||||
// Searching opens every node so the matches show.
|
||||
@@ -250,7 +295,11 @@ pub fn view<'a>(
|
||||
let mut tree = column![].spacing(1);
|
||||
for cloud in clouds(document, selected) {
|
||||
let root = key(cloud.handle);
|
||||
let scans: Vec<&(String, String)> = cloud.scans.iter().filter(|(name, _)| matches(name)).collect();
|
||||
let scans: Vec<&(String, String)> = cloud
|
||||
.scans
|
||||
.iter()
|
||||
.filter(|(name, _)| matches(name))
|
||||
.collect();
|
||||
let show_scans = matches(&scans_label) || !scans.is_empty();
|
||||
let children = matches(®ions) || matches(&unassigned) || show_scans;
|
||||
if !matches(&cloud.name) && !children {
|
||||
@@ -331,12 +380,22 @@ pub fn view<'a>(
|
||||
style: iced::font::Style::Italic,
|
||||
..iced::Font::DEFAULT
|
||||
})
|
||||
.style(|theme: &Theme| text::Style { color: Some(theme.palette().primary.base.color) })
|
||||
.style(|theme: &Theme| text::Style {
|
||||
color: Some(theme.palette().primary.base.color),
|
||||
})
|
||||
.width(Fill);
|
||||
let header = row![
|
||||
heading,
|
||||
fold_button(crate::ui::icons::MINUS, crate::t!("Collapse all").into_owned(), PcManagerMsg::CollapseAll),
|
||||
fold_button(crate::ui::icons::PLUS, crate::t!("Expand all").into_owned(), PcManagerMsg::ExpandAll),
|
||||
fold_button(
|
||||
crate::ui::icons::MINUS,
|
||||
crate::t!("Collapse all").into_owned(),
|
||||
PcManagerMsg::CollapseAll
|
||||
),
|
||||
fold_button(
|
||||
crate::ui::icons::PLUS,
|
||||
crate::t!("Expand all").into_owned(),
|
||||
PcManagerMsg::ExpandAll
|
||||
),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center);
|
||||
@@ -346,7 +405,10 @@ pub fn view<'a>(
|
||||
.height(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let search = text_input(&crate::t!("Search"), &state.search)
|
||||
|
||||
+659
-198
File diff suppressed because it is too large.
Load diff
+411
-263
@@ -7,15 +7,14 @@
|
||||
use crate::app::Message;
|
||||
use crate::io::paper_catalog::{self, CustomPaper, Margins, PaperSize, PaperUnits};
|
||||
use crate::io::plot_device::PrinterCapabilities;
|
||||
use crate::t;
|
||||
use crate::ui::style::common::muted_style;
|
||||
use crate::ui::style::form;
|
||||
use crate::ui::style::form::{hdivider, vseparator as vsep};
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, mouse_area, row, scrollable, text, text_input,
|
||||
Space,
|
||||
button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fit, Length, Theme};
|
||||
use crate::t;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
@@ -44,11 +43,19 @@ struct PlotChoice {
|
||||
|
||||
impl PlotChoice {
|
||||
fn raw(value: impl Into<String>) -> Self {
|
||||
Self { raw: value.into(), localized: false, display: None }
|
||||
Self {
|
||||
raw: value.into(),
|
||||
localized: false,
|
||||
display: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn localized(value: impl Into<String>) -> Self {
|
||||
Self { raw: value.into(), localized: true, display: None }
|
||||
Self {
|
||||
raw: value.into(),
|
||||
localized: true,
|
||||
display: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn paper(paper: &PaperSize) -> Self {
|
||||
@@ -96,7 +103,15 @@ mod tests {
|
||||
assert_eq!((custom.width, custom.height), (1500.0, 609.6));
|
||||
assert_eq!(custom.units, PaperUnits::Millimeters);
|
||||
// Blank or unparsable margins mean no margin, never a refusal.
|
||||
assert_eq!(custom.margins, Margins { left: 5.0, bottom: 17.0, right: 0.0, top: 0.0 });
|
||||
assert_eq!(
|
||||
custom.margins,
|
||||
Margins {
|
||||
left: 5.0,
|
||||
bottom: 17.0,
|
||||
right: 0.0,
|
||||
top: 0.0
|
||||
}
|
||||
);
|
||||
assert_eq!(custom.canonical(), "Roll_24_(609.60_x_1500.00_MM)");
|
||||
draft.width = "0".into();
|
||||
assert_eq!(draft.build().unwrap_err(), CustomPaperError::Size);
|
||||
@@ -113,7 +128,15 @@ mod tests {
|
||||
assert!(draft.inches);
|
||||
assert_eq!(draft.margins, ["0.25", "0.25", "0.25", "0.25"]);
|
||||
let a4 = resolve("ISO_A4_(210.00_x_297.00_MM)").unwrap();
|
||||
let draft = CustomPaperDraft::from_paper(&a4, Margins { left: 5.0, bottom: 17.0, right: 6.0, top: 18.0 });
|
||||
let draft = CustomPaperDraft::from_paper(
|
||||
&a4,
|
||||
Margins {
|
||||
left: 5.0,
|
||||
bottom: 17.0,
|
||||
right: 6.0,
|
||||
top: 18.0,
|
||||
},
|
||||
);
|
||||
assert!(!draft.inches);
|
||||
assert_eq!(draft.margins, ["5", "17", "6", "18"]);
|
||||
}
|
||||
@@ -124,7 +147,10 @@ mod tests {
|
||||
let choice = PlotChoice::localized(format!("View: {name}"));
|
||||
assert_eq!(choice.to_string(), crate::tf!("View: {name}").as_ref());
|
||||
assert_eq!(choice.raw.strip_prefix("View: "), Some(name));
|
||||
assert_eq!(PlotChoice::raw("View: PDF printer").to_string(), "View: PDF printer");
|
||||
assert_eq!(
|
||||
PlotChoice::raw("View: PDF printer").to_string(),
|
||||
"View: PDF printer"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +225,10 @@ pub enum PlotDlgMsg {
|
||||
/// The driver options of a printer arrived (or failed to), for the
|
||||
/// in-line properties editor; carries the printer's name so a late
|
||||
/// answer for a printer the user has since left is ignored.
|
||||
PrinterOptionsLoaded(String, Result<Vec<crate::io::print_to_printer::PrinterOption>, String>),
|
||||
PrinterOptionsLoaded(
|
||||
String,
|
||||
Result<Vec<crate::io::print_to_printer::PrinterOption>, String>,
|
||||
),
|
||||
/// One option of the properties editor was set to a choice keyword.
|
||||
PrinterOptionSet(String, String),
|
||||
/// Keep the editor's choices for the printer and close it.
|
||||
@@ -357,7 +386,11 @@ impl CustomPaperDraft {
|
||||
if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
|
||||
return Err(CustomPaperError::Size);
|
||||
}
|
||||
let margin = |text: &str| number(text).filter(|v| v.is_finite() && *v >= 0.0).unwrap_or(0.0);
|
||||
let margin = |text: &str| {
|
||||
number(text)
|
||||
.filter(|v| v.is_finite() && *v >= 0.0)
|
||||
.unwrap_or(0.0)
|
||||
};
|
||||
Ok(CustomPaper {
|
||||
name: self.name.trim().to_string(),
|
||||
width,
|
||||
@@ -437,7 +470,8 @@ pub struct PlotDialogState {
|
||||
/// Driver options chosen per printer (`printer → key → choice`), the way
|
||||
/// a plotter configuration remembers its device settings; applied to
|
||||
/// every job sent to that printer and kept across sessions.
|
||||
pub driver_options: std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>,
|
||||
pub driver_options:
|
||||
std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>,
|
||||
/// The in-line printer-properties editor while it is open.
|
||||
#[serde(skip)]
|
||||
pub printer_editor: Option<PrinterOptionsDraft>,
|
||||
@@ -647,7 +681,6 @@ impl PlotDialogState {
|
||||
self.show_plot_styles = o.show_plot_styles;
|
||||
self.style_missing = o.style_missing;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn legacy_fit_to_paper_default() -> bool {
|
||||
@@ -710,7 +743,13 @@ fn drop_row<'a>(
|
||||
ctor: fn(String) -> PlotDlgMsg,
|
||||
width: Length,
|
||||
) -> Element<'a, Message> {
|
||||
form::labeled_pick_list(label, options, selected, move |choice| Message::PlotDlg(ctor(choice.raw)), width)
|
||||
form::labeled_pick_list(
|
||||
label,
|
||||
options,
|
||||
selected,
|
||||
move |choice| Message::PlotDlg(ctor(choice.raw)),
|
||||
width,
|
||||
)
|
||||
}
|
||||
|
||||
fn drop_row_enabled<'a>(
|
||||
@@ -738,7 +777,12 @@ fn field_row<'a>(
|
||||
ctor: fn(String) -> PlotDlgMsg,
|
||||
width: u16,
|
||||
) -> Element<'a, Message> {
|
||||
form::labeled_field(label, value, move |s| Message::PlotDlg(ctor(s)), width as f32)
|
||||
form::labeled_field(
|
||||
label,
|
||||
value,
|
||||
move |s| Message::PlotDlg(ctor(s)),
|
||||
width as f32,
|
||||
)
|
||||
}
|
||||
|
||||
fn field_row_enabled<'a>(
|
||||
@@ -748,7 +792,13 @@ fn field_row_enabled<'a>(
|
||||
width: u16,
|
||||
enabled: bool,
|
||||
) -> Element<'a, Message> {
|
||||
form::labeled_field_enabled(label, value, move |s| Message::PlotDlg(ctor(s)), width as f32, enabled)
|
||||
form::labeled_field_enabled(
|
||||
label,
|
||||
value,
|
||||
move |s| Message::PlotDlg(ctor(s)),
|
||||
width as f32,
|
||||
enabled,
|
||||
)
|
||||
}
|
||||
|
||||
fn check<'a>(label: Cow<'static, str>, on: bool, flag: PlotFlag) -> Element<'a, Message> {
|
||||
@@ -803,7 +853,9 @@ fn page_setup_import_chooser<'a>(
|
||||
}
|
||||
let all = !draft.setups.is_empty() && draft.setups.iter().all(|(_, _, on)| *on);
|
||||
let any = draft.setups.iter().any(|(_, _, on)| *on);
|
||||
let mut import = button(text(t!("Import")).size(11)).style(btn(true)).padding([4, 8]);
|
||||
let mut import = button(text(t!("Import")).size(11))
|
||||
.style(btn(true))
|
||||
.padding([4, 8]);
|
||||
if any {
|
||||
import = import.on_press(msg(PageSetupImportMsg::Apply));
|
||||
}
|
||||
@@ -860,32 +912,33 @@ fn custom_scale_row<'a>(s: &'a PlotDialogState, enabled: bool) -> Element<'a, Me
|
||||
/// the file's own problem, or where a copy would be found.
|
||||
fn style_note<'a>(s: &'a PlotDialogState) -> Element<'a, Message> {
|
||||
match &s.style_error {
|
||||
Some(error) if s.style_missing => {
|
||||
text(error.clone()).size(10).style(muted_style).into()
|
||||
}
|
||||
Some(error) if s.style_missing => text(error.clone()).size(10).style(muted_style).into(),
|
||||
_ => Space::new().height(0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn panel<'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
container(content).width(Length::Fill).into()
|
||||
}
|
||||
|
||||
fn choices(items: &[&str]) -> Vec<PlotChoice> {
|
||||
items.iter().map(|&value| PlotChoice::localized(value)).collect()
|
||||
items
|
||||
.iter()
|
||||
.map(|&value| PlotChoice::localized(value))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The in-line printer-properties editor: one `label : choice` row per
|
||||
/// driver option (media type, colour mode, quality, duplex, trays…), with
|
||||
/// Reset / Cancel / Apply. Mirrors what LibreOffice's Properties… shows for a
|
||||
/// CUPS printer, without leaving the plot dialog.
|
||||
fn printer_options_editor<'a>(draft: &'a PrinterOptionsDraft, width: Length) -> Element<'a, Message> {
|
||||
let mut rows = column![section_label(Cow::Owned(crate::tf!(
|
||||
"Printer properties — {printer}",
|
||||
printer = draft.printer
|
||||
).into_owned()))]
|
||||
fn printer_options_editor<'a>(
|
||||
draft: &'a PrinterOptionsDraft,
|
||||
width: Length,
|
||||
) -> Element<'a, Message> {
|
||||
let mut rows = column![section_label(Cow::Owned(
|
||||
crate::tf!("Printer properties — {printer}", printer = draft.printer).into_owned()
|
||||
))]
|
||||
.spacing(6);
|
||||
match (&draft.options, &draft.error) {
|
||||
(Some(options), _) if options.is_empty() => {
|
||||
@@ -917,7 +970,9 @@ fn printer_options_editor<'a>(draft: &'a PrinterOptionsDraft, width: Length) ->
|
||||
Cow::Owned(option.label.clone()),
|
||||
choices,
|
||||
selected,
|
||||
move |choice| Message::PlotDlg(PlotDlgMsg::PrinterOptionSet(key.clone(), choice.raw)),
|
||||
move |choice| {
|
||||
Message::PlotDlg(PlotDlgMsg::PrinterOptionSet(key.clone(), choice.raw))
|
||||
},
|
||||
Length::Fill,
|
||||
));
|
||||
}
|
||||
@@ -932,7 +987,11 @@ fn printer_options_editor<'a>(draft: &'a PrinterOptionsDraft, width: Length) ->
|
||||
);
|
||||
}
|
||||
(None, None) => {
|
||||
rows = rows.push(text(t!("Reading the printer's options…")).size(10).style(muted_style));
|
||||
rows = rows.push(
|
||||
text(t!("Reading the printer's options…"))
|
||||
.size(10)
|
||||
.style(muted_style),
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut buttons = row![
|
||||
@@ -982,12 +1041,17 @@ fn custom_paper_editor<'a>(draft: &'a CustomPaperDraft, width: Length) -> Elemen
|
||||
PlotChoice::localized("Millimeters"),
|
||||
PlotChoice::localized("Inches"),
|
||||
];
|
||||
let unit_selected = Some(PlotChoice::localized(if draft.inches { "Inches" } else { "Millimeters" }));
|
||||
let unit_selected = Some(PlotChoice::localized(if draft.inches {
|
||||
"Inches"
|
||||
} else {
|
||||
"Millimeters"
|
||||
}));
|
||||
const SIDE_LABEL: f32 = 52.0;
|
||||
const NUMBER: f32 = 64.0;
|
||||
let dimension = |label: Cow<'static, str>, value: &'a str, ctor: fn(String) -> CustomPaperMsg| {
|
||||
form::labeled_field_compact(label, value, move |v| msg(ctor(v)), SIDE_LABEL, NUMBER)
|
||||
};
|
||||
let dimension =
|
||||
|label: Cow<'static, str>, value: &'a str, ctor: fn(String) -> CustomPaperMsg| {
|
||||
form::labeled_field_compact(label, value, move |v| msg(ctor(v)), SIDE_LABEL, NUMBER)
|
||||
};
|
||||
let margin = |label: Cow<'static, str>, side: MarginSide, index: usize| {
|
||||
form::labeled_field_compact(
|
||||
label,
|
||||
@@ -1009,7 +1073,12 @@ fn custom_paper_editor<'a>(draft: &'a CustomPaperDraft, width: Length) -> Elemen
|
||||
container(
|
||||
column![
|
||||
section_label(t!("Custom paper size")),
|
||||
form::labeled_field(t!("Name"), &draft.name, move |v| msg(CustomPaperMsg::Name(v)), 200.0),
|
||||
form::labeled_field(
|
||||
t!("Name"),
|
||||
&draft.name,
|
||||
move |v| msg(CustomPaperMsg::Name(v)),
|
||||
200.0
|
||||
),
|
||||
form::labeled_pick_list(
|
||||
t!("Units"),
|
||||
unit_options,
|
||||
@@ -1107,30 +1176,29 @@ pub fn view_window(
|
||||
} else {
|
||||
scrollable(column(rows).spacing(1)).height(height).into()
|
||||
};
|
||||
let new_name: Element<'_, Message> =
|
||||
if !s.name_rename && s.name_input.is_some() {
|
||||
row![
|
||||
text_input("", rename_buf)
|
||||
.on_input(|value| Message::PlotDlg(PlotDlgMsg::NameInput(value)))
|
||||
.on_submit(Message::PlotDlg(PlotDlgMsg::NameCommit))
|
||||
.style(form::field_style)
|
||||
.size(11)
|
||||
.padding([4, 8])
|
||||
.width(Length::Fill),
|
||||
button(text(t!("Save")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::NameCommit))
|
||||
.style(btn(true))
|
||||
.padding([4, 8]),
|
||||
button(text("×").size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::NameCancel))
|
||||
.style(btn(false))
|
||||
.padding([4, 8]),
|
||||
]
|
||||
.spacing(4)
|
||||
.into()
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
};
|
||||
let new_name: Element<'_, Message> = if !s.name_rename && s.name_input.is_some() {
|
||||
row![
|
||||
text_input("", rename_buf)
|
||||
.on_input(|value| Message::PlotDlg(PlotDlgMsg::NameInput(value)))
|
||||
.on_submit(Message::PlotDlg(PlotDlgMsg::NameCommit))
|
||||
.style(form::field_style)
|
||||
.size(11)
|
||||
.padding([4, 8])
|
||||
.width(Length::Fill),
|
||||
button(text(t!("Save")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::NameCommit))
|
||||
.style(btn(true))
|
||||
.padding([4, 8]),
|
||||
button(text("×").size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::NameCancel))
|
||||
.style(btn(false))
|
||||
.padding([4, 8]),
|
||||
]
|
||||
.spacing(4)
|
||||
.into()
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
};
|
||||
// While an import is being chosen, the list shows the other drawing's
|
||||
// setups with a tick each instead of this drawing's.
|
||||
let list_body: Element<'_, Message> = match &s.import_draft {
|
||||
@@ -1192,16 +1260,11 @@ pub fn view_window(
|
||||
.style(btn(false))
|
||||
.padding([4, 12]);
|
||||
if !print_all_options && s.import_draft.is_none() {
|
||||
import_button = import_button
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::Import(PageSetupImportMsg::Pick)));
|
||||
import_button = import_button.on_press(Message::PlotDlg(PlotDlgMsg::Import(
|
||||
PageSetupImportMsg::Pick,
|
||||
)));
|
||||
}
|
||||
let left_bar = row![
|
||||
new_button,
|
||||
copy_button,
|
||||
delete_button,
|
||||
import_button,
|
||||
]
|
||||
.spacing(4);
|
||||
let left_bar = row![new_button, copy_button, delete_button, import_button,].spacing(4);
|
||||
|
||||
// ── Printer / plotter ─────────────────────────────────────────────────
|
||||
// The default entry names the printer it resolves to, when known, so the
|
||||
@@ -1233,8 +1296,15 @@ pub fn view_window(
|
||||
let selected_paper = paper_catalog::from_drawing(&s.paper, s.paper_width_mm, s.paper_height_mm);
|
||||
let printer_sheets = (!s.to_file).then_some(()).and(s.printer_media.as_deref());
|
||||
let mut paper_opts: Vec<PlotChoice> = match printer_sheets {
|
||||
Some(caps) => caps.media.iter().map(|media| PlotChoice::paper(&media.paper)).collect(),
|
||||
None => paper_catalog::catalog().iter().map(PlotChoice::paper).collect(),
|
||||
Some(caps) => caps
|
||||
.media
|
||||
.iter()
|
||||
.map(|media| PlotChoice::paper(&media.paper))
|
||||
.collect(),
|
||||
None => paper_catalog::catalog()
|
||||
.iter()
|
||||
.map(PlotChoice::paper)
|
||||
.collect(),
|
||||
};
|
||||
// User-defined sheets come last, after whatever the device offers.
|
||||
for custom in &s.custom_papers {
|
||||
@@ -1252,20 +1322,24 @@ pub fn view_window(
|
||||
.iter()
|
||||
.any(|custom| custom.canonical() == selected_paper.canonical);
|
||||
let paper_source_note: Element<'_, Message> = if printer_sheets.is_some() {
|
||||
text(t!("Sheet sizes and printable areas reported by the printer."))
|
||||
.size(10)
|
||||
.style(muted_style)
|
||||
.width(width)
|
||||
.into()
|
||||
text(t!(
|
||||
"Sheet sizes and printable areas reported by the printer."
|
||||
))
|
||||
.size(10)
|
||||
.style(muted_style)
|
||||
.width(width)
|
||||
.into()
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
};
|
||||
let paper_note: Element<'_, Message> = if s.area == "Layout" {
|
||||
text(t!("Layout plots the current sheet using the selected paper size."))
|
||||
.size(10)
|
||||
.style(muted_style)
|
||||
.width(width)
|
||||
.into()
|
||||
text(t!(
|
||||
"Layout plots the current sheet using the selected paper size."
|
||||
))
|
||||
.size(10)
|
||||
.style(muted_style)
|
||||
.width(width)
|
||||
.into()
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
};
|
||||
@@ -1273,7 +1347,10 @@ pub fn view_window(
|
||||
// announce — and the picker takes the whole row so a long queue name
|
||||
// stays readable; the driver's Properties… button sits beneath it.
|
||||
let output_field = row![
|
||||
text(t!("Printer / plotter")).size(11).style(muted_style).width(form::LABEL_WIDTH),
|
||||
text(t!("Printer / plotter"))
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(form::LABEL_WIDTH),
|
||||
iced::widget::pick_list(printer_sel, printer_opts, |value| value.to_string())
|
||||
.on_select(|choice| Message::PlotDlg(PlotDlgMsg::Printer(choice.raw)))
|
||||
.text_size(12)
|
||||
@@ -1323,7 +1400,13 @@ pub fn view_window(
|
||||
// The picker takes the whole row so long media names stay readable; the
|
||||
// custom-sheet buttons sit right-aligned beneath it, and the editor
|
||||
// unfolds in their place.
|
||||
let size_row = drop_row(t!("Paper size"), paper_opts, Some(paper_sel), PlotDlgMsg::Paper, Length::Fill);
|
||||
let size_row = drop_row(
|
||||
t!("Paper size"),
|
||||
paper_opts,
|
||||
Some(paper_sel),
|
||||
PlotDlgMsg::Paper,
|
||||
Length::Fill,
|
||||
);
|
||||
let custom_controls: Element<'_, Message> = match &s.custom_editor {
|
||||
Some(draft) => custom_paper_editor(draft, width),
|
||||
None => {
|
||||
@@ -1331,7 +1414,9 @@ pub fn view_window(
|
||||
if selected_is_custom {
|
||||
buttons = buttons.push(
|
||||
button(text(t!("Remove")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::CustomPaper(CustomPaperMsg::Remove)))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::CustomPaper(
|
||||
CustomPaperMsg::Remove,
|
||||
)))
|
||||
.style(btn(false))
|
||||
.padding([4, 10]),
|
||||
);
|
||||
@@ -1339,7 +1424,9 @@ pub fn view_window(
|
||||
buttons
|
||||
.push(
|
||||
button(text(t!("Custom…")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::CustomPaper(CustomPaperMsg::Open)))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::CustomPaper(
|
||||
CustomPaperMsg::Open,
|
||||
)))
|
||||
.style(btn(false))
|
||||
.padding([4, 10]),
|
||||
)
|
||||
@@ -1348,12 +1435,8 @@ pub fn view_window(
|
||||
};
|
||||
// Like the printer row: the section is its one picker, so heading and
|
||||
// control share a line.
|
||||
let paper_panel = panel(column![
|
||||
size_row,
|
||||
custom_controls,
|
||||
paper_source_note,
|
||||
paper_note,
|
||||
].spacing(7));
|
||||
let paper_panel =
|
||||
panel(column![size_row, custom_controls, paper_source_note, paper_note,].spacing(7));
|
||||
|
||||
let mut area_options = if print_all_options {
|
||||
choices(&["Layout"])
|
||||
@@ -1371,16 +1454,19 @@ pub fn view_window(
|
||||
);
|
||||
}
|
||||
let mut area_row = row![
|
||||
text(t!("What to plot")).size(11).style(muted_style).width(92),
|
||||
text(t!("What to plot"))
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(92),
|
||||
iced::widget::pick_list(
|
||||
Some(PlotChoice::localized(s.area.clone())),
|
||||
area_options,
|
||||
|value| value.to_string(),
|
||||
)
|
||||
.on_select(|choice| Message::PlotDlg(PlotDlgMsg::Area(choice.raw)))
|
||||
.text_size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fill),
|
||||
.on_select(|choice| Message::PlotDlg(PlotDlgMsg::Area(choice.raw)))
|
||||
.text_size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center);
|
||||
@@ -1399,57 +1485,87 @@ pub fn view_window(
|
||||
} else {
|
||||
(t!("X (mm)"), t!("Y (mm)"))
|
||||
};
|
||||
let area_panel = panel(column![
|
||||
section_label(t!("Plot area")),
|
||||
area_row,
|
||||
section_label(t!("Plot offset")),
|
||||
let area_panel = panel(
|
||||
column![
|
||||
field_row_enabled(offset_x_label, &s.offset_x, PlotDlgMsg::OffsetX, 70, common_area && !s.center),
|
||||
field_row_enabled(offset_y_label, &s.offset_y, PlotDlgMsg::OffsetY, 70, common_area && !s.center),
|
||||
section_label(t!("Plot area")),
|
||||
area_row,
|
||||
section_label(t!("Plot offset")),
|
||||
column![
|
||||
field_row_enabled(
|
||||
offset_x_label,
|
||||
&s.offset_x,
|
||||
PlotDlgMsg::OffsetX,
|
||||
70,
|
||||
common_area && !s.center
|
||||
),
|
||||
field_row_enabled(
|
||||
offset_y_label,
|
||||
&s.offset_y,
|
||||
PlotDlgMsg::OffsetY,
|
||||
70,
|
||||
common_area && !s.center
|
||||
),
|
||||
]
|
||||
.spacing(7),
|
||||
check_enabled(
|
||||
t!("Center the plot"),
|
||||
s.center,
|
||||
PlotFlag::Center,
|
||||
common_area
|
||||
),
|
||||
]
|
||||
.spacing(7),
|
||||
check_enabled(t!("Center the plot"), s.center, PlotFlag::Center, common_area),
|
||||
].spacing(7));
|
||||
);
|
||||
let scale_options = s
|
||||
.scales
|
||||
.iter()
|
||||
.map(|(name, _)| PlotChoice::raw(name.clone()))
|
||||
.collect();
|
||||
let scale_panel = panel(column![
|
||||
section_label(t!("Plot scale")),
|
||||
check_enabled(
|
||||
t!("Fit to paper"),
|
||||
s.fit_to_paper,
|
||||
PlotFlag::FitToPaper,
|
||||
common_area,
|
||||
),
|
||||
// "Scale" intentionally stays untranslated: it would clash with the
|
||||
// ribbon's zoom tool label under the same lookup key.
|
||||
drop_row_enabled(
|
||||
Cow::Borrowed("Scale"),
|
||||
scale_options,
|
||||
Some(PlotChoice::raw(s.scale.clone())),
|
||||
PlotDlgMsg::Scale,
|
||||
width,
|
||||
common_area && !s.fit_to_paper,
|
||||
),
|
||||
// The custom scale spells the picked ratio in the page-setup unit
|
||||
// and accepts one of its own; the unit picker decides that unit.
|
||||
drop_row(
|
||||
t!("Units"),
|
||||
vec![PlotChoice::localized("Millimeters"), PlotChoice::localized("Inches")],
|
||||
Some(PlotChoice::localized(if inches { "Inches" } else { "Millimeters" })),
|
||||
PlotDlgMsg::PaperUnits,
|
||||
width,
|
||||
),
|
||||
custom_scale_row(s, common_area && !s.fit_to_paper),
|
||||
check_enabled(
|
||||
t!("Scale lineweights"),
|
||||
s.scale_lw && !s.fit_to_paper,
|
||||
PlotFlag::ScaleLw,
|
||||
!s.fit_to_paper,
|
||||
),
|
||||
].spacing(7));
|
||||
let scale_panel = panel(
|
||||
column![
|
||||
section_label(t!("Plot scale")),
|
||||
check_enabled(
|
||||
t!("Fit to paper"),
|
||||
s.fit_to_paper,
|
||||
PlotFlag::FitToPaper,
|
||||
common_area,
|
||||
),
|
||||
// "Scale" intentionally stays untranslated: it would clash with the
|
||||
// ribbon's zoom tool label under the same lookup key.
|
||||
drop_row_enabled(
|
||||
Cow::Borrowed("Scale"),
|
||||
scale_options,
|
||||
Some(PlotChoice::raw(s.scale.clone())),
|
||||
PlotDlgMsg::Scale,
|
||||
width,
|
||||
common_area && !s.fit_to_paper,
|
||||
),
|
||||
// The custom scale spells the picked ratio in the page-setup unit
|
||||
// and accepts one of its own; the unit picker decides that unit.
|
||||
drop_row(
|
||||
t!("Units"),
|
||||
vec![
|
||||
PlotChoice::localized("Millimeters"),
|
||||
PlotChoice::localized("Inches")
|
||||
],
|
||||
Some(PlotChoice::localized(if inches {
|
||||
"Inches"
|
||||
} else {
|
||||
"Millimeters"
|
||||
})),
|
||||
PlotDlgMsg::PaperUnits,
|
||||
width,
|
||||
),
|
||||
custom_scale_row(s, common_area && !s.fit_to_paper),
|
||||
check_enabled(
|
||||
t!("Scale lineweights"),
|
||||
s.scale_lw && !s.fit_to_paper,
|
||||
PlotFlag::ScaleLw,
|
||||
!s.fit_to_paper,
|
||||
),
|
||||
]
|
||||
.spacing(7),
|
||||
);
|
||||
|
||||
// ── Style and shaded viewport settings ───────────────────────────────
|
||||
let mut style_options = vec![PlotChoice::localized(STYLE_NONE)];
|
||||
@@ -1466,111 +1582,147 @@ pub fn view_window(
|
||||
} else {
|
||||
PlotChoice::raw(s.style_name.clone())
|
||||
};
|
||||
let style_panel = panel(column![
|
||||
section_label(t!("Plot style table (pen assignments)")),
|
||||
drop_row(
|
||||
t!("Table"),
|
||||
style_options,
|
||||
Some(style_selected),
|
||||
PlotDlgMsg::Style,
|
||||
width,
|
||||
),
|
||||
style_note(s),
|
||||
check_enabled(
|
||||
t!("Plot with plot styles"),
|
||||
s.apply_plot_styles,
|
||||
PlotFlag::PlotStyles,
|
||||
!s.style_name.is_empty(),
|
||||
),
|
||||
check_enabled(
|
||||
t!("Display plot styles"),
|
||||
s.show_plot_styles,
|
||||
PlotFlag::DisplayStyles,
|
||||
s.paper_space && !s.style_name.is_empty(),
|
||||
),
|
||||
row![
|
||||
button(text(t!("Load…")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::LoadStyle))
|
||||
.style(btn(false))
|
||||
.padding([4, 10]),
|
||||
|
||||
button(text(t!("Edit…")).size(11))
|
||||
.on_press(Message::PlotStylePanelOpen)
|
||||
.style(btn(false))
|
||||
.padding([4, 10]),
|
||||
let style_panel = panel(
|
||||
column![
|
||||
section_label(t!("Plot style table (pen assignments)")),
|
||||
drop_row(
|
||||
t!("Table"),
|
||||
style_options,
|
||||
Some(style_selected),
|
||||
PlotDlgMsg::Style,
|
||||
width,
|
||||
),
|
||||
style_note(s),
|
||||
check_enabled(
|
||||
t!("Plot with plot styles"),
|
||||
s.apply_plot_styles,
|
||||
PlotFlag::PlotStyles,
|
||||
!s.style_name.is_empty(),
|
||||
),
|
||||
check_enabled(
|
||||
t!("Display plot styles"),
|
||||
s.show_plot_styles,
|
||||
PlotFlag::DisplayStyles,
|
||||
s.paper_space && !s.style_name.is_empty(),
|
||||
),
|
||||
row![
|
||||
button(text(t!("Load…")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::LoadStyle))
|
||||
.style(btn(false))
|
||||
.padding([4, 10]),
|
||||
button(text(t!("Edit…")).size(11))
|
||||
.on_press(Message::PlotStylePanelOpen)
|
||||
.style(btn(false))
|
||||
.padding([4, 10]),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
].spacing(7));
|
||||
.spacing(7),
|
||||
);
|
||||
|
||||
let shaded_panel = panel(column![
|
||||
section_label(t!("Shaded viewport options")),
|
||||
drop_row(
|
||||
t!("Shade plot"),
|
||||
choices(&[
|
||||
"As displayed",
|
||||
"2D Wireframe",
|
||||
"3D Wireframe",
|
||||
"Hidden Line",
|
||||
"Flat Shaded",
|
||||
"Gouraud Shaded",
|
||||
"Flat Shaded + Edges",
|
||||
"Gouraud Shaded + Edges",
|
||||
]),
|
||||
Some(PlotChoice::localized(s.shade.clone())),
|
||||
PlotDlgMsg::Shade,
|
||||
width,
|
||||
),
|
||||
drop_row(
|
||||
t!("Quality"),
|
||||
choices(&["Low", "Normal", "High"]),
|
||||
Some(PlotChoice::localized(s.quality.clone())),
|
||||
PlotDlgMsg::Quality,
|
||||
width,
|
||||
),
|
||||
].spacing(7));
|
||||
let shaded_panel = panel(
|
||||
column![
|
||||
section_label(t!("Shaded viewport options")),
|
||||
drop_row(
|
||||
t!("Shade plot"),
|
||||
choices(&[
|
||||
"As displayed",
|
||||
"2D Wireframe",
|
||||
"3D Wireframe",
|
||||
"Hidden Line",
|
||||
"Flat Shaded",
|
||||
"Gouraud Shaded",
|
||||
"Flat Shaded + Edges",
|
||||
"Gouraud Shaded + Edges",
|
||||
]),
|
||||
Some(PlotChoice::localized(s.shade.clone())),
|
||||
PlotDlgMsg::Shade,
|
||||
width,
|
||||
),
|
||||
drop_row(
|
||||
t!("Quality"),
|
||||
choices(&["Low", "Normal", "High"]),
|
||||
Some(PlotChoice::localized(s.quality.clone())),
|
||||
PlotDlgMsg::Quality,
|
||||
width,
|
||||
),
|
||||
]
|
||||
.spacing(7),
|
||||
);
|
||||
|
||||
// ── Output options and orientation ────────────────────────────────────
|
||||
let paper_space_option = |on: bool, label: Cow<'static, str>, flag: PlotFlag| -> Element<'_, Message> {
|
||||
if s.paper_space {
|
||||
check(label, on, flag)
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
}
|
||||
};
|
||||
let options_panel = panel(column![
|
||||
section_label(t!("Plot options")),
|
||||
row![
|
||||
column![
|
||||
check(t!("Plot in background"), s.background, PlotFlag::Background),
|
||||
check(t!("Object lineweights"), s.lineweights, PlotFlag::Lineweights),
|
||||
check(t!("Plot transparency"), s.transparency, PlotFlag::Transparency),
|
||||
paper_space_option(s.hide_paperspace, t!("Hide paperspace objects"), PlotFlag::HidePaperspace),
|
||||
let paper_space_option =
|
||||
|on: bool, label: Cow<'static, str>, flag: PlotFlag| -> Element<'_, Message> {
|
||||
if s.paper_space {
|
||||
check(label, on, flag)
|
||||
} else {
|
||||
Space::new().height(0).into()
|
||||
}
|
||||
};
|
||||
let options_panel = panel(
|
||||
column![
|
||||
section_label(t!("Plot options")),
|
||||
row![
|
||||
column![
|
||||
check(t!("Plot in background"), s.background, PlotFlag::Background),
|
||||
check(
|
||||
t!("Object lineweights"),
|
||||
s.lineweights,
|
||||
PlotFlag::Lineweights
|
||||
),
|
||||
check(
|
||||
t!("Plot transparency"),
|
||||
s.transparency,
|
||||
PlotFlag::Transparency
|
||||
),
|
||||
paper_space_option(
|
||||
s.hide_paperspace,
|
||||
t!("Hide paperspace objects"),
|
||||
PlotFlag::HidePaperspace
|
||||
),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(width),
|
||||
column![
|
||||
paper_space_option(
|
||||
s.paperspace_last,
|
||||
t!("Paper space last"),
|
||||
PlotFlag::PaperspaceLast
|
||||
),
|
||||
check(
|
||||
t!("Merge overlapping lines"),
|
||||
s.merge_lines,
|
||||
PlotFlag::MergeLines
|
||||
),
|
||||
check(t!("Plot stamp"), s.stamp, PlotFlag::Stamp),
|
||||
check(
|
||||
t!("Save changes to layout"),
|
||||
s.save_to_layout,
|
||||
PlotFlag::SaveToLayout
|
||||
),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(width),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(width),
|
||||
column![
|
||||
paper_space_option(s.paperspace_last, t!("Paper space last"), PlotFlag::PaperspaceLast),
|
||||
check(t!("Merge overlapping lines"), s.merge_lines, PlotFlag::MergeLines),
|
||||
check(t!("Plot stamp"), s.stamp, PlotFlag::Stamp),
|
||||
check(t!("Save changes to layout"), s.save_to_layout, PlotFlag::SaveToLayout),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(width),
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(10),
|
||||
].spacing(7));
|
||||
.spacing(7),
|
||||
);
|
||||
|
||||
let orientation_panel = panel(column![
|
||||
drop_row(
|
||||
t!("Orientation"),
|
||||
choices(&["Portrait", "Landscape"]),
|
||||
Some(PlotChoice::localized(s.orientation.clone())),
|
||||
PlotDlgMsg::Orientation,
|
||||
width,
|
||||
),
|
||||
check(t!("Plot upside-down"), s.upside_down, PlotFlag::UpsideDown),
|
||||
].spacing(7));
|
||||
let orientation_panel = panel(
|
||||
column![
|
||||
drop_row(
|
||||
t!("Orientation"),
|
||||
choices(&["Portrait", "Landscape"]),
|
||||
Some(PlotChoice::localized(s.orientation.clone())),
|
||||
PlotDlgMsg::Orientation,
|
||||
width,
|
||||
),
|
||||
check(t!("Plot upside-down"), s.upside_down, PlotFlag::UpsideDown),
|
||||
]
|
||||
.spacing(7),
|
||||
);
|
||||
|
||||
let left = column![
|
||||
printer_panel,
|
||||
@@ -1580,8 +1732,8 @@ pub fn view_window(
|
||||
hdivider(width),
|
||||
area_panel,
|
||||
]
|
||||
.spacing(9)
|
||||
.width(width);
|
||||
.spacing(9)
|
||||
.width(width);
|
||||
let right = column![
|
||||
scale_panel,
|
||||
hdivider(width),
|
||||
@@ -1591,32 +1743,30 @@ pub fn view_window(
|
||||
hdivider(width),
|
||||
options_panel,
|
||||
]
|
||||
.spacing(9)
|
||||
.width(width);
|
||||
let detail = scrollable(
|
||||
container(row![left, right].spacing(18).width(width)).padding(14),
|
||||
)
|
||||
.width(width)
|
||||
.height(height);
|
||||
.spacing(9)
|
||||
.width(width);
|
||||
let detail = scrollable(container(row![left, right].spacing(18).width(width)).padding(14))
|
||||
.width(width)
|
||||
.height(height);
|
||||
let body = row![list_panel, vsep(height), detail]
|
||||
.width(width)
|
||||
.height(height);
|
||||
let mut toolbar_row = row![left_bar, Space::new().width(width)]
|
||||
.align_y(iced::Center)
|
||||
.push(
|
||||
button(text(t!("Set current")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::SetCurrent))
|
||||
.style(btn(false))
|
||||
.padding([4, 12]),
|
||||
)
|
||||
.push(Space::new().width(6))
|
||||
.push(
|
||||
button(text(t!("Preview")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::Preview))
|
||||
.style(btn(false))
|
||||
.padding([4, 12]),
|
||||
)
|
||||
.push(Space::new().width(6));
|
||||
.align_y(iced::Center)
|
||||
.push(
|
||||
button(text(t!("Set current")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::SetCurrent))
|
||||
.style(btn(false))
|
||||
.padding([4, 12]),
|
||||
)
|
||||
.push(Space::new().width(6))
|
||||
.push(
|
||||
button(text(t!("Preview")).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::Preview))
|
||||
.style(btn(false))
|
||||
.padding([4, 12]),
|
||||
)
|
||||
.push(Space::new().width(6));
|
||||
toolbar_row = toolbar_row.push(
|
||||
button(text(action).size(11))
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::Commit))
|
||||
@@ -1633,9 +1783,7 @@ pub fn view_window(
|
||||
|
||||
container(column![toolbar, hdivider(width), body].spacing(0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(width)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::plugin::external::{ExternalPlugin, RegistryEntry, ReleaseInfo};
|
||||
use crate::t;
|
||||
use crate::ui::style::common::muted_style;
|
||||
use iced::widget::{
|
||||
button, column, container, markdown, row, rule, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use crate::t;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::borrow::Cow;
|
||||
|
||||
@@ -61,13 +61,13 @@ fn badge<'a>(label: String) -> Element<'a, Message> {
|
||||
.style(|theme: &Theme| {
|
||||
let pair = theme.palette().primary.weak;
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -85,7 +85,11 @@ fn toggle_button<'a>(id: &str, disabled: bool) -> Element<'a, Message> {
|
||||
button(text(label).size(12))
|
||||
.padding([3, 12])
|
||||
.on_press(Message::SetPluginEnabled(id_owned, want_enabled))
|
||||
.style(if disabled { button::success } else { button::danger })
|
||||
.style(if disabled {
|
||||
button::success
|
||||
} else {
|
||||
button::danger
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -110,13 +114,13 @@ fn status_badge<'a>(label: Cow<'static, str>, kind: StatusKind) -> Element<'a, M
|
||||
StatusKind::Warning => palette.warning.weak,
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -140,10 +144,7 @@ fn card_style(theme: &Theme, selected: bool) -> container::Style {
|
||||
}
|
||||
}
|
||||
|
||||
fn repository_for_external(
|
||||
plugin: &ExternalPlugin,
|
||||
registry: &[RegistryEntry],
|
||||
) -> Option<String> {
|
||||
fn repository_for_external(plugin: &ExternalPlugin, registry: &[RegistryEntry]) -> Option<String> {
|
||||
plugin.repository.clone().or_else(|| {
|
||||
registry
|
||||
.iter()
|
||||
@@ -182,8 +183,7 @@ fn repository_is_installed(
|
||||
) -> bool {
|
||||
externals.iter().any(|plugin| {
|
||||
plugin.repository.as_deref() == Some(repository)
|
||||
|| registry_name
|
||||
.is_some_and(|name| plugin.name.eq_ignore_ascii_case(name))
|
||||
|| registry_name.is_some_and(|name| plugin.name.eq_ignore_ascii_case(name))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,8 +221,8 @@ fn external_card<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
let failed_old_api = load_error.is_some() && !p.api_compatible();
|
||||
let acadrust_mismatch = p.acadrust_declared && !p.acadrust_compatible();
|
||||
let rustc_mismatch = ocs_plugin_api::version_info::uses_acadrust_gate(p.api_version)
|
||||
&& !p.rustc_compatible();
|
||||
let rustc_mismatch =
|
||||
ocs_plugin_api::version_info::uses_acadrust_gate(p.api_version) && !p.rustc_compatible();
|
||||
let (status, kind) = if loaded && disabled {
|
||||
(t!("Disabled"), StatusKind::Muted)
|
||||
} else if loaded {
|
||||
@@ -274,14 +274,11 @@ fn external_card<'a>(
|
||||
} else {
|
||||
t!("Load failed: %{error}", error = error)
|
||||
};
|
||||
info_body = info_body.push(
|
||||
text(detail)
|
||||
.size(11)
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().danger.base.color),
|
||||
}),
|
||||
);
|
||||
info_body = info_body.push(text(detail).size(11).width(Fill).style(|theme: &Theme| {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.palette().danger.base.color),
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
let info: Element<'a, Message> = if let Some(repo) = repository.clone() {
|
||||
@@ -366,17 +363,17 @@ fn install_controls<'a>(
|
||||
.iter()
|
||||
.map(|release| release.tag.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let selected_release = selected.as_ref().and_then(|selected| {
|
||||
releases.iter().find(|release| release.tag == *selected)
|
||||
});
|
||||
let selected_release = selected
|
||||
.as_ref()
|
||||
.and_then(|selected| releases.iter().find(|release| release.tag == *selected));
|
||||
let picker: Element<'_, Message> = if tags.is_empty() {
|
||||
text(t!("no releases")).size(11).style(muted_style).into()
|
||||
} else {
|
||||
let r = repo_s.clone();
|
||||
iced::widget::pick_list(selected, tags, |value| value.to_string())
|
||||
.on_select(move |tag| Message::PluginReleaseSelect(r.clone(), tag))
|
||||
.text_size(12)
|
||||
.into()
|
||||
.on_select(move |tag| Message::PluginReleaseSelect(r.clone(), tag))
|
||||
.text_size(12)
|
||||
.into()
|
||||
};
|
||||
let action = match selected_release {
|
||||
Some(release)
|
||||
@@ -504,11 +501,7 @@ fn registry_notice<'a>(m: &MarketView) -> Option<Element<'a, Message>> {
|
||||
if let Some(error) = m.registry_error {
|
||||
let (title, message) = registry_error_message(error);
|
||||
let actions = row![
|
||||
pill_button(
|
||||
t!("Retry"),
|
||||
Message::PluginRegistryRetry,
|
||||
button::primary,
|
||||
),
|
||||
pill_button(t!("Retry"), Message::PluginRegistryRetry, button::primary,),
|
||||
Space::new().width(6),
|
||||
pill_button(
|
||||
if m.registry_error_details_open {
|
||||
@@ -565,9 +558,11 @@ fn registry_notice<'a>(m: &MarketView) -> Option<Element<'a, Message>> {
|
||||
container(
|
||||
column![
|
||||
text(t!("Loading plugin catalog…")).size(13),
|
||||
text(t!("Connecting securely using your system certificate settings."))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
text(t!(
|
||||
"Connecting securely using your system certificate settings."
|
||||
))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(5)
|
||||
.padding([10, 12]),
|
||||
@@ -578,10 +573,7 @@ fn registry_notice<'a>(m: &MarketView) -> Option<Element<'a, Message>> {
|
||||
})
|
||||
}
|
||||
|
||||
fn marketplace_section<'a>(
|
||||
m: &MarketView,
|
||||
externals: &[ExternalPlugin],
|
||||
) -> Element<'a, Message> {
|
||||
fn marketplace_section<'a>(m: &MarketView, externals: &[ExternalPlugin]) -> Element<'a, Message> {
|
||||
let mut col = column![text(t!("Available plugins")).size(13).style(primary_style)].spacing(6);
|
||||
let mut visible = 0usize;
|
||||
if let Some(notice) = registry_notice(m) {
|
||||
@@ -656,9 +648,7 @@ fn marketplace_section<'a>(
|
||||
));
|
||||
}
|
||||
|
||||
if visible == 0
|
||||
&& m.registry_error.is_none()
|
||||
&& !(m.registry_loading && m.registry.is_empty())
|
||||
if visible == 0 && m.registry_error.is_none() && !(m.registry_loading && m.registry.is_empty())
|
||||
{
|
||||
let message = if m.search.trim().is_empty() {
|
||||
t!("No additional plugins are available.")
|
||||
@@ -677,10 +667,7 @@ fn marketplace_section<'a>(
|
||||
}
|
||||
|
||||
fn resolve_readme_link(repo: &str, uri: &str) -> String {
|
||||
if uri.starts_with("https://")
|
||||
|| uri.starts_with("http://")
|
||||
|| uri.starts_with("mailto:")
|
||||
{
|
||||
if uri.starts_with("https://") || uri.starts_with("http://") || uri.starts_with("mailto:") {
|
||||
uri.to_string()
|
||||
} else if uri.starts_with('/') {
|
||||
format!("https://github.com{uri}")
|
||||
@@ -761,9 +748,7 @@ fn readme_panel<'a>(
|
||||
readme.items(),
|
||||
markdown::Settings::with_text_size(13, theme),
|
||||
)
|
||||
.map(move |uri| {
|
||||
Message::OpenUrl(resolve_readme_link(&source_repo, &uri))
|
||||
})
|
||||
.map(move |uri| Message::OpenUrl(resolve_readme_link(&source_repo, &uri)))
|
||||
}
|
||||
Some(Err(error)) => container(
|
||||
column![
|
||||
@@ -831,15 +816,20 @@ pub fn view_window<'a>(
|
||||
let width = sizing.width;
|
||||
let height = sizing.height;
|
||||
let title = text(t!("Plugins")).size(20);
|
||||
let subtitle =
|
||||
text(t!("Browse, install, and manage add-ons. Select one to view its README."))
|
||||
.size(12)
|
||||
.style(muted_style);
|
||||
let subtitle = text(t!(
|
||||
"Browse, install, and manage add-ons. Select one to view its README."
|
||||
))
|
||||
.size(12)
|
||||
.style(muted_style);
|
||||
|
||||
let mut list = column![].spacing(10);
|
||||
// Installed external packages (from the plugins folder).
|
||||
if externals.is_empty() {
|
||||
list = list.push(text(t!("No plugins installed yet.")).size(13).style(muted_style));
|
||||
list = list.push(
|
||||
text(t!("No plugins installed yet."))
|
||||
.size(13)
|
||||
.style(muted_style),
|
||||
);
|
||||
} else {
|
||||
let mut visible_installed = 0usize;
|
||||
for p in externals {
|
||||
@@ -914,9 +904,7 @@ pub fn view_window<'a>(
|
||||
.height(height),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(width)
|
||||
@@ -942,9 +930,7 @@ pub fn view_web_notice<'a>() -> Element<'a, Message> {
|
||||
))
|
||||
.center(Length::Fixed(44.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().primary.weak.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().primary.weak.color)),
|
||||
border: Border {
|
||||
radius: 12.0.into(),
|
||||
..Default::default()
|
||||
@@ -976,11 +962,11 @@ pub fn view_web_notice<'a>() -> Element<'a, Message> {
|
||||
.width(Length::Fit.max(380.0));
|
||||
|
||||
container(notice)
|
||||
.center_x(Length::Fit)
|
||||
.padding([16, 20])
|
||||
.width(Length::Fit)
|
||||
.height(Length::Fit)
|
||||
.into()
|
||||
.center_x(Length::Fit)
|
||||
.padding([16, 20])
|
||||
.width(Length::Fit)
|
||||
.height(Length::Fit)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -37,12 +37,18 @@ pub fn view_window<'a>(
|
||||
})
|
||||
.collect();
|
||||
let list: Element<'a, Message> = if rows.is_empty() {
|
||||
container(text(t!("No paper layouts are available.")).size(12).style(muted))
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.into()
|
||||
container(
|
||||
text(t!("No paper layouts are available."))
|
||||
.size(12)
|
||||
.style(muted),
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.into()
|
||||
} else {
|
||||
scrollable(column(rows).spacing(2)).height(sizing.height).into()
|
||||
scrollable(column(rows).spacing(2))
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
};
|
||||
let printer = printer
|
||||
.filter(|name| !name.is_empty())
|
||||
@@ -81,8 +87,10 @@ pub fn view_window<'a>(
|
||||
|
||||
container(
|
||||
column![
|
||||
text(t!("Select the layouts to output. Each page is printed in Layout mode."))
|
||||
.size(12),
|
||||
text(t!(
|
||||
"Select the layouts to output. Each page is printed in Layout mode."
|
||||
))
|
||||
.size(12),
|
||||
row![
|
||||
button(text(t!("Select all")).size(11))
|
||||
.on_press(Message::PrintAllSelectAll)
|
||||
|
||||
@@ -4,9 +4,7 @@ use crate::ui::style::common::muted_style;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Shrink, Theme};
|
||||
|
||||
fn status_style(
|
||||
status: RecoveryStatus,
|
||||
) -> impl Fn(&Theme) -> iced::widget::text::Style + Copy {
|
||||
fn status_style(status: RecoveryStatus) -> impl Fn(&Theme) -> iced::widget::text::Style + Copy {
|
||||
move |theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(match status {
|
||||
RecoveryStatus::Recovered => theme.palette().warning.base.color,
|
||||
@@ -107,9 +105,13 @@ pub fn view_window<'a>(
|
||||
}
|
||||
if let Some(path) = &report.log_path {
|
||||
details = details.push(
|
||||
text(format!("{}: {}", crate::tr!("recovery", "log-path"), path.display()))
|
||||
.size(10)
|
||||
.style(muted_style),
|
||||
text(format!(
|
||||
"{}: {}",
|
||||
crate::tr!("recovery", "log-path"),
|
||||
path.display()
|
||||
))
|
||||
.size(10)
|
||||
.style(muted_style),
|
||||
);
|
||||
} else if let Some(error) = &report.log_error {
|
||||
details = details.push(
|
||||
|
||||
+727
-210
File diff suppressed because it is too large.
Load diff
+29
-34
@@ -29,18 +29,16 @@ fn danger_input_style(
|
||||
let danger = theme.palette().danger.base;
|
||||
iced::widget::text_input::Style {
|
||||
background: Background::Color(theme.palette().danger.weak.color),
|
||||
border: iced::border::rounded(4)
|
||||
.color(danger.color)
|
||||
.width(
|
||||
if matches!(
|
||||
status,
|
||||
iced::widget::text_input::Status::Focused { is_hovered: _ }
|
||||
) {
|
||||
1.5
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
),
|
||||
border: iced::border::rounded(4).color(danger.color).width(
|
||||
if matches!(
|
||||
status,
|
||||
iced::widget::text_input::Status::Focused { is_hovered: _ }
|
||||
) {
|
||||
1.5
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
),
|
||||
icon: danger.color,
|
||||
placeholder: danger.color.scale_alpha(0.7),
|
||||
value: danger.text,
|
||||
@@ -74,8 +72,7 @@ pub fn view_window<'a>(
|
||||
|
||||
let head = container(
|
||||
row![
|
||||
container(text(t!("Key")).size(11).style(muted_style))
|
||||
.width(Length::Fixed(180.0)),
|
||||
container(text(t!("Key")).size(11).style(muted_style)).width(Length::Fixed(180.0)),
|
||||
container(text(t!("Command")).size(11).style(muted_style)).width(sizing.width),
|
||||
Space::new().width(Length::Fixed(30.0)),
|
||||
]
|
||||
@@ -119,8 +116,7 @@ pub fn view_window<'a>(
|
||||
.width(Length::Fixed(180.0))
|
||||
.style(button::secondary)
|
||||
};
|
||||
let unknown_command =
|
||||
!command.is_empty() && unknown_commands.contains(command.trim());
|
||||
let unknown_command = !command.is_empty() && unknown_commands.contains(command.trim());
|
||||
let command_box = text_input(t!("command").as_ref(), command)
|
||||
.on_input(move |value| Message::ShortcutEditorInput {
|
||||
idx,
|
||||
@@ -199,9 +195,11 @@ pub fn view_window<'a>(
|
||||
let stats = if reset_confirm {
|
||||
row![Space::new().width(Length::Fixed(0.0))]
|
||||
} else {
|
||||
row![text(format!("{}: {}", t!("Number of shortcuts"), rows.len()))
|
||||
.size(12)
|
||||
.style(muted_style)]
|
||||
row![
|
||||
text(format!("{}: {}", t!("Number of shortcuts"), rows.len()))
|
||||
.size(12)
|
||||
.style(muted_style)
|
||||
]
|
||||
};
|
||||
// Reset asks for confirmation in place: the add/reset buttons are
|
||||
// replaced by the question with Yes / No.
|
||||
@@ -284,9 +282,15 @@ pub fn view_window<'a>(
|
||||
Space::new().height(6),
|
||||
conflict_banner,
|
||||
Space::new().height(4),
|
||||
row![stats, add_area, reset_area, Space::new().width(sizing.width), apply_area]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
stats,
|
||||
add_area,
|
||||
reset_area,
|
||||
Space::new().width(sizing.width),
|
||||
apply_area
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(sizing.width)
|
||||
@@ -296,9 +300,7 @@ pub fn view_window<'a>(
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -313,12 +315,7 @@ pub fn view_window<'a>(
|
||||
.height(Length::Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.strongest
|
||||
.color
|
||||
.scale_alpha(0.55),
|
||||
theme.palette().background.strongest.color.scale_alpha(0.55),
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -346,9 +343,7 @@ pub fn view_window<'a>(
|
||||
.padding(16)
|
||||
.width(Length::Fixed(320.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
border: iced::Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use crate::ui::style::common::muted_style;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Element, Theme};
|
||||
use crate::t;
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
@@ -50,17 +50,17 @@ fn version_card<'a>(
|
||||
palette.background.base
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: if highlight {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: if highlight {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -78,9 +78,7 @@ fn version_card<'a>(
|
||||
fn render_notes_line<'a>(raw: &str) -> Element<'a, Message> {
|
||||
let trimmed = raw.trim_end();
|
||||
if trimmed.is_empty() {
|
||||
return Space::new()
|
||||
.height(iced::Length::Fixed(6.0))
|
||||
.into();
|
||||
return Space::new().height(iced::Length::Fixed(6.0)).into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("## ") {
|
||||
return text(strip_inline_md(rest))
|
||||
@@ -89,13 +87,18 @@ fn render_notes_line<'a>(raw: &str) -> Element<'a, Message> {
|
||||
.into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("### ") {
|
||||
return text(strip_inline_md(rest))
|
||||
.size(12)
|
||||
.into();
|
||||
return text(strip_inline_md(rest)).size(12).into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("- ").or_else(|| trimmed.strip_prefix("* ")) {
|
||||
if let Some(rest) = trimmed
|
||||
.strip_prefix("- ")
|
||||
.or_else(|| trimmed.strip_prefix("* "))
|
||||
{
|
||||
return row![
|
||||
container(crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 5.0)).width(14),
|
||||
container(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::DOT,
|
||||
5.0
|
||||
))
|
||||
.width(14),
|
||||
text(strip_inline_md(rest)).size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
@@ -129,10 +132,14 @@ pub fn view_window<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
let header = container(
|
||||
column![
|
||||
text(t!("New Release Available")).size(20).style(primary_style),
|
||||
text(t!("A newer Open CAD Studio version is published on GitHub."))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
text(t!("New Release Available"))
|
||||
.size(20)
|
||||
.style(primary_style),
|
||||
text(t!(
|
||||
"A newer Open CAD Studio version is published on GitHub."
|
||||
))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
@@ -161,9 +168,9 @@ pub fn view_window<'a>(
|
||||
20.0,
|
||||
))
|
||||
.width(iced::Length::Fixed(32.0))
|
||||
.height(sizing.height)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center);
|
||||
.height(sizing.height)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center);
|
||||
let info_block = row![installed, arrow, latest_card]
|
||||
.spacing(0)
|
||||
.align_y(iced::Center)
|
||||
@@ -192,8 +199,8 @@ pub fn view_window<'a>(
|
||||
// Release notes panel. Rendered as a light-markdown column inside a
|
||||
// bordered scrollable so long bodies stay contained and don't
|
||||
// explode the window. Empty body → "No release notes provided."
|
||||
let notes_heading = container(text(t!("What's new")).size(11).style(muted_style))
|
||||
.padding(iced::Padding {
|
||||
let notes_heading =
|
||||
container(text(t!("What's new")).size(11).style(muted_style)).padding(iced::Padding {
|
||||
top: 10.0,
|
||||
right: 0.0,
|
||||
bottom: 4.0,
|
||||
@@ -238,9 +245,7 @@ pub fn view_window<'a>(
|
||||
}),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(sizing.width)
|
||||
|
||||
+28
-12
@@ -3,9 +3,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use codec::Handle;
|
||||
use iced::widget::{
|
||||
button, column, combo_box, container, pick_list, row, text, text_input, Space,
|
||||
};
|
||||
use iced::widget::{button, column, combo_box, container, pick_list, row, text, text_input, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
@@ -294,27 +292,37 @@ pub fn view_window<'a>(
|
||||
qsel_btn = qsel_btn.style(button_style(false));
|
||||
}
|
||||
|
||||
let obj_btns_row = row![sel_obj_btn, qsel_btn]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
let obj_btns_row = row![sel_obj_btn, qsel_btn].spacing(5).align_y(iced::Center);
|
||||
|
||||
let object_radios = column![
|
||||
form_radio(
|
||||
t!("Retain"),
|
||||
WblockObjectMode::Retain,
|
||||
if is_objects { Some(state.object_mode) } else { None },
|
||||
if is_objects {
|
||||
Some(state.object_mode)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
Message::WblockObjectMode,
|
||||
),
|
||||
form_radio(
|
||||
t!("Convert to block"),
|
||||
WblockObjectMode::Convert,
|
||||
if is_objects { Some(state.object_mode) } else { None },
|
||||
if is_objects {
|
||||
Some(state.object_mode)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
Message::WblockObjectMode,
|
||||
),
|
||||
form_radio(
|
||||
t!("Delete from drawing"),
|
||||
WblockObjectMode::Delete,
|
||||
if is_objects { Some(state.object_mode) } else { None },
|
||||
if is_objects {
|
||||
Some(state.object_mode)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
Message::WblockObjectMode,
|
||||
),
|
||||
]
|
||||
@@ -348,8 +356,14 @@ pub fn view_window<'a>(
|
||||
|
||||
let objects_subgroup = group(
|
||||
t!("Objects").into_owned(),
|
||||
column![obj_btns_row, Space::new().height(2), object_radios, Space::new().height(2), status_row,]
|
||||
.spacing(2),
|
||||
column![
|
||||
obj_btns_row,
|
||||
Space::new().height(2),
|
||||
object_radios,
|
||||
Space::new().height(2),
|
||||
status_row,
|
||||
]
|
||||
.spacing(2),
|
||||
Length::Fixed(142.0),
|
||||
);
|
||||
|
||||
@@ -389,7 +403,9 @@ pub fn view_window<'a>(
|
||||
.style(button::secondary)
|
||||
.padding([3, 0]);
|
||||
|
||||
let path_row = row![file_input, browse_btn].spacing(5).align_y(iced::Center);
|
||||
let path_row = row![file_input, browse_btn]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
|
||||
let unit_options: Vec<UnitChoice> = units::all()
|
||||
.map(|(code, label)| UnitChoice { code, label })
|
||||
|
||||
@@ -152,7 +152,10 @@ fn number_field<'a>(
|
||||
field = field.on_input(on_input);
|
||||
}
|
||||
row![
|
||||
text(label).size(11).style(muted_style).width(Length::Fixed(16.0)),
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(16.0)),
|
||||
field
|
||||
]
|
||||
.spacing(6)
|
||||
@@ -236,18 +239,12 @@ pub fn view_window<'a>(
|
||||
let reference_type = group(
|
||||
t!("Reference Type").into_owned(),
|
||||
column![
|
||||
form_radio(
|
||||
t!("Attachment"),
|
||||
false,
|
||||
Some(state.overlay),
|
||||
|v| msg(XrefAttachMsg::Overlay(v)),
|
||||
),
|
||||
form_radio(
|
||||
t!("Overlay"),
|
||||
true,
|
||||
Some(state.overlay),
|
||||
|v| msg(XrefAttachMsg::Overlay(v)),
|
||||
),
|
||||
form_radio(t!("Attachment"), false, Some(state.overlay), |v| msg(
|
||||
XrefAttachMsg::Overlay(v)
|
||||
),),
|
||||
form_radio(t!("Overlay"), true, Some(state.overlay), |v| msg(
|
||||
XrefAttachMsg::Overlay(v)
|
||||
),),
|
||||
]
|
||||
.spacing(6),
|
||||
Length::Shrink,
|
||||
@@ -313,8 +310,10 @@ pub fn view_window<'a>(
|
||||
// ── Path type, rotation, block unit ─────────────────────────────────
|
||||
let path_type = group(
|
||||
t!("Path type").into_owned(),
|
||||
pick_list(Some(state.path_type), PathTypeChoice::ALL, |choice| choice.to_string())
|
||||
.on_select(|v| msg(XrefAttachMsg::PathType(v)))
|
||||
pick_list(Some(state.path_type), PathTypeChoice::ALL, |choice| {
|
||||
choice.to_string()
|
||||
})
|
||||
.on_select(|v| msg(XrefAttachMsg::PathType(v)))
|
||||
.text_size(11)
|
||||
.padding([3, 6])
|
||||
.width(Fill),
|
||||
|
||||
@@ -18,9 +18,8 @@ pub fn view_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Me
|
||||
// Fixed window width: paragraphs wrap against this instead of the
|
||||
// toolbar, which is what made the dropdown unreadable.
|
||||
let body_w = Length::Fixed(460.0);
|
||||
let para = |label: String| -> Element<'static, Message> {
|
||||
text(label).size(12).width(Fill).into()
|
||||
};
|
||||
let para =
|
||||
|label: String| -> Element<'static, Message> { text(label).size(12).width(Fill).into() };
|
||||
|
||||
let intro = para(crate::t!("How the Reference Manager works on web").into_owned());
|
||||
let list = para(
|
||||
@@ -48,18 +47,14 @@ pub fn view_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Me
|
||||
true,
|
||||
);
|
||||
let close_button = dialog_button(crate::t!("Close"), Message::CloseModal, false);
|
||||
let action_row: Element<'static, Message> = row![
|
||||
Space::new().width(Fill),
|
||||
report_button,
|
||||
close_button,
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into();
|
||||
let action_row: Element<'static, Message> =
|
||||
row![Space::new().width(Fill), report_button, close_button,]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into();
|
||||
|
||||
let content = column![
|
||||
text(crate::t!("Reference Manager — Web").into_owned())
|
||||
.size(14),
|
||||
text(crate::t!("Reference Manager — Web").into_owned()).size(14),
|
||||
text(crate::t!("EXTERNALREFERENCES on the web build").into_owned())
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
@@ -78,9 +73,7 @@ pub fn view_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Me
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
||||
+197
-179
@@ -7,10 +7,12 @@
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::io::xref::collect_entries_with_prev;
|
||||
use crate::io::xref_model::{normalize_lexical, Pathtype, RefKind, RefStatus, RefType, ReferenceEntry};
|
||||
use crate::ui::ROW_H;
|
||||
use crate::io::xref_model::{
|
||||
normalize_lexical, Pathtype, RefKind, RefStatus, RefType, ReferenceEntry,
|
||||
};
|
||||
use crate::ui::style::common::muted_style;
|
||||
use crate::ui::style::row_button::cell_button_style;
|
||||
use crate::ui::ROW_H;
|
||||
use codec::CadDocument;
|
||||
use iced::widget::{button, column, container, mouse_area, row, scrollable, text, tooltip};
|
||||
use iced::Padding;
|
||||
@@ -24,8 +26,6 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
/// read-only list renders everywhere.
|
||||
const IS_WASM: bool = cfg!(target_arch = "wasm32");
|
||||
|
||||
|
||||
|
||||
/// Font size for table cells (mirrors `layers.rs`).
|
||||
const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px at ROW_H=26
|
||||
/// Fixed table height so the details pane below keeps stable space.
|
||||
@@ -380,7 +380,11 @@ impl XrefManagerPanel {
|
||||
// re-ranges from the original anchor (plain click moves it).
|
||||
// Moving it every time collapsed the range and dropped rows.
|
||||
let from = self.anchor.unwrap_or(index);
|
||||
let (lo, hi) = if from <= index { (from, index) } else { (index, from) };
|
||||
let (lo, hi) = if from <= index {
|
||||
(from, index)
|
||||
} else {
|
||||
(index, from)
|
||||
};
|
||||
self.selected.clear();
|
||||
self.selected
|
||||
.extend((lo..=hi).filter(|i| *i < self.entries.len()));
|
||||
@@ -444,8 +448,10 @@ impl XrefManagerPanel {
|
||||
}
|
||||
if let Some(img) = reference_preview(a) {
|
||||
let (w, h) = (img.width(), img.height());
|
||||
self.previews
|
||||
.insert(key, iced::widget::image::Handle::from_rgba(w, h, img.into_raw()));
|
||||
self.previews.insert(
|
||||
key,
|
||||
iced::widget::image::Handle::from_rgba(w, h, img.into_raw()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,13 +514,16 @@ impl XrefManagerPanel {
|
||||
};
|
||||
if !self.tree {
|
||||
let mut rows = vec![host];
|
||||
rows.extend(self.entries.iter().enumerate().map(|(index, _)| {
|
||||
DisplayRow {
|
||||
index,
|
||||
depth: 0,
|
||||
is_nested: self.nested.contains(&index),
|
||||
}
|
||||
}));
|
||||
rows.extend(
|
||||
self.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| DisplayRow {
|
||||
index,
|
||||
depth: 0,
|
||||
is_nested: self.nested.contains(&index),
|
||||
}),
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
fn append_tree(
|
||||
@@ -524,7 +533,9 @@ impl XrefManagerPanel {
|
||||
seen: &mut HashSet<(String, String)>,
|
||||
rows: &mut Vec<DisplayRow>,
|
||||
) {
|
||||
let Some(entry) = panel.entries.get(index) else { return; };
|
||||
let Some(entry) = panel.entries.get(index) else {
|
||||
return;
|
||||
};
|
||||
if !claim_path(seen, &entry.saved_path, &entry.name) {
|
||||
return;
|
||||
}
|
||||
@@ -547,10 +558,7 @@ impl XrefManagerPanel {
|
||||
let mut seen: HashSet<(String, String)> = HashSet::new();
|
||||
// Roots first, then recursively expanded descendants.
|
||||
for (index, _) in self.entries.iter().enumerate() {
|
||||
let dominated = self
|
||||
.children
|
||||
.values()
|
||||
.any(|kids| kids.contains(&index));
|
||||
let dominated = self.children.values().any(|kids| kids.contains(&index));
|
||||
if self.nested.contains(&index) && dominated {
|
||||
continue;
|
||||
}
|
||||
@@ -589,7 +597,11 @@ impl XrefManagerPanel {
|
||||
use crate::ui::dock::PanelId;
|
||||
let title_bar = crate::ui::dock::title_bar(
|
||||
PanelId::ExternalReferences,
|
||||
format!("{} ({})", crate::t!("External References").as_ref(), self.entries.len()),
|
||||
format!(
|
||||
"{} ({})",
|
||||
crate::t!("External References").as_ref(),
|
||||
self.entries.len()
|
||||
),
|
||||
auto_collapse,
|
||||
);
|
||||
// Table content width: column widths plus gutters, stretched to the
|
||||
@@ -600,7 +612,10 @@ impl XrefManagerPanel {
|
||||
// The main button runs the default; the triangle opens the rest in an
|
||||
// overlay menu. Formats without an attach command in this build
|
||||
// (DWF/DGN/point clouds/coordination models) are omitted, not dead.
|
||||
let web_tip = crate::t!("File attach is not available on web — the reference list below is read-only.").into_owned();
|
||||
let web_tip = crate::t!(
|
||||
"File attach is not available on web — the reference list below is read-only."
|
||||
)
|
||||
.into_owned();
|
||||
let attach = if IS_WASM {
|
||||
toolbar_tip(crate::t!("Attach DWG").into_owned(), web_tip.clone(), false)
|
||||
} else {
|
||||
@@ -703,13 +718,14 @@ impl XrefManagerPanel {
|
||||
.enumerate()
|
||||
.any(|(i, _)| self.selected.contains(&i) && !self.nested.contains(&i));
|
||||
let single_direct_anchor = self.anchor.is_some_and(|a| {
|
||||
self.selected.len() == 1
|
||||
&& self.entries.get(a).is_some()
|
||||
&& !self.nested.contains(&a)
|
||||
self.selected.len() == 1 && self.entries.get(a).is_some() && !self.nested.contains(&a)
|
||||
});
|
||||
let host_saved = !self.host_path.is_empty();
|
||||
let web_readonly: Option<String> = IS_WASM.then(|| {
|
||||
crate::t!("Reference changes are not available on web — the reference list is read-only.").into_owned()
|
||||
crate::t!(
|
||||
"Reference changes are not available on web — the reference list is read-only."
|
||||
)
|
||||
.into_owned()
|
||||
});
|
||||
let path_group_gate: Option<String> = web_readonly.clone().or_else(|| {
|
||||
if has_direct {
|
||||
@@ -789,19 +805,12 @@ impl XrefManagerPanel {
|
||||
false,
|
||||
);
|
||||
let toolbar = container(
|
||||
row![
|
||||
attach,
|
||||
refresh,
|
||||
change_path,
|
||||
help
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
row![attach, refresh, change_path, help]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weakest.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weakest.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -860,20 +869,20 @@ impl XrefManagerPanel {
|
||||
}
|
||||
let col_header: Element<'_, Message> = mouse_area(
|
||||
container(header_row.width(Length::Fixed(table_w)))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
.width(Fill),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
.width(Fill),
|
||||
)
|
||||
.on_move(|p| Message::XrefColMove(p))
|
||||
.on_release(Message::XrefColRelease)
|
||||
@@ -951,7 +960,9 @@ impl XrefManagerPanel {
|
||||
self.row_change_path_open,
|
||||
));
|
||||
}
|
||||
scrollable(tree_col).height(Length::Fixed(self.table_h)).into()
|
||||
scrollable(tree_col)
|
||||
.height(Length::Fixed(self.table_h))
|
||||
.into()
|
||||
} else {
|
||||
scrollable(
|
||||
column![
|
||||
@@ -971,9 +982,7 @@ impl XrefManagerPanel {
|
||||
let table_rows: Element<'_, Message> = container(table_rows)
|
||||
.padding(4)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weakest.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weakest.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
@@ -1029,13 +1038,13 @@ impl XrefManagerPanel {
|
||||
});
|
||||
let preview_body: Element<'_, Message> = match single {
|
||||
Some(e) => {
|
||||
let cached = e.found_at.as_deref().and_then(|found| {
|
||||
self.previews.get(&(e.key, found.to_string()))
|
||||
});
|
||||
let cached = e
|
||||
.found_at
|
||||
.as_deref()
|
||||
.and_then(|found| self.previews.get(&(e.key, found.to_string())));
|
||||
match cached {
|
||||
Some(handle) => container(
|
||||
iced::widget::image(handle.clone())
|
||||
.width(Length::Fixed(220.0)),
|
||||
iced::widget::image(handle.clone()).width(Length::Fixed(220.0)),
|
||||
)
|
||||
.center_x(Fill)
|
||||
.center_y(Fill)
|
||||
@@ -1045,7 +1054,9 @@ impl XrefManagerPanel {
|
||||
None => container(
|
||||
column![
|
||||
text(e.name.as_str()).size(11),
|
||||
text(crate::t!("Preview not available")).size(11).style(muted_style),
|
||||
text(crate::t!("Preview not available"))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
@@ -1063,9 +1074,7 @@ impl XrefManagerPanel {
|
||||
.width(Fill)
|
||||
.height(Length::Fixed(120.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weak.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into(),
|
||||
@@ -1075,8 +1084,7 @@ impl XrefManagerPanel {
|
||||
.width(Fill)
|
||||
.into()
|
||||
} else {
|
||||
let details =
|
||||
details_pane(self.anchor.and_then(|i| self.entries.get(i)), doc);
|
||||
let details = details_pane(self.anchor.and_then(|i| self.entries.get(i)), doc);
|
||||
container(column![pane_tabs, details].spacing(2))
|
||||
.padding([6, 8])
|
||||
.width(Fill)
|
||||
@@ -1108,9 +1116,7 @@ impl XrefManagerPanel {
|
||||
// setting — and resize — actually takes effect.
|
||||
container(content)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fixed(width))
|
||||
@@ -1240,7 +1246,11 @@ fn type_text(entry: &ReferenceEntry) -> std::borrow::Cow<'static, str> {
|
||||
.extension()
|
||||
.map(|e| e.to_string_lossy().to_ascii_uppercase())
|
||||
.unwrap_or_default();
|
||||
if ext == "RCS" { "RCS".into() } else { "RCP".into() }
|
||||
if ext == "RCS" {
|
||||
"RCS".into()
|
||||
} else {
|
||||
"RCP".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1300,9 +1310,7 @@ fn toolbar_btn(label: String, msg: Option<Message>, fill: bool) -> Element<'stat
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => {
|
||||
palette.background.strong
|
||||
}
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.strong,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
@@ -1329,18 +1337,13 @@ fn toolbar_btn(label: String, msg: Option<Message>, fill: bool) -> Element<'stat
|
||||
/// Disabled toolbar affordance with an explanatory tooltip (gated
|
||||
/// operations, nested-selection blocks, or web-gated mutations).
|
||||
fn toolbar_tip(label: String, tip: String, fill: bool) -> Element<'static, Message> {
|
||||
let b = button(text(label).size(11).style(|theme: &Theme| {
|
||||
iced::widget::text::Style {
|
||||
color: Some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.42),
|
||||
),
|
||||
}
|
||||
}))
|
||||
let b = button(
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().background.base.text.scale_alpha(0.42)),
|
||||
}),
|
||||
)
|
||||
.style(|theme: &Theme, _| {
|
||||
let palette = theme.palette();
|
||||
button::Style {
|
||||
@@ -1385,9 +1388,7 @@ fn split_button(
|
||||
move |theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => {
|
||||
palette.background.strong
|
||||
}
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.strong,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
let radius = if left {
|
||||
@@ -1435,16 +1436,16 @@ fn split_button(
|
||||
// where they meet.
|
||||
let head = row![main, caret].spacing(-1.0).align_y(iced::Center);
|
||||
let popup: Element<'static, Message> = container(
|
||||
column(items.into_iter().map(|item| {
|
||||
container(item).width(Fill).into()
|
||||
}))
|
||||
column(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|item| container(item).width(Fill).into()),
|
||||
)
|
||||
.spacing(2)
|
||||
.padding(4),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
@@ -1463,7 +1464,11 @@ fn split_button(
|
||||
|
||||
/// One dropdown-menu row: a full-width button when the option can execute,
|
||||
/// otherwise greyed text carrying the reason.
|
||||
fn menu_item(label: String, msg: Option<Message>, gate: Option<String>) -> Element<'static, Message> {
|
||||
fn menu_item(
|
||||
label: String,
|
||||
msg: Option<Message>,
|
||||
gate: Option<String>,
|
||||
) -> Element<'static, Message> {
|
||||
match (msg, gate) {
|
||||
(Some(m), None) => toolbar_btn(label, Some(m), true),
|
||||
(_, reason) => toolbar_tip(label, reason.unwrap_or_default(), true),
|
||||
@@ -1543,9 +1548,13 @@ impl<'a> iced_core::Widget<Message, Theme, iced::Renderer> for RightClickArea<'a
|
||||
viewport: &iced::Rectangle,
|
||||
renderer: &iced::Renderer,
|
||||
) -> iced_core::mouse::Interaction {
|
||||
self.child
|
||||
.as_widget()
|
||||
.mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
|
||||
self.child.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
@@ -1680,7 +1689,9 @@ fn row_menu_for(id: XrefRowId, change_path_open: bool) -> Element<'static, Messa
|
||||
let change_path_row: Element<'static, Message> = mouse_area(
|
||||
container(
|
||||
row![
|
||||
text(crate::t!("Change Path Type").into_owned()).size(12).width(Fill),
|
||||
text(crate::t!("Change Path Type").into_owned())
|
||||
.size(12)
|
||||
.width(Fill),
|
||||
crate::ui::icons::themed_arrow_right(10.0),
|
||||
]
|
||||
.align_y(iced::Center)
|
||||
@@ -1732,9 +1743,7 @@ fn row_menu_for(id: XrefRowId, change_path_open: bool) -> Element<'static, Messa
|
||||
.padding(MENU_PADDING),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
@@ -1751,7 +1760,8 @@ fn row_menu_for(id: XrefRowId, change_path_open: bool) -> Element<'static, Messa
|
||||
|
||||
// Top offset so the flyout top border aligns directly with the "Change Path Type" row.
|
||||
// 5 items * 24.0 + 5 * 1.0 (spacing) + 5.0 (separator) + 1.0 (spacing) + 4.0 (container padding) = 135.0
|
||||
let flyout_offset = MENU_PADDING + 5.0 * MENU_ROW_H + 5.0 * MENU_SPACING + MENU_SEP_H + MENU_SPACING;
|
||||
let flyout_offset =
|
||||
MENU_PADDING + 5.0 * MENU_ROW_H + 5.0 * MENU_SPACING + MENU_SEP_H + MENU_SPACING;
|
||||
|
||||
// Plain container on purpose: each submenu row tracks hover itself
|
||||
// (`on_enter` keeps the flyout open, `on_exit` closes it). Wrapping the
|
||||
@@ -1759,23 +1769,15 @@ fn row_menu_for(id: XrefRowId, change_path_open: bool) -> Element<'static, Messa
|
||||
// break click delivery inside the `ContextMenu` overlay — see `menu_row`.
|
||||
let flyout: Element<'static, Message> = container(
|
||||
column![
|
||||
pathtype_item(
|
||||
&crate::t!("Make Absolute").into_owned(),
|
||||
Pathtype::Full
|
||||
),
|
||||
pathtype_item(
|
||||
&crate::t!("Make Relative").into_owned(),
|
||||
Pathtype::Relative
|
||||
),
|
||||
pathtype_item(&crate::t!("Make Absolute").into_owned(), Pathtype::Full),
|
||||
pathtype_item(&crate::t!("Make Relative").into_owned(), Pathtype::Relative),
|
||||
pathtype_item(&crate::t!("Remove Path").into_owned(), Pathtype::None),
|
||||
]
|
||||
.spacing(MENU_SPACING)
|
||||
.padding(MENU_PADDING),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
@@ -1813,9 +1815,8 @@ fn xref_row<'a>(
|
||||
}
|
||||
let mut name_cell = row![].spacing(2).align_y(iced::Center);
|
||||
if display.depth > 0 {
|
||||
name_cell = name_cell.push(
|
||||
iced::widget::Space::new().width(Length::Fixed(INDENT_W * display.depth as f32)),
|
||||
);
|
||||
name_cell = name_cell
|
||||
.push(iced::widget::Space::new().width(Length::Fixed(INDENT_W * display.depth as f32)));
|
||||
}
|
||||
// Tree parents own the only interactive cell besides selection: the
|
||||
// expand/collapse arrow. Nested rows never show one.
|
||||
@@ -1848,13 +1849,21 @@ fn xref_row<'a>(
|
||||
gutter(),
|
||||
status_cell,
|
||||
gutter(),
|
||||
text(format_size(entry.size_bytes)).size(FONT_SZ).width(Length::Fixed(cw[2])),
|
||||
text(format_size(entry.size_bytes))
|
||||
.size(FONT_SZ)
|
||||
.width(Length::Fixed(cw[2])),
|
||||
gutter(),
|
||||
text(type_text(entry)).size(FONT_SZ).width(Length::Fixed(cw[3])),
|
||||
text(type_text(entry))
|
||||
.size(FONT_SZ)
|
||||
.width(Length::Fixed(cw[3])),
|
||||
gutter(),
|
||||
text(format_date(entry.modified)).size(FONT_SZ).width(Length::Fixed(cw[4])),
|
||||
text(format_date(entry.modified))
|
||||
.size(FONT_SZ)
|
||||
.width(Length::Fixed(cw[4])),
|
||||
gutter(),
|
||||
text(entry.saved_path.clone()).size(FONT_SZ).width(Length::Fixed(cw[5])),
|
||||
text(entry.saved_path.clone())
|
||||
.size(FONT_SZ)
|
||||
.width(Length::Fixed(cw[5])),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(Length::Fixed(table_w))
|
||||
@@ -1923,8 +1932,11 @@ fn tree_row(
|
||||
cells = cells.push(arrow);
|
||||
}
|
||||
cells = cells.push(
|
||||
container(crate::ui::icons::themed_secondary(crate::ui::icons::DOC, 14.0))
|
||||
.align_y(iced::Center),
|
||||
container(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::DOC,
|
||||
14.0,
|
||||
))
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
cells = cells.push(text(entry.name.clone()).size(TREE_FONT_SZ));
|
||||
let index = display.index;
|
||||
@@ -1974,9 +1986,7 @@ fn tree_host_row(display: DisplayRow, host_name: &str) -> Element<'_, Message> {
|
||||
name.push('*');
|
||||
let mut cells = row![].spacing(2).align_y(iced::Center);
|
||||
cells = cells.push(tree_indent(display.depth));
|
||||
cells = cells.push(
|
||||
container(crate::ui::icons::themed_home(14.0)).align_y(iced::Center),
|
||||
);
|
||||
cells = cells.push(container(crate::ui::icons::themed_home(14.0)).align_y(iced::Center));
|
||||
cells = cells.push(text(name).size(TREE_FONT_SZ));
|
||||
container(cells.spacing(4).width(Fill))
|
||||
.style(|theme: &Theme| {
|
||||
@@ -1996,18 +2006,27 @@ fn tree_host_row(display: DisplayRow, host_name: &str) -> Element<'_, Message> {
|
||||
.width(Fill)
|
||||
.into()
|
||||
}
|
||||
fn host_row<'a>(display: DisplayRow, host_name: &'a str, host_path: &'a str, table_w: f32, cw: &[f32; 6]) -> Element<'a, Message> {
|
||||
fn host_row<'a>(
|
||||
display: DisplayRow,
|
||||
host_name: &'a str,
|
||||
host_path: &'a str,
|
||||
table_w: f32,
|
||||
cw: &[f32; 6],
|
||||
) -> Element<'a, Message> {
|
||||
let name = if host_name.is_empty() {
|
||||
crate::t!("Untitled").into_owned()
|
||||
} else {
|
||||
host_name.to_string()
|
||||
};
|
||||
let saved = if host_path.is_empty() { "—" } else { host_path };
|
||||
let saved = if host_path.is_empty() {
|
||||
"—"
|
||||
} else {
|
||||
host_path
|
||||
};
|
||||
let mut name_cell = row![].spacing(2).align_y(iced::Center);
|
||||
if display.depth > 0 {
|
||||
name_cell = name_cell.push(
|
||||
iced::widget::Space::new().width(Length::Fixed(INDENT_W * display.depth as f32)),
|
||||
);
|
||||
name_cell = name_cell
|
||||
.push(iced::widget::Space::new().width(Length::Fixed(INDENT_W * display.depth as f32)));
|
||||
}
|
||||
name_cell = name_cell.push({
|
||||
let name_text: Element<'_, Message> = text(name).size(FONT_SZ).into();
|
||||
@@ -2017,11 +2036,15 @@ fn host_row<'a>(display: DisplayRow, host_name: &'a str, host_path: &'a str, tab
|
||||
let content = row![
|
||||
name_cell.width(Length::Fixed(cw[0])),
|
||||
gutter(),
|
||||
text(crate::t!("Opened")).size(FONT_SZ).width(Length::Fixed(cw[1])),
|
||||
text(crate::t!("Opened"))
|
||||
.size(FONT_SZ)
|
||||
.width(Length::Fixed(cw[1])),
|
||||
gutter(),
|
||||
text("—").size(FONT_SZ).width(Length::Fixed(cw[2])),
|
||||
gutter(),
|
||||
text(crate::t!("Current")).size(FONT_SZ).width(Length::Fixed(cw[3])),
|
||||
text(crate::t!("Current"))
|
||||
.size(FONT_SZ)
|
||||
.width(Length::Fixed(cw[3])),
|
||||
gutter(),
|
||||
text("—").size(FONT_SZ).width(Length::Fixed(cw[4])),
|
||||
gutter(),
|
||||
@@ -2050,7 +2073,10 @@ fn host_row<'a>(display: DisplayRow, host_name: &'a str, host_path: &'a str, tab
|
||||
.into()
|
||||
}
|
||||
|
||||
fn details_pane<'a>(entry: Option<&'a ReferenceEntry>, doc: &'a CadDocument) -> Element<'a, Message> {
|
||||
fn details_pane<'a>(
|
||||
entry: Option<&'a ReferenceEntry>,
|
||||
doc: &'a CadDocument,
|
||||
) -> Element<'a, Message> {
|
||||
let inner: Element<'_, Message> = match entry {
|
||||
None => text(crate::t!("Select a reference to inspect its details"))
|
||||
.size(11)
|
||||
@@ -2096,14 +2122,8 @@ fn details_pane<'a>(entry: Option<&'a ReferenceEntry>, doc: &'a CadDocument) ->
|
||||
}
|
||||
if let Some((w, h)) = find_image_display_size(doc, e.key) {
|
||||
rows = rows
|
||||
.push(detail_row(
|
||||
crate::t!("Display Width"),
|
||||
format!("{:.2}", w),
|
||||
))
|
||||
.push(detail_row(
|
||||
crate::t!("Display Height"),
|
||||
format!("{:.2}", h),
|
||||
));
|
||||
.push(detail_row(crate::t!("Display Width"), format!("{:.2}", w)))
|
||||
.push(detail_row(crate::t!("Display Height"), format!("{:.2}", h)));
|
||||
}
|
||||
}
|
||||
rows.into()
|
||||
@@ -2169,14 +2189,13 @@ fn reference_preview(entry: &ReferenceEntry) -> Option<image::RgbaImage> {
|
||||
|
||||
/// Image definition backing a [`RefKind::Image`] entry, looked up by the
|
||||
/// entry key (definition-object handle) for the Details pane extras.
|
||||
fn find_image_def(
|
||||
doc: &CadDocument,
|
||||
key: u64,
|
||||
) -> Option<&codec::objects::ImageDefinition> {
|
||||
doc.objects.get(&codec::types::Handle::from(key)).and_then(|o| match o {
|
||||
codec::objects::ObjectType::ImageDefinition(def) => Some(def),
|
||||
_ => None,
|
||||
})
|
||||
fn find_image_def(doc: &CadDocument, key: u64) -> Option<&codec::objects::ImageDefinition> {
|
||||
doc.objects
|
||||
.get(&codec::types::Handle::from(key))
|
||||
.and_then(|o| match o {
|
||||
codec::objects::ObjectType::ImageDefinition(def) => Some(def),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_image_display_size(doc: &CadDocument, key: u64) -> Option<(f64, f64)> {
|
||||
@@ -2184,8 +2203,10 @@ fn find_image_display_size(doc: &CadDocument, key: u64) -> Option<(f64, f64)> {
|
||||
for entity in doc.entities() {
|
||||
if let codec::EntityType::RasterImage(img) = entity {
|
||||
if img.definition_handle == Some(handle) {
|
||||
let w = (img.u_vector.x.powi(2) + img.u_vector.y.powi(2) + img.u_vector.z.powi(2)).sqrt();
|
||||
let h = (img.v_vector.x.powi(2) + img.v_vector.y.powi(2) + img.v_vector.z.powi(2)).sqrt();
|
||||
let w = (img.u_vector.x.powi(2) + img.u_vector.y.powi(2) + img.u_vector.z.powi(2))
|
||||
.sqrt();
|
||||
let h = (img.v_vector.x.powi(2) + img.v_vector.y.powi(2) + img.v_vector.z.powi(2))
|
||||
.sqrt();
|
||||
return Some((w, h));
|
||||
}
|
||||
}
|
||||
@@ -2200,7 +2221,10 @@ fn detail_row<'a>(
|
||||
let label: std::borrow::Cow<'a, str> = label.into();
|
||||
let value: std::borrow::Cow<'a, str> = value.into();
|
||||
row![
|
||||
text(label).size(11).style(muted_style).width(Length::Fixed(84.0)),
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(84.0)),
|
||||
text(value).size(11),
|
||||
]
|
||||
.spacing(6)
|
||||
@@ -2226,7 +2250,9 @@ mod tests {
|
||||
fn date_formats_known_days() {
|
||||
assert_eq!(format_date(None), "—");
|
||||
let day = |days: u64| {
|
||||
format_date(Some(UNIX_EPOCH + std::time::Duration::from_secs(days * 86_400)))
|
||||
format_date(Some(
|
||||
UNIX_EPOCH + std::time::Duration::from_secs(days * 86_400),
|
||||
))
|
||||
};
|
||||
assert_eq!(day(0), "1970-01-01");
|
||||
assert_eq!(day(10957), "2000-01-01");
|
||||
@@ -2283,11 +2309,7 @@ mod tests {
|
||||
panel.entries.push(entry(5, "C", "c.dwg"));
|
||||
panel.children.insert(5, vec![2]);
|
||||
panel.expanded.insert(5);
|
||||
let count = panel
|
||||
.display_rows()
|
||||
.iter()
|
||||
.filter(|r| r.index == 2)
|
||||
.count();
|
||||
let count = panel.display_rows().iter().filter(|r| r.index == 2).count();
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
|
||||
@@ -2346,7 +2368,10 @@ mod tests {
|
||||
panel.drag_col_by(2, 10000.0);
|
||||
assert_eq!(panel.col_widths[2], super::COL_MAX_W);
|
||||
panel.drag_col_by(99, 10.0); // no-op, no panic
|
||||
assert_eq!(panel.table_content_width(), panel.col_widths.iter().sum::<f32>() + super::COL_GUTTER * 5.0);
|
||||
assert_eq!(
|
||||
panel.table_content_width(),
|
||||
panel.col_widths.iter().sum::<f32>() + super::COL_GUTTER * 5.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2476,12 +2501,8 @@ mod tests {
|
||||
let mut def = ImageDefinition::with_dimensions(img_path.clone(), 8, 8);
|
||||
def.handle = h;
|
||||
doc.objects.insert(h, ObjectType::ImageDefinition(def));
|
||||
let mut ent = codec::entities::RasterImage::new(
|
||||
&img_path,
|
||||
codec::types::Vector3::ZERO,
|
||||
8.0,
|
||||
8.0,
|
||||
);
|
||||
let mut ent =
|
||||
codec::entities::RasterImage::new(&img_path, codec::types::Vector3::ZERO, 8.0, 8.0);
|
||||
ent.definition_handle = Some(h);
|
||||
doc.add_entity(codec::EntityType::RasterImage(ent)).unwrap();
|
||||
doc
|
||||
@@ -2623,15 +2644,11 @@ mod tests {
|
||||
/// user right-clicking a reference row.
|
||||
fn open_row_menu(change_path_open: bool) -> MenuSimulator {
|
||||
let row_id = XrefRowId::of(0, "");
|
||||
let mut ui = iced_test::simulator(iced_aw::ContextMenu::new(
|
||||
text("anchor"),
|
||||
move || row_menu_for(row_id.clone(), change_path_open),
|
||||
));
|
||||
let mut ui = iced_test::simulator(iced_aw::ContextMenu::new(text("anchor"), move || {
|
||||
row_menu_for(row_id.clone(), change_path_open)
|
||||
}));
|
||||
let anchor = ui.find("anchor").expect("anchor underlay present");
|
||||
let center = anchor
|
||||
.visible_bounds()
|
||||
.expect("anchor visible")
|
||||
.center();
|
||||
let center = anchor.visible_bounds().expect("anchor visible").center();
|
||||
ui.point_at(center);
|
||||
ui.simulate([iced_core::Event::Mouse(iced::mouse::Event::ButtonPressed(
|
||||
iced::mouse::Button::Right,
|
||||
@@ -2669,10 +2686,7 @@ mod tests {
|
||||
let target = ui
|
||||
.find(label.as_str())
|
||||
.expect("Change Path Type row present in overlay");
|
||||
let center = target
|
||||
.visible_bounds()
|
||||
.expect("row visible")
|
||||
.center();
|
||||
let center = target.visible_bounds().expect("row visible").center();
|
||||
ui.point_at(center);
|
||||
ui.simulate([iced_core::Event::Mouse(iced::mouse::Event::CursorMoved {
|
||||
position: center,
|
||||
@@ -2696,7 +2710,10 @@ mod tests {
|
||||
ui.click(make_absolute.as_str())
|
||||
.expect("flyout row is clickable inside the overlay");
|
||||
assert!(
|
||||
published_ops(ui).contains(&(XrefRowId::of(0, ""), XrefPaletteOp::Pathtype(Pathtype::Full))),
|
||||
published_ops(ui).contains(&(
|
||||
XrefRowId::of(0, ""),
|
||||
XrefPaletteOp::Pathtype(Pathtype::Full)
|
||||
)),
|
||||
"clicking Make Absolute must publish the Full-pathtype op"
|
||||
);
|
||||
}
|
||||
@@ -2724,7 +2741,8 @@ mod tests {
|
||||
// buttons again.
|
||||
use iced_core::Widget;
|
||||
|
||||
let mut menu = iced_aw::ContextMenu::new(text("anchor"), || row_menu_for(XrefRowId::of(0, ""), false));
|
||||
let mut menu =
|
||||
iced_aw::ContextMenu::new(text("anchor"), || row_menu_for(XrefRowId::of(0, ""), false));
|
||||
let mut tree = iced_core::widget::Tree::new(
|
||||
&menu as &dyn Widget<Message, iced::Theme, iced::Renderer>,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user