mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
WIP: clip_rect
This commit is contained in:
@@ -82,7 +82,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain_paint_lists(&self) -> Vec<PaintCmd> {
|
||||
pub fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
|
||||
let memory = self.memory.lock();
|
||||
self.graphics.lock().drain(&memory.window_order).collect()
|
||||
}
|
||||
@@ -224,7 +224,10 @@ impl Context {
|
||||
}
|
||||
|
||||
pub fn add_paint_cmd(&self, layer: Layer, paint_cmd: PaintCmd) {
|
||||
self.graphics.lock().layer(layer).push(paint_cmd)
|
||||
self.graphics
|
||||
.lock()
|
||||
.layer(layer)
|
||||
.push((Rect::everything(), paint_cmd))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{layout, mesher::Mesher, widgets::*, *};
|
||||
use crate::{layout, mesher::*, widgets::*, *};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct Stats {
|
||||
num_batches: usize,
|
||||
num_vertices: usize,
|
||||
num_triangles: usize,
|
||||
}
|
||||
@@ -55,16 +56,20 @@ impl Emigui {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self) -> Mesh {
|
||||
pub fn paint(&mut self) -> PaintBatches {
|
||||
let mesher_options = MesherOptions {
|
||||
anti_alias: self.anti_alias,
|
||||
aa_size: 1.0 / self.last_input.pixels_per_point,
|
||||
};
|
||||
let paint_commands = self.ctx.drain_paint_lists();
|
||||
let mut mesher = Mesher::new(self.last_input.pixels_per_point);
|
||||
mesher.options.anti_alias = self.anti_alias;
|
||||
|
||||
mesher.paint(&self.ctx.fonts, &paint_commands);
|
||||
let mesh = mesher.mesh;
|
||||
self.stats.num_vertices = mesh.vertices.len();
|
||||
self.stats.num_triangles = mesh.indices.len() / 3;
|
||||
mesh
|
||||
let batches = mesh_paint_commands(&mesher_options, &self.ctx.fonts, paint_commands);
|
||||
self.stats = Default::default();
|
||||
self.stats.num_batches = batches.len();
|
||||
for (_, mesh) in &batches {
|
||||
self.stats.num_vertices += mesh.vertices.len();
|
||||
self.stats.num_triangles += mesh.indices.len() / 3;
|
||||
}
|
||||
batches
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, region: &mut Region) {
|
||||
@@ -104,6 +109,7 @@ impl Emigui {
|
||||
region.cursor().x,
|
||||
region.cursor().y,
|
||||
));
|
||||
region.add(label!("num_batches: {}", self.stats.num_batches));
|
||||
region.add(label!("num_vertices: {}", self.stats.num_vertices));
|
||||
region.add(label!("num_triangles: {}", self.stats.num_triangles));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{Id, PaintCmd};
|
||||
use crate::{math::Rect, Id, PaintCmd};
|
||||
|
||||
// TODO: support multiple windows
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
|
||||
@@ -11,7 +11,8 @@ pub enum Layer {
|
||||
Popup,
|
||||
}
|
||||
|
||||
type PaintList = Vec<PaintCmd>;
|
||||
/// Each PaintCmd is paired with a clip rectangle.
|
||||
type PaintList = Vec<(Rect, PaintCmd)>;
|
||||
|
||||
/// TODO: improve this
|
||||
#[derive(Clone, Default)]
|
||||
@@ -30,7 +31,10 @@ impl GraphicLayers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain(&mut self, window_oreder: &[Id]) -> impl ExactSizeIterator<Item = PaintCmd> {
|
||||
pub fn drain(
|
||||
&mut self,
|
||||
window_oreder: &[Id],
|
||||
) -> impl ExactSizeIterator<Item = (Rect, PaintCmd)> {
|
||||
let mut all_commands: Vec<_> = self.bg.drain(..).collect();
|
||||
|
||||
for id in window_oreder {
|
||||
|
||||
@@ -70,6 +70,7 @@ pub enum Align {
|
||||
/// Right/Bottom
|
||||
/// Note: requires a bounded/known available_width.
|
||||
Max,
|
||||
// TODO: Justified
|
||||
}
|
||||
|
||||
impl Default for Align {
|
||||
@@ -123,11 +124,14 @@ where
|
||||
let mut graphics = ctx.graphics.lock();
|
||||
graphics.layer(layer).insert(
|
||||
where_to_put_background,
|
||||
PaintCmd::Rect {
|
||||
corner_radius: 5.0,
|
||||
fill_color: Some(style.background_fill_color()),
|
||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||
rect,
|
||||
},
|
||||
(
|
||||
Rect::everything(),
|
||||
PaintCmd::Rect {
|
||||
corner_radius: 5.0,
|
||||
fill_color: Some(style.background_fill_color()),
|
||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||
rect,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ pub use {
|
||||
layout::Align,
|
||||
math::*,
|
||||
memory::Memory,
|
||||
mesher::{Mesh, Vertex},
|
||||
mesher::{Mesh, PaintBatches, Vertex},
|
||||
region::Region,
|
||||
style::Style,
|
||||
texture_atlas::Texture,
|
||||
|
||||
@@ -4,6 +4,13 @@ pub struct Vec2 {
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
impl PartialEq for Vec2 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.x == other.x && self.y == other.y
|
||||
}
|
||||
}
|
||||
impl Eq for Vec2 {}
|
||||
|
||||
impl Vec2 {
|
||||
pub fn splat(v: impl Into<f32>) -> Vec2 {
|
||||
let v: f32 = v.into();
|
||||
@@ -153,6 +160,13 @@ pub struct Pos2 {
|
||||
// implicit w = 1
|
||||
}
|
||||
|
||||
impl PartialEq for Pos2 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.x == other.x && self.y == other.y
|
||||
}
|
||||
}
|
||||
impl Eq for Pos2 {}
|
||||
|
||||
impl Pos2 {
|
||||
pub fn dist(self: Pos2, other: Pos2) -> f32 {
|
||||
(self - other).length()
|
||||
@@ -238,13 +252,30 @@ pub fn pos2(x: f32, y: f32) -> Pos2 {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Rect {
|
||||
min: Pos2,
|
||||
max: Pos2,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Infinite rectangle that contains everything
|
||||
pub fn everything() -> Self {
|
||||
let inf = std::f32::INFINITY;
|
||||
Self {
|
||||
min: pos2(-inf, -inf),
|
||||
max: pos2(inf, inf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nothing() -> Self {
|
||||
let inf = std::f32::INFINITY;
|
||||
Self {
|
||||
min: pos2(inf, inf),
|
||||
max: pos2(-inf, -inf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_min_max(min: Pos2, max: Pos2) -> Self {
|
||||
Rect { min, max: max }
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ pub struct Vertex {
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct Mesh {
|
||||
/// Draw as triangles (i.e. the length is a multiple of three)
|
||||
@@ -24,6 +22,11 @@ pub struct Mesh {
|
||||
pub vertices: Vec<Vertex>,
|
||||
}
|
||||
|
||||
/// Grouped by clip rectangles, in pixel coordinates
|
||||
pub type PaintBatches = Vec<(Rect, Mesh)>;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl Mesh {
|
||||
pub fn append(&mut self, mesh: &Mesh) {
|
||||
let index_offset = self.vertices.len() as u32;
|
||||
@@ -373,139 +376,141 @@ pub fn paint_path(
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct Mesher {
|
||||
pub options: MesherOptions,
|
||||
|
||||
/// Where the output goes
|
||||
pub mesh: Mesh,
|
||||
}
|
||||
|
||||
impl Mesher {
|
||||
pub fn new(pixels_per_point: f32) -> Mesher {
|
||||
Mesher {
|
||||
options: MesherOptions {
|
||||
anti_alias: true,
|
||||
aa_size: 1.0 / pixels_per_point,
|
||||
},
|
||||
mesh: Default::default(),
|
||||
pub fn mesh_command(
|
||||
options: &MesherOptions,
|
||||
fonts: &Fonts,
|
||||
command: PaintCmd,
|
||||
out_mesh: &mut Mesh,
|
||||
) {
|
||||
match command {
|
||||
PaintCmd::Circle {
|
||||
center,
|
||||
fill_color,
|
||||
outline,
|
||||
radius,
|
||||
} => {
|
||||
let mut path = Path::default();
|
||||
path.add_circle(center, radius);
|
||||
if let Some(color) = fill_color {
|
||||
fill_closed_path(out_mesh, options, &path.0, color);
|
||||
}
|
||||
if let Some(outline) = outline {
|
||||
paint_path(
|
||||
out_mesh,
|
||||
options,
|
||||
Closed,
|
||||
&path.0,
|
||||
outline.color,
|
||||
outline.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, fonts: &Fonts, commands: &[PaintCmd]) {
|
||||
let mut path = Path::default();
|
||||
|
||||
for cmd in commands {
|
||||
match cmd {
|
||||
PaintCmd::Circle {
|
||||
center,
|
||||
fill_color,
|
||||
outline,
|
||||
radius,
|
||||
} => {
|
||||
path.clear();
|
||||
path.add_circle(*center, *radius);
|
||||
if let Some(color) = fill_color {
|
||||
fill_closed_path(&mut self.mesh, &self.options, &path.0, *color);
|
||||
}
|
||||
if let Some(outline) = outline {
|
||||
paint_path(
|
||||
&mut self.mesh,
|
||||
&self.options,
|
||||
Closed,
|
||||
&path.0,
|
||||
outline.color,
|
||||
outline.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
PaintCmd::Mesh(cmd_frame) => {
|
||||
self.mesh.append(cmd_frame);
|
||||
}
|
||||
PaintCmd::Line {
|
||||
points,
|
||||
color,
|
||||
width,
|
||||
} => {
|
||||
let n = points.len();
|
||||
if n >= 2 {
|
||||
path.clear();
|
||||
path.add_line(points);
|
||||
paint_path(&mut self.mesh, &self.options, Open, &path.0, *color, *width);
|
||||
}
|
||||
}
|
||||
PaintCmd::Path {
|
||||
path,
|
||||
PaintCmd::Mesh(mesh) => {
|
||||
out_mesh.append(&mesh);
|
||||
}
|
||||
PaintCmd::Line {
|
||||
points,
|
||||
color,
|
||||
width,
|
||||
} => {
|
||||
let n = points.len();
|
||||
if n >= 2 {
|
||||
let mut path = Path::default();
|
||||
path.add_line(&points);
|
||||
paint_path(out_mesh, options, Open, &path.0, color, width);
|
||||
}
|
||||
}
|
||||
PaintCmd::Path {
|
||||
path,
|
||||
closed,
|
||||
fill_color,
|
||||
outline,
|
||||
} => {
|
||||
if let Some(fill_color) = fill_color {
|
||||
debug_assert!(
|
||||
closed,
|
||||
fill_color,
|
||||
outline,
|
||||
} => {
|
||||
if let Some(fill_color) = fill_color {
|
||||
debug_assert!(
|
||||
*closed,
|
||||
"You asked to fill a path that is not closed. That makes no sense."
|
||||
);
|
||||
fill_closed_path(&mut self.mesh, &self.options, &path.0, *fill_color);
|
||||
}
|
||||
if let Some(outline) = outline {
|
||||
paint_path(
|
||||
&mut self.mesh,
|
||||
&self.options,
|
||||
Closed,
|
||||
&path.0,
|
||||
outline.color,
|
||||
outline.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
PaintCmd::Rect {
|
||||
corner_radius,
|
||||
fill_color,
|
||||
outline,
|
||||
rect,
|
||||
} => {
|
||||
path.clear();
|
||||
path.add_rounded_rectangle(rect, *corner_radius);
|
||||
if let Some(fill_color) = fill_color {
|
||||
fill_closed_path(&mut self.mesh, &self.options, &path.0, *fill_color);
|
||||
}
|
||||
if let Some(outline) = outline {
|
||||
paint_path(
|
||||
&mut self.mesh,
|
||||
&self.options,
|
||||
Closed,
|
||||
&path.0,
|
||||
outline.color,
|
||||
outline.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
PaintCmd::Text {
|
||||
color,
|
||||
pos,
|
||||
text,
|
||||
text_style,
|
||||
x_offsets,
|
||||
} => {
|
||||
let font = &fonts[*text_style];
|
||||
for (c, x_offset) in text.chars().zip(x_offsets.iter()) {
|
||||
if let Some(glyph) = font.uv_rect(c) {
|
||||
let mut top_left = Vertex {
|
||||
pos: *pos + glyph.offset + vec2(*x_offset, 0.0),
|
||||
uv: glyph.min,
|
||||
color: *color,
|
||||
};
|
||||
top_left.pos.x = font.round_to_pixel(top_left.pos.x); // Pixel-perfection.
|
||||
top_left.pos.y = font.round_to_pixel(top_left.pos.y); // Pixel-perfection.
|
||||
let bottom_right = Vertex {
|
||||
pos: top_left.pos + glyph.size,
|
||||
uv: glyph.max,
|
||||
color: *color,
|
||||
};
|
||||
self.mesh.add_rect(top_left, bottom_right);
|
||||
}
|
||||
}
|
||||
"You asked to fill a path that is not closed. That makes no sense."
|
||||
);
|
||||
fill_closed_path(out_mesh, options, &path.0, fill_color);
|
||||
}
|
||||
if let Some(outline) = outline {
|
||||
paint_path(
|
||||
out_mesh,
|
||||
options,
|
||||
Closed,
|
||||
&path.0,
|
||||
outline.color,
|
||||
outline.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
PaintCmd::Rect {
|
||||
corner_radius,
|
||||
fill_color,
|
||||
outline,
|
||||
rect,
|
||||
} => {
|
||||
let mut path = Path::default();
|
||||
path.add_rounded_rectangle(&rect, corner_radius);
|
||||
if let Some(fill_color) = fill_color {
|
||||
fill_closed_path(out_mesh, options, &path.0, fill_color);
|
||||
}
|
||||
if let Some(outline) = outline {
|
||||
paint_path(
|
||||
out_mesh,
|
||||
options,
|
||||
Closed,
|
||||
&path.0,
|
||||
outline.color,
|
||||
outline.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
PaintCmd::Text {
|
||||
color,
|
||||
pos,
|
||||
text,
|
||||
text_style,
|
||||
x_offsets,
|
||||
} => {
|
||||
let font = &fonts[text_style];
|
||||
for (c, x_offset) in text.chars().zip(x_offsets.iter()) {
|
||||
if let Some(glyph) = font.uv_rect(c) {
|
||||
let mut top_left = Vertex {
|
||||
pos: pos + glyph.offset + vec2(*x_offset, 0.0),
|
||||
uv: glyph.min,
|
||||
color: color,
|
||||
};
|
||||
top_left.pos.x = font.round_to_pixel(top_left.pos.x); // Pixel-perfection.
|
||||
top_left.pos.y = font.round_to_pixel(top_left.pos.y); // Pixel-perfection.
|
||||
let bottom_right = Vertex {
|
||||
pos: top_left.pos + glyph.size,
|
||||
uv: glyph.max,
|
||||
color: color,
|
||||
};
|
||||
out_mesh.add_rect(top_left, bottom_right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mesh_paint_commands(
|
||||
options: &MesherOptions,
|
||||
fonts: &Fonts,
|
||||
commands: Vec<(Rect, PaintCmd)>,
|
||||
) -> Vec<(Rect, Mesh)> {
|
||||
let mut batches = PaintBatches::default();
|
||||
for (clip_rect, cmd) in commands {
|
||||
// TODO: cull(clip_rect, cmd)
|
||||
|
||||
if batches.is_empty() || batches.last().unwrap().0 != clip_rect {
|
||||
batches.push((clip_rect, Mesh::default()));
|
||||
}
|
||||
|
||||
let out_mesh = &mut batches.last_mut().unwrap().1;
|
||||
mesh_command(options, fonts, cmd, out_mesh);
|
||||
}
|
||||
|
||||
batches
|
||||
}
|
||||
|
||||
@@ -61,11 +61,20 @@ impl Region {
|
||||
/// Can be used for free painting.
|
||||
/// NOTE: all coordinates are screen coordinates!
|
||||
pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) {
|
||||
self.ctx.graphics.lock().layer(self.layer).push(paint_cmd)
|
||||
self.ctx
|
||||
.graphics
|
||||
.lock()
|
||||
.layer(self.layer)
|
||||
.push((self.clip_rect(), paint_cmd))
|
||||
}
|
||||
|
||||
pub fn add_paint_cmds(&mut self, mut cmds: Vec<PaintCmd>) {
|
||||
self.ctx.graphics.lock().layer(self.layer).append(&mut cmds)
|
||||
let clip_rect = self.clip_rect();
|
||||
self.ctx
|
||||
.graphics
|
||||
.lock()
|
||||
.layer(self.layer)
|
||||
.extend(cmds.drain(..).map(|cmd| (clip_rect, cmd)));
|
||||
}
|
||||
|
||||
/// Options for this region, and any child regions we may spawn.
|
||||
@@ -85,6 +94,13 @@ impl Region {
|
||||
&*self.ctx.fonts
|
||||
}
|
||||
|
||||
/// Screen-space rectangle for clipping what we paint in this region.
|
||||
/// This is used, for instance, to avoid painting outside a window that is smaller
|
||||
/// than its contents.
|
||||
pub fn clip_rect(&self) -> Rect {
|
||||
self.rect
|
||||
}
|
||||
|
||||
pub fn available_width(&self) -> f32 {
|
||||
self.rect.max().x - self.cursor.x
|
||||
}
|
||||
|
||||
@@ -123,12 +123,15 @@ impl Window {
|
||||
let corner_radius = style.window.corner_radius;
|
||||
graphics.layer(layer).insert(
|
||||
where_to_put_background,
|
||||
PaintCmd::Rect {
|
||||
corner_radius,
|
||||
fill_color: Some(style.background_fill_color()),
|
||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||
rect: state.rect,
|
||||
},
|
||||
(
|
||||
Rect::everything(),
|
||||
PaintCmd::Rect {
|
||||
corner_radius,
|
||||
fill_color: Some(style.background_fill_color()),
|
||||
outline: Some(Outline::new(1.0, color::WHITE)),
|
||||
rect: state.rect,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
let corner_interact = if self.resizeable {
|
||||
@@ -148,12 +151,15 @@ impl Window {
|
||||
path.add_point(corner_center, vec2(-1.0, 0.0));
|
||||
graphics.layer(layer).insert(
|
||||
where_to_put_background + 1,
|
||||
PaintCmd::Path {
|
||||
path,
|
||||
closed: true,
|
||||
fill_color: style.interact_fill_color(&corner_interact),
|
||||
outline: style.interact_outline(&corner_interact),
|
||||
},
|
||||
(
|
||||
Rect::everything(),
|
||||
PaintCmd::Path {
|
||||
path,
|
||||
closed: true,
|
||||
fill_color: style.interact_fill_color(&corner_interact),
|
||||
outline: style.interact_outline(&corner_interact),
|
||||
},
|
||||
),
|
||||
);
|
||||
corner_interact
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user