Add shadows under windows

This commit is contained in:
Emil Ernerfeldt 2020-12-29 01:13:14 +01:00
parent d38b16f1ea
commit 6dd15dd1a3
10 changed files with 421 additions and 255 deletions

View file

@ -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.

View file

@ -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,

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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
View 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
}
}

View file

@ -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);

View file

@ -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,
)
}
}

View file

@ -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,