Merge: master at 0.13.6, with the drag-ghost file and the shared model lookup ported into the split modules
This commit is contained in:
@@ -86,6 +86,107 @@ pub(super) 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.
|
||||
pub(super) 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)
|
||||
}
|
||||
|
||||
pub(super) 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
|
||||
/// resample would cost more than it could visibly buy. `dim` darkens the layers
|
||||
/// beneath the top one so the stack reads as depth rather than as a smear.
|
||||
///
|
||||
/// The buffer is premultiplied, so the alpha applied here is baked into the
|
||||
/// colour channels as well.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
/// 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
|
||||
|
||||
Reference in New Issue
Block a user