mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 15:13:12 -04:00
Merge branch 'master' of https://github.com/emilk/egui into multiples_viewports
This commit is contained in:
@@ -36,4 +36,4 @@ opt-level = 2
|
||||
|
||||
[workspace.dependencies]
|
||||
thiserror = "1.0.37"
|
||||
wgpu = { version = "0.17.0", features = ["fragile-send-sync-non-atomic-wasm"] }
|
||||
wgpu = "0.17.0"
|
||||
|
||||
@@ -10,8 +10,8 @@ pub use wgpu;
|
||||
|
||||
/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`].
|
||||
pub mod renderer;
|
||||
pub use renderer::CallbackFn;
|
||||
pub use renderer::Renderer;
|
||||
pub use renderer::{Callback, CallbackResources, CallbackTrait};
|
||||
|
||||
/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].
|
||||
#[cfg(feature = "winit")]
|
||||
|
||||
@@ -1,107 +1,109 @@
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::Range;
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use std::{borrow::Cow, num::NonZeroU64, ops::Range};
|
||||
|
||||
use epaint::{ahash::HashMap, emath::NumExt, PaintCallbackInfo, Primitive, Vertex};
|
||||
|
||||
use type_map::concurrent::TypeMap;
|
||||
use wgpu;
|
||||
use wgpu::util::DeviceExt as _;
|
||||
|
||||
use epaint::{emath::NumExt, PaintCallbackInfo, Primitive, Vertex};
|
||||
// Only implements Send + Sync on wasm32 in order to allow storing wgpu resources on the type map.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type CallbackResources = type_map::concurrent::TypeMap;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub type CallbackResources = type_map::TypeMap;
|
||||
|
||||
/// A callback function that can be used to compose an [`epaint::PaintCallback`] for custom WGPU
|
||||
/// rendering.
|
||||
///
|
||||
/// The callback is composed of two functions: `prepare` and `paint`:
|
||||
/// - `prepare` is called every frame before `paint`, and can use the passed-in
|
||||
/// [`wgpu::Device`] and [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
|
||||
/// - `paint` is called after `prepare` and is given access to the [`wgpu::RenderPass`] so
|
||||
/// that it can issue draw commands into the same [`wgpu::RenderPass`] that is used for
|
||||
/// all other egui elements.
|
||||
///
|
||||
/// The final argument of both the `prepare` and `paint` callbacks is a the
|
||||
/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources].
|
||||
/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to
|
||||
/// store buffers, pipelines, and other information that needs to be accessed during the render
|
||||
/// pass.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
|
||||
pub struct CallbackFn {
|
||||
prepare: Box<PrepareCallback>,
|
||||
paint: Box<PaintCallback>,
|
||||
}
|
||||
pub struct Callback(Box<dyn CallbackTrait>);
|
||||
|
||||
type PrepareCallback = dyn Fn(
|
||||
&wgpu::Device,
|
||||
&wgpu::Queue,
|
||||
&mut wgpu::CommandEncoder,
|
||||
&mut TypeMap,
|
||||
) -> Vec<wgpu::CommandBuffer>
|
||||
+ Sync
|
||||
+ Send;
|
||||
|
||||
type PaintCallback =
|
||||
dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send;
|
||||
|
||||
impl Default for CallbackFn {
|
||||
fn default() -> Self {
|
||||
CallbackFn {
|
||||
prepare: Box::new(|_, _, _, _| Vec::new()),
|
||||
paint: Box::new(|_, _, _| ()),
|
||||
impl Callback {
|
||||
/// Creates a new [`epaint::PaintCallback`] from a callback trait instance.
|
||||
pub fn new_paint_callback(
|
||||
rect: epaint::emath::Rect,
|
||||
callback: impl CallbackTrait + 'static,
|
||||
) -> epaint::PaintCallback {
|
||||
epaint::PaintCallback {
|
||||
rect,
|
||||
callback: std::sync::Arc::new(Self(Box::new(callback))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallbackFn {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
/// A callback trait that can be used to compose an [`epaint::PaintCallback`] via [`Callback`]
|
||||
/// for custom WGPU rendering.
|
||||
///
|
||||
/// Callbacks in [`Renderer`] are done in three steps:
|
||||
/// * [`CallbackTrait::prepare`]: called for all registered callbacks before the main egui render pass.
|
||||
/// * [`CallbackTrait::finish_prepare`]: called for all registered callbacks after all callbacks finished calling prepare.
|
||||
/// * [`CallbackTrait::paint`]: called for all registered callbacks during the main egui render pass.
|
||||
///
|
||||
/// Each callback has access to an instance of [`CallbackResources`] that is stored in the [`Renderer`].
|
||||
/// This can be used to store wgpu resources that need to be accessed during the [`CallbackTrait::paint`] step.
|
||||
///
|
||||
/// The callbacks implementing [`CallbackTrait`] itself must always be Send + Sync, but resources stored in
|
||||
/// [`Renderer::callback_resources`] are not required to implement Send + Sync when building for wasm.
|
||||
/// (this is because wgpu stores references to the JS heap in most of its resources which can not be shared with other threads).
|
||||
///
|
||||
///
|
||||
/// # Command submission
|
||||
///
|
||||
/// ## Command Encoder
|
||||
///
|
||||
/// The passed-in `CommandEncoder` is egui's and can be used directly to register
|
||||
/// wgpu commands for simple use cases.
|
||||
/// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui
|
||||
/// rendering itself.
|
||||
///
|
||||
/// ## Command Buffers
|
||||
///
|
||||
/// For more complicated use cases, one can also return a list of arbitrary
|
||||
/// `CommandBuffer`s and have complete control over how they get created and fed.
|
||||
/// In particular, this gives an opportunity to parallelize command registration and
|
||||
/// prevents a faulty callback from poisoning the main wgpu pipeline.
|
||||
///
|
||||
/// When using eframe, the main egui command buffer, as well as all user-defined
|
||||
/// command buffers returned by this function, are guaranteed to all be submitted
|
||||
/// at once in a single call.
|
||||
///
|
||||
/// Command Buffers returned by [`CallbackTrait::finish_prepare`] will always be issued *after*
|
||||
/// those returned by [`CallbackTrait::prepare`].
|
||||
/// Order within command buffers returned by [`CallbackTrait::prepare`] is dependent
|
||||
/// on the order the respective [`epaint::Shape::Callback`]s were submitted in.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
|
||||
pub trait CallbackTrait: Send + Sync {
|
||||
fn prepare(
|
||||
&self,
|
||||
_device: &wgpu::Device,
|
||||
_queue: &wgpu::Queue,
|
||||
_egui_encoder: &mut wgpu::CommandEncoder,
|
||||
_callback_resources: &mut CallbackResources,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Set the prepare callback.
|
||||
///
|
||||
/// The passed-in `CommandEncoder` is egui's and can be used directly to register
|
||||
/// wgpu commands for simple use cases.
|
||||
/// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui
|
||||
/// rendering itself.
|
||||
///
|
||||
/// For more complicated use cases, one can also return a list of arbitrary
|
||||
/// `CommandBuffer`s and have complete control over how they get created and fed.
|
||||
/// In particular, this gives an opportunity to parallelize command registration and
|
||||
/// prevents a faulty callback from poisoning the main wgpu pipeline.
|
||||
///
|
||||
/// When using eframe, the main egui command buffer, as well as all user-defined
|
||||
/// command buffers returned by this function, are guaranteed to all be submitted
|
||||
/// at once in a single call.
|
||||
pub fn prepare<F>(mut self, prepare: F) -> Self
|
||||
where
|
||||
F: Fn(
|
||||
&wgpu::Device,
|
||||
&wgpu::Queue,
|
||||
&mut wgpu::CommandEncoder,
|
||||
&mut TypeMap,
|
||||
) -> Vec<wgpu::CommandBuffer>
|
||||
+ Sync
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.prepare = Box::new(prepare) as _;
|
||||
self
|
||||
/// Called after all [`CallbackTrait::prepare`] calls are done.
|
||||
fn finish_prepare(
|
||||
&self,
|
||||
_device: &wgpu::Device,
|
||||
_queue: &wgpu::Queue,
|
||||
_egui_encoder: &mut wgpu::CommandEncoder,
|
||||
_callback_resources: &mut CallbackResources,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Set the paint callback
|
||||
pub fn paint<F>(mut self, paint: F) -> Self
|
||||
where
|
||||
F: for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap)
|
||||
+ Sync
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.paint = Box::new(paint) as _;
|
||||
self
|
||||
}
|
||||
/// Called after all [`CallbackTrait::finish_prepare`] calls are done.
|
||||
///
|
||||
/// It is given access to the [`wgpu::RenderPass`] so that it can issue draw commands
|
||||
/// into the same [`wgpu::RenderPass`] that is used for all other egui elements.
|
||||
fn paint<'a>(
|
||||
&'a self,
|
||||
info: PaintCallbackInfo,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
callback_resources: &'a CallbackResources,
|
||||
);
|
||||
}
|
||||
|
||||
/// Information about the screen used for rendering.
|
||||
@@ -164,9 +166,10 @@ pub struct Renderer {
|
||||
next_user_texture_id: u64,
|
||||
samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
|
||||
|
||||
/// Storage for use by [`epaint::PaintCallback`]'s that need to store resources such as render
|
||||
/// pipelines that must have the lifetime of the renderpass.
|
||||
pub paint_callback_resources: TypeMap,
|
||||
/// Storage for resources shared with all invocations of [`CallbackTrait`]'s methods.
|
||||
///
|
||||
/// See also [`CallbackTrait`].
|
||||
pub callback_resources: CallbackResources,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@@ -346,10 +349,10 @@ impl Renderer {
|
||||
},
|
||||
uniform_bind_group,
|
||||
texture_bind_group_layout,
|
||||
textures: HashMap::new(),
|
||||
textures: HashMap::default(),
|
||||
next_user_texture_id: 0,
|
||||
samplers: HashMap::new(),
|
||||
paint_callback_resources: TypeMap::default(),
|
||||
samplers: HashMap::default(),
|
||||
callback_resources: CallbackResources::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +360,7 @@ impl Renderer {
|
||||
pub fn render<'rp>(
|
||||
&'rp self,
|
||||
render_pass: &mut wgpu::RenderPass<'rp>,
|
||||
paint_jobs: &[epaint::ClippedPrimitive],
|
||||
paint_jobs: &'rp [epaint::ClippedPrimitive],
|
||||
screen_descriptor: &ScreenDescriptor,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
@@ -432,7 +435,7 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
Primitive::Callback(callback) => {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<Callback>() {
|
||||
c
|
||||
} else {
|
||||
// We already warned in the `prepare` callback
|
||||
@@ -467,7 +470,7 @@ impl Renderer {
|
||||
);
|
||||
}
|
||||
|
||||
(cbfn.paint)(
|
||||
cbfn.0.paint(
|
||||
PaintCallbackInfo {
|
||||
viewport: callback.rect,
|
||||
clip_rect: *clip_rect,
|
||||
@@ -475,7 +478,7 @@ impl Renderer {
|
||||
screen_size_px: size_in_pixels,
|
||||
},
|
||||
render_pass,
|
||||
&self.paint_callback_resources,
|
||||
&self.callback_resources,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -751,7 +754,7 @@ impl Renderer {
|
||||
/// Uploads the uniform, vertex and index data used by the renderer.
|
||||
/// Should be called before `render()`.
|
||||
///
|
||||
/// Returns all user-defined command buffers gathered from prepare callbacks.
|
||||
/// Returns all user-defined command buffers gathered from [`CallbackTrait::prepare`] & [`CallbackTrait::finish_prepare`] callbacks.
|
||||
pub fn update_buffers(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
@@ -778,7 +781,8 @@ impl Renderer {
|
||||
self.previous_uniform_buffer_content = uniform_buffer_content;
|
||||
}
|
||||
|
||||
// Determine how many vertices & indices need to be rendered.
|
||||
// Determine how many vertices & indices need to be rendered, and gather prepare callbacks
|
||||
let mut callbacks = Vec::new();
|
||||
let (vertex_count, index_count) = {
|
||||
crate::profile_scope!("count_vertices_indices");
|
||||
paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
|
||||
@@ -786,7 +790,14 @@ impl Renderer {
|
||||
Primitive::Mesh(mesh) => {
|
||||
(acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
|
||||
}
|
||||
Primitive::Callback(_) => acc,
|
||||
Primitive::Callback(callback) => {
|
||||
if let Some(c) = callback.callback.downcast_ref::<Callback>() {
|
||||
callbacks.push(c.0.as_ref());
|
||||
} else {
|
||||
log::warn!("Unknown paint callback: expected `egui_wgpu::Callback`");
|
||||
};
|
||||
acc
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -861,32 +872,31 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
let mut user_cmd_bufs = Vec::new();
|
||||
{
|
||||
crate::profile_scope!("user command buffers");
|
||||
let mut user_cmd_bufs = Vec::new(); // collect user command buffers
|
||||
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||
match primitive {
|
||||
Primitive::Mesh(_) => {}
|
||||
Primitive::Callback(callback) => {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||
c
|
||||
} else {
|
||||
log::warn!("Unknown paint callback: expected `egui_wgpu::CallbackFn`");
|
||||
continue;
|
||||
};
|
||||
|
||||
crate::profile_scope!("callback");
|
||||
user_cmd_bufs.extend((cbfn.prepare)(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&mut self.paint_callback_resources,
|
||||
));
|
||||
}
|
||||
}
|
||||
crate::profile_scope!("prepare callbacks");
|
||||
for callback in &callbacks {
|
||||
user_cmd_bufs.extend(callback.prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&mut self.callback_resources,
|
||||
));
|
||||
}
|
||||
user_cmd_bufs
|
||||
}
|
||||
{
|
||||
crate::profile_scope!("finish prepare callbacks");
|
||||
for callback in &callbacks {
|
||||
user_cmd_bufs.extend(callback.finish_prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&mut self.callback_resources,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
user_cmd_bufs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,6 +979,9 @@ impl ScissorRect {
|
||||
}
|
||||
}
|
||||
|
||||
// Wgpu objects contain references to the JS heap on the web, therefore they are not Send/Sync.
|
||||
// It follows that egui_wgpu::Renderer can not be Send/Sync either when building with wasm.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[test]
|
||||
fn renderer_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
|
||||
@@ -55,6 +55,8 @@ impl State {
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// The previous rectangle used by this area can be obtained through [`crate::Memory::area_rect()`].
|
||||
#[must_use = "You should call .show()"]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Area {
|
||||
|
||||
@@ -555,13 +555,12 @@ impl CollapsingHeader {
|
||||
let visuals = ui.style().interact_selectable(&header_response, selected);
|
||||
|
||||
if ui.visuals().collapsing_header_frame || show_background {
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: header_response.rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.weak_bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
// stroke: Default::default(),
|
||||
});
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
header_response.rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
));
|
||||
}
|
||||
|
||||
if selected || selectable && (header_response.hovered() || header_response.has_focus())
|
||||
|
||||
@@ -383,12 +383,12 @@ fn button_frame(
|
||||
|
||||
ui.painter().set(
|
||||
where_to_put_background,
|
||||
epaint::RectShape {
|
||||
rect: outer_rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.weak_bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
},
|
||||
epaint::RectShape::new(
|
||||
outer_rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -235,12 +235,7 @@ impl Frame {
|
||||
stroke,
|
||||
} = *self;
|
||||
|
||||
let frame_shape = Shape::Rect(epaint::RectShape {
|
||||
rect: outer_rect,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
let frame_shape = Shape::Rect(epaint::RectShape::new(outer_rect, rounding, fill, stroke));
|
||||
|
||||
if shadow == Default::default() {
|
||||
frame_shape
|
||||
|
||||
@@ -22,6 +22,9 @@ use super::*;
|
||||
/// ui.label("Hello World!");
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// The previous rectangle used by this window can be obtained through [`crate::Memory::area_rect()`].
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct Window<'open> {
|
||||
title: WidgetText,
|
||||
|
||||
@@ -568,6 +568,10 @@ pub struct PointerState {
|
||||
/// Used to check for triple-clicks.
|
||||
last_last_click_time: f64,
|
||||
|
||||
/// When was the pointer last moved?
|
||||
/// Used for things like showing hover ui/tooltip with a delay.
|
||||
last_move_time: f64,
|
||||
|
||||
/// All button events that occurred this frame
|
||||
pub(crate) pointer_events: Vec<PointerEvent>,
|
||||
}
|
||||
@@ -587,6 +591,7 @@ impl Default for PointerState {
|
||||
has_moved_too_much_for_a_click: false,
|
||||
last_click_time: std::f64::NEG_INFINITY,
|
||||
last_last_click_time: std::f64::NEG_INFINITY,
|
||||
last_move_time: std::f64::NEG_INFINITY,
|
||||
pointer_events: vec![],
|
||||
}
|
||||
}
|
||||
@@ -711,6 +716,9 @@ impl PointerState {
|
||||
} else {
|
||||
Vec2::default()
|
||||
};
|
||||
if self.velocity != Vec2::ZERO {
|
||||
self.last_move_time = time;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
@@ -790,6 +798,12 @@ impl PointerState {
|
||||
self.velocity != Vec2::ZERO
|
||||
}
|
||||
|
||||
/// How long has it been (in seconds) since the pointer was last moved?
|
||||
#[inline(always)]
|
||||
pub fn time_since_last_movement(&self) -> f64 {
|
||||
self.time - self.last_move_time
|
||||
}
|
||||
|
||||
/// Was any pointer button pressed (`!down -> down`) this frame?
|
||||
/// This can sometimes return `true` even if `any_down() == false`
|
||||
/// because a press can be shorted than one frame.
|
||||
@@ -1035,6 +1049,7 @@ impl PointerState {
|
||||
last_click_time,
|
||||
last_last_click_time,
|
||||
pointer_events,
|
||||
last_move_time,
|
||||
} = self;
|
||||
|
||||
ui.label(format!("latest_pos: {latest_pos:?}"));
|
||||
@@ -1052,6 +1067,7 @@ impl PointerState {
|
||||
));
|
||||
ui.label(format!("last_click_time: {last_click_time:#?}"));
|
||||
ui.label(format!("last_last_click_time: {last_last_click_time:#?}"));
|
||||
ui.label(format!("last_move_time: {last_move_time:#?}"));
|
||||
ui.label(format!("pointer_events: {pointer_events:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,11 @@ impl Memory {
|
||||
pub fn reset_areas(&mut self) {
|
||||
self.areas = Default::default();
|
||||
}
|
||||
|
||||
/// Obtain the previous rectangle of an area.
|
||||
pub fn area_rect(&self, id: impl Into<Id>) -> Option<Rect> {
|
||||
self.areas.get(id.into()).map(|state| state.rect())
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Popups
|
||||
|
||||
@@ -311,12 +311,7 @@ impl Painter {
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
});
|
||||
self.add(RectShape::new(rect, rounding, fill_color, stroke));
|
||||
}
|
||||
|
||||
pub fn rect_filled(
|
||||
@@ -325,12 +320,7 @@ impl Painter {
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
});
|
||||
self.add(RectShape::filled(rect, rounding, fill_color));
|
||||
}
|
||||
|
||||
pub fn rect_stroke(
|
||||
@@ -339,12 +329,7 @@ impl Painter {
|
||||
rounding: impl Into<Rounding>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) {
|
||||
self.add(RectShape {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
});
|
||||
self.add(RectShape::stroke(rect, rounding, stroke));
|
||||
}
|
||||
|
||||
/// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.
|
||||
|
||||
@@ -435,6 +435,15 @@ impl Response {
|
||||
}
|
||||
}
|
||||
|
||||
if !self.is_tooltip_open()
|
||||
&& self.ctx.input(|i| i.pointer.time_since_last_movement())
|
||||
< self.ctx.style().interaction.tooltip_delay
|
||||
{
|
||||
// Keep waiting until the mouse has been still for a while
|
||||
self.ctx.request_repaint();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't want tooltips of things while we are dragging them,
|
||||
// but we do want tooltips while holding down on an item on a touch screen.
|
||||
if self
|
||||
|
||||
@@ -441,6 +441,9 @@ pub struct Interaction {
|
||||
|
||||
/// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving
|
||||
pub show_tooltips_only_when_still: bool,
|
||||
|
||||
/// Delay in seconds before showing tooltips after the mouse stops moving
|
||||
pub tooltip_delay: f64,
|
||||
}
|
||||
|
||||
/// Controls the visual style (colors etc) of egui.
|
||||
@@ -762,6 +765,7 @@ impl Default for Interaction {
|
||||
resize_grab_radius_side: 5.0,
|
||||
resize_grab_radius_corner: 10.0,
|
||||
show_tooltips_only_when_still: true,
|
||||
tooltip_delay: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1218,6 +1222,7 @@ impl Interaction {
|
||||
resize_grab_radius_side,
|
||||
resize_grab_radius_corner,
|
||||
show_tooltips_only_when_still,
|
||||
tooltip_delay,
|
||||
} = self;
|
||||
ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side"));
|
||||
ui.add(
|
||||
@@ -1227,6 +1232,7 @@ impl Interaction {
|
||||
show_tooltips_only_when_still,
|
||||
"Only show tooltips if mouse is still",
|
||||
);
|
||||
ui.add(Slider::new(tooltip_delay, 0.0..=1.0).text("tooltip_delay"));
|
||||
|
||||
ui.vertical_centered(|ui| reset_button(ui, self));
|
||||
}
|
||||
|
||||
@@ -318,12 +318,12 @@ impl<'a> Widget for Checkbox<'a> {
|
||||
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
||||
let visuals = ui.style().interact(&response);
|
||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: big_icon_rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
});
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
big_icon_rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
));
|
||||
|
||||
if *checked {
|
||||
// Check mark:
|
||||
@@ -535,7 +535,7 @@ impl Widget for ImageButton {
|
||||
let selection = ui.visuals().selection;
|
||||
(
|
||||
Vec2::ZERO,
|
||||
Rounding::none(),
|
||||
Rounding::ZERO,
|
||||
selection.bg_fill,
|
||||
selection.stroke,
|
||||
)
|
||||
@@ -552,6 +552,8 @@ impl Widget for ImageButton {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let image = image.rounding(rounding); // apply rounding to the image
|
||||
|
||||
// Draw frame background (for transparent images):
|
||||
ui.painter()
|
||||
.rect_filled(rect.expand2(expansion), rounding, fill);
|
||||
|
||||
@@ -42,6 +42,7 @@ pub struct Image {
|
||||
tint: Color32,
|
||||
sense: Sense,
|
||||
rotation: Option<(Rot2, Vec2)>,
|
||||
rounding: Rounding,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
@@ -54,6 +55,7 @@ impl Image {
|
||||
tint: Color32::WHITE,
|
||||
sense: Sense::hover(),
|
||||
rotation: None,
|
||||
rounding: Rounding::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +91,26 @@ impl Image {
|
||||
/// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
|
||||
///
|
||||
/// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off rounding of the image.
|
||||
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
|
||||
self.rotation = Some((Rot2::from_angle(angle), origin));
|
||||
self.rounding = Rounding::ZERO; // incompatible with rotation
|
||||
self
|
||||
}
|
||||
|
||||
/// Round the corners of the image.
|
||||
///
|
||||
/// The default is no rounding ([`Rounding::ZERO`]).
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off any rotation of the image.
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.rounding = rounding.into();
|
||||
if self.rounding != Rounding::ZERO {
|
||||
self.rotation = None; // incompatible with rounding
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -111,6 +131,7 @@ impl Image {
|
||||
tint,
|
||||
sense: _,
|
||||
rotation,
|
||||
rounding,
|
||||
} = self;
|
||||
|
||||
if *bg_fill != Default::default() {
|
||||
@@ -119,14 +140,27 @@ impl Image {
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
|
||||
{
|
||||
// TODO(emilk): builder pattern for Mesh
|
||||
if let Some((rot, origin)) = rotation {
|
||||
// TODO(emilk): implement this using `PathShape` (add texture support to it).
|
||||
// This will also give us anti-aliasing of rotated images.
|
||||
egui_assert!(
|
||||
*rounding == Rounding::ZERO,
|
||||
"Image had both rounding and rotation. Please pick only one"
|
||||
);
|
||||
|
||||
let mut mesh = Mesh::with_texture(*texture_id);
|
||||
mesh.add_rect_with_uv(rect, *uv, *tint);
|
||||
if let Some((rot, origin)) = rotation {
|
||||
mesh.rotate(*rot, rect.min + *origin * *size);
|
||||
}
|
||||
mesh.rotate(*rot, rect.min + *origin * *size);
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
} else {
|
||||
ui.painter().add(RectShape {
|
||||
rect,
|
||||
rounding: *rounding,
|
||||
fill: *tint,
|
||||
stroke: Stroke::NONE,
|
||||
fill_texture_id: *texture_id,
|
||||
uv: *uv,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{Response, Sense, TextStyle, Ui, WidgetText};
|
||||
|
||||
use super::{transform::PlotTransform, GridMark};
|
||||
|
||||
pub(super) type AxisFormatterFn = fn(f64, usize, &RangeInclusive<f64>) -> String;
|
||||
pub(super) type AxisFormatterFn = dyn Fn(f64, usize, &RangeInclusive<f64>) -> String;
|
||||
|
||||
/// X or Y axis.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -81,7 +81,7 @@ impl From<VPlacement> for Placement {
|
||||
#[derive(Clone)]
|
||||
pub struct AxisHints {
|
||||
pub(super) label: WidgetText,
|
||||
pub(super) formatter: AxisFormatterFn,
|
||||
pub(super) formatter: Arc<AxisFormatterFn>,
|
||||
pub(super) digits: usize,
|
||||
pub(super) placement: Placement,
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl Default for AxisHints {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: Default::default(),
|
||||
formatter: Self::default_formatter,
|
||||
formatter: Arc::new(Self::default_formatter),
|
||||
digits: 5,
|
||||
placement: Placement::LeftBottom,
|
||||
}
|
||||
@@ -111,8 +111,11 @@ impl AxisHints {
|
||||
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
||||
/// The second parameter is the maximum number of characters that fit into y-labels.
|
||||
/// The second parameter of `formatter` is the currently shown range on this axis.
|
||||
pub fn formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||
self.formatter = fmt;
|
||||
pub fn formatter(
|
||||
mut self,
|
||||
fmt: impl Fn(f64, usize, &RangeInclusive<f64>) -> String + 'static,
|
||||
) -> Self {
|
||||
self.formatter = Arc::new(fmt);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -127,12 +127,7 @@ impl Bar {
|
||||
};
|
||||
|
||||
let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max());
|
||||
let rect = Shape::Rect(RectShape {
|
||||
rect,
|
||||
rounding: Rounding::none(),
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
|
||||
|
||||
shapes.push(rect);
|
||||
}
|
||||
|
||||
@@ -150,12 +150,7 @@ impl BoxElem {
|
||||
&self.point_at(self.argument - self.box_width / 2.0, self.spread.quartile1),
|
||||
&self.point_at(self.argument + self.box_width / 2.0, self.spread.quartile3),
|
||||
);
|
||||
let rect = Shape::Rect(RectShape {
|
||||
rect,
|
||||
rounding: Rounding::none(),
|
||||
fill,
|
||||
stroke,
|
||||
});
|
||||
let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
|
||||
shapes.push(rect);
|
||||
|
||||
let line_between = |v1, v2| {
|
||||
|
||||
@@ -613,24 +613,32 @@ impl Plot {
|
||||
|
||||
/// Specify custom formatter for ticks on the main X-axis.
|
||||
///
|
||||
/// The first parameter of `fmt` is the raw tick value as `f64`.
|
||||
/// The second parameter is the maximum requested number of characters per tick label.
|
||||
/// The second parameter of `fmt` is the currently shown range on this axis.
|
||||
pub fn x_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||
/// Arguments of `fmt`:
|
||||
/// * raw tick value as `f64`.
|
||||
/// * maximum requested number of characters per tick label.
|
||||
/// * currently shown range on this axis.
|
||||
pub fn x_axis_formatter(
|
||||
mut self,
|
||||
fmt: impl Fn(f64, usize, &RangeInclusive<f64>) -> String + 'static,
|
||||
) -> Self {
|
||||
if let Some(main) = self.x_axes.first_mut() {
|
||||
main.formatter = fmt;
|
||||
main.formatter = Arc::new(fmt);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify custom formatter for ticks on the main Y-axis.
|
||||
///
|
||||
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
||||
/// The second parameter is the maximum requested number of characters per tick label.
|
||||
/// The second parameter of `formatter` is the currently shown range on this axis.
|
||||
pub fn y_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||
/// Arguments of `fmt`:
|
||||
/// * raw tick value as `f64`.
|
||||
/// * maximum requested number of characters per tick label.
|
||||
/// * currently shown range on this axis.
|
||||
pub fn y_axis_formatter(
|
||||
mut self,
|
||||
fmt: impl Fn(f64, usize, &RangeInclusive<f64>) -> String + 'static,
|
||||
) -> Self {
|
||||
if let Some(main) = self.y_axes.first_mut() {
|
||||
main.formatter = fmt;
|
||||
main.formatter = Arc::new(fmt);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -864,12 +872,14 @@ impl Plot {
|
||||
|
||||
// Background
|
||||
if show_background {
|
||||
ui.painter().with_clip_rect(rect).add(epaint::RectShape {
|
||||
rect,
|
||||
rounding: Rounding::same(2.0),
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
});
|
||||
ui.painter()
|
||||
.with_clip_rect(rect)
|
||||
.add(epaint::RectShape::new(
|
||||
rect,
|
||||
Rounding::same(2.0),
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
));
|
||||
}
|
||||
|
||||
// --- Legend ---
|
||||
|
||||
@@ -368,31 +368,27 @@ impl<'t> TextEdit<'t> {
|
||||
let frame_rect = frame_rect.expand(visuals.expansion);
|
||||
let shape = if is_mutable {
|
||||
if output.response.has_focus() {
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
rounding: visuals.rounding,
|
||||
// fill: ui.visuals().selection.bg_fill,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().selection.stroke,
|
||||
}
|
||||
epaint::RectShape::new(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.visuals().selection.stroke,
|
||||
)
|
||||
} else {
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
rounding: visuals.rounding,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
}
|
||||
epaint::RectShape::new(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let visuals = &ui.style().visuals.widgets.inactive;
|
||||
epaint::RectShape {
|
||||
rect: frame_rect,
|
||||
rounding: visuals.rounding,
|
||||
// fill: ui.visuals().extreme_bg_color,
|
||||
// fill: visuals.bg_fill,
|
||||
fill: Color32::TRANSPARENT,
|
||||
stroke: visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
}
|
||||
epaint::RectShape::stroke(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
)
|
||||
};
|
||||
|
||||
ui.painter().set(where_to_put_background, shape);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{num::NonZeroU64, sync::Arc};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use eframe::{
|
||||
egui_wgpu::wgpu::util::DeviceExt,
|
||||
@@ -85,7 +85,7 @@ impl Custom3d {
|
||||
wgpu_render_state
|
||||
.renderer
|
||||
.write()
|
||||
.paint_callback_resources
|
||||
.callback_resources
|
||||
.insert(TriangleRenderResources {
|
||||
pipeline,
|
||||
bind_group,
|
||||
@@ -129,46 +129,64 @@ impl eframe::App for Custom3d {
|
||||
}
|
||||
}
|
||||
|
||||
// Callbacks in egui_wgpu have 3 stages:
|
||||
// * prepare (per callback impl)
|
||||
// * finish_prepare (once)
|
||||
// * paint (per callback impl)
|
||||
//
|
||||
// The prepare callback is called every frame before paint and is given access to the wgpu
|
||||
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
|
||||
// rendering.
|
||||
// If [`egui_wgpu::Renderer`] has [`egui_wgpu::FinishPrepareCallback`] registered,
|
||||
// it will be called after all `prepare` callbacks have been called.
|
||||
// You can use this to update any shared resources that need to be updated once per frame
|
||||
// after all callbacks have been processed.
|
||||
//
|
||||
// On both prepare methods you can use the main `CommandEncoder` that is passed-in,
|
||||
// return an arbitrary number of user-defined `CommandBuffer`s, or both.
|
||||
// The main command buffer, as well as all user-defined ones, will be submitted together
|
||||
// to the GPU in a single call.
|
||||
//
|
||||
// The paint callback is called after finish prepare and is given access to egui's main render pass,
|
||||
// which can be used to issue draw commands.
|
||||
struct CustomTriangleCallback {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl egui_wgpu::CallbackTrait for CustomTriangleCallback {
|
||||
fn prepare(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
_egui_encoder: &mut wgpu::CommandEncoder,
|
||||
resources: &mut egui_wgpu::CallbackResources,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
let resources: &TriangleRenderResources = resources.get().unwrap();
|
||||
resources.prepare(device, queue, self.angle);
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn paint<'a>(
|
||||
&self,
|
||||
_info: egui::PaintCallbackInfo,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
resources: &'a egui_wgpu::CallbackResources,
|
||||
) {
|
||||
let resources: &TriangleRenderResources = resources.get().unwrap();
|
||||
resources.paint(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||
|
||||
self.angle += response.drag_delta().x * 0.01;
|
||||
|
||||
// Clone locals so we can move them into the paint callback:
|
||||
let angle = self.angle;
|
||||
|
||||
// The callback function for WGPU is in two stages: prepare, and paint.
|
||||
//
|
||||
// The prepare callback is called every frame before paint and is given access to the wgpu
|
||||
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
|
||||
// rendering.
|
||||
//
|
||||
// You can use the main `CommandEncoder` that is passed-in, return an arbitrary number
|
||||
// of user-defined `CommandBuffer`s, or both.
|
||||
// The main command buffer, as well as all user-defined ones, will be submitted together
|
||||
// to the GPU in a single call.
|
||||
//
|
||||
// The paint callback is called after prepare and is given access to the render pass, which
|
||||
// can be used to issue draw commands.
|
||||
let cb = egui_wgpu::CallbackFn::new()
|
||||
.prepare(move |device, queue, _encoder, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.prepare(device, queue, angle);
|
||||
Vec::new()
|
||||
})
|
||||
.paint(move |_info, render_pass, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.paint(render_pass);
|
||||
});
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
ui.painter().add(egui_wgpu::Callback::new_paint_callback(
|
||||
rect,
|
||||
callback: Arc::new(cb),
|
||||
};
|
||||
|
||||
ui.painter().add(callback);
|
||||
CustomTriangleCallback { angle: self.angle },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,12 +70,12 @@ impl FrameHistory {
|
||||
let to_screen = emath::RectTransform::from_to(graph_rect, rect);
|
||||
|
||||
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
||||
shapes.push(Shape::Rect(epaint::RectShape {
|
||||
shapes.push(Shape::Rect(epaint::RectShape::new(
|
||||
rect,
|
||||
rounding: style.rounding,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.style().noninteractive().bg_stroke,
|
||||
}));
|
||||
style.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.style().noninteractive().bg_stroke,
|
||||
)));
|
||||
|
||||
let rect = rect.shrink(4.0);
|
||||
let color = ui.visuals().text_color();
|
||||
|
||||
@@ -64,12 +64,7 @@ pub fn drop_target<R>(
|
||||
|
||||
ui.painter().set(
|
||||
where_to_put_background,
|
||||
epaint::RectShape {
|
||||
rounding: style.rounding,
|
||||
fill,
|
||||
stroke,
|
||||
rect,
|
||||
},
|
||||
epaint::RectShape::new(rect, style.rounding, fill, stroke),
|
||||
);
|
||||
|
||||
InnerResponse::new(ret, response)
|
||||
|
||||
@@ -8,7 +8,7 @@ fn main() {
|
||||
|
||||
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
|
||||
|
||||
let png_data = include_bytes!("../../../examples/retained_image/src/rust-logo-256x256.png");
|
||||
let png_data = include_bytes!("../../../examples/retained_image/src/crab.png");
|
||||
let image = load_glium_image(png_data);
|
||||
let image_size = egui::vec2(image.width as f32, image.height as f32);
|
||||
// Load to gpu memory
|
||||
|
||||
@@ -58,6 +58,12 @@ impl Rect {
|
||||
max: pos2(f32::NAN, f32::NAN),
|
||||
};
|
||||
|
||||
/// A [`Rect`] filled with zeroes.
|
||||
pub const ZERO: Self = Self {
|
||||
min: Pos2::ZERO,
|
||||
max: Pos2::ZERO,
|
||||
};
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_min_max(min: Pos2, max: Pos2) -> Self {
|
||||
Rect { min, max }
|
||||
|
||||
@@ -120,13 +120,13 @@ impl Mesh {
|
||||
pub fn append_ref(&mut self, other: &Mesh) {
|
||||
crate::epaint_assert!(other.is_valid());
|
||||
|
||||
if !self.is_empty() {
|
||||
if self.is_empty() {
|
||||
self.texture_id = other.texture_id;
|
||||
} else {
|
||||
assert_eq!(
|
||||
self.texture_id, other.texture_id,
|
||||
"Can't merge Mesh using different textures"
|
||||
);
|
||||
} else {
|
||||
self.texture_id = other.texture_id;
|
||||
}
|
||||
|
||||
let index_offset = self.vertices.len() as u32;
|
||||
|
||||
@@ -282,6 +282,8 @@ impl Shape {
|
||||
pub fn texture_id(&self) -> super::TextureId {
|
||||
if let Shape::Mesh(mesh) = self {
|
||||
mesh.texture_id
|
||||
} else if let Shape::Rect(rect_shape) = self {
|
||||
rect_shape.fill_texture_id
|
||||
} else {
|
||||
super::TextureId::default()
|
||||
}
|
||||
@@ -406,6 +408,8 @@ pub struct PathShape {
|
||||
|
||||
/// Color and thickness of the line.
|
||||
pub stroke: Stroke,
|
||||
// TODO(emilk): Add texture support either by supplying uv for each point,
|
||||
// or by some transform from points to uv (e.g. a callback or a linear transform matrix).
|
||||
}
|
||||
|
||||
impl PathShape {
|
||||
@@ -476,7 +480,7 @@ impl From<PathShape> for Shape {
|
||||
pub struct RectShape {
|
||||
pub rect: Rect,
|
||||
|
||||
/// How rounded the corners are. Use `Rounding::none()` for no rounding.
|
||||
/// How rounded the corners are. Use `Rounding::ZERO` for no rounding.
|
||||
pub rounding: Rounding,
|
||||
|
||||
/// How to fill the rectangle.
|
||||
@@ -484,9 +488,37 @@ pub struct RectShape {
|
||||
|
||||
/// The thickness and color of the outline.
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// If the rect should be filled with a texture, which one?
|
||||
///
|
||||
/// The texture is multiplied with [`Self::fill`].
|
||||
pub fill_texture_id: TextureId,
|
||||
|
||||
/// What UV coordinates to use for the texture?
|
||||
///
|
||||
/// To display a texture, set [`Self::fill_texture_id`],
|
||||
/// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`.
|
||||
pub uv: Rect,
|
||||
}
|
||||
|
||||
impl RectShape {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filled(
|
||||
rect: Rect,
|
||||
@@ -498,6 +530,8 @@ impl RectShape {
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,6 +542,8 @@ impl RectShape {
|
||||
rounding: rounding.into(),
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +585,7 @@ pub struct Rounding {
|
||||
impl Default for Rounding {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::none()
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +602,14 @@ impl From<f32> for Rounding {
|
||||
}
|
||||
|
||||
impl Rounding {
|
||||
/// No rounding on any corner.
|
||||
pub const ZERO: Self = Self {
|
||||
nw: 0.0,
|
||||
ne: 0.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn same(radius: f32) -> Self {
|
||||
Self {
|
||||
@@ -577,6 +621,7 @@ impl Rounding {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[deprecated = "Use Rounding::ZERO"]
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
nw: 0.0,
|
||||
@@ -807,7 +852,7 @@ pub struct PaintCallback {
|
||||
///
|
||||
/// The concrete value of `callback` depends on the rendering backend used. For instance, the
|
||||
/// `glow` backend requires that callback be an `egui_glow::CallbackFn` while the `wgpu`
|
||||
/// backend requires a `egui_wgpu::CallbackFn`.
|
||||
/// backend requires a `egui_wgpu::Callback`.
|
||||
///
|
||||
/// If the type cannot be downcast to the type expected by the current backend the callback
|
||||
/// will not be drawn.
|
||||
@@ -817,7 +862,9 @@ pub struct PaintCallback {
|
||||
///
|
||||
/// The rendering backend is also responsible for restoring any state, such as the bound shader
|
||||
/// program, vertex array, etc.
|
||||
pub callback: Arc<dyn Any + Sync + Send>,
|
||||
///
|
||||
/// Shape has to be clone, therefore this has to be an `Arc` instead of a `Box`.
|
||||
pub callback: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PaintCallback {
|
||||
|
||||
@@ -492,6 +492,20 @@ impl Path {
|
||||
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
|
||||
fill_closed_path(feathering, &mut self.0, color, out);
|
||||
}
|
||||
|
||||
/// Like [`Self::fill`] but with texturing.
|
||||
///
|
||||
/// The `uv_from_pos` is called for each vertex position.
|
||||
pub fn fill_with_uv(
|
||||
&mut self,
|
||||
feathering: f32,
|
||||
color: Color32,
|
||||
texture_id: TextureId,
|
||||
uv_from_pos: impl Fn(Pos2) -> Pos2,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
fill_closed_path_with_uv(feathering, &mut self.0, color, texture_id, uv_from_pos, out);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod path {
|
||||
@@ -508,7 +522,7 @@ pub mod path {
|
||||
|
||||
let r = clamp_radius(rounding, rect);
|
||||
|
||||
if r == Rounding::none() {
|
||||
if r == Rounding::ZERO {
|
||||
let min = rect.min;
|
||||
let max = rect.max;
|
||||
path.reserve(4);
|
||||
@@ -728,6 +742,89 @@ fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`fill_closed_path`] but with texturing.
|
||||
///
|
||||
/// The `uv_from_pos` is called for each vertex position.
|
||||
fn fill_closed_path_with_uv(
|
||||
feathering: f32,
|
||||
path: &mut [PathPoint],
|
||||
color: Color32,
|
||||
texture_id: TextureId,
|
||||
uv_from_pos: impl Fn(Pos2) -> Pos2,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
if color == Color32::TRANSPARENT {
|
||||
return;
|
||||
}
|
||||
|
||||
if out.is_empty() {
|
||||
out.texture_id = texture_id;
|
||||
} else {
|
||||
assert_eq!(
|
||||
out.texture_id, texture_id,
|
||||
"Mixing different `texture_id` in the same "
|
||||
);
|
||||
}
|
||||
|
||||
let n = path.len() as u32;
|
||||
if feathering > 0.0 {
|
||||
if cw_signed_area(path) < 0.0 {
|
||||
// Wrong winding order - fix:
|
||||
path.reverse();
|
||||
for point in path.iter_mut() {
|
||||
point.normal = -point.normal;
|
||||
}
|
||||
}
|
||||
|
||||
out.reserve_triangles(3 * n as usize);
|
||||
out.reserve_vertices(2 * n as usize);
|
||||
let color_outer = Color32::TRANSPARENT;
|
||||
let idx_inner = out.vertices.len() as u32;
|
||||
let idx_outer = idx_inner + 1;
|
||||
|
||||
// The fill:
|
||||
for i in 2..n {
|
||||
out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
|
||||
}
|
||||
|
||||
// The feathering:
|
||||
let mut i0 = n - 1;
|
||||
for i1 in 0..n {
|
||||
let p1 = &path[i1 as usize];
|
||||
let dm = 0.5 * feathering * p1.normal;
|
||||
|
||||
let pos = p1.pos - dm;
|
||||
out.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: uv_from_pos(pos),
|
||||
color,
|
||||
});
|
||||
|
||||
let pos = p1.pos + dm;
|
||||
out.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: uv_from_pos(pos),
|
||||
color: color_outer,
|
||||
});
|
||||
|
||||
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
||||
out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
|
||||
i0 = i1;
|
||||
}
|
||||
} else {
|
||||
out.reserve_triangles(n as usize);
|
||||
let idx = out.vertices.len() as u32;
|
||||
out.vertices.extend(path.iter().map(|p| Vertex {
|
||||
pos: p.pos,
|
||||
uv: uv_from_pos(p.pos),
|
||||
color,
|
||||
}));
|
||||
for i in 2..n {
|
||||
out.add_triangle(idx, idx + i - 1, idx + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tessellate the given path as a stroke with thickness.
|
||||
fn stroke_path(
|
||||
feathering: f32,
|
||||
@@ -1304,6 +1401,8 @@ impl Tessellator {
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
fill_texture_id,
|
||||
uv,
|
||||
} = *rect;
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
@@ -1345,7 +1444,21 @@ impl Tessellator {
|
||||
path.clear();
|
||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||
path.add_line_loop(&self.scratchpad_points);
|
||||
path.fill(self.feathering, fill, out);
|
||||
|
||||
if uv.is_positive() {
|
||||
// Textured
|
||||
let uv_from_pos = |p: Pos2| {
|
||||
pos2(
|
||||
remap(p.x, rect.x_range(), uv.x_range()),
|
||||
remap(p.y, rect.y_range(), uv.y_range()),
|
||||
)
|
||||
};
|
||||
path.fill_with_uv(self.feathering, fill, fill_texture_id, uv_from_pos, out);
|
||||
} else {
|
||||
// Untextured
|
||||
path.fill(self.feathering, fill, out);
|
||||
}
|
||||
|
||||
path.stroke_closed(self.feathering, stroke, out);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
examples/retained_image/src/crab.png
Normal file
BIN
examples/retained_image/src/crab.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
@@ -6,7 +6,7 @@ use egui_extras::RetainedImage;
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(300.0, 900.0)),
|
||||
initial_window_size: Some(egui::vec2(400.0, 1000.0)),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
@@ -18,18 +18,17 @@ fn main() -> Result<(), eframe::Error> {
|
||||
|
||||
struct MyApp {
|
||||
image: RetainedImage,
|
||||
rounding: f32,
|
||||
tint: egui::Color32,
|
||||
}
|
||||
|
||||
impl Default for MyApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
image: RetainedImage::from_image_bytes(
|
||||
"rust-logo-256x256.png",
|
||||
include_bytes!("rust-logo-256x256.png"),
|
||||
)
|
||||
.unwrap(),
|
||||
tint: egui::Color32::from_rgb(255, 0, 255),
|
||||
// crab image is CC0, found on https://stocksnap.io/search/crab
|
||||
image: RetainedImage::from_image_bytes("crab.png", include_bytes!("crab.png")).unwrap(),
|
||||
rounding: 32.0,
|
||||
tint: egui::Color32::from_rgb(100, 200, 200),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,30 +44,50 @@ impl eframe::App for MyApp {
|
||||
render(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
let Self {
|
||||
image,
|
||||
rounding,
|
||||
tint,
|
||||
} = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("This is an image:");
|
||||
self.image.show(ui);
|
||||
image.show(ui);
|
||||
|
||||
ui.heading("This is a rotated image with a tint:");
|
||||
ui.add_space(32.0);
|
||||
|
||||
ui.heading("This is a tinted image with rounded corners:");
|
||||
ui.add(
|
||||
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2())
|
||||
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5))
|
||||
.tint(self.tint),
|
||||
egui::Image::new(image.texture_id(ctx), image.size_vec2())
|
||||
.tint(*tint)
|
||||
.rounding(*rounding),
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Tint:");
|
||||
egui::color_picker::color_edit_button_srgba(
|
||||
ui,
|
||||
&mut self.tint,
|
||||
tint,
|
||||
egui::color_picker::Alpha::BlendOrAdditive,
|
||||
);
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.label("Rounding:");
|
||||
ui.add(
|
||||
egui::DragValue::new(rounding)
|
||||
.speed(1.0)
|
||||
.clamp_range(0.0..=0.5 * image.size_vec2().min_elem()),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(32.0);
|
||||
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(
|
||||
self.image.texture_id(ctx),
|
||||
self.image.size_vec2(),
|
||||
image.texture_id(ctx),
|
||||
image.size_vec2(),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
@@ -1 +0,0 @@
|
||||
Rust logo by Mozilla, from https://github.com/rust-lang/rust-artwork
|
||||
Reference in New Issue
Block a user