mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 21:30:03 -04:00
Make epi::Frame cloneable so you can allocate textures in other threads (#999)
Closes https://github.com/emilk/egui/issues/673 Also adds `epi::Image`
This commit is contained in:
211
epi/src/lib.rs
211
epi/src/lib.rs
@@ -95,6 +95,8 @@ pub mod file_storage;
|
||||
|
||||
pub use egui; // Re-export for user convenience
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
|
||||
@@ -104,8 +106,11 @@ pub trait App {
|
||||
///
|
||||
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
|
||||
///
|
||||
/// To force a repaint, call either [`egui::Context::request_repaint`] or use [`Frame::repaint_signal`].
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
|
||||
/// The given [`egui::CtxRef`] is only valid for the duration of this call.
|
||||
/// The [`Frame`] however can be cloned and saved.
|
||||
///
|
||||
/// To force a repaint, call either [`egui::Context::request_repaint`] or [`Frame::request_repaint`].
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &Frame);
|
||||
|
||||
/// Called once before the first frame.
|
||||
///
|
||||
@@ -113,13 +118,7 @@ pub trait App {
|
||||
/// [`egui::Context::set_visuals`] etc.
|
||||
///
|
||||
/// Also allows you to restore state, if there is a storage (required the "persistence" feature).
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &mut Frame<'_>,
|
||||
_storage: Option<&dyn Storage>,
|
||||
) {
|
||||
}
|
||||
fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {}
|
||||
|
||||
/// If `true` a warm-up call to [`Self::update`] will be issued where
|
||||
/// `ctx.memory().everything_is_visible()` will be set to `true`.
|
||||
@@ -240,7 +239,7 @@ impl Default for NativeOptions {
|
||||
/// Image data for the icon.
|
||||
#[derive(Clone)]
|
||||
pub struct IconData {
|
||||
/// RGBA pixels.
|
||||
/// RGBA pixels, unmultiplied.
|
||||
pub rgba: Vec<u8>,
|
||||
|
||||
/// Image width. This should be a multiple of 4.
|
||||
@@ -254,57 +253,99 @@ pub struct IconData {
|
||||
///
|
||||
/// It provides methods to inspect the surroundings (are we on the web?),
|
||||
/// allocate textures, and change settings (e.g. window size).
|
||||
pub struct Frame<'a>(backend::FrameBuilder<'a>);
|
||||
///
|
||||
/// [`Frame`] is cheap to clone and is safe to pass to other threads.
|
||||
#[derive(Clone)]
|
||||
pub struct Frame(pub Arc<Mutex<backend::FrameData>>);
|
||||
|
||||
impl Frame {
|
||||
/// Create a `Frame` - called by the integration.
|
||||
#[doc(hidden)]
|
||||
pub fn new(frame_data: backend::FrameData) -> Self {
|
||||
Self(Arc::new(Mutex::new(frame_data)))
|
||||
}
|
||||
|
||||
/// Convenience to access the underlying `backend::FrameData`.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
impl<'a> Frame<'a> {
|
||||
/// True if you are in a web environment.
|
||||
pub fn is_web(&self) -> bool {
|
||||
self.info().web_info.is_some()
|
||||
self.lock().info.web_info.is_some()
|
||||
}
|
||||
|
||||
/// Information about the integration.
|
||||
pub fn info(&self) -> &IntegrationInfo {
|
||||
&self.0.info
|
||||
}
|
||||
|
||||
/// A way to allocate textures.
|
||||
pub fn tex_allocator(&mut self) -> &mut dyn TextureAllocator {
|
||||
self.0.tex_allocator
|
||||
pub fn info(&self) -> IntegrationInfo {
|
||||
self.lock().info.clone()
|
||||
}
|
||||
|
||||
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
|
||||
/// The framework will not quit immediately, but at the end of the this frame.
|
||||
pub fn quit(&mut self) {
|
||||
self.0.output.quit = true;
|
||||
pub fn quit(&self) {
|
||||
self.lock().output.quit = true;
|
||||
}
|
||||
|
||||
/// Set the desired inner size of the window (in egui points).
|
||||
pub fn set_window_size(&mut self, size: egui::Vec2) {
|
||||
self.0.output.window_size = Some(size);
|
||||
pub fn set_window_size(&self, size: egui::Vec2) {
|
||||
self.lock().output.window_size = Some(size);
|
||||
}
|
||||
|
||||
/// Set the desired title of the window.
|
||||
pub fn set_window_title(&mut self, title: &str) {
|
||||
self.0.output.window_title = Some(title.to_owned());
|
||||
pub fn set_window_title(&self, title: &str) {
|
||||
self.lock().output.window_title = Some(title.to_owned());
|
||||
}
|
||||
|
||||
/// Set whether to show window decorations (i.e. a frame around you app).
|
||||
/// If false it will be difficult to move and resize the app.
|
||||
pub fn set_decorations(&mut self, decorated: bool) {
|
||||
self.0.output.decorated = Some(decorated);
|
||||
pub fn set_decorations(&self, decorated: bool) {
|
||||
self.lock().output.decorated = Some(decorated);
|
||||
}
|
||||
|
||||
/// When called, the native window will follow the
|
||||
/// movement of the cursor while the primary mouse button is down.
|
||||
///
|
||||
/// Does not work on the web, and works badly on Mac.
|
||||
pub fn drag_window(&mut self) {
|
||||
self.0.output.drag_window = true;
|
||||
/// Does not work on the web.
|
||||
pub fn drag_window(&self) {
|
||||
self.lock().output.drag_window = true;
|
||||
}
|
||||
|
||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
||||
pub fn repaint_signal(&self) -> std::sync::Arc<dyn RepaintSignal> {
|
||||
self.0.repaint_signal.clone()
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
///
|
||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
||||
pub fn request_repaint(&self) {
|
||||
self.lock().repaint_signal.request_repaint();
|
||||
}
|
||||
|
||||
/// for integrations only: call once per frame
|
||||
pub fn take_app_output(&self) -> crate::backend::AppOutput {
|
||||
let mut lock = self.lock();
|
||||
let next_id = lock.output.tex_allocation_data.next_id;
|
||||
let app_output = std::mem::take(&mut lock.output);
|
||||
lock.output.tex_allocation_data.next_id = next_id;
|
||||
app_output
|
||||
}
|
||||
|
||||
/// Allocate a texture. Free it again with [`Self::free_texture`].
|
||||
pub fn alloc_texture(&self, image: Image) -> egui::TextureId {
|
||||
self.lock().output.tex_allocation_data.alloc(image)
|
||||
}
|
||||
|
||||
/// Free a texture that has been previously allocated with [`Self::alloc_texture`]. Idempotent.
|
||||
pub fn free_texture(&self, id: egui::TextureId) {
|
||||
self.lock().output.tex_allocation_data.free(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureAllocator for Frame {
|
||||
fn alloc(&self, image: Image) -> egui::TextureId {
|
||||
self.lock().output.tex_allocation_data.alloc(image)
|
||||
}
|
||||
|
||||
fn free(&self, id: egui::TextureId) {
|
||||
self.lock().output.tex_allocation_data.free(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,14 +385,33 @@ pub trait TextureAllocator {
|
||||
///
|
||||
/// There is no way to change a texture.
|
||||
/// Instead allocate a new texture and free the previous one with [`Self::free`].
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[egui::Color32],
|
||||
) -> egui::TextureId;
|
||||
fn alloc(&self, image: Image) -> egui::TextureId;
|
||||
|
||||
/// Free the given texture.
|
||||
fn free(&mut self, id: egui::TextureId);
|
||||
fn free(&self, id: egui::TextureId);
|
||||
}
|
||||
|
||||
/// A 2D color image in RAM.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Image {
|
||||
/// width, height
|
||||
pub size: [usize; 2],
|
||||
/// The pixels, row by row, from top to bottom.
|
||||
pub pixels: Vec<egui::Color32>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
/// Create an `Image` from flat RGBA data.
|
||||
/// Panics unless `size[0] * size[1] * 4 == rgba.len()`.
|
||||
/// This is usually what you want to use after having loaded an image.
|
||||
pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
|
||||
assert_eq!(size[0] * size[1] * 4, rgba.len());
|
||||
let pixels = rgba
|
||||
.chunks_exact(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstraction for platform dependent texture reference
|
||||
@@ -367,13 +427,6 @@ pub trait NativeTexture {
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture);
|
||||
}
|
||||
|
||||
/// How to signal the [`egui`] integration that a repaint is required.
|
||||
pub trait RepaintSignal: Send + Sync {
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
/// This is meant to be called when a background process finishes in an async context and/or background thread.
|
||||
fn request_repaint(&self);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||
@@ -423,29 +476,68 @@ pub const APP_KEY: &str = "app";
|
||||
|
||||
/// You only need to look here if you are writing a backend for `epi`.
|
||||
pub mod backend {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// How to signal the [`egui`] integration that a repaint is required.
|
||||
pub trait RepaintSignal: Send + Sync {
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
///
|
||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
||||
fn request_repaint(&self);
|
||||
}
|
||||
|
||||
/// The data required by [`Frame`] each frame.
|
||||
pub struct FrameBuilder<'a> {
|
||||
pub struct FrameData {
|
||||
/// Information about the integration.
|
||||
pub info: IntegrationInfo,
|
||||
/// A way to allocate textures (on integrations that support it).
|
||||
pub tex_allocator: &'a mut dyn TextureAllocator,
|
||||
/// Where the app can issue commands back to the integration.
|
||||
pub output: &'a mut AppOutput,
|
||||
pub output: AppOutput,
|
||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
||||
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
|
||||
}
|
||||
|
||||
impl<'a> FrameBuilder<'a> {
|
||||
/// Wrap us in a [`Frame`] to send to [`App::update`].
|
||||
pub fn build(self) -> Frame<'a> {
|
||||
Frame(self)
|
||||
/// The data needed in order to allocate and free textures/images.
|
||||
#[derive(Default)]
|
||||
#[must_use]
|
||||
pub struct TexAllocationData {
|
||||
/// We allocate texture id linearly.
|
||||
pub(crate) next_id: u64,
|
||||
/// New creations this frame
|
||||
pub creations: HashMap<u64, Image>,
|
||||
/// destructions this frame.
|
||||
pub destructions: Vec<u64>,
|
||||
}
|
||||
|
||||
impl TexAllocationData {
|
||||
/// Should only be used by integrations
|
||||
pub fn take(&mut self) -> Self {
|
||||
let next_id = self.next_id;
|
||||
let ret = std::mem::take(self);
|
||||
self.next_id = next_id;
|
||||
ret
|
||||
}
|
||||
|
||||
/// Allocate a new texture.
|
||||
pub fn alloc(&mut self, image: Image) -> egui::TextureId {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.creations.insert(id, image);
|
||||
egui::TextureId::User(id)
|
||||
}
|
||||
|
||||
/// Free an existing texture.
|
||||
pub fn free(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
self.destructions.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Action that can be taken by the user app.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Default)]
|
||||
#[must_use]
|
||||
pub struct AppOutput {
|
||||
/// Set to `true` to stop the app.
|
||||
/// This does nothing for web apps.
|
||||
@@ -457,10 +549,13 @@ pub mod backend {
|
||||
/// Set to some string to rename the outer window (e.g. glium window) to this title.
|
||||
pub window_title: Option<String>,
|
||||
|
||||
/// Set to some bool to change window decorations
|
||||
/// Set to some bool to change window decorations.
|
||||
pub decorated: Option<bool>,
|
||||
|
||||
/// Set to true to drap window
|
||||
/// Set to true to drag window while primary mouse button is down.
|
||||
pub drag_window: bool,
|
||||
|
||||
/// A way to allocate textures (on integrations that support it).
|
||||
pub tex_allocation_data: TexAllocationData,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user