Merge pull request #3975 from abc2cb/codex/fill-mask-preview-20261012

Fill layers: match masks in creation and Pattern Fill preview (part 3/3)
This commit is contained in:
aiongg authored and GitHub committed 2026-10-21 17:53:41 -04:00
commit 43b1488325
27 files changed
+186 -14

No files matched your search

+11 -5
View File
@@ -419,7 +419,7 @@ pub(crate) fn layer_panel_option(s: &Session, key: &str, fallback: bool) -> bool
/// mask, which makes a Solid Color fill a shape (#1419); without one, the selection becomes its
/// layer mask ([`selection_mask`]); without either, a white mask reveals the whole layer (#1768).
/// A path that isn't there is an error.
pub(crate) fn mask_new_layer(doc: &Document, l: &mut Layer, p: &Value, cmd: &str) -> Result<()> {
pub(crate) fn mask_new_layer(doc: &Document, l: &mut Layer, p: &Value, cmd: &str, default_mask: bool) -> Result<()> {
let path = match p.get("path") {
None | Some(Value::Null) => None,
Some(Value::String(n)) if crate::vector_cmds::is_work(Some(n)) => Some(doc.work_path.clone().ok_or_else(|| bad(cmd, "no work path"))?),
@@ -430,7 +430,9 @@ pub(crate) fn mask_new_layer(doc: &Document, l: &mut Layer, p: &Value, cmd: &str
};
match path {
Some(path) => l.vector_mask = Some(photocraft_doc::VectorMask::new(path)),
None => l.mask = Some(mask_at_depth(selection_mask(doc)?.unwrap_or_else(LayerMask::reveal_all), doc.depth)?),
None => {
l.mask = selection_mask(doc)?.or_else(|| default_mask.then(LayerMask::reveal_all)).map(|mask| mask_at_depth(mask, doc.depth)).transpose()?;
}
}
Ok(())
}
@@ -439,12 +441,13 @@ pub(crate) fn mask_new_layer(doc: &Document, l: &mut Layer, p: &Value, cmd: &str
pub(crate) const NEW_LAYER_PATH: &str =
r##""path":"work"|saved path name|{…path}? (the active path: becomes the vector mask; else the selection is the layer mask, or white with no selection)"##;
/// Add an adjustment layer above the active one; it takes the mask `mask_new_layer` builds from `p` (a path, the selection, or the default white mask).
fn new_adjustment(s: &mut Session, adj: Adjustment, p: &Value, cmd: &str) -> Result<Value> {
let label = format!("New {} Layer", adj.label());
let name = adj.label().to_string();
let id = s.edit(&label, |doc, active| {
let mut l = Layer::new(doc.next_layer_name(&name), LayerContent::Adjustment(adj));
mask_new_layer(doc, &mut l, p, cmd)?;
mask_new_layer(doc, &mut l, p, cmd, true)?;
let id = doc.insert_above(*active, l);
*active = Some(id);
Ok(id)
@@ -452,6 +455,7 @@ fn new_adjustment(s: &mut Session, adj: Adjustment, p: &Value, cmd: &str) -> Res
Ok(json!({ "layer": id.0 }))
}
/// Apply `adj` in place to the targeted layer mask, the targeted alpha channel or Quick Mask, or else the active layer's pixels, as one history step named `label`.
fn destructive_adjust(s: &mut Session, label: &str, adj: Adjustment, p: &Value) -> Result<Value> {
if is_mask_target(p) {
// The targeted layer mask (#780): ⌘I inverts it, as in Photoshop.
@@ -1035,10 +1039,11 @@ fn build() -> Vec<CommandSpec> {
has_doc,
|s, p| {
let c = color_param("layer.newFillLayer.solidColor", p, "color", s.tools.foreground)?;
let default_mask = layer_panel_option(s, "useDefaultMasksOnFillLayers", true);
let id = s.edit("New Color Fill Layer", |doc, active| {
let fill = Fill::Solid(Color::rgba(c[0], c[1], c[2], c[3])).in_mode(doc.mode);
let mut l = Layer::new(doc.next_layer_name("Color Fill"), LayerContent::Fill(fill));
mask_new_layer(doc, &mut l, p, "layer.newFillLayer.solidColor")?;
mask_new_layer(doc, &mut l, p, "layer.newFillLayer.solidColor", default_mask)?;
let id = doc.insert_above(*active, l);
*active = Some(id);
Ok(id)
@@ -1059,6 +1064,7 @@ fn build() -> Vec<CommandSpec> {
let angle = crate::gradient_fill_cmds::angle_param("layer.newFillLayer.gradient", p, 90.0)?;
let style = crate::layer_style::gradient_style(p.get("style").and_then(Value::as_str).unwrap_or("linear"));
let reverse = p.get("reverse").and_then(Value::as_bool).unwrap_or(false);
let default_mask = layer_panel_option(s, "useDefaultMasksOnFillLayers", true);
let id = s.edit("New Gradient Fill Layer", |doc, active| {
let fill = Fill::gradient(
vec![(0.0, Color::rgba(a[0], a[1], a[2], a[3])), (1.0, Color::rgba(b[0], b[1], b[2], b[3]))],
@@ -1069,7 +1075,7 @@ fn build() -> Vec<CommandSpec> {
)
.in_mode(doc.mode);
let mut l = Layer::new(doc.next_layer_name("Gradient Fill"), LayerContent::Fill(fill));
mask_new_layer(doc, &mut l, p, "layer.newFillLayer.gradient")?;
mask_new_layer(doc, &mut l, p, "layer.newFillLayer.gradient", default_mask)?;
let id = doc.insert_above(*active, l);
*active = Some(id);
Ok(id)
+64
View File
@@ -3123,6 +3123,70 @@ mod layers_panel_parity_tests {
assert!(s.active().unwrap().fx_collapsed.contains(&target));
}
/// New Solid Color and Gradient fill layers get the default mask only while Use Default Masks on Fill Layers is on.
#[test]
fn fill_layers_respect_use_default_masks_option() {
let (mut s, _) = new_session();
option(&mut s, "useDefaultMasksOnFillLayers", false);
let new = s.execute("layer.newFillLayer.solidColor", json!({"color": "#112233"})).unwrap();
let id = LayerId(new["layer"].as_u64().unwrap());
assert!(s.active().unwrap().doc.layer(id).unwrap().mask.is_none());
option(&mut s, "useDefaultMasksOnFillLayers", true);
let new = s.execute("layer.newFillLayer.gradient", json!({})).unwrap();
let id = LayerId(new["layer"].as_u64().unwrap());
assert!(s.active().unwrap().doc.layer(id).unwrap().mask.is_some());
}
/// With the option off only the default reveal-all mask goes away: an active selection still
/// becomes the layer mask and an explicit path still becomes the vector mask (path first).
#[test]
fn fill_layers_without_default_masks_keep_selection_and_explicit_path_masks() {
const KINDS: [(&str, &str); 3] = [
("layer.newFillLayer.solidColor", r##"{"color": "#112233"}"##),
("layer.newFillLayer.gradient", "{}"),
("layer.newFillLayer.pattern", r#"{"pattern": "Checkerboard"}"#),
];
let path = json!({"subpaths": [{"closed": true, "knots": [[2, 2], [10, 2], [10, 10], [2, 10]]}]});
let (mut s, _) = new_session();
option(&mut s, "useDefaultMasksOnFillLayers", false);
// Creates the fill layer; returns its raster mask and whether it has a vector mask.
let create = |s: &mut Session, cmd: &str, params: &str, path: Option<&Value>| {
let mut p: Value = serde_json::from_str(params).unwrap();
if let Some(path) = path {
p["path"] = path.clone();
}
let id = LayerId(s.execute(cmd, p).unwrap()["layer"].as_u64().unwrap());
let l = s.active().unwrap().doc.layer(id).unwrap();
(l.mask.clone(), l.vector_mask.is_some())
};
for (cmd, params) in KINDS {
let (mask, vector) = create(&mut s, cmd, params, None);
assert!(mask.is_none() && !vector, "{cmd}: no selection, no path: no mask at all");
}
s.execute("select.rect", json!({"x": 4, "y": 4, "width": 8, "height": 6})).unwrap();
for (cmd, params) in KINDS {
let (mask, vector) = create(&mut s, cmd, params, None);
let mask = mask.unwrap_or_else(|| panic!("{cmd}: the selection still becomes the layer mask"));
assert!(!vector, "{cmd}");
assert!(mask.surface.pixel(6, 6)[0] > 0.99, "{cmd}: inside the selection reveals");
assert!(mask.surface.pixel(20, 12)[0] < 0.01, "{cmd}: outside the selection hides");
}
// An explicit path wins over the selection, as with the option on.
for selected in [true, false] {
if !selected {
s.execute("select.deselect", json!({})).unwrap();
}
for (cmd, params) in KINDS {
let (mask, vector) = create(&mut s, cmd, params, Some(&path));
assert!(vector, "{cmd}: the explicit path becomes the vector mask (selection: {selected})");
assert!(mask.is_none(), "{cmd}: no raster mask next to the vector mask (selection: {selected})");
}
}
}
/// Whether new effects start expanded is view state: it adds no history step, and editing an existing effect keeps its disclosure.
#[test]
fn expand_new_effects_is_view_state_and_keeps_edit_history_clean() {
+12 -3
View File
@@ -317,23 +317,32 @@ pub(crate) fn placement(cmd: &str, p: &Value) -> Result<(f32, f32, bool, (f32, f
/// The edit of `layer.newFillLayer.pattern`: a Pattern Fill layer of `pat` placed as `p` asks,
/// above `active`. The Pattern Fill dialog previews it on a copy of the document.
pub(crate) fn insert_fill_layer(doc: &mut Document, active: &mut Option<photocraft_doc::LayerId>, pat: &Pattern, p: &Value) -> Result<photocraft_doc::LayerId> {
pub(crate) fn insert_fill_layer(
doc: &mut Document,
active: &mut Option<photocraft_doc::LayerId>,
pat: &Pattern,
p: &Value,
default_mask: bool,
) -> Result<photocraft_doc::LayerId> {
let (scale, angle, link, phase) = placement("layer.newFillLayer.pattern", p)?;
ensure_in_doc(doc, pat);
let fill = Fill::Pattern { name: pat.name.clone(), scale, id: pat.id.clone(), angle, link, phase };
let mut l = Layer::new(doc.next_layer_name("Pattern Fill"), LayerContent::Fill(fill));
crate::commands::mask_new_layer(doc, &mut l, p, "layer.newFillLayer.pattern")?;
crate::commands::mask_new_layer(doc, &mut l, p, "layer.newFillLayer.pattern", default_mask)?;
let id = doc.insert_above(*active, l);
*active = Some(id);
Ok(id)
}
/// `layer.newFillLayer.pattern`: a Pattern Fill layer above the active one, masked as the Layers panel's Use Default Masks on Fill Layers option says.
fn new_fill_layer(s: &mut Session, p: &Value) -> Result<Value> {
let pat = resolve_param(s, "layer.newFillLayer.pattern", p)?;
let id = s.edit("New Pattern Fill Layer", |doc, active| insert_fill_layer(doc, active, &pat, p))?;
let default_mask = crate::commands::layer_panel_option(s, "useDefaultMasksOnFillLayers", true);
let id = s.edit("New Pattern Fill Layer", |doc, active| insert_fill_layer(doc, active, &pat, p, default_mask))?;
Ok(json!({"layer": id.0, "pattern": pat.id}))
}
/// `brush.texturePattern`: use a pattern as the brush texture, converted to grey as the luminance of its composite over white.
fn brush_texture(s: &mut Session, p: &Value) -> Result<Value> {
let pat = resolve_param(s, "brush.texturePattern", p)?;
let n = pat.is_paintable().then(|| usize::try_from(pat.pixel_count()).ok()).flatten();
+37 -1
View File
@@ -124,13 +124,15 @@ pub fn preview(s: &Session, cmd: &str, p: &Value) -> Result<Document> {
CREATE => {
let pat = crate::pattern_cmds::resolve_param(s, CREATE, p)?;
let mut active = st.active_layer;
crate::pattern_cmds::insert_fill_layer(&mut doc, &mut active, &pat, p)?;
let default_mask = crate::commands::layer_panel_option(s, "useDefaultMasksOnFillLayers", true);
crate::pattern_cmds::insert_fill_layer(&mut doc, &mut active, &pat, p, default_mask)?;
}
other => return Err(EngineError::BadParams { cmd: other.into(), msg: "no Pattern Fill preview for this command".into() }),
}
Ok(doc)
}
/// Register `layer.fill.pattern.set` (the Pattern Fill dialog's command), journaled like any other edit.
pub fn specs() -> Vec<CommandSpec> {
vec![CommandSpec {
id: SET,
@@ -216,6 +218,40 @@ mod tests {
assert!(preview(&s, "edit.fill", &json!({})).is_err());
}
/// At 8, 16 and 32 bits the Pattern Fill preview and the committed layer carry the default mask only while the option is on, and creating one is one undoable step.
#[test]
fn create_preview_and_commit_honor_default_masks_option() {
for depth in [8, 16, 32] {
for default_mask in [false, true] {
let mut s = Session::new();
s.execute("file.new", json!({"width": 40, "height": 30, "depth": depth})).unwrap();
s.edit_prefs(|prefs| {
prefs.dialogs.insert("ui.layersPanelOptions".into(), json!({"useDefaultMasksOnFillLayers": default_mask}));
});
let before = s.active().unwrap().doc.clone();
let count = s.active().unwrap().history.entries().len();
let p = json!({"pattern": "Grid", "scale": 50});
let shown = preview(&s, CREATE, &p).unwrap();
assert!(Arc::ptr_eq(&before, &s.active().unwrap().doc));
assert_eq!(s.active().unwrap().history.entries().len(), count);
let previewed = shown.layers.iter().find(|l| matches!(l.content, LayerContent::Fill(Fill::Pattern { .. }))).unwrap();
assert_eq!(previewed.mask.is_some(), default_mask);
if let Some(mask) = &previewed.mask {
assert_eq!(mask.surface.format(), photocraft_color::PixelFormat::new(photocraft_color::ColorMode::Grayscale, shown.depth, false));
}
let id = LayerId(s.execute(CREATE, p).unwrap()["layer"].as_u64().unwrap());
let committed = s.active().unwrap().doc.layer(id).unwrap();
assert_eq!(committed.mask, previewed.mask);
assert_eq!(committed.content, previewed.content);
s.undo();
assert_eq!(*s.active().unwrap().doc, *before);
s.redo();
assert_eq!(s.active().unwrap().doc.layer(id).unwrap().mask.is_some(), default_mask);
}
}
}
/// Malformed or out-of-range params, and missing layers or patterns, are errors in both the command and its preview, never panics.
#[test]
fn bad_params_and_targets_are_errors_not_panics() {
let (mut s, id) = session(8);
+1
View File
@@ -2682,6 +2682,7 @@ cameraData Lens Objektiv
Show Layers Zobrazit vrstvy
Thumbnail Contents Obsah miniatur
Thumbnail Size Velikost miniatur
Use Default Masks on Fill Layers Používat výchozí masky u vrstev výplně
Expand New Effects Rozbalovat nové efekty
Add “copy” to Copied Layers and Groups Přidávat „kopie“ ke kopírovaným vrstvám a skupinám
Preview on canvas when reordering layers Zobrazovat náhled na plátně při změně pořadí vrstev
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -2713,6 +2713,7 @@ cameraData Lens Objektiv
Show Layers Ebenen einblenden
Thumbnail Contents Miniaturinhalt
Thumbnail Size Miniaturgröße
Use Default Masks on Fill Layers Standardmasken für Füllebenen verwenden
Expand New Effects Neue Effekte aufklappen
Add “copy” to Copied Layers and Groups „Kopie“ zu kopierten Ebenen und Gruppen hinzufügen
Preview on canvas when reordering layers Vorschau auf der Arbeitsfläche beim Neuanordnen von Ebenen
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3764,6 +3764,7 @@ cameraData Lens Φακός
Show Layers Εμφάνιση επιπέδων
Thumbnail Contents Περιεχόμενο μικρογραφιών
Thumbnail Size Μέγεθος μικρογραφιών
Use Default Masks on Fill Layers Χρήση προεπιλεγμένων μασκών σε επίπεδα γεμίσματος
Expand New Effects Ανάπτυξη νέων εφέ
Add “copy” to Copied Layers and Groups Προσθήκη «αντίγραφο» σε αντιγραμμένα επίπεδα και ομάδες
Preview on canvas when reordering layers Προεπισκόπηση στον καμβά κατά την αναδιάταξη επιπέδων
Can't render this file because it contains an unexpected character in line 1030 and column 18.
+1
View File
@@ -2753,6 +2753,7 @@ cameraData Lens Objetivo
Show Layers Mostrar capas
Thumbnail Contents Contenido de miniaturas
Thumbnail Size Tamaño de miniaturas
Use Default Masks on Fill Layers Usar máscaras predeterminadas en capas de relleno
Expand New Effects Expandir nuevos efectos
Add “copy” to Copied Layers and Groups Añadir «copia» a las capas y grupos duplicados
Preview on canvas when reordering layers Previsualizar en el lienzo al reordenar capas
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -3087,6 +3087,7 @@ cameraData Lens Objectif
Show Layers Afficher les calques
Thumbnail Contents Contenu des vignettes
Thumbnail Size Taille des vignettes
Use Default Masks on Fill Layers Utiliser les masques par défaut sur les calques de remplissage
Expand New Effects Développer les nouveaux effets
Add “copy” to Copied Layers and Groups Ajouter « copie » aux calques et groupes dupliqués
Preview on canvas when reordering layers Prévisualiser sur la zone de travail lors du réagencement des calques
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -4168,6 +4168,7 @@ cameraRaw Yellow Sárga
Show layer mask badges Rétegmaszk-jelvények megjelenítése
Thumbnail Contents Bélyegkép tartalma
Thumbnail Size Bélyegképméret
Use Default Masks on Fill Layers Alapértelmezett maszkok kitöltőrétegeken
Accept and Go to Conflict Elfogadás és ugrás az ütközéshez
Changes undone. Változások visszavonva.
Choose a new shortcut for this command, or leave it without one. Válasszon új billentyűparancsot ehhez a parancshoz, vagy hagyja nélküle.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3074,6 +3074,7 @@ cameraData Lens Lensa
Show Layers Tampilkan Lapisan
Thumbnail Contents Isi Gambar Mini
Thumbnail Size Ukuran Gambar Mini
Use Default Masks on Fill Layers Gunakan Masker Default pada Lapisan Isian
Expand New Effects Perluas Efek Baru
Add “copy” to Copied Layers and Groups Tambahkan “salinan” ke Lapisan dan Grup yang Disalin
Preview on canvas when reordering layers Pratinjau pada kanvas saat mengurutkan ulang lapisan
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -2738,6 +2738,7 @@ cameraData Lens Obiettivo
Show Layers Mostra livelli
Thumbnail Contents Contenuto miniature
Thumbnail Size Dimensione miniature
Use Default Masks on Fill Layers Usa maschere predefinite sui livelli di riempimento
Expand New Effects Espandi nuovi effetti
Add “copy” to Copied Layers and Groups Aggiungi «copia» ai livelli e ai gruppi duplicati
Preview on canvas when reordering layers Anteprima sulla tela durante il riordino dei livelli
Can't render this file because it contains an unexpected character in line 2658 and column 53.
+1
View File
@@ -3679,6 +3679,7 @@ cameraData Lens レンズ
Show Layers レイヤーを表示
Thumbnail Contents サムネールの内容
Thumbnail Size サムネールのサイズ
Use Default Masks on Fill Layers 塗りつぶしレイヤーにデフォルトのマスクを使用
Expand New Effects 新しい効果を展開
Add “copy” to Copied Layers and Groups コピーしたレイヤーとグループの名前に「コピー」を追加
Preview on canvas when reordering layers レイヤーの並べ替え時にカンバスでプレビュー
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3257,6 +3257,7 @@ cameraData Lens 렌즈
Show Layers 레이어 표시
Thumbnail Contents 축소판 내용
Thumbnail Size 축소판 크기
Use Default Masks on Fill Layers 칠 레이어에 기본 마스크 사용
Expand New Effects 새 효과 펼치기
Add “copy” to Copied Layers and Groups 복사한 레이어 및 그룹에 “복사” 추가
Preview on canvas when reordering layers 레이어 순서 변경 시 캔버스에서 미리보기
Can't render this file because it contains an unexpected character in line 3172 and column 53.
+1
View File
@@ -3823,6 +3823,7 @@ cameraData Lens Lens
Show Layers Lagen tonen
Thumbnail Contents Inhoud van miniaturen
Thumbnail Size Grootte van miniaturen
Use Default Masks on Fill Layers Standaardmaskers gebruiken op opvullagen
Expand New Effects Nieuwe effecten uitvouwen
Add “copy” to Copied Layers and Groups „kopie” toevoegen aan gekopieerde lagen en groepen
Preview on canvas when reordering layers Voorvertoning op canvas bij herschikken van lagen
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -3681,6 +3681,7 @@ cameraData Lens Obiektyw
Show Layers Pokaż warstwy
Thumbnail Contents Zawartość miniaturek
Thumbnail Size Rozmiar miniaturek
Use Default Masks on Fill Layers Użyj domyślnych masek na warstwach wypełnienia
Expand New Effects Rozwijaj nowe efekty
Add “copy” to Copied Layers and Groups Dodawaj „kopia” do skopiowanych warstw i grup
Preview on canvas when reordering layers Podgląd na obszarze roboczym podczas zmiany kolejności warstw
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3090,6 +3090,7 @@ cameraData Lens Lente
Show Layers Mostrar camadas
Thumbnail Contents Conteúdo das miniaturas
Thumbnail Size Tamanho das miniaturas
Use Default Masks on Fill Layers Usar máscaras padrão nas camadas de preenchimento
Expand New Effects Expandir novos efeitos
Add “copy” to Copied Layers and Groups Adicionar “cópia” às camadas e grupos copiados
Preview on canvas when reordering layers Pré-visualizar na tela ao reordenar camadas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3011,6 +3011,7 @@ cameraData Lens Объектив
Show Layers Показать слои
Thumbnail Contents Содержимое миниатюр
Thumbnail Size Размер миниатюр
Use Default Masks on Fill Layers Использовать маски по умолчанию для слоёв-заливок
Expand New Effects Раскрывать новые эффекты
Add “copy” to Copied Layers and Groups Добавлять «копия» к дублированным слоям и группам
Preview on canvas when reordering layers Предпросмотр на холсте при изменении порядка слоёв
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -4322,6 +4322,7 @@ shape Appearance Izgled
Show layer mask badges Prikaži oznake maske sloja
Thumbnail Contents Sadržaj sličice
Thumbnail Size Veličina sličice
Use Default Masks on Fill Layers Koristi podrazumevane maske na slojevima popune
Accept and Go to Conflict Prihvati i idi na sukob
Changes undone. Izmene su poništene.
Choose a new shortcut for this command, or leave it without one. Izaberite novu prečicu za ovu komandu ili je ostavite bez prečice.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3979,6 +3979,7 @@ cameraData Lens Lens
Show layer mask badges Katman maskesi rozetlerini göster
Thumbnail Contents Küçük Resim İçeriği
Thumbnail Size Küçük Resim Boyutu
Use Default Masks on Fill Layers Dolgu Katmanlarında Varsayılan Maskeleri Kullan
Accept and Go to Conflict Kabul Et ve Çakışmaya Git
Changes undone. Değişiklikler geri alındı.
Choose a new shortcut for this command, or leave it without one. Bu komut için yeni bir kısayol seçin veya kısayolsuz bırakın.
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -3755,6 +3755,7 @@ cameraData Lens Об'єктив
Show Layers Показати шари
Thumbnail Contents Вміст мініатюр
Thumbnail Size Розмір мініатюр
Use Default Masks on Fill Layers Використовувати типові маски для шарів заливки
Expand New Effects Розгортати нові ефекти
Add “copy” to Copied Layers and Groups Додавати «копія» до скопійованих шарів і груп
Preview on canvas when reordering layers Попередній перегляд на полотні під час зміни порядку шарів
Can't render this file because it contains an unexpected character in line 1393 and column 18.
+1
View File
@@ -3740,6 +3740,7 @@ curves Smooth Làm mượt
Show layer mask badges Hiện huy hiệu mặt nạ lớp
Thumbnail Contents Nội dung hình thu nhỏ
Thumbnail Size Kích thước hình thu nhỏ
Use Default Masks on Fill Layers Dùng mặt nạ mặc định cho lớp tô
Accept and Go to Conflict Chấp nhận và đến xung đột
Changes undone. Đã hoàn tác thay đổi.
Choose a new shortcut for this command, or leave it without one. Chọn phím tắt mới cho lệnh này, hoặc để lệnh không có phím tắt.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -2690,6 +2690,7 @@ cameraData Lens 镜头
Show Layers 显示图层
Thumbnail Contents 缩略图内容
Thumbnail Size 缩略图大小
Use Default Masks on Fill Layers 在填充图层上使用默认蒙版
Expand New Effects 展开新效果
Add “copy” to Copied Layers and Groups 为复制的图层和组添加“副本”
Preview on canvas when reordering layers 重新排列图层时在画布上预览
Can't render this file because it contains an unexpected character in line 2606 and column 53.
+1
View File
@@ -2660,6 +2660,7 @@ cameraData Lens 鏡頭
Show Layers 顯示圖層
Thumbnail Contents 縮圖內容
Thumbnail Size 縮圖大小
Use Default Masks on Fill Layers 在填色圖層使用預設遮色片
Expand New Effects 展開新效果
Add “copy” to Copied Layers and Groups 為複製的圖層和群組加上「副本」
Preview on canvas when reordering layers 重新排列圖層時在畫布上預覽
Can't render this file because it contains an unexpected character in line 6 and column 47.
+24
View File
@@ -238,6 +238,8 @@ fn options_body(ui: &mut Ui, draft: &mut LayersPanelOptions) {
ui.add_space(4.0);
});
ui.add_space(9.0);
ui.checkbox(&mut draft.use_default_masks_on_fill_layers, tl!("Use Default Masks on Fill Layers"));
ui.add_space(3.0);
ui.checkbox(&mut draft.expand_new_effects, tl!("Expand New Effects"));
ui.add_space(3.0);
ui.checkbox(&mut draft.add_copy_to_copied_layers_and_groups, tl!("Add “copy” to Copied Layers and Groups"));
@@ -346,6 +348,7 @@ mod tests {
assert_eq!(options.thumbnail_size, LayerThumbnailSize::Medium);
assert_eq!(options.thumbnail_contents, LayerThumbnailContents::EntireDocument);
assert!(options.show_filters);
assert!(options.use_default_masks_on_fill_layers);
assert!(options.expand_new_effects);
assert!(options.add_copy_to_copied_layers_and_groups);
assert!(options.preview_on_canvas_when_reordering_layers);
@@ -385,6 +388,26 @@ mod tests {
assert!(restored.layers_panel_dialog.is_none());
}
/// With the option off a new fill layer has no mask and the mask is not targeted; with it on the new layer's mask is targeted.
#[test]
fn disabling_default_fill_masks_never_targets_an_absent_mask() {
let mut session = photocraft_engine::Session::new();
session.execute("file.new", json!({"width": 32, "height": 24})).unwrap();
let mut app = PhotocraftApp::new(session, crate::Services::default());
app.ui.layers_panel_options.use_default_masks_on_fill_layers = false;
persist_options(&mut app);
app.run("layer.newFillLayer.solidColor", json!({"color": "#778899"})).unwrap();
let layer = app.session.active().and_then(|st| st.active_layer.and_then(|id| st.doc.layer(id))).unwrap();
assert!(layer.mask.is_none());
assert!(!app.ui.mask_target && !app.ui.vector_mask_target);
app.ui.layers_panel_options.use_default_masks_on_fill_layers = true;
persist_options(&mut app);
app.run("layer.newFillLayer.gradient", json!({})).unwrap();
let layer = app.session.active().and_then(|st| st.active_layer.and_then(|id| st.doc.layer(id))).unwrap();
assert!(layer.mask.is_some());
assert!(app.ui.mask_target);
}
/// Every option, including the mask badges, goes to the preferences and comes back from them.
#[test]
fn prefs_options_save_and_restore_all_switches() {
@@ -395,6 +418,7 @@ mod tests {
thumbnail_size: LayerThumbnailSize::Large,
thumbnail_contents: LayerThumbnailContents::EntireDocument,
show_filters: false,
use_default_masks_on_fill_layers: false,
expand_new_effects: false,
add_copy_to_copied_layers_and_groups: false,
preview_on_canvas_when_reordering_layers: false,
+16 -5
View File
@@ -1247,13 +1247,24 @@ impl PhotocraftApp {
jobs_ui::run(self, id, params)
};
let creates_adjustment_or_fill = id.starts_with("layer.newAdjustmentLayer.") || id.starts_with("layer.newFillLayer.");
if r.is_ok() && (ADDS_LAYER_MASK.contains(&id) || creates_adjustment_or_fill) {
// Adding a layer mask targets it, as in Photoshop (#2166).
if r.is_ok() && ADDS_LAYER_MASK.contains(&id) {
// Adding an explicit mask targets it, as in Photoshop (#2166).
self.ui.mask_target = true;
self.ui.vector_mask_target = false;
// Properties still shows the new adjustment or fill layer's settings, not its mask,
// as in Photoshop (#3369): clicking the mask thumbnail switches it to the mask.
self.ui.properties_layer = if creates_adjustment_or_fill { self.session.active().and_then(|st| st.active_layer).map(|l| l.0) } else { None };
self.ui.properties_layer = None;
} else if r.is_ok() && creates_adjustment_or_fill {
// Panel Options may suppress a fill layer's default mask. Never target a
// nonexistent mask; an active path creates a vector mask instead.
let masks = self
.session
.active()
.and_then(|st| st.active_layer.and_then(|id| st.doc.layer(id)))
.map(|layer| (layer.mask.is_some(), layer.vector_mask.is_some()))
.unwrap_or((false, false));
self.ui.mask_target = masks.0;
self.ui.vector_mask_target = !masks.0 && masks.1;
// Keep Properties on the new layer contents, as on main (#3369).
self.ui.properties_layer = self.session.active().and_then(|st| st.active_layer).map(|l| l.0);
}
if r.is_ok() && creates_adjustment_or_fill {
// Properties comes to the front, shown and expanded, so the new layer's settings can
+2
View File
@@ -32,6 +32,7 @@ pub struct LayersPanelOptions {
#[serde(skip)]
pub thumbnail_contents: LayerThumbnailContents,
pub show_filters: bool,
pub use_default_masks_on_fill_layers: bool,
pub expand_new_effects: bool,
pub add_copy_to_copied_layers_and_groups: bool,
pub preview_on_canvas_when_reordering_layers: bool,
@@ -45,6 +46,7 @@ impl LayersPanelOptions {
thumbnail_size: LayerThumbnailSize::Medium,
thumbnail_contents: LayerThumbnailContents::EntireDocument,
show_filters: true,
use_default_masks_on_fill_layers: true,
expand_new_effects: true,
add_copy_to_copied_layers_and_groups: true,
preview_on_canvas_when_reordering_layers: true,