Add shadows under windows
This commit is contained in:
parent
d38b16f1ea
commit
6dd15dd1a3
10 changed files with 421 additions and 255 deletions
|
@ -13,10 +13,11 @@ pub trait App {
|
||||||
/// The name of your App.
|
/// The name of your App.
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
/// Background color for the app.
|
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
|
||||||
/// 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 {
|
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.
|
/// Called once on start. Allows you to restore state.
|
||||||
|
|
|
@ -160,9 +160,21 @@ impl Prepared {
|
||||||
|
|
||||||
pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui {
|
pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui {
|
||||||
let max_rect = Rect::from_min_size(self.state.pos, Vec2::infinity());
|
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(ctx.style().visuals.clip_rect_margin)
|
||||||
|
.expand(shadow_radius)
|
||||||
.intersect(ctx.input().screen_rect);
|
.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(
|
Ui::new(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
self.layer_id,
|
self.layer_id,
|
||||||
|
|
|
@ -3,22 +3,38 @@
|
||||||
use crate::{layers::PaintCmdIdx, paint::*, *};
|
use crate::{layers::PaintCmdIdx, paint::*, *};
|
||||||
|
|
||||||
/// Adds a rectangular frame and background to some [`Ui`].
|
/// Adds a rectangular frame and background to some [`Ui`].
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
// On each side
|
// On each side
|
||||||
pub margin: Vec2,
|
pub margin: Vec2,
|
||||||
pub corner_radius: f32,
|
pub corner_radius: f32,
|
||||||
|
pub shadow: Shadow,
|
||||||
pub fill: Srgba,
|
pub fill: Srgba,
|
||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn panel(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Vec2::zero(),
|
margin: Vec2::new(8.0, 2.0),
|
||||||
corner_radius: 0.0,
|
corner_radius: 0.0,
|
||||||
fill: Default::default(),
|
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||||
stroke: Stroke::none(),
|
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 {
|
Self {
|
||||||
margin: style.spacing.window_padding,
|
margin: style.spacing.window_padding,
|
||||||
corner_radius: style.visuals.window_corner_radius,
|
corner_radius: style.visuals.window_corner_radius,
|
||||||
|
shadow: style.visuals.window_shadow,
|
||||||
fill: style.visuals.widgets.noninteractive.bg_fill,
|
fill: style.visuals.widgets.noninteractive.bg_fill,
|
||||||
stroke: style.visuals.widgets.inactive.bg_stroke, // because we can resize windows
|
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
|
/// dark canvas to draw on
|
||||||
pub fn dark_canvas(style: &Style) -> Self {
|
pub fn dark_canvas(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -38,46 +75,12 @@ impl Frame {
|
||||||
corner_radius: 5.0,
|
corner_radius: 5.0,
|
||||||
fill: Srgba::black_alpha(250),
|
fill: Srgba::black_alpha(250),
|
||||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Suitable for a fullscreen app
|
impl Frame {
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fill(mut self, fill: Srgba) -> Self {
|
pub fn fill(mut self, fill: Srgba) -> Self {
|
||||||
self.fill = fill;
|
self.fill = fill;
|
||||||
self
|
self
|
||||||
|
@ -138,15 +141,23 @@ impl Prepared {
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.painter().set(
|
let frame_cmd = PaintCmd::Rect {
|
||||||
where_to_put_background,
|
rect: outer_rect,
|
||||||
PaintCmd::Rect {
|
|
||||||
corner_radius: frame.corner_radius,
|
corner_radius: frame.corner_radius,
|
||||||
fill: frame.fill,
|
fill: frame.fill,
|
||||||
stroke: frame.stroke,
|
stroke: frame.stroke,
|
||||||
rect: outer_rect,
|
};
|
||||||
},
|
|
||||||
);
|
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);
|
ui.advance_cursor_after_rect(outer_rect);
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ impl CentralPanel {
|
||||||
let clip_rect = ctx.input().screen_rect();
|
let clip_rect = ctx.input().screen_rect();
|
||||||
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_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 = frame.show(&mut panel_ui, |ui| {
|
||||||
let r = add_contents(ui);
|
let r = add_contents(ui);
|
||||||
ui.expand_to_include_rect(ui.max_rect()); // Use it all
|
ui.expand_to_include_rect(ui.max_rect()); // Use it all
|
||||||
|
|
|
@ -8,10 +8,14 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A paint primitive such as a circle or a piece of text.
|
/// A paint primitive such as a circle or a piece of text.
|
||||||
|
/// Coordinates are all screen space points (not physical pixels).
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PaintCmd {
|
pub enum PaintCmd {
|
||||||
/// Paint nothing. This can be useful as a placeholder.
|
/// Paint nothing. This can be useful as a placeholder.
|
||||||
Noop,
|
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<PaintCmd>),
|
||||||
Circle {
|
Circle {
|
||||||
center: Pos2,
|
center: Pos2,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
|
@ -159,6 +163,11 @@ impl PaintCmd {
|
||||||
pub fn translate(&mut self, delta: Vec2) {
|
pub fn translate(&mut self, delta: Vec2) {
|
||||||
match self {
|
match self {
|
||||||
PaintCmd::Noop => {}
|
PaintCmd::Noop => {}
|
||||||
|
PaintCmd::Vec(commands) => {
|
||||||
|
for command in commands {
|
||||||
|
command.translate(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
PaintCmd::Circle { center, .. } => {
|
PaintCmd::Circle { center, .. } => {
|
||||||
*center += delta;
|
*center += delta;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod command;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod fonts;
|
pub mod fonts;
|
||||||
mod galley;
|
mod galley;
|
||||||
|
mod shadow;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod tessellator;
|
pub mod tessellator;
|
||||||
mod texture_atlas;
|
mod texture_atlas;
|
||||||
|
@ -14,9 +15,18 @@ pub use {
|
||||||
command::{PaintCmd, Stroke},
|
command::{PaintCmd, Stroke},
|
||||||
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
|
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
|
||||||
galley::*,
|
galley::*,
|
||||||
|
shadow::Shadow,
|
||||||
stats::PaintStats,
|
stats::PaintStats,
|
||||||
tessellator::{
|
tessellator::{
|
||||||
PaintJob, PaintJobs, TessellationOptions, TextureId, Triangles, Vertex, WHITE_UV,
|
PaintJob, PaintJobs, TessellationOptions, TextureId, Triangles, Vertex, WHITE_UV,
|
||||||
},
|
},
|
||||||
texture_atlas::{Texture, TextureAtlas},
|
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,
|
||||||
|
}
|
||||||
|
|
49
egui/src/paint/shadow.rs
Normal file
49
egui/src/paint/shadow.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,17 +54,18 @@ impl std::ops::AddAssign for AllocInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AllocInfo {
|
impl AllocInfo {
|
||||||
pub fn from_paint_cmd(cmd: &PaintCmd) -> Self {
|
// pub fn from_paint_cmd(cmd: &PaintCmd) -> Self {
|
||||||
match cmd {
|
// match cmd {
|
||||||
PaintCmd::Noop
|
// PaintCmd::Noop
|
||||||
| PaintCmd::Circle { .. }
|
// PaintCmd::Vec(commands) => Self::from_paint_commands(commands)
|
||||||
| PaintCmd::LineSegment { .. }
|
// | PaintCmd::Circle { .. }
|
||||||
| PaintCmd::Rect { .. } => Self::default(),
|
// | PaintCmd::LineSegment { .. }
|
||||||
PaintCmd::Path { points, .. } => Self::from_slice(points),
|
// | PaintCmd::Rect { .. } => Self::default(),
|
||||||
PaintCmd::Text { galley, .. } => Self::from_galley(galley),
|
// PaintCmd::Path { points, .. } => Self::from_slice(points),
|
||||||
PaintCmd::Triangles(triangles) => Self::from_triangles(triangles),
|
// PaintCmd::Text { galley, .. } => Self::from_galley(galley),
|
||||||
}
|
// PaintCmd::Triangles(triangles) => Self::from_triangles(triangles),
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn from_galley(galley: &Galley) -> Self {
|
pub fn from_galley(galley: &Galley) -> Self {
|
||||||
Self::from_slice(galley.text.as_bytes()) + Self::from_slice(&galley.rows)
|
Self::from_slice(galley.text.as_bytes()) + Self::from_slice(&galley.rows)
|
||||||
|
@ -136,6 +137,7 @@ pub struct PaintStats {
|
||||||
cmd_text: AllocInfo,
|
cmd_text: AllocInfo,
|
||||||
cmd_path: AllocInfo,
|
cmd_path: AllocInfo,
|
||||||
cmd_mesh: AllocInfo,
|
cmd_mesh: AllocInfo,
|
||||||
|
cmd_vec: AllocInfo,
|
||||||
|
|
||||||
/// Number of separate clip rectangles
|
/// Number of separate clip rectangles
|
||||||
jobs: AllocInfo,
|
jobs: AllocInfo,
|
||||||
|
@ -147,27 +149,40 @@ impl PaintStats {
|
||||||
pub fn from_paint_commands(paint_commands: &[(Rect, PaintCmd)]) -> Self {
|
pub fn from_paint_commands(paint_commands: &[(Rect, PaintCmd)]) -> Self {
|
||||||
let mut stats = Self::default();
|
let mut stats = Self::default();
|
||||||
stats.cmd_path.element_size = ElementSize::Heterogenous; // nicer display later
|
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);
|
stats.primitives = AllocInfo::from_slice(paint_commands);
|
||||||
for (_, cmd) in paint_commands {
|
for (_, cmd) in paint_commands {
|
||||||
|
stats.add(cmd);
|
||||||
|
}
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, cmd: &PaintCmd) {
|
||||||
match cmd {
|
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::Noop
|
||||||
| PaintCmd::Circle { .. }
|
| PaintCmd::Circle { .. }
|
||||||
| PaintCmd::LineSegment { .. }
|
| PaintCmd::LineSegment { .. }
|
||||||
| PaintCmd::Rect { .. } => Default::default(),
|
| PaintCmd::Rect { .. } => Default::default(),
|
||||||
PaintCmd::Path { points, .. } => {
|
PaintCmd::Path { points, .. } => {
|
||||||
stats.cmd_path += AllocInfo::from_slice(points);
|
self.cmd_path += AllocInfo::from_slice(points);
|
||||||
}
|
}
|
||||||
PaintCmd::Text { galley, .. } => {
|
PaintCmd::Text { galley, .. } => {
|
||||||
stats.cmd_text += AllocInfo::from_galley(galley);
|
self.cmd_text += AllocInfo::from_galley(galley);
|
||||||
}
|
}
|
||||||
PaintCmd::Triangles(triangles) => {
|
PaintCmd::Triangles(triangles) => {
|
||||||
stats.cmd_mesh += AllocInfo::from_triangles(triangles);
|
self.cmd_mesh += AllocInfo::from_triangles(triangles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_paint_jobs(mut self, paint_jobs: &[crate::paint::PaintJob]) -> Self {
|
pub fn with_paint_jobs(mut self, paint_jobs: &[crate::paint::PaintJob]) -> Self {
|
||||||
self.jobs += AllocInfo::from_slice(paint_jobs);
|
self.jobs += AllocInfo::from_slice(paint_jobs);
|
||||||
|
@ -198,19 +213,32 @@ impl PaintStats {
|
||||||
ui.advance_cursor(10.0);
|
ui.advance_cursor(10.0);
|
||||||
|
|
||||||
ui.style_mut().body_text_style = TextStyle::Monospace;
|
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("Intermediate:");
|
||||||
ui.label(self.primitives.format("primitives"))
|
ui.label(primitives.format("primitives"))
|
||||||
.on_hover_text("Boxes, circles, etc");
|
.on_hover_text("Boxes, circles, etc");
|
||||||
ui.label(self.cmd_text.format("text"));
|
ui.label(cmd_text.format("text"));
|
||||||
ui.label(self.cmd_path.format("paths"));
|
ui.label(cmd_path.format("paths"));
|
||||||
ui.label(self.cmd_mesh.format("meshes"));
|
ui.label(cmd_mesh.format("meshes"));
|
||||||
|
ui.label(cmd_vec.format("nested"));
|
||||||
ui.advance_cursor(10.0);
|
ui.advance_cursor(10.0);
|
||||||
|
|
||||||
ui.label("Tessellated:");
|
ui.label("Tessellated:");
|
||||||
ui.label(self.jobs.format("jobs"))
|
ui.label(jobs.format("jobs"))
|
||||||
.on_hover_text("Number of separate clip rectangles");
|
.on_hover_text("Number of separate clip rectangles");
|
||||||
ui.label(self.vertices.format("vertices"));
|
ui.label(vertices.format("vertices"));
|
||||||
ui.label(self.indices.format("indices"))
|
ui.label(indices.format("indices"))
|
||||||
.on_hover_text("Three 32-bit indices per triangles");
|
.on_hover_text("Three 32-bit indices per triangles");
|
||||||
ui.advance_cursor(10.0);
|
ui.advance_cursor(10.0);
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
use {
|
use {
|
||||||
super::{
|
super::{
|
||||||
color::{self, srgba, Rgba, Srgba, TRANSPARENT},
|
color::{self, srgba, Rgba, Srgba, TRANSPARENT},
|
||||||
fonts::Fonts,
|
*,
|
||||||
PaintCmd, Stroke,
|
|
||||||
},
|
},
|
||||||
crate::math::*,
|
crate::math::*,
|
||||||
std::f32::consts::TAU,
|
std::f32::consts::TAU,
|
||||||
|
@ -665,28 +664,48 @@ fn mul_color(color: Srgba, factor: f32) -> Srgba {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Tessellate a single [`PaintCmd`] into a [`Triangles`].
|
pub struct Tessellator {
|
||||||
///
|
pub options: TessellationOptions,
|
||||||
/// * `command`: the command to tessellate
|
/// Only used for culling
|
||||||
/// * `options`: tessellation quality
|
pub clip_rect: Rect,
|
||||||
/// * `fonts`: font source when tessellating text
|
scratchpad_points: Vec<Pos2>,
|
||||||
/// * `out`: where the triangles are put
|
scratchpad_path: Path,
|
||||||
/// * `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(
|
impl Tessellator {
|
||||||
options: TessellationOptions,
|
pub fn from_options(options: TessellationOptions) -> Self {
|
||||||
|
Self {
|
||||||
|
options,
|
||||||
|
clip_rect: Rect::everything(),
|
||||||
|
scratchpad_points: Default::default(),
|
||||||
|
scratchpad_path: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
fonts: &Fonts,
|
||||||
clip_rect: Rect,
|
|
||||||
command: PaintCmd,
|
command: PaintCmd,
|
||||||
out: &mut Triangles,
|
out: &mut Triangles,
|
||||||
scratchpad_points: &mut Vec<Pos2>,
|
) {
|
||||||
scratchpad_path: &mut Path,
|
let clip_rect = self.clip_rect;
|
||||||
) {
|
let options = self.options;
|
||||||
let path = scratchpad_path;
|
|
||||||
path.clear();
|
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
PaintCmd::Noop => {}
|
PaintCmd::Noop => {}
|
||||||
|
PaintCmd::Vec(vec) => {
|
||||||
|
for command in vec {
|
||||||
|
self.tessellate_paint_command(fonts, command, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
PaintCmd::Circle {
|
PaintCmd::Circle {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
|
@ -703,6 +722,8 @@ fn tessellate_paint_command(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let path = &mut self.scratchpad_path;
|
||||||
|
path.clear();
|
||||||
path.add_circle(center, radius);
|
path.add_circle(center, radius);
|
||||||
fill_closed_path(&path.0, fill, options, out);
|
fill_closed_path(&path.0, fill, options, out);
|
||||||
stroke_path(&path.0, Closed, stroke, options, out);
|
stroke_path(&path.0, Closed, stroke, options, out);
|
||||||
|
@ -715,6 +736,8 @@ fn tessellate_paint_command(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PaintCmd::LineSegment { points, stroke } => {
|
PaintCmd::LineSegment { points, stroke } => {
|
||||||
|
let path = &mut self.scratchpad_path;
|
||||||
|
path.clear();
|
||||||
path.add_line_segment(points);
|
path.add_line_segment(points);
|
||||||
stroke_path(&path.0, Open, stroke, options, out);
|
stroke_path(&path.0, Open, stroke, options, out);
|
||||||
}
|
}
|
||||||
|
@ -725,6 +748,8 @@ fn tessellate_paint_command(
|
||||||
stroke,
|
stroke,
|
||||||
} => {
|
} => {
|
||||||
if points.len() >= 2 {
|
if points.len() >= 2 {
|
||||||
|
let path = &mut self.scratchpad_path;
|
||||||
|
path.clear();
|
||||||
if closed {
|
if closed {
|
||||||
path.add_line_loop(&points);
|
path.add_line_loop(&points);
|
||||||
} else {
|
} else {
|
||||||
|
@ -743,55 +768,69 @@ fn tessellate_paint_command(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PaintCmd::Rect {
|
PaintCmd::Rect {
|
||||||
mut rect,
|
rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
} => {
|
} => {
|
||||||
if rect.is_empty() {
|
let rect = PaintRect {
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.coarse_tessellation_culling
|
pub(crate) fn tessellate_rect(&mut self, rect: &PaintRect, out: &mut Triangles) {
|
||||||
&& !rect.expand(stroke.width).intersects(clip_rect)
|
let PaintRect {
|
||||||
|
mut rect,
|
||||||
|
corner_radius,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
} = *rect;
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if rect.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
||||||
// Make sure we can handle that:
|
// Make sure we can handle that:
|
||||||
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||||
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||||
|
|
||||||
path::rounded_rectangle(scratchpad_points, rect, corner_radius);
|
let path = &mut self.scratchpad_path;
|
||||||
path.add_line_loop(scratchpad_points);
|
path.clear();
|
||||||
fill_closed_path(&path.0, fill, options, out);
|
path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius);
|
||||||
stroke_path(&path.0, Closed, stroke, options, out);
|
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);
|
||||||
}
|
}
|
||||||
PaintCmd::Text {
|
|
||||||
pos,
|
|
||||||
galley,
|
|
||||||
text_style,
|
|
||||||
color,
|
|
||||||
} => {
|
|
||||||
tessellate_text(
|
|
||||||
options, fonts, clip_rect, pos, &galley, text_style, color, out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub fn tessellate_text(
|
||||||
fn tessellate_text(
|
&mut self,
|
||||||
options: TessellationOptions,
|
|
||||||
fonts: &Fonts,
|
fonts: &Fonts,
|
||||||
clip_rect: Rect,
|
|
||||||
pos: Pos2,
|
pos: Pos2,
|
||||||
galley: &super::Galley,
|
galley: &super::Galley,
|
||||||
text_style: super::TextStyle,
|
text_style: super::TextStyle,
|
||||||
color: Srgba,
|
color: Srgba,
|
||||||
out: &mut Triangles,
|
out: &mut Triangles,
|
||||||
) {
|
) {
|
||||||
if color == TRANSPARENT {
|
if color == TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -806,7 +845,7 @@ fn tessellate_text(
|
||||||
|
|
||||||
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 font = &fonts[text_style];
|
||||||
let mut chars = galley.text.chars();
|
let mut chars = galley.text.chars();
|
||||||
|
@ -818,14 +857,15 @@ fn tessellate_text(
|
||||||
for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) {
|
for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) {
|
||||||
let c = chars.next().unwrap();
|
let c = chars.next().unwrap();
|
||||||
|
|
||||||
if options.coarse_tessellation_culling && !is_line_visible {
|
if self.options.coarse_tessellation_culling && !is_line_visible {
|
||||||
// culling individual lines of text is important, since a single `PaintCmd::Text`
|
// culling individual lines of text is important, since a single `PaintCmd::Text`
|
||||||
// can span hundreds of lines.
|
// can span hundreds of lines.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(glyph) = font.uv_rect(c) {
|
if let Some(glyph) = font.uv_rect(c) {
|
||||||
let mut left_top = pos + glyph.offset + vec2(*x_offset, line.y_min) + text_offset;
|
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.x = font.round_to_pixel(left_top.x); // Pixel-perfection.
|
||||||
left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection.
|
left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection.
|
||||||
|
|
||||||
|
@ -843,6 +883,7 @@ fn tessellate_text(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(chars.next(), None);
|
assert_eq!(chars.next(), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns [`PaintCmd`]:s into sets of triangles.
|
/// Turns [`PaintCmd`]:s into sets of triangles.
|
||||||
|
@ -861,8 +902,7 @@ pub fn tessellate_paint_commands(
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
fonts: &Fonts,
|
fonts: &Fonts,
|
||||||
) -> Vec<(Rect, Triangles)> {
|
) -> Vec<(Rect, Triangles)> {
|
||||||
let mut scratchpad_points = Vec::new();
|
let mut tessellator = Tessellator::from_options(options);
|
||||||
let mut scratchpad_path = Path::default();
|
|
||||||
|
|
||||||
let mut jobs = PaintJobs::default();
|
let mut jobs = PaintJobs::default();
|
||||||
for (clip_rect, cmd) in commands {
|
for (clip_rect, cmd) in commands {
|
||||||
|
@ -876,23 +916,15 @@ pub fn tessellate_paint_commands(
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = &mut jobs.last_mut().unwrap().1;
|
let out = &mut jobs.last_mut().unwrap().1;
|
||||||
tessellate_paint_command(
|
tessellator.clip_rect = clip_rect;
|
||||||
options,
|
tessellator.tessellate_paint_command(fonts, cmd, out);
|
||||||
fonts,
|
|
||||||
clip_rect,
|
|
||||||
cmd,
|
|
||||||
out,
|
|
||||||
&mut scratchpad_points,
|
|
||||||
&mut scratchpad_path,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.debug_paint_clip_rects {
|
if options.debug_paint_clip_rects {
|
||||||
for (clip_rect, triangles) in &mut jobs {
|
for (clip_rect, triangles) in &mut jobs {
|
||||||
tessellate_paint_command(
|
tessellator.clip_rect = Rect::everything();
|
||||||
options,
|
tessellator.tessellate_paint_command(
|
||||||
fonts,
|
fonts,
|
||||||
Rect::everything(),
|
|
||||||
PaintCmd::Rect {
|
PaintCmd::Rect {
|
||||||
rect: *clip_rect,
|
rect: *clip_rect,
|
||||||
corner_radius: 0.0,
|
corner_radius: 0.0,
|
||||||
|
@ -900,8 +932,6 @@ pub fn tessellate_paint_commands(
|
||||||
stroke: Stroke::new(2.0, srgba(150, 255, 150, 255)),
|
stroke: Stroke::new(2.0, srgba(150, 255, 150, 255)),
|
||||||
},
|
},
|
||||||
triangles,
|
triangles,
|
||||||
&mut scratchpad_points,
|
|
||||||
&mut scratchpad_path,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
color::*,
|
color::*,
|
||||||
math::*,
|
math::*,
|
||||||
paint::{Stroke, TextStyle},
|
paint::{Shadow, Stroke, TextStyle},
|
||||||
types::*,
|
types::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ pub struct Visuals {
|
||||||
pub hyperlink_color: Srgba,
|
pub hyperlink_color: Srgba,
|
||||||
|
|
||||||
pub window_corner_radius: f32,
|
pub window_corner_radius: f32,
|
||||||
|
pub window_shadow: Shadow,
|
||||||
|
|
||||||
pub resize_corner_size: f32,
|
pub resize_corner_size: f32,
|
||||||
|
|
||||||
|
@ -275,6 +276,7 @@ impl Default for Visuals {
|
||||||
dark_bg_color: Srgba::black_alpha(140),
|
dark_bg_color: Srgba::black_alpha(140),
|
||||||
hyperlink_color: Srgba::from_rgb(90, 170, 255),
|
hyperlink_color: Srgba::from_rgb(90, 170, 255),
|
||||||
window_corner_radius: 10.0,
|
window_corner_radius: 10.0,
|
||||||
|
window_shadow: Shadow::big(),
|
||||||
resize_corner_size: 12.0,
|
resize_corner_size: 12.0,
|
||||||
text_cursor_width: 2.0,
|
text_cursor_width: 2.0,
|
||||||
clip_rect_margin: 1.0, // should be half the size of the widest frame stroke
|
clip_rect_margin: 1.0, // should be half the size of the widest frame stroke
|
||||||
|
@ -479,6 +481,7 @@ impl Visuals {
|
||||||
dark_bg_color,
|
dark_bg_color,
|
||||||
hyperlink_color,
|
hyperlink_color,
|
||||||
window_corner_radius,
|
window_corner_radius,
|
||||||
|
window_shadow,
|
||||||
resize_corner_size,
|
resize_corner_size,
|
||||||
text_cursor_width,
|
text_cursor_width,
|
||||||
clip_rect_margin,
|
clip_rect_margin,
|
||||||
|
@ -492,6 +495,7 @@ impl Visuals {
|
||||||
ui_color(ui, dark_bg_color, "dark_bg_color");
|
ui_color(ui, dark_bg_color, "dark_bg_color");
|
||||||
ui_color(ui, hyperlink_color, "hyperlink_color");
|
ui_color(ui, hyperlink_color, "hyperlink_color");
|
||||||
ui.add(Slider::f32(window_corner_radius, 0.0..=20.0).text("window_corner_radius"));
|
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(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(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"));
|
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
|
// TODO: improve and standardize ui_slider_vec2
|
||||||
fn ui_slider_vec2(
|
fn ui_slider_vec2(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
|
|
Loading…
Reference in a new issue