mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -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:
@@ -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| {
|
||||
|
||||
Reference in New Issue
Block a user