Introduce explicit image::allocate API
This commit is contained in:
parent
6fa54f7f6b
commit
23039e758e
15 changed files with 355 additions and 51 deletions
|
|
@ -4,8 +4,10 @@ pub use bytes::Bytes;
|
||||||
use crate::{Radians, Rectangle, Size};
|
use crate::{Radians, Rectangle, Size};
|
||||||
|
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
/// A raster image that can be drawn.
|
/// A raster image that can be drawn.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
@ -227,6 +229,56 @@ pub enum FilterMethod {
|
||||||
Nearest,
|
Nearest,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A memory allocation of a [`Handle`], often in GPU memory.
|
||||||
|
///
|
||||||
|
/// Renderers tend to decode and upload image data concurrently to
|
||||||
|
/// avoid blocking the user interface. This means that when you use a
|
||||||
|
/// [`Handle`] in a widget, there may be a slight frame delay until it
|
||||||
|
/// is finally visible. If you are animating images, this can cause
|
||||||
|
/// undesirable flicker.
|
||||||
|
///
|
||||||
|
/// When you obtain an [`Allocation`] explicitly, you get the guarantee
|
||||||
|
/// that using a [`Handle`] will draw the corresponding [`Image`]
|
||||||
|
/// immediately in the next frame.
|
||||||
|
///
|
||||||
|
/// This guarantee is valid as long as you hold an [`Allocation`].
|
||||||
|
/// Only when you drop all its clones, the renderer may choose to free
|
||||||
|
/// the memory of the [`Handle`]. Be careful!
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Allocation(Arc<Memory>);
|
||||||
|
|
||||||
|
/// Some memory taken by an [`Allocation`].
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Memory(Handle);
|
||||||
|
|
||||||
|
impl Allocation {
|
||||||
|
/// Returns a weak reference to the [`Memory`] of the [`Allocation`].
|
||||||
|
pub fn downgrade(&self) -> Weak<Memory> {
|
||||||
|
Arc::downgrade(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upgrades a [`Weak`] memory reference to an [`Allocation`].
|
||||||
|
pub fn upgrade(weak: &Weak<Memory>) -> Option<Allocation> {
|
||||||
|
Weak::upgrade(weak).map(Allocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Handle`] of this [`Allocation`].
|
||||||
|
pub fn handle(&self) -> &Handle {
|
||||||
|
&self.0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Allocation`] for the given handle.
|
||||||
|
///
|
||||||
|
/// This should only be used internally by renderer implementations.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Must only be created once the [`Handle`] is allocated in memory.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub unsafe fn allocate(handle: &Handle) -> Allocation {
|
||||||
|
Allocation(Arc::new(Memory(handle.clone())))
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`Renderer`] that can render raster graphics.
|
/// A [`Renderer`] that can render raster graphics.
|
||||||
///
|
///
|
||||||
/// [renderer]: crate::renderer
|
/// [renderer]: crate::renderer
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod null;
|
mod null;
|
||||||
|
|
||||||
|
use crate::image;
|
||||||
use crate::{
|
use crate::{
|
||||||
Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size,
|
Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size,
|
||||||
Transformation, Vector,
|
Transformation, Vector,
|
||||||
|
|
@ -62,6 +63,13 @@ pub trait Renderer {
|
||||||
|
|
||||||
/// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch.
|
/// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch.
|
||||||
fn reset(&mut self, new_bounds: Rectangle);
|
fn reset(&mut self, new_bounds: Rectangle);
|
||||||
|
|
||||||
|
/// Creates an [`image::Allocation`] for the given [`image::Handle`] and calls the given callback with it.
|
||||||
|
fn allocate_image(
|
||||||
|
&mut self,
|
||||||
|
handle: &image::Handle,
|
||||||
|
callback: impl FnOnce(image::Allocation) + Send + 'static,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A polygon with four sides.
|
/// A polygon with four sides.
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,15 @@ impl Renderer for () {
|
||||||
_background: impl Into<Background>,
|
_background: impl Into<Background>,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allocate_image(
|
||||||
|
&mut self,
|
||||||
|
handle: &image::Handle,
|
||||||
|
callback: impl FnOnce(image::Allocation) + Send + 'static,
|
||||||
|
) {
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
callback(unsafe { image::allocate(handle) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl text::Renderer for () {
|
impl text::Renderer for () {
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,9 @@ enum Message {
|
||||||
ImagesListed(Result<Vec<Image>, Error>),
|
ImagesListed(Result<Vec<Image>, Error>),
|
||||||
ImagePoppedIn(Id),
|
ImagePoppedIn(Id),
|
||||||
ImagePoppedOut(Id),
|
ImagePoppedOut(Id),
|
||||||
ImageDownloaded(Result<Bytes, Error>),
|
ImageDownloaded(Result<image::Allocation, Error>),
|
||||||
ThumbnailDownloaded(Id, Result<Bytes, Error>),
|
ThumbnailDownloaded(Id, Result<Bytes, Error>),
|
||||||
|
ThumbnailAllocated(Id, image::Allocation),
|
||||||
ThumbnailHovered(Id, bool),
|
ThumbnailHovered(Id, bool),
|
||||||
BlurhashDecoded(Id, civitai::Blurhash),
|
BlurhashDecoded(Id, civitai::Blurhash),
|
||||||
Open(Id),
|
Open(Id),
|
||||||
|
|
@ -110,14 +111,16 @@ impl Gallery {
|
||||||
let _ = self.visible.insert(id);
|
let _ = self.visible.insert(id);
|
||||||
|
|
||||||
if self.downloaded.contains(&id) {
|
if self.downloaded.contains(&id) {
|
||||||
if let Some(Preview::Ready { thumbnail, .. }) =
|
let Some(Preview::Ready { thumbnail, .. }) =
|
||||||
self.previews.get_mut(&id)
|
self.previews.get_mut(&id)
|
||||||
{
|
else {
|
||||||
thumbnail.fade_in =
|
return Task::none();
|
||||||
Animation::new(false).slow().go(true, now);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return Task::none();
|
return image::allocate(image::Handle::from_bytes(
|
||||||
|
thumbnail.bytes.clone(),
|
||||||
|
))
|
||||||
|
.map(Message::ThumbnailAllocated.with(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.downloaded.insert(id);
|
let _ = self.downloaded.insert(id);
|
||||||
|
|
@ -134,22 +137,56 @@ impl Gallery {
|
||||||
Message::ImagePoppedOut(id) => {
|
Message::ImagePoppedOut(id) => {
|
||||||
let _ = self.visible.remove(&id);
|
let _ = self.visible.remove(&id);
|
||||||
|
|
||||||
|
if let Some(Preview::Ready {
|
||||||
|
thumbnail,
|
||||||
|
blurhash,
|
||||||
|
}) = self.previews.get_mut(&id)
|
||||||
|
{
|
||||||
|
thumbnail.reset();
|
||||||
|
|
||||||
|
if let Some(blurhash) = blurhash {
|
||||||
|
blurhash.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ImageDownloaded(Ok(bytes)) => {
|
Message::ImageDownloaded(Ok(allocation)) => {
|
||||||
self.viewer.show(bytes, self.now);
|
self.viewer.show(allocation, self.now);
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ThumbnailDownloaded(id, Ok(bytes)) => {
|
Message::ThumbnailDownloaded(id, Ok(bytes)) => {
|
||||||
let thumbnail = if let Some(preview) = self.previews.remove(&id)
|
let preview = if let Some(preview) = self.previews.remove(&id) {
|
||||||
{
|
preview.load(bytes.clone())
|
||||||
preview.load(bytes, self.now)
|
|
||||||
} else {
|
} else {
|
||||||
Preview::ready(bytes, self.now)
|
Preview::ready(bytes.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = self.previews.insert(id, thumbnail);
|
let _ = self.previews.insert(id, preview);
|
||||||
|
|
||||||
|
image::allocate(image::Handle::from_bytes(bytes))
|
||||||
|
.map(Message::ThumbnailAllocated.with(id))
|
||||||
|
}
|
||||||
|
Message::ThumbnailAllocated(id, allocation) => {
|
||||||
|
if !self.visible.contains(&id) {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(Preview::Ready {
|
||||||
|
thumbnail,
|
||||||
|
blurhash,
|
||||||
|
..
|
||||||
|
}) = self.previews.get_mut(&id)
|
||||||
|
else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(blurhash) = blurhash {
|
||||||
|
blurhash.show(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnail.show(allocation, now);
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +218,12 @@ impl Gallery {
|
||||||
|
|
||||||
self.viewer.open(self.now);
|
self.viewer.open(self.now);
|
||||||
|
|
||||||
Task::perform(
|
Task::future(image.download(Size::Original))
|
||||||
image.download(Size::Original),
|
.and_then(|bytes| {
|
||||||
Message::ImageDownloaded,
|
image::allocate(image::Handle::from_bytes(bytes))
|
||||||
)
|
.map(Ok)
|
||||||
|
})
|
||||||
|
.map(Message::ImageDownloaded)
|
||||||
}
|
}
|
||||||
Message::Close => {
|
Message::Close => {
|
||||||
self.viewer.close(self.now);
|
self.viewer.close(self.now);
|
||||||
|
|
@ -238,9 +277,11 @@ fn card<'a>(
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let image = if let Some(preview) = preview {
|
let image = if let Some(preview) = preview {
|
||||||
let thumbnail: Element<'_, _> =
|
let thumbnail: Element<'_, _> =
|
||||||
if let Preview::Ready { thumbnail, .. } = &preview {
|
if let Preview::Ready { thumbnail, .. } = &preview
|
||||||
|
&& let Some(allocation) = &thumbnail.allocation
|
||||||
|
{
|
||||||
float(
|
float(
|
||||||
image(&thumbnail.handle)
|
image(allocation.handle())
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.content_fit(ContentFit::Cover)
|
.content_fit(ContentFit::Cover)
|
||||||
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)),
|
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)),
|
||||||
|
|
@ -320,8 +361,21 @@ struct Blurhash {
|
||||||
fade_in: Animation<bool>,
|
fade_in: Animation<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Blurhash {
|
||||||
|
pub fn show(&mut self, now: Instant) {
|
||||||
|
self.fade_in.go_mut(true, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.fade_in = Animation::new(false)
|
||||||
|
.easing(animation::Easing::EaseIn)
|
||||||
|
.slow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Thumbnail {
|
struct Thumbnail {
|
||||||
handle: image::Handle,
|
bytes: Bytes,
|
||||||
|
allocation: Option<image::Allocation>,
|
||||||
fade_in: Animation<bool>,
|
fade_in: Animation<bool>,
|
||||||
zoom: Animation<bool>,
|
zoom: Animation<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -346,21 +400,21 @@ impl Preview {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ready(bytes: Bytes, now: Instant) -> Self {
|
fn ready(bytes: Bytes) -> Self {
|
||||||
Self::Ready {
|
Self::Ready {
|
||||||
blurhash: None,
|
blurhash: None,
|
||||||
thumbnail: Thumbnail::new(bytes, now),
|
thumbnail: Thumbnail::new(bytes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(self, bytes: Bytes, now: Instant) -> Self {
|
fn load(self, bytes: Bytes) -> Self {
|
||||||
let Self::Loading { blurhash } = self else {
|
let Self::Loading { blurhash } = self else {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::Ready {
|
Self::Ready {
|
||||||
blurhash: Some(blurhash),
|
blurhash: Some(blurhash),
|
||||||
thumbnail: Thumbnail::new(bytes, now),
|
thumbnail: Thumbnail::new(bytes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,26 +441,45 @@ impl Preview {
|
||||||
blurhash: Some(blurhash),
|
blurhash: Some(blurhash),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
..
|
..
|
||||||
} if thumbnail.fade_in.is_animating(now) => Some(blurhash),
|
} if !thumbnail.fade_in.value()
|
||||||
|
|| thumbnail.fade_in.is_animating(now) =>
|
||||||
|
{
|
||||||
|
Some(blurhash)
|
||||||
|
}
|
||||||
Self::Ready { .. } => None,
|
Self::Ready { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Thumbnail {
|
impl Thumbnail {
|
||||||
pub fn new(bytes: Bytes, now: Instant) -> Self {
|
pub fn new(bytes: Bytes) -> Self {
|
||||||
Self {
|
Self {
|
||||||
handle: image::Handle::from_bytes(bytes),
|
bytes,
|
||||||
fade_in: Animation::new(false).slow().go(true, now),
|
allocation: None,
|
||||||
|
fade_in: Animation::new(false)
|
||||||
|
.easing(animation::Easing::EaseIn)
|
||||||
|
.slow(),
|
||||||
zoom: Animation::new(false)
|
zoom: Animation::new(false)
|
||||||
.quick()
|
.quick()
|
||||||
.easing(animation::Easing::EaseInOut),
|
.easing(animation::Easing::EaseInOut),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.allocation = None;
|
||||||
|
self.fade_in = Animation::new(false)
|
||||||
|
.easing(animation::Easing::EaseIn)
|
||||||
|
.slow();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&mut self, allocation: image::Allocation, now: Instant) {
|
||||||
|
self.allocation = Some(allocation);
|
||||||
|
self.fade_in.go_mut(true, now);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Viewer {
|
struct Viewer {
|
||||||
image: Option<image::Handle>,
|
image: Option<image::Allocation>,
|
||||||
background_fade_in: Animation<bool>,
|
background_fade_in: Animation<bool>,
|
||||||
image_fade_in: Animation<bool>,
|
image_fade_in: Animation<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -429,8 +502,8 @@ impl Viewer {
|
||||||
self.background_fade_in.go_mut(true, now);
|
self.background_fade_in.go_mut(true, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, bytes: Bytes, now: Instant) {
|
fn show(&mut self, allocation: image::Allocation, now: Instant) {
|
||||||
self.image = Some(image::Handle::from_bytes(bytes));
|
self.image = Some(allocation);
|
||||||
self.background_fade_in.go_mut(true, now);
|
self.background_fade_in.go_mut(true, now);
|
||||||
self.image_fade_in.go_mut(true, now);
|
self.image_fade_in.go_mut(true, now);
|
||||||
}
|
}
|
||||||
|
|
@ -452,8 +525,8 @@ impl Viewer {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let image = self.image.as_ref().map(|handle| {
|
let image = self.image.as_ref().map(|allocation| {
|
||||||
image(handle)
|
image(allocation.handle())
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.opacity(self.image_fade_in.interpolate(0.0, 1.0, now))
|
.opacity(self.image_fade_in.interpolate(0.0, 1.0, now))
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,14 @@ where
|
||||||
fn end_transformation(&mut self) {
|
fn end_transformation(&mut self) {
|
||||||
delegate!(self, renderer, renderer.end_transformation());
|
delegate!(self, renderer, renderer.end_transformation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allocate_image(
|
||||||
|
&mut self,
|
||||||
|
handle: &image::Handle,
|
||||||
|
callback: impl FnOnce(image::Allocation) + Send + 'static,
|
||||||
|
) {
|
||||||
|
delegate!(self, renderer, renderer.allocate_image(handle, callback));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B> core::text::Renderer for Renderer<A, B>
|
impl<A, B> core::text::Renderer for Renderer<A, B>
|
||||||
|
|
|
||||||
24
runtime/src/image.rs
Normal file
24
runtime/src/image.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! Allocate images explicitly to control presentation.
|
||||||
|
use crate::core::image::Handle;
|
||||||
|
use crate::futures::futures::channel::oneshot;
|
||||||
|
use crate::task::{self, Task};
|
||||||
|
|
||||||
|
pub use crate::core::image::Allocation;
|
||||||
|
|
||||||
|
/// An image action.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Action {
|
||||||
|
/// Allocates the given [`Handle`].
|
||||||
|
Allocate(Handle, oneshot::Sender<Allocation>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates an image [`Handle`].
|
||||||
|
///
|
||||||
|
/// When you obtain an [`Allocation`] explicitly, you get the guarantee
|
||||||
|
/// that using a [`Handle`] will draw the corresponding image immediately
|
||||||
|
/// in the next frame.
|
||||||
|
pub fn allocate(handle: Handle) -> Task<Allocation> {
|
||||||
|
task::oneshot(|sender| {
|
||||||
|
crate::Action::Image(Action::Allocate(handle, sender))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
|
pub mod image;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|
@ -55,6 +56,9 @@ pub enum Action<T> {
|
||||||
/// Run a system action.
|
/// Run a system action.
|
||||||
System(system::Action),
|
System(system::Action),
|
||||||
|
|
||||||
|
/// An image action.
|
||||||
|
Image(image::Action),
|
||||||
|
|
||||||
/// Recreate all user interfaces and redraw all windows.
|
/// Recreate all user interfaces and redraw all windows.
|
||||||
Reload,
|
Reload,
|
||||||
|
|
||||||
|
|
@ -81,6 +85,7 @@ impl<T> Action<T> {
|
||||||
Action::Clipboard(action) => Err(Action::Clipboard(action)),
|
Action::Clipboard(action) => Err(Action::Clipboard(action)),
|
||||||
Action::Window(action) => Err(Action::Window(action)),
|
Action::Window(action) => Err(Action::Window(action)),
|
||||||
Action::System(action) => Err(Action::System(action)),
|
Action::System(action) => Err(Action::System(action)),
|
||||||
|
Action::Image(action) => Err(Action::Image(action)),
|
||||||
Action::Reload => Err(Action::Reload),
|
Action::Reload => Err(Action::Reload),
|
||||||
Action::Exit => Err(Action::Exit),
|
Action::Exit => Err(Action::Exit),
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +110,7 @@ where
|
||||||
}
|
}
|
||||||
Action::Window(_) => write!(f, "Action::Window"),
|
Action::Window(_) => write!(f, "Action::Window"),
|
||||||
Action::System(action) => write!(f, "Action::System({action:?})"),
|
Action::System(action) => write!(f, "Action::System({action:?})"),
|
||||||
|
Action::Image(_) => write!(f, "Action::Image"),
|
||||||
Action::Reload => write!(f, "Action::Reload"),
|
Action::Reload => write!(f, "Action::Reload"),
|
||||||
Action::Exit => write!(f, "Action::Exit"),
|
Action::Exit => write!(f, "Action::Exit"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -379,14 +379,16 @@ impl<T, E> Task<Result<T, E>> {
|
||||||
/// The success value is provided to the closure to create the subsequent [`Task`].
|
/// The success value is provided to the closure to create the subsequent [`Task`].
|
||||||
pub fn and_then<A>(
|
pub fn and_then<A>(
|
||||||
self,
|
self,
|
||||||
f: impl Fn(T) -> Task<A> + MaybeSend + 'static,
|
f: impl Fn(T) -> Task<Result<A, E>> + MaybeSend + 'static,
|
||||||
) -> Task<A>
|
) -> Task<Result<A, E>>
|
||||||
where
|
where
|
||||||
T: MaybeSend + 'static,
|
T: MaybeSend + 'static,
|
||||||
E: MaybeSend + 'static,
|
E: MaybeSend + 'static,
|
||||||
A: MaybeSend + 'static,
|
A: MaybeSend + 'static,
|
||||||
{
|
{
|
||||||
self.then(move |option| option.map_or_else(|_| Task::none(), &f))
|
self.then(move |result| {
|
||||||
|
result.map_or_else(|error| Task::done(Err(error)), &f)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -625,6 +625,13 @@ pub mod widget {
|
||||||
pub use iced_runtime::widget::*;
|
pub use iced_runtime::widget::*;
|
||||||
pub use iced_widget::*;
|
pub use iced_widget::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub mod image {
|
||||||
|
//! Images display raster graphics in different formats (PNG, JPG, etc.).
|
||||||
|
pub use iced_runtime::image::{Allocation, allocate};
|
||||||
|
pub use iced_widget::image::*;
|
||||||
|
}
|
||||||
|
|
||||||
// We hide the re-exported modules by `iced_widget`
|
// We hide the re-exported modules by `iced_widget`
|
||||||
mod core {}
|
mod core {}
|
||||||
mod graphics {}
|
mod graphics {}
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,10 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
// TODO
|
// TODO
|
||||||
dbg!(action);
|
dbg!(action);
|
||||||
}
|
}
|
||||||
|
iced_runtime::Action::Image(action) => {
|
||||||
|
// TODO
|
||||||
|
dbg!(action);
|
||||||
|
}
|
||||||
runtime::Action::Exit => {
|
runtime::Action::Exit => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,16 @@ impl core::Renderer for Renderer {
|
||||||
fn reset(&mut self, new_bounds: Rectangle) {
|
fn reset(&mut self, new_bounds: Rectangle) {
|
||||||
self.layers.reset(new_bounds);
|
self.layers.reset(new_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allocate_image(
|
||||||
|
&mut self,
|
||||||
|
handle: &core::image::Handle,
|
||||||
|
callback: impl FnOnce(core::image::Allocation) + Send + 'static,
|
||||||
|
) {
|
||||||
|
// TODO: Concurrency
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
callback(unsafe { core::image::allocate(handle) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::text::Renderer for Renderer {
|
impl core::text::Renderer for Renderer {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,11 @@ use crate::graphics::Shell;
|
||||||
use crate::image::atlas::{self, Atlas};
|
use crate::image::atlas::{self, Atlas};
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
use std::collections::BTreeSet;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
atlas: Atlas,
|
atlas: Atlas,
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
|
|
@ -46,7 +45,7 @@ impl Cache {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
raster: Raster {
|
raster: Raster {
|
||||||
cache: crate::image::raster::Cache::default(),
|
cache: crate::image::raster::Cache::default(),
|
||||||
pending: BTreeSet::new(),
|
pending: HashMap::new(),
|
||||||
jobs: jobs.clone(),
|
jobs: jobs.clone(),
|
||||||
},
|
},
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
|
|
@ -60,6 +59,44 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub fn allocate_image(
|
||||||
|
&mut self,
|
||||||
|
handle: &core::image::Handle,
|
||||||
|
callback: impl FnOnce(core::image::Allocation) + Send + 'static,
|
||||||
|
) {
|
||||||
|
use crate::image::raster::Memory;
|
||||||
|
|
||||||
|
let callback = Box::new(callback);
|
||||||
|
|
||||||
|
if let Some(callbacks) = self.raster.pending.get_mut(&handle.id()) {
|
||||||
|
callbacks.push(callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Memory::Device { allocation, .. }) =
|
||||||
|
self.raster.cache.get_mut(handle)
|
||||||
|
{
|
||||||
|
if let Some(allocation) = allocation
|
||||||
|
.as_ref()
|
||||||
|
.and_then(core::image::Allocation::upgrade)
|
||||||
|
{
|
||||||
|
callback(allocation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
let new = unsafe { core::image::allocate(handle) };
|
||||||
|
*allocation = Some(new.downgrade());
|
||||||
|
callback(new);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.raster.pending.insert(handle.id(), vec![callback]);
|
||||||
|
let _ = self.raster.jobs.send(Job::Load(handle.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
||||||
self.receive();
|
self.receive();
|
||||||
|
|
@ -69,6 +106,7 @@ impl Cache {
|
||||||
&mut self.raster.pending,
|
&mut self.raster.pending,
|
||||||
&mut self.raster.jobs,
|
&mut self.raster.jobs,
|
||||||
handle,
|
handle,
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
return memory.dimensions();
|
return memory.dimensions();
|
||||||
}
|
}
|
||||||
|
|
@ -99,9 +137,13 @@ impl Cache {
|
||||||
&mut self.raster.pending,
|
&mut self.raster.pending,
|
||||||
&mut self.raster.jobs,
|
&mut self.raster.jobs,
|
||||||
handle,
|
handle,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Memory::Device { entry, bind_group } = memory {
|
if let Memory::Device {
|
||||||
|
entry, bind_group, ..
|
||||||
|
} = memory
|
||||||
|
{
|
||||||
return Some((
|
return Some((
|
||||||
entry,
|
entry,
|
||||||
bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
|
bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
|
||||||
|
|
@ -126,6 +168,7 @@ impl Cache {
|
||||||
*memory = Memory::Device {
|
*memory = Memory::Device {
|
||||||
entry,
|
entry,
|
||||||
bind_group: None,
|
bind_group: None,
|
||||||
|
allocation: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Memory::Device { entry, .. } = memory {
|
if let Memory::Device { entry, .. } = memory {
|
||||||
|
|
@ -133,7 +176,7 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.raster.pending.contains(&handle.id()) {
|
if !self.raster.pending.contains_key(&handle.id()) {
|
||||||
let _ = self.jobs.send(Job::Upload {
|
let _ = self.jobs.send(Job::Upload {
|
||||||
handle: handle.clone(),
|
handle: handle.clone(),
|
||||||
rgba: image.clone().into_raw(),
|
rgba: image.clone().into_raw(),
|
||||||
|
|
@ -141,7 +184,7 @@ impl Cache {
|
||||||
height: image.height(),
|
height: image.height(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = self.raster.pending.insert(handle.id());
|
let _ = self.raster.pending.insert(handle.id(), Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
@ -194,15 +237,32 @@ impl Cache {
|
||||||
entry,
|
entry,
|
||||||
bind_group,
|
bind_group,
|
||||||
} => {
|
} => {
|
||||||
|
let callbacks = self.raster.pending.remove(&handle.id());
|
||||||
|
|
||||||
|
let allocation = if let Some(callbacks) = callbacks {
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
let allocation =
|
||||||
|
unsafe { core::image::allocate(&handle) };
|
||||||
|
|
||||||
|
let reference = allocation.downgrade();
|
||||||
|
|
||||||
|
for callback in callbacks {
|
||||||
|
callback(allocation.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(reference)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
self.raster.cache.insert(
|
self.raster.cache.insert(
|
||||||
&handle,
|
&handle,
|
||||||
Memory::Device {
|
Memory::Device {
|
||||||
entry,
|
entry,
|
||||||
bind_group: Some(bind_group),
|
bind_group: Some(bind_group),
|
||||||
|
allocation,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = self.raster.pending.remove(&handle.id());
|
|
||||||
}
|
}
|
||||||
Work::Error { handle, error } => {
|
Work::Error { handle, error } => {
|
||||||
self.raster.cache.insert(&handle, Memory::error(error));
|
self.raster.cache.insert(&handle, Memory::error(error));
|
||||||
|
|
@ -225,19 +285,22 @@ impl Drop for Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
#[derive(Debug)]
|
|
||||||
struct Raster {
|
struct Raster {
|
||||||
cache: crate::image::raster::Cache,
|
cache: crate::image::raster::Cache,
|
||||||
pending: BTreeSet<core::image::Id>,
|
pending: HashMap<core::image::Id, Vec<Callback>>,
|
||||||
jobs: mpsc::SyncSender<Job>,
|
jobs: mpsc::SyncSender<Job>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
type Callback = Box<dyn FnOnce(core::image::Allocation) + Send>;
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
fn load_image<'a>(
|
fn load_image<'a>(
|
||||||
cache: &'a mut crate::image::raster::Cache,
|
cache: &'a mut crate::image::raster::Cache,
|
||||||
pending: &mut BTreeSet<core::image::Id>,
|
pending: &mut HashMap<core::image::Id, Vec<Callback>>,
|
||||||
jobs: &mut mpsc::SyncSender<Job>,
|
jobs: &mut mpsc::SyncSender<Job>,
|
||||||
handle: &core::image::Handle,
|
handle: &core::image::Handle,
|
||||||
|
callback: Option<Callback>,
|
||||||
) -> Option<&'a mut crate::image::raster::Memory> {
|
) -> Option<&'a mut crate::image::raster::Memory> {
|
||||||
use crate::image::raster::Memory;
|
use crate::image::raster::Memory;
|
||||||
|
|
||||||
|
|
@ -248,9 +311,9 @@ fn load_image<'a>(
|
||||||
} else if let core::image::Handle::Rgba { .. } = handle {
|
} else if let core::image::Handle::Rgba { .. } = handle {
|
||||||
// Load RGBA handles synchronously, since it's very cheap
|
// Load RGBA handles synchronously, since it's very cheap
|
||||||
cache.insert(handle, Memory::load(handle));
|
cache.insert(handle, Memory::load(handle));
|
||||||
} else if !pending.contains(&handle.id()) {
|
} else if !pending.contains_key(&handle.id()) {
|
||||||
let _ = jobs.send(Job::Load(handle.clone()));
|
let _ = jobs.send(Job::Load(handle.clone()));
|
||||||
let _ = pending.insert(handle.id());
|
let _ = pending.insert(handle.id(), Vec::from_iter(callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use crate::graphics::image::image_rs;
|
||||||
use crate::image::atlas::{self, Atlas};
|
use crate::image::atlas::{self, Atlas};
|
||||||
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
use std::sync::Weak;
|
||||||
|
|
||||||
type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>;
|
type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>;
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ pub enum Memory {
|
||||||
Device {
|
Device {
|
||||||
entry: atlas::Entry,
|
entry: atlas::Entry,
|
||||||
bind_group: Option<wgpu::BindGroup>,
|
bind_group: Option<wgpu::BindGroup>,
|
||||||
|
allocation: Option<Weak<image::Memory>>,
|
||||||
},
|
},
|
||||||
/// Image not found
|
/// Image not found
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
@ -100,7 +102,16 @@ impl Cache {
|
||||||
self.map.retain(|k, memory| {
|
self.map.retain(|k, memory| {
|
||||||
let retain = hits.contains(k);
|
let retain = hits.contains(k);
|
||||||
|
|
||||||
if !retain && let Memory::Device { entry, bind_group } = memory {
|
if !retain
|
||||||
|
&& let Memory::Device {
|
||||||
|
entry,
|
||||||
|
bind_group,
|
||||||
|
allocation: memory,
|
||||||
|
} = memory
|
||||||
|
&& memory
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|memory| memory.strong_count() == 0)
|
||||||
|
{
|
||||||
if let Some(bind_group) = bind_group.take() {
|
if let Some(bind_group) = bind_group.take() {
|
||||||
on_drop(bind_group);
|
on_drop(bind_group);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -696,6 +696,17 @@ impl core::Renderer for Renderer {
|
||||||
fn reset(&mut self, new_bounds: Rectangle) {
|
fn reset(&mut self, new_bounds: Rectangle) {
|
||||||
self.layers.reset(new_bounds);
|
self.layers.reset(new_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allocate_image(
|
||||||
|
&mut self,
|
||||||
|
_handle: &core::image::Handle,
|
||||||
|
_callback: impl FnOnce(core::image::Allocation) + Send + 'static,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
self.image_cache
|
||||||
|
.get_mut()
|
||||||
|
.allocate_image(_handle, _callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::text::Renderer for Renderer {
|
impl core::text::Renderer for Renderer {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ use crate::futures::futures::{Future, StreamExt};
|
||||||
use crate::futures::subscription;
|
use crate::futures::subscription;
|
||||||
use crate::futures::{Executor, Runtime};
|
use crate::futures::{Executor, Runtime};
|
||||||
use crate::graphics::{Compositor, Shell, compositor};
|
use crate::graphics::{Compositor, Shell, compositor};
|
||||||
|
use crate::runtime::image;
|
||||||
use crate::runtime::system;
|
use crate::runtime::system;
|
||||||
use crate::runtime::user_interface::{self, UserInterface};
|
use crate::runtime::user_interface::{self, UserInterface};
|
||||||
use crate::runtime::{Action, Task};
|
use crate::runtime::{Action, Task};
|
||||||
|
|
@ -1730,6 +1731,21 @@ fn run_action<'a, P, C>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::Image(action) => match action {
|
||||||
|
image::Action::Allocate(handle, sender) => {
|
||||||
|
use core::Renderer as _;
|
||||||
|
|
||||||
|
// TODO: Shared image cache in compositor
|
||||||
|
if let Some((_id, window)) = window_manager.iter_mut().next() {
|
||||||
|
window.renderer.allocate_image(
|
||||||
|
&handle,
|
||||||
|
move |allocation| {
|
||||||
|
let _ = sender.send(allocation);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Action::LoadFont { bytes, channel } => {
|
Action::LoadFont { bytes, channel } => {
|
||||||
if let Some(compositor) = compositor {
|
if let Some(compositor) = compositor {
|
||||||
// TODO: Error handling (?)
|
// TODO: Error handling (?)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue