Merge pull request #1549 from MarcelP102/codex/graphic-attributes-cleanup

[UX] Introducing graphic attributes panel

# Conflicts:
#	locales/ar-SA/opencadstudio.ftl
#	locales/bg-BG/opencadstudio.ftl
#	locales/cs-CZ/opencadstudio.ftl
#	locales/de-DE/opencadstudio.ftl
#	locales/el-GR/opencadstudio.ftl
#	locales/en-US/opencadstudio.ftl
#	locales/es-ES/opencadstudio.ftl
#	locales/fi-FI/opencadstudio.ftl
#	locales/fr-FR/opencadstudio.ftl
#	locales/hi-IN/opencadstudio.ftl
#	locales/hu-HU/opencadstudio.ftl
#	locales/it-IT/opencadstudio.ftl
#	locales/ja-JP/opencadstudio.ftl
#	locales/ko-KR/opencadstudio.ftl
#	locales/lo-LA/opencadstudio.ftl
#	locales/nl-NL/opencadstudio.ftl
#	locales/pl-PL/opencadstudio.ftl
#	locales/pt-BR/opencadstudio.ftl
#	locales/ru-RU/opencadstudio.ftl
#	locales/tr-TR/opencadstudio.ftl
#	locales/zh-CN/opencadstudio.ftl
#	locales/zh-TW/opencadstudio.ftl
#	src/app/mod.rs
#	src/app/update/dialog.rs
#	src/app/view/mod.rs
#	src/locale_catalog.rs
#	src/ui/dock.rs
This commit is contained in:
Hakan Seven committed 2026-10-12 00:49:27 +03:00
commit 1333efcad2
68 files changed
+6178 -173

No files matched your search

