use darkly::engine::types::{LayerInfo, StrokeOp};
use darkly::engine::DarklyEngine;
use darkly::gpu::context::GpuContext;
use darkly::gpu::test_utils::test_device;
use darkly::layer::LayerId;
fn test_engine(width: u32, height: u32) -> DarklyEngine {
let (device, queue) = test_device();
let gpu = GpuContext::new_headless(device, queue);
DarklyEngine::new(gpu, width, height)
}
fn paint_dot(engine: &mut DarklyEngine, layer_id: LayerId, x: f32, y: f32) {
engine.begin_stroke(layer_id);
engine.stroke_to(StrokeOp::BrushStroke {
x,
y,
pressure: 1.0,
x_tilt: 0.0,
y_tilt: 0.0,
rotation: 0.0,
tangential_pressure: 0.0,
time_ms: 0.0,
cr: 1.0,
cg: 0.0,
cb: 0.0,
ca: 1.0,
});
engine.end_stroke();
engine.render(0.0);
}
fn drain_rich_copy(engine: &mut DarklyEngine) -> String {
engine.test_flush_readbacks();
engine
.poll_copy_rich_result()
.expect("rich copy never produced a result")
}
fn raster_props(engine: &DarklyEngine, id: LayerId) -> (String, f32, String, usize) {
let id_f = id.to_ffi() as f64;
let tree = engine.layer_tree();
for info in tree {
if let LayerInfo::Raster {
id: lid,
name,
opacity,
blend_mode,
modifiers: filters,
..
} = info
{
if (lid - id_f).abs() < 0.5 {
return (name, opacity, blend_mode.to_string(), filters.len());
}
}
}
panic!("layer {id_f} not found in tree");
}
#[test]
fn rich_copy_paste_preserves_blend_mode_opacity_and_pixels() {
let (w, h) = (64u32, 64u32);
let mut source = test_engine(w, h);
let layer = source.add_raster_layer(None);
paint_dot(&mut source, layer, 32.0, 32.0);
source.set_opacity(layer, 0.42);
source.set_blend_mode(layer, "multiply");
source.set_layer_name(layer, "Source layer");
source.copy_layer_rich(layer);
let json = drain_rich_copy(&mut source);
assert!(json.contains("\"blend_mode\":\"multiply\""));
assert!(json.contains("\"name\":\"Source layer\""));
let mut sink = test_engine(w, h);
let pasted_id = sink
.paste_layer_rich(&json, None)
.expect("paste_layer_rich should succeed for a v1 payload");
let (name, opacity, blend_mode, _modifier_count) = raster_props(&sink, pasted_id);
assert_eq!(name, "Source layer");
assert!(
(opacity - 0.42).abs() < 1e-5,
"opacity should round-trip; got {opacity}"
);
assert_eq!(blend_mode, "multiply", "blend mode should round-trip");
let pasted_pixels = sink.test_readback_layer(pasted_id);
assert!(
!pasted_pixels.is_empty(),
"pasted layer should have non-empty pixel readback"
);
let nonzero = pasted_pixels.chunks_exact(4).any(|p| p[3] > 0);
assert!(
nonzero,
"pasted layer should contain at least one non-zero alpha pixel"
);
}
#[test]
fn rich_paste_records_mask_presence_v1() {
let (w, h) = (32u32, 32u32);
let mut source = test_engine(w, h);
let layer = source.add_raster_layer(None);
paint_dot(&mut source, layer, 16.0, 16.0);
source.add_mask(layer);
source.copy_layer_rich(layer);
let json = drain_rich_copy(&mut source);
assert!(
json.contains("\"mask\":{"),
"mask metadata should be in JSON"
);
let mut sink = test_engine(w, h);
let pasted_id = sink.paste_layer_rich(&json, None).expect("paste succeeds");
let (_, _, _, modifier_count) = raster_props(&sink, pasted_id);
assert!(
modifier_count > 0,
"pasted layer should have a filter (the mask)"
);
}
#[test]
fn rich_paste_rejects_malformed_json() {
let mut sink = test_engine(32, 32);
assert_eq!(sink.paste_layer_rich("not json", None), None);
assert_eq!(sink.paste_layer_rich("{}", None), None);
}