diff --git a/egui/src/app.rs b/egui/src/app.rs index b5dbd75c..3982fd27 100644 --- a/egui/src/app.rs +++ b/egui/src/app.rs @@ -13,10 +13,11 @@ pub trait App { /// The name of your App. fn name(&self) -> &str; - /// Background color for the app. - /// e.g. what is sent to `gl.clearColor` + /// Background color for the app, e.g. what is sent to `gl.clearColor`. + /// This is the background of your windows if you don't set a central panel. fn clear_color(&self) -> crate::Rgba { - crate::Srgba::from_rgb(16, 16, 16).into() + // NOTE: a bright gray makes the shadows of the windows look weird. + crate::Srgba::from_rgb(12, 12, 12).into() } /// Called once on start. Allows you to restore state. diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index c8f7db45..3dffabc4 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -160,9 +160,21 @@ impl Prepared { pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui { let max_rect = Rect::from_min_size(self.state.pos, Vec2::infinity()); - let clip_rect = max_rect + let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky + let mut clip_rect = max_rect .expand(ctx.style().visuals.clip_rect_margin) + .expand(shadow_radius) .intersect(ctx.input().screen_rect); + + // Windows are constrained to central area, + // (except in rare cases where they don't fit). + // Adjust clip rect so we don't cast shadows on side panels: + let central_area = ctx.available_rect(); + let is_within_central_area = central_area.contains(self.state.pos); + if is_within_central_area { + clip_rect = clip_rect.intersect(central_area); + } + Ui::new( ctx.clone(), self.layer_id, diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 5fe551a8..1092a883 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -3,22 +3,38 @@ use crate::{layers::PaintCmdIdx, paint::*, *}; /// Adds a rectangular frame and background to some [`Ui`]. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Frame { // On each side pub margin: Vec2, pub corner_radius: f32, + pub shadow: Shadow, pub fill: Srgba, pub stroke: Stroke, } impl Frame { pub fn none() -> Self { + Self::default() + } + + pub(crate) fn panel(style: &Style) -> Self { Self { - margin: Vec2::zero(), + margin: Vec2::new(8.0, 2.0), corner_radius: 0.0, - fill: Default::default(), - stroke: Stroke::none(), + fill: style.visuals.widgets.noninteractive.bg_fill, + stroke: style.visuals.widgets.noninteractive.bg_stroke, + ..Default::default() + } + } + + pub fn central_panel(style: &Style) -> Self { + Self { + margin: Vec2::new(8.0, 8.0), + corner_radius: 0.0, + fill: style.visuals.widgets.noninteractive.bg_fill, + stroke: Default::default(), + ..Default::default() } } @@ -26,11 +42,32 @@ impl Frame { Self { margin: style.spacing.window_padding, corner_radius: style.visuals.window_corner_radius, + shadow: style.visuals.window_shadow, fill: style.visuals.widgets.noninteractive.bg_fill, stroke: style.visuals.widgets.inactive.bg_stroke, // because we can resize windows } } + pub fn menu(style: &Style) -> Self { + Self { + margin: Vec2::splat(1.0), + corner_radius: 2.0, + shadow: Shadow::small(), + fill: style.visuals.widgets.noninteractive.bg_fill, + stroke: style.visuals.widgets.noninteractive.bg_stroke, + } + } + + pub fn popup(style: &Style) -> Self { + Self { + margin: style.spacing.window_padding, + corner_radius: 5.0, + shadow: Shadow::small(), + fill: style.visuals.widgets.noninteractive.bg_fill, + stroke: style.visuals.widgets.noninteractive.bg_stroke, + } + } + /// dark canvas to draw on pub fn dark_canvas(style: &Style) -> Self { Self { @@ -38,46 +75,12 @@ impl Frame { corner_radius: 5.0, fill: Srgba::black_alpha(250), stroke: style.visuals.widgets.noninteractive.bg_stroke, + ..Default::default() } } +} - /// Suitable for a fullscreen app - pub fn background(style: &Style) -> Self { - Self { - margin: Vec2::new(8.0, 8.0), - corner_radius: 0.0, - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: Default::default(), - } - } - - pub(crate) fn panel(style: &Style) -> Self { - Self { - margin: Vec2::new(8.0, 2.0), - corner_radius: 0.0, - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, - } - } - - pub fn menu(style: &Style) -> Self { - Self { - margin: Vec2::splat(1.0), - corner_radius: 2.0, - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, - } - } - - pub fn popup(style: &Style) -> Self { - Self { - margin: style.spacing.window_padding, - corner_radius: 5.0, - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, - } - } - +impl Frame { pub fn fill(mut self, fill: Srgba) -> Self { self.fill = fill; self @@ -138,15 +141,23 @@ impl Prepared { .. } = self; - ui.painter().set( - where_to_put_background, - PaintCmd::Rect { - corner_radius: frame.corner_radius, - fill: frame.fill, - stroke: frame.stroke, - rect: outer_rect, - }, - ); + let frame_cmd = PaintCmd::Rect { + rect: outer_rect, + corner_radius: frame.corner_radius, + fill: frame.fill, + stroke: frame.stroke, + }; + + if frame.shadow == Default::default() { + ui.painter().set(where_to_put_background, frame_cmd); + } else { + let shadow = frame.shadow.tessellate(outer_rect, frame.corner_radius); + let shadow = PaintCmd::Triangles(shadow); + ui.painter().set( + where_to_put_background, + PaintCmd::Vec(vec![shadow, frame_cmd]), + ) + }; ui.advance_cursor_after_rect(outer_rect); diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs index 20d30822..dcc02895 100644 --- a/egui/src/containers/panel.rs +++ b/egui/src/containers/panel.rs @@ -151,7 +151,7 @@ impl CentralPanel { let clip_rect = ctx.input().screen_rect(); let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_rect); - let frame = Frame::background(&ctx.style()); + let frame = Frame::central_panel(&ctx.style()); let r = frame.show(&mut panel_ui, |ui| { let r = add_contents(ui); ui.expand_to_include_rect(ui.max_rect()); // Use it all diff --git a/egui/src/paint/command.rs b/egui/src/paint/command.rs index 1ef1dcff..0b1f6e54 100644 --- a/egui/src/paint/command.rs +++ b/egui/src/paint/command.rs @@ -8,10 +8,14 @@ use { }; /// A paint primitive such as a circle or a piece of text. +/// Coordinates are all screen space points (not physical pixels). #[derive(Clone, Debug)] pub enum PaintCmd { /// Paint nothing. This can be useful as a placeholder. Noop, + /// Recursively nest more paint commands - sometimes a convenience to be able to do. + /// For performance reasons it is better to avoid it. + Vec(Vec), Circle { center: Pos2, radius: f32, @@ -159,6 +163,11 @@ impl PaintCmd { pub fn translate(&mut self, delta: Vec2) { match self { PaintCmd::Noop => {} + PaintCmd::Vec(commands) => { + for command in commands { + command.translate(delta); + } + } PaintCmd::Circle { center, .. } => { *center += delta; } diff --git a/egui/src/paint/mod.rs b/egui/src/paint/mod.rs index 0b1c94fe..ee382c6d 100644 --- a/egui/src/paint/mod.rs +++ b/egui/src/paint/mod.rs @@ -5,6 +5,7 @@ pub mod command; pub mod font; pub mod fonts; mod galley; +mod shadow; pub mod stats; pub mod tessellator; mod texture_atlas; @@ -14,9 +15,18 @@ pub use { command::{PaintCmd, Stroke}, fonts::{FontDefinitions, FontFamily, Fonts, TextStyle}, galley::*, + shadow::Shadow, stats::PaintStats, tessellator::{ PaintJob, PaintJobs, TessellationOptions, TextureId, Triangles, Vertex, WHITE_UV, }, texture_atlas::{Texture, TextureAtlas}, }; + +pub(crate) struct PaintRect { + pub rect: crate::Rect, + /// How rounded the corners are. Use `0.0` for no rounding. + pub corner_radius: f32, + pub fill: Srgba, + pub stroke: Stroke, +} diff --git a/egui/src/paint/shadow.rs b/egui/src/paint/shadow.rs new file mode 100644 index 00000000..54b7ee99 --- /dev/null +++ b/egui/src/paint/shadow.rs @@ -0,0 +1,49 @@ +use super::*; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Shadow { + // The shadow extends this much outside the rect. + pub extrusion: f32, + pub color: Srgba, +} + +impl Shadow { + /// Tooltips, menus, ... + pub fn small() -> Self { + Self { + extrusion: 8.0, + color: Srgba::black_alpha(64), + } + } + + /// Windows + pub fn big() -> Self { + Self { + extrusion: 32.0, + color: Srgba::black_alpha(96), + } + } + + pub fn tessellate(&self, rect: crate::Rect, corner_radius: f32) -> Triangles { + // tessellator.clip_rect = clip_rect; // TODO: culling + + let Self { extrusion, color } = *self; + + use crate::paint::tessellator::*; + let rect = PaintRect { + rect: rect.expand(0.5 * extrusion), + corner_radius: corner_radius + 0.5 * extrusion, + fill: color, + stroke: Default::default(), + }; + let mut tessellator = Tessellator::from_options(TessellationOptions { + aa_size: extrusion, + anti_alias: true, + ..Default::default() + }); + let mut triangles = Triangles::default(); + tessellator.tessellate_rect(&rect, &mut triangles); + triangles + } +} diff --git a/egui/src/paint/stats.rs b/egui/src/paint/stats.rs index 0b888a99..0893d460 100644 --- a/egui/src/paint/stats.rs +++ b/egui/src/paint/stats.rs @@ -54,17 +54,18 @@ impl std::ops::AddAssign for AllocInfo { } impl AllocInfo { - pub fn from_paint_cmd(cmd: &PaintCmd) -> Self { - match cmd { - PaintCmd::Noop - | PaintCmd::Circle { .. } - | PaintCmd::LineSegment { .. } - | PaintCmd::Rect { .. } => Self::default(), - PaintCmd::Path { points, .. } => Self::from_slice(points), - PaintCmd::Text { galley, .. } => Self::from_galley(galley), - PaintCmd::Triangles(triangles) => Self::from_triangles(triangles), - } - } + // pub fn from_paint_cmd(cmd: &PaintCmd) -> Self { + // match cmd { + // PaintCmd::Noop + // PaintCmd::Vec(commands) => Self::from_paint_commands(commands) + // | PaintCmd::Circle { .. } + // | PaintCmd::LineSegment { .. } + // | PaintCmd::Rect { .. } => Self::default(), + // PaintCmd::Path { points, .. } => Self::from_slice(points), + // PaintCmd::Text { galley, .. } => Self::from_galley(galley), + // PaintCmd::Triangles(triangles) => Self::from_triangles(triangles), + // } + // } pub fn from_galley(galley: &Galley) -> Self { Self::from_slice(galley.text.as_bytes()) + Self::from_slice(&galley.rows) @@ -136,6 +137,7 @@ pub struct PaintStats { cmd_text: AllocInfo, cmd_path: AllocInfo, cmd_mesh: AllocInfo, + cmd_vec: AllocInfo, /// Number of separate clip rectangles jobs: AllocInfo, @@ -147,28 +149,41 @@ impl PaintStats { pub fn from_paint_commands(paint_commands: &[(Rect, PaintCmd)]) -> Self { let mut stats = Self::default(); stats.cmd_path.element_size = ElementSize::Heterogenous; // nicer display later + stats.cmd_vec.element_size = ElementSize::Heterogenous; // nicer display later stats.primitives = AllocInfo::from_slice(paint_commands); for (_, cmd) in paint_commands { - match cmd { - PaintCmd::Noop - | PaintCmd::Circle { .. } - | PaintCmd::LineSegment { .. } - | PaintCmd::Rect { .. } => Default::default(), - PaintCmd::Path { points, .. } => { - stats.cmd_path += AllocInfo::from_slice(points); - } - PaintCmd::Text { galley, .. } => { - stats.cmd_text += AllocInfo::from_galley(galley); - } - PaintCmd::Triangles(triangles) => { - stats.cmd_mesh += AllocInfo::from_triangles(triangles); - } - } + stats.add(cmd); } stats } + fn add(&mut self, cmd: &PaintCmd) { + match cmd { + PaintCmd::Vec(paint_commands) => { + // self += PaintStats::from_paint_commands(&paint_commands); // TODO + self.primitives += AllocInfo::from_slice(paint_commands); + self.cmd_vec += AllocInfo::from_slice(paint_commands); + for cmd in paint_commands { + self.add(cmd); + } + } + PaintCmd::Noop + | PaintCmd::Circle { .. } + | PaintCmd::LineSegment { .. } + | PaintCmd::Rect { .. } => Default::default(), + PaintCmd::Path { points, .. } => { + self.cmd_path += AllocInfo::from_slice(points); + } + PaintCmd::Text { galley, .. } => { + self.cmd_text += AllocInfo::from_galley(galley); + } + PaintCmd::Triangles(triangles) => { + self.cmd_mesh += AllocInfo::from_triangles(triangles); + } + } + } + pub fn with_paint_jobs(mut self, paint_jobs: &[crate::paint::PaintJob]) -> Self { self.jobs += AllocInfo::from_slice(paint_jobs); for (_, indices) in paint_jobs { @@ -198,19 +213,32 @@ impl PaintStats { ui.advance_cursor(10.0); ui.style_mut().body_text_style = TextStyle::Monospace; + + let Self { + primitives, + cmd_text, + cmd_path, + cmd_mesh, + cmd_vec, + jobs, + vertices, + indices, + } = self; + ui.label("Intermediate:"); - ui.label(self.primitives.format("primitives")) + ui.label(primitives.format("primitives")) .on_hover_text("Boxes, circles, etc"); - ui.label(self.cmd_text.format("text")); - ui.label(self.cmd_path.format("paths")); - ui.label(self.cmd_mesh.format("meshes")); + ui.label(cmd_text.format("text")); + ui.label(cmd_path.format("paths")); + ui.label(cmd_mesh.format("meshes")); + ui.label(cmd_vec.format("nested")); ui.advance_cursor(10.0); ui.label("Tessellated:"); - ui.label(self.jobs.format("jobs")) + ui.label(jobs.format("jobs")) .on_hover_text("Number of separate clip rectangles"); - ui.label(self.vertices.format("vertices")); - ui.label(self.indices.format("indices")) + ui.label(vertices.format("vertices")); + ui.label(indices.format("indices")) .on_hover_text("Three 32-bit indices per triangles"); ui.advance_cursor(10.0); diff --git a/egui/src/paint/tessellator.rs b/egui/src/paint/tessellator.rs index 25ea04b2..b6833ba5 100644 --- a/egui/src/paint/tessellator.rs +++ b/egui/src/paint/tessellator.rs @@ -8,8 +8,7 @@ use { super::{ color::{self, srgba, Rgba, Srgba, TRANSPARENT}, - fonts::Fonts, - PaintCmd, Stroke, + *, }, crate::math::*, std::f32::consts::TAU, @@ -665,184 +664,226 @@ fn mul_color(color: Srgba, factor: f32) -> Srgba { // ---------------------------------------------------------------------------- -/// Tessellate a single [`PaintCmd`] into a [`Triangles`]. -/// -/// * `command`: the command to tessellate -/// * `options`: tessellation quality -/// * `fonts`: font source when tessellating text -/// * `out`: where the triangles are put -/// * `scratchpad_path`: if you plan to run `tessellate_paint_command` -/// many times, pass it a reference to the same `Path` to avoid excessive allocations. -fn tessellate_paint_command( - options: TessellationOptions, - fonts: &Fonts, - clip_rect: Rect, - command: PaintCmd, - out: &mut Triangles, - scratchpad_points: &mut Vec, - scratchpad_path: &mut Path, -) { - let path = scratchpad_path; - path.clear(); +pub struct Tessellator { + pub options: TessellationOptions, + /// Only used for culling + pub clip_rect: Rect, + scratchpad_points: Vec, + scratchpad_path: Path, +} - match command { - PaintCmd::Noop => {} - PaintCmd::Circle { - center, - radius, - fill, - stroke, - } => { - if radius <= 0.0 { - return; - } +impl Tessellator { + pub fn from_options(options: TessellationOptions) -> Self { + Self { + options, + clip_rect: Rect::everything(), + scratchpad_points: Default::default(), + scratchpad_path: Default::default(), + } + } - if options.coarse_tessellation_culling - && !clip_rect.expand(radius + stroke.width).contains(center) - { - return; - } + /// Tessellate a single [`PaintCmd`] into a [`Triangles`]. + /// + /// * `command`: the command to tessellate + /// * `options`: tessellation quality + /// * `fonts`: font source when tessellating text + /// * `out`: where the triangles are put + /// * `scratchpad_path`: if you plan to run `tessellate_paint_command` + /// many times, pass it a reference to the same `Path` to avoid excessive allocations. + pub fn tessellate_paint_command( + &mut self, + fonts: &Fonts, + command: PaintCmd, + out: &mut Triangles, + ) { + let clip_rect = self.clip_rect; + let options = self.options; - path.add_circle(center, radius); - fill_closed_path(&path.0, fill, options, out); - stroke_path(&path.0, Closed, stroke, options, out); - } - PaintCmd::Triangles(triangles) => { - if triangles.is_valid() { - out.append(triangles); - } else { - debug_assert!(false, "Invalid Triangles in PaintCmd::Triangles"); + match command { + PaintCmd::Noop => {} + PaintCmd::Vec(vec) => { + for command in vec { + self.tessellate_paint_command(fonts, command, out) + } } - } - PaintCmd::LineSegment { points, stroke } => { - path.add_line_segment(points); - stroke_path(&path.0, Open, stroke, options, out); - } - PaintCmd::Path { - points, - closed, - fill, - stroke, - } => { - if points.len() >= 2 { - if closed { - path.add_line_loop(&points); + PaintCmd::Circle { + center, + radius, + fill, + stroke, + } => { + if radius <= 0.0 { + return; + } + + if options.coarse_tessellation_culling + && !clip_rect.expand(radius + stroke.width).contains(center) + { + return; + } + + let path = &mut self.scratchpad_path; + path.clear(); + path.add_circle(center, radius); + fill_closed_path(&path.0, fill, options, out); + stroke_path(&path.0, Closed, stroke, options, out); + } + PaintCmd::Triangles(triangles) => { + if triangles.is_valid() { + out.append(triangles); } else { - path.add_open_points(&points); + debug_assert!(false, "Invalid Triangles in PaintCmd::Triangles"); } + } + PaintCmd::LineSegment { points, stroke } => { + let path = &mut self.scratchpad_path; + path.clear(); + path.add_line_segment(points); + stroke_path(&path.0, Open, stroke, options, out); + } + PaintCmd::Path { + points, + closed, + fill, + stroke, + } => { + if points.len() >= 2 { + let path = &mut self.scratchpad_path; + path.clear(); + if closed { + path.add_line_loop(&points); + } else { + path.add_open_points(&points); + } - if fill != TRANSPARENT { - debug_assert!( - closed, - "You asked to fill a path that is not closed. That makes no sense." - ); - fill_closed_path(&path.0, fill, options, out); + if fill != TRANSPARENT { + debug_assert!( + closed, + "You asked to fill a path that is not closed. That makes no sense." + ); + fill_closed_path(&path.0, fill, options, out); + } + let typ = if closed { Closed } else { Open }; + stroke_path(&path.0, typ, stroke, options, out); } - let typ = if closed { Closed } else { Open }; - stroke_path(&path.0, typ, stroke, options, out); + } + PaintCmd::Rect { + rect, + corner_radius, + fill, + stroke, + } => { + let rect = PaintRect { + rect, + corner_radius, + fill, + stroke, + }; + self.tessellate_rect(&rect, out); + } + PaintCmd::Text { + pos, + galley, + text_style, + color, + } => { + self.tessellate_text(fonts, pos, &galley, text_style, color, out); } } - PaintCmd::Rect { + } + + pub(crate) fn tessellate_rect(&mut self, rect: &PaintRect, out: &mut Triangles) { + let PaintRect { mut rect, corner_radius, fill, stroke, - } => { - if rect.is_empty() { - return; - } + } = *rect; - if options.coarse_tessellation_culling - && !rect.expand(stroke.width).intersects(clip_rect) - { - return; - } - - // It is common to (sometimes accidentally) create an infinitely sized rectangle. - // Make sure we can handle that: - rect.min = rect.min.at_least(pos2(-1e7, -1e7)); - rect.max = rect.max.at_most(pos2(1e7, 1e7)); - - path::rounded_rectangle(scratchpad_points, rect, corner_radius); - path.add_line_loop(scratchpad_points); - fill_closed_path(&path.0, fill, options, out); - stroke_path(&path.0, Closed, stroke, options, out); + if self.options.coarse_tessellation_culling + && !rect.expand(stroke.width).intersects(self.clip_rect) + { + return; } - PaintCmd::Text { - pos, - galley, - text_style, - color, - } => { - tessellate_text( - options, fonts, clip_rect, pos, &galley, text_style, color, out, - ); + if rect.is_empty() { + return; } + + // It is common to (sometimes accidentally) create an infinitely sized rectangle. + // Make sure we can handle that: + rect.min = rect.min.at_least(pos2(-1e7, -1e7)); + rect.max = rect.max.at_most(pos2(1e7, 1e7)); + + let path = &mut self.scratchpad_path; + path.clear(); + path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius); + path.add_line_loop(&self.scratchpad_points); + fill_closed_path(&path.0, fill, self.options, out); + stroke_path(&path.0, Closed, stroke, self.options, out); } -} -#[allow(clippy::too_many_arguments)] -fn tessellate_text( - options: TessellationOptions, - fonts: &Fonts, - clip_rect: Rect, - pos: Pos2, - galley: &super::Galley, - text_style: super::TextStyle, - color: Srgba, - out: &mut Triangles, -) { - if color == TRANSPARENT { - return; - } - galley.sanity_check(); + pub fn tessellate_text( + &mut self, + fonts: &Fonts, + pos: Pos2, + galley: &super::Galley, + text_style: super::TextStyle, + color: Srgba, + out: &mut Triangles, + ) { + if color == TRANSPARENT { + return; + } + galley.sanity_check(); - let num_chars = galley.text.chars().count(); - out.reserve_triangles(num_chars * 2); - out.reserve_vertices(num_chars * 4); + let num_chars = galley.text.chars().count(); + out.reserve_triangles(num_chars * 2); + out.reserve_vertices(num_chars * 4); - let tex_w = fonts.texture().width as f32; - let tex_h = fonts.texture().height as f32; + let tex_w = fonts.texture().width as f32; + let tex_h = fonts.texture().height as f32; - let text_offset = vec2(0.0, 1.0); // Eye-balled for buttons. TODO: why is this needed? + let text_offset = vec2(0.0, 1.0); // Eye-balled for buttons. TODO: why is this needed? - let clip_rect = clip_rect.expand(2.0); // Some fudge to handle letters that are slightly larger than expected. + let clip_rect = self.clip_rect.expand(2.0); // Some fudge to handle letters that are slightly larger than expected. - let font = &fonts[text_style]; - let mut chars = galley.text.chars(); - for line in &galley.rows { - let line_min_y = pos.y + line.y_min + text_offset.x; - let line_max_y = line_min_y + font.row_height(); - let is_line_visible = line_max_y >= clip_rect.min.y && line_min_y <= clip_rect.max.y; + let font = &fonts[text_style]; + let mut chars = galley.text.chars(); + for line in &galley.rows { + let line_min_y = pos.y + line.y_min + text_offset.x; + let line_max_y = line_min_y + font.row_height(); + let is_line_visible = line_max_y >= clip_rect.min.y && line_min_y <= clip_rect.max.y; - for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) { - let c = chars.next().unwrap(); + for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) { + let c = chars.next().unwrap(); - if options.coarse_tessellation_culling && !is_line_visible { - // culling individual lines of text is important, since a single `PaintCmd::Text` - // can span hundreds of lines. - continue; + if self.options.coarse_tessellation_culling && !is_line_visible { + // culling individual lines of text is important, since a single `PaintCmd::Text` + // can span hundreds of lines. + continue; + } + + if let Some(glyph) = font.uv_rect(c) { + let mut left_top = + pos + glyph.offset + vec2(*x_offset, line.y_min) + text_offset; + left_top.x = font.round_to_pixel(left_top.x); // Pixel-perfection. + left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection. + + let pos = Rect::from_min_max(left_top, left_top + glyph.size); + let uv = Rect::from_min_max( + pos2(glyph.min.0 as f32 / tex_w, glyph.min.1 as f32 / tex_h), + pos2(glyph.max.0 as f32 / tex_w, glyph.max.1 as f32 / tex_h), + ); + out.add_rect_with_uv(pos, uv, color); + } } - - if let Some(glyph) = font.uv_rect(c) { - let mut left_top = pos + glyph.offset + vec2(*x_offset, line.y_min) + text_offset; - left_top.x = font.round_to_pixel(left_top.x); // Pixel-perfection. - left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection. - - let pos = Rect::from_min_max(left_top, left_top + glyph.size); - let uv = Rect::from_min_max( - pos2(glyph.min.0 as f32 / tex_w, glyph.min.1 as f32 / tex_h), - pos2(glyph.max.0 as f32 / tex_w, glyph.max.1 as f32 / tex_h), - ); - out.add_rect_with_uv(pos, uv, color); + if line.ends_with_newline { + let newline = chars.next().unwrap(); + debug_assert_eq!(newline, '\n'); } } - if line.ends_with_newline { - let newline = chars.next().unwrap(); - debug_assert_eq!(newline, '\n'); - } + assert_eq!(chars.next(), None); } - assert_eq!(chars.next(), None); } /// Turns [`PaintCmd`]:s into sets of triangles. @@ -861,8 +902,7 @@ pub fn tessellate_paint_commands( options: TessellationOptions, fonts: &Fonts, ) -> Vec<(Rect, Triangles)> { - let mut scratchpad_points = Vec::new(); - let mut scratchpad_path = Path::default(); + let mut tessellator = Tessellator::from_options(options); let mut jobs = PaintJobs::default(); for (clip_rect, cmd) in commands { @@ -876,23 +916,15 @@ pub fn tessellate_paint_commands( } let out = &mut jobs.last_mut().unwrap().1; - tessellate_paint_command( - options, - fonts, - clip_rect, - cmd, - out, - &mut scratchpad_points, - &mut scratchpad_path, - ); + tessellator.clip_rect = clip_rect; + tessellator.tessellate_paint_command(fonts, cmd, out); } if options.debug_paint_clip_rects { for (clip_rect, triangles) in &mut jobs { - tessellate_paint_command( - options, + tessellator.clip_rect = Rect::everything(); + tessellator.tessellate_paint_command( fonts, - Rect::everything(), PaintCmd::Rect { rect: *clip_rect, corner_radius: 0.0, @@ -900,8 +932,6 @@ pub fn tessellate_paint_commands( stroke: Stroke::new(2.0, srgba(150, 255, 150, 255)), }, triangles, - &mut scratchpad_points, - &mut scratchpad_path, ) } } diff --git a/egui/src/style.rs b/egui/src/style.rs index cd715a7d..fa0a6a97 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -3,7 +3,7 @@ use crate::{ color::*, math::*, - paint::{Stroke, TextStyle}, + paint::{Shadow, Stroke, TextStyle}, types::*, }; @@ -133,6 +133,7 @@ pub struct Visuals { pub hyperlink_color: Srgba, pub window_corner_radius: f32, + pub window_shadow: Shadow, pub resize_corner_size: f32, @@ -275,6 +276,7 @@ impl Default for Visuals { dark_bg_color: Srgba::black_alpha(140), hyperlink_color: Srgba::from_rgb(90, 170, 255), window_corner_radius: 10.0, + window_shadow: Shadow::big(), resize_corner_size: 12.0, text_cursor_width: 2.0, clip_rect_margin: 1.0, // should be half the size of the widest frame stroke @@ -479,6 +481,7 @@ impl Visuals { dark_bg_color, hyperlink_color, window_corner_radius, + window_shadow, resize_corner_size, text_cursor_width, clip_rect_margin, @@ -492,6 +495,7 @@ impl Visuals { ui_color(ui, dark_bg_color, "dark_bg_color"); ui_color(ui, hyperlink_color, "hyperlink_color"); ui.add(Slider::f32(window_corner_radius, 0.0..=20.0).text("window_corner_radius")); + window_shadow.ui(ui, "Window shadow:"); ui.add(Slider::f32(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); ui.add(Slider::f32(text_cursor_width, 0.0..=2.0).text("text_cursor_width")); ui.add(Slider::f32(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); @@ -527,6 +531,18 @@ impl Stroke { } } +impl Shadow { + pub fn ui(&mut self, ui: &mut crate::Ui, text: &str) { + let Self { extrusion, color } = self; + ui.horizontal(|ui| { + ui.label(text); + ui.add(DragValue::f32(extrusion).speed(1.0).range(0.0..=100.0)) + .on_hover_text("Extrusion"); + ui.color_edit_button_srgba(color); + }); + } +} + // TODO: improve and standardize ui_slider_vec2 fn ui_slider_vec2( ui: &mut Ui,