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:
parent
647e020824
commit
b7441eeee7
28 changed files with 548 additions and 824 deletions
|
@ -5,6 +5,9 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m
|
|||
|
||||
|
||||
## Unreleased
|
||||
* `Frame` can now be cloned, saved, and passed to background threads ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
* Added `Frame::request_repaint` to replace `repaint_signal` ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator` ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -11,7 +11,7 @@ impl epi::App for MyApp {
|
|||
"Native file dialogs and drag-and-drop files"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.label("Drag-and-drop files onto the window!");
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ impl epi::App for MyApp {
|
|||
"My egui App"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
let Self { name, age } = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
|
|
|
@ -10,26 +10,20 @@ impl epi::App for MyApp {
|
|||
"Show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if self.texture.is_none() {
|
||||
// Load the image:
|
||||
let image_data = include_bytes!("rust-logo-256x256.png");
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(image_data).expect("Failed to load image");
|
||||
let image_buffer = image.to_rgba8();
|
||||
let size = (image.width() as usize, image.height() as usize);
|
||||
let size = [image.width() as usize, image.height() as usize];
|
||||
let pixels = image_buffer.into_vec();
|
||||
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
||||
let pixels: Vec<_> = pixels
|
||||
.chunks_exact(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
|
||||
|
||||
// Allocate a texture:
|
||||
let texture = frame
|
||||
.tex_allocator()
|
||||
.alloc_srgba_premultiplied(size, &pixels);
|
||||
let size = egui::Vec2::new(size.0 as f32, size.1 as f32);
|
||||
let texture = frame.alloc_texture(image);
|
||||
let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
|
||||
self.texture = Some((size, texture));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
//! "My egui App"
|
||||
//! }
|
||||
//!
|
||||
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
|
@ -127,7 +127,7 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
|
|||
/// "My egui App"
|
||||
/// }
|
||||
///
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
|
|
|
@ -9,5 +9,6 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
|||
* Remove `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
First stand-alone release. Previously part of `egui_glium`.
|
||||
|
|
|
@ -55,9 +55,10 @@ pub fn handle_app_output(
|
|||
window: &winit::window::Window,
|
||||
current_pixels_per_point: f32,
|
||||
app_output: epi::backend::AppOutput,
|
||||
) {
|
||||
) -> epi::backend::TexAllocationData {
|
||||
let epi::backend::AppOutput {
|
||||
quit: _,
|
||||
tex_allocation_data,
|
||||
window_size,
|
||||
window_title,
|
||||
decorated,
|
||||
|
@ -85,6 +86,8 @@ pub fn handle_app_output(
|
|||
if drag_window {
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
|
||||
tex_allocation_data
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -186,13 +189,11 @@ impl Persistence {
|
|||
|
||||
/// Everything needed to make a winit-based integration for [`epi`].
|
||||
pub struct EpiIntegration {
|
||||
integration_name: &'static str,
|
||||
frame: epi::Frame,
|
||||
persistence: crate::epi::Persistence,
|
||||
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
|
||||
pub egui_ctx: egui::CtxRef,
|
||||
egui_winit: crate::State,
|
||||
pub app: Box<dyn epi::App>,
|
||||
latest_frame_time: Option<f32>,
|
||||
/// When set, it is time to quit
|
||||
quit: bool,
|
||||
}
|
||||
|
@ -201,8 +202,7 @@ impl EpiIntegration {
|
|||
pub fn new(
|
||||
integration_name: &'static str,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
|
||||
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
|
||||
persistence: crate::epi::Persistence,
|
||||
app: Box<dyn epi::App>,
|
||||
) -> Self {
|
||||
|
@ -210,54 +210,50 @@ impl EpiIntegration {
|
|||
|
||||
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
||||
|
||||
let mut slf = Self {
|
||||
integration_name,
|
||||
persistence,
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: integration_name,
|
||||
web_info: None,
|
||||
prefer_dark_mode: None, // TODO: figure out system default
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
},
|
||||
output: Default::default(),
|
||||
repaint_signal,
|
||||
});
|
||||
|
||||
let mut slf = Self {
|
||||
frame,
|
||||
persistence,
|
||||
egui_ctx,
|
||||
egui_winit: crate::State::new(window),
|
||||
app,
|
||||
latest_frame_time: None,
|
||||
quit: false,
|
||||
};
|
||||
|
||||
slf.setup(window, tex_allocator);
|
||||
slf.setup(window);
|
||||
if slf.app.warm_up_enabled() {
|
||||
slf.warm_up(window, tex_allocator);
|
||||
slf.warm_up(window);
|
||||
}
|
||||
|
||||
slf
|
||||
}
|
||||
|
||||
fn setup(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
) {
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(self.integration_name, window, None),
|
||||
tex_allocator,
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.repaint_signal.clone(),
|
||||
}
|
||||
.build();
|
||||
fn setup(&mut self, window: &winit::window::Window) {
|
||||
self.app
|
||||
.setup(&self.egui_ctx, &mut frame, self.persistence.storage());
|
||||
|
||||
.setup(&self.egui_ctx, &self.frame, self.persistence.storage());
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
let tex_alloc_data =
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // Do it later
|
||||
}
|
||||
|
||||
fn warm_up(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
) {
|
||||
fn warm_up(&mut self, window: &winit::window::Window) {
|
||||
let saved_memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
self.update(window, tex_allocator);
|
||||
let (_, tex_alloc_data, _) = self.update(window);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame
|
||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
|
@ -277,37 +273,31 @@ impl EpiIntegration {
|
|||
pub fn update(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
) -> (bool, Vec<egui::epaint::ClippedShape>) {
|
||||
) -> (
|
||||
bool,
|
||||
epi::backend::TexAllocationData,
|
||||
Vec<egui::epaint::ClippedShape>,
|
||||
) {
|
||||
let frame_start = std::time::Instant::now();
|
||||
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(self.integration_name, window, self.latest_frame_time),
|
||||
tex_allocator,
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.repaint_signal.clone(),
|
||||
}
|
||||
.build();
|
||||
|
||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
self.app.update(egui_ctx, &mut frame);
|
||||
self.app.update(egui_ctx, &self.frame);
|
||||
});
|
||||
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
self.egui_winit
|
||||
.handle_output(window, &self.egui_ctx, egui_output);
|
||||
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
let tex_allocation_data =
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
|
||||
let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||
self.latest_frame_time = Some(frame_time);
|
||||
self.frame.lock().info.cpu_usage = Some(frame_time);
|
||||
|
||||
(needs_repaint, shapes)
|
||||
(needs_repaint, tex_allocation_data, shapes)
|
||||
}
|
||||
|
||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||
|
@ -321,17 +311,3 @@ impl EpiIntegration {
|
|||
.save(&mut *self.app, &self.egui_ctx, window);
|
||||
}
|
||||
}
|
||||
|
||||
fn integration_info(
|
||||
integration_name: &'static str,
|
||||
window: &winit::window::Window,
|
||||
previous_frame_time: Option<f32>,
|
||||
) -> epi::IntegrationInfo {
|
||||
epi::IntegrationInfo {
|
||||
name: integration_name,
|
||||
web_info: None,
|
||||
prefer_dark_mode: None, // TODO: figure out system default
|
||||
cpu_usage: previous_frame_time,
|
||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for ColorTest {
|
|||
"🎨 Color test"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
|
@ -43,18 +43,14 @@ impl epi::App for ColorTest {
|
|||
ui.separator();
|
||||
}
|
||||
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.ui(ui, &mut Some(frame.tex_allocator()));
|
||||
self.ui(ui, Some(frame));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorTest {
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
) {
|
||||
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
|
||||
ui.set_max_width(680.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -105,10 +101,10 @@ impl ColorTest {
|
|||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", WHITE, &g);
|
||||
}
|
||||
if let Some(tex_allocator) = &mut tex_allocator {
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let g = Gradient::one_color(Color32::from(tex_color));
|
||||
let tex = self.tex_mngr.get(*tex_allocator, &g);
|
||||
let tex = self.tex_mngr.get(tex_allocator, &g);
|
||||
let texel_offset = 0.5 / (g.0.len() as f32);
|
||||
let uv =
|
||||
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
|
@ -167,7 +163,7 @@ impl ColorTest {
|
|||
fn show_gradients(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
bg_fill: Color32,
|
||||
(left, right): (Color32, Color32),
|
||||
) {
|
||||
|
@ -261,7 +257,7 @@ impl ColorTest {
|
|||
fn tex_gradient(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
label: &str,
|
||||
bg_fill: Color32,
|
||||
gradient: &Gradient,
|
||||
|
@ -271,7 +267,7 @@ impl ColorTest {
|
|||
}
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let tex = self.tex_mngr.get(*tex_allocator, gradient);
|
||||
let tex = self.tex_mngr.get(tex_allocator, gradient);
|
||||
let texel_offset = 0.5 / (gradient.0.len() as f32);
|
||||
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
|
||||
|
@ -391,16 +387,15 @@ impl Gradient {
|
|||
struct TextureManager(HashMap<Gradient, TextureId>);
|
||||
|
||||
impl TextureManager {
|
||||
fn get(
|
||||
&mut self,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
gradient: &Gradient,
|
||||
) -> TextureId {
|
||||
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
|
||||
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||
let pixels = gradient.to_pixel_row();
|
||||
let width = pixels.len();
|
||||
let height = 1;
|
||||
tex_allocator.alloc_srgba_premultiplied((width, height), &pixels)
|
||||
tex_allocator.alloc(epi::Image {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ impl epi::App for DemoApp {
|
|||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &mut epi::Frame<'_>,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
#[cfg(feature = "persistence")]
|
||||
|
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
|
|||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ impl epi::App for FractalClock {
|
|||
"🕑 Fractal Clock"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::dark_canvas(&ctx.style()))
|
||||
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
|
||||
|
|
|
@ -7,7 +7,7 @@ struct Resource {
|
|||
text: Option<String>,
|
||||
|
||||
/// If set, the response was an image.
|
||||
image: Option<Image>,
|
||||
image: Option<epi::Image>,
|
||||
|
||||
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
|
||||
colored_text: Option<ColoredText>,
|
||||
|
@ -17,7 +17,7 @@ impl Resource {
|
|||
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
let image = if content_type.starts_with("image/") {
|
||||
Image::decode(&response.bytes)
|
||||
decode_image(&response.bytes)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -67,7 +67,7 @@ impl epi::App for HttpApp {
|
|||
"⬇ HTTP"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if let Some(receiver) = &mut self.in_progress {
|
||||
// Are we there yet?
|
||||
if let Ok(result) = receiver.try_recv() {
|
||||
|
@ -95,13 +95,13 @@ impl epi::App for HttpApp {
|
|||
|
||||
if trigger_fetch {
|
||||
let request = ehttp::Request::get(&self.url);
|
||||
let repaint_signal = frame.repaint_signal();
|
||||
let frame = frame.clone();
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
self.in_progress = Some(receiver);
|
||||
|
||||
ehttp::fetch(request, move |response| {
|
||||
sender.send(response).ok();
|
||||
repaint_signal.request_repaint();
|
||||
frame.request_repaint();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ impl epi::App for HttpApp {
|
|||
}
|
||||
}
|
||||
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bool {
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -160,12 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bo
|
|||
trigger_fetch
|
||||
}
|
||||
|
||||
fn ui_resource(
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut epi::Frame<'_>,
|
||||
tex_mngr: &mut TexMngr,
|
||||
resource: &Resource,
|
||||
) {
|
||||
fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
|
||||
let Resource {
|
||||
response,
|
||||
text,
|
||||
|
@ -218,7 +213,7 @@ fn ui_resource(
|
|||
|
||||
if let Some(image) = image {
|
||||
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) {
|
||||
let mut size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32);
|
||||
let mut size = egui::Vec2::new(image.size[0] as f32, image.size[1] as f32);
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
ui.image(texture_id, size);
|
||||
}
|
||||
|
@ -304,44 +299,27 @@ struct TexMngr {
|
|||
impl TexMngr {
|
||||
fn texture(
|
||||
&mut self,
|
||||
frame: &mut epi::Frame<'_>,
|
||||
frame: &epi::Frame,
|
||||
url: &str,
|
||||
image: &Image,
|
||||
image: &epi::Image,
|
||||
) -> Option<egui::TextureId> {
|
||||
if self.loaded_url != url {
|
||||
if let Some(texture_id) = self.texture_id.take() {
|
||||
frame.tex_allocator().free(texture_id);
|
||||
frame.free_texture(texture_id);
|
||||
}
|
||||
|
||||
self.texture_id = Some(
|
||||
frame
|
||||
.tex_allocator()
|
||||
.alloc_srgba_premultiplied(image.size, &image.pixels),
|
||||
);
|
||||
self.texture_id = Some(frame.alloc_texture(image.clone()));
|
||||
self.loaded_url = url.to_owned();
|
||||
}
|
||||
self.texture_id
|
||||
}
|
||||
}
|
||||
|
||||
struct Image {
|
||||
size: (usize, usize),
|
||||
pixels: Vec<egui::Color32>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn decode(bytes: &[u8]) -> Option<Image> {
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(bytes).ok()?;
|
||||
let image_buffer = image.to_rgba8();
|
||||
let size = (image.width() as usize, image.height() as usize);
|
||||
let pixels = image_buffer.into_vec();
|
||||
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
||||
let pixels = pixels
|
||||
.chunks(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
|
||||
Some(Image { size, pixels })
|
||||
}
|
||||
fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(bytes).ok()?;
|
||||
let image_buffer = image.to_rgba8();
|
||||
let size = [image.width() as usize, image.height() as usize];
|
||||
let pixels = image_buffer.into_vec();
|
||||
Some(epi::Image::from_rgba_unmultiplied(size, &pixels))
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ impl Default for BackendPanel {
|
|||
}
|
||||
|
||||
impl BackendPanel {
|
||||
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
self.frame_history
|
||||
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
|
||||
|
||||
|
@ -92,7 +92,7 @@ impl BackendPanel {
|
|||
self.egui_windows.windows(ctx);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
egui::trace!(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("💻 Backend");
|
||||
|
@ -147,7 +147,7 @@ impl BackendPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
if frame.is_web() {
|
||||
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||
ui.label(
|
||||
|
@ -170,13 +170,13 @@ impl BackendPanel {
|
|||
}
|
||||
}
|
||||
|
||||
show_integration_name(ui, frame.info());
|
||||
show_integration_name(ui, &frame.info());
|
||||
|
||||
// For instance: `egui_web` sets `pixels_per_point` every frame to force
|
||||
// egui to use the same scale as the web zoom factor.
|
||||
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
|
||||
if !integration_controls_pixels_per_point {
|
||||
if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, frame.info()) {
|
||||
if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, &frame.info()) {
|
||||
ui.ctx().set_pixels_per_point(new_pixels_per_point);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
|
|||
"🖹 EasyMark editor"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||
|
|
|
@ -45,7 +45,7 @@ impl epi::App for WrapApp {
|
|||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &mut epi::Frame<'_>,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
#[cfg(feature = "persistence")]
|
||||
|
@ -72,7 +72,7 @@ impl epi::App for WrapApp {
|
|||
cfg!(not(debug_assertions))
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if let Some(web_info) = frame.info().web_info.as_ref() {
|
||||
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
|
||||
self.selected_anchor = anchor.to_owned();
|
||||
|
@ -126,7 +126,7 @@ impl epi::App for WrapApp {
|
|||
}
|
||||
|
||||
impl WrapApp {
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
// A menu-bar is a horizontal layout with some special styles applied.
|
||||
// egui::menu::bar(ui, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
|
|
|
@ -6,6 +6,7 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
|||
* Simplify `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Remove `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `glium` to 0.31 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
* Changed the `Painter` inteface slightly ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
use crate::*;
|
||||
use egui::Color32;
|
||||
use glium::glutin;
|
||||
|
||||
impl epi::TextureAllocator for Painter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture();
|
||||
self.set_user_texture(id, size, srgba_pixels);
|
||||
id
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id);
|
||||
}
|
||||
}
|
||||
use crate::*;
|
||||
|
||||
struct RequestRepaintEvent;
|
||||
|
||||
|
@ -24,7 +8,7 @@ struct GliumRepaintSignal(
|
|||
std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>,
|
||||
);
|
||||
|
||||
impl epi::RepaintSignal for GliumRepaintSignal {
|
||||
impl epi::backend::RepaintSignal for GliumRepaintSignal {
|
||||
fn request_repaint(&self) {
|
||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
||||
}
|
||||
|
@ -64,7 +48,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
"egui_glium",
|
||||
display.gl_window().window(),
|
||||
&mut painter,
|
||||
repaint_signal,
|
||||
persistence,
|
||||
app,
|
||||
|
@ -83,10 +66,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let (needs_repaint, shapes) =
|
||||
integration.update(display.gl_window().window(), &mut painter);
|
||||
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||
integration.update(display.gl_window().window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
painter.set_texture(&display, id, &image);
|
||||
}
|
||||
|
||||
// paint:
|
||||
{
|
||||
use glium::Surface as _;
|
||||
let mut target = display.draw();
|
||||
|
@ -104,6 +92,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
target.finish().unwrap();
|
||||
}
|
||||
|
||||
for id in tex_allocation_data.destructions.drain(..) {
|
||||
painter.free_texture(id);
|
||||
}
|
||||
|
||||
{
|
||||
*control_flow = if integration.should_quit() {
|
||||
glutin::event_loop::ControlFlow::Exit
|
||||
|
|
|
@ -14,7 +14,7 @@ use {
|
|||
uniform,
|
||||
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
||||
},
|
||||
std::rc::Rc,
|
||||
std::{collections::HashMap, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct Painter {
|
||||
|
@ -22,19 +22,11 @@ pub struct Painter {
|
|||
egui_texture: Option<SrgbTexture2d>,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserTexture {
|
||||
/// Pending upload (will be emptied later).
|
||||
/// This is the format glium likes.
|
||||
pixels: Vec<Vec<(u8, u8, u8, u8)>>,
|
||||
|
||||
/// Lazily uploaded from [`Self::pixels`],
|
||||
/// or owned by the user via `register_native_texture`.
|
||||
gl_texture: Option<Rc<SrgbTexture2d>>,
|
||||
// TODO: 128-bit texture space?
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
|
@ -65,6 +57,7 @@ impl Painter {
|
|||
egui_texture: None,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +99,6 @@ impl Painter {
|
|||
egui_texture: &egui::Texture,
|
||||
) {
|
||||
self.upload_egui_texture(display, egui_texture);
|
||||
self.upload_pending_user_textures(display);
|
||||
|
||||
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
||||
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
|
||||
|
@ -114,7 +106,7 @@ impl Painter {
|
|||
}
|
||||
|
||||
#[inline(never)] // Easier profiling
|
||||
pub fn paint_mesh<T: glium::Surface>(
|
||||
fn paint_mesh<T: glium::Surface>(
|
||||
&mut self,
|
||||
target: &mut T,
|
||||
display: &glium::Display,
|
||||
|
@ -229,82 +221,40 @@ impl Painter {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// user textures: this is an experimental feature.
|
||||
// No need to implement this in your egui integration!
|
||||
|
||||
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return egui::TextureId::User(i as u64);
|
||||
}
|
||||
}
|
||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set_user_texture(
|
||||
pub fn set_texture(
|
||||
&mut self,
|
||||
id: egui::TextureId,
|
||||
size: (usize, usize),
|
||||
pixels: &[Color32],
|
||||
facade: &dyn glium::backend::Facade,
|
||||
tex_id: u64,
|
||||
image: &epi::Image,
|
||||
) {
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
pixels.len(),
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
||||
.chunks(size.0 as usize)
|
||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||
.collect();
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = image
|
||||
.pixels
|
||||
.chunks(image.size[0] as usize)
|
||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||
.collect();
|
||||
|
||||
*user_texture = UserTexture {
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture.into());
|
||||
}
|
||||
|
||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref()
|
||||
.map(|rc| rc.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
user_texture.gl_texture = Some(
|
||||
SrgbTexture2d::with_format(facade, pixels, format, mipmaps)
|
||||
.unwrap()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -314,26 +264,15 @@ impl epi::NativeTexture for Painter {
|
|||
type Texture = Rc<SrgbTexture2d>;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture();
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
pixels: vec![],
|
||||
gl_texture: Some(native),
|
||||
}
|
||||
}
|
||||
}
|
||||
id
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
pixels: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
};
|
||||
}
|
||||
self.user_textures.insert(id, replacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
|||
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Remove `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `glutin` to 0.28 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
* Changed the `Painter` inteface slightly ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -4,7 +4,7 @@ struct RequestRepaintEvent;
|
|||
|
||||
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>);
|
||||
|
||||
impl epi::RepaintSignal for GlowRepaintSignal {
|
||||
impl epi::backend::RepaintSignal for GlowRepaintSignal {
|
||||
fn request_repaint(&self) {
|
||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
"egui_glow",
|
||||
gl_window.window(),
|
||||
&mut painter,
|
||||
repaint_signal,
|
||||
persistence,
|
||||
app,
|
||||
|
@ -83,9 +82,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let (needs_repaint, shapes) = integration.update(gl_window.window(), &mut painter);
|
||||
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||
integration.update(gl_window.window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
painter.set_texture(&gl, id, &image);
|
||||
}
|
||||
|
||||
// paint:
|
||||
{
|
||||
let color = integration.app.clear_color();
|
||||
unsafe {
|
||||
|
@ -96,8 +101,8 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
}
|
||||
painter.upload_egui_texture(&gl, &integration.egui_ctx.texture());
|
||||
painter.paint_meshes(
|
||||
gl_window.window().inner_size().into(),
|
||||
&gl,
|
||||
gl_window.window().inner_size().into(),
|
||||
integration.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
);
|
||||
|
@ -105,6 +110,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
for id in tex_allocation_data.destructions.drain(..) {
|
||||
painter.free_texture(id);
|
||||
}
|
||||
|
||||
{
|
||||
*control_flow = if integration.should_quit() {
|
||||
glutin::event_loop::ControlFlow::Exit
|
||||
|
|
|
@ -166,8 +166,8 @@ impl EguiGlow {
|
|||
self.painter
|
||||
.upload_egui_texture(gl, &self.egui_ctx.texture());
|
||||
self.painter.paint_meshes(
|
||||
dimensions,
|
||||
gl,
|
||||
dimensions,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#![allow(unsafe_code)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use egui::{
|
||||
emath::Rect,
|
||||
epaint::{Color32, Mesh, Vertex},
|
||||
};
|
||||
pub use glow::Context;
|
||||
|
||||
use memoffset::offset_of;
|
||||
|
||||
use glow::HasContext;
|
||||
use memoffset::offset_of;
|
||||
|
||||
use crate::misc_util::{
|
||||
as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d,
|
||||
|
@ -17,6 +16,8 @@ use crate::post_process::PostProcess;
|
|||
use crate::shader_version::ShaderVersion;
|
||||
use crate::vao_emulate;
|
||||
|
||||
pub use glow::Context;
|
||||
|
||||
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
||||
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
|
||||
|
||||
|
@ -34,29 +35,21 @@ pub struct Painter {
|
|||
is_embedded: bool,
|
||||
vertex_array: crate::misc_util::VAO,
|
||||
srgb_support: bool,
|
||||
/// `None` means unallocated (freed) slot.
|
||||
pub(crate) user_textures: Vec<Option<UserTexture>>,
|
||||
post_process: Option<PostProcess>,
|
||||
vertex_buffer: glow::Buffer,
|
||||
element_array_buffer: glow::Buffer,
|
||||
|
||||
// Stores outdated OpenGL textures that are yet to be deleted
|
||||
old_textures: Vec<glow::Texture>,
|
||||
// Only used in debug builds, to make sure we are destroyed correctly.
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, glow::Texture>,
|
||||
// TODO: 128-bit texture space?
|
||||
next_native_tex_id: u64,
|
||||
/// Stores outdated OpenGL textures that are yet to be deleted
|
||||
textures_to_destroy: Vec<glow::Texture>,
|
||||
|
||||
/// Only used in debug builds, to make sure we are destroyed correctly.
|
||||
destroyed: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct UserTexture {
|
||||
/// Pending upload (will be emptied later).
|
||||
/// This is the format glow likes.
|
||||
pub(crate) data: Vec<u8>,
|
||||
pub(crate) size: (usize, usize),
|
||||
|
||||
/// Lazily uploaded
|
||||
pub(crate) gl_texture: Option<glow::Texture>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
/// Create painter.
|
||||
///
|
||||
|
@ -195,11 +188,12 @@ impl Painter {
|
|||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||
vertex_array,
|
||||
srgb_support,
|
||||
user_textures: Default::default(),
|
||||
post_process,
|
||||
vertex_buffer,
|
||||
element_array_buffer,
|
||||
old_textures: Vec::new(),
|
||||
user_textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
textures_to_destroy: Vec::new(),
|
||||
destroyed: false,
|
||||
})
|
||||
}
|
||||
|
@ -298,15 +292,13 @@ impl Painter {
|
|||
/// of the effects your program might have on this code. Look at the source if in doubt.
|
||||
pub fn paint_meshes(
|
||||
&mut self,
|
||||
inner_size: [u32; 2],
|
||||
gl: &glow::Context,
|
||||
inner_size: [u32; 2],
|
||||
pixels_per_point: f32,
|
||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
) {
|
||||
//chimera of egui_glow and egui_web
|
||||
self.assert_not_destroyed();
|
||||
|
||||
self.upload_pending_user_textures(gl);
|
||||
if let Some(ref mut post_process) = self.post_process {
|
||||
unsafe {
|
||||
post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32);
|
||||
|
@ -393,128 +385,47 @@ impl Painter {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// user textures: this is an experimental feature.
|
||||
// No need to implement this in your egui integration!
|
||||
|
||||
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||
pub fn set_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return egui::TextureId::User(i as u64);
|
||||
}
|
||||
}
|
||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
id
|
||||
}
|
||||
|
||||
/// register glow texture as egui texture
|
||||
/// Usable for render to image rectangle
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn register_glow_texture(&mut self, texture: glow::Texture) -> egui::TextureId {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
let id = self.alloc_user_texture();
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
if let UserTexture {
|
||||
gl_texture: Some(old_tex),
|
||||
..
|
||||
} = std::mem::replace(
|
||||
user_texture,
|
||||
UserTexture {
|
||||
data: vec![],
|
||||
size: (0, 0),
|
||||
gl_texture: Some(texture),
|
||||
},
|
||||
) {
|
||||
self.old_textures.push(old_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set_user_texture(
|
||||
&mut self,
|
||||
id: egui::TextureId,
|
||||
size: (usize, usize),
|
||||
pixels: &[Color32],
|
||||
) {
|
||||
self.assert_not_destroyed();
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
pixels.len(),
|
||||
"Mismatch between size and texel count"
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
let data: Vec<u8> = pixels
|
||||
.iter()
|
||||
.flat_map(|srgba| Vec::from(srgba.to_array()))
|
||||
.collect();
|
||||
// TODO: optimize
|
||||
let pixels: Vec<u8> = image
|
||||
.pixels
|
||||
.iter()
|
||||
.flat_map(|srgba| Vec::from(srgba.to_array()))
|
||||
.collect();
|
||||
|
||||
if let UserTexture {
|
||||
gl_texture: Some(old_tex),
|
||||
..
|
||||
} = std::mem::replace(
|
||||
user_texture,
|
||||
UserTexture {
|
||||
data,
|
||||
size,
|
||||
gl_texture: None,
|
||||
},
|
||||
) {
|
||||
self.old_textures.push(old_tex);
|
||||
}
|
||||
}
|
||||
let gl_texture = srgbtexture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
&pixels,
|
||||
image.size[0],
|
||||
image.size[1],
|
||||
);
|
||||
|
||||
if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => self.egui_texture,
|
||||
egui::TextureId::User(id) => self.user_textures.get(id as usize)?.as_ref()?.gl_texture,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_pending_user_textures(&mut self, gl: &glow::Context) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let data = std::mem::take(&mut user_texture.data);
|
||||
user_texture.gl_texture = Some(srgbtexture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
&data,
|
||||
user_texture.size.0,
|
||||
user_texture.size.1,
|
||||
));
|
||||
user_texture.size = (0, 0);
|
||||
}
|
||||
}
|
||||
for t in self.old_textures.drain(..) {
|
||||
unsafe {
|
||||
gl.delete_texture(t);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id).copied(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -523,14 +434,12 @@ impl Painter {
|
|||
if let Some(tex) = self.egui_texture {
|
||||
gl.delete_texture(tex);
|
||||
}
|
||||
for tex in self.user_textures.iter().flatten() {
|
||||
if let Some(t) = tex.gl_texture {
|
||||
gl.delete_texture(t);
|
||||
}
|
||||
for tex in self.user_textures.values() {
|
||||
gl.delete_texture(*tex);
|
||||
}
|
||||
gl.delete_buffer(self.vertex_buffer);
|
||||
gl.delete_buffer(self.element_array_buffer);
|
||||
for t in &self.old_textures {
|
||||
for t in &self.textures_to_destroy {
|
||||
gl.delete_texture(*t);
|
||||
}
|
||||
}
|
||||
|
@ -580,39 +489,25 @@ impl Drop for Painter {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
impl epi::TextureAllocator for Painter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture();
|
||||
self.set_user_texture(id, size, srgba_pixels);
|
||||
id
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
impl epi::NativeTexture for Painter {
|
||||
type Texture = glow::Texture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
self.register_glow_texture(native)
|
||||
self.assert_not_destroyed();
|
||||
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
|
||||
self.user_textures.insert(id, native);
|
||||
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
data: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
size: (0, 0),
|
||||
};
|
||||
if let Some(old_tex) = self.user_textures.insert(id, replacing) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
|
|||
* Fix [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)).
|
||||
* Add feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
### Added
|
||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||
|
|
|
@ -67,7 +67,7 @@ impl NeedRepaint {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::RepaintSignal for NeedRepaint {
|
||||
impl epi::backend::RepaintSignal for NeedRepaint {
|
||||
fn request_repaint(&self) {
|
||||
self.0.store(true, SeqCst);
|
||||
}
|
||||
|
@ -76,28 +76,44 @@ impl epi::RepaintSignal for NeedRepaint {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct AppRunner {
|
||||
frame: epi::Frame,
|
||||
egui_ctx: egui::CtxRef,
|
||||
painter: Box<dyn Painter>,
|
||||
previous_frame_time: Option<f32>,
|
||||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
storage: LocalStorage,
|
||||
prefer_dark_mode: Option<bool>,
|
||||
last_save_time: f64,
|
||||
screen_reader: crate::screen_reader::ScreenReader,
|
||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||
pub(crate) mutable_text_under_cursor: bool,
|
||||
pending_texture_destructions: Vec<u64>,
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
||||
let egui_ctx = egui::CtxRef::default();
|
||||
|
||||
load_memory(&egui_ctx);
|
||||
let painter = create_painter(canvas_id)?;
|
||||
|
||||
let prefer_dark_mode = crate::prefer_dark_mode();
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: painter.name(),
|
||||
web_info: Some(epi::WebInfo {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
}),
|
||||
prefer_dark_mode,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
},
|
||||
output: Default::default(),
|
||||
repaint_signal: needs_repaint.clone(),
|
||||
});
|
||||
|
||||
let egui_ctx = egui::CtxRef::default();
|
||||
load_memory(&egui_ctx);
|
||||
if prefer_dark_mode == Some(true) {
|
||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
} else {
|
||||
|
@ -107,32 +123,24 @@ impl AppRunner {
|
|||
let storage = LocalStorage::default();
|
||||
|
||||
let mut runner = Self {
|
||||
frame,
|
||||
egui_ctx,
|
||||
painter: create_painter(canvas_id)?,
|
||||
previous_frame_time: None,
|
||||
painter,
|
||||
input: Default::default(),
|
||||
app,
|
||||
needs_repaint: Default::default(),
|
||||
needs_repaint,
|
||||
storage,
|
||||
prefer_dark_mode,
|
||||
last_save_time: now_sec(),
|
||||
screen_reader: Default::default(),
|
||||
text_cursor_pos: None,
|
||||
mutable_text_under_cursor: false,
|
||||
pending_texture_destructions: Default::default(),
|
||||
};
|
||||
|
||||
{
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: runner.integration_info(),
|
||||
tex_allocator: runner.painter.as_tex_allocator(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: runner.needs_repaint.clone(),
|
||||
}
|
||||
.build();
|
||||
runner
|
||||
.app
|
||||
.setup(&runner.egui_ctx, &mut frame, Some(&runner.storage));
|
||||
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage));
|
||||
}
|
||||
|
||||
Ok(runner)
|
||||
|
@ -170,18 +178,6 @@ impl AppRunner {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn integration_info(&self) -> epi::IntegrationInfo {
|
||||
epi::IntegrationInfo {
|
||||
name: self.painter.name(),
|
||||
web_info: Some(epi::WebInfo {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
}),
|
||||
prefer_dark_mode: self.prefer_dark_mode,
|
||||
cpu_usage: self.previous_frame_time,
|
||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
|
||||
let frame_start = now_sec();
|
||||
|
||||
|
@ -189,33 +185,31 @@ impl AppRunner {
|
|||
let canvas_size = canvas_size_in_points(self.canvas_id());
|
||||
let raw_input = self.input.new_frame(canvas_size);
|
||||
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: self.integration_info(),
|
||||
tex_allocator: self.painter.as_tex_allocator(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.needs_repaint.clone(),
|
||||
}
|
||||
.build();
|
||||
|
||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
self.app.update(egui_ctx, &mut frame);
|
||||
self.app.update(egui_ctx, &self.frame);
|
||||
});
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
||||
self.handle_egui_output(&egui_output);
|
||||
|
||||
{
|
||||
let app_output = self.frame.take_app_output();
|
||||
let epi::backend::AppOutput {
|
||||
quit: _, // Can't quit a web page
|
||||
window_size: _, // Can't resize a web page
|
||||
window_title: _, // TODO: change title of window
|
||||
decorated: _, // Can't toggle decorations
|
||||
drag_window: _, // Can't be dragged
|
||||
tex_allocation_data,
|
||||
} = app_output;
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
self.painter.set_texture(id, image);
|
||||
}
|
||||
self.pending_texture_destructions = tex_allocation_data.destructions;
|
||||
}
|
||||
|
||||
self.previous_frame_time = Some((now_sec() - frame_start) as f32);
|
||||
self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
Ok((egui_output, clipped_meshes))
|
||||
}
|
||||
|
||||
|
@ -223,7 +217,11 @@ impl AppRunner {
|
|||
self.painter.upload_egui_texture(&self.egui_ctx.texture());
|
||||
self.painter.clear(self.app.clear_color());
|
||||
self.painter
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())?;
|
||||
for id in self.pending_texture_destructions.drain(..) {
|
||||
self.painter.free_texture(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_egui_output(&mut self, output: &egui::Output) {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::web_sys::WebGl2RenderingContext;
|
||||
use crate::web_sys::WebGlRenderingContext;
|
||||
use crate::{canvas_element_or_die, console_error};
|
||||
use egui::{ClippedMesh, Rgba, Texture};
|
||||
use egui_glow::glow;
|
||||
use epi::TextureAllocator;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use web_sys::WebGl2RenderingContext;
|
||||
use web_sys::WebGlRenderingContext;
|
||||
|
||||
pub(crate) struct WrappedGlowPainter {
|
||||
pub(crate) gl_ctx: glow::Context,
|
||||
|
@ -67,8 +66,12 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool {
|
|||
}
|
||||
|
||||
impl crate::Painter for WrappedGlowPainter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator {
|
||||
&mut self.painter
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
self.painter.set_texture(&self.gl_ctx, tex_id, &image);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.painter.free_texture(tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
@ -99,8 +102,8 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
) -> Result<(), JsValue> {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
self.painter.paint_meshes(
|
||||
canvas_dimension,
|
||||
&self.gl_ctx,
|
||||
canvas_dimension,
|
||||
pixels_per_point,
|
||||
clipped_meshes,
|
||||
);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use wasm_bindgen::prelude::JsValue;
|
||||
|
||||
pub trait Painter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator;
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image);
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64);
|
||||
|
||||
fn debug_info(&self) -> String;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use {
|
||||
js_sys::WebAssembly,
|
||||
wasm_bindgen::{prelude::*, JsCast},
|
||||
|
@ -29,19 +31,11 @@ pub struct WebGlPainter {
|
|||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, WebGlTexture>,
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserTexture {
|
||||
size: (usize, usize),
|
||||
|
||||
/// Pending upload (will be emptied later).
|
||||
pixels: Vec<u8>,
|
||||
|
||||
/// Lazily uploaded
|
||||
gl_texture: Option<WebGlTexture>,
|
||||
// TODO: 128-bit texture space?
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl WebGlPainter {
|
||||
|
@ -111,109 +105,14 @@ impl WebGlPainter {
|
|||
egui_texture,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
fn alloc_user_texture_index(&mut self) -> usize {
|
||||
for (index, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return index;
|
||||
}
|
||||
}
|
||||
let index = self.user_textures.len();
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
index
|
||||
}
|
||||
|
||||
fn alloc_user_texture(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let index = self.alloc_user_texture_index();
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
srgba_pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(index) {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||
for srgba in srgba_pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
*user_texture = UserTexture {
|
||||
size,
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
|
||||
egui::TextureId::User(index as u64)
|
||||
}
|
||||
|
||||
fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_user_textures(&mut self) {
|
||||
let gl = &self.gl;
|
||||
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = self.texture_format;
|
||||
let border = 0;
|
||||
let src_format = self.texture_format;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
user_texture.size.0 as i32,
|
||||
user_texture.size.1 as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
user_texture.gl_texture = Some(gl_texture);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,51 +237,75 @@ impl WebGlPainter {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::TextureAllocator for WebGlPainter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[egui::Color32],
|
||||
) -> egui::TextureId {
|
||||
self.alloc_user_texture(size, srgba_pixels)
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGlPainter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture_index();
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(native),
|
||||
}
|
||||
}
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
}
|
||||
if let Some(user_texture) = self.user_textures.get_mut(&id) {
|
||||
*user_texture = replacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGlPainter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
|
||||
self
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
// TODO: optimize
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(image.pixels.len() * 4);
|
||||
for srgba in image.pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let gl = &self.gl;
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = self.texture_format;
|
||||
let border = 0;
|
||||
let src_format = self.texture_format;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
image.size[0] as _,
|
||||
image.size[1] as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
@ -467,8 +390,6 @@ impl crate::Painter for WebGlPainter {
|
|||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
self.upload_user_textures();
|
||||
|
||||
let gl = &self.gl;
|
||||
|
||||
if let Some(ref mut post_process) = self.post_process {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! Mostly a carbon-copy of `webgl1.rs`.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use {
|
||||
js_sys::WebAssembly,
|
||||
|
@ -30,19 +31,11 @@ pub struct WebGl2Painter {
|
|||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, WebGlTexture>,
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserTexture {
|
||||
size: (usize, usize),
|
||||
|
||||
/// Pending upload (will be emptied later).
|
||||
pixels: Vec<u8>,
|
||||
|
||||
/// Lazily uploaded
|
||||
gl_texture: Option<WebGlTexture>,
|
||||
// TODO: 128-bit texture space?
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl WebGl2Painter {
|
||||
|
@ -96,109 +89,14 @@ impl WebGl2Painter {
|
|||
egui_texture,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
fn alloc_user_texture_index(&mut self) -> usize {
|
||||
for (index, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return index;
|
||||
}
|
||||
}
|
||||
let index = self.user_textures.len();
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
index
|
||||
}
|
||||
|
||||
fn alloc_user_texture(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let index = self.alloc_user_texture_index();
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
srgba_pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(index) {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||
for srgba in srgba_pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
*user_texture = UserTexture {
|
||||
size,
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
|
||||
egui::TextureId::User(index as u64)
|
||||
}
|
||||
|
||||
fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_user_textures(&mut self) {
|
||||
let gl = &self.gl;
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = Gl::SRGB8_ALPHA8;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
user_texture.size.0 as i32,
|
||||
user_texture.size.1 as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
user_texture.gl_texture = Some(gl_texture);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,51 +221,75 @@ impl WebGl2Painter {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::TextureAllocator for WebGl2Painter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[egui::Color32],
|
||||
) -> egui::TextureId {
|
||||
self.alloc_user_texture(size, srgba_pixels)
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGl2Painter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture_index();
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(native),
|
||||
}
|
||||
}
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
}
|
||||
if let Some(user_texture) = self.user_textures.get_mut(&id) {
|
||||
*user_texture = replacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGl2Painter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
|
||||
self
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
// TODO: optimize
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(image.pixels.len() * 4);
|
||||
for srgba in image.pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let gl = &self.gl;
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = Gl::SRGB8_ALPHA8;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
image.size[0] as _,
|
||||
image.size[1] as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
@ -448,8 +370,6 @@ impl crate::Painter for WebGl2Painter {
|
|||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
self.upload_user_textures();
|
||||
|
||||
let gl = &self.gl;
|
||||
|
||||
self.post_process
|
||||
|
|
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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue