forked from 1b-agent/opencadstudio-upstream
Graphic Attributes: tidy helpers, split tests, translate the palette
- The hatch pattern picker takes its messages as one PatternPickerMessages struct instead of eight arguments. - The palette's fill index is keyed on the scene's geometry epoch alone; epochs are unique across scenes, so the scene address is not needed. - The ribbon lineweight handler is an on_ribbon_lineweight_changed method, used by the palette instead of a recursive update() call. - The 430-line palette test is split into eight focused tests with shared helpers; no checks were dropped. - Palette, gradient editor, prompts, messages and undo labels go through t!/tf!. The 33 new strings are in a graphic-attributes Fluent group in all 21 locales; "[Yes / No] <Yes>", ByLayer and ByBlock stay English. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VgkJN8DEojuPTumAXLHotu
This commit is contained in:
28 files changed
+1060
-482
No files matched your search
+25
-10
@@ -249,16 +249,29 @@ 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,
|
||||
search_id: &'static str,
|
||||
on_search: fn(String) -> Message,
|
||||
on_confirm: Message,
|
||||
on_focus: fn(usize) -> Message,
|
||||
on_changed: fn(String) -> Message,
|
||||
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)
|
||||
@@ -1663,11 +1676,13 @@ impl PropertiesPanel {
|
||||
&self.hatch_pattern_search,
|
||||
self.hatch_pattern_focus,
|
||||
current,
|
||||
"hatch-pattern-search",
|
||||
Message::PropHatchPatternSearchChanged,
|
||||
Message::PropHatchPatternConfirm,
|
||||
Message::PropHatchPatternFocus,
|
||||
Message::PropHatchPatternChanged,
|
||||
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(
|
||||
|
||||
@@ -276,7 +276,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
}
|
||||
})
|
||||
};
|
||||
let color_field = |label: &'static str, index, color: AcadColor| {
|
||||
let color_field = |label: std::borrow::Cow<'static, str>, index, color: AcadColor| {
|
||||
let (background, _) = crate::ui::properties::acad_color_display(color);
|
||||
let value = crate::ui::color_select::color_display_name(color);
|
||||
let picker = button(
|
||||
@@ -320,11 +320,11 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
.width(Fill)
|
||||
};
|
||||
let kinds = [
|
||||
(GradientKind::Linear, "Linear"),
|
||||
(GradientKind::Cylinder, "Cyl."),
|
||||
(GradientKind::Spherical, "Spher."),
|
||||
(GradientKind::Hemispherical, "Hemi."),
|
||||
(GradientKind::Curved, "Curved"),
|
||||
(GradientKind::Linear, crate::t!("Linear")),
|
||||
(GradientKind::Cylinder, crate::t!("Cyl.")),
|
||||
(GradientKind::Spherical, crate::t!("Spher.")),
|
||||
(GradientKind::Hemispherical, crate::t!("Hemi.")),
|
||||
(GradientKind::Curved, crate::t!("Curved")),
|
||||
];
|
||||
let mut type_row = row![].spacing(3);
|
||||
for (kind, label) in kinds {
|
||||
@@ -357,7 +357,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
));
|
||||
}
|
||||
let invert_selected = state.inverted && state.kind != GradientKind::Linear;
|
||||
let invert = button(column![text("⇄").size(18), text("Invert").size(9)].align_x(iced::Center))
|
||||
let invert = button(column![text("⇄").size(18), text(crate::t!("Invert")).size(9)].align_x(iced::Center))
|
||||
.on_press_maybe(
|
||||
(state.kind != GradientKind::Linear).then(|| msg(GradientMsg::InvertToggled)),
|
||||
)
|
||||
@@ -378,21 +378,21 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
});
|
||||
type_row = type_row.push(separator).push(tooltip(
|
||||
invert,
|
||||
text("Invert gradient").size(10),
|
||||
text(crate::t!("Invert gradient")).size(10),
|
||||
tooltip::Position::Bottom,
|
||||
));
|
||||
let colors: Element<'_, Message> = if state.color_mode == GradientColorMode::Two {
|
||||
row![
|
||||
color_field("Color 1", 1, state.color_1),
|
||||
color_field("Color 2", 2, state.color_2)
|
||||
color_field(crate::t!("Color 1"), 1, state.color_1),
|
||||
color_field(crate::t!("Color 2"), 2, state.color_2)
|
||||
]
|
||||
.spacing(6)
|
||||
.into()
|
||||
} else {
|
||||
row![
|
||||
color_field("Color 1", 1, state.color_1),
|
||||
color_field(crate::t!("Color 1"), 1, state.color_1),
|
||||
row![
|
||||
text("Tone").size(11).width(43),
|
||||
text(crate::t!("Tone")).size(11).width(43),
|
||||
slider(0..=100, (state.shade_tint * 100.0) as i32, |v| {
|
||||
msg(GradientMsg::ShadeTint(v as f32 / 100.0))
|
||||
})
|
||||
@@ -408,7 +408,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
.into()
|
||||
};
|
||||
let angle = row![
|
||||
text("Angle").size(11).width(43),
|
||||
text(crate::t!("Angle")).size(11).width(43),
|
||||
text_input("0.0°", &state.angle)
|
||||
.on_input(|value| msg(GradientMsg::Angle(value)))
|
||||
.size(11)
|
||||
@@ -420,19 +420,19 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
.padding(3)
|
||||
.width(24)
|
||||
.height(crate::ui::ROW_H),
|
||||
text("Reset angle").size(10),
|
||||
text(crate::t!("Reset angle")).size(10),
|
||||
tooltip::Position::Bottom,
|
||||
),
|
||||
Space::new().width(Fill),
|
||||
checkbox(state.centered)
|
||||
.label("Centered")
|
||||
.label(crate::t!("Centered"))
|
||||
.on_toggle(|value| msg(GradientMsg::Centered(value)))
|
||||
.size(14)
|
||||
.text_size(11)
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
let types = row![text("Type").size(11).width(43), type_row]
|
||||
let types = row![text(crate::t!("Type")).size(11).width(43), type_row]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
let actions = container(
|
||||
@@ -453,8 +453,8 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
|
||||
let content = column![
|
||||
container(preview(state)).width(Fill).height(80),
|
||||
row![
|
||||
segment("One Color", GradientColorMode::One),
|
||||
segment("Two Colors", GradientColorMode::Two)
|
||||
segment(crate::t!("One Color"), GradientColorMode::One),
|
||||
segment(crate::t!("Two Colors"), GradientColorMode::Two)
|
||||
]
|
||||
.spacing(2),
|
||||
colors,
|
||||
|
||||
@@ -71,12 +71,12 @@ impl GraphicAttribute {
|
||||
|
||||
impl std::fmt::Display for GraphicAttribute {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Varies => "*VARIES*",
|
||||
Self::None => "None",
|
||||
Self::Solid => "Solid",
|
||||
Self::Hatch => "Hatch",
|
||||
Self::Gradient => "Gradient",
|
||||
f.write_str(&match self {
|
||||
Self::Varies => "*VARIES*".into(),
|
||||
Self::None => crate::t!("None"),
|
||||
Self::Solid => crate::t!("Solid"),
|
||||
Self::Hatch => crate::t!("Hatch"),
|
||||
Self::Gradient => crate::t!("Gradient"),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ pub struct GraphicAttributesState {
|
||||
pub fill_on_current_layer: bool,
|
||||
/// View-side caches; `view` runs after every message, so neither the
|
||||
/// fill lookup nor the gradient preview is rebuilt unless its input changed.
|
||||
fill_index: RefCell<Option<((usize, u64), FillIndex)>>,
|
||||
fill_index: RefCell<Option<(u64, FillIndex)>>,
|
||||
gradient_preview: RefCell<Option<(HatchGradientPattern, Option<image::Handle>)>>,
|
||||
}
|
||||
|
||||
@@ -166,7 +166,9 @@ impl GraphicAttributesState {
|
||||
/// The fills and fill kind of `selected`, from an index of associative
|
||||
/// fills that is rebuilt only when the scene's geometry changes.
|
||||
pub(crate) fn fills(&self, scene: &Scene, selected: &[Handle]) -> (Vec<Handle>, GraphicAttribute) {
|
||||
let key = (std::ptr::from_ref(scene) as usize, scene.geometry_epoch);
|
||||
// Geometry epochs are unique across all scenes, so the epoch alone
|
||||
// identifies both the tab and the state of its drawing.
|
||||
let key = scene.geometry_epoch;
|
||||
let mut cache = self.fill_index.borrow_mut();
|
||||
if cache.as_ref().is_none_or(|(cached, _)| *cached != key) {
|
||||
*cache = Some((key, FillIndex::build(&scene.document)));
|
||||
@@ -219,12 +221,13 @@ impl GraphicAttributesState {
|
||||
pub fn fill_close_prompt(&self) -> Option<String> {
|
||||
let count = self.pending_fill_close.as_ref()?.open_boundaries.len();
|
||||
Some(if count == 1 {
|
||||
"The selected object is open. Close it before creating the fill? [Yes / No] <Yes>"
|
||||
.to_string()
|
||||
crate::t!("The selected object is open. Close it before creating the fill? [Yes / No] <Yes>")
|
||||
.into_owned()
|
||||
} else {
|
||||
format!(
|
||||
crate::tf!(
|
||||
"{count} selected objects are open. Close them before creating the fill? [Yes / No] <Yes>"
|
||||
)
|
||||
.into_owned()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -634,7 +637,7 @@ fn compact_button_content(label: &'static str) -> Element<'static, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn popup_item<'a>(label: &'a str, message: Message) -> Element<'a, Message> {
|
||||
fn popup_item<'a>(label: std::borrow::Cow<'a, str>, message: Message) -> Element<'a, Message> {
|
||||
button(text(label).size(11))
|
||||
.on_press(message)
|
||||
.style(crate::ui::color_select::list_row_style)
|
||||
@@ -650,7 +653,7 @@ fn mode_menu<'a>(
|
||||
menu: Menu,
|
||||
open_menu: Option<Menu>,
|
||||
height: f32,
|
||||
items: Vec<(&'a str, Message)>,
|
||||
items: Vec<(std::borrow::Cow<'a, str>, Message)>,
|
||||
) -> Element<'a, Message> {
|
||||
let open = open_menu == Some(menu);
|
||||
let more = button(compact_button_content(mode.button_label()))
|
||||
@@ -738,9 +741,9 @@ fn color_control<'a>(
|
||||
open_menu,
|
||||
COMPACT_BUTTON_HEIGHT,
|
||||
vec![
|
||||
("ByLayer", msg(changed(AcadColor::ByLayer))),
|
||||
("ByBlock", msg(changed(AcadColor::ByBlock))),
|
||||
("Custom...", Message::OpenColorWindow(target, info.color)),
|
||||
("ByLayer".into(), msg(changed(AcadColor::ByLayer))),
|
||||
("ByBlock".into(), msg(changed(AcadColor::ByBlock))),
|
||||
(crate::t!("Custom…"), Message::OpenColorWindow(target, info.color)),
|
||||
],
|
||||
);
|
||||
row![swatch, more].spacing(4).align_y(iced::Center).into()
|
||||
@@ -777,9 +780,9 @@ fn transparency_control<'a>(
|
||||
open_menu,
|
||||
crate::ui::ROW_H,
|
||||
vec![
|
||||
("ByLayer", msg(changed(Transparency::BY_LAYER))),
|
||||
("ByBlock", msg(changed(Transparency::BY_BLOCK))),
|
||||
("Custom", msg(changed(percent(info.value)))),
|
||||
("ByLayer".into(), msg(changed(Transparency::BY_LAYER))),
|
||||
("ByBlock".into(), msg(changed(Transparency::BY_BLOCK))),
|
||||
(crate::t!("Custom"), msg(changed(percent(info.value)))),
|
||||
],
|
||||
);
|
||||
row![control, more].spacing(4).align_y(iced::Center).into()
|
||||
@@ -804,7 +807,7 @@ fn header_menu(side: DockSide, open: bool, fill_on_current_layer: bool) -> Eleme
|
||||
}
|
||||
|
||||
let item = |label: &'static str, message| {
|
||||
button(text(label).size(11))
|
||||
button(text(crate::t!(label)).size(11))
|
||||
.on_press(msg(message))
|
||||
.style(crate::ui::color_select::list_row_style)
|
||||
.width(Fill)
|
||||
@@ -817,7 +820,7 @@ fn header_menu(side: DockSide, open: bool, fill_on_current_layer: bool) -> Eleme
|
||||
item("Create Layer with active settings", GraphicAttributesMsg::CreateLayer),
|
||||
container(
|
||||
iced::widget::checkbox(fill_on_current_layer)
|
||||
.label("Create fills on the current layer")
|
||||
.label(crate::t!("Create fills on the current layer"))
|
||||
.on_toggle(|_| msg(GraphicAttributesMsg::ToggleFillOnCurrentLayer))
|
||||
.size(12)
|
||||
.text_size(11),
|
||||
@@ -952,7 +955,7 @@ fn preview_row<'a>(
|
||||
preview: Element<'a, Message>,
|
||||
editor_open: bool,
|
||||
toggle: GraphicAttributesMsg,
|
||||
tooltip_label: &'static str,
|
||||
tooltip_label: std::borrow::Cow<'static, str>,
|
||||
flyout: Option<(Element<'a, Message>, f32, Option<f32>)>,
|
||||
side: DockSide,
|
||||
) -> Element<'a, Message> {
|
||||
@@ -1054,8 +1057,8 @@ fn linetype_row<'a>(
|
||||
open_menu,
|
||||
COMPACT_BUTTON_HEIGHT,
|
||||
vec![
|
||||
("ByLayer", msg(GraphicAttributesMsg::LineLinetype("ByLayer".into()))),
|
||||
("ByBlock", msg(GraphicAttributesMsg::LineLinetype("ByBlock".into()))),
|
||||
("ByLayer".into(), msg(GraphicAttributesMsg::LineLinetype("ByLayer".into()))),
|
||||
("ByBlock".into(), msg(GraphicAttributesMsg::LineLinetype("ByBlock".into()))),
|
||||
],
|
||||
);
|
||||
row![crate::ui::wide_menu::wide_menu(combo, 220.0), more]
|
||||
@@ -1166,8 +1169,8 @@ fn lineweight_field<'a>(
|
||||
open_menu,
|
||||
COMPACT_BUTTON_HEIGHT,
|
||||
vec![
|
||||
("ByLayer", msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByLayer))),
|
||||
("ByBlock", msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByBlock))),
|
||||
("ByLayer".into(), msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByLayer))),
|
||||
("ByBlock".into(), msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByBlock))),
|
||||
],
|
||||
);
|
||||
row![
|
||||
@@ -1324,11 +1327,13 @@ pub fn view<'a>(
|
||||
&editor.search,
|
||||
editor.focus,
|
||||
hatch.map_or("", |hatch| hatch.pattern.name.as_str()),
|
||||
"graphic-hatch-pattern-search",
|
||||
|search| msg(GraphicAttributesMsg::HatchPatternSearch(search)),
|
||||
msg(GraphicAttributesMsg::HatchPatternConfirm),
|
||||
|index| msg(GraphicAttributesMsg::HatchPatternFocus(index)),
|
||||
|name| msg(GraphicAttributesMsg::HatchPattern(name)),
|
||||
crate::ui::properties::PatternPickerMessages {
|
||||
search_id: "graphic-hatch-pattern-search",
|
||||
on_search: |search| msg(GraphicAttributesMsg::HatchPatternSearch(search)),
|
||||
on_confirm: msg(GraphicAttributesMsg::HatchPatternConfirm),
|
||||
on_focus: |index| msg(GraphicAttributesMsg::HatchPatternFocus(index)),
|
||||
on_changed: |name| msg(GraphicAttributesMsg::HatchPattern(name)),
|
||||
},
|
||||
);
|
||||
let popup = container(picker)
|
||||
.padding(1)
|
||||
@@ -1352,7 +1357,7 @@ pub fn view<'a>(
|
||||
} else {
|
||||
GraphicAttributesMsg::HatchEditorOpen
|
||||
},
|
||||
"Edit hatch",
|
||||
crate::t!("Edit hatch"),
|
||||
flyout,
|
||||
side,
|
||||
));
|
||||
@@ -1390,7 +1395,7 @@ pub fn view<'a>(
|
||||
} else {
|
||||
GradientMsg::Open
|
||||
}),
|
||||
"Edit gradient",
|
||||
crate::t!("Edit gradient"),
|
||||
flyout,
|
||||
side,
|
||||
));
|
||||
|
||||
Reference in new issue
Block a user