Hand the drag ghost to the renderer through a file, so it draws
The bitmap under the cursor was a solid red rectangle. Slint's drag overlay uploads the image as a texture, draws it and drops the texture in one call; with the wgpu FemtoVG renderer the drop is immediate and the draw is deferred to the flush, so the frame binds femtovg's placeholder — which is red. An image with a cache key survives in the texture cache until after the flush, and only a path gives one. So the composite goes to the data directory's scratch as a PNG and comes back through load_from_path; one file per drag, removed when the drag ends. A workaround for Slint 1.17.1, written up as one beside the code.
This commit is contained in:
@@ -1006,6 +1006,97 @@ fn compose_drag_image(thumbs: &[slint::Image]) -> slint::Image {
|
||||
slint::Image::from_rgba8_premultiplied(canvas)
|
||||
}
|
||||
|
||||
/// Hand the composite to the renderer by way of a file.
|
||||
///
|
||||
/// **A workaround for a renderer fault, and it should read as one.** The
|
||||
/// ghost under the cursor is drawn by Slint's own drag overlay, which
|
||||
/// uploads the image as a texture, draws it, and drops the texture in the
|
||||
/// same call. With the wgpu FemtoVG renderer that drop is immediate and
|
||||
/// the draw is deferred to the frame's flush, so by the time the frame is
|
||||
/// rendered the texture is gone and the renderer binds its placeholder
|
||||
/// instead — a solid red rectangle the size of the ghost. An image with a
|
||||
/// cache key is kept in the renderer's texture cache until after the
|
||||
/// flush; an image built from pixels has none, and only a path gives one.
|
||||
/// So the composite goes to disk as a PNG and comes back through
|
||||
/// `load_from_path`. Slint 1.17.1, `draw_image_direct` in the FemtoVG
|
||||
/// item renderer; the GL FemtoVG renderer is not affected.
|
||||
///
|
||||
/// One file per drag, named uniquely: the core caches decoded images by
|
||||
/// path, so reusing a name would show the previous drag's ghost. The file
|
||||
/// is removed when the drag ends, or when the next one begins.
|
||||
///
|
||||
/// If anything on the way fails the composite is handed over as it is,
|
||||
/// which on the affected renderer draws the placeholder — no worse than
|
||||
/// before, and a log line says why.
|
||||
fn drag_image_via_file(composite: slint::Image) -> slint::Image {
|
||||
let Some(buffer) = composite.to_rgba8() else {
|
||||
log::warn!("drag ghost: the composite has no pixels to write");
|
||||
return composite;
|
||||
};
|
||||
log::debug!("drag ghost: {}×{}", buffer.width(), buffer.height());
|
||||
if buffer.width() == 0 || buffer.height() == 0 {
|
||||
return composite;
|
||||
}
|
||||
match write_drag_image(&buffer) {
|
||||
Ok(path) => match slint::Image::load_from_path(&path) {
|
||||
Ok(image) => {
|
||||
forget_drag_image_file();
|
||||
*DRAG_IMAGE_FILE.lock().unwrap() = Some(path);
|
||||
image
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("drag ghost: {} did not load back", path.display());
|
||||
let _ = std::fs::remove_file(&path);
|
||||
composite
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("drag ghost: {e}");
|
||||
composite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The file the current drag's ghost is loaded from, if any.
|
||||
static DRAG_IMAGE_FILE: std::sync::Mutex<Option<std::path::PathBuf>> = std::sync::Mutex::new(None);
|
||||
|
||||
fn write_drag_image(
|
||||
buffer: &slint::SharedPixelBuffer<slint::Rgba8Pixel>,
|
||||
) -> std::io::Result<std::path::PathBuf> {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
let dir = crate::library::scratch_dir();
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
let path = dir.join(format!(
|
||||
"drag-{}-{}.png",
|
||||
std::process::id(),
|
||||
SERIAL.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
let file = std::fs::File::create(&path)?;
|
||||
let mut encoder = png::Encoder::new(
|
||||
std::io::BufWriter::new(file),
|
||||
buffer.width(),
|
||||
buffer.height(),
|
||||
);
|
||||
encoder.set_color(png::ColorType::Rgba);
|
||||
encoder.set_depth(png::BitDepth::Eight);
|
||||
// Fastest: this is a 160px bitmap written once per drag and read once.
|
||||
encoder.set_compression(png::Compression::Fastest);
|
||||
let mut writer = encoder.write_header().map_err(std::io::Error::other)?;
|
||||
writer
|
||||
.write_image_data(buffer.as_bytes())
|
||||
.map_err(std::io::Error::other)?;
|
||||
writer.finish().map_err(std::io::Error::other)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn forget_drag_image_file() {
|
||||
if let Some(path) = DRAG_IMAGE_FILE.lock().unwrap().take() {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw one thumbnail into the composite, scaled to `tw`×`th` at `dx`,`dy`.
|
||||
///
|
||||
/// Nearest-neighbour: this is a transient 160px cursor bitmap, and a filtered
|
||||
@@ -2161,7 +2252,7 @@ pub fn wire<S, R, P, C>(
|
||||
.map(|c| c.thumbnail)
|
||||
.collect()
|
||||
};
|
||||
w.set_library_drag_image(compose_drag_image(&thumbs));
|
||||
w.set_library_drag_image(drag_image_via_file(compose_drag_image(&thumbs)));
|
||||
|
||||
*ctl.dragging.borrow_mut() = carried.clone();
|
||||
sync_selection(&w, &ctl, &ids);
|
||||
@@ -2311,6 +2402,7 @@ pub fn wire<S, R, P, C>(
|
||||
// released — it holds a copy of every thumbnail it composited.
|
||||
sync_lifted(&w, &[], &visible());
|
||||
w.set_library_drag_image(slint::Image::default());
|
||||
forget_drag_image_file();
|
||||
|
||||
if landed.is_some() {
|
||||
let borrow = catalog.borrow();
|
||||
|
||||
@@ -4340,6 +4340,15 @@ pub fn inference_cache_dir() -> PathBuf {
|
||||
data_root().join("inference")
|
||||
}
|
||||
|
||||
/// Somewhere to put a file that only needs to exist for a moment.
|
||||
///
|
||||
/// Under the data root rather than `std::env::temp_dir()`, which on Android
|
||||
/// names a directory the app cannot write to. Nothing here survives a
|
||||
/// launch on purpose: whoever writes into it deletes what they wrote.
|
||||
pub fn scratch_dir() -> PathBuf {
|
||||
data_root().join("scratch")
|
||||
}
|
||||
|
||||
/// The detector and embedder files, if both are present — and the eye
|
||||
/// models beside them, if those are.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user