Texture loading in egui (#1110)
* Move texture allocation into epaint/egui proper * Add TextureHandle * egui_glow: cast using bytemuck instead of unsafe code * Optimize glium painter * Optimize WebGL * Add example of loading an image from file
This commit is contained in:
parent
6c616a1b69
commit
66d80e2519
|
|
@ -8,6 +8,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
## Unreleased
|
||||
|
||||
### Added ⭐
|
||||
* `Context::load_texture` to convert an image into a texture which can be displayed using e.g. `ui.image(texture, size)` ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Added `Ui::add_visible` and `Ui::add_visible_ui`.
|
||||
|
||||
### Changed 🔧
|
||||
|
|
@ -18,6 +19,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
* `Context` can now be cloned and stored between frames ([#1050](https://github.com/emilk/egui/pull/1050)).
|
||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
|
||||
* For integrations: `FontImage` has been replaced by `TexturesDelta` (found in `Output`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
|
||||
|
|
|
|||
|
|
@ -841,6 +841,8 @@ dependencies = [
|
|||
name = "egui_glium"
|
||||
version = "0.16.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
"egui",
|
||||
"egui-winit",
|
||||
"epi",
|
||||
|
|
@ -852,6 +854,7 @@ dependencies = [
|
|||
name = "egui_glow"
|
||||
version = "0.16.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"egui",
|
||||
"egui-winit",
|
||||
"epi",
|
||||
|
|
@ -867,6 +870,7 @@ dependencies = [
|
|||
name = "egui_web"
|
||||
version = "0.16.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"egui",
|
||||
"egui_glow",
|
||||
"epi",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Removed `Frame::alloc_texture`. Use `egui::Context::load_texture` instead ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use eframe::{egui, epi};
|
|||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
texture: Option<(egui::Vec2, egui::TextureId)>,
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
|
|
@ -12,31 +12,18 @@ impl epi::App for MyApp {
|
|||
"Show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, 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 pixels = image_buffer.into_vec();
|
||||
let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
|
||||
|
||||
// Allocate a texture:
|
||||
let texture = frame.alloc_texture(image);
|
||||
let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
|
||||
self.texture = Some((size, texture));
|
||||
}
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
let image = load_image(include_bytes!("rust-logo-256x256.png")).unwrap();
|
||||
ctx.load_texture("rust-logo", image)
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if let Some((size, texture)) = self.texture {
|
||||
ui.heading("This is an image:");
|
||||
ui.image(texture, size);
|
||||
ui.heading("This is an image:");
|
||||
ui.image(texture, texture.size_vec2());
|
||||
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(texture, size));
|
||||
}
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(texture, texture.size_vec2()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -45,3 +32,15 @@ fn main() {
|
|||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
use image::GenericImageView as _;
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let image_buffer = image.to_rgba8();
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,9 @@ 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,
|
||||
|
|
@ -86,8 +85,6 @@ pub fn handle_app_output(
|
|||
if drag_window {
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
|
||||
tex_allocation_data
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -244,16 +241,14 @@ impl EpiIntegration {
|
|||
.setup(&self.egui_ctx, &self.frame, self.persistence.storage());
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
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
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
}
|
||||
|
||||
fn warm_up(&mut self, window: &winit::window::Window) {
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
let (_, tex_alloc_data, _) = self.update(window);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame
|
||||
let (_, textures_delta, _) = self.update(window);
|
||||
self.egui_ctx.output().textures_delta = textures_delta; // 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();
|
||||
}
|
||||
|
|
@ -273,11 +268,7 @@ impl EpiIntegration {
|
|||
pub fn update(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
) -> (
|
||||
bool,
|
||||
epi::backend::TexAllocationData,
|
||||
Vec<egui::epaint::ClippedShape>,
|
||||
) {
|
||||
) -> (bool, egui::TexturesDelta, Vec<egui::epaint::ClippedShape>) {
|
||||
let frame_start = instant::Instant::now();
|
||||
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
|
|
@ -286,18 +277,19 @@ impl EpiIntegration {
|
|||
});
|
||||
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
self.egui_winit
|
||||
let textures_delta = self
|
||||
.egui_winit
|
||||
.handle_output(window, &self.egui_ctx, egui_output);
|
||||
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
let tex_allocation_data =
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
|
||||
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||
self.frame.lock().info.cpu_usage = Some(frame_time);
|
||||
|
||||
(needs_repaint, tex_allocation_data, shapes)
|
||||
(needs_repaint, textures_delta, shapes)
|
||||
}
|
||||
|
||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||
|
|
|
|||
|
|
@ -512,26 +512,39 @@ impl State {
|
|||
window: &winit::window::Window,
|
||||
egui_ctx: &egui::Context,
|
||||
output: egui::Output,
|
||||
) {
|
||||
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
||||
|
||||
) -> egui::TexturesDelta {
|
||||
if egui_ctx.memory().options.screen_reader {
|
||||
self.screen_reader.speak(&output.events_description());
|
||||
}
|
||||
|
||||
self.set_cursor_icon(window, output.cursor_icon);
|
||||
let egui::Output {
|
||||
cursor_icon,
|
||||
open_url,
|
||||
copied_text,
|
||||
needs_repaint: _, // needs to be handled elsewhere
|
||||
events: _, // handled above
|
||||
mutable_text_under_cursor: _, // only used in egui_web
|
||||
text_cursor_pos,
|
||||
textures_delta,
|
||||
} = output;
|
||||
|
||||
if let Some(open) = output.open_url {
|
||||
open_url(&open.url);
|
||||
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
||||
|
||||
self.set_cursor_icon(window, cursor_icon);
|
||||
|
||||
if let Some(open_url) = open_url {
|
||||
open_url_in_browser(&open_url.url);
|
||||
}
|
||||
|
||||
if !output.copied_text.is_empty() {
|
||||
self.clipboard.set(output.copied_text);
|
||||
if !copied_text.is_empty() {
|
||||
self.clipboard.set(copied_text);
|
||||
}
|
||||
|
||||
if let Some(egui::Pos2 { x, y }) = output.text_cursor_pos {
|
||||
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
|
||||
window.set_ime_position(winit::dpi::LogicalPosition { x, y });
|
||||
}
|
||||
|
||||
textures_delta
|
||||
}
|
||||
|
||||
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
||||
|
|
@ -554,7 +567,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
fn open_url(_url: &str) {
|
||||
fn open_url_in_browser(_url: &str) {
|
||||
#[cfg(feature = "webbrowser")]
|
||||
if let Err(err) = webbrowser::open(_url) {
|
||||
eprintln!("Failed to open url: {}", err);
|
||||
|
|
|
|||
|
|
@ -2,18 +2,39 @@
|
|||
|
||||
use crate::{
|
||||
animation_manager::AnimationManager, data::output::Output, frame_state::FrameState,
|
||||
input_state::*, layers::GraphicLayers, *,
|
||||
input_state::*, layers::GraphicLayers, TextureHandle, *,
|
||||
};
|
||||
use epaint::{mutex::*, stats::*, text::Fonts, *};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct WrappedTextureManager(Arc<RwLock<epaint::TextureManager>>);
|
||||
|
||||
impl Default for WrappedTextureManager {
|
||||
fn default() -> Self {
|
||||
let mut tex_mngr = epaint::textures::TextureManager::default();
|
||||
|
||||
// Will be filled in later
|
||||
let font_id = tex_mngr.alloc(
|
||||
"egui_font_texture".into(),
|
||||
epaint::AlphaImage::new([0, 0]).into(),
|
||||
);
|
||||
assert_eq!(font_id, TextureId::default());
|
||||
|
||||
Self(Arc::new(RwLock::new(tex_mngr)))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContextImpl {
|
||||
/// `None` until the start of the first frame.
|
||||
fonts: Option<Fonts>,
|
||||
memory: Memory,
|
||||
animation_manager: AnimationManager,
|
||||
latest_font_image_version: Option<u64>,
|
||||
tex_manager: WrappedTextureManager,
|
||||
|
||||
input: InputState,
|
||||
|
||||
|
|
@ -157,7 +178,7 @@ impl Context {
|
|||
///
|
||||
/// You can alternatively run [`Self::begin_frame`] and [`Context::end_frame`].
|
||||
///
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// // One egui context that you keep reusing:
|
||||
/// let mut ctx = egui::Context::default();
|
||||
///
|
||||
|
|
@ -183,7 +204,7 @@ impl Context {
|
|||
|
||||
/// An alternative to calling [`Self::run`].
|
||||
///
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// // One egui context that you keep reusing:
|
||||
/// let mut ctx = egui::Context::default();
|
||||
///
|
||||
|
|
@ -492,14 +513,6 @@ impl Context {
|
|||
self.write().repaint_requests = 2;
|
||||
}
|
||||
|
||||
/// The egui font image, containing font characters etc.
|
||||
///
|
||||
/// Not valid until first call to [`Context::run()`].
|
||||
/// That's because since we don't know the proper `pixels_per_point` until then.
|
||||
pub fn font_image(&self) -> Arc<epaint::FontImage> {
|
||||
self.fonts().font_image()
|
||||
}
|
||||
|
||||
/// Tell `egui` which fonts to use.
|
||||
///
|
||||
/// The default `egui` fonts only support latin and cyrillic alphabets,
|
||||
|
|
@ -593,6 +606,54 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
/// Allocate a texture.
|
||||
///
|
||||
/// In order to display an image you must convert it to a texture using this function.
|
||||
///
|
||||
/// Make sure to only call this once for each image, i.e. NOT in your main GUI code.
|
||||
///
|
||||
/// The given name can be useful for later debugging, and will be visible if you call [`Self::texture_ui`].
|
||||
///
|
||||
/// For how to load an image, see [`ImageData`] and [`ColorImage::from_rgba_unmultiplied`].
|
||||
///
|
||||
/// ```
|
||||
/// struct MyImage {
|
||||
/// texture: Option<egui::TextureHandle>,
|
||||
/// }
|
||||
///
|
||||
/// impl MyImage {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // Load the texture only once.
|
||||
/// ui.ctx().load_texture("my-image", egui::ColorImage::example())
|
||||
/// });
|
||||
///
|
||||
/// // Show the image:
|
||||
/// ui.image(texture, texture.size_vec2());
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Se also [`crate::ImageData`], [`crate::Ui::image`] and [`crate::ImageButton`].
|
||||
pub fn load_texture(
|
||||
&self,
|
||||
name: impl Into<String>,
|
||||
image: impl Into<ImageData>,
|
||||
) -> TextureHandle {
|
||||
let tex_mngr = self.tex_manager();
|
||||
let tex_id = tex_mngr.write().alloc(name.into(), image.into());
|
||||
TextureHandle::new(tex_mngr, tex_id)
|
||||
}
|
||||
|
||||
/// Low-level texture manager.
|
||||
///
|
||||
/// In general it is easier to use [`Self::load_texture`] and [`TextureHandle`].
|
||||
///
|
||||
/// You can show stats about the allocated textures using [`Self::texture_ui`].
|
||||
pub fn tex_manager(&self) -> Arc<RwLock<epaint::textures::TextureManager>> {
|
||||
self.read().tex_manager.0.clone()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// Constrain the position of a window/area so it fits within the provided boundary.
|
||||
|
|
@ -640,14 +701,30 @@ impl Context {
|
|||
self.request_repaint();
|
||||
}
|
||||
|
||||
self.fonts().end_frame();
|
||||
|
||||
{
|
||||
let ctx_impl = &mut *self.write();
|
||||
ctx_impl
|
||||
.memory
|
||||
.end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids);
|
||||
}
|
||||
|
||||
self.fonts().end_frame();
|
||||
let font_image = ctx_impl.fonts.as_ref().unwrap().font_image();
|
||||
let font_image_version = font_image.version;
|
||||
|
||||
if Some(font_image_version) != ctx_impl.latest_font_image_version {
|
||||
ctx_impl
|
||||
.tex_manager
|
||||
.0
|
||||
.write()
|
||||
.set(TextureId::default(), font_image.image.clone().into());
|
||||
ctx_impl.latest_font_image_version = Some(font_image_version);
|
||||
}
|
||||
ctx_impl
|
||||
.output
|
||||
.textures_delta
|
||||
.append(ctx_impl.tex_manager.0.write().take_delta());
|
||||
}
|
||||
|
||||
let mut output: Output = std::mem::take(&mut self.output());
|
||||
if self.read().repaint_requests > 0 {
|
||||
|
|
@ -936,11 +1013,59 @@ impl Context {
|
|||
});
|
||||
|
||||
CollapsingHeader::new("📊 Paint stats")
|
||||
.default_open(true)
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
let paint_stats = self.write().paint_stats;
|
||||
paint_stats.ui(ui);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("🖼 Textures")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
self.texture_ui(ui);
|
||||
});
|
||||
}
|
||||
|
||||
/// Show stats about the allocated textures.
|
||||
pub fn texture_ui(&self, ui: &mut crate::Ui) {
|
||||
let tex_mngr = self.tex_manager();
|
||||
let tex_mngr = tex_mngr.read();
|
||||
|
||||
let mut textures: Vec<_> = tex_mngr.allocated().collect();
|
||||
textures.sort_by_key(|(id, _)| *id);
|
||||
|
||||
let mut bytes = 0;
|
||||
for (_, tex) in &textures {
|
||||
bytes += tex.bytes_used();
|
||||
}
|
||||
|
||||
ui.label(format!(
|
||||
"{} allocated texture(s), using {:.1} MB",
|
||||
textures.len(),
|
||||
bytes as f64 * 1e-6
|
||||
));
|
||||
|
||||
ui.group(|ui| {
|
||||
ScrollArea::vertical()
|
||||
.max_height(300.0)
|
||||
.auto_shrink([false, true])
|
||||
.show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||
Grid::new("textures")
|
||||
.striped(true)
|
||||
.num_columns(3)
|
||||
.spacing(Vec2::new(16.0, 2.0))
|
||||
.show(ui, |ui| {
|
||||
for (_id, texture) in &textures {
|
||||
let [w, h] = texture.size;
|
||||
ui.label(format!("{} x {}", w, h));
|
||||
ui.label(format!("{:.3} MB", texture.bytes_used() as f64 * 1e-6));
|
||||
ui.label(format!("{:?}", texture.name));
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn memory_ui(&self, ui: &mut crate::Ui) {
|
||||
|
|
|
|||
|
|
@ -375,10 +375,13 @@ impl RawInput {
|
|||
}
|
||||
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
||||
ui.label(format!("modifiers: {:#?}", modifiers));
|
||||
ui.label(format!("events: {:?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
ui.label(format!("hovered_files: {}", hovered_files.len()));
|
||||
ui.label(format!("dropped_files: {}", dropped_files.len()));
|
||||
ui.scope(|ui| {
|
||||
ui.set_min_height(150.0);
|
||||
ui.label(format!("events: {:#?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ pub struct Output {
|
|||
|
||||
/// Screen-space position of text edit cursor (used for IME).
|
||||
pub text_cursor_pos: Option<crate::Pos2>,
|
||||
|
||||
/// Texture changes since last frame.
|
||||
pub textures_delta: epaint::textures::TexturesDelta,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
|
|
@ -71,6 +74,7 @@ impl Output {
|
|||
mut events,
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
textures_delta,
|
||||
} = newer;
|
||||
|
||||
self.cursor_icon = cursor_icon;
|
||||
|
|
@ -84,6 +88,7 @@ impl Output {
|
|||
self.events.append(&mut events);
|
||||
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
||||
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
|
||||
self.textures_delta.append(textures_delta);
|
||||
}
|
||||
|
||||
/// Take everything ephemeral (everything except `cursor_icon` currently)
|
||||
|
|
|
|||
|
|
@ -730,8 +730,11 @@ impl InputState {
|
|||
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
||||
ui.label(format!("modifiers: {:#?}", modifiers));
|
||||
ui.label(format!("keys_down: {:?}", keys_down));
|
||||
ui.label(format!("events: {:?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
ui.scope(|ui| {
|
||||
ui.set_min_height(150.0);
|
||||
ui.label(format!("events: {:#?}", events))
|
||||
.on_hover_text("key presses etc");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,16 @@ impl Widget for &epaint::FontImage {
|
|||
|
||||
ui.vertical(|ui| {
|
||||
// Show font texture in demo Ui
|
||||
let [width, height] = self.size();
|
||||
|
||||
ui.label(format!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
self.width, self.height
|
||||
width, height
|
||||
));
|
||||
if self.width <= 1 || self.height <= 1 {
|
||||
if width <= 1 || height <= 1 {
|
||||
return;
|
||||
}
|
||||
let mut size = vec2(self.width as f32, self.height as f32);
|
||||
let mut size = vec2(width as f32, height as f32);
|
||||
if size.x > ui.available_width() {
|
||||
size *= ui.available_width() / size.x;
|
||||
}
|
||||
|
|
@ -27,7 +29,7 @@ impl Widget for &epaint::FontImage {
|
|||
);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
|
||||
let (tex_w, tex_h) = (self.width as f32, self.height as f32);
|
||||
let (tex_w, tex_h) = (width as f32, height as f32);
|
||||
|
||||
response
|
||||
.on_hover_cursor(CursorIcon::ZoomIn)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
//!
|
||||
//! ### Quick start
|
||||
//!
|
||||
//! ``` rust
|
||||
//! ```
|
||||
//! # egui::__run_test_ui(|ui| {
|
||||
//! # let mut my_string = String::new();
|
||||
//! # let mut my_boolean = true;
|
||||
|
|
@ -218,7 +218,7 @@
|
|||
//! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`].
|
||||
//! 3. Use a justified layout:
|
||||
//!
|
||||
//! ``` rust
|
||||
//! ```
|
||||
//! # egui::__run_test_ui(|ui| {
|
||||
//! ui.with_layout(egui::Layout::top_down_justified(egui::Align::Center), |ui| {
|
||||
//! ui.button("I am becoming wider as needed");
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
//!
|
||||
//! 4. Fill in extra space with emptiness:
|
||||
//!
|
||||
//! ``` rust
|
||||
//! ```
|
||||
//! # egui::__run_test_ui(|ui| {
|
||||
//! ui.allocate_space(ui.available_size()); // put this LAST in your panel/window code
|
||||
//! # });
|
||||
|
|
@ -386,7 +386,9 @@ pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos
|
|||
pub use epaint::{
|
||||
color, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, TextStyle},
|
||||
ClippedMesh, Color32, FontImage, Rgba, Shape, Stroke, TextureId,
|
||||
textures::TexturesDelta,
|
||||
AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Rgba, Shape, Stroke, TextureHandle,
|
||||
TextureId,
|
||||
};
|
||||
|
||||
pub mod text {
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ impl Response {
|
|||
|
||||
/// Response to secondary clicks (right-clicks) by showing the given menu.
|
||||
///
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let response = ui.label("Right-click me!");
|
||||
/// response.context_menu(|ui| {
|
||||
|
|
|
|||
|
|
@ -1341,9 +1341,30 @@ impl Ui {
|
|||
|
||||
/// Show an image here with the given size.
|
||||
///
|
||||
/// See also [`Image`].
|
||||
/// In order to display an image you must first acquire a [`TextureHandle`]
|
||||
/// using [`Context::load_texture`].
|
||||
///
|
||||
/// ```
|
||||
/// struct MyImage {
|
||||
/// texture: Option<egui::TextureHandle>,
|
||||
/// }
|
||||
///
|
||||
/// impl MyImage {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // Load the texture only once.
|
||||
/// ui.ctx().load_texture("my-image", egui::ColorImage::example())
|
||||
/// });
|
||||
///
|
||||
/// // Show the image:
|
||||
/// ui.image(texture, texture.size_vec2());
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Se also [`crate::Image`] and [`crate::ImageButton`].
|
||||
#[inline]
|
||||
pub fn image(&mut self, texture_id: TextureId, size: impl Into<Vec2>) -> Response {
|
||||
pub fn image(&mut self, texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Response {
|
||||
Image::new(texture_id, size).ui(self)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,11 +109,11 @@ where
|
|||
/// `(time, value)` pairs
|
||||
/// Time difference between values can be zero, but never negative.
|
||||
// TODO: impl IntoIter
|
||||
pub fn iter(&'_ self) -> impl Iterator<Item = (f64, T)> + '_ {
|
||||
pub fn iter(&'_ self) -> impl ExactSizeIterator<Item = (f64, T)> + '_ {
|
||||
self.values.iter().map(|(time, value)| (*time, *value))
|
||||
}
|
||||
|
||||
pub fn values(&'_ self) -> impl Iterator<Item = T> + '_ {
|
||||
pub fn values(&'_ self) -> impl ExactSizeIterator<Item = T> + '_ {
|
||||
self.values.iter().map(|(_time, value)| *value)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ pub struct ImageButton {
|
|||
}
|
||||
|
||||
impl ImageButton {
|
||||
pub fn new(texture_id: TextureId, size: impl Into<Vec2>) -> Self {
|
||||
pub fn new(texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Self {
|
||||
Self {
|
||||
image: widgets::Image::new(texture_id, size),
|
||||
sense: Sense::click(),
|
||||
|
|
|
|||
|
|
@ -2,17 +2,31 @@ use crate::*;
|
|||
|
||||
/// An widget to show an image of a given size.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let my_texture_id = egui::TextureId::User(0);
|
||||
/// ui.add(egui::Image::new(my_texture_id, [640.0, 480.0]));
|
||||
/// In order to display an image you must first acquire a [`TextureHandle`]
|
||||
/// using [`Context::load_texture`].
|
||||
///
|
||||
/// // Shorter version:
|
||||
/// ui.image(my_texture_id, [640.0, 480.0]);
|
||||
/// # });
|
||||
/// ```
|
||||
/// struct MyImage {
|
||||
/// texture: Option<egui::TextureHandle>,
|
||||
/// }
|
||||
///
|
||||
/// impl MyImage {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
/// let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
/// // Load the texture only once.
|
||||
/// ui.ctx().load_texture("my-image", egui::ColorImage::example())
|
||||
/// });
|
||||
///
|
||||
/// // Show the image:
|
||||
/// ui.add(egui::Image::new(texture, texture.size_vec2()));
|
||||
///
|
||||
/// // Shorter version:
|
||||
/// ui.image(texture, texture.size_vec2());
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Se also [`crate::ImageButton`].
|
||||
/// Se also [`crate::Ui::image`] and [`crate::ImageButton`].
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Image {
|
||||
|
|
@ -25,9 +39,9 @@ pub struct Image {
|
|||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(texture_id: TextureId, size: impl Into<Vec2>) -> Self {
|
||||
pub fn new(texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Self {
|
||||
Self {
|
||||
texture_id,
|
||||
texture_id: texture_id.into(),
|
||||
uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
|
||||
size: size.into(),
|
||||
bg_fill: Default::default(),
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl Label {
|
|||
/// By calling this you can turn the label into a button of sorts.
|
||||
/// This will also give the label the hover-effect of a button, but without the frame.
|
||||
///
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// # use egui::{Label, Sense};
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// if ui.add(Label::new("click me").sense(Sense::click())).clicked() {
|
||||
|
|
@ -82,7 +82,9 @@ impl Label {
|
|||
}
|
||||
|
||||
let valign = ui.layout().vertical_align();
|
||||
let mut text_job = self.text.into_text_job(ui.style(), TextStyle::Body, valign);
|
||||
let mut text_job = self
|
||||
.text
|
||||
.into_text_job(ui.style(), ui.style().body_text_style, valign);
|
||||
|
||||
let should_wrap = self.wrap.unwrap_or_else(|| ui.wrap_text());
|
||||
let available_width = ui.available_width();
|
||||
|
|
|
|||
|
|
@ -1087,12 +1087,12 @@ pub struct PlotImage {
|
|||
|
||||
impl PlotImage {
|
||||
/// Create a new image with position and size in plot coordinates.
|
||||
pub fn new(texture_id: TextureId, position: Value, size: impl Into<Vec2>) -> Self {
|
||||
pub fn new(texture_id: impl Into<TextureId>, position: Value, size: impl Into<Vec2>) -> Self {
|
||||
Self {
|
||||
position,
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
texture_id,
|
||||
texture_id: texture_id.into(),
|
||||
uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
|
||||
size: size.into(),
|
||||
bg_fill: Default::default(),
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ pub enum MarkerShape {
|
|||
|
||||
impl MarkerShape {
|
||||
/// Get a vector containing all marker shapes.
|
||||
pub fn all() -> impl Iterator<Item = MarkerShape> {
|
||||
pub fn all() -> impl ExactSizeIterator<Item = MarkerShape> {
|
||||
[
|
||||
Self::Circle,
|
||||
Self::Diamond,
|
||||
|
|
|
|||
|
|
@ -43,14 +43,14 @@ impl epi::App for ColorTest {
|
|||
ui.separator();
|
||||
}
|
||||
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.ui(ui, Some(frame));
|
||||
self.ui(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorTest {
|
||||
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.set_max_width(680.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
|
|
@ -70,13 +70,7 @@ impl ColorTest {
|
|||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||
let g = Gradient::one_color(Color32::from_rgb(255, 165, 0));
|
||||
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"orange rgb(255, 165, 0) - texture",
|
||||
WHITE,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "orange rgb(255, 165, 0) - texture", WHITE, &g);
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
|
@ -99,20 +93,18 @@ impl ColorTest {
|
|||
{
|
||||
let g = Gradient::one_color(Color32::from(tex_color * vertex_color));
|
||||
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) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let g = Gradient::one_color(Color32::from(tex_color));
|
||||
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));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
|
||||
.on_hover_text(format!("A texture that is {} texels wide", g.0.len()));
|
||||
ui.label("GPU result");
|
||||
});
|
||||
self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g);
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let g = Gradient::one_color(Color32::from(tex_color));
|
||||
let tex = self.tex_mngr.get(ui.ctx(), &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));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
|
||||
.on_hover_text(format!("A texture that is {} texels wide", g.0.len()));
|
||||
ui.label("GPU result");
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
|
@ -120,18 +112,18 @@ impl ColorTest {
|
|||
// TODO: test color multiplication (image tint),
|
||||
// to make sure vertex and texture color multiplication is done in linear space.
|
||||
|
||||
self.show_gradients(ui, tex_allocator, WHITE, (RED, GREEN));
|
||||
self.show_gradients(ui, WHITE, (RED, GREEN));
|
||||
if self.srgb {
|
||||
ui.label("Notice the darkening in the center of the naive sRGB interpolation.");
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.show_gradients(ui, tex_allocator, RED, (TRANSPARENT, GREEN));
|
||||
self.show_gradients(ui, RED, (TRANSPARENT, GREEN));
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.show_gradients(ui, tex_allocator, WHITE, (TRANSPARENT, GREEN));
|
||||
self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN));
|
||||
if self.srgb {
|
||||
ui.label(
|
||||
"Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.",
|
||||
|
|
@ -142,15 +134,14 @@ impl ColorTest {
|
|||
|
||||
// TODO: another ground truth where we do the alpha-blending against the background also.
|
||||
// TODO: exactly the same thing, but with vertex colors (no textures)
|
||||
self.show_gradients(ui, tex_allocator, WHITE, (TRANSPARENT, BLACK));
|
||||
self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK));
|
||||
ui.separator();
|
||||
self.show_gradients(ui, tex_allocator, BLACK, (TRANSPARENT, WHITE));
|
||||
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
|
||||
ui.separator();
|
||||
|
||||
ui.label("Additive blending: add more and more blue to the red background:");
|
||||
self.show_gradients(
|
||||
ui,
|
||||
tex_allocator,
|
||||
RED,
|
||||
(TRANSPARENT, Color32::from_rgb_additive(0, 0, 255)),
|
||||
);
|
||||
|
|
@ -160,13 +151,7 @@ impl ColorTest {
|
|||
pixel_test(ui);
|
||||
}
|
||||
|
||||
fn show_gradients(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
bg_fill: Color32,
|
||||
(left, right): (Color32, Color32),
|
||||
) {
|
||||
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
|
||||
let is_opaque = left.is_opaque() && right.is_opaque();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
|
@ -186,13 +171,7 @@ impl ColorTest {
|
|||
if is_opaque {
|
||||
let g = Gradient::ground_truth_linear_gradient(left, right);
|
||||
self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Ground Truth (CPU gradient) - texture",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "Ground Truth (CPU gradient) - texture", bg_fill, &g);
|
||||
} else {
|
||||
let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill);
|
||||
self.vertex_gradient(
|
||||
|
|
@ -203,20 +182,13 @@ impl ColorTest {
|
|||
);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Ground Truth (CPU gradient, CPU blending) - texture",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
let g = Gradient::ground_truth_linear_gradient(left, right);
|
||||
self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"CPU gradient, GPU blending - texture",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "CPU gradient, GPU blending - texture", bg_fill, &g);
|
||||
}
|
||||
|
||||
let g = Gradient::texture_gradient(left, right);
|
||||
|
|
@ -226,13 +198,7 @@ impl ColorTest {
|
|||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Texture of width 2 (test texture sampler)",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g);
|
||||
|
||||
if self.srgb {
|
||||
let g =
|
||||
|
|
@ -243,41 +209,26 @@ impl ColorTest {
|
|||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(
|
||||
ui,
|
||||
tex_allocator,
|
||||
"Naive sRGBA interpolation (WRONG)",
|
||||
bg_fill,
|
||||
&g,
|
||||
);
|
||||
self.tex_gradient(ui, "Naive sRGBA interpolation (WRONG)", bg_fill, &g);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn tex_gradient(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
label: &str,
|
||||
bg_fill: Color32,
|
||||
gradient: &Gradient,
|
||||
) {
|
||||
fn tex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) {
|
||||
if !self.texture_gradients {
|
||||
return;
|
||||
}
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
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))
|
||||
.on_hover_text(format!(
|
||||
"A texture that is {} texels wide",
|
||||
gradient.0.len()
|
||||
));
|
||||
ui.label(label);
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let tex = self.tex_mngr.get(ui.ctx(), 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))
|
||||
.on_hover_text(format!(
|
||||
"A texture that is {} texels wide",
|
||||
gradient.0.len()
|
||||
));
|
||||
ui.label(label);
|
||||
});
|
||||
}
|
||||
|
||||
fn vertex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) {
|
||||
|
|
@ -384,18 +335,21 @@ impl Gradient {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TextureManager(HashMap<Gradient, TextureId>);
|
||||
struct TextureManager(HashMap<Gradient, TextureHandle>);
|
||||
|
||||
impl TextureManager {
|
||||
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
|
||||
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||
fn get(&mut self, ctx: &egui::Context, gradient: &Gradient) -> &TextureHandle {
|
||||
self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||
let pixels = gradient.to_pixel_row();
|
||||
let width = pixels.len();
|
||||
let height = 1;
|
||||
tex_allocator.alloc(epi::Image {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
})
|
||||
ctx.load_texture(
|
||||
"color_test_gradient",
|
||||
epaint::ColorImage {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,9 +306,9 @@ impl Widget for &mut LegendDemo {
|
|||
}
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
struct ItemsDemo {}
|
||||
|
||||
impl ItemsDemo {}
|
||||
struct ItemsDemo {
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl Widget for &mut ItemsDemo {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
|
|
@ -343,12 +343,17 @@ impl Widget for &mut ItemsDemo {
|
|||
);
|
||||
Arrows::new(arrow_origins, arrow_tips)
|
||||
};
|
||||
|
||||
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
|
||||
ui.ctx()
|
||||
.load_texture("plot_demo", egui::ColorImage::example())
|
||||
});
|
||||
let image = PlotImage::new(
|
||||
TextureId::Egui,
|
||||
texture,
|
||||
Value::new(0.0, 10.0),
|
||||
[
|
||||
ui.fonts().font_image().width as f32 / 100.0,
|
||||
ui.fonts().font_image().height as f32 / 100.0,
|
||||
ui.fonts().font_image().width() as f32 / 100.0,
|
||||
ui.fonts().font_image().height() as f32 / 100.0,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ pub struct WidgetGallery {
|
|||
string: String,
|
||||
color: egui::Color32,
|
||||
animate_progress_bar: bool,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl Default for WidgetGallery {
|
||||
|
|
@ -30,6 +32,7 @@ impl Default for WidgetGallery {
|
|||
string: Default::default(),
|
||||
color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5),
|
||||
animate_progress_bar: false,
|
||||
texture: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -99,8 +102,14 @@ impl WidgetGallery {
|
|||
string,
|
||||
color,
|
||||
animate_progress_bar,
|
||||
texture,
|
||||
} = self;
|
||||
|
||||
let texture: &egui::TextureHandle = texture.get_or_insert_with(|| {
|
||||
ui.ctx()
|
||||
.load_texture("example", egui::ColorImage::example())
|
||||
});
|
||||
|
||||
ui.add(doc_link_label("Label", "label,heading"));
|
||||
ui.label("Welcome to the widget gallery!");
|
||||
ui.end_row();
|
||||
|
|
@ -180,17 +189,14 @@ impl WidgetGallery {
|
|||
ui.color_edit_button_srgba(color);
|
||||
ui.end_row();
|
||||
|
||||
let img_size = 16.0 * texture.size_vec2() / texture.size_vec2().y;
|
||||
|
||||
ui.add(doc_link_label("Image", "Image"));
|
||||
ui.image(egui::TextureId::Egui, [24.0, 16.0])
|
||||
.on_hover_text("The egui font texture was the convenient choice to show here.");
|
||||
ui.image(texture, img_size);
|
||||
ui.end_row();
|
||||
|
||||
ui.add(doc_link_label("ImageButton", "ImageButton"));
|
||||
if ui
|
||||
.add(egui::ImageButton::new(egui::TextureId::Egui, [24.0, 16.0]))
|
||||
.on_hover_text("The egui font texture was the convenient choice to show here.")
|
||||
.clicked()
|
||||
{
|
||||
if ui.add(egui::ImageButton::new(texture, img_size)).clicked() {
|
||||
*boolean = !*boolean;
|
||||
}
|
||||
ui.end_row();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ struct Resource {
|
|||
text: Option<String>,
|
||||
|
||||
/// If set, the response was an image.
|
||||
image: Option<epi::Image>,
|
||||
image: Option<egui::ImageData>,
|
||||
|
||||
/// 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/") {
|
||||
decode_image(&response.bytes)
|
||||
load_image(&response.bytes).ok().map(|img| img.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -112,7 +112,7 @@ impl epi::App for HttpApp {
|
|||
} else if let Some(result) = &self.result {
|
||||
match result {
|
||||
Ok(resource) => {
|
||||
ui_resource(ui, frame, &mut self.tex_mngr, resource);
|
||||
ui_resource(ui, &mut self.tex_mngr, resource);
|
||||
}
|
||||
Err(error) => {
|
||||
// This should only happen if the fetch API isn't available or something similar.
|
||||
|
|
@ -160,7 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool {
|
|||
trigger_fetch
|
||||
}
|
||||
|
||||
fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
|
||||
fn ui_resource(ui: &mut egui::Ui, tex_mngr: &mut TexMngr, resource: &Resource) {
|
||||
let Resource {
|
||||
response,
|
||||
text,
|
||||
|
|
@ -212,11 +212,10 @@ fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, re
|
|||
}
|
||||
|
||||
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);
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
ui.image(texture_id, size);
|
||||
}
|
||||
let texture = tex_mngr.texture(ui.ctx(), &response.url, image);
|
||||
let mut size = texture.size_vec2();
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
ui.image(texture, size);
|
||||
} else if let Some(colored_text) = colored_text {
|
||||
colored_text.ui(ui);
|
||||
} else if let Some(text) = &text {
|
||||
|
|
@ -293,33 +292,32 @@ impl ColoredText {
|
|||
#[derive(Default)]
|
||||
struct TexMngr {
|
||||
loaded_url: String,
|
||||
texture_id: Option<egui::TextureId>,
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl TexMngr {
|
||||
fn texture(
|
||||
&mut self,
|
||||
frame: &epi::Frame,
|
||||
ctx: &egui::Context,
|
||||
url: &str,
|
||||
image: &epi::Image,
|
||||
) -> Option<egui::TextureId> {
|
||||
if self.loaded_url != url {
|
||||
if let Some(texture_id) = self.texture_id.take() {
|
||||
frame.free_texture(texture_id);
|
||||
}
|
||||
|
||||
self.texture_id = Some(frame.alloc_texture(image.clone()));
|
||||
image: &egui::ImageData,
|
||||
) -> &egui::TextureHandle {
|
||||
if self.loaded_url != url || self.texture.is_none() {
|
||||
self.texture = Some(ctx.load_texture(url, image.clone()));
|
||||
self.loaded_url = url.to_owned();
|
||||
}
|
||||
self.texture_id
|
||||
self.texture.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(bytes).ok()?;
|
||||
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
|
||||
use image::GenericImageView as _;
|
||||
let image = image::load_from_memory(image_data)?;
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
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))
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
size,
|
||||
pixels.as_slice(),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ enum SyntectTheme {
|
|||
|
||||
#[cfg(feature = "syntect")]
|
||||
impl SyntectTheme {
|
||||
fn all() -> impl Iterator<Item = Self> {
|
||||
fn all() -> impl ExactSizeIterator<Item = Self> {
|
||||
[
|
||||
Self::Base16EightiesDark,
|
||||
Self::Base16MochaDark,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* `EguiGlium::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlium::paint` ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Optimize the painter and texture uploading ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
|
|
|
|||
|
|
@ -23,10 +23,15 @@ include = [
|
|||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = [
|
||||
"convert_bytemuck",
|
||||
"single_threaded",
|
||||
] }
|
||||
egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false, features = ["epi"] }
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
|
||||
ahash = "0.7"
|
||||
bytemuck = "1.7"
|
||||
glium = "0.31"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ fn main() {
|
|||
let mut redraw = || {
|
||||
let mut quit = false;
|
||||
|
||||
let (needs_repaint, shapes) = egui_glium.run(&display, |egui_ctx| {
|
||||
let needs_repaint = egui_glium.run(&display, |egui_ctx| {
|
||||
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
|
||||
if ui
|
||||
.add(egui::Button::image_and_text(
|
||||
|
|
@ -98,7 +98,7 @@ fn main() {
|
|||
|
||||
// draw things behind egui here
|
||||
|
||||
egui_glium.paint(&display, &mut target, shapes);
|
||||
egui_glium.paint(&display, &mut target);
|
||||
|
||||
// draw things on top of egui here
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ fn main() {
|
|||
let mut redraw = || {
|
||||
let mut quit = false;
|
||||
|
||||
let (needs_repaint, shapes) = egui_glium.run(&display, |egui_ctx| {
|
||||
let needs_repaint = egui_glium.run(&display, |egui_ctx| {
|
||||
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
|
||||
ui.heading("Hello World!");
|
||||
if ui.button("Quit").clicked() {
|
||||
|
|
@ -59,7 +59,7 @@ fn main() {
|
|||
|
||||
// draw things behind egui here
|
||||
|
||||
egui_glium.paint(&display, &mut target, shapes);
|
||||
egui_glium.paint(&display, &mut target);
|
||||
|
||||
// draw things on top of egui here
|
||||
|
||||
|
|
|
|||
|
|
@ -66,11 +66,11 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||
let (needs_repaint, mut textures_delta, shapes) =
|
||||
integration.update(display.gl_window().window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
for (id, image) in textures_delta.set {
|
||||
painter.set_texture(&display, id, &image);
|
||||
}
|
||||
|
||||
|
|
@ -86,13 +86,12 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
&mut target,
|
||||
integration.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
&integration.egui_ctx.font_image(),
|
||||
);
|
||||
|
||||
target.finish().unwrap();
|
||||
}
|
||||
|
||||
for id in tex_allocation_data.destructions.drain(..) {
|
||||
for id in textures_delta.free.drain(..) {
|
||||
painter.free_texture(id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ pub struct EguiGlium {
|
|||
pub egui_ctx: egui::Context,
|
||||
pub egui_winit: egui_winit::State,
|
||||
pub painter: crate::Painter,
|
||||
|
||||
shapes: Vec<egui::epaint::ClippedShape>,
|
||||
textures_delta: egui::TexturesDelta,
|
||||
}
|
||||
|
||||
impl EguiGlium {
|
||||
|
|
@ -112,6 +115,8 @@ impl EguiGlium {
|
|||
egui_ctx: Default::default(),
|
||||
egui_winit: egui_winit::State::new(display.gl_window().window()),
|
||||
painter: crate::Painter::new(display),
|
||||
shapes: Default::default(),
|
||||
textures_delta: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,35 +130,45 @@ impl EguiGlium {
|
|||
self.egui_winit.on_event(&self.egui_ctx, event)
|
||||
}
|
||||
|
||||
/// Returns `needs_repaint` and shapes to draw.
|
||||
pub fn run(
|
||||
&mut self,
|
||||
display: &glium::Display,
|
||||
run_ui: impl FnMut(&egui::Context),
|
||||
) -> (bool, Vec<egui::epaint::ClippedShape>) {
|
||||
/// Returns `true` if egui requests a repaint.
|
||||
///
|
||||
/// Call [`Self::paint`] later to paint.
|
||||
pub fn run(&mut self, display: &glium::Display, run_ui: impl FnMut(&egui::Context)) -> bool {
|
||||
let raw_input = self
|
||||
.egui_winit
|
||||
.take_egui_input(display.gl_window().window());
|
||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui);
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
self.egui_winit
|
||||
.handle_output(display.gl_window().window(), &self.egui_ctx, egui_output);
|
||||
(needs_repaint, shapes)
|
||||
let textures_delta = self.egui_winit.handle_output(
|
||||
display.gl_window().window(),
|
||||
&self.egui_ctx,
|
||||
egui_output,
|
||||
);
|
||||
|
||||
self.shapes = shapes;
|
||||
self.textures_delta.append(textures_delta);
|
||||
needs_repaint
|
||||
}
|
||||
|
||||
pub fn paint<T: glium::Surface>(
|
||||
&mut self,
|
||||
display: &glium::Display,
|
||||
target: &mut T,
|
||||
shapes: Vec<egui::epaint::ClippedShape>,
|
||||
) {
|
||||
/// Paint the results of the last call to [`Self::run`].
|
||||
pub fn paint<T: glium::Surface>(&mut self, display: &glium::Display, target: &mut T) {
|
||||
let shapes = std::mem::take(&mut self.shapes);
|
||||
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
|
||||
for (id, image) in textures_delta.set {
|
||||
self.painter.set_texture(display, id, &image);
|
||||
}
|
||||
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
self.painter.paint_meshes(
|
||||
display,
|
||||
target,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
&self.egui_ctx.font_image(),
|
||||
);
|
||||
|
||||
for id in textures_delta.free.drain(..) {
|
||||
self.painter.free_texture(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@
|
|||
#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro
|
||||
|
||||
use {
|
||||
egui::{
|
||||
emath::Rect,
|
||||
epaint::{Color32, Mesh},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
egui::{emath::Rect, epaint::Mesh},
|
||||
glium::{
|
||||
implement_vertex,
|
||||
index::PrimitiveType,
|
||||
|
|
@ -14,19 +12,17 @@ use {
|
|||
uniform,
|
||||
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
||||
},
|
||||
std::{collections::HashMap, rc::Rc},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct Painter {
|
||||
program: glium::Program,
|
||||
egui_texture: Option<SrgbTexture2d>,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
|
||||
textures: AHashMap<egui::TextureId, Rc<SrgbTexture2d>>,
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
/// [`egui::TextureId::User`] index
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
|
|
@ -54,40 +50,12 @@ impl Painter {
|
|||
|
||||
Painter {
|
||||
program,
|
||||
egui_texture: None,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: 1 << 32,
|
||||
next_native_tex_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_egui_texture(
|
||||
&mut self,
|
||||
facade: &dyn glium::backend::Facade,
|
||||
font_image: &egui::FontImage,
|
||||
) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = font_image
|
||||
.pixels
|
||||
.chunks(font_image.width as usize)
|
||||
.map(|row| {
|
||||
row.iter()
|
||||
.map(|&a| Color32::from_white_alpha(a).to_tuple())
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
self.egui_texture =
|
||||
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
/// Main entry-point for painting a frame.
|
||||
/// You should call `target.clear_color(..)` before
|
||||
/// and `target.finish()` after this.
|
||||
|
|
@ -97,10 +65,7 @@ impl Painter {
|
|||
target: &mut T,
|
||||
pixels_per_point: f32,
|
||||
cipped_meshes: Vec<egui::ClippedMesh>,
|
||||
font_image: &egui::FontImage,
|
||||
) {
|
||||
self.upload_egui_texture(display, font_image);
|
||||
|
||||
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
||||
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
|
||||
}
|
||||
|
|
@ -118,7 +83,8 @@ impl Painter {
|
|||
debug_assert!(mesh.is_valid());
|
||||
|
||||
let vertex_buffer = {
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Vertex {
|
||||
a_pos: [f32; 2],
|
||||
a_tc: [f32; 2],
|
||||
|
|
@ -126,18 +92,10 @@ impl Painter {
|
|||
}
|
||||
implement_vertex!(Vertex, a_pos, a_tc, a_srgba);
|
||||
|
||||
let vertices: Vec<Vertex> = mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|v| Vertex {
|
||||
a_pos: [v.pos.x, v.pos.y],
|
||||
a_tc: [v.uv.x, v.uv.y],
|
||||
a_srgba: v.color.to_array(),
|
||||
})
|
||||
.collect();
|
||||
let vertices: &[Vertex] = bytemuck::cast_slice(&mesh.vertices);
|
||||
|
||||
// TODO: we should probably reuse the `VertexBuffer` instead of allocating a new one each frame.
|
||||
glium::VertexBuffer::new(display, &vertices).unwrap()
|
||||
glium::VertexBuffer::new(display, vertices).unwrap()
|
||||
};
|
||||
|
||||
// TODO: we should probably reuse the `IndexBuffer` instead of allocating a new one each frame.
|
||||
|
|
@ -223,41 +181,48 @@ impl Painter {
|
|||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
pub fn set_texture(
|
||||
&mut self,
|
||||
facade: &dyn glium::backend::Facade,
|
||||
tex_id: u64,
|
||||
image: &epi::Image,
|
||||
tex_id: egui::TextureId,
|
||||
image: &egui::ImageData,
|
||||
) {
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
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();
|
||||
|
||||
let pixels: Vec<(u8, u8, u8, u8)> = match image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
image.pixels.iter().map(|color| color.to_tuple()).collect()
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = 1.0;
|
||||
image
|
||||
.srgba_pixels(gamma)
|
||||
.map(|color| color.to_tuple())
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
let glium_image = glium::texture::RawImage2d {
|
||||
data: std::borrow::Cow::Owned(pixels),
|
||||
width: image.width() as _,
|
||||
height: image.height() as _,
|
||||
format: glium::texture::ClientFormat::U8U8U8U8,
|
||||
};
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
||||
let gl_texture = SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture.into());
|
||||
self.textures.insert(tex_id, gl_texture.into());
|
||||
}
|
||||
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
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).map(|rc| rc.as_ref()),
|
||||
}
|
||||
self.textures.get(&texture_id).map(|rc| rc.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,15 +231,13 @@ impl epi::NativeTexture for Painter {
|
|||
type Texture = Rc<SrgbTexture2d>;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.next_native_tex_id;
|
||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
self.textures.insert(id, native);
|
||||
id
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
self.user_textures.insert(id, replacing);
|
||||
}
|
||||
self.textures.insert(id, replacing);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* `EguiGlow::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlow::paint` ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Added `set_texture_filter` method to `Painter` ((#1041)[https://github.com/emilk/egui/pull/1041]).
|
||||
* Fix failure to run in Chrome ((#1092)[https://github.com/emilk/egui/pull/1092]).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
* Simplified `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ include = [
|
|||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded", "convert_bytemuck"] }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = [
|
||||
"convert_bytemuck",
|
||||
"single_threaded",
|
||||
] }
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
|
||||
bytemuck = "1.7"
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
glow = "0.11"
|
||||
memoffset = "0.6"
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ fn main() {
|
|||
let mut redraw = || {
|
||||
let mut quit = false;
|
||||
|
||||
let (needs_repaint, shapes) = egui_glow.run(gl_window.window(), |egui_ctx| {
|
||||
let needs_repaint = egui_glow.run(gl_window.window(), |egui_ctx| {
|
||||
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
|
||||
ui.heading("Hello World!");
|
||||
if ui.button("Quit").clicked() {
|
||||
|
|
@ -78,7 +78,7 @@ fn main() {
|
|||
|
||||
// draw things behind egui here
|
||||
|
||||
egui_glow.paint(&gl_window, &gl, shapes);
|
||||
egui_glow.paint(&gl_window, &gl);
|
||||
|
||||
// draw things on top of egui here
|
||||
|
||||
|
|
|
|||
|
|
@ -82,11 +82,11 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||
let (needs_repaint, mut textures_delta, shapes) =
|
||||
integration.update(gl_window.window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
for (id, image) in textures_delta.set {
|
||||
painter.set_texture(&gl, id, &image);
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
gl.clear_color(color[0], color[1], color[2], color[3]);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
painter.upload_egui_texture(&gl, &integration.egui_ctx.font_image());
|
||||
painter.paint_meshes(
|
||||
&gl,
|
||||
gl_window.window().inner_size().into(),
|
||||
|
|
@ -110,8 +109,8 @@ 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);
|
||||
for id in textures_delta.free.drain(..) {
|
||||
painter.free_texture(&gl, id);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -112,6 +112,9 @@ pub struct EguiGlow {
|
|||
pub egui_ctx: egui::Context,
|
||||
pub egui_winit: egui_winit::State,
|
||||
pub painter: crate::Painter,
|
||||
|
||||
shapes: Vec<egui::epaint::ClippedShape>,
|
||||
textures_delta: egui::TexturesDelta,
|
||||
}
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
|
|
@ -128,6 +131,8 @@ impl EguiGlow {
|
|||
eprintln!("some error occurred in initializing painter\n{}", error);
|
||||
})
|
||||
.unwrap(),
|
||||
shapes: Default::default(),
|
||||
textures_delta: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,36 +146,51 @@ impl EguiGlow {
|
|||
self.egui_winit.on_event(&self.egui_ctx, event)
|
||||
}
|
||||
|
||||
/// Returns `needs_repaint` and shapes to draw.
|
||||
/// Returns `true` if egui requests a repaint.
|
||||
///
|
||||
/// Call [`Self::paint`] later to paint.
|
||||
pub fn run(
|
||||
&mut self,
|
||||
window: &glutin::window::Window,
|
||||
run_ui: impl FnMut(&egui::Context),
|
||||
) -> (bool, Vec<egui::epaint::ClippedShape>) {
|
||||
) -> bool {
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui);
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
self.egui_winit
|
||||
let textures_delta = self
|
||||
.egui_winit
|
||||
.handle_output(window, &self.egui_ctx, egui_output);
|
||||
(needs_repaint, shapes)
|
||||
|
||||
self.shapes = shapes;
|
||||
self.textures_delta.append(textures_delta);
|
||||
needs_repaint
|
||||
}
|
||||
|
||||
/// Paint the results of the last call to [`Self::run`].
|
||||
pub fn paint(
|
||||
&mut self,
|
||||
gl_window: &glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||
gl: &glow::Context,
|
||||
shapes: Vec<egui::epaint::ClippedShape>,
|
||||
) {
|
||||
let shapes = std::mem::take(&mut self.shapes);
|
||||
let mut textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
|
||||
for (id, image) in textures_delta.set {
|
||||
self.painter.set_texture(gl, id, &image);
|
||||
}
|
||||
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
let dimensions: [u32; 2] = gl_window.window().inner_size().into();
|
||||
self.painter
|
||||
.upload_egui_texture(gl, &self.egui_ctx.font_image());
|
||||
self.painter.paint_meshes(
|
||||
gl,
|
||||
dimensions,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
);
|
||||
|
||||
for id in textures_delta.free.drain(..) {
|
||||
self.painter.free_texture(gl, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Call to release the allocated graphics resources.
|
||||
|
|
|
|||
|
|
@ -86,10 +86,6 @@ pub fn check_for_gl_error(gl: &glow::Context, context: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn as_u8_slice<T>(s: &[T]) -> &[u8] {
|
||||
std::slice::from_raw_parts(s.as_ptr().cast::<u8>(), s.len() * std::mem::size_of::<T>())
|
||||
}
|
||||
|
||||
pub(crate) fn glow_print(s: impl std::fmt::Display) {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_sys::console::log_1(&format!("egui_glow: {}", s).into());
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bytemuck::cast_slice;
|
||||
use egui::{
|
||||
emath::Rect,
|
||||
epaint::{Color32, Mesh, Vertex},
|
||||
|
|
@ -11,7 +10,7 @@ use glow::HasContext;
|
|||
use memoffset::offset_of;
|
||||
|
||||
use crate::misc_util::{
|
||||
as_u8_slice, check_for_gl_error, compile_shader, glow_print, link_program, srgb_texture2d,
|
||||
check_for_gl_error, compile_shader, glow_print, link_program, srgb_texture2d,
|
||||
};
|
||||
use crate::post_process::PostProcess;
|
||||
use crate::shader_version::ShaderVersion;
|
||||
|
|
@ -30,8 +29,6 @@ pub struct Painter {
|
|||
program: glow::Program,
|
||||
u_screen_size: glow::UniformLocation,
|
||||
u_sampler: glow::UniformLocation,
|
||||
egui_texture: Option<glow::Texture>,
|
||||
egui_texture_version: Option<u64>,
|
||||
is_webgl_1: bool,
|
||||
is_embedded: bool,
|
||||
vertex_array: crate::misc_util::VAO,
|
||||
|
|
@ -42,8 +39,7 @@ pub struct Painter {
|
|||
vertex_buffer: glow::Buffer,
|
||||
element_array_buffer: glow::Buffer,
|
||||
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, glow::Texture>,
|
||||
textures: HashMap<egui::TextureId, glow::Texture>,
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
|
|
@ -212,8 +208,6 @@ impl Painter {
|
|||
program,
|
||||
u_screen_size,
|
||||
u_sampler,
|
||||
egui_texture: None,
|
||||
egui_texture_version: None,
|
||||
is_webgl_1,
|
||||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||
vertex_array,
|
||||
|
|
@ -222,7 +216,7 @@ impl Painter {
|
|||
post_process,
|
||||
vertex_buffer,
|
||||
element_array_buffer,
|
||||
user_textures: Default::default(),
|
||||
textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: 1 << 32,
|
||||
textures_to_destroy: Vec::new(),
|
||||
|
|
@ -231,41 +225,6 @@ impl Painter {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn upload_egui_texture(&mut self, gl: &glow::Context, font_image: &egui::FontImage) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
let gamma = if self.is_embedded && self.post_process.is_none() {
|
||||
1.0 / 2.2
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let pixels: Vec<u8> = font_image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| Vec::from(a.to_array()))
|
||||
.collect();
|
||||
|
||||
if let Some(old_tex) = std::mem::replace(
|
||||
&mut self.egui_texture,
|
||||
Some(srgb_texture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
self.texture_filter,
|
||||
&pixels,
|
||||
font_image.width,
|
||||
font_image.height,
|
||||
)),
|
||||
) {
|
||||
unsafe {
|
||||
gl.delete_texture(old_tex);
|
||||
}
|
||||
}
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
unsafe fn prepare_painting(
|
||||
&mut self,
|
||||
[width_in_pixels, height_in_pixels]: [u32; 2],
|
||||
|
|
@ -370,14 +329,14 @@ impl Painter {
|
|||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
|
||||
gl.buffer_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
as_u8_slice(mesh.vertices.as_slice()),
|
||||
bytemuck::cast_slice(&mesh.vertices),
|
||||
glow::STREAM_DRAW,
|
||||
);
|
||||
|
||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
|
||||
gl.buffer_data_u8_slice(
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
as_u8_slice(mesh.indices.as_slice()),
|
||||
bytemuck::cast_slice(&mesh.indices),
|
||||
glow::STREAM_DRAW,
|
||||
);
|
||||
|
||||
|
|
@ -425,52 +384,75 @@ impl Painter {
|
|||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
pub fn set_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) {
|
||||
pub fn set_texture(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
tex_id: egui::TextureId,
|
||||
image: &egui::ImageData,
|
||||
) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
let gl_texture = match image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let data: &[u8] = cast_slice(image.pixels.as_ref());
|
||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||
|
||||
let gl_texture = srgb_texture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
self.texture_filter,
|
||||
data,
|
||||
image.size[0],
|
||||
image.size[1],
|
||||
);
|
||||
srgb_texture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
self.texture_filter,
|
||||
data,
|
||||
image.size[0],
|
||||
image.size[1],
|
||||
)
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = if self.is_embedded && self.post_process.is_none() {
|
||||
1.0 / 2.2
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let data: Vec<u8> = image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.collect();
|
||||
|
||||
if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
srgb_texture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
self.texture_filter,
|
||||
&data,
|
||||
image.size[0],
|
||||
image.size[1],
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(old_tex) = self.textures.insert(tex_id, gl_texture) {
|
||||
unsafe { gl.delete_texture(old_tex) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
pub fn free_texture(&mut self, gl: &glow::Context, tex_id: egui::TextureId) {
|
||||
if let Some(old_tex) = self.textures.remove(&tex_id) {
|
||||
unsafe { gl.delete_texture(old_tex) };
|
||||
}
|
||||
}
|
||||
|
||||
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).copied(),
|
||||
}
|
||||
self.textures.get(&texture_id).copied()
|
||||
}
|
||||
|
||||
unsafe fn destroy_gl(&self, gl: &glow::Context) {
|
||||
gl.delete_program(self.program);
|
||||
if let Some(tex) = self.egui_texture {
|
||||
gl.delete_texture(tex);
|
||||
}
|
||||
for tex in self.user_textures.values() {
|
||||
for tex in self.textures.values() {
|
||||
gl.delete_texture(*tex);
|
||||
}
|
||||
gl.delete_buffer(self.vertex_buffer);
|
||||
|
|
@ -533,20 +515,15 @@ impl epi::NativeTexture for Painter {
|
|||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
let id = self.next_native_tex_id;
|
||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||
self.next_native_tex_id += 1;
|
||||
|
||||
self.user_textures.insert(id, native);
|
||||
|
||||
egui::TextureId::User(id as u64)
|
||||
self.textures.insert(id, native);
|
||||
id
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(old_tex) = self.user_textures.insert(id, replacing) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
}
|
||||
if let Some(old_tex) = self.textures.insert(id, replacing) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ impl PostProcess {
|
|||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(pos_buffer));
|
||||
gl.buffer_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
crate::misc_util::as_u8_slice(&positions),
|
||||
bytemuck::cast_slice(&positions),
|
||||
glow::STATIC_DRAW,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,13 @@ crate-type = ["cdylib", "rlib"]
|
|||
|
||||
[dependencies]
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = [
|
||||
"convert_bytemuck",
|
||||
"single_threaded",
|
||||
] }
|
||||
egui_glow = { version = "0.16.0",path = "../egui_glow", default-features = false, optional = true }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
|
||||
bytemuck = "1.7"
|
||||
js-sys = "0.3"
|
||||
ron = { version = "0.7", optional = true }
|
||||
serde = { version = "1", optional = true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
use egui::TexturesDelta;
|
||||
pub use egui::{pos2, Color32};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -92,7 +93,7 @@ pub struct AppRunner {
|
|||
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>,
|
||||
textures_delta: TexturesDelta,
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
|
|
@ -139,7 +140,7 @@ impl AppRunner {
|
|||
screen_reader: Default::default(),
|
||||
text_cursor_pos: None,
|
||||
mutable_text_under_cursor: false,
|
||||
pending_texture_destructions: Default::default(),
|
||||
textures_delta: Default::default(),
|
||||
};
|
||||
|
||||
{
|
||||
|
|
@ -183,7 +184,10 @@ impl AppRunner {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
|
||||
/// Returns `true` if egui requests a repaint.
|
||||
///
|
||||
/// Call [`Self::paint`] later to paint
|
||||
pub fn logic(&mut self) -> Result<(bool, Vec<egui::ClippedMesh>), JsValue> {
|
||||
let frame_start = now_sec();
|
||||
|
||||
resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
|
||||
|
|
@ -195,7 +199,9 @@ impl AppRunner {
|
|||
});
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
||||
self.handle_egui_output(&egui_output);
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
let textures_delta = self.handle_egui_output(egui_output);
|
||||
self.textures_delta.append(textures_delta);
|
||||
|
||||
{
|
||||
let app_output = self.frame.take_app_output();
|
||||
|
|
@ -205,32 +211,32 @@ impl AppRunner {
|
|||
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.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
Ok((egui_output, clipped_meshes))
|
||||
Ok((needs_repaint, clipped_meshes))
|
||||
}
|
||||
|
||||
/// Paint the results of the last call to [`Self::logic`].
|
||||
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
|
||||
self.painter
|
||||
.upload_egui_texture(&self.egui_ctx.font_image());
|
||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
for (id, image) in textures_delta.set {
|
||||
self.painter.set_texture(id, image);
|
||||
}
|
||||
|
||||
self.painter.clear(self.app.clear_color());
|
||||
self.painter
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())?;
|
||||
for id in self.pending_texture_destructions.drain(..) {
|
||||
|
||||
for id in textures_delta.free {
|
||||
self.painter.free_texture(id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_egui_output(&mut self, output: &egui::Output) {
|
||||
fn handle_egui_output(&mut self, output: egui::Output) -> egui::TexturesDelta {
|
||||
if self.egui_ctx.memory().options.screen_reader {
|
||||
self.screen_reader.speak(&output.events_description());
|
||||
}
|
||||
|
|
@ -243,27 +249,30 @@ impl AppRunner {
|
|||
events: _, // already handled
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
textures_delta,
|
||||
} = output;
|
||||
|
||||
set_cursor_icon(*cursor_icon);
|
||||
set_cursor_icon(cursor_icon);
|
||||
if let Some(open) = open_url {
|
||||
crate::open_url(&open.url, open.new_tab);
|
||||
}
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
if !copied_text.is_empty() {
|
||||
set_clipboard_text(copied_text);
|
||||
set_clipboard_text(&copied_text);
|
||||
}
|
||||
|
||||
#[cfg(not(web_sys_unstable_apis))]
|
||||
let _ = copied_text;
|
||||
|
||||
self.mutable_text_under_cursor = *mutable_text_under_cursor;
|
||||
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
||||
|
||||
if &self.text_cursor_pos != text_cursor_pos {
|
||||
if self.text_cursor_pos != text_cursor_pos {
|
||||
move_text_cursor(text_cursor_pos, self.canvas_id());
|
||||
self.text_cursor_pos = *text_cursor_pos;
|
||||
self.text_cursor_pos = text_cursor_pos;
|
||||
}
|
||||
|
||||
textures_delta
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{canvas_element_or_die, console_error};
|
||||
use egui::{ClippedMesh, FontImage, Rgba};
|
||||
use egui::{ClippedMesh, Rgba};
|
||||
use egui_glow::glow;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
|
@ -40,12 +40,12 @@ impl WrappedGlowPainter {
|
|||
}
|
||||
|
||||
impl crate::Painter for WrappedGlowPainter {
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
||||
self.painter.set_texture(&self.glow_ctx, tex_id, &image);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.painter.free_texture(tex_id);
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.painter.free_texture(&self.glow_ctx, tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
|
@ -60,10 +60,6 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, font_image: &FontImage) {
|
||||
self.painter.upload_egui_texture(&self.glow_ctx, font_image)
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: Rgba) {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
egui_glow::painter::clear(&self.glow_ctx, canvas_dimension, clear_color)
|
||||
|
|
|
|||
|
|
@ -482,9 +482,9 @@ fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
|
|||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||
let mut runner_lock = runner_ref.0.lock();
|
||||
if runner_lock.needs_repaint.fetch_and_clear() {
|
||||
let (output, clipped_meshes) = runner_lock.logic()?;
|
||||
let (needs_repaint, clipped_meshes) = runner_lock.logic()?;
|
||||
runner_lock.paint(clipped_meshes)?;
|
||||
if output.needs_repaint {
|
||||
if needs_repaint {
|
||||
runner_lock.needs_repaint.set_true();
|
||||
}
|
||||
runner_lock.auto_save();
|
||||
|
|
@ -1214,7 +1214,7 @@ fn is_mobile() -> Option<bool> {
|
|||
// candidate window moves following text element (agent),
|
||||
// so it appears that the IME candidate window moves with text cursor.
|
||||
// On mobile devices, there is no need to do that.
|
||||
fn move_text_cursor(cursor: &Option<egui::Pos2>, canvas_id: &str) -> Option<()> {
|
||||
fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<()> {
|
||||
let style = text_agent().style();
|
||||
// Note: movint agent on mobile devices will lead to unpredictable scroll.
|
||||
if is_mobile() == Some(false) {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
use wasm_bindgen::prelude::JsValue;
|
||||
|
||||
pub trait Painter {
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image);
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData);
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64);
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId);
|
||||
|
||||
fn debug_info(&self) -> String;
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
fn canvas_id(&self) -> &str;
|
||||
|
||||
fn upload_egui_texture(&mut self, font_image: &egui::FontImage);
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba);
|
||||
|
||||
fn paint_meshes(
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
use egui::{
|
||||
emath::vec2,
|
||||
epaint::{Color32, FontImage},
|
||||
};
|
||||
use egui::{emath::vec2, epaint::Color32};
|
||||
|
||||
type Gl = WebGlRenderingContext;
|
||||
|
||||
|
|
@ -28,13 +25,8 @@ pub struct WebGlPainter {
|
|||
texture_format: u32,
|
||||
post_process: Option<PostProcess>,
|
||||
|
||||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, WebGlTexture>,
|
||||
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
textures: HashMap<egui::TextureId, WebGlTexture>,
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl WebGlPainter {
|
||||
|
|
@ -101,18 +93,13 @@ impl WebGlPainter {
|
|||
color_buffer,
|
||||
texture_format,
|
||||
post_process,
|
||||
egui_texture,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
self.textures.get(&texture_id)
|
||||
}
|
||||
|
||||
fn paint_mesh(&self, mesh: &egui::epaint::Mesh16) -> Result<(), JsValue> {
|
||||
|
|
@ -234,44 +221,8 @@ impl WebGlPainter {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGlPainter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
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(user_texture) = self.user_textures.get_mut(&id) {
|
||||
*user_texture = replacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGlPainter {
|
||||
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());
|
||||
}
|
||||
|
||||
fn set_texture_rgba(&mut self, tex_id: egui::TextureId, size: [usize; 2], pixels: &[u8]) {
|
||||
let gl = &self.gl;
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
|
@ -291,20 +242,64 @@ impl crate::Painter for WebGlPainter {
|
|||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
image.size[0] as _,
|
||||
image.size[1] as _,
|
||||
size[0] as _,
|
||||
size[1] as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
Some(pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture);
|
||||
self.textures.insert(tex_id, gl_texture);
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGlPainter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, texture: Self::Texture) -> egui::TextureId {
|
||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||
self.next_native_tex_id += 1;
|
||||
self.textures.insert(id, texture);
|
||||
id
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, texture: Self::Texture) {
|
||||
self.textures.insert(id, texture);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGlPainter {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
||||
match image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||
self.set_texture_rgba(tex_id, image.size, data);
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = if self.post_process.is_none() {
|
||||
1.0 / 2.2 // HACK due to non-linear framebuffer blending.
|
||||
} else {
|
||||
1.0 // post process enables linear blending
|
||||
};
|
||||
let data: Vec<u8> = image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.collect();
|
||||
self.set_texture_rgba(tex_id, image.size, &data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
|
@ -323,48 +318,6 @@ impl crate::Painter for WebGlPainter {
|
|||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, font_image: &FontImage) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
let gamma = if self.post_process.is_none() {
|
||||
1.0 / 2.2 // HACK due to non-linear framebuffer blending.
|
||||
} else {
|
||||
1.0 // post process enables linear blending
|
||||
};
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(font_image.pixels.len() * 4);
|
||||
for srgba in font_image.srgba_pixels(gamma) {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let gl = &self.gl;
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_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,
|
||||
font_image.width as i32,
|
||||
font_image.height as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba) {
|
||||
let gl = &self.gl;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
use egui::{
|
||||
emath::vec2,
|
||||
epaint::{Color32, FontImage},
|
||||
};
|
||||
use egui::{emath::vec2, epaint::Color32};
|
||||
|
||||
type Gl = WebGl2RenderingContext;
|
||||
|
||||
|
|
@ -28,13 +25,8 @@ pub struct WebGl2Painter {
|
|||
color_buffer: WebGlBuffer,
|
||||
post_process: PostProcess,
|
||||
|
||||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, WebGlTexture>,
|
||||
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
textures: HashMap<egui::TextureId, WebGlTexture>,
|
||||
next_native_tex_id: u64,
|
||||
}
|
||||
|
||||
impl WebGl2Painter {
|
||||
|
|
@ -85,18 +77,13 @@ impl WebGl2Painter {
|
|||
tc_buffer,
|
||||
color_buffer,
|
||||
post_process,
|
||||
egui_texture,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
self.textures.get(&texture_id)
|
||||
}
|
||||
|
||||
fn paint_mesh(&self, mesh: &egui::epaint::Mesh16) -> Result<(), JsValue> {
|
||||
|
|
@ -218,44 +205,8 @@ impl WebGl2Painter {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGl2Painter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
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(user_texture) = self.user_textures.get_mut(&id) {
|
||||
*user_texture = replacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGl2Painter {
|
||||
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());
|
||||
}
|
||||
|
||||
fn set_texture_rgba(&mut self, tex_id: egui::TextureId, size: [usize; 2], pixels: &[u8]) {
|
||||
let gl = &self.gl;
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
|
@ -271,24 +222,65 @@ impl crate::Painter for WebGl2Painter {
|
|||
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 _,
|
||||
image.size[0] as _,
|
||||
image.size[1] as _,
|
||||
internal_format as i32,
|
||||
size[0] as i32,
|
||||
size[1] as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
Some(pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture);
|
||||
self.textures.insert(tex_id, gl_texture);
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGl2Painter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||
self.next_native_tex_id += 1;
|
||||
self.textures.insert(id, native);
|
||||
id
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, native: Self::Texture) {
|
||||
self.textures.insert(id, native);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGl2Painter {
|
||||
fn set_texture(&mut self, tex_id: egui::TextureId, image: egui::ImageData) {
|
||||
match image {
|
||||
egui::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
|
||||
self.set_texture_rgba(tex_id, image.size, data);
|
||||
}
|
||||
egui::ImageData::Alpha(image) => {
|
||||
let gamma = 1.0;
|
||||
let data: Vec<u8> = image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| a.to_array())
|
||||
.collect();
|
||||
self.set_texture_rgba(tex_id, image.size, &data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: egui::TextureId) {
|
||||
self.textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
|
@ -307,44 +299,6 @@ impl crate::Painter for WebGl2Painter {
|
|||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, font_image: &FontImage) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(font_image.pixels.len() * 4);
|
||||
for srgba in font_image.srgba_pixels(1.0) {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let gl = &self.gl;
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_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,
|
||||
font_image.width as i32,
|
||||
font_image.height as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba) {
|
||||
let gl = &self.gl;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ impl Align {
|
|||
}
|
||||
}
|
||||
|
||||
/// ``` rust
|
||||
/// ```
|
||||
/// assert_eq!(emath::Align::Min.align_size_within_range(2.0, 10.0..=20.0), 10.0..=12.0);
|
||||
/// assert_eq!(emath::Align::Center.align_size_within_range(2.0, 10.0..=20.0), 14.0..=16.0);
|
||||
/// assert_eq!(emath::Align::Max.align_size_within_range(2.0, 10.0..=20.0), 18.0..=20.0);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ All notable changes to the epaint crate will be documented in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Added `Shape::dashed_line_many` ([#1027](https://github.com/emilk/egui/pull/1027)).
|
||||
* Add `ImageData` and `TextureManager` for loading images into textures ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,241 @@
|
|||
use crate::Color32;
|
||||
|
||||
/// An image stored in RAM.
|
||||
///
|
||||
/// To load an image file, see [`ColorImage::from_rgba_unmultiplied`].
|
||||
///
|
||||
/// In order to paint the image on screen, you first need to convert it to
|
||||
///
|
||||
/// See also: [`ColorImage`], [`AlphaImage`].
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum ImageData {
|
||||
/// RGBA image.
|
||||
Color(ColorImage),
|
||||
/// Used for the font texture.
|
||||
Alpha(AlphaImage),
|
||||
}
|
||||
|
||||
impl ImageData {
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
match self {
|
||||
Self::Color(image) => image.size,
|
||||
Self::Alpha(image) => image.size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.size()[0]
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.size()[1]
|
||||
}
|
||||
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
Self::Color(_) => 4,
|
||||
Self::Alpha(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A 2D RGBA color image in RAM.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ColorImage {
|
||||
/// width, height.
|
||||
pub size: [usize; 2],
|
||||
/// The pixels, row by row, from top to bottom.
|
||||
pub pixels: Vec<Color32>,
|
||||
}
|
||||
|
||||
impl ColorImage {
|
||||
/// Create an image filled with the given color.
|
||||
pub fn new(size: [usize; 2], color: Color32) -> Self {
|
||||
Self {
|
||||
size,
|
||||
pixels: vec![color; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an `Image` from flat un-multiplied RGBA data.
|
||||
///
|
||||
/// This is usually what you want to use after having loaded an image file.
|
||||
///
|
||||
/// Panics if `size[0] * size[1] * 4 != rgba.len()`.
|
||||
///
|
||||
/// ## Example using the [`image`](crates.io/crates/image) crate:
|
||||
/// ``` ignore
|
||||
/// fn load_image_from_path(path: &std::path::Path) -> Result<egui::ColorImage, image::ImageError> {
|
||||
/// use image::GenericImageView as _;
|
||||
/// let image = image::io::Reader::open(path)?.decode()?;
|
||||
/// let size = [image.width() as _, image.height() as _];
|
||||
/// let image_buffer = image.to_rgba8();
|
||||
/// let pixels = image_buffer.as_flat_samples();
|
||||
/// Ok(egui::ColorImage::from_rgba_unmultiplied(
|
||||
/// size,
|
||||
/// pixels.as_slice(),
|
||||
/// ))
|
||||
/// }
|
||||
///
|
||||
/// fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
|
||||
/// use image::GenericImageView as _;
|
||||
/// let image = image::load_from_memory(image_data)?;
|
||||
/// let size = [image.width() as _, image.height() as _];
|
||||
/// let image_buffer = image.to_rgba8();
|
||||
/// let pixels = image_buffer.as_flat_samples();
|
||||
/// Ok(ColorImage::from_rgba_unmultiplied(
|
||||
/// size,
|
||||
/// pixels.as_slice(),
|
||||
/// ))
|
||||
/// }
|
||||
/// ```
|
||||
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| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
}
|
||||
|
||||
/// An example color image, useful for tests.
|
||||
pub fn example() -> Self {
|
||||
let width = 128;
|
||||
let height = 64;
|
||||
let mut img = Self::new([width, height], Color32::TRANSPARENT);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let h = x as f32 / width as f32;
|
||||
let s = 1.0;
|
||||
let v = 1.0;
|
||||
let a = y as f32 / height as f32;
|
||||
img[(x, y)] = crate::color::Hsva { h, s, v, a }.into();
|
||||
}
|
||||
}
|
||||
img
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.size[1]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for ColorImage {
|
||||
type Output = Color32;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &Color32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for ColorImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorImage> for ImageData {
|
||||
#[inline(always)]
|
||||
fn from(image: ColorImage) -> Self {
|
||||
Self::Color(image)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// An 8-bit image, representing difference levels of transparent white.
|
||||
///
|
||||
/// Used for the font texture
|
||||
#[derive(Clone, Default, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct AlphaImage {
|
||||
/// width, height
|
||||
pub size: [usize; 2],
|
||||
/// The alpha (linear space 0-255) of something white.
|
||||
///
|
||||
/// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AlphaImage {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
size,
|
||||
pixels: vec![0; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.size[1]
|
||||
}
|
||||
|
||||
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||
///
|
||||
/// `gamma` should normally be set to 1.0.
|
||||
/// If you are having problems with text looking skinny and pixelated, try
|
||||
/// setting a lower gamma, e.g. `0.5`.
|
||||
pub fn srgba_pixels(
|
||||
&'_ self,
|
||||
gamma: f32,
|
||||
) -> impl ExactSizeIterator<Item = super::Color32> + '_ {
|
||||
let srgba_from_alpha_lut: Vec<Color32> = (0..=255)
|
||||
.map(|a| {
|
||||
let a = super::color::linear_f32_from_linear_u8(a).powf(gamma);
|
||||
super::Rgba::from_white_alpha(a).into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.pixels
|
||||
.iter()
|
||||
.map(move |&a| srgba_from_alpha_lut[a as usize])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for AlphaImage {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &u8 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for AlphaImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AlphaImage> for ImageData {
|
||||
#[inline(always)]
|
||||
fn from(image: AlphaImage) -> Self {
|
||||
Self::Alpha(image)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@
|
|||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
pub mod color;
|
||||
pub mod image;
|
||||
mod mesh;
|
||||
pub mod mutex;
|
||||
mod shadow;
|
||||
|
|
@ -98,10 +99,13 @@ mod stroke;
|
|||
pub mod tessellator;
|
||||
pub mod text;
|
||||
mod texture_atlas;
|
||||
mod texture_handle;
|
||||
pub mod textures;
|
||||
pub mod util;
|
||||
|
||||
pub use {
|
||||
color::{Color32, Rgba},
|
||||
image::{AlphaImage, ColorImage, ImageData},
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
shadow::Shadow,
|
||||
shape::{CircleShape, PathShape, RectShape, Shape, TextShape},
|
||||
|
|
@ -110,6 +114,8 @@ pub use {
|
|||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
texture_atlas::{FontImage, TextureAtlas},
|
||||
texture_handle::TextureHandle,
|
||||
textures::TextureManager,
|
||||
};
|
||||
|
||||
pub use emath::{pos2, vec2, Pos2, Rect, Vec2};
|
||||
|
|
@ -124,21 +130,25 @@ pub use emath;
|
|||
pub const WHITE_UV: emath::Pos2 = emath::pos2(0.0, 0.0);
|
||||
|
||||
/// What texture to use in a [`Mesh`] mesh.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
///
|
||||
/// If you don't want to use a texture, use `TextureId::Epaint(0)` and the [`WHITE_UV`] for uv-coord.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum TextureId {
|
||||
/// The egui font texture.
|
||||
/// If you don't want to use a texture, pick this and the [`WHITE_UV`] for uv-coord.
|
||||
Egui,
|
||||
/// Textures allocated using [`TextureManager`].
|
||||
///
|
||||
/// The first texture (`TextureId::Epaint(0)`) is used for the font data.
|
||||
Managed(u64),
|
||||
|
||||
/// Your own texture, defined in any which way you want.
|
||||
/// egui won't care. The backend renderer will presumably use this to look up what texture to use.
|
||||
/// The backend renderer will presumably use this to look up what texture to use.
|
||||
User(u64),
|
||||
}
|
||||
|
||||
impl Default for TextureId {
|
||||
/// The epaint font texture.
|
||||
fn default() -> Self {
|
||||
Self::Egui
|
||||
Self::Managed(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ impl Mesh {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
|
||||
crate::epaint_assert!(self.texture_id == TextureId::Egui);
|
||||
crate::epaint_assert!(self.texture_id == TextureId::default());
|
||||
self.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
|
|
@ -168,7 +168,7 @@ impl Mesh {
|
|||
/// Uniformly colored rectangle.
|
||||
#[inline(always)]
|
||||
pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) {
|
||||
crate::epaint_assert!(self.texture_id == TextureId::Egui);
|
||||
crate::epaint_assert!(self.texture_id == TextureId::default());
|
||||
self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ impl Shape {
|
|||
if let Shape::Mesh(mesh) = self {
|
||||
mesh.texture_id
|
||||
} else {
|
||||
super::TextureId::Egui
|
||||
super::TextureId::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -376,12 +376,12 @@ fn allocate_glyph(
|
|||
} else {
|
||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
||||
|
||||
let texture = atlas.image_mut();
|
||||
let image = atlas.image_mut();
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
texture[(px, py)] = (v * 255.0).round() as u8;
|
||||
image.image[(px, py)] = (v * 255.0).round() as u8;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub enum TextStyle {
|
|||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn all() -> impl Iterator<Item = TextStyle> {
|
||||
pub fn all() -> impl ExactSizeIterator<Item = TextStyle> {
|
||||
[
|
||||
TextStyle::Small,
|
||||
TextStyle::Body,
|
||||
|
|
@ -253,13 +253,13 @@ impl Fonts {
|
|||
|
||||
// We want an atlas big enough to be able to include all the Emojis in the `TextStyle::Heading`,
|
||||
// so we can show the Emoji picker demo window.
|
||||
let mut atlas = TextureAtlas::new(2048, 64);
|
||||
let mut atlas = TextureAtlas::new([2048, 64]);
|
||||
|
||||
{
|
||||
// Make the top left pixel fully white:
|
||||
let pos = atlas.allocate((1, 1));
|
||||
assert_eq!(pos, (0, 0));
|
||||
atlas.image_mut()[pos] = 255;
|
||||
atlas.image_mut().image[pos] = 255;
|
||||
}
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
|
@ -287,7 +287,7 @@ impl Fonts {
|
|||
let mut atlas = atlas.lock();
|
||||
let texture = atlas.image_mut();
|
||||
// Make sure we seed the texture version with something unique based on the default characters:
|
||||
texture.version = crate::util::hash(&texture.pixels);
|
||||
texture.version = crate::util::hash(&texture.image);
|
||||
}
|
||||
|
||||
Self {
|
||||
|
|
@ -295,7 +295,7 @@ impl Fonts {
|
|||
definitions,
|
||||
fonts,
|
||||
atlas,
|
||||
buffered_font_image: Default::default(), //atlas.lock().texture().clone();
|
||||
buffered_font_image: Default::default(),
|
||||
galley_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,30 @@
|
|||
use crate::image::AlphaImage;
|
||||
|
||||
/// An 8-bit texture containing font data.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FontImage {
|
||||
/// e.g. a hash of the data. Use this to detect changes!
|
||||
/// If the texture changes, this too will change.
|
||||
pub version: u64,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
/// The alpha (linear space 0-255) of something white.
|
||||
///
|
||||
/// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<u8>,
|
||||
|
||||
/// The actual image data.
|
||||
pub image: AlphaImage,
|
||||
}
|
||||
|
||||
impl FontImage {
|
||||
#[inline]
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
[self.width, self.height]
|
||||
self.image.size
|
||||
}
|
||||
|
||||
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||
///
|
||||
/// `gamma` should normally be set to 1.0.
|
||||
/// If you are having problems with egui text looking skinny and pixelated, try
|
||||
/// setting a lower gamma, e.g. `0.5`.
|
||||
pub fn srgba_pixels(&'_ self, gamma: f32) -> impl Iterator<Item = super::Color32> + '_ {
|
||||
use super::Color32;
|
||||
|
||||
let srgba_from_luminance_lut: Vec<Color32> = (0..=255)
|
||||
.map(|a| {
|
||||
let a = super::color::linear_f32_from_linear_u8(a).powf(gamma);
|
||||
super::Rgba::from_white_alpha(a).into()
|
||||
})
|
||||
.collect();
|
||||
self.pixels
|
||||
.iter()
|
||||
.map(move |&l| srgba_from_luminance_lut[l as usize])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for FontImage {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &u8 {
|
||||
assert!(x < self.width);
|
||||
assert!(y < self.height);
|
||||
&self.pixels[y * self.width + x]
|
||||
pub fn width(&self) -> usize {
|
||||
self.image.size[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
|
||||
assert!(x < self.width);
|
||||
assert!(y < self.height);
|
||||
&mut self.pixels[y * self.width + x]
|
||||
pub fn height(&self) -> usize {
|
||||
self.image.size[1]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,13 +41,11 @@ pub struct TextureAtlas {
|
|||
}
|
||||
|
||||
impl TextureAtlas {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
image: FontImage {
|
||||
version: 0,
|
||||
width,
|
||||
height,
|
||||
pixels: vec![0; width * height],
|
||||
image: AlphaImage::new(size),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
|
|
@ -99,12 +68,12 @@ impl TextureAtlas {
|
|||
const PADDING: usize = 1;
|
||||
|
||||
assert!(
|
||||
w <= self.image.width,
|
||||
w <= self.image.width(),
|
||||
"Tried to allocate a {} wide glyph in a {} wide texture atlas",
|
||||
w,
|
||||
self.image.width
|
||||
self.image.width()
|
||||
);
|
||||
if self.cursor.0 + w > self.image.width {
|
||||
if self.cursor.0 + w > self.image.width() {
|
||||
// New row:
|
||||
self.cursor.0 = 0;
|
||||
self.cursor.1 += self.row_height + PADDING;
|
||||
|
|
@ -112,15 +81,7 @@ impl TextureAtlas {
|
|||
}
|
||||
|
||||
self.row_height = self.row_height.max(h);
|
||||
while self.cursor.1 + self.row_height >= self.image.height {
|
||||
self.image.height *= 2;
|
||||
}
|
||||
|
||||
if self.image.width * self.image.height > self.image.pixels.len() {
|
||||
self.image
|
||||
.pixels
|
||||
.resize(self.image.width * self.image.height, 0);
|
||||
}
|
||||
resize_to_min_height(&mut self.image.image, self.cursor.1 + self.row_height);
|
||||
|
||||
let pos = self.cursor;
|
||||
self.cursor.0 += w + PADDING;
|
||||
|
|
@ -128,3 +89,13 @@ impl TextureAtlas {
|
|||
(pos.0 as usize, pos.1 as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_to_min_height(image: &mut AlphaImage, min_height: usize) {
|
||||
while min_height >= image.height() {
|
||||
image.size[1] *= 2; // double the height
|
||||
}
|
||||
|
||||
if image.width() * image.height() > image.pixels.len() {
|
||||
image.pixels.resize(image.width() * image.height(), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
use crate::{
|
||||
emath::NumExt,
|
||||
mutex::{Arc, RwLock},
|
||||
ImageData, TextureId, TextureManager,
|
||||
};
|
||||
|
||||
/// Used to paint images.
|
||||
///
|
||||
/// An _image_ is pixels stored in RAM, and represented using [`ImageData`].
|
||||
/// Before you can paint it however, you need to convert it to a _texture_.
|
||||
///
|
||||
/// If you are using egui, use `egui::Context::load_texture`.
|
||||
///
|
||||
/// The [`TextureHandle`] can be cloned cheaply.
|
||||
/// When the last [`TextureHandle`] for specific texture is dropped, the texture is freed.
|
||||
///
|
||||
/// See also [`TextureManager`].
|
||||
#[must_use]
|
||||
pub struct TextureHandle {
|
||||
tex_mngr: Arc<RwLock<TextureManager>>,
|
||||
id: TextureId,
|
||||
}
|
||||
|
||||
impl Drop for TextureHandle {
|
||||
fn drop(&mut self) {
|
||||
self.tex_mngr.write().free(self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TextureHandle {
|
||||
fn clone(&self) -> Self {
|
||||
self.tex_mngr.write().retain(self.id);
|
||||
Self {
|
||||
tex_mngr: self.tex_mngr.clone(),
|
||||
id: self.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TextureHandle {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TextureHandle {}
|
||||
|
||||
impl std::hash::Hash for TextureHandle {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureHandle {
|
||||
/// If you are using egui, use `egui::Context::load_texture` instead.
|
||||
pub fn new(tex_mngr: Arc<RwLock<TextureManager>>, id: TextureId) -> Self {
|
||||
Self { tex_mngr, id }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> TextureId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Assign a new image to an existing texture.
|
||||
pub fn set(&mut self, image: impl Into<ImageData>) {
|
||||
self.tex_mngr.write().set(self.id, image.into());
|
||||
}
|
||||
|
||||
/// width x height
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
self.tex_mngr.read().meta(self.id).unwrap().size
|
||||
}
|
||||
|
||||
/// width x height
|
||||
pub fn size_vec2(&self) -> crate::Vec2 {
|
||||
let [w, h] = self.size();
|
||||
crate::Vec2::new(w as f32, h as f32)
|
||||
}
|
||||
|
||||
/// width / height
|
||||
pub fn aspect_ratio(&self) -> f32 {
|
||||
let [w, h] = self.size();
|
||||
w as f32 / h.at_least(1) as f32
|
||||
}
|
||||
|
||||
/// Debug-name.
|
||||
pub fn name(&self) -> String {
|
||||
self.tex_mngr.read().meta(self.id).unwrap().name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TextureHandle> for TextureId {
|
||||
#[inline(always)]
|
||||
fn from(handle: &TextureHandle) -> Self {
|
||||
handle.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&mut TextureHandle> for TextureId {
|
||||
#[inline(always)]
|
||||
fn from(handle: &mut TextureHandle) -> Self {
|
||||
handle.id()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
use crate::{image::ImageData, TextureId};
|
||||
use ahash::AHashMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Low-level manager for allocating textures.
|
||||
///
|
||||
/// Communicates with the painting subsystem using [`Self::take_delta`].
|
||||
#[derive(Default)]
|
||||
pub struct TextureManager {
|
||||
/// We allocate texture id:s linearly.
|
||||
next_id: u64,
|
||||
/// Information about currently allocated textures.
|
||||
metas: AHashMap<TextureId, TextureMeta>,
|
||||
delta: TexturesDelta,
|
||||
}
|
||||
|
||||
impl TextureManager {
|
||||
/// Allocate a new texture.
|
||||
///
|
||||
/// The given name can be useful for later debugging.
|
||||
///
|
||||
/// The returned [`TextureId`] will be [`TextureId::Managed`], with an index
|
||||
/// starting from zero and increasing with each call to [`Self::alloc`].
|
||||
///
|
||||
/// The first texture you allocate will be `TextureId::Managed(0) == TexureId::default()` and
|
||||
/// MUST have a white pixel at (0,0) ([`crate::WHITE_UV`]).
|
||||
///
|
||||
/// The texture is given a retain-count of `1`, requiring one call to [`Self::free`] to free it.
|
||||
pub fn alloc(&mut self, name: String, image: ImageData) -> TextureId {
|
||||
let id = TextureId::Managed(self.next_id);
|
||||
self.next_id += 1;
|
||||
|
||||
self.metas.entry(id).or_insert_with(|| TextureMeta {
|
||||
name,
|
||||
size: image.size(),
|
||||
bytes_per_pixel: image.bytes_per_pixel(),
|
||||
retain_count: 1,
|
||||
});
|
||||
|
||||
self.delta.set.insert(id, image);
|
||||
id
|
||||
}
|
||||
|
||||
/// Assign a new image to an existing texture.
|
||||
pub fn set(&mut self, id: TextureId, image: ImageData) {
|
||||
if let Some(meta) = self.metas.get_mut(&id) {
|
||||
meta.size = image.size();
|
||||
meta.bytes_per_pixel = image.bytes_per_pixel();
|
||||
self.delta.set.insert(id, image);
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
"Tried setting texture {:?} which is not allocated",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Free an existing texture.
|
||||
pub fn free(&mut self, id: TextureId) {
|
||||
if let std::collections::hash_map::Entry::Occupied(mut entry) = self.metas.entry(id) {
|
||||
let meta = entry.get_mut();
|
||||
meta.retain_count -= 1;
|
||||
if meta.retain_count == 0 {
|
||||
entry.remove();
|
||||
self.delta.free.push(id);
|
||||
}
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
"Tried freeing texture {:?} which is not allocated",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase the retain-count of the given texture.
|
||||
///
|
||||
/// For each time you call [`Self::retain`] you must call [`Self::free`] on additional time.
|
||||
pub fn retain(&mut self, id: TextureId) {
|
||||
if let Some(meta) = self.metas.get_mut(&id) {
|
||||
meta.retain_count += 1;
|
||||
} else {
|
||||
crate::epaint_assert!(
|
||||
false,
|
||||
"Tried retaining texture {:?} which is not allocated",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Take and reset changes since last frame.
|
||||
///
|
||||
/// These should be applied to the painting subsystem each frame.
|
||||
pub fn take_delta(&mut self) -> TexturesDelta {
|
||||
std::mem::take(&mut self.delta)
|
||||
}
|
||||
|
||||
/// Get meta-data about a specific texture.
|
||||
pub fn meta(&self, id: TextureId) -> Option<&TextureMeta> {
|
||||
self.metas.get(&id)
|
||||
}
|
||||
|
||||
/// Get meta-data about all allocated textures in some arbitrary order.
|
||||
pub fn allocated(&self) -> impl ExactSizeIterator<Item = (&TextureId, &TextureMeta)> {
|
||||
self.metas.iter()
|
||||
}
|
||||
|
||||
/// Total number of allocated textures.
|
||||
pub fn num_allocated(&self) -> usize {
|
||||
self.metas.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Meta-data about an allocated texture.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TextureMeta {
|
||||
/// A human-readable name useful for debugging.
|
||||
pub name: String,
|
||||
|
||||
/// width x height
|
||||
pub size: [usize; 2],
|
||||
|
||||
/// 4 or 1
|
||||
pub bytes_per_pixel: usize,
|
||||
|
||||
/// Free when this reaches zero.
|
||||
pub retain_count: usize,
|
||||
}
|
||||
|
||||
impl TextureMeta {
|
||||
/// Size in bytes.
|
||||
/// width x height x [`Self::bytes_per_pixel`].
|
||||
pub fn bytes_used(&self) -> usize {
|
||||
self.size[0] * self.size[1] * self.bytes_per_pixel
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// What has been allocated and freed during the last period.
|
||||
///
|
||||
/// These are commands given to the integration painter.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[must_use = "The painter must take care of this"]
|
||||
pub struct TexturesDelta {
|
||||
/// New or changed textures. Apply before painting.
|
||||
pub set: AHashMap<TextureId, ImageData>,
|
||||
|
||||
/// Texture to free after painting.
|
||||
pub free: Vec<TextureId>,
|
||||
}
|
||||
|
||||
impl TexturesDelta {
|
||||
pub fn append(&mut self, mut newer: TexturesDelta) {
|
||||
self.set.extend(newer.set.into_iter());
|
||||
self.free.append(&mut newer.free);
|
||||
}
|
||||
}
|
||||
105
epi/src/lib.rs
105
epi/src/lib.rs
|
|
@ -328,31 +328,7 @@ impl Frame {
|
|||
|
||||
/// 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);
|
||||
std::mem::take(&mut self.lock().output)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,41 +362,6 @@ pub struct IntegrationInfo {
|
|||
pub native_pixels_per_point: Option<f32>,
|
||||
}
|
||||
|
||||
/// How to allocate textures (images) to use in [`egui`].
|
||||
pub trait TextureAllocator {
|
||||
/// Allocate a new user texture.
|
||||
///
|
||||
/// There is no way to change a texture.
|
||||
/// Instead allocate a new texture and free the previous one with [`Self::free`].
|
||||
fn alloc(&self, image: Image) -> egui::TextureId;
|
||||
|
||||
/// Free the given texture.
|
||||
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
|
||||
pub trait NativeTexture {
|
||||
/// The native texture type.
|
||||
|
|
@ -482,8 +423,6 @@ 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.
|
||||
|
|
@ -498,49 +437,14 @@ pub mod backend {
|
|||
pub struct FrameData {
|
||||
/// Information about the integration.
|
||||
pub info: IntegrationInfo,
|
||||
|
||||
/// Where the app can issue commands back to the integration.
|
||||
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>,
|
||||
}
|
||||
|
||||
/// 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(Default)]
|
||||
#[must_use]
|
||||
|
|
@ -560,8 +464,5 @@ pub mod backend {
|
|||
|
||||
/// 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 New Issue