mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-02 18:13:39 +00:00
feat(ui): edit independent rectangle corner radii with narrow-panel controls and history tests
This commit is contained in:
committed by
Claude
parent
a9fbeb0447
commit
34183af287
@@ -25,6 +25,10 @@
|
||||
Layer Vrstva
|
||||
Type Text
|
||||
Select Výběr
|
||||
Top Left Vlevo nahoře
|
||||
Top Right Vpravo nahoře
|
||||
Bottom Left Vlevo dole
|
||||
Bottom Right Vpravo dole
|
||||
Filter Filtr
|
||||
View Zobrazení
|
||||
Window Okno
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -21,6 +21,10 @@
|
||||
Layer Ebene
|
||||
Type Text
|
||||
Select Auswahl
|
||||
Top Left Oben links
|
||||
Top Right Oben rechts
|
||||
Bottom Left Unten links
|
||||
Bottom Right Unten rechts
|
||||
Filter Filter
|
||||
View Ansicht
|
||||
Window Fenster
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -17,6 +17,10 @@
|
||||
Layer Capa
|
||||
Type Texto
|
||||
Select Selección
|
||||
Top Left Superior izquierda
|
||||
Top Right Superior derecha
|
||||
Bottom Left Inferior izquierda
|
||||
Bottom Right Inferior derecha
|
||||
Filter Filtro
|
||||
View Ver
|
||||
Window Ventana
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
@@ -24,6 +24,10 @@
|
||||
Layer Calque
|
||||
Type Texte
|
||||
Select Sélection
|
||||
Top Left En haut à gauche
|
||||
Top Right En haut à droite
|
||||
Bottom Left En bas à gauche
|
||||
Bottom Right En bas à droite
|
||||
Filter Filtre
|
||||
View Affichage
|
||||
Window Fenêtre
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -17,6 +17,10 @@
|
||||
Layer Layer
|
||||
Type Teks
|
||||
Select Seleksi
|
||||
Top Left Kiri atas
|
||||
Top Right Kanan atas
|
||||
Bottom Left Kiri bawah
|
||||
Bottom Right Kanan bawah
|
||||
Filter Filter
|
||||
View Tampilan
|
||||
Window Jendela
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
@@ -17,6 +17,10 @@
|
||||
Layer Livello
|
||||
Type Testo
|
||||
Select Selezione
|
||||
Top Left In alto a sinistra
|
||||
Top Right In alto a destra
|
||||
Bottom Left In basso a sinistra
|
||||
Bottom Right In basso a destra
|
||||
Filter Filtro
|
||||
View Vista
|
||||
Window Finestra
|
||||
|
||||
|
@@ -21,6 +21,10 @@
|
||||
Layer レイヤー
|
||||
Type 文字
|
||||
Select 選択
|
||||
Top Left 左上
|
||||
Top Right 右上
|
||||
Bottom Left 左下
|
||||
Bottom Right 右下
|
||||
Filter フィルター
|
||||
View 表示
|
||||
Window ウィンドウ
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -21,6 +21,10 @@
|
||||
Layer Camada
|
||||
Type Tipo
|
||||
Select Selecionar
|
||||
Top Left Superior esquerdo
|
||||
Top Right Superior direito
|
||||
Bottom Left Inferior esquerdo
|
||||
Bottom Right Inferior direito
|
||||
Filter Filtro
|
||||
View Visualizar
|
||||
Window Janela
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -24,6 +24,10 @@
|
||||
Layer Слой
|
||||
Type Текст
|
||||
Select Выделение
|
||||
Top Left Верхний левый
|
||||
Top Right Верхний правый
|
||||
Bottom Left Нижний левый
|
||||
Bottom Right Нижний правый
|
||||
Filter Фильтр
|
||||
View Просмотр
|
||||
Window Окно
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -17,6 +17,10 @@
|
||||
Layer 图层
|
||||
Type 文字
|
||||
Select 选择
|
||||
Top Left 左上
|
||||
Top Right 右上
|
||||
Bottom Left 左下
|
||||
Bottom Right 右下
|
||||
Filter 滤镜
|
||||
View 视图
|
||||
Window 窗口
|
||||
|
||||
|
@@ -25,6 +25,10 @@
|
||||
Layer 圖層
|
||||
Type 文字
|
||||
Select 選取
|
||||
Top Left 左上
|
||||
Top Right 右上
|
||||
Bottom Left 左下
|
||||
Bottom Right 右下
|
||||
Filter 濾鏡
|
||||
View 檢視
|
||||
Window 視窗
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -602,6 +602,16 @@ fn swatch(ui: &mut egui::Ui, fill: Option<&photocraft_doc::Fill>, tip: &str) ->
|
||||
out
|
||||
}
|
||||
|
||||
/// One corner of the live rectangle, preserving the other three radii.
|
||||
/// The engine takes the full [top-left, top-right, bottom-right, bottom-left] array.
|
||||
fn corner_radii_patch(radii: [f64; 4], corner: usize, radius: f32) -> Value {
|
||||
let mut next = radii;
|
||||
if let Some(r) = next.get_mut(corner) {
|
||||
*r = f64::from(radius.max(0.0));
|
||||
}
|
||||
json!({"radii": next})
|
||||
}
|
||||
|
||||
/// Properties panel for a shape layer: Appearance (fill, stroke) and live shape geometry.
|
||||
pub fn shape_properties(app: &mut PhotocraftApp, ui: &mut egui::Ui, id: photocraft_doc::LayerId) {
|
||||
let Some(sh) = app.session.active().and_then(|s| s.doc.layer(id)).and_then(|l| match &l.content {
|
||||
@@ -669,28 +679,49 @@ pub fn shape_properties(app: &mut PhotocraftApp, ui: &mut egui::Ui, id: photocra
|
||||
}
|
||||
photocraft_doc::vector::LiveShape::Line { .. } => {}
|
||||
}
|
||||
ui.horizontal(|ui| match live {
|
||||
ui.vertical(|ui| match live {
|
||||
photocraft_doc::vector::LiveShape::Rect { radii, .. } => {
|
||||
let mut r = radii[0] as f32;
|
||||
if num(ui, tl!("Corner radius"), &mut r, 0.0..=100000.0, "px") {
|
||||
edit = Some(json!({"radii": r, "coalesce": key("radius")}));
|
||||
// Keep the uniform workflow available after independent corner edits too.
|
||||
ui.horizontal(|ui| {
|
||||
let mut all = radii[0] as f32;
|
||||
if num(ui, tl!("Corner radius"), &mut all, 0.0..=100000.0, "px") {
|
||||
edit = Some(json!({"radii": all, "coalesce": key("radius-all")}));
|
||||
}
|
||||
});
|
||||
// Separate rows also fit the narrow Properties dock. The labels use the
|
||||
// localized corner names, rather than untranslated abbreviations.
|
||||
for (index, label) in [(0, tl!("Top Left")), (1, tl!("Top Right")), (3, tl!("Bottom Left")), (2, tl!("Bottom Right"))] {
|
||||
ui.horizontal(|ui| {
|
||||
let mut radius = radii[index] as f32;
|
||||
if num(ui, label, &mut radius, 0.0..=100000.0, "px") {
|
||||
let mut patch = corner_radii_patch(*radii, index, radius);
|
||||
patch["coalesce"] = json!(key(&format!("radius-{index}")));
|
||||
edit = Some(patch);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
photocraft_doc::vector::LiveShape::Polygon { sides, star_ratio, .. } => {
|
||||
let mut n = *sides as f32;
|
||||
if num(ui, tl!("Sides"), &mut n, 3.0..=100.0, "") {
|
||||
edit = Some(json!({"sides": n.round() as u32, "coalesce": key("sides")}));
|
||||
}
|
||||
let mut sr = (*star_ratio * 100.0) as f32;
|
||||
if num(ui, tl!("Star ratio"), &mut sr, 1.0..=100.0, "%") {
|
||||
edit = Some(json!({"starRatio": sr as f64 / 100.0, "coalesce": key("star")}));
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let mut n = *sides as f32;
|
||||
if num(ui, tl!("Sides"), &mut n, 3.0..=100.0, "") {
|
||||
edit = Some(json!({"sides": n.round() as u32, "coalesce": key("sides")}));
|
||||
}
|
||||
let mut sr = (*star_ratio * 100.0) as f32;
|
||||
if num(ui, tl!("Star ratio"), &mut sr, 1.0..=100.0, "%") {
|
||||
edit = Some(json!({"starRatio": sr as f64 / 100.0, "coalesce": key("star")}));
|
||||
}
|
||||
})
|
||||
.inner
|
||||
}
|
||||
photocraft_doc::vector::LiveShape::Line { weight, .. } => {
|
||||
let mut w = *weight as f32;
|
||||
if num(ui, tl!("Weight"), &mut w, 1.0..=10000.0, "px") {
|
||||
edit = Some(json!({"weight": w, "coalesce": key("weight")}));
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let mut w = *weight as f32;
|
||||
if num(ui, tl!("Weight"), &mut w, 1.0..=10000.0, "px") {
|
||||
edit = Some(json!({"weight": w, "coalesce": key("weight")}));
|
||||
}
|
||||
})
|
||||
.inner
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
@@ -1200,6 +1231,51 @@ mod tests {
|
||||
assert!(!legacy.dragging && !legacy.adjusting_last);
|
||||
}
|
||||
|
||||
/// #1520: changing one corner through the Properties command must keep the other three
|
||||
/// independent, remain a live rectangle, and undo/redo as one geometry edit.
|
||||
#[test]
|
||||
fn live_rectangle_corner_radius_edit_preserves_other_corners_and_history() {
|
||||
let mut app = app();
|
||||
let created = app.run("shape.create", json!({"kind": "roundedRect", "rect": [10, 10, 150, 100], "radii": [5, 10, 15, 20]})).unwrap();
|
||||
let id = created["layer"].as_u64().unwrap();
|
||||
let get_radii = |app: &PhotocraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
let shape = st.doc.layer(photocraft_doc::LayerId(id)).unwrap();
|
||||
match &shape.content {
|
||||
LayerContent::Shape(sh) => match &sh.live {
|
||||
Some(photocraft_doc::vector::LiveShape::Rect { radii, .. }) => *radii,
|
||||
_ => panic!("rectangle must remain a live shape"),
|
||||
},
|
||||
_ => panic!("expected shape layer"),
|
||||
}
|
||||
};
|
||||
assert_eq!(get_radii(&app), [5.0, 10.0, 15.0, 20.0]);
|
||||
let mut patch = corner_radii_patch(get_radii(&app), 1, 30.0);
|
||||
patch["layer"] = json!(id);
|
||||
app.run("shape.edit", patch).unwrap();
|
||||
assert_eq!(get_radii(&app), [5.0, 30.0, 15.0, 20.0]);
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(get_radii(&app), [5.0, 10.0, 15.0, 20.0]);
|
||||
app.run("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(get_radii(&app), [5.0, 30.0, 15.0, 20.0]);
|
||||
for corner in 0..4 {
|
||||
let before = get_radii(&app);
|
||||
let radius = 35.0 + corner as f32;
|
||||
let mut expected = before;
|
||||
expected[corner] = f64::from(radius);
|
||||
let mut patch = corner_radii_patch(before, corner, radius);
|
||||
patch["layer"] = json!(id);
|
||||
app.run("shape.edit", patch).unwrap();
|
||||
assert_eq!(get_radii(&app), expected);
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(get_radii(&app), before);
|
||||
app.run("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(get_radii(&app), expected);
|
||||
}
|
||||
app.run("shape.edit", json!({"layer": id, "radii": 12})).unwrap();
|
||||
assert_eq!(get_radii(&app), [12.0; 4]);
|
||||
}
|
||||
|
||||
/// #534: dragging in a shape's fill picker, opened from the Properties panel at the right edge
|
||||
/// of the window, keeps the picker in place (it flipped from side to side as its width
|
||||
/// followed the colour readouts, which moved it under the pointer).
|
||||
|
||||
Reference in New Issue
Block a user