fix(ui): make modal layouts intrinsic

Pin Iced 0.15 so dialogs can measure content before distributing the resolved frame across child panels. Keep the web plugin notice compact and non-resizable.\n\nCloses #582
This commit is contained in:
Hakan Seven committed 2026-07-30 14:47:28 +03:00
1 parent 284d6ef517
commit 3ab8dc3ca5
59 files changed
+2197 -1652

No files matched your search

+4 -4
View File
@@ -7,7 +7,7 @@ fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
text(label)
.size(11)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
})
.width(100),
text(value).size(11),
@@ -28,12 +28,12 @@ pub fn view_window<'a>() -> Element<'a, Message> {
text("Open CAD Studio")
.size(32)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.extended_palette().primary.base.color),
color: Some(theme.palette().primary.base.color),
}),
text("CAD application for Architecture & Engineering")
.size(11)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}),
]
.spacing(4)
@@ -84,7 +84,7 @@ pub fn view_window<'a>() -> Element<'a, Message> {
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color,
theme.palette().background.base.color,
)),
..Default::default()
})
+15 -12
View File
@@ -6,7 +6,7 @@
use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Background, Element, Fill, Length, Theme};
use iced::{Background, Element, Length, Theme};
/// Which column of an alias row a text edit targets.
#[derive(Clone, Copy, Debug)]
@@ -20,12 +20,15 @@ const GUTTER: f32 = 16.0;
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
/// Build the alias editor content. `rows` is the live working buffer.
pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
pub fn view_window(
rows: &[(String, String)],
sizing: crate::ui::modal::ModalSizing,
) -> Element<'_, Message> {
let title = text("Command Aliases").size(15);
let hint = text(
"Type an alias and the command it runs (e.g. L → LINE). \
@@ -42,7 +45,7 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
let head = container(
row![
container(text("Alias").size(11).style(muted_style)).width(Length::Fixed(120.0)),
container(text("Command").size(11).style(muted_style)).width(Fill),
container(text("Command").size(11).style(muted_style)).width(sizing.width),
Space::new().width(Length::Fixed(30.0)),
]
.spacing(8),
@@ -60,7 +63,7 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Command, value: v })
.size(13)
.padding([3, 6])
.width(Fill);
.width(sizing.width);
let del = button(crate::ui::icons::themed_danger(crate::ui::icons::CLOSE, 12.0))
.on_press(Message::AliasEditorRemove(idx))
.padding([2, 6])
@@ -89,20 +92,20 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
hint,
Space::new().height(6),
head,
scrollable(container(list).padding(gutter)).height(Fill),
scrollable(container(list).padding(gutter)).height(sizing.height),
Space::new().height(6),
row![add, Space::new().width(Fill), apply].align_y(iced::Center),
row![add, Space::new().width(sizing.width), apply].align_y(iced::Center),
]
.spacing(6)
.width(Fill)
.height(Fill),
.width(sizing.width)
.height(sizing.height),
)
.padding(12)
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color,
theme.palette().background.base.color,
)),
..Default::default()
})
+85 -47
View File
@@ -17,7 +17,7 @@ use crate::app::Message;
use acadrust::entities::{HorizontalAlignment, VerticalAlignment};
use acadrust::types::{Color as AcadColor, LineWeight};
use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
button, checkbox, column, container, row, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Element, Length, Theme};
@@ -135,12 +135,12 @@ pub fn color_from_label(label: &str) -> Option<AcadColor> {
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
let palette = theme.extended_palette();
let palette = theme.palette();
let border = match status {
text_input::Status::Focused { .. } => palette.primary.base.color,
_ => palette.background.neutral.color,
@@ -156,13 +156,13 @@ fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
}
/// Horizontal 1px divider in the shared border colour (matches style windows).
fn hdivider<'a>() -> Element<'a, Message> {
container(Space::new().width(Length::Fill).height(1))
.width(Length::Fill)
fn hdivider<'a>(width: Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color
theme.palette().background.neutral.color
)),
..Default::default()
})
@@ -170,13 +170,18 @@ fn hdivider<'a>() -> Element<'a, Message> {
}
/// One `label : widget` row with a fixed-width label column.
fn field_row<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
fn field_row<'a>(
label: &'a str,
widget: Element<'a, Message>,
width: Length,
) -> Element<'a, Message> {
row![
container(text(label).size(12).style(muted_style)).width(LABEL_W),
widget,
]
.spacing(8)
.align_y(iced::Center)
.width(width)
.into()
}
@@ -185,6 +190,7 @@ fn edit_field<'a>(
label: &'a str,
value: &'a str,
on_input: impl Fn(String) -> Message + 'a,
width: Length,
) -> Element<'a, Message> {
let ti = text_input("", value)
.on_input(on_input)
@@ -192,8 +198,8 @@ fn edit_field<'a>(
.style(field_style)
.size(13)
.padding([3, 6])
.width(Length::Fill);
field_row(label, ti.into())
.width(width);
field_row(label, ti.into(), width)
}
/// A labelled pick_list of owned string options.
@@ -202,12 +208,13 @@ fn pick_field<'a>(
options: Vec<String>,
selected: Option<String>,
on_select: impl Fn(String) -> Message + 'a,
width: Length,
) -> Element<'a, Message> {
let pl = pick_list(options, selected, on_select)
let pl = crate::ui::pick_list(options, selected, on_select)
.text_size(13)
.padding([3, 6])
.width(Length::Fill);
field_row(label, pl.into())
.width(width);
field_row(label, pl.into(), width)
}
fn tab_button<'a>(label: &'a str, this: AttrTab, active: AttrTab) -> Element<'a, Message> {
@@ -216,7 +223,7 @@ fn tab_button<'a>(label: &'a str, this: AttrTab, active: AttrTab) -> Element<'a,
.padding([4, 12])
.on_press(Message::AttrEditorTab(this))
.style(move |theme: &Theme, status| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = match (is_active, status) {
(true, _) => palette.primary.strong,
(false, button::Status::Hovered | button::Status::Pressed) => {
@@ -250,7 +257,10 @@ pub fn view_window<'a>(
layers: Vec<String>,
linetypes: Vec<String>,
styles: Vec<String>,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let width = sizing.width;
let height = sizing.height;
// ── Top toolbar: block name on the left, Apply on the right ───────────
// Mirrors the style-manager windows (actions left, primary action right).
let apply = button(text("Apply").size(11))
@@ -260,18 +270,18 @@ pub fn view_window<'a>(
let toolbar = container(
row![
text(format!("Block: {block}")).size(12).style(muted_style),
Space::new().width(Length::Fill),
Space::new().width(width),
apply,
]
.align_y(iced::Center),
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weak.color
theme.palette().background.weak.color
)),
..Default::default()
})
.width(Length::Fill)
.width(width)
.padding([5, 8]);
let tabs = row![
@@ -285,36 +295,49 @@ pub fn view_window<'a>(
text("This block has no attributes.").size(13).style(muted_style).into()
} else {
match tab {
AttrTab::Attribute => attribute_tab(rows, selected),
AttrTab::TextOptions => text_options_tab(&rows[selected.min(rows.len() - 1)], styles),
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)
properties_tab(
&rows[selected.min(rows.len() - 1)],
layers,
linetypes,
width,
height,
)
}
}
};
let content = container(column![tabs, body].spacing(8))
.width(Length::Fill)
.height(Length::Fill)
.width(width)
.height(height)
.padding(12);
container(column![toolbar, hdivider(), content])
container(column![toolbar, hdivider(width), content])
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color
theme.palette().background.base.color
)),
..Default::default()
})
.width(Length::Fill)
.height(Length::Fill)
.width(width)
.height(height)
.into()
}
/// Attribute tab: tag / prompt / value list with row-select, plus a value box.
fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Message> {
fn attribute_tab<'a>(
rows: &'a [AttrRow],
selected: usize,
width: Length,
height: Length,
) -> Element<'a, Message> {
let head = row![
container(text("Tag").size(11).style(muted_style)).width(130),
container(text("Prompt").size(11).style(muted_style)).width(Length::Fill),
container(text("Prompt").size(11).style(muted_style)).width(width),
container(text("Value").size(11).style(muted_style)).width(140),
]
.spacing(6);
@@ -324,16 +347,16 @@ fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Messag
let is_sel = idx == selected;
let line = row![
container(text(r.tag.as_str()).size(12)).width(130),
container(text(r.prompt.as_str()).size(12).style(muted_style)).width(Length::Fill),
container(text(r.prompt.as_str()).size(12).style(muted_style)).width(width),
container(text(r.value.as_str()).size(12)).width(140),
]
.spacing(6);
let btn = button(line)
.on_press(Message::AttrEditorSelect(idx))
.padding([3, 4])
.width(Length::Fill)
.width(width)
.style(move |theme: &Theme, status| {
let palette = theme.extended_palette();
let palette = theme.palette();
let hovered = matches!(status, button::Status::Hovered);
let pair = if is_sel {
palette.primary.strong
@@ -359,20 +382,27 @@ fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Messag
.style(field_style)
.size(13)
.padding([4, 6])
.width(Length::Fill);
.width(width);
column![
head,
scrollable(list).height(Length::Fill),
scrollable(list).width(width).height(height),
Space::new().height(8),
field_row("Value:", value_box.into()),
field_row("Value:", value_box.into(), width),
]
.spacing(6)
.width(width)
.height(height)
.into()
}
/// Text Options tab: the selected attribute's text formatting.
fn text_options_tab<'a>(r: &'a AttrRow, styles: Vec<String>) -> Element<'a, Message> {
fn text_options_tab<'a>(
r: &'a AttrRow,
styles: Vec<String>,
width: Length,
height: Length,
) -> Element<'a, Message> {
let style_sel = if r.text_style.is_empty() {
None
} else {
@@ -384,14 +414,14 @@ fn text_options_tab<'a>(r: &'a AttrRow, styles: Vec<String>) -> Element<'a, Mess
column![
pick_field("Text Style", styles, style_sel, |s| {
Message::AttrEditorTextStyle(s)
}),
}, width),
pick_field("Justification", justify_opts, justify_sel, |s| {
Message::AttrEditorJustify(s)
}),
edit_field("Height", &r.height, Message::AttrEditorHeight),
edit_field("Rotation", &r.rotation, Message::AttrEditorRotation),
edit_field("Width Factor", &r.width_factor, Message::AttrEditorWidth),
edit_field("Oblique Angle", &r.oblique, Message::AttrEditorOblique),
}, width),
edit_field("Height", &r.height, Message::AttrEditorHeight, width),
edit_field("Rotation", &r.rotation, Message::AttrEditorRotation, width),
edit_field("Width Factor", &r.width_factor, Message::AttrEditorWidth, width),
edit_field("Oblique Angle", &r.oblique, Message::AttrEditorOblique, width),
field_row(
"",
checkbox(r.backwards)
@@ -400,6 +430,7 @@ fn text_options_tab<'a>(r: &'a AttrRow, styles: Vec<String>) -> Element<'a, Mess
.size(15)
.text_size(12)
.into(),
width,
),
field_row(
"",
@@ -409,9 +440,12 @@ fn text_options_tab<'a>(r: &'a AttrRow, styles: Vec<String>) -> Element<'a, Mess
.size(15)
.text_size(12)
.into(),
width,
),
]
.spacing(8)
.width(width)
.height(height)
.into()
}
@@ -420,6 +454,8 @@ fn properties_tab<'a>(
r: &'a AttrRow,
layers: Vec<String>,
linetypes: Vec<String>,
width: Length,
height: Length,
) -> Element<'a, Message> {
let layer_sel = Some(r.layer.clone());
let lt_sel = Some(if r.linetype.is_empty() {
@@ -439,21 +475,23 @@ fn properties_tab<'a>(
let lw_opts = lw_options();
let lw_sel = LwItem(r.line_weight);
let lw = pick_list(lw_opts, Some(lw_sel), |it: LwItem| {
let lw = crate::ui::pick_list(lw_opts, Some(lw_sel), |it: LwItem| {
Message::AttrEditorLineweight(it.0)
})
.text_size(13)
.padding([3, 6])
.width(Length::Fill);
.width(width);
column![
pick_field("Layer", layers, layer_sel, Message::AttrEditorLayer),
pick_field("Linetype", linetypes, lt_sel, Message::AttrEditorLinetype),
pick_field("Layer", layers, layer_sel, Message::AttrEditorLayer, width),
pick_field("Linetype", linetypes, lt_sel, Message::AttrEditorLinetype, width),
pick_field("Color", color_opts, color_sel, |s| {
Message::AttrEditorColor(s)
}),
field_row("Lineweight", lw.into()),
}, width),
field_row("Lineweight", lw.into(), width),
]
.spacing(8)
.width(width)
.height(height)
.into()
}
+45 -41
View File
@@ -4,7 +4,7 @@ use crate::app::{LayerStateLayerFlag, LayerStateProperty, Message};
use crate::ui::properties::{lw_options, LwItem};
use acadrust::{LayerState, LayerStateMask};
use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
button, checkbox, column, container, row, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Element, Fill, Length, Theme};
use std::fmt;
@@ -39,7 +39,7 @@ fn muted(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(
theme
.extended_palette()
.palette()
.background
.base
.text
@@ -68,13 +68,13 @@ fn list_style(selected: bool) -> impl Fn(&Theme, button::Status) -> button::Styl
}
}
fn divider<'a>() -> Element<'a, Message> {
container(Space::new().width(Fill).height(1))
.width(Fill)
fn divider<'a>(width: Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color,
theme.palette().background.neutral.color,
)),
..Default::default()
})
@@ -107,6 +107,7 @@ pub fn view_window<'a>(
name: &'a str,
description: &'a str,
filter: &'a str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let selected_state = selected.and_then(|selected| {
states
@@ -139,7 +140,7 @@ pub fn view_window<'a>(
.on_press(Message::LayerStateManagerSelect(state.name.clone()))
.style(list_style(is_selected))
.padding([6, 9])
.width(Fill)
.width(sizing.width)
.into()
})
.collect();
@@ -165,7 +166,9 @@ pub fn view_window<'a>(
let state_list: Element<'_, Message> = if rows.is_empty() {
empty
} else {
scrollable(column(rows).spacing(2)).height(Fill).into()
scrollable(column(rows).spacing(2))
.height(sizing.height)
.into()
};
let left = container(
@@ -175,12 +178,12 @@ pub fn view_window<'a>(
.size(11)
.padding([5, 8]),
container(state_list)
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.padding(3)
.style(|theme: &Theme| container::Style {
border: Border {
color: theme.extended_palette().background.neutral.color,
color: theme.palette().background.neutral.color,
width: 1.0,
radius: 3.0.into(),
},
@@ -188,10 +191,10 @@ pub fn view_window<'a>(
}),
]
.spacing(8)
.height(Fill),
.height(sizing.height),
)
.width(280)
.height(Fill)
.height(sizing.height)
.padding(iced::Padding {
top: 12.0,
right: 8.0,
@@ -261,13 +264,13 @@ pub fn view_window<'a>(
.style(button_style(false))
.padding([5, 12]),
edit.padding([5, 12]),
Space::new().width(Fill),
Space::new().width(sizing.width),
restore.padding([5, 12]),
delete.padding([5, 12]),
]
.spacing(6)
.align_y(iced::Center),
divider(),
divider(sizing.width),
text("Name").size(10).style(muted),
text_input("Layer state name", name)
.on_input(Message::LayerStateManagerName)
@@ -281,7 +284,7 @@ pub fn view_window<'a>(
.padding([5, 8]),
Space::new().height(8),
details,
Space::new().height(Fill),
Space::new().height(sizing.height),
text("Layer states are stored inside the drawing and remain available after reopening it.")
.size(10)
.style(muted),
@@ -296,15 +299,15 @@ pub fn view_window<'a>(
.padding([6, 14]),
]
.spacing(7)
.height(Fill),
.height(sizing.height),
)
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.padding([12, 12]);
container(row![left, right].height(Fill))
.width(Fill)
.height(Fill)
container(row![left, right].height(sizing.height))
.width(sizing.width)
.height(sizing.height)
.into()
}
@@ -380,10 +383,10 @@ fn editor_header<'a>() -> Element<'a, Message> {
.padding([5, 8])
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weak.color,
theme.palette().background.weak.color,
)),
border: Border {
color: theme.extended_palette().background.neutral.color,
color: theme.palette().background.neutral.color,
width: 1.0,
radius: 0.0.into(),
},
@@ -429,13 +432,13 @@ fn editor_layer_row<'a>(
54.0
),
container(color).width(Length::Fixed(135.0)),
pick_list(linetypes, current_linetype, move |value| {
crate::ui::pick_list(linetypes, current_linetype, move |value| {
Message::LayerStateEditorLayerLinetype(index, value)
})
.text_size(11)
.padding([3, 5])
.width(Length::Fixed(150.0)),
pick_list(lw_options(), current_lineweight, move |item: LwItem| {
crate::ui::pick_list(lw_options(), current_lineweight, move |item: LwItem| {
Message::LayerStateEditorLayerLineweight(index, item.0)
})
.text_size(11)
@@ -446,7 +449,7 @@ fn editor_layer_row<'a>(
.size(11)
.padding([3, 5])
.width(Length::Fixed(135.0)),
pick_list(
crate::ui::pick_list(
transparency_options(layer.transparency),
Some(TransparencyItem(layer.transparency)),
move |item| Message::LayerStateEditorLayerTransparency(index, item.0),
@@ -461,7 +464,7 @@ fn editor_layer_row<'a>(
.padding([3, 8])
.style(move |theme: &Theme| container::Style {
background: (index % 2 == 1).then_some(Background::Color(
theme.extended_palette().background.weak.color,
theme.palette().background.weak.color,
)),
..Default::default()
})
@@ -474,6 +477,7 @@ pub fn view_editor<'a>(
filter: &'a str,
color_open: Option<usize>,
linetypes: Vec<String>,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let properties = [
("On / Off", LayerStateProperty::On),
@@ -529,7 +533,7 @@ pub fn view_editor<'a>(
.on_input(Message::LayerStateEditorDescription)
.size(11)
.padding([3, 6])
.width(Fill),
.width(sizing.width),
]
.spacing(8)
.align_y(iced::Center),
@@ -537,9 +541,9 @@ pub fn view_editor<'a>(
text(format!("{} saved layers", state.layers.len()))
.size(10)
.style(muted),
Space::new().width(Fill),
Space::new().width(sizing.width),
text("Current layer").size(10).style(muted),
pick_list(
crate::ui::pick_list(
layer_names,
Some(state.current_layer.clone()),
Message::LayerStateEditorCurrentLayer,
@@ -550,12 +554,12 @@ pub fn view_editor<'a>(
]
.spacing(8)
.align_y(iced::Center),
divider(),
divider(sizing.width),
text("Properties restored by this state").size(10).style(muted),
mask_controls,
row![
text("Saved layer values").size(12),
Space::new().width(Fill),
Space::new().width(sizing.width),
text_input("Search layers…", filter)
.on_input(Message::LayerStateEditorFilter)
.size(11)
@@ -564,14 +568,14 @@ pub fn view_editor<'a>(
]
.align_y(iced::Center),
container(
column![editor_header(), scrollable(rows).height(Fill)]
column![editor_header(), scrollable(rows).height(sizing.height)]
.spacing(0)
.height(Fill),
.height(sizing.height),
)
.height(Fill)
.height(sizing.height)
.style(|theme: &Theme| container::Style {
border: Border {
color: theme.extended_palette().background.neutral.color,
color: theme.palette().background.neutral.color,
width: 1.0,
radius: 3.0.into(),
},
@@ -581,7 +585,7 @@ pub fn view_editor<'a>(
text("Changes affect the saved state only; the drawing is unchanged until Restore.")
.size(10)
.style(muted),
Space::new().width(Fill),
Space::new().width(sizing.width),
button(text("Cancel").size(11))
.on_press(Message::LayerStateEditorCancel)
.style(button_style(false))
@@ -595,10 +599,10 @@ pub fn view_editor<'a>(
.align_y(iced::Center),
]
.spacing(8)
.height(Fill),
.height(sizing.height),
)
.padding(12)
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.into()
}
+31 -23
View File
@@ -49,7 +49,7 @@ pub const LAYER_TABLE_SCROLL_ID: &str = "layer-manager-table-scroll";
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
@@ -57,7 +57,7 @@ fn table_input_style(
theme: &Theme,
status: iced::widget::text_input::Status,
) -> iced::widget::text_input::Style {
let palette = theme.extended_palette();
let palette = theme.palette();
let border = match status {
iced::widget::text_input::Status::Focused { .. } => palette.primary.base.color,
_ => palette.background.neutral.color,
@@ -308,11 +308,19 @@ impl LayerPanel {
}
/// Render the layer panel as the full content of its own OS window.
pub fn view_window(&self, name_col_w: f32) -> Element<'_, Message> {
self.view_content(name_col_w)
pub fn view_window(
&self,
name_col_w: f32,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'_, Message> {
self.view_content(name_col_w, sizing)
}
fn view_content(&self, name_col_w: f32) -> Element<'_, Message> {
fn view_content(
&self,
name_col_w: f32,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'_, Message> {
let has_sel = self.selected.is_some();
let sel_is_zero = self
.selected
@@ -335,7 +343,7 @@ impl LayerPanel {
Message::LayerSetCurrent,
has_sel,
),
iced::widget::Space::new().width(Fill),
iced::widget::Space::new().width(sizing.width),
// Search box: filters rows by name as the user types (#343).
text_input("Search…", &self.filter)
.on_input(Message::LayerManagerFilterChanged)
@@ -349,11 +357,11 @@ impl LayerPanel {
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weak.color
theme.palette().background.weak.color
)),
..Default::default()
})
.width(Fill)
.width(sizing.width)
.padding([4, 8]);
// ── Column header ─────────────────────────────────────────────────
@@ -367,7 +375,7 @@ impl LayerPanel {
container(iced::widget::Space::new().width(2).height(14)).style(
|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color
theme.palette().background.neutral.color
)),
..Default::default()
},
@@ -390,7 +398,7 @@ impl LayerPanel {
),
]
.spacing(4)
.width(Fill)
.width(sizing.width)
.align_y(iced::Center);
for vp in &self.vp_cols {
@@ -404,7 +412,7 @@ impl LayerPanel {
let col_header = container(header_row)
.style(|theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: Some(Background::Color(palette.background.weak.color)),
border: Border {
@@ -416,7 +424,7 @@ impl LayerPanel {
}
})
.padding([4, 8])
.width(Fill);
.width(sizing.width);
// ── Layer rows ────────────────────────────────────────────────────
let mut rows_col = column![].spacing(0);
@@ -457,18 +465,18 @@ impl LayerPanel {
let table = scrollable(rows_col)
.id(iced::advanced::widget::Id::new(LAYER_TABLE_SCROLL_ID))
.height(Fill);
.height(sizing.height.min(240.0));
// ── Full-window frame ─────────────────────────────────────────────
container(column![toolbar, col_header, table].spacing(0))
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color
theme.palette().background.base.color
)),
..Default::default()
})
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.into()
}
}
@@ -481,7 +489,7 @@ fn layer_cell_button_style(
is_selected: bool,
index: usize,
) -> button::Style {
let palette = theme.extended_palette();
let palette = theme.palette();
let highlighted = matches!(status, button::Status::Hovered);
let pair = if highlighted {
palette.background.strong
@@ -500,7 +508,7 @@ fn layer_cell_button_style(
}
fn layer_header_button_style(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let palette = theme.palette();
let highlighted = matches!(
status,
button::Status::Hovered | button::Status::Pressed
@@ -568,7 +576,7 @@ fn toolbar_btn<'a>(icon: &'static [u8], label: &'a str, msg: Message) -> Element
)
.on_press(msg)
.style(|theme: &Theme, status| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = match status {
button::Status::Hovered | button::Status::Pressed => {
palette.background.strong
@@ -608,7 +616,7 @@ fn toolbar_btn_cond<'a>(
} else {
text(label).size(11).style(|theme: &Theme| iced::widget::text::Style {
color: Some(
theme.extended_palette().background.base.text.scale_alpha(0.42)
theme.palette().background.base.text.scale_alpha(0.42)
),
})
},
@@ -617,7 +625,7 @@ fn toolbar_btn_cond<'a>(
.align_y(iced::Center),
)
.style(move |theme: &Theme, status| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = match status {
button::Status::Hovered if enabled => palette.background.strong,
_ => palette.background.weak,
@@ -657,7 +665,7 @@ fn name_tip<'a>(name: &'a str) -> Element<'a, Message> {
right: 7.0,
})
.style(|theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: Some(Background::Color(palette.background.strong.color)),
border: Border {
@@ -891,7 +899,7 @@ fn layer_row<'a>(
mouse_area(
container(row_content)
.style(move |theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = if is_selected {
palette.primary.weak
} else if index % 2 == 0 {
+27 -26
View File
@@ -2,17 +2,17 @@
use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Background, Element, Fill, Theme};
use iced::{Background, Element, Theme};
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().primary.base.color),
color: Some(theme.palette().primary.base.color),
}
}
@@ -36,26 +36,26 @@ fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
}
}
fn hdivider<'a>() -> Element<'a, Message> {
container(Space::new().width(Fill).height(1))
.width(Fill)
fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color,
theme.palette().background.neutral.color,
)),
..Default::default()
})
.into()
}
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
fn vsep<'a>(height: iced::Length) -> Element<'a, Message> {
container(Space::new().width(1).height(height))
.width(1)
.height(Fill)
.height(height)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color,
theme.palette().background.neutral.color,
)),
..Default::default()
})
@@ -67,6 +67,7 @@ pub fn view_window<'a>(
selected: &'a str,
rename_buf: &'a str,
current: String,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let is_model = selected == "Model";
@@ -87,7 +88,7 @@ pub fn view_window<'a>(
}
})
.padding([4, 10]),
Space::new().width(Fill),
Space::new().width(sizing.width),
button(
row![
crate::ui::icons::themed_arrow_left(9.0),
@@ -120,11 +121,11 @@ pub fn view_window<'a>(
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weakest.color,
theme.palette().background.weakest.color,
)),
..Default::default()
})
.width(Fill)
.width(sizing.width)
.padding([5, 8]);
// ── Left: Layout list ─────────────────────────────────────────────────
@@ -143,7 +144,7 @@ pub fn view_window<'a>(
.on_press(Message::LayoutManagerSelect(name.clone()))
.style(list_item(is_sel))
.padding([5, 10])
.width(Fill)
.width(sizing.width)
.into()
})
.collect();
@@ -151,17 +152,17 @@ pub fn view_window<'a>(
let layout_list = container(
column![
text("Layouts").size(10).style(muted_style),
container(scrollable(column(list_items).spacing(2)).height(Fill))
container(scrollable(column(list_items).spacing(2)).height(sizing.height))
.style(container::bordered_box)
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.padding(2),
]
.spacing(4)
.height(Fill),
.height(sizing.height),
)
.width(220)
.height(Fill)
.height(sizing.height)
.padding(iced::Padding {
top: 12.0,
right: 8.0,
@@ -221,19 +222,19 @@ pub fn view_window<'a>(
]
.spacing(8),
)
.width(Fill)
.width(sizing.width)
.padding([12, 12]);
let body = row![layout_list, vsep(), details].height(Fill);
let body = row![layout_list, vsep(sizing.height), details].height(sizing.height);
container(column![toolbar, hdivider(), body].spacing(0))
container(column![toolbar, hdivider(sizing.width), body].spacing(0))
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color,
theme.palette().background.base.color,
)),
..Default::default()
})
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.into()
}
+6 -6
View File
@@ -62,7 +62,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().primary.base.color
theme.palette().primary.base.color
)),
border: Border {
radius: 3.0.into(),
@@ -85,7 +85,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
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.extended_palette().background.strong.color
theme.palette().background.strong.color
)),
border: Border {
radius: 3.0.into(),
@@ -110,7 +110,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
))
.size(13)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.82)),
color: Some(theme.palette().background.base.text.scale_alpha(0.82)),
});
let phase_line = text(format!(
@@ -120,7 +120,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
))
.size(12)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.extended_palette().primary.base.color),
color: Some(theme.palette().primary.base.color),
});
let cancel_btn: Element<'_, Message> = button(text("Cancel").size(12))
@@ -138,7 +138,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
)
.padding([18, 22])
.style(|theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: Some(Background::Color(palette.background.weak.color)),
border: Border {
@@ -154,7 +154,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.extended_palette().background.strong.color.scale_alpha(0.72)
theme.palette().background.strong.color.scale_alpha(0.72)
)),
..Default::default()
})
+19 -16
View File
@@ -1,14 +1,15 @@
use crate::app::config::UiThemeConfig;
use crate::app::Message;
use iced::widget::{
button, column, container, pick_list, row, scrollable, text, text_input, Space,
button, column, container, row, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Element, Fill, Theme};
use iced::{Background, Border, Element, Theme};
pub fn view_window<'a>(
default_save_format: &'a str,
ui_theme: &'a UiThemeConfig,
theme_color_inputs: &'a [String; 6],
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let selected_format = crate::io::SAVE_FORMAT_OPTIONS
.iter()
@@ -40,7 +41,7 @@ pub fn view_window<'a>(
.style(move |theme: &Theme| container::Style {
background: Some(Background::Color(color)),
border: Border {
color: theme.extended_palette().background.strong.color,
color: theme.palette().background.strong.color,
width: 1.0,
radius: 3.0.into(),
},
@@ -69,12 +70,12 @@ pub fn view_window<'a>(
Space::new().height(10),
row![
text("Default save format:").size(12).width(150),
pick_list(
crate::ui::pick_list(
crate::io::SAVE_FORMAT_OPTIONS,
selected_format,
|format: &str| Message::DefaultSaveFormatChanged(format.to_string())
)
.width(Fill),
.width(sizing.width),
]
.spacing(12)
.align_y(iced::Center),
@@ -82,18 +83,19 @@ pub fn view_window<'a>(
text(
"Used for the first save of a new drawing. Existing drawings keep their file type and version."
)
.size(11),
.size(11)
.width(sizing.width),
Space::new().height(22),
text("Theme").size(15),
Space::new().height(10),
row![
text("Iced theme:").size(12).width(150),
pick_list(
crate::ui::pick_list(
theme_options,
selected_theme,
Message::OptionsThemeChanged,
)
.width(Fill),
.width(sizing.width),
]
.spacing(12)
.align_y(iced::Center),
@@ -101,25 +103,26 @@ pub fn view_window<'a>(
text(
"Changing a base colour switches to Custom. Iced generates every component shade from these six colours."
)
.size(11),
.size(11)
.width(sizing.width),
Space::new().height(12),
color_controls,
]
.spacing(0)
.width(Fill);
.width(sizing.width);
let body = column![
scrollable(content).height(Fill),
scrollable(content).height(sizing.height),
Space::new().height(12),
row![Space::new().width(Fill), close],
row![Space::new().width(sizing.width), close],
]
.width(Fill)
.height(Fill);
.width(sizing.width)
.height(sizing.height);
container(body)
.style(container::rounded_box)
.padding([16, 18])
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.into()
}
+67 -55
View File
@@ -7,10 +7,10 @@
use crate::app::Message;
use crate::io::paper_sizes::PaperSize;
use iced::widget::{
button, checkbox, column, container, mouse_area, pick_list, row, scrollable, text, text_input,
button, checkbox, column, container, mouse_area, row, scrollable, text, text_input,
Space,
};
use iced::{Background, Border, Element, Fill, Length, Theme};
use iced::{Background, Border, Element, Fit, Length, Theme};
/// Sentinel entries in the printer dropdown (not real printer names).
pub const OUT_DEFAULT: &str = "System default printer";
@@ -212,7 +212,7 @@ impl PlotDialogState {
fn btn(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |theme: &Theme, st| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = match (accent, st) {
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
(false, button::Status::Hovered | button::Status::Pressed) => {
@@ -236,7 +236,7 @@ fn btn(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
}
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
let palette = theme.extended_palette();
let palette = theme.palette();
let border = match status {
text_input::Status::Focused { .. } => palette.primary.base.color,
_ => palette.background.neutral.color,
@@ -253,17 +253,17 @@ fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn hdivider<'a>() -> Element<'a, Message> {
container(Space::new().width(Fill).height(1))
.width(Fill)
fn hdivider<'a>(width: Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color
theme.palette().background.neutral.color
)),
..Default::default()
})
@@ -274,13 +274,13 @@ fn section_label<'a>(s: &'static str) -> Element<'a, Message> {
text(s).size(11).style(muted_style).into()
}
fn vsep<'a>() -> Element<'a, Message> {
container(Space::new().width(1).height(Fill))
fn vsep<'a>(height: Length) -> Element<'a, Message> {
container(Space::new().width(1).height(height))
.width(1)
.height(Fill)
.height(height)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color
theme.palette().background.neutral.color
)),
..Default::default()
})
@@ -302,15 +302,15 @@ fn setup_row<'a>(
.style(field_style)
.size(11)
.padding([4, 8])
.width(Fill)
.width(Fit)
.into();
}
let is_sel = name == selected;
let cell = container(text(name.to_string()).size(11))
.padding([4, 8])
.width(Fill)
.width(Fit)
.style(move |theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: is_sel.then_some(Background::Color(palette.primary.strong.color)),
text_color: is_sel.then_some(palette.primary.strong.text),
@@ -330,11 +330,12 @@ fn drop_row<'a>(
options: Vec<String>,
selected: Option<String>,
ctor: fn(String) -> PlotDlgMsg,
width: Length,
) -> Element<'a, Message> {
let pl = pick_list(options, selected, move |s| Message::PlotDlg(ctor(s)))
let pl = crate::ui::pick_list(options, selected, move |s| Message::PlotDlg(ctor(s)))
.text_size(12)
.padding([3, 6])
.width(Length::Fill);
.width(width);
row![text(label).size(11).style(muted_style).width(92), pl]
.spacing(8)
.align_y(iced::Center)
@@ -375,7 +376,12 @@ fn strs(items: &[&str]) -> Vec<String> {
items.iter().map(|s| s.to_string()).collect()
}
pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
pub fn view_window(
s: &PlotDialogState,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'_, Message> {
let width = sizing.width;
let height = sizing.height;
// ── Toolbar: Cancel … Preview Print/Export ──────────────────────────
let action = if s.to_file { "Export PDF" } else { "Print" };
// `<none>` / `<previous>` are pseudo-entries; layout rows are `*name*`.
@@ -409,7 +415,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
let toolbar = container(
row![
left_bar,
Space::new().width(Fill),
Space::new().width(width),
button(text("Set current").size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::SetCurrent))
.style(btn(false))
@@ -429,11 +435,11 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weak.color
theme.palette().background.weak.color
)),
..Default::default()
})
.width(Fill)
.width(width)
.padding([5, 10]);
// ── Printer dropdown: default + discovered printers + PDF sentinel ────
@@ -465,14 +471,14 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
.padding([6, 8])
.into()
} else {
scrollable(column(rows).spacing(1)).height(Fill).into()
scrollable(column(rows).spacing(1)).height(height).into()
};
let list_panel = container(
column![
text("Page setups").size(10).style(muted_style),
container(list_body)
.style(|theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: Some(Background::Color(palette.background.weak.color)),
border: Border {
@@ -483,15 +489,15 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
..Default::default()
}
})
.width(Fill)
.height(Fill)
.width(Length::Fill)
.height(height)
.padding(2),
]
.spacing(4)
.height(Fill),
.height(height),
)
.width(160)
.height(Fill)
.height(height)
.padding(iced::Padding {
top: 12.0,
right: 8.0,
@@ -507,37 +513,39 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
};
let left = column![
section_label("Printer / plotter"),
drop_row("Output", printer_opts, printer_sel, PlotDlgMsg::Printer),
drop_row("Output", printer_opts, printer_sel, PlotDlgMsg::Printer, width),
field_row("Copies", &s.copies, PlotDlgMsg::Copies, 60),
hdivider(),
hdivider(width),
section_label("Paper"),
drop_row("Size", paper_opts, Some(s.paper.clone()), PlotDlgMsg::Paper),
drop_row("Size", paper_opts, Some(s.paper.clone()), PlotDlgMsg::Paper, width),
drop_row(
"Orientation",
strs(&["Portrait", "Landscape"]),
Some(s.orientation.clone()),
PlotDlgMsg::Orientation,
width,
),
drop_row(
"Rotation",
strs(&["0°", "90°", "180°", "270°"]),
Some(s.rotation.clone()),
PlotDlgMsg::Rotation,
width,
),
hdivider(),
hdivider(width),
section_label("Plot area"),
row![
container(
pick_list(
crate::ui::pick_list(
strs(&["Layout", "Extents", "Display", "Window"]),
Some(s.area.clone()),
move |v| Message::PlotDlg(PlotDlgMsg::Area(v)),
)
.text_size(12)
.padding([3, 6])
.width(Length::Fill)
.width(width)
)
.width(Fill),
.width(width),
button(text("Pick…").size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::PickWindow))
.style(btn(false))
@@ -554,7 +562,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
check("Center the plot", s.center, PlotFlag::Center),
]
.spacing(9)
.width(Fill);
.width(width);
// ── Right column ─────────────────────────────────────────────────────
let right = column![
@@ -564,14 +572,15 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
strs(&["Fit", "1:1", "1:2", "1:5", "1:10", "1:20", "1:50", "1:100", "2:1"]),
Some(s.scale.clone()),
PlotDlgMsg::Scale,
width,
),
check("Scale lineweights", s.scale_lw, PlotFlag::ScaleLw),
hdivider(),
hdivider(width),
section_label("Plot style table (pen assignments)"),
row![
container(text(style_label).size(12))
.style(|theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: Some(Background::Color(palette.background.base.color)),
border: Border {
@@ -583,7 +592,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
}
})
.padding([4, 8])
.width(Fill),
.width(width),
button(text("Load…").size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::LoadStyle))
.style(btn(false))
@@ -595,20 +604,20 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
]
.spacing(6)
.align_y(iced::Center),
hdivider(),
hdivider(width),
section_label("Quality"),
row![
container(
pick_list(
crate::ui::pick_list(
strs(&["Draft", "Normal", "High", "Maximum"]),
Some(s.quality.clone()),
move |v| Message::PlotDlg(PlotDlgMsg::Quality(v)),
)
.text_size(12)
.padding([3, 6])
.width(Length::Fill)
.width(width)
)
.width(Fill),
.width(width),
text("DPI").size(11).style(muted_style),
text_input("", &s.dpi)
.on_input(move |v| Message::PlotDlg(PlotDlgMsg::Dpi(v)))
@@ -623,8 +632,9 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
strs(&["As displayed", "Wireframe", "Hidden", "Rendered"]),
Some(s.shade.clone()),
PlotDlgMsg::Shade,
width,
),
hdivider(),
hdivider(width),
section_label("Plot options"),
row![
column![
@@ -634,7 +644,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
check("Plot transparency", s.transparency, PlotFlag::Transparency),
]
.spacing(6)
.width(Fill),
.width(width),
column![
check("Paperspace last", s.paperspace_last, PlotFlag::PaperspaceLast),
check("Hide paperspace", s.hide_paperspace, PlotFlag::HidePaperspace),
@@ -642,28 +652,30 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
check("Save to layout", s.save_layout, PlotFlag::SaveLayout),
]
.spacing(6)
.width(Fill),
.width(width),
]
.spacing(10),
]
.spacing(9)
.width(Fill);
.width(width);
let detail = scrollable(
container(row![left, right].spacing(18).width(Fill)).padding(14),
container(row![left, right].spacing(18).width(width)).padding(14),
)
.width(Fill)
.height(Fill);
let body = row![list_panel, vsep(), detail].height(Fill);
.width(width)
.height(height);
let body = row![list_panel, vsep(height), detail]
.width(width)
.height(height);
container(column![toolbar, hdivider(), body].spacing(0))
container(column![toolbar, hdivider(width), body].spacing(0))
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color
theme.palette().background.base.color
)),
..Default::default()
})
.width(Fill)
.height(Fill)
.width(width)
.height(height)
.into()
}
+96 -51
View File
@@ -9,7 +9,7 @@
use crate::app::Message;
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
use iced::widget::{
button, column, container, markdown, pick_list, row, rule, scrollable, text, text_input, Space,
button, column, container, markdown, row, rule, scrollable, text, text_input, Space,
};
use iced::{Background, Border, Element, Fill, Length, Theme};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -48,13 +48,13 @@ inventory::submit!(crate::command::CommandRegistration {
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().primary.base.color),
color: Some(theme.palette().primary.base.color),
}
}
@@ -62,7 +62,7 @@ fn badge<'a>(label: String) -> Element<'a, Message> {
container(text(label).size(11))
.padding([2, 8])
.style(|theme: &Theme| {
let pair = theme.extended_palette().primary.weak;
let pair = theme.palette().primary.weak;
container::Style {
background: Some(Background::Color(pair.color)),
text_color: Some(pair.text),
@@ -105,7 +105,7 @@ fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
container(text(label.to_string()).size(11))
.padding([2, 8])
.style(move |theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = match kind {
StatusKind::Muted => palette.background.weak,
StatusKind::Success => palette.success.weak,
@@ -126,7 +126,7 @@ fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
}
fn card_style(theme: &Theme, selected: bool) -> container::Style {
let palette = theme.extended_palette();
let palette = theme.palette();
container::Style {
background: selected
.then(|| Background::Color(palette.primary.weak.color.scale_alpha(0.18))),
@@ -325,7 +325,7 @@ fn install_controls<'a>(
text("no releases").size(11).style(muted_style).into()
} else {
let r = repo_s.clone();
pick_list(tags, selected, move |tag| {
crate::ui::pick_list(tags, selected, move |tag| {
Message::PluginReleaseSelect(r.clone(), tag)
})
.text_size(12)
@@ -490,7 +490,7 @@ fn registry_notice<'a>(m: &MarketView) -> Option<Element<'a, Message>> {
container(body.padding([10, 12]))
.width(Fill)
.style(|theme: &Theme| {
let pair = theme.extended_palette().warning.weak;
let pair = theme.palette().warning.weak;
container::Style {
background: Some(Background::Color(pair.color.scale_alpha(0.16))),
border: Border {
@@ -638,7 +638,12 @@ fn resolve_readme_link(repo: &str, uri: &str) -> String {
}
}
fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Message> {
fn readme_panel<'a>(
market: &MarketView<'a>,
theme: &Theme,
width: Length,
height: Length,
) -> Element<'a, Message> {
let Some(repo) = market.selected_repo else {
return container(
column![
@@ -649,9 +654,10 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
]
.spacing(8),
)
.center(Fill)
.width(Fill)
.height(Fill)
.center_x(width)
.center_y(height)
.width(width)
.height(height)
.style(container::bordered_box)
.into();
};
@@ -667,7 +673,7 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
text(repo.to_string()).size(11).style(primary_style),
]
.spacing(3)
.width(Fill),
.width(width),
pill_button(
"View on GitHub",
Message::OpenUrl(format!("https://github.com/{repo}")),
@@ -686,9 +692,10 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
]
.spacing(6),
)
.center(Fill)
.width(Fill)
.height(Fill)
.center_x(width)
.center_y(height)
.width(width)
.height(height)
.into()
} else {
match market.readmes.get(repo) {
@@ -715,18 +722,20 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
]
.spacing(6),
)
.center(Fill)
.width(Fill)
.height(Fill)
.center_x(width)
.center_y(height)
.width(width)
.height(height)
.into(),
None => container(
text("Select the plugin again to load its README.")
.size(12)
.style(muted_style),
)
.center(Fill)
.width(Fill)
.height(Fill)
.center_x(width)
.center_y(height)
.width(width)
.height(height)
.into(),
}
};
@@ -738,18 +747,18 @@ fn readme_panel<'a>(market: &MarketView<'a>, theme: &Theme) -> Element<'a, Messa
left: 0.0,
};
let readme = scrollable(container(content).padding(gutter))
.height(Fill)
.width(Fill);
.height(height)
.width(width);
container(
column![header, rule::horizontal(1), readme]
.spacing(10)
.padding([12, 14])
.width(Fill)
.height(Fill),
.width(width)
.height(height),
)
.width(Fill)
.height(Fill)
.width(width)
.height(height)
.style(container::bordered_box)
.into()
}
@@ -760,7 +769,10 @@ pub fn view_window<'a>(
loaded: &FxHashSet<String>,
market: MarketView<'a>,
theme: &'a Theme,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let width = sizing.width;
let height = sizing.height;
let title = text("Plugins").size(20);
let subtitle = text("Browse, install, and manage add-ons. Select one to view its README.")
.size(12)
@@ -813,40 +825,43 @@ pub fn view_window<'a>(
left: 0.0,
};
let catalog = scrollable(container(list.width(Fill)).padding(gutter))
.height(Fill)
.height(height)
.width(Fill);
let search = text_input("Search plugins…", market.search)
.on_input(Message::PluginSearchInput)
.size(13)
.padding([7, 10])
.width(Fill);
let catalog_pane = column![search, catalog].spacing(10).width(Fill).height(Fill);
let details = readme_panel(&market, theme);
let catalog_pane = column![search, catalog]
.spacing(10)
.width(Fill)
.height(height);
let details = readme_panel(&market, theme, width, height);
let body = row![
container(catalog_pane)
.width(Length::Fixed(410.0))
.height(Fill),
.height(height),
details,
]
.spacing(14)
.height(Fill)
.width(Fill);
.height(height)
.width(width);
container(
column![title, subtitle, Space::new().height(12), body]
.spacing(4)
.padding(18)
.width(Fill)
.height(Fill),
.width(width)
.height(height),
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color,
theme.palette().background.base.color,
)),
..Default::default()
})
.width(Fill)
.height(Fill)
.width(width)
.height(height)
.into()
}
@@ -855,28 +870,58 @@ pub fn view_window<'a>(
pub fn view_web_notice<'a>() -> Element<'a, Message> {
let download = button(text("Download desktop app").size(13))
.on_press(Message::OpenUrl(DESKTOP_DOWNLOAD_URL.to_string()))
.padding([8, 16])
.style(button::primary);
.padding([9, 18])
.style(|theme: &Theme, status| {
let mut style = button::primary(theme, status);
style.text_color = iced::Color::WHITE;
style
});
container(
let icon = container(crate::ui::icons::themed_primary(
crate::ui::icons::GEAR,
26.0,
))
.center(Length::Fixed(44.0))
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().primary.weak.color,
)),
border: Border {
radius: 12.0.into(),
..Default::default()
},
..Default::default()
});
let notice = container(
column![
text("Plugins require the desktop app").size(20),
icon,
text("Plugins are available in the desktop app")
.size(20)
.width(Length::Fit)
.align_x(iced::alignment::Horizontal::Center),
text(
"Open CAD Studio plugins are native packages and cannot run inside a browser. \
Install the desktop app to browse, install, and use plugins.",
Download the desktop app to browse, install, and use plugins.",
)
.size(13)
.width(Length::Fit)
.align_x(iced::alignment::Horizontal::Center)
.style(muted_style),
Space::new().height(8),
Space::new().height(4),
download,
]
.spacing(8)
.padding(24)
.width(Fill),
.spacing(10)
.align_x(iced::alignment::Horizontal::Center)
.width(Length::Fit),
)
.center(Fill)
.width(Fill)
.height(Fill)
.width(Length::Fit.max(380.0));
container(notice)
.center_x(Length::Fit)
.padding([16, 20])
.width(Length::Fit)
.height(Length::Fit)
.into()
}
+27 -17
View File
@@ -2,7 +2,7 @@
use crate::app::Message;
use iced::widget::{column, container, row, scrollable, text, Space};
use iced::{Background, Element, Fill, Theme};
use iced::{Background, Element, Theme};
use std::borrow::Cow;
/// Display name of the primary accelerator modifier on this platform.
@@ -16,23 +16,23 @@ const MOD: &str = "Ctrl";
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().primary.base.color),
color: Some(theme.palette().primary.base.color),
}
}
fn hdivider<'a>() -> Element<'a, Message> {
container(Space::new().width(Fill).height(1))
.width(Fill)
fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.neutral.color,
theme.palette().background.neutral.color,
)),
..Default::default()
})
@@ -70,6 +70,7 @@ fn section<'a>(title: impl Into<Cow<'static, str>>) -> Element<'a, Message> {
pub fn view_window<'a>(
overrides: &'a rustc_hash::FxHashMap<String, String>,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
@@ -82,11 +83,11 @@ pub fn view_window<'a>(
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.weakest.color,
theme.palette().background.weakest.color,
)),
..Default::default()
})
.width(Fill)
.width(sizing.width)
.padding([5, 10]);
// ── Shortcut entries ──────────────────────────────────────────────────
@@ -152,8 +153,8 @@ pub fn view_window<'a>(
// ── Section headers styled separately ────────────────────────────────
let content = scrollable(column(rows).spacing(3).padding([12, 16]))
.width(Fill)
.height(Fill);
.width(sizing.width)
.height(sizing.height);
// ── Header row with accent ────────────────────────────────────────────
let header = container(
@@ -166,20 +167,29 @@ pub fn view_window<'a>(
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().primary.weak.color,
theme.palette().primary.weak.color,
)),
..Default::default()
})
.width(Fill);
.width(sizing.width);
container(column![toolbar, hdivider(), header, hdivider(), content].spacing(0))
container(
column![
toolbar,
hdivider(sizing.width),
header,
hdivider(sizing.width),
content
]
.spacing(0),
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color,
theme.palette().background.base.color,
)),
..Default::default()
})
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.into()
}
+35 -26
View File
@@ -1,23 +1,28 @@
use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, Space};
use iced::{Background, Border, Element, Fill, Theme};
use iced::{Background, Border, Element, Theme};
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.extended_palette().primary.base.color),
color: Some(theme.palette().primary.base.color),
}
}
/// Renders one of the two "Installed" / "Latest" cards. The `highlight`
/// flag tints the border + label with the accent colour, making the new
/// version the visual anchor of the row.
fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Element<'a, Message> {
fn version_card<'a>(
label: &'static str,
value: String,
highlight: bool,
width: iced::Length,
) -> Element<'a, Message> {
container(
column![
text(label)
@@ -32,7 +37,7 @@ fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Elem
.spacing(4)
.align_x(iced::Center),
)
.width(Fill)
.width(width)
.padding(iced::Padding {
top: 14.0,
right: 12.0,
@@ -41,7 +46,7 @@ fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Elem
})
.align_x(iced::Center)
.style(move |theme: &Theme| {
let palette = theme.extended_palette();
let palette = theme.palette();
let pair = if highlight {
palette.primary.weak
} else {
@@ -120,7 +125,11 @@ fn strip_inline_md(s: &str) -> String {
out
}
pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
pub fn view_window<'a>(
latest: &'a str,
body: &'a str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let header = container(
column![
text("New Release Available").size(20).style(primary_style),
@@ -131,7 +140,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
.spacing(4)
.align_x(iced::Center),
)
.width(Fill)
.width(sizing.width)
.padding(iced::Padding {
top: 14.0,
right: 0.0,
@@ -147,20 +156,21 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
"Installed",
format!("v{}", env!("CARGO_PKG_VERSION")),
false,
sizing.width,
);
let latest_card = version_card("Latest", format!("v{}", latest), true);
let latest_card = version_card("Latest", format!("v{}", latest), true, sizing.width);
let arrow = container(crate::ui::icons::themed_secondary(
crate::ui::icons::ARROW_LONG_RIGHT,
20.0,
))
.width(iced::Length::Fixed(32.0))
.height(Fill)
.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)
.width(Fill);
.width(sizing.width);
let later_btn = button(text("Later").size(11))
.on_press(Message::UpdateNoticeClose)
@@ -172,7 +182,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
.style(button::primary)
.padding([6, 16]);
let footer = row![Space::new().width(Fill), later_btn, open_btn]
let footer = row![Space::new().width(sizing.width), later_btn, open_btn]
.spacing(8)
.align_y(iced::Center)
.padding(iced::Padding {
@@ -203,27 +213,26 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
for line in body.lines() {
col = col.push(render_notes_line(line));
}
scrollable(container(col).padding([10, 14])).height(Fill).into()
scrollable(container(col).padding([10, 14]))
.height(sizing.height)
.into()
};
let notes_block = container(notes_body)
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.style(container::bordered_box);
// Wrap notes_block in a Fill-height container outside the column so it
// greedily claims every pixel left over after the fixed-height rows
// (header, version cards, heading, footer). Without this iced lets the
// notes panel shrink to its content height, leaving a gap above the
// footer.
// Keep the notes panel content-sized. The outer modal supplies the maximum
// height, so long release notes scroll instead of stretching the dialog.
let notes_fill = container(notes_block)
.width(Fill)
.height(Fill);
.width(sizing.width)
.height(sizing.height);
container(
column![header, info_block, notes_heading, notes_fill, footer]
.spacing(0)
.height(Fill)
.height(sizing.height)
.padding(iced::Padding {
top: 0.0,
right: 20.0,
@@ -233,11 +242,11 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.extended_palette().background.base.color,
theme.palette().background.base.color,
)),
..Default::default()
})
.width(Fill)
.height(Fill)
.width(sizing.width)
.height(sizing.height)
.into()
}