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.
|
||||
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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<PaintCmd>),
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
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 {
|
||||
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);
|
||||
|
||||
|
|
|
@ -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<Pos2>,
|
||||
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<Pos2>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue