mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 23:13:13 -04:00
Merge branch 'master' into cache_galley_lines
This commit is contained in:
19
crates/epaint/src/brush.rs
Normal file
19
crates/epaint/src/brush.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::{Rect, TextureId};
|
||||
|
||||
/// Controls texturing of a [`crate::RectShape`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Brush {
|
||||
/// If the rect should be filled with a texture, which one?
|
||||
///
|
||||
/// The texture is multiplied with [`crate::RectShape::fill`].
|
||||
pub fill_texture_id: TextureId,
|
||||
|
||||
/// What UV coordinates to use for the texture?
|
||||
///
|
||||
/// To display a texture, set [`Self::fill_texture_id`],
|
||||
/// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`.
|
||||
///
|
||||
/// Use [`Rect::ZERO`] to turn off texturing.
|
||||
pub uv: Rect,
|
||||
}
|
||||
@@ -23,15 +23,18 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
mod bezier;
|
||||
mod brush;
|
||||
pub mod color;
|
||||
pub mod image;
|
||||
mod margin;
|
||||
mod marginf;
|
||||
mod mesh;
|
||||
pub mod mutex;
|
||||
mod rounding;
|
||||
mod roundingf;
|
||||
mod shadow;
|
||||
mod shape;
|
||||
pub mod shape_transform;
|
||||
mod shapes;
|
||||
pub mod stats;
|
||||
mod stroke;
|
||||
pub mod tessellator;
|
||||
@@ -40,17 +43,21 @@ mod texture_atlas;
|
||||
mod texture_handle;
|
||||
pub mod textures;
|
||||
pub mod util;
|
||||
mod viewport;
|
||||
|
||||
pub use self::{
|
||||
bezier::{CubicBezierShape, QuadraticBezierShape},
|
||||
brush::Brush,
|
||||
color::ColorMode,
|
||||
image::{ColorImage, FontImage, ImageData, ImageDelta},
|
||||
margin::Margin,
|
||||
marginf::Marginf,
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
rounding::Rounding,
|
||||
roundingf::Roundingf,
|
||||
shadow::Shadow,
|
||||
shape::{
|
||||
CircleShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape,
|
||||
Rounding, Shape, TextShape,
|
||||
shapes::{
|
||||
CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape,
|
||||
QuadraticBezierShape, RectShape, Shape, TextShape,
|
||||
},
|
||||
stats::PaintStats,
|
||||
stroke::{PathStroke, Stroke, StrokeKind},
|
||||
@@ -59,6 +66,7 @@ pub use self::{
|
||||
texture_atlas::TextureAtlas,
|
||||
texture_handle::TextureHandle,
|
||||
textures::TextureManager,
|
||||
viewport::ViewportInPixels,
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
|
||||
@@ -4,27 +4,33 @@ use emath::{vec2, Rect, Vec2};
|
||||
/// often used to express padding or spacing.
|
||||
///
|
||||
/// Can be added and subtracted to/from [`Rect`]s.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
///
|
||||
/// Negative margins are possible, but may produce weird behavior.
|
||||
/// Use with care.
|
||||
///
|
||||
/// All values are stored as [`i8`] to keep the size of [`Margin`] small.
|
||||
/// If you want floats, use [`crate::Marginf`] instead.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Margin {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
pub left: i8,
|
||||
pub right: i8,
|
||||
pub top: i8,
|
||||
pub bottom: i8,
|
||||
}
|
||||
|
||||
impl Margin {
|
||||
pub const ZERO: Self = Self {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
/// The same margin on every side.
|
||||
#[doc(alias = "symmetric")]
|
||||
#[inline]
|
||||
pub const fn same(margin: f32) -> Self {
|
||||
pub const fn same(margin: i8) -> Self {
|
||||
Self {
|
||||
left: margin,
|
||||
right: margin,
|
||||
@@ -35,7 +41,7 @@ impl Margin {
|
||||
|
||||
/// Margins with the same size on opposing sides
|
||||
#[inline]
|
||||
pub const fn symmetric(x: f32, y: f32) -> Self {
|
||||
pub const fn symmetric(x: i8, y: i8) -> Self {
|
||||
Self {
|
||||
left: x,
|
||||
right: x,
|
||||
@@ -44,53 +50,84 @@ impl Margin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Left margin, as `f32`
|
||||
#[inline]
|
||||
pub const fn leftf(self) -> f32 {
|
||||
self.left as _
|
||||
}
|
||||
|
||||
/// Right margin, as `f32`
|
||||
#[inline]
|
||||
pub const fn rightf(self) -> f32 {
|
||||
self.right as _
|
||||
}
|
||||
|
||||
/// Top margin, as `f32`
|
||||
#[inline]
|
||||
pub const fn topf(self) -> f32 {
|
||||
self.top as _
|
||||
}
|
||||
|
||||
/// Bottom margin, as `f32`
|
||||
#[inline]
|
||||
pub const fn bottomf(self) -> f32 {
|
||||
self.bottom as _
|
||||
}
|
||||
|
||||
/// Total margins on both sides
|
||||
#[inline]
|
||||
pub fn sum(&self) -> Vec2 {
|
||||
vec2(self.left + self.right, self.top + self.bottom)
|
||||
pub fn sum(self) -> Vec2 {
|
||||
vec2(self.leftf() + self.rightf(), self.topf() + self.bottomf())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn left_top(&self) -> Vec2 {
|
||||
vec2(self.left, self.top)
|
||||
pub const fn left_top(self) -> Vec2 {
|
||||
vec2(self.leftf(), self.topf())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn right_bottom(&self) -> Vec2 {
|
||||
vec2(self.right, self.bottom)
|
||||
pub const fn right_bottom(self) -> Vec2 {
|
||||
vec2(self.rightf(), self.bottomf())
|
||||
}
|
||||
|
||||
/// Are the margin on every side the same?
|
||||
#[doc(alias = "symmetric")]
|
||||
#[inline]
|
||||
pub fn is_same(&self) -> bool {
|
||||
pub const fn is_same(self) -> bool {
|
||||
self.left == self.right && self.left == self.top && self.left == self.bottom
|
||||
}
|
||||
|
||||
#[deprecated = "Use `rect + margin` instead"]
|
||||
#[inline]
|
||||
pub fn expand_rect(&self, rect: Rect) -> Rect {
|
||||
pub fn expand_rect(self, rect: Rect) -> Rect {
|
||||
Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom())
|
||||
}
|
||||
|
||||
#[deprecated = "Use `rect - margin` instead"]
|
||||
#[inline]
|
||||
pub fn shrink_rect(&self, rect: Rect) -> Rect {
|
||||
pub fn shrink_rect(self, rect: Rect) -> Rect {
|
||||
Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for Margin {
|
||||
#[inline]
|
||||
fn from(v: i8) -> Self {
|
||||
Self::same(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Margin {
|
||||
#[inline]
|
||||
fn from(v: f32) -> Self {
|
||||
Self::same(v)
|
||||
Self::same(v.round() as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec2> for Margin {
|
||||
#[inline]
|
||||
fn from(v: Vec2) -> Self {
|
||||
Self::symmetric(v.x, v.y)
|
||||
Self::symmetric(v.x.round() as _, v.y.round() as _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,37 +138,34 @@ impl std::ops::Add for Margin {
|
||||
#[inline]
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
left: self.left + other.left,
|
||||
right: self.right + other.right,
|
||||
top: self.top + other.top,
|
||||
bottom: self.bottom + other.bottom,
|
||||
left: self.left.saturating_add(other.left),
|
||||
right: self.right.saturating_add(other.right),
|
||||
top: self.top.saturating_add(other.top),
|
||||
bottom: self.bottom.saturating_add(other.bottom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Margin + f32`
|
||||
impl std::ops::Add<f32> for Margin {
|
||||
/// `Margin + i8`
|
||||
impl std::ops::Add<i8> for Margin {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, v: f32) -> Self {
|
||||
fn add(self, v: i8) -> Self {
|
||||
Self {
|
||||
left: self.left + v,
|
||||
right: self.right + v,
|
||||
top: self.top + v,
|
||||
bottom: self.bottom + v,
|
||||
left: self.left.saturating_add(v),
|
||||
right: self.right.saturating_add(v),
|
||||
top: self.top.saturating_add(v),
|
||||
bottom: self.bottom.saturating_add(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Margind += f32`
|
||||
impl std::ops::AddAssign<f32> for Margin {
|
||||
/// `Margin += i8`
|
||||
impl std::ops::AddAssign<i8> for Margin {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, v: f32) {
|
||||
self.left += v;
|
||||
self.right += v;
|
||||
self.top += v;
|
||||
self.bottom += v;
|
||||
fn add_assign(&mut self, v: i8) {
|
||||
*self = *self + v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,10 +176,10 @@ impl std::ops::Mul<f32> for Margin {
|
||||
#[inline]
|
||||
fn mul(self, v: f32) -> Self {
|
||||
Self {
|
||||
left: self.left * v,
|
||||
right: self.right * v,
|
||||
top: self.top * v,
|
||||
bottom: self.bottom * v,
|
||||
left: (self.leftf() * v).round() as _,
|
||||
right: (self.rightf() * v).round() as _,
|
||||
top: (self.topf() * v).round() as _,
|
||||
bottom: (self.bottomf() * v).round() as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,10 +188,7 @@ impl std::ops::Mul<f32> for Margin {
|
||||
impl std::ops::MulAssign<f32> for Margin {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, v: f32) {
|
||||
self.left *= v;
|
||||
self.right *= v;
|
||||
self.top *= v;
|
||||
self.bottom *= v;
|
||||
*self = *self * v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,12 +198,8 @@ impl std::ops::Div<f32> for Margin {
|
||||
|
||||
#[inline]
|
||||
fn div(self, v: f32) -> Self {
|
||||
Self {
|
||||
left: self.left / v,
|
||||
right: self.right / v,
|
||||
top: self.top / v,
|
||||
bottom: self.bottom / v,
|
||||
}
|
||||
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||
self * v.recip()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +207,7 @@ impl std::ops::Div<f32> for Margin {
|
||||
impl std::ops::DivAssign<f32> for Margin {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, v: f32) {
|
||||
self.left /= v;
|
||||
self.right /= v;
|
||||
self.top /= v;
|
||||
self.bottom /= v;
|
||||
*self = *self / v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,37 +218,34 @@ impl std::ops::Sub for Margin {
|
||||
#[inline]
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self {
|
||||
left: self.left - other.left,
|
||||
right: self.right - other.right,
|
||||
top: self.top - other.top,
|
||||
bottom: self.bottom - other.bottom,
|
||||
left: self.left.saturating_sub(other.left),
|
||||
right: self.right.saturating_sub(other.right),
|
||||
top: self.top.saturating_sub(other.top),
|
||||
bottom: self.bottom.saturating_sub(other.bottom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Margin - f32`
|
||||
impl std::ops::Sub<f32> for Margin {
|
||||
/// `Margin - i8`
|
||||
impl std::ops::Sub<i8> for Margin {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, v: f32) -> Self {
|
||||
fn sub(self, v: i8) -> Self {
|
||||
Self {
|
||||
left: self.left - v,
|
||||
right: self.right - v,
|
||||
top: self.top - v,
|
||||
bottom: self.bottom - v,
|
||||
left: self.left.saturating_sub(v),
|
||||
right: self.right.saturating_sub(v),
|
||||
top: self.top.saturating_sub(v),
|
||||
bottom: self.bottom.saturating_sub(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Margin -= f32`
|
||||
impl std::ops::SubAssign<f32> for Margin {
|
||||
/// `Margin -= i8`
|
||||
impl std::ops::SubAssign<i8> for Margin {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, v: f32) {
|
||||
self.left -= v;
|
||||
self.right -= v;
|
||||
self.top -= v;
|
||||
self.bottom -= v;
|
||||
fn sub_assign(&mut self, v: i8) {
|
||||
*self = *self - v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
299
crates/epaint/src/marginf.rs
Normal file
299
crates/epaint/src/marginf.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
use emath::{vec2, Rect, Vec2};
|
||||
|
||||
use crate::Margin;
|
||||
|
||||
/// A value for all four sides of a rectangle,
|
||||
/// often used to express padding or spacing.
|
||||
///
|
||||
/// Can be added and subtracted to/from [`Rect`]s.
|
||||
///
|
||||
/// For storage, use [`crate::Margin`] instead.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Marginf {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
}
|
||||
|
||||
impl From<Margin> for Marginf {
|
||||
#[inline]
|
||||
fn from(margin: Margin) -> Self {
|
||||
Self {
|
||||
left: margin.left as _,
|
||||
right: margin.right as _,
|
||||
top: margin.top as _,
|
||||
bottom: margin.bottom as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Marginf> for Margin {
|
||||
#[inline]
|
||||
fn from(marginf: Marginf) -> Self {
|
||||
Self {
|
||||
left: marginf.left as _,
|
||||
right: marginf.right as _,
|
||||
top: marginf.top as _,
|
||||
bottom: marginf.bottom as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Marginf {
|
||||
pub const ZERO: Self = Self {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
};
|
||||
|
||||
/// The same margin on every side.
|
||||
#[doc(alias = "symmetric")]
|
||||
#[inline]
|
||||
pub const fn same(margin: f32) -> Self {
|
||||
Self {
|
||||
left: margin,
|
||||
right: margin,
|
||||
top: margin,
|
||||
bottom: margin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Margins with the same size on opposing sides
|
||||
#[inline]
|
||||
pub const fn symmetric(x: f32, y: f32) -> Self {
|
||||
Self {
|
||||
left: x,
|
||||
right: x,
|
||||
top: y,
|
||||
bottom: y,
|
||||
}
|
||||
}
|
||||
|
||||
/// Total margins on both sides
|
||||
#[inline]
|
||||
pub fn sum(&self) -> Vec2 {
|
||||
vec2(self.left + self.right, self.top + self.bottom)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn left_top(&self) -> Vec2 {
|
||||
vec2(self.left, self.top)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn right_bottom(&self) -> Vec2 {
|
||||
vec2(self.right, self.bottom)
|
||||
}
|
||||
|
||||
/// Are the margin on every side the same?
|
||||
#[doc(alias = "symmetric")]
|
||||
#[inline]
|
||||
pub fn is_same(&self) -> bool {
|
||||
self.left == self.right && self.left == self.top && self.left == self.bottom
|
||||
}
|
||||
|
||||
#[deprecated = "Use `rect + margin` instead"]
|
||||
#[inline]
|
||||
pub fn expand_rect(&self, rect: Rect) -> Rect {
|
||||
Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom())
|
||||
}
|
||||
|
||||
#[deprecated = "Use `rect - margin` instead"]
|
||||
#[inline]
|
||||
pub fn shrink_rect(&self, rect: Rect) -> Rect {
|
||||
Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Marginf {
|
||||
#[inline]
|
||||
fn from(v: f32) -> Self {
|
||||
Self::same(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec2> for Marginf {
|
||||
#[inline]
|
||||
fn from(v: Vec2) -> Self {
|
||||
Self::symmetric(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf + Marginf`
|
||||
impl std::ops::Add for Marginf {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
left: self.left + other.left,
|
||||
right: self.right + other.right,
|
||||
top: self.top + other.top,
|
||||
bottom: self.bottom + other.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf + f32`
|
||||
impl std::ops::Add<f32> for Marginf {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, v: f32) -> Self {
|
||||
Self {
|
||||
left: self.left + v,
|
||||
right: self.right + v,
|
||||
top: self.top + v,
|
||||
bottom: self.bottom + v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Margind += f32`
|
||||
impl std::ops::AddAssign<f32> for Marginf {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, v: f32) {
|
||||
self.left += v;
|
||||
self.right += v;
|
||||
self.top += v;
|
||||
self.bottom += v;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf * f32`
|
||||
impl std::ops::Mul<f32> for Marginf {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, v: f32) -> Self {
|
||||
Self {
|
||||
left: self.left * v,
|
||||
right: self.right * v,
|
||||
top: self.top * v,
|
||||
bottom: self.bottom * v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf *= f32`
|
||||
impl std::ops::MulAssign<f32> for Marginf {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, v: f32) {
|
||||
self.left *= v;
|
||||
self.right *= v;
|
||||
self.top *= v;
|
||||
self.bottom *= v;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf / f32`
|
||||
impl std::ops::Div<f32> for Marginf {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn div(self, v: f32) -> Self {
|
||||
Self {
|
||||
left: self.left / v,
|
||||
right: self.right / v,
|
||||
top: self.top / v,
|
||||
bottom: self.bottom / v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf /= f32`
|
||||
impl std::ops::DivAssign<f32> for Marginf {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, v: f32) {
|
||||
self.left /= v;
|
||||
self.right /= v;
|
||||
self.top /= v;
|
||||
self.bottom /= v;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf - Marginf`
|
||||
impl std::ops::Sub for Marginf {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self {
|
||||
left: self.left - other.left,
|
||||
right: self.right - other.right,
|
||||
top: self.top - other.top,
|
||||
bottom: self.bottom - other.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf - f32`
|
||||
impl std::ops::Sub<f32> for Marginf {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, v: f32) -> Self {
|
||||
Self {
|
||||
left: self.left - v,
|
||||
right: self.right - v,
|
||||
top: self.top - v,
|
||||
bottom: self.bottom - v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf -= f32`
|
||||
impl std::ops::SubAssign<f32> for Marginf {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, v: f32) {
|
||||
self.left -= v;
|
||||
self.right -= v;
|
||||
self.top -= v;
|
||||
self.bottom -= v;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect + Marginf`
|
||||
impl std::ops::Add<Marginf> for Rect {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, margin: Marginf) -> Self {
|
||||
Self::from_min_max(
|
||||
self.min - margin.left_top(),
|
||||
self.max + margin.right_bottom(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect += Marginf`
|
||||
impl std::ops::AddAssign<Marginf> for Rect {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, margin: Marginf) {
|
||||
*self = *self + margin;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect - Marginf`
|
||||
impl std::ops::Sub<Marginf> for Rect {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, margin: Marginf) -> Self {
|
||||
Self::from_min_max(
|
||||
self.min + margin.left_top(),
|
||||
self.max - margin.right_bottom(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect -= Marginf`
|
||||
impl std::ops::SubAssign<Marginf> for Rect {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, margin: Marginf) {
|
||||
*self = *self - margin;
|
||||
}
|
||||
}
|
||||
220
crates/epaint/src/rounding.rs
Normal file
220
crates/epaint/src/rounding.rs
Normal file
@@ -0,0 +1,220 @@
|
||||
/// How rounded the corners of things should be.
|
||||
///
|
||||
/// The rounding uses `u8` to save space,
|
||||
/// so the amount of rounding is limited to integers in the range `[0, 255]`.
|
||||
///
|
||||
/// For calculations, you may want to use [`crate::Roundingf`] instead, which uses `f32`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Rounding {
|
||||
/// Radius of the rounding of the North-West (left top) corner.
|
||||
pub nw: u8,
|
||||
|
||||
/// Radius of the rounding of the North-East (right top) corner.
|
||||
pub ne: u8,
|
||||
|
||||
/// Radius of the rounding of the South-West (left bottom) corner.
|
||||
pub sw: u8,
|
||||
|
||||
/// Radius of the rounding of the South-East (right bottom) corner.
|
||||
pub se: u8,
|
||||
}
|
||||
|
||||
impl Default for Rounding {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Rounding {
|
||||
#[inline]
|
||||
fn from(radius: u8) -> Self {
|
||||
Self::same(radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Rounding {
|
||||
#[inline]
|
||||
fn from(radius: f32) -> Self {
|
||||
Self::same(radius.round() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rounding {
|
||||
/// No rounding on any corner.
|
||||
pub const ZERO: Self = Self {
|
||||
nw: 0,
|
||||
ne: 0,
|
||||
sw: 0,
|
||||
se: 0,
|
||||
};
|
||||
|
||||
/// Same rounding on all four corners.
|
||||
#[inline]
|
||||
pub const fn same(radius: u8) -> Self {
|
||||
Self {
|
||||
nw: radius,
|
||||
ne: radius,
|
||||
sw: radius,
|
||||
se: radius,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do all corners have the same rounding?
|
||||
#[inline]
|
||||
pub fn is_same(self) -> bool {
|
||||
self.nw == self.ne && self.nw == self.sw && self.nw == self.se
|
||||
}
|
||||
|
||||
/// Make sure each corner has a rounding of at least this.
|
||||
#[inline]
|
||||
pub fn at_least(self, min: u8) -> Self {
|
||||
Self {
|
||||
nw: self.nw.max(min),
|
||||
ne: self.ne.max(min),
|
||||
sw: self.sw.max(min),
|
||||
se: self.se.max(min),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make sure each corner has a rounding of at most this.
|
||||
#[inline]
|
||||
pub fn at_most(self, max: u8) -> Self {
|
||||
Self {
|
||||
nw: self.nw.min(max),
|
||||
ne: self.ne.min(max),
|
||||
sw: self.sw.min(max),
|
||||
se: self.se.min(max),
|
||||
}
|
||||
}
|
||||
|
||||
/// Average rounding of the corners.
|
||||
pub fn average(&self) -> f32 {
|
||||
(self.nw as f32 + self.ne as f32 + self.sw as f32 + self.se as f32) / 4.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Rounding {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
nw: self.nw + rhs.nw,
|
||||
ne: self.ne + rhs.ne,
|
||||
sw: self.sw + rhs.sw,
|
||||
se: self.se + rhs.se,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Rounding {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
nw: self.nw + rhs.nw,
|
||||
ne: self.ne + rhs.ne,
|
||||
sw: self.sw + rhs.sw,
|
||||
se: self.se + rhs.se,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<u8> for Rounding {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: u8) {
|
||||
*self = Self {
|
||||
nw: self.nw.saturating_add(rhs),
|
||||
ne: self.ne.saturating_add(rhs),
|
||||
sw: self.sw.saturating_add(rhs),
|
||||
se: self.se.saturating_add(rhs),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Rounding {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
nw: self.nw.saturating_sub(rhs.nw),
|
||||
ne: self.ne.saturating_sub(rhs.ne),
|
||||
sw: self.sw.saturating_sub(rhs.sw),
|
||||
se: self.se.saturating_sub(rhs.se),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign for Rounding {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
nw: self.nw.saturating_sub(rhs.nw),
|
||||
ne: self.ne.saturating_sub(rhs.ne),
|
||||
sw: self.sw.saturating_sub(rhs.sw),
|
||||
se: self.se.saturating_sub(rhs.se),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<u8> for Rounding {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: u8) {
|
||||
*self = Self {
|
||||
nw: self.nw.saturating_sub(rhs),
|
||||
ne: self.ne.saturating_sub(rhs),
|
||||
sw: self.sw.saturating_sub(rhs),
|
||||
se: self.se.saturating_sub(rhs),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f32> for Rounding {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn div(self, rhs: f32) -> Self {
|
||||
Self {
|
||||
nw: (self.nw as f32 / rhs) as u8,
|
||||
ne: (self.ne as f32 / rhs) as u8,
|
||||
sw: (self.sw as f32 / rhs) as u8,
|
||||
se: (self.se as f32 / rhs) as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DivAssign<f32> for Rounding {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
nw: (self.nw as f32 / rhs) as u8,
|
||||
ne: (self.ne as f32 / rhs) as u8,
|
||||
sw: (self.sw as f32 / rhs) as u8,
|
||||
se: (self.se as f32 / rhs) as u8,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Rounding {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
Self {
|
||||
nw: (self.nw as f32 * rhs) as u8,
|
||||
ne: (self.ne as f32 * rhs) as u8,
|
||||
sw: (self.sw as f32 * rhs) as u8,
|
||||
se: (self.se as f32 * rhs) as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::MulAssign<f32> for Rounding {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
nw: (self.nw as f32 * rhs) as u8,
|
||||
ne: (self.ne as f32 * rhs) as u8,
|
||||
sw: (self.sw as f32 * rhs) as u8,
|
||||
se: (self.se as f32 * rhs) as u8,
|
||||
};
|
||||
}
|
||||
}
|
||||
236
crates/epaint/src/roundingf.rs
Normal file
236
crates/epaint/src/roundingf.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use crate::Rounding;
|
||||
|
||||
/// How rounded the corners of things should be, in `f32`.
|
||||
///
|
||||
/// This is used for calculations, but storage is usually done with the more compact [`Rounding`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Roundingf {
|
||||
/// Radius of the rounding of the North-West (left top) corner.
|
||||
pub nw: f32,
|
||||
|
||||
/// Radius of the rounding of the North-East (right top) corner.
|
||||
pub ne: f32,
|
||||
|
||||
/// Radius of the rounding of the South-West (left bottom) corner.
|
||||
pub sw: f32,
|
||||
|
||||
/// Radius of the rounding of the South-East (right bottom) corner.
|
||||
pub se: f32,
|
||||
}
|
||||
|
||||
impl From<Rounding> for Roundingf {
|
||||
#[inline]
|
||||
fn from(rounding: Rounding) -> Self {
|
||||
Self {
|
||||
nw: rounding.nw as f32,
|
||||
ne: rounding.ne as f32,
|
||||
sw: rounding.sw as f32,
|
||||
se: rounding.se as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Roundingf> for Rounding {
|
||||
#[inline]
|
||||
fn from(rounding: Roundingf) -> Self {
|
||||
Self {
|
||||
nw: rounding.nw.round() as u8,
|
||||
ne: rounding.ne.round() as u8,
|
||||
sw: rounding.sw.round() as u8,
|
||||
se: rounding.se.round() as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Roundingf {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Roundingf {
|
||||
#[inline]
|
||||
fn from(radius: f32) -> Self {
|
||||
Self {
|
||||
nw: radius,
|
||||
ne: radius,
|
||||
sw: radius,
|
||||
se: radius,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Roundingf {
|
||||
/// No rounding on any corner.
|
||||
pub const ZERO: Self = Self {
|
||||
nw: 0.0,
|
||||
ne: 0.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
};
|
||||
|
||||
/// Same rounding on all four corners.
|
||||
#[inline]
|
||||
pub const fn same(radius: f32) -> Self {
|
||||
Self {
|
||||
nw: radius,
|
||||
ne: radius,
|
||||
sw: radius,
|
||||
se: radius,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do all corners have the same rounding?
|
||||
#[inline]
|
||||
pub fn is_same(&self) -> bool {
|
||||
self.nw == self.ne && self.nw == self.sw && self.nw == self.se
|
||||
}
|
||||
|
||||
/// Make sure each corner has a rounding of at least this.
|
||||
#[inline]
|
||||
pub fn at_least(&self, min: f32) -> Self {
|
||||
Self {
|
||||
nw: self.nw.max(min),
|
||||
ne: self.ne.max(min),
|
||||
sw: self.sw.max(min),
|
||||
se: self.se.max(min),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make sure each corner has a rounding of at most this.
|
||||
#[inline]
|
||||
pub fn at_most(&self, max: f32) -> Self {
|
||||
Self {
|
||||
nw: self.nw.min(max),
|
||||
ne: self.ne.min(max),
|
||||
sw: self.sw.min(max),
|
||||
se: self.se.min(max),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Roundingf {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
nw: self.nw + rhs.nw,
|
||||
ne: self.ne + rhs.ne,
|
||||
sw: self.sw + rhs.sw,
|
||||
se: self.se + rhs.se,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Roundingf {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
nw: self.nw + rhs.nw,
|
||||
ne: self.ne + rhs.ne,
|
||||
sw: self.sw + rhs.sw,
|
||||
se: self.se + rhs.se,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<f32> for Roundingf {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
nw: self.nw + rhs,
|
||||
ne: self.ne + rhs,
|
||||
sw: self.sw + rhs,
|
||||
se: self.se + rhs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Roundingf {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
nw: self.nw - rhs.nw,
|
||||
ne: self.ne - rhs.ne,
|
||||
sw: self.sw - rhs.sw,
|
||||
se: self.se - rhs.se,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign for Roundingf {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
nw: self.nw - rhs.nw,
|
||||
ne: self.ne - rhs.ne,
|
||||
sw: self.sw - rhs.sw,
|
||||
se: self.se - rhs.se,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<f32> for Roundingf {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
nw: self.nw - rhs,
|
||||
ne: self.ne - rhs,
|
||||
sw: self.sw - rhs,
|
||||
se: self.se - rhs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f32> for Roundingf {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn div(self, rhs: f32) -> Self {
|
||||
Self {
|
||||
nw: self.nw / rhs,
|
||||
ne: self.ne / rhs,
|
||||
sw: self.sw / rhs,
|
||||
se: self.se / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DivAssign<f32> for Roundingf {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
nw: self.nw / rhs,
|
||||
ne: self.ne / rhs,
|
||||
sw: self.sw / rhs,
|
||||
se: self.se / rhs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Roundingf {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
Self {
|
||||
nw: self.nw * rhs,
|
||||
ne: self.ne * rhs,
|
||||
sw: self.sw * rhs,
|
||||
se: self.se * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::MulAssign<f32> for Roundingf {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
nw: self.nw * rhs,
|
||||
ne: self.ne * rhs,
|
||||
sw: self.sw * rhs,
|
||||
se: self.se * rhs,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,45 @@
|
||||
use super::{Color32, Margin, Rect, RectShape, Rounding, Vec2};
|
||||
use crate::{Color32, Marginf, Rect, RectShape, Rounding, Vec2};
|
||||
|
||||
/// The color and fuzziness of a fuzzy shape.
|
||||
///
|
||||
/// Can be used for a rectangular shadow with a soft penumbra.
|
||||
///
|
||||
/// Very similar to a box-shadow in CSS.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Shadow {
|
||||
/// Move the shadow by this much.
|
||||
///
|
||||
/// For instance, a value of `[1.0, 2.0]` will move the shadow 1 point to the right and 2 points down,
|
||||
/// causing a drop-shadow effect.
|
||||
pub offset: Vec2,
|
||||
pub offset: [i8; 2],
|
||||
|
||||
/// The width of the blur, i.e. the width of the fuzzy penumbra.
|
||||
///
|
||||
/// A value of 0.0 means a sharp shadow.
|
||||
pub blur: f32,
|
||||
/// A value of 0 means a sharp shadow.
|
||||
pub blur: u8,
|
||||
|
||||
/// Expand the shadow in all directions by this much.
|
||||
pub spread: f32,
|
||||
pub spread: u8,
|
||||
|
||||
/// Color of the opaque center of the shadow.
|
||||
pub color: Color32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shadow_size() {
|
||||
assert_eq!(
|
||||
std::mem::size_of::<Shadow>(), 8,
|
||||
"Shadow changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
|
||||
);
|
||||
}
|
||||
|
||||
impl Shadow {
|
||||
/// No shadow at all.
|
||||
pub const NONE: Self = Self {
|
||||
offset: Vec2::ZERO,
|
||||
blur: 0.0,
|
||||
spread: 0.0,
|
||||
offset: [0, 0],
|
||||
blur: 0,
|
||||
spread: 0,
|
||||
color: Color32::TRANSPARENT,
|
||||
};
|
||||
|
||||
@@ -45,26 +53,32 @@ impl Shadow {
|
||||
spread,
|
||||
color,
|
||||
} = *self;
|
||||
let [offset_x, offset_y] = offset;
|
||||
|
||||
let rect = rect.translate(offset).expand(spread);
|
||||
let rounding = rounding.into() + Rounding::same(spread.abs());
|
||||
let rect = rect
|
||||
.translate(Vec2::new(offset_x as _, offset_y as _))
|
||||
.expand(spread as _);
|
||||
let rounding = rounding.into() + Rounding::from(spread);
|
||||
|
||||
RectShape::filled(rect, rounding, color).with_blur_width(blur)
|
||||
RectShape::filled(rect, rounding, color).with_blur_width(blur as _)
|
||||
}
|
||||
|
||||
/// How much larger than the parent rect are we in each direction?
|
||||
pub fn margin(&self) -> Margin {
|
||||
pub fn margin(&self) -> Marginf {
|
||||
let Self {
|
||||
offset,
|
||||
blur,
|
||||
spread,
|
||||
color: _,
|
||||
} = *self;
|
||||
Margin {
|
||||
left: spread + 0.5 * blur - offset.x,
|
||||
right: spread + 0.5 * blur + offset.x,
|
||||
top: spread + 0.5 * blur - offset.y,
|
||||
bottom: spread + 0.5 * blur + offset.y,
|
||||
let spread = spread as f32;
|
||||
let blur = blur as f32;
|
||||
let [offset_x, offset_y] = offset;
|
||||
Marginf {
|
||||
left: spread + 0.5 * blur - offset_x as f32,
|
||||
right: spread + 0.5 * blur + offset_x as f32,
|
||||
top: spread + 0.5 * blur - offset_y as f32,
|
||||
bottom: spread + 0.5 * blur + offset_y as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,8 +64,7 @@ pub fn adjust_colors(
|
||||
fill,
|
||||
stroke,
|
||||
blur_width: _,
|
||||
fill_texture_id: _,
|
||||
uv: _,
|
||||
brush: _,
|
||||
}) => {
|
||||
adjust_color(fill);
|
||||
adjust_color(&mut stroke.color);
|
||||
@@ -87,7 +86,7 @@ pub fn adjust_colors(
|
||||
}
|
||||
|
||||
if !galley.is_empty() {
|
||||
let galley = std::sync::Arc::make_mut(galley);
|
||||
let galley = Arc::make_mut(galley);
|
||||
for placed_row in &mut galley.rows {
|
||||
let row = Arc::make_mut(&mut placed_row.row);
|
||||
for vertex in &mut row.visuals.mesh.vertices {
|
||||
@@ -97,11 +96,13 @@ pub fn adjust_colors(
|
||||
}
|
||||
}
|
||||
|
||||
Shape::Mesh(Mesh {
|
||||
indices: _,
|
||||
vertices,
|
||||
texture_id: _,
|
||||
}) => {
|
||||
Shape::Mesh(mesh) => {
|
||||
let Mesh {
|
||||
indices: _,
|
||||
vertices,
|
||||
texture_id: _,
|
||||
} = Arc::make_mut(mesh);
|
||||
|
||||
for v in vertices {
|
||||
adjust_color(&mut v.color);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{shape::Shape, Color32, PathShape, PathStroke};
|
||||
use crate::{Color32, PathShape, PathStroke, Shape};
|
||||
use emath::{Pos2, Rect, RectTransform};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
52
crates/epaint/src/shapes/circle_shape.rs
Normal file
52
crates/epaint/src/shapes/circle_shape.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use crate::{Color32, Pos2, Rect, Shape, Stroke, Vec2};
|
||||
|
||||
/// How to paint a circle.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct CircleShape {
|
||||
pub center: Pos2,
|
||||
pub radius: f32,
|
||||
pub fill: Color32,
|
||||
pub stroke: Stroke,
|
||||
}
|
||||
|
||||
impl CircleShape {
|
||||
#[inline]
|
||||
pub fn filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke width)
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
||||
Rect::NOTHING
|
||||
} else {
|
||||
Rect::from_center_size(
|
||||
self.center,
|
||||
Vec2::splat(self.radius * 2.0 + self.stroke.width),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CircleShape> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: CircleShape) -> Self {
|
||||
Self::Circle(shape)
|
||||
}
|
||||
}
|
||||
54
crates/epaint/src/shapes/ellipse_shape.rs
Normal file
54
crates/epaint/src/shapes/ellipse_shape.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::*;
|
||||
|
||||
/// How to paint an ellipse.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct EllipseShape {
|
||||
pub center: Pos2,
|
||||
|
||||
/// Radius is the vector (a, b) where the width of the Ellipse is 2a and the height is 2b
|
||||
pub radius: Vec2,
|
||||
pub fill: Color32,
|
||||
pub stroke: Stroke,
|
||||
}
|
||||
|
||||
impl EllipseShape {
|
||||
#[inline]
|
||||
pub fn filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stroke(center: Pos2, radius: Vec2, stroke: impl Into<Stroke>) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke width)
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
||||
Rect::NOTHING
|
||||
} else {
|
||||
Rect::from_center_size(
|
||||
self.center,
|
||||
self.radius * 2.0 + Vec2::splat(self.stroke.width),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EllipseShape> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: EllipseShape) -> Self {
|
||||
Self::Ellipse(shape)
|
||||
}
|
||||
}
|
||||
19
crates/epaint/src/shapes/mod.rs
Normal file
19
crates/epaint/src/shapes/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
mod bezier_shape;
|
||||
mod circle_shape;
|
||||
mod ellipse_shape;
|
||||
mod paint_callback;
|
||||
mod path_shape;
|
||||
mod rect_shape;
|
||||
mod shape;
|
||||
mod text_shape;
|
||||
|
||||
pub use self::{
|
||||
bezier_shape::{CubicBezierShape, QuadraticBezierShape},
|
||||
circle_shape::CircleShape,
|
||||
ellipse_shape::EllipseShape,
|
||||
paint_callback::{PaintCallback, PaintCallbackInfo},
|
||||
path_shape::PathShape,
|
||||
rect_shape::RectShape,
|
||||
shape::Shape,
|
||||
text_shape::TextShape,
|
||||
};
|
||||
103
crates/epaint/src/shapes/paint_callback.rs
Normal file
103
crates/epaint/src/shapes/paint_callback.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Information passed along with [`PaintCallback`] ([`Shape::Callback`]).
|
||||
pub struct PaintCallbackInfo {
|
||||
/// Viewport in points.
|
||||
///
|
||||
/// This specifies where on the screen to paint, and the borders of this
|
||||
/// Rect is the [-1, +1] of the Normalized Device Coordinates.
|
||||
///
|
||||
/// Note than only a portion of this may be visible due to [`Self::clip_rect`].
|
||||
///
|
||||
/// This comes from [`PaintCallback::rect`].
|
||||
pub viewport: Rect,
|
||||
|
||||
/// Clip rectangle in points.
|
||||
pub clip_rect: Rect,
|
||||
|
||||
/// Pixels per point.
|
||||
pub pixels_per_point: f32,
|
||||
|
||||
/// Full size of the screen, in pixels.
|
||||
pub screen_size_px: [u32; 2],
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_viewport_rounding() {
|
||||
for i in 0..=10_000 {
|
||||
// Two adjacent viewports should never overlap:
|
||||
let x = i as f32 / 97.0;
|
||||
let left = Rect::from_min_max(pos2(0.0, 0.0), pos2(100.0, 100.0)).with_max_x(x);
|
||||
let right = Rect::from_min_max(pos2(0.0, 0.0), pos2(100.0, 100.0)).with_min_x(x);
|
||||
|
||||
for pixels_per_point in [0.618, 1.0, std::f32::consts::PI] {
|
||||
let left = ViewportInPixels::from_points(&left, pixels_per_point, [100, 100]);
|
||||
let right = ViewportInPixels::from_points(&right, pixels_per_point, [100, 100]);
|
||||
assert_eq!(left.left_px + left.width_px, right.left_px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaintCallbackInfo {
|
||||
/// The viewport rectangle. This is what you would use in e.g. `glViewport`.
|
||||
pub fn viewport_in_pixels(&self) -> ViewportInPixels {
|
||||
ViewportInPixels::from_points(&self.viewport, self.pixels_per_point, self.screen_size_px)
|
||||
}
|
||||
|
||||
/// The "scissor" or "clip" rectangle. This is what you would use in e.g. `glScissor`.
|
||||
pub fn clip_rect_in_pixels(&self) -> ViewportInPixels {
|
||||
ViewportInPixels::from_points(&self.clip_rect, self.pixels_per_point, self.screen_size_px)
|
||||
}
|
||||
}
|
||||
|
||||
/// If you want to paint some 3D shapes inside an egui region, you can use this.
|
||||
///
|
||||
/// This is advanced usage, and is backend specific.
|
||||
#[derive(Clone)]
|
||||
pub struct PaintCallback {
|
||||
/// Where to paint.
|
||||
///
|
||||
/// This will become [`PaintCallbackInfo::viewport`].
|
||||
pub rect: Rect,
|
||||
|
||||
/// Paint something custom (e.g. 3D stuff).
|
||||
///
|
||||
/// The concrete value of `callback` depends on the rendering backend used. For instance, the
|
||||
/// `glow` backend requires that callback be an `egui_glow::CallbackFn` while the `wgpu`
|
||||
/// backend requires a `egui_wgpu::Callback`.
|
||||
///
|
||||
/// If the type cannot be downcast to the type expected by the current backend the callback
|
||||
/// will not be drawn.
|
||||
///
|
||||
/// The rendering backend is responsible for first setting the active viewport to
|
||||
/// [`Self::rect`].
|
||||
///
|
||||
/// The rendering backend is also responsible for restoring any state, such as the bound shader
|
||||
/// program, vertex array, etc.
|
||||
///
|
||||
/// Shape has to be clone, therefore this has to be an `Arc` instead of a `Box`.
|
||||
pub callback: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PaintCallback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CustomShape")
|
||||
.field("rect", &self.rect)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq for PaintCallback {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.rect.eq(&other.rect) && Arc::ptr_eq(&self.callback, &other.callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaintCallback> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: PaintCallback) -> Self {
|
||||
Self::Callback(shape)
|
||||
}
|
||||
}
|
||||
81
crates/epaint/src/shapes/path_shape.rs
Normal file
81
crates/epaint/src/shapes/path_shape.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use crate::*;
|
||||
|
||||
/// A path which can be stroked and/or filled (if closed).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct PathShape {
|
||||
/// Filled paths should prefer clockwise order.
|
||||
pub points: Vec<Pos2>,
|
||||
|
||||
/// If true, connect the first and last of the points together.
|
||||
/// This is required if `fill != TRANSPARENT`.
|
||||
pub closed: bool,
|
||||
|
||||
/// Fill is only supported for convex polygons.
|
||||
pub fill: Color32,
|
||||
|
||||
/// Color and thickness of the line.
|
||||
pub stroke: PathStroke,
|
||||
// TODO(emilk): Add texture support either by supplying uv for each point,
|
||||
// or by some transform from points to uv (e.g. a callback or a linear transform matrix).
|
||||
}
|
||||
|
||||
impl PathShape {
|
||||
/// A line through many points.
|
||||
///
|
||||
/// Use [`Shape::line_segment`] instead if your line only connects two points.
|
||||
#[inline]
|
||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||
Self {
|
||||
points,
|
||||
closed: false,
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A line that closes back to the start point again.
|
||||
#[inline]
|
||||
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||
Self {
|
||||
points,
|
||||
closed: true,
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A convex polygon with a fill and optional stroke.
|
||||
///
|
||||
/// The most performant winding order is clockwise.
|
||||
#[inline]
|
||||
pub fn convex_polygon(
|
||||
points: Vec<Pos2>,
|
||||
fill: impl Into<Color32>,
|
||||
stroke: impl Into<PathStroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
points,
|
||||
closed: true,
|
||||
fill: fill.into(),
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke width)
|
||||
#[inline]
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
||||
Rect::NOTHING
|
||||
} else {
|
||||
Rect::from_points(&self.points).expand(self.stroke.width / 2.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathShape> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: PathShape) -> Self {
|
||||
Self::Path(shape)
|
||||
}
|
||||
}
|
||||
135
crates/epaint/src/shapes/rect_shape.rs
Normal file
135
crates/epaint/src/shapes/rect_shape.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// How to paint a rectangle.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct RectShape {
|
||||
pub rect: Rect,
|
||||
|
||||
/// How rounded the corners are. Use `Rounding::ZERO` for no rounding.
|
||||
pub rounding: Rounding,
|
||||
|
||||
/// How to fill the rectangle.
|
||||
pub fill: Color32,
|
||||
|
||||
/// The thickness and color of the outline.
|
||||
///
|
||||
/// The stroke extends _outside_ the edge of [`Self::rect`],
|
||||
/// i.e. using [`crate::StrokeKind::Outside`].
|
||||
///
|
||||
/// This means the [`Self::visual_bounding_rect`] is `rect.size() + 2.0 * stroke.width`.
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// If larger than zero, the edges of the rectangle
|
||||
/// (for both fill and stroke) will be blurred.
|
||||
///
|
||||
/// This can be used to produce shadows and glow effects.
|
||||
///
|
||||
/// The blur is currently implemented using a simple linear blur in sRGBA gamma space.
|
||||
pub blur_width: f32,
|
||||
|
||||
/// Controls texturing, if any.
|
||||
///
|
||||
/// Since most rectangles do not have a texture, this is optional and in an `Arc`,
|
||||
/// so that [`RectShape`] is kept small..
|
||||
pub brush: Option<Arc<Brush>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rect_shape_size() {
|
||||
assert_eq!(
|
||||
std::mem::size_of::<RectShape>(), 48,
|
||||
"RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
|
||||
);
|
||||
assert!(
|
||||
std::mem::size_of::<RectShape>() <= 64,
|
||||
"RectShape is getting way too big!"
|
||||
);
|
||||
}
|
||||
|
||||
impl RectShape {
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
#[inline]
|
||||
pub fn new(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
blur_width: 0.0,
|
||||
brush: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filled(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) -> Self {
|
||||
Self::new(rect, rounding, fill_color, Stroke::NONE)
|
||||
}
|
||||
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
#[inline]
|
||||
pub fn stroke(rect: Rect, rounding: impl Into<Rounding>, stroke: impl Into<Stroke>) -> Self {
|
||||
let fill = Color32::TRANSPARENT;
|
||||
Self::new(rect, rounding, fill, stroke)
|
||||
}
|
||||
|
||||
/// If larger than zero, the edges of the rectangle
|
||||
/// (for both fill and stroke) will be blurred.
|
||||
///
|
||||
/// This can be used to produce shadows and glow effects.
|
||||
///
|
||||
/// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space.
|
||||
#[inline]
|
||||
pub fn with_blur_width(mut self, blur_width: f32) -> Self {
|
||||
self.blur_width = blur_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the texture to use when painting this rectangle, if any.
|
||||
#[inline]
|
||||
pub fn with_texture(mut self, fill_texture_id: TextureId, uv: Rect) -> Self {
|
||||
self.brush = Some(Arc::new(Brush {
|
||||
fill_texture_id,
|
||||
uv,
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke width)
|
||||
#[inline]
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
||||
Rect::NOTHING
|
||||
} else {
|
||||
let Stroke { width, .. } = self.stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke`
|
||||
self.rect.expand(width + self.blur_width / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The texture to use when painting this rectangle, if any.
|
||||
///
|
||||
/// If no texture is set, this will return [`TextureId::default`].
|
||||
pub fn fill_texture_id(&self) -> TextureId {
|
||||
self.brush
|
||||
.as_ref()
|
||||
.map_or_else(TextureId::default, |brush| brush.fill_texture_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectShape> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: RectShape) -> Self {
|
||||
Self::Rect(shape)
|
||||
}
|
||||
}
|
||||
563
crates/epaint/src/shapes/shape.rs
Normal file
563
crates/epaint/src/shapes/shape.rs
Normal file
@@ -0,0 +1,563 @@
|
||||
//! The different shapes that can be painted.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2};
|
||||
|
||||
use crate::{
|
||||
stroke::PathStroke,
|
||||
text::{FontId, Fonts, Galley},
|
||||
Color32, Mesh, Rounding, Stroke, TextureId,
|
||||
};
|
||||
|
||||
use super::{
|
||||
CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PathShape, QuadraticBezierShape,
|
||||
RectShape, TextShape,
|
||||
};
|
||||
|
||||
/// A paint primitive such as a circle or a piece of text.
|
||||
/// Coordinates are all screen space points (not physical pixels).
|
||||
///
|
||||
/// You should generally recreate your [`Shape`]s each frame,
|
||||
/// but storing them should also be fine with one exception:
|
||||
/// [`Shape::Text`] depends on the current `pixels_per_point` (dpi scale)
|
||||
/// and so must be recreated every time `pixels_per_point` changes.
|
||||
#[must_use = "Add a Shape to a Painter"]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Shape {
|
||||
/// Paint nothing. This can be useful as a placeholder.
|
||||
Noop,
|
||||
|
||||
/// Recursively nest more shapes - sometimes a convenience to be able to do.
|
||||
/// For performance reasons it is better to avoid it.
|
||||
Vec(Vec<Shape>),
|
||||
|
||||
/// Circle with optional outline and fill.
|
||||
Circle(CircleShape),
|
||||
|
||||
/// Ellipse with optional outline and fill.
|
||||
Ellipse(EllipseShape),
|
||||
|
||||
/// A line between two points.
|
||||
LineSegment { points: [Pos2; 2], stroke: Stroke },
|
||||
|
||||
/// A series of lines between points.
|
||||
/// The path can have a stroke and/or fill (if closed).
|
||||
Path(PathShape),
|
||||
|
||||
/// Rectangle with optional outline and fill.
|
||||
Rect(RectShape),
|
||||
|
||||
/// Text.
|
||||
///
|
||||
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||
Text(TextShape),
|
||||
|
||||
/// A general triangle mesh.
|
||||
///
|
||||
/// Can be used to display images.
|
||||
///
|
||||
/// Wrapped in an [`Arc`] to minimize the size of [`Shape`].
|
||||
Mesh(Arc<Mesh>),
|
||||
|
||||
/// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||
QuadraticBezier(QuadraticBezierShape),
|
||||
|
||||
/// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||
CubicBezier(CubicBezierShape),
|
||||
|
||||
/// Backend-specific painting.
|
||||
Callback(PaintCallback),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_size() {
|
||||
assert_eq!(
|
||||
std::mem::size_of::<Shape>(), 64,
|
||||
"Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
|
||||
);
|
||||
assert!(
|
||||
std::mem::size_of::<Shape>() <= 64,
|
||||
"Shape is getting way too big!"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<Shape>();
|
||||
}
|
||||
|
||||
impl From<Vec<Self>> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shapes: Vec<Self>) -> Self {
|
||||
Self::Vec(shapes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mesh> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(mesh: Mesh) -> Self {
|
||||
Self::Mesh(mesh.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<Mesh>> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(mesh: Arc<Mesh>) -> Self {
|
||||
Self::Mesh(mesh)
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Constructors
|
||||
impl Shape {
|
||||
/// A line between two points.
|
||||
/// More efficient than calling [`Self::line`].
|
||||
#[inline]
|
||||
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
|
||||
Self::LineSegment {
|
||||
points,
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A horizontal line.
|
||||
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
|
||||
let x = x.into();
|
||||
Self::LineSegment {
|
||||
points: [pos2(x.min, y), pos2(x.max, y)],
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A vertical line.
|
||||
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
|
||||
let y = y.into();
|
||||
Self::LineSegment {
|
||||
points: [pos2(x, y.min), pos2(x, y.max)],
|
||||
stroke: stroke.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A line through many points.
|
||||
///
|
||||
/// Use [`Self::line_segment`] instead if your line only connects two points.
|
||||
#[inline]
|
||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||
Self::Path(PathShape::line(points, stroke))
|
||||
}
|
||||
|
||||
/// A line that closes back to the start point again.
|
||||
#[inline]
|
||||
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||
Self::Path(PathShape::closed_line(points, stroke))
|
||||
}
|
||||
|
||||
/// Turn a line into equally spaced dots.
|
||||
pub fn dotted_line(
|
||||
path: &[Pos2],
|
||||
color: impl Into<Color32>,
|
||||
spacing: f32,
|
||||
radius: f32,
|
||||
) -> Vec<Self> {
|
||||
let mut shapes = Vec::new();
|
||||
points_from_line(path, spacing, radius, color.into(), &mut shapes);
|
||||
shapes
|
||||
}
|
||||
|
||||
/// Turn a line into dashes.
|
||||
pub fn dashed_line(
|
||||
path: &[Pos2],
|
||||
stroke: impl Into<Stroke>,
|
||||
dash_length: f32,
|
||||
gap_length: f32,
|
||||
) -> Vec<Self> {
|
||||
let mut shapes = Vec::new();
|
||||
dashes_from_line(
|
||||
path,
|
||||
stroke.into(),
|
||||
&[dash_length],
|
||||
&[gap_length],
|
||||
&mut shapes,
|
||||
0.,
|
||||
);
|
||||
shapes
|
||||
}
|
||||
|
||||
/// Turn a line into dashes with different dash/gap lengths and a start offset.
|
||||
pub fn dashed_line_with_offset(
|
||||
path: &[Pos2],
|
||||
stroke: impl Into<Stroke>,
|
||||
dash_lengths: &[f32],
|
||||
gap_lengths: &[f32],
|
||||
dash_offset: f32,
|
||||
) -> Vec<Self> {
|
||||
let mut shapes = Vec::new();
|
||||
dashes_from_line(
|
||||
path,
|
||||
stroke.into(),
|
||||
dash_lengths,
|
||||
gap_lengths,
|
||||
&mut shapes,
|
||||
dash_offset,
|
||||
);
|
||||
shapes
|
||||
}
|
||||
|
||||
/// Turn a line into dashes. If you need to create many dashed lines use this instead of
|
||||
/// [`Self::dashed_line`].
|
||||
pub fn dashed_line_many(
|
||||
points: &[Pos2],
|
||||
stroke: impl Into<Stroke>,
|
||||
dash_length: f32,
|
||||
gap_length: f32,
|
||||
shapes: &mut Vec<Self>,
|
||||
) {
|
||||
dashes_from_line(
|
||||
points,
|
||||
stroke.into(),
|
||||
&[dash_length],
|
||||
&[gap_length],
|
||||
shapes,
|
||||
0.,
|
||||
);
|
||||
}
|
||||
|
||||
/// Turn a line into dashes with different dash/gap lengths and a start offset. If you need to
|
||||
/// create many dashed lines use this instead of [`Self::dashed_line_with_offset`].
|
||||
pub fn dashed_line_many_with_offset(
|
||||
points: &[Pos2],
|
||||
stroke: impl Into<Stroke>,
|
||||
dash_lengths: &[f32],
|
||||
gap_lengths: &[f32],
|
||||
dash_offset: f32,
|
||||
shapes: &mut Vec<Self>,
|
||||
) {
|
||||
dashes_from_line(
|
||||
points,
|
||||
stroke.into(),
|
||||
dash_lengths,
|
||||
gap_lengths,
|
||||
shapes,
|
||||
dash_offset,
|
||||
);
|
||||
}
|
||||
|
||||
/// A convex polygon with a fill and optional stroke.
|
||||
///
|
||||
/// The most performant winding order is clockwise.
|
||||
#[inline]
|
||||
pub fn convex_polygon(
|
||||
points: Vec<Pos2>,
|
||||
fill: impl Into<Color32>,
|
||||
stroke: impl Into<PathStroke>,
|
||||
) -> Self {
|
||||
Self::Path(PathShape::convex_polygon(points, fill, stroke))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
|
||||
Self::Circle(CircleShape::filled(center, radius, fill_color))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||
Self::Circle(CircleShape::stroke(center, radius, stroke))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ellipse_filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
|
||||
Self::Ellipse(EllipseShape::filled(center, radius, fill_color))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ellipse_stroke(center: Pos2, radius: Vec2, stroke: impl Into<Stroke>) -> Self {
|
||||
Self::Ellipse(EllipseShape::stroke(center, radius, stroke))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rect_filled(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) -> Self {
|
||||
Self::Rect(RectShape::filled(rect, rounding, fill_color))
|
||||
}
|
||||
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
#[inline]
|
||||
pub fn rect_stroke(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) -> Self {
|
||||
Self::Rect(RectShape::stroke(rect, rounding, stroke))
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn text(
|
||||
fonts: &Fonts,
|
||||
pos: Pos2,
|
||||
anchor: Align2,
|
||||
text: impl ToString,
|
||||
font_id: FontId,
|
||||
color: Color32,
|
||||
) -> Self {
|
||||
let galley = fonts.layout_no_wrap(text.to_string(), font_id, color);
|
||||
let rect = anchor.anchor_size(pos, galley.size());
|
||||
Self::galley(rect.min, galley, color)
|
||||
}
|
||||
|
||||
/// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color.
|
||||
///
|
||||
/// Any non-placeholder color in the galley takes precedence over this fallback color.
|
||||
#[inline]
|
||||
pub fn galley(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
|
||||
TextShape::new(pos, galley, fallback_color).into()
|
||||
}
|
||||
|
||||
/// All text color in the [`Galley`] will be replaced with the given color.
|
||||
#[inline]
|
||||
pub fn galley_with_override_text_color(
|
||||
pos: Pos2,
|
||||
galley: Arc<Galley>,
|
||||
text_color: Color32,
|
||||
) -> Self {
|
||||
TextShape::new(pos, galley, text_color)
|
||||
.with_override_text_color(text_color)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"]
|
||||
pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
|
||||
Self::galley_with_override_text_color(pos, galley, text_color)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
|
||||
let mesh = mesh.into();
|
||||
debug_assert!(mesh.is_valid());
|
||||
Self::Mesh(mesh)
|
||||
}
|
||||
|
||||
/// An image at the given position.
|
||||
///
|
||||
/// `uv` should normally be `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`
|
||||
/// unless you want to crop or flip the image.
|
||||
///
|
||||
/// `tint` is a color multiplier. Use [`Color32::WHITE`] if you don't want to tint the image.
|
||||
pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
|
||||
let mut mesh = Mesh::with_texture(texture_id);
|
||||
mesh.add_rect_with_uv(rect, uv, tint);
|
||||
Self::mesh(mesh)
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke widths)
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
match self {
|
||||
Self::Noop => Rect::NOTHING,
|
||||
Self::Vec(shapes) => {
|
||||
let mut rect = Rect::NOTHING;
|
||||
for shape in shapes {
|
||||
rect = rect.union(shape.visual_bounding_rect());
|
||||
}
|
||||
rect
|
||||
}
|
||||
Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
|
||||
Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
|
||||
Self::LineSegment { points, stroke } => {
|
||||
if stroke.is_empty() {
|
||||
Rect::NOTHING
|
||||
} else {
|
||||
Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
|
||||
}
|
||||
}
|
||||
Self::Path(path_shape) => path_shape.visual_bounding_rect(),
|
||||
Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
|
||||
Self::Text(text_shape) => text_shape.visual_bounding_rect(),
|
||||
Self::Mesh(mesh) => mesh.calc_bounds(),
|
||||
Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
|
||||
Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
|
||||
Self::Callback(custom) => custom.rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Inspection and transforms
|
||||
impl Shape {
|
||||
#[inline(always)]
|
||||
pub fn texture_id(&self) -> crate::TextureId {
|
||||
if let Self::Mesh(mesh) = self {
|
||||
mesh.texture_id
|
||||
} else if let Self::Rect(rect_shape) = self {
|
||||
rect_shape.fill_texture_id()
|
||||
} else {
|
||||
crate::TextureId::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Scale the shape by `factor`, in-place.
|
||||
///
|
||||
/// A wrapper around [`Self::transform`].
|
||||
#[inline(always)]
|
||||
pub fn scale(&mut self, factor: f32) {
|
||||
self.transform(TSTransform::from_scaling(factor));
|
||||
}
|
||||
|
||||
/// Move the shape by `delta`, in-place.
|
||||
///
|
||||
/// A wrapper around [`Self::transform`].
|
||||
#[inline(always)]
|
||||
pub fn translate(&mut self, delta: Vec2) {
|
||||
self.transform(TSTransform::from_translation(delta));
|
||||
}
|
||||
|
||||
/// Move the shape by this many points, in-place.
|
||||
///
|
||||
/// If using a [`PaintCallback`], note that only the rect is scaled as opposed
|
||||
/// to other shapes where the stroke is also scaled.
|
||||
pub fn transform(&mut self, transform: TSTransform) {
|
||||
match self {
|
||||
Self::Noop => {}
|
||||
Self::Vec(shapes) => {
|
||||
for shape in shapes {
|
||||
shape.transform(transform);
|
||||
}
|
||||
}
|
||||
Self::Circle(circle_shape) => {
|
||||
circle_shape.center = transform * circle_shape.center;
|
||||
circle_shape.radius *= transform.scaling;
|
||||
circle_shape.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::Ellipse(ellipse_shape) => {
|
||||
ellipse_shape.center = transform * ellipse_shape.center;
|
||||
ellipse_shape.radius *= transform.scaling;
|
||||
ellipse_shape.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::LineSegment { points, stroke } => {
|
||||
for p in points {
|
||||
*p = transform * *p;
|
||||
}
|
||||
stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::Path(path_shape) => {
|
||||
for p in &mut path_shape.points {
|
||||
*p = transform * *p;
|
||||
}
|
||||
path_shape.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::Rect(rect_shape) => {
|
||||
rect_shape.rect = transform * rect_shape.rect;
|
||||
rect_shape.stroke.width *= transform.scaling;
|
||||
rect_shape.rounding *= transform.scaling;
|
||||
}
|
||||
Self::Text(text_shape) => {
|
||||
text_shape.pos = transform * text_shape.pos;
|
||||
|
||||
// Scale text:
|
||||
let galley = Arc::make_mut(&mut text_shape.galley);
|
||||
for placed_row in &mut galley.rows {
|
||||
let row = Arc::make_mut(&mut placed_row.row);
|
||||
row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds;
|
||||
for v in &mut row.visuals.mesh.vertices {
|
||||
v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
galley.mesh_bounds = transform.scaling * galley.mesh_bounds;
|
||||
galley.rect = transform.scaling * galley.rect;
|
||||
}
|
||||
Self::Mesh(mesh) => {
|
||||
Arc::make_mut(mesh).transform(transform);
|
||||
}
|
||||
Self::QuadraticBezier(bezier_shape) => {
|
||||
bezier_shape.points[0] = transform * bezier_shape.points[0];
|
||||
bezier_shape.points[1] = transform * bezier_shape.points[1];
|
||||
bezier_shape.points[2] = transform * bezier_shape.points[2];
|
||||
bezier_shape.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::CubicBezier(cubic_curve) => {
|
||||
for p in &mut cubic_curve.points {
|
||||
*p = transform * *p;
|
||||
}
|
||||
cubic_curve.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::Callback(shape) => {
|
||||
shape.rect = transform * shape.rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Creates equally spaced filled circles from a line.
|
||||
fn points_from_line(
|
||||
path: &[Pos2],
|
||||
spacing: f32,
|
||||
radius: f32,
|
||||
color: Color32,
|
||||
shapes: &mut Vec<Shape>,
|
||||
) {
|
||||
let mut position_on_segment = 0.0;
|
||||
path.windows(2).for_each(|window| {
|
||||
let (start, end) = (window[0], window[1]);
|
||||
let vector = end - start;
|
||||
let segment_length = vector.length();
|
||||
while position_on_segment < segment_length {
|
||||
let new_point = start + vector * (position_on_segment / segment_length);
|
||||
shapes.push(Shape::circle_filled(new_point, radius, color));
|
||||
position_on_segment += spacing;
|
||||
}
|
||||
position_on_segment -= segment_length;
|
||||
});
|
||||
}
|
||||
|
||||
/// Creates dashes from a line.
|
||||
fn dashes_from_line(
|
||||
path: &[Pos2],
|
||||
stroke: Stroke,
|
||||
dash_lengths: &[f32],
|
||||
gap_lengths: &[f32],
|
||||
shapes: &mut Vec<Shape>,
|
||||
dash_offset: f32,
|
||||
) {
|
||||
assert_eq!(dash_lengths.len(), gap_lengths.len());
|
||||
let mut position_on_segment = dash_offset;
|
||||
let mut drawing_dash = false;
|
||||
let mut step = 0;
|
||||
let steps = dash_lengths.len();
|
||||
path.windows(2).for_each(|window| {
|
||||
let (start, end) = (window[0], window[1]);
|
||||
let vector = end - start;
|
||||
let segment_length = vector.length();
|
||||
|
||||
let mut start_point = start;
|
||||
while position_on_segment < segment_length {
|
||||
let new_point = start + vector * (position_on_segment / segment_length);
|
||||
if drawing_dash {
|
||||
// This is the end point.
|
||||
shapes.push(Shape::line_segment([start_point, new_point], stroke));
|
||||
position_on_segment += gap_lengths[step];
|
||||
// Increment step counter
|
||||
step += 1;
|
||||
if step >= steps {
|
||||
step = 0;
|
||||
}
|
||||
} else {
|
||||
// Start a new dash.
|
||||
start_point = new_point;
|
||||
position_on_segment += dash_lengths[step];
|
||||
}
|
||||
drawing_dash = !drawing_dash;
|
||||
}
|
||||
|
||||
// If the segment ends and the dash is not finished, add the segment's end point.
|
||||
if drawing_dash {
|
||||
shapes.push(Shape::line_segment([start_point, end], stroke));
|
||||
}
|
||||
|
||||
position_on_segment -= segment_length;
|
||||
});
|
||||
}
|
||||
97
crates/epaint/src/shapes/text_shape.rs
Normal file
97
crates/epaint/src/shapes/text_shape.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// How to paint some text on screen.
|
||||
///
|
||||
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct TextShape {
|
||||
/// Top left corner of the first character.
|
||||
pub pos: Pos2,
|
||||
|
||||
/// The laid out text, from [`Fonts::layout_job`].
|
||||
pub galley: Arc<Galley>,
|
||||
|
||||
/// Add this underline to the whole text.
|
||||
/// You can also set an underline when creating the galley.
|
||||
pub underline: Stroke,
|
||||
|
||||
/// Any [`Color32::PLACEHOLDER`] in the galley will be replaced by the given color.
|
||||
/// Affects everything: backgrounds, glyphs, strikethrough, underline, etc.
|
||||
pub fallback_color: Color32,
|
||||
|
||||
/// If set, the text color in the galley will be ignored and replaced
|
||||
/// with the given color.
|
||||
///
|
||||
/// This only affects the glyphs and will NOT replace background color nor strikethrough/underline color.
|
||||
pub override_text_color: Option<Color32>,
|
||||
|
||||
/// If set, the text will be rendered with the given opacity in gamma space
|
||||
/// Affects everything: backgrounds, glyphs, strikethrough, underline, etc.
|
||||
pub opacity_factor: f32,
|
||||
|
||||
/// Rotate text by this many radians clockwise.
|
||||
/// The pivot is `pos` (the upper left corner of the text).
|
||||
pub angle: f32,
|
||||
}
|
||||
|
||||
impl TextShape {
|
||||
/// The given fallback color will be used for any uncolored part of the galley (using [`Color32::PLACEHOLDER`]).
|
||||
///
|
||||
/// Any non-placeholder color in the galley takes precedence over this fallback color.
|
||||
#[inline]
|
||||
pub fn new(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
galley,
|
||||
underline: Stroke::NONE,
|
||||
fallback_color,
|
||||
override_text_color: None,
|
||||
opacity_factor: 1.0,
|
||||
angle: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle
|
||||
#[inline]
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
self.galley.mesh_bounds.translate(self.pos.to_vec2())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_underline(mut self, underline: Stroke) -> Self {
|
||||
self.underline = underline;
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the given color for the text, regardless of what color is already in the galley.
|
||||
#[inline]
|
||||
pub fn with_override_text_color(mut self, override_text_color: Color32) -> Self {
|
||||
self.override_text_color = Some(override_text_color);
|
||||
self
|
||||
}
|
||||
|
||||
/// Rotate text by this many radians clockwise.
|
||||
/// The pivot is `pos` (the upper left corner of the text).
|
||||
#[inline]
|
||||
pub fn with_angle(mut self, angle: f32) -> Self {
|
||||
self.angle = angle;
|
||||
self
|
||||
}
|
||||
|
||||
/// Render text with this opacity in gamma space
|
||||
#[inline]
|
||||
pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self {
|
||||
self.opacity_factor = opacity_factor;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextShape> for Shape {
|
||||
#[inline(always)]
|
||||
fn from(shape: TextShape) -> Self {
|
||||
Self::Text(shape)
|
||||
}
|
||||
}
|
||||
@@ -525,7 +525,7 @@ impl Path {
|
||||
|
||||
pub mod path {
|
||||
//! Helpers for constructing paths
|
||||
use crate::shape::Rounding;
|
||||
use crate::Rounding;
|
||||
use emath::{pos2, Pos2, Rect};
|
||||
|
||||
/// overwrites existing points
|
||||
@@ -548,6 +548,8 @@ pub mod path {
|
||||
// Duplicated vertices can happen when one side is all rounding, with no straight edge between.
|
||||
let eps = f32::EPSILON * rect.size().max_elem();
|
||||
|
||||
let r = crate::Roundingf::from(r);
|
||||
|
||||
add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); // south east
|
||||
|
||||
if rect.width() <= r.se + r.sw + eps {
|
||||
@@ -628,7 +630,7 @@ pub mod path {
|
||||
let half_width = rect.width() * 0.5;
|
||||
let half_height = rect.height() * 0.5;
|
||||
let max_cr = half_width.min(half_height);
|
||||
rounding.at_most(max_cr).at_least(0.0)
|
||||
rounding.at_most(max_cr.floor() as _).at_least(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1289,29 +1291,6 @@ impl Tessellator {
|
||||
self.clip_rect = clip_rect;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel_center(&self, point: f32) -> f32 {
|
||||
((point * self.pixels_per_point - 0.5).round() + 0.5) / self.pixels_per_point
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_pos_to_pixel(&self, pos: Pos2) -> Pos2 {
|
||||
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_pos_to_pixel_center(&self, pos: Pos2) -> Pos2 {
|
||||
pos2(
|
||||
self.round_to_pixel_center(pos.x),
|
||||
self.round_to_pixel_center(pos.y),
|
||||
)
|
||||
}
|
||||
|
||||
/// Tessellate a clipped shape into a list of primitives.
|
||||
pub fn tessellate_clipped_shape(
|
||||
&mut self,
|
||||
@@ -1404,7 +1383,7 @@ impl Tessellator {
|
||||
return;
|
||||
}
|
||||
|
||||
out.append(mesh);
|
||||
out.append_ref(&mesh);
|
||||
}
|
||||
Shape::LineSegment { points, stroke } => {
|
||||
self.tessellate_line_segment(points, stroke, out);
|
||||
@@ -1691,14 +1670,14 @@ impl Tessellator {
|
||||
/// * `rect`: the rectangle to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||
let brush = rect.brush.as_ref();
|
||||
let RectShape {
|
||||
mut rect,
|
||||
mut rounding,
|
||||
fill,
|
||||
stroke,
|
||||
mut blur_width,
|
||||
fill_texture_id,
|
||||
uv,
|
||||
..
|
||||
} = *rect;
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
@@ -1714,8 +1693,16 @@ impl Tessellator {
|
||||
// Since the stroke extends outside of the rectangle,
|
||||
// we can round the rectangle sides to the physical pixel edges,
|
||||
// and the filled rect will appear crisp, as will the inside of the stroke.
|
||||
let Stroke { .. } = stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke`
|
||||
rect = rect.round_to_pixels(self.pixels_per_point);
|
||||
let Stroke { width, .. } = stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke`
|
||||
if width <= self.feathering && !stroke.is_empty() {
|
||||
// If the stroke is thin, make sure its center is in the center of the pixel:
|
||||
rect = rect
|
||||
.expand(width / 2.0)
|
||||
.round_to_pixel_center(self.pixels_per_point)
|
||||
.shrink(width / 2.0);
|
||||
} else {
|
||||
rect = rect.round_to_pixels(self.pixels_per_point);
|
||||
}
|
||||
}
|
||||
|
||||
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
||||
@@ -1725,7 +1712,7 @@ impl Tessellator {
|
||||
|
||||
let old_feathering = self.feathering;
|
||||
|
||||
if old_feathering < blur_width {
|
||||
if self.feathering < blur_width {
|
||||
// We accomplish the blur by using a larger-than-normal feathering.
|
||||
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
|
||||
|
||||
@@ -1741,7 +1728,7 @@ impl Tessellator {
|
||||
.at_most(rect.size().min_elem() - eps)
|
||||
.at_least(0.0);
|
||||
|
||||
rounding += Rounding::same(0.5 * blur_width);
|
||||
rounding += Rounding::from(0.5 * blur_width);
|
||||
|
||||
self.feathering = self.feathering.max(blur_width);
|
||||
}
|
||||
@@ -1773,7 +1760,11 @@ impl Tessellator {
|
||||
path.add_line_loop(&self.scratchpad_points);
|
||||
let path_stroke = PathStroke::from(stroke).outside();
|
||||
|
||||
if uv.is_positive() {
|
||||
if let Some(brush) = brush {
|
||||
let crate::Brush {
|
||||
fill_texture_id,
|
||||
uv,
|
||||
} = **brush;
|
||||
// Textured
|
||||
let uv_from_pos = |p: Pos2| {
|
||||
pos2(
|
||||
@@ -1830,10 +1821,7 @@ impl Tessellator {
|
||||
// The contents of the galley are already snapped to pixel coordinates,
|
||||
// but we need to make sure the galley ends up on the start of a physical pixel:
|
||||
let galley_pos = if self.options.round_text_to_pixels {
|
||||
pos2(
|
||||
self.round_to_pixel(galley_pos.x),
|
||||
self.round_to_pixel(galley_pos.y),
|
||||
)
|
||||
galley_pos.round_to_pixels(self.pixels_per_point)
|
||||
} else {
|
||||
*galley_pos
|
||||
};
|
||||
@@ -1913,13 +1901,11 @@ impl Tessellator {
|
||||
);
|
||||
|
||||
if *underline != Stroke::NONE {
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path.add_line_segment([
|
||||
self.round_pos_to_pixel_center(row_rect.left_bottom()),
|
||||
self.round_pos_to_pixel_center(row_rect.right_bottom()),
|
||||
]);
|
||||
self.scratchpad_path
|
||||
.stroke_open(0.0, &PathStroke::from(*underline), out);
|
||||
self.tessellate_line_segment(
|
||||
[row_rect.left_bottom(), row_rect.right_bottom()],
|
||||
*underline,
|
||||
out,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2173,7 +2159,7 @@ impl Tessellator {
|
||||
|
||||
profiling::scope!("distribute results", tessellated.len().to_string());
|
||||
for (index, mesh) in tessellated {
|
||||
shapes[index].shape = Shape::Mesh(mesh);
|
||||
shapes[index].shape = Shape::Mesh(mesh.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
54
crates/epaint/src/viewport.rs
Normal file
54
crates/epaint/src/viewport.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::Rect;
|
||||
|
||||
/// Size of the viewport in whole, physical pixels.
|
||||
pub struct ViewportInPixels {
|
||||
/// Physical pixel offset for left side of the viewport.
|
||||
pub left_px: i32,
|
||||
|
||||
/// Physical pixel offset for top side of the viewport.
|
||||
pub top_px: i32,
|
||||
|
||||
/// Physical pixel offset for bottom side of the viewport.
|
||||
///
|
||||
/// This is what `glViewport`, `glScissor` etc expects for the y axis.
|
||||
pub from_bottom_px: i32,
|
||||
|
||||
/// Viewport width in physical pixels.
|
||||
pub width_px: i32,
|
||||
|
||||
/// Viewport height in physical pixels.
|
||||
pub height_px: i32,
|
||||
}
|
||||
|
||||
impl ViewportInPixels {
|
||||
/// Convert from ui points.
|
||||
pub fn from_points(rect: &Rect, pixels_per_point: f32, screen_size_px: [u32; 2]) -> Self {
|
||||
// Fractional pixel values for viewports are generally valid, but may cause sampling issues
|
||||
// and rounding errors might cause us to get out of bounds.
|
||||
|
||||
// Round:
|
||||
let left_px = (pixels_per_point * rect.min.x).round() as i32; // inclusive
|
||||
let top_px = (pixels_per_point * rect.min.y).round() as i32; // inclusive
|
||||
let right_px = (pixels_per_point * rect.max.x).round() as i32; // exclusive
|
||||
let bottom_px = (pixels_per_point * rect.max.y).round() as i32; // exclusive
|
||||
|
||||
// Clamp to screen:
|
||||
let screen_width = screen_size_px[0] as i32;
|
||||
let screen_height = screen_size_px[1] as i32;
|
||||
let left_px = left_px.clamp(0, screen_width);
|
||||
let right_px = right_px.clamp(left_px, screen_width);
|
||||
let top_px = top_px.clamp(0, screen_height);
|
||||
let bottom_px = bottom_px.clamp(top_px, screen_height);
|
||||
|
||||
let width_px = right_px - left_px;
|
||||
let height_px = bottom_px - top_px;
|
||||
|
||||
Self {
|
||||
left_px,
|
||||
top_px,
|
||||
from_bottom_px: screen_height - height_px - top_px,
|
||||
width_px,
|
||||
height_px,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user