mirror of
https://github.com/emilk/egui.git
synced 2026-06-28 07:23:13 -04:00
Add Shape::Callback to do custom rendering inside of an egui UI (#1351)
* Add Shape::Callback to do custom rendering inside of an egui UI * Use Rc<glow::Context> everywhere * Remove trait WebPainter * Add glow::Context to epi::App::setup
This commit is contained in:
@@ -110,7 +110,7 @@ pub use {
|
||||
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
shadow::Shadow,
|
||||
shape::{CircleShape, PathShape, RectShape, Rounding, Shape, TextShape},
|
||||
shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape},
|
||||
stats::PaintStats,
|
||||
stroke::Stroke,
|
||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
@@ -166,18 +166,24 @@ pub struct ClippedShape(
|
||||
pub Shape,
|
||||
);
|
||||
|
||||
/// A [`Mesh`] within a clip rectangle.
|
||||
/// A [`Mesh`] or [`PaintCallback`] within a clip rectangle.
|
||||
///
|
||||
/// Everything is using logical points.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ClippedMesh(
|
||||
pub struct ClippedPrimitive {
|
||||
/// Clip / scissor rectangle.
|
||||
/// Only show the part of the [`Mesh`] that falls within this.
|
||||
pub emath::Rect,
|
||||
/// The shape
|
||||
pub Mesh,
|
||||
);
|
||||
pub clip_rect: emath::Rect,
|
||||
/// What to paint - either a [`Mesh`] or a [`PaintCallback`].
|
||||
pub primitive: Primitive,
|
||||
}
|
||||
|
||||
/// A rendering primitive - either a [`Mesh`] or a [`PaintCallback`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Primitive {
|
||||
Mesh(Mesh),
|
||||
Callback(PaintCallback),
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
//! The different shapes that can be painted.
|
||||
|
||||
use crate::{
|
||||
text::{FontId, Fonts, Galley},
|
||||
Color32, Mesh, Stroke,
|
||||
};
|
||||
use crate::{CubicBezierShape, QuadraticBezierShape};
|
||||
use emath::*;
|
||||
|
||||
pub use crate::{CubicBezierShape, QuadraticBezierShape};
|
||||
|
||||
/// A paint primitive such as a circle or a piece of text.
|
||||
/// Coordinates are all screen space points (not physical pixels).
|
||||
#[must_use = "Add a Shape to a Painter"]
|
||||
@@ -29,6 +32,16 @@ pub enum Shape {
|
||||
Mesh(Mesh),
|
||||
QuadraticBezier(QuadraticBezierShape),
|
||||
CubicBezier(CubicBezierShape),
|
||||
|
||||
/// Backend-specific painting.
|
||||
Callback(PaintCallback),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn shape_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<Shape>();
|
||||
}
|
||||
|
||||
impl From<Vec<Shape>> for Shape {
|
||||
@@ -196,6 +209,7 @@ impl Shape {
|
||||
Self::Mesh(mesh) => mesh.calc_bounds(),
|
||||
Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
|
||||
Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
|
||||
Self::Callback(custom) => custom.rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,6 +266,9 @@ impl Shape {
|
||||
*p += delta;
|
||||
}
|
||||
}
|
||||
Shape::Callback(shape) => {
|
||||
shape.rect = shape.rect.translate(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -616,3 +633,57 @@ fn dashes_from_line(
|
||||
position_on_segment -= segment_length;
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// If you want to paint some 3D shapes inside an egui region, you can use this.
|
||||
///
|
||||
/// This is advanced usage, and is backend specific.
|
||||
#[derive(Clone)]
|
||||
pub struct PaintCallback {
|
||||
/// Where to paint.
|
||||
pub rect: Rect,
|
||||
|
||||
/// Paint something custom using.
|
||||
///
|
||||
/// The argument is the render context, and what it contains depends on the backend.
|
||||
/// In `eframe` it will be `egui_glow::Painter`.
|
||||
///
|
||||
/// The rendering backend is responsible for first setting the active viewport to [`Self::rect`].
|
||||
/// The rendering backend is also responsible for restoring any state it needs,
|
||||
/// such as the bound shader program and vertex array.
|
||||
pub callback: std::sync::Arc<dyn Fn(&dyn std::any::Any) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl PaintCallback {
|
||||
#[inline]
|
||||
pub fn call(&self, render_ctx: &dyn std::any::Any) {
|
||||
(self.callback)(render_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PaintCallback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CustomShape")
|
||||
.field("rect", &self.rect)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq for PaintCallback {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// As I understand it, the problem this clippy is trying to protect against
|
||||
// can only happen if we do dynamic casts back and forth on the pointers, and we don't do that.
|
||||
#[allow(clippy::vtable_address_comparisons)]
|
||||
{
|
||||
self.rect.eq(&other.rect) && std::sync::Arc::ptr_eq(&self.callback, &other.callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaintCallback> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: PaintCallback) -> Self {
|
||||
Self::Callback(shape)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,5 +51,8 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||
adjust_color(&mut bezier.fill);
|
||||
adjust_color(&mut bezier.stroke.color);
|
||||
}
|
||||
Shape::Callback(_) => {
|
||||
// Can't tint user callback code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,12 +162,13 @@ pub struct PaintStats {
|
||||
pub shape_path: AllocInfo,
|
||||
pub shape_mesh: AllocInfo,
|
||||
pub shape_vec: AllocInfo,
|
||||
pub num_callbacks: usize,
|
||||
|
||||
pub text_shape_vertices: AllocInfo,
|
||||
pub text_shape_indices: AllocInfo,
|
||||
|
||||
/// Number of separate clip rectangles
|
||||
pub clipped_meshes: AllocInfo,
|
||||
pub clipped_primitives: AllocInfo,
|
||||
pub vertices: AllocInfo,
|
||||
pub indices: AllocInfo,
|
||||
}
|
||||
@@ -215,27 +216,25 @@ impl PaintStats {
|
||||
Shape::Mesh(mesh) => {
|
||||
self.shape_mesh += AllocInfo::from_mesh(mesh);
|
||||
}
|
||||
Shape::Callback(_) => {
|
||||
self.num_callbacks += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_clipped_meshes(mut self, clipped_meshes: &[crate::ClippedMesh]) -> Self {
|
||||
self.clipped_meshes += AllocInfo::from_slice(clipped_meshes);
|
||||
for ClippedMesh(_, indices) in clipped_meshes {
|
||||
self.vertices += AllocInfo::from_slice(&indices.vertices);
|
||||
self.indices += AllocInfo::from_slice(&indices.indices);
|
||||
pub fn with_clipped_primitives(
|
||||
mut self,
|
||||
clipped_primitives: &[crate::ClippedPrimitive],
|
||||
) -> Self {
|
||||
self.clipped_primitives += AllocInfo::from_slice(clipped_primitives);
|
||||
for clipped_primitive in clipped_primitives {
|
||||
if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
|
||||
self.vertices += AllocInfo::from_slice(&mesh.vertices);
|
||||
self.indices += AllocInfo::from_slice(&mesh.indices);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// pub fn total(&self) -> AllocInfo {
|
||||
// self.shapes
|
||||
// + self.shape_text
|
||||
// + self.shape_path
|
||||
// + self.shape_mesh
|
||||
// + self.clipped_meshes
|
||||
// + self.vertices
|
||||
// + self.indices
|
||||
// }
|
||||
}
|
||||
|
||||
fn megabytes(size: usize) -> String {
|
||||
|
||||
@@ -781,6 +781,9 @@ impl Tessellator {
|
||||
self.tessellate_quadratic_bezier(quadratic_shape, out);
|
||||
}
|
||||
Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out),
|
||||
Shape::Callback(_) => {
|
||||
panic!("Shape::Callback passed to Tessellator");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,58 +1049,97 @@ pub fn tessellate_shapes(
|
||||
shapes: Vec<ClippedShape>,
|
||||
options: TessellationOptions,
|
||||
tex_size: [usize; 2],
|
||||
) -> Vec<ClippedMesh> {
|
||||
) -> Vec<ClippedPrimitive> {
|
||||
let mut tessellator = Tessellator::from_options(options);
|
||||
|
||||
let mut clipped_meshes: Vec<ClippedMesh> = Vec::default();
|
||||
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
||||
|
||||
for ClippedShape(clip_rect, shape) in shapes {
|
||||
if !clip_rect.is_positive() {
|
||||
for ClippedShape(new_clip_rect, new_shape) in shapes {
|
||||
if !new_clip_rect.is_positive() {
|
||||
continue; // skip empty clip rectangles
|
||||
}
|
||||
|
||||
let start_new_mesh = match clipped_meshes.last() {
|
||||
None => true,
|
||||
Some(cm) => cm.0 != clip_rect || cm.1.texture_id != shape.texture_id(),
|
||||
};
|
||||
if let Shape::Callback(callback) = new_shape {
|
||||
clipped_primitives.push(ClippedPrimitive {
|
||||
clip_rect: new_clip_rect,
|
||||
primitive: Primitive::Callback(callback),
|
||||
});
|
||||
} else {
|
||||
let start_new_mesh = match clipped_primitives.last() {
|
||||
None => true,
|
||||
Some(output_clipped_primitive) => {
|
||||
output_clipped_primitive.clip_rect != new_clip_rect
|
||||
|| if let Primitive::Mesh(output_mesh) = &output_clipped_primitive.primitive
|
||||
{
|
||||
output_mesh.texture_id != new_shape.texture_id()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if start_new_mesh {
|
||||
clipped_meshes.push(ClippedMesh(clip_rect, Mesh::default()));
|
||||
}
|
||||
if start_new_mesh {
|
||||
clipped_primitives.push(ClippedPrimitive {
|
||||
clip_rect: new_clip_rect,
|
||||
primitive: Primitive::Mesh(Mesh::default()),
|
||||
});
|
||||
}
|
||||
|
||||
let out = &mut clipped_meshes.last_mut().unwrap().1;
|
||||
tessellator.clip_rect = clip_rect;
|
||||
tessellator.tessellate_shape(tex_size, shape, out);
|
||||
}
|
||||
let out = clipped_primitives.last_mut().unwrap();
|
||||
|
||||
if options.debug_paint_clip_rects {
|
||||
for ClippedMesh(clip_rect, mesh) in &mut clipped_meshes {
|
||||
if mesh.texture_id == TextureId::default() {
|
||||
tessellator.clip_rect = Rect::EVERYTHING;
|
||||
tessellator.tessellate_shape(
|
||||
tex_size,
|
||||
Shape::rect_stroke(
|
||||
*clip_rect,
|
||||
0.0,
|
||||
Stroke::new(2.0, Color32::from_rgb(150, 255, 150)),
|
||||
),
|
||||
mesh,
|
||||
);
|
||||
if let Primitive::Mesh(out_mesh) = &mut out.primitive {
|
||||
tessellator.clip_rect = new_clip_rect;
|
||||
tessellator.tessellate_shape(tex_size, new_shape, out_mesh);
|
||||
} else {
|
||||
// TODO: create a new `ClippedMesh` just for the painted clip rectangle
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.debug_paint_clip_rects {
|
||||
clipped_primitives = add_clip_rects(&mut tessellator, tex_size, clipped_primitives);
|
||||
}
|
||||
|
||||
if options.debug_ignore_clip_rects {
|
||||
for ClippedMesh(clip_rect, _) in &mut clipped_meshes {
|
||||
*clip_rect = Rect::EVERYTHING;
|
||||
for clipped_primitive in &mut clipped_primitives {
|
||||
clipped_primitive.clip_rect = Rect::EVERYTHING;
|
||||
}
|
||||
}
|
||||
|
||||
for ClippedMesh(_, mesh) in &clipped_meshes {
|
||||
crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
|
||||
for clipped_primitive in &clipped_primitives {
|
||||
if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
|
||||
crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
|
||||
}
|
||||
}
|
||||
|
||||
clipped_meshes
|
||||
clipped_primitives
|
||||
}
|
||||
|
||||
fn add_clip_rects(
|
||||
tessellator: &mut Tessellator,
|
||||
tex_size: [usize; 2],
|
||||
clipped_primitives: Vec<ClippedPrimitive>,
|
||||
) -> Vec<ClippedPrimitive> {
|
||||
tessellator.clip_rect = Rect::EVERYTHING;
|
||||
let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
|
||||
|
||||
clipped_primitives
|
||||
.into_iter()
|
||||
.flat_map(|clipped_primitive| {
|
||||
let mut clip_rect_mesh = Mesh::default();
|
||||
tessellator.tessellate_shape(
|
||||
tex_size,
|
||||
Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke),
|
||||
&mut clip_rect_mesh,
|
||||
);
|
||||
|
||||
[
|
||||
clipped_primitive,
|
||||
ClippedPrimitive {
|
||||
clip_rect: Rect::EVERYTHING, // whatever
|
||||
primitive: Primitive::Mesh(clip_rect_mesh),
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user