+151 -90
View File
@@ -55,6 +55,9 @@ pub fn linetype_display_name(name: &str) -> String {
impl fmt::Display for LinetypeItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.name.is_empty() && !self.art.is_empty() {
return f.write_str(&self.art);
}
let name = linetype_display_name(&self.name);
if self.art.is_empty() {
write!(f, "{name}")
@@ -95,6 +98,7 @@ impl fmt::Display for SelectionGroup {
#[derive(Clone)]
struct HatchPatternPreview {
pattern: crate::scene::model::hatch_model::HatchPattern,
padding: f32,
}
impl canvas::Program<Message> for HatchPatternPreview {
@@ -112,7 +116,7 @@ impl canvas::Program<Message> for HatchPatternPreview {
let mut frame = canvas::Frame::new(renderer, bounds.size());
let palette = theme.palette();
let pad = 4.0;
let pad = self.padding;
let sample = canvas::Path::rectangle(
Point::new(pad, pad),
Size::new(
@@ -201,6 +205,31 @@ fn hatch_preview_scale(pattern: &crate::scene::model::hatch_model::HatchPattern)
}
}
fn hatch_pattern_preview(
pattern: crate::scene::model::hatch_model::HatchPattern,
height: f32,
) -> Element<'static, Message> {
canvas(HatchPatternPreview {
pattern,
padding: 4.0,
})
.width(Length::Fill)
.height(height)
.into()
}
pub(crate) fn compact_hatch_pattern_preview(
pattern: crate::scene::model::hatch_model::HatchPattern,
) -> Element<'static, Message> {
canvas(HatchPatternPreview {
pattern,
padding: 0.5,
})
.width(Length::Fill)
.height(ROW_H)
.into()
}
fn hatch_pattern_matches(
entry: &crate::scene::model::hatch_patterns::PatternEntry,
search: &str,
@@ -223,6 +252,113 @@ pub(crate) fn filtered_hatch_patterns(
.collect()
}
/// Messages of a hatch pattern picker; the Properties panel and the Graphic
/// Attributes palette each route them to their own state.
pub(crate) struct PatternPickerMessages {
pub search_id: &'static str,
pub on_search: fn(String) -> Message,
pub on_confirm: Message,
pub on_focus: fn(usize) -> Message,
pub on_changed: fn(String) -> Message,
}
pub(crate) fn hatch_pattern_picker_content<'a>(
search_value: &'a str,
focus: usize,
current: &'a str,
messages: PatternPickerMessages,
) -> Element<'a, Message> {
let PatternPickerMessages {
search_id,
on_search,
on_confirm,
on_focus,
on_changed,
} = messages;
let search = text_input(t!("Search patterns…").as_ref(), search_value)
.id(iced::widget::Id::new(search_id))
.on_input(on_search)
.on_submit(on_confirm)
.size(FONT_SZ)
.padding([5, 7])
.width(Length::Fill);
let mut grid = column![].spacing(6);
let visible = filtered_hatch_patterns(search_value);
for (row_index, pair) in visible.chunks(2).enumerate() {
let mut cards = row![].spacing(6);
for (column_index, entry) in pair.iter().enumerate() {
let index = row_index * 2 + column_index;
let selected = current.eq_ignore_ascii_case(&entry.name);
let focused = focus == index;
let name = entry.name.clone();
let preview = hatch_pattern_preview(entry.gpu.clone(), PATTERN_PREVIEW_H);
let card = button(
column![
preview,
container(text(crate::ui::text_util::elide(&entry.name, 20)).size(FONT_SZ))
.width(Length::Fill)
.align_x(iced::Center),
]
.spacing(3),
)
.on_press(on_changed(name))
.style(move |theme: &Theme, status| {
let palette = theme.palette();
let hovered = matches!(status, button::Status::Hovered | button::Status::Pressed);
let pair = if selected {
palette.primary.weak
} else if hovered || focused {
palette.background.strong
} else {
palette.background.weak
};
button::Style {
background: Some(Background::Color(pair.color)),
text_color: pair.text,
border: Border {
color: if selected || focused {
palette.primary.base.color
} else {
palette.background.neutral.color
},
width: if selected || focused { 2.0 } else { 1.0 },
radius: 4.0.into(),
},
..Default::default()
}
})
.padding(5)
.width(PATTERN_CARD_W);
cards = cards.push(mouse_area(card).on_enter(on_focus(index)));
}
grid = grid.push(cards);
}
let results: Element<'_, Message> = if visible.is_empty() {
container(
text(t!("No matching patterns"))
.size(FONT_SZ)
.style(hint_text_style),
)
.padding(12)
.width(Length::Fill)
.center_x(Length::Fill)
.into()
} else {
scrollable(grid)
.height(Length::Fill)
.width(Length::Fill)
.into()
};
container(column![search, results].spacing(7))
.style(container::bordered_box)
.padding(8)
.width(PATTERN_PICKER_W)
.height(Length::Fixed(PATTERN_PICKER_H))
.into()
}
/// All standard CAD lineweight options for the combobox.
pub fn lw_options() -> Vec<LwItem> {
[
@@ -1504,99 +1640,24 @@ impl PropertiesPanel {
return prop_row_widget(label, head.into());
}
let search = text_input(t!("Search patterns…").as_ref(), &self.hatch_pattern_search)
.id(iced::widget::Id::new("hatch-pattern-search"))
.on_input(Message::PropHatchPatternSearchChanged)
.on_submit(Message::PropHatchPatternConfirm)
.size(FONT_SZ)
.padding([5, 7])
.width(Length::Fill);
let mut grid = column![].spacing(6);
let visible = filtered_hatch_patterns(&self.hatch_pattern_search);
for (row_index, pair) in visible.chunks(2).enumerate() {
let mut cards = row![].spacing(6);
for (column_index, entry) in pair.iter().enumerate() {
let index = row_index * 2 + column_index;
let selected = current.eq_ignore_ascii_case(&entry.name);
let focused = self.hatch_pattern_focus == index;
let name = entry.name.clone();
let preview = canvas(HatchPatternPreview {
pattern: entry.gpu.clone(),
})
.width(Length::Fill)
.height(PATTERN_PREVIEW_H);
let card = button(
column![
preview,
container(text(crate::ui::text_util::elide(&entry.name, 20)).size(FONT_SZ))
.width(Length::Fill)
.align_x(iced::Center),
]
.spacing(3),
)
.on_press(Message::PropHatchPatternChanged(name))
.style(move |theme: &Theme, status| {
let palette = theme.palette();
let hovered =
matches!(status, button::Status::Hovered | button::Status::Pressed);
let pair = if selected {
palette.primary.weak
} else if hovered || focused {
palette.background.strong
} else {
palette.background.weak
};
button::Style {
background: Some(Background::Color(pair.color)),
text_color: pair.text,
border: Border {
color: if selected || focused {
palette.primary.base.color
} else {
palette.background.neutral.color
},
width: if selected || focused { 2.0 } else { 1.0 },
radius: 4.0.into(),
},
..Default::default()
}
})
.padding(5)
.width(PATTERN_CARD_W);
cards =
cards.push(mouse_area(card).on_enter(Message::PropHatchPatternFocus(index)));
}
grid = grid.push(cards);
}
let results: Element<'_, Message> = if visible.is_empty() {
container(
text(t!("No matching patterns"))
.size(FONT_SZ)
.style(hint_text_style),
)
.padding(12)
.width(Length::Fill)
.center_x(Length::Fill)
.into()
} else {
scrollable(grid)
.height(Length::Fill)
.width(Length::Fill)
.into()
};
let popup = container(column![search, results].spacing(7))
.style(container::bordered_box)
.padding(8)
.width(PATTERN_PICKER_W)
.height(Length::Fixed(PATTERN_PICKER_H));
let popup = hatch_pattern_picker_content(
&self.hatch_pattern_search,
self.hatch_pattern_focus,
current,
PatternPickerMessages {
search_id: "hatch-pattern-search",
on_search: Message::PropHatchPatternSearchChanged,
on_confirm: Message::PropHatchPatternConfirm,
on_focus: Message::PropHatchPatternFocus,
on_changed: Message::PropHatchPatternChanged,
},
);
prop_row_widget(
label,
crate::ui::color_select::drop_down_below(
head.into(),
popup.into(),
popup,
Some(Length::Fixed(PATTERN_PICKER_W)),
Length::Fixed(PATTERN_PICKER_H),
Message::PropHatchPatternPickerToggle(current.to_string()),
@@ -2158,7 +2219,7 @@ fn text_input_style(theme: &Theme, status: text_input::Status) -> text_input::St
}
}
fn combo_input_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
pub(crate) fn combo_input_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
text_input_style(theme, status)
}