mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
Merge branch 'master' into cache_galley_lines
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
/// How rounded the corners of things should be.
|
||||
///
|
||||
/// This specific the _corner radius_ of the underlying geometric shape (e.g. rectangle).
|
||||
/// If there is a stroke, then the stroke will have an inner and outer corner radius
|
||||
/// which will depends on its width and [`crate::StrokeKind`].
|
||||
///
|
||||
/// 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`.
|
||||
/// For calculations, you may want to use [`crate::CornerRadiusF32`] instead, which uses `f32`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Rounding {
|
||||
pub struct CornerRadius {
|
||||
/// Radius of the rounding of the North-West (left top) corner.
|
||||
pub nw: u8,
|
||||
|
||||
@@ -20,28 +24,28 @@ pub struct Rounding {
|
||||
pub se: u8,
|
||||
}
|
||||
|
||||
impl Default for Rounding {
|
||||
impl Default for CornerRadius {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Rounding {
|
||||
impl From<u8> for CornerRadius {
|
||||
#[inline]
|
||||
fn from(radius: u8) -> Self {
|
||||
Self::same(radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Rounding {
|
||||
impl From<f32> for CornerRadius {
|
||||
#[inline]
|
||||
fn from(radius: f32) -> Self {
|
||||
Self::same(radius.round() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rounding {
|
||||
impl CornerRadius {
|
||||
/// No rounding on any corner.
|
||||
pub const ZERO: Self = Self {
|
||||
nw: 0,
|
||||
@@ -95,32 +99,45 @@ impl Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Rounding {
|
||||
impl std::ops::Add for CornerRadius {
|
||||
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,
|
||||
nw: self.nw.saturating_add(rhs.nw),
|
||||
ne: self.ne.saturating_add(rhs.ne),
|
||||
sw: self.sw.saturating_add(rhs.sw),
|
||||
se: self.se.saturating_add(rhs.se),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Rounding {
|
||||
impl std::ops::Add<u8> for CornerRadius {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn add(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::AddAssign for CornerRadius {
|
||||
#[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,
|
||||
nw: self.nw.saturating_add(rhs.nw),
|
||||
ne: self.ne.saturating_add(rhs.ne),
|
||||
sw: self.sw.saturating_add(rhs.sw),
|
||||
se: self.se.saturating_add(rhs.se),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<u8> for Rounding {
|
||||
impl std::ops::AddAssign<u8> for CornerRadius {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: u8) {
|
||||
*self = Self {
|
||||
@@ -132,7 +149,7 @@ impl std::ops::AddAssign<u8> for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Rounding {
|
||||
impl std::ops::Sub for CornerRadius {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
@@ -145,7 +162,20 @@ impl std::ops::Sub for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign for Rounding {
|
||||
impl std::ops::Sub<u8> for CornerRadius {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn sub(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::SubAssign for CornerRadius {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
@@ -157,7 +187,7 @@ impl std::ops::SubAssign for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<u8> for Rounding {
|
||||
impl std::ops::SubAssign<u8> for CornerRadius {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: u8) {
|
||||
*self = Self {
|
||||
@@ -169,7 +199,7 @@ impl std::ops::SubAssign<u8> for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f32> for Rounding {
|
||||
impl std::ops::Div<f32> for CornerRadius {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn div(self, rhs: f32) -> Self {
|
||||
@@ -182,7 +212,7 @@ impl std::ops::Div<f32> for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DivAssign<f32> for Rounding {
|
||||
impl std::ops::DivAssign<f32> for CornerRadius {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
@@ -194,7 +224,7 @@ impl std::ops::DivAssign<f32> for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Rounding {
|
||||
impl std::ops::Mul<f32> for CornerRadius {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
@@ -207,7 +237,7 @@ impl std::ops::Mul<f32> for Rounding {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::MulAssign<f32> for Rounding {
|
||||
impl std::ops::MulAssign<f32> for CornerRadius {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::Rounding;
|
||||
use crate::CornerRadius;
|
||||
|
||||
/// 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`].
|
||||
/// This is used for calculations, but storage is usually done with the more compact [`CornerRadius`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Roundingf {
|
||||
pub struct CornerRadiusF32 {
|
||||
/// Radius of the rounding of the North-West (left top) corner.
|
||||
pub nw: f32,
|
||||
|
||||
@@ -19,38 +19,38 @@ pub struct Roundingf {
|
||||
pub se: f32,
|
||||
}
|
||||
|
||||
impl From<Rounding> for Roundingf {
|
||||
impl From<CornerRadius> for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn from(rounding: Rounding) -> Self {
|
||||
fn from(cr: CornerRadius) -> Self {
|
||||
Self {
|
||||
nw: rounding.nw as f32,
|
||||
ne: rounding.ne as f32,
|
||||
sw: rounding.sw as f32,
|
||||
se: rounding.se as f32,
|
||||
nw: cr.nw as f32,
|
||||
ne: cr.ne as f32,
|
||||
sw: cr.sw as f32,
|
||||
se: cr.se as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Roundingf> for Rounding {
|
||||
impl From<CornerRadiusF32> for CornerRadius {
|
||||
#[inline]
|
||||
fn from(rounding: Roundingf) -> Self {
|
||||
fn from(cr: CornerRadiusF32) -> 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,
|
||||
nw: cr.nw.round() as u8,
|
||||
ne: cr.ne.round() as u8,
|
||||
sw: cr.sw.round() as u8,
|
||||
se: cr.se.round() as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Roundingf {
|
||||
impl Default for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Roundingf {
|
||||
impl From<f32> for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn from(radius: f32) -> Self {
|
||||
Self {
|
||||
@@ -62,7 +62,7 @@ impl From<f32> for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl Roundingf {
|
||||
impl CornerRadiusF32 {
|
||||
/// No rounding on any corner.
|
||||
pub const ZERO: Self = Self {
|
||||
nw: 0.0,
|
||||
@@ -111,7 +111,7 @@ impl Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Roundingf {
|
||||
impl std::ops::Add for CornerRadiusF32 {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
@@ -124,7 +124,7 @@ impl std::ops::Add for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Roundingf {
|
||||
impl std::ops::AddAssign for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
@@ -136,7 +136,7 @@ impl std::ops::AddAssign for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<f32> for Roundingf {
|
||||
impl std::ops::AddAssign<f32> for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
@@ -148,7 +148,7 @@ impl std::ops::AddAssign<f32> for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Roundingf {
|
||||
impl std::ops::Sub for CornerRadiusF32 {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
@@ -161,7 +161,7 @@ impl std::ops::Sub for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign for Roundingf {
|
||||
impl std::ops::SubAssign for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Self {
|
||||
@@ -173,7 +173,7 @@ impl std::ops::SubAssign for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<f32> for Roundingf {
|
||||
impl std::ops::SubAssign<f32> for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
@@ -185,7 +185,7 @@ impl std::ops::SubAssign<f32> for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f32> for Roundingf {
|
||||
impl std::ops::Div<f32> for CornerRadiusF32 {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn div(self, rhs: f32) -> Self {
|
||||
@@ -198,7 +198,7 @@ impl std::ops::Div<f32> for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DivAssign<f32> for Roundingf {
|
||||
impl std::ops::DivAssign<f32> for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
@@ -210,7 +210,7 @@ impl std::ops::DivAssign<f32> for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Roundingf {
|
||||
impl std::ops::Mul<f32> for CornerRadiusF32 {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
@@ -223,7 +223,7 @@ impl std::ops::Mul<f32> for Roundingf {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::MulAssign<f32> for Roundingf {
|
||||
impl std::ops::MulAssign<f32> for CornerRadiusF32 {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
*self = Self {
|
||||
@@ -94,7 +94,13 @@ impl ColorImage {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
|
||||
assert_eq!(size[0] * size[1] * 4, rgba.len());
|
||||
assert_eq!(
|
||||
size[0] * size[1] * 4,
|
||||
rgba.len(),
|
||||
"size: {:?}, rgba.len(): {}",
|
||||
size,
|
||||
rgba.len()
|
||||
);
|
||||
let pixels = rgba
|
||||
.chunks_exact(4)
|
||||
.map(|p| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
@@ -103,7 +109,13 @@ impl ColorImage {
|
||||
}
|
||||
|
||||
pub fn from_rgba_premultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
|
||||
assert_eq!(size[0] * size[1] * 4, rgba.len());
|
||||
assert_eq!(
|
||||
size[0] * size[1] * 4,
|
||||
rgba.len(),
|
||||
"size: {:?}, rgba.len(): {}",
|
||||
size,
|
||||
rgba.len()
|
||||
);
|
||||
let pixels = rgba
|
||||
.chunks_exact(4)
|
||||
.map(|p| Color32::from_rgba_premultiplied(p[0], p[1], p[2], p[3]))
|
||||
@@ -115,7 +127,13 @@ impl ColorImage {
|
||||
///
|
||||
/// Panics if `size[0] * size[1] != gray.len()`.
|
||||
pub fn from_gray(size: [usize; 2], gray: &[u8]) -> Self {
|
||||
assert_eq!(size[0] * size[1], gray.len());
|
||||
assert_eq!(
|
||||
size[0] * size[1],
|
||||
gray.len(),
|
||||
"size: {:?}, gray.len(): {}",
|
||||
size,
|
||||
gray.len()
|
||||
);
|
||||
let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect();
|
||||
Self { size, pixels }
|
||||
}
|
||||
@@ -127,7 +145,13 @@ impl ColorImage {
|
||||
#[doc(alias = "from_grey_iter")]
|
||||
pub fn from_gray_iter(size: [usize; 2], gray_iter: impl Iterator<Item = u8>) -> Self {
|
||||
let pixels: Vec<_> = gray_iter.map(Color32::from_gray).collect();
|
||||
assert_eq!(size[0] * size[1], pixels.len());
|
||||
assert_eq!(
|
||||
size[0] * size[1],
|
||||
pixels.len(),
|
||||
"size: {:?}, pixels.len(): {}",
|
||||
size,
|
||||
pixels.len()
|
||||
);
|
||||
Self { size, pixels }
|
||||
}
|
||||
|
||||
@@ -150,7 +174,13 @@ impl ColorImage {
|
||||
///
|
||||
/// Panics if `size[0] * size[1] * 3 != rgb.len()`.
|
||||
pub fn from_rgb(size: [usize; 2], rgb: &[u8]) -> Self {
|
||||
assert_eq!(size[0] * size[1] * 3, rgb.len());
|
||||
assert_eq!(
|
||||
size[0] * size[1] * 3,
|
||||
rgb.len(),
|
||||
"size: {:?}, rgb.len(): {}",
|
||||
size,
|
||||
rgb.len()
|
||||
);
|
||||
let pixels = rgb
|
||||
.chunks_exact(3)
|
||||
.map(|p| Color32::from_rgb(p[0], p[1], p[2]))
|
||||
@@ -225,7 +255,7 @@ impl std::ops::Index<(usize, usize)> for ColorImage {
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &Color32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
@@ -234,7 +264,7 @@ impl std::ops::IndexMut<(usize, usize)> for ColorImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
@@ -306,28 +336,54 @@ impl FontImage {
|
||||
/// If you are having problems with text looking skinny and pixelated, try using a low gamma, e.g. `0.4`.
|
||||
#[inline]
|
||||
pub fn srgba_pixels(&self, gamma: Option<f32>) -> impl ExactSizeIterator<Item = Color32> + '_ {
|
||||
// TODO(emilk): this default coverage gamma is a magic constant, chosen by eye. I don't even know why we need it.
|
||||
// Maybe we need to implement the ideas in https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html
|
||||
let gamma = gamma.unwrap_or(0.55);
|
||||
// This whole function is less than rigorous.
|
||||
// Ideally we should do this in a shader instead, and use different computations
|
||||
// for different text colors.
|
||||
// See https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html for an in-depth analysis.
|
||||
self.pixels.iter().map(move |coverage| {
|
||||
let alpha = coverage.powf(gamma);
|
||||
// We want to multiply with `vec4(alpha)` in the fragment shader:
|
||||
let a = fast_round(alpha * 255.0);
|
||||
Color32::from_rgba_premultiplied(a, a, a, a)
|
||||
let alpha = if let Some(gamma) = gamma {
|
||||
coverage.powf(gamma)
|
||||
} else {
|
||||
// alpha = coverage * coverage; // recommended by the article for WHITE text (using linear blending)
|
||||
|
||||
// The following is recommended by the article for BLACK text (using linear blending).
|
||||
// Very similar to a gamma of 0.5, but produces sharper text.
|
||||
// In practice it works well for all text colors (better than a gamma of 0.5, for instance).
|
||||
// See https://www.desmos.com/calculator/w0ndf5blmn for a visual comparison.
|
||||
2.0 * coverage - coverage * coverage
|
||||
};
|
||||
Color32::from_white_alpha(ecolor::linear_u8_from_linear_f32(alpha))
|
||||
})
|
||||
}
|
||||
|
||||
/// Clone a sub-region as a new image.
|
||||
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
|
||||
assert!(x + w <= self.width());
|
||||
assert!(y + h <= self.height());
|
||||
assert!(
|
||||
x + w <= self.width(),
|
||||
"x + w should be <= self.width(), but x: {}, w: {}, width: {}",
|
||||
x,
|
||||
w,
|
||||
self.width()
|
||||
);
|
||||
assert!(
|
||||
y + h <= self.height(),
|
||||
"y + h should be <= self.height(), but y: {}, h: {}, height: {}",
|
||||
y,
|
||||
h,
|
||||
self.height()
|
||||
);
|
||||
|
||||
let mut pixels = Vec::with_capacity(w * h);
|
||||
for y in y..y + h {
|
||||
let offset = y * self.width() + x;
|
||||
pixels.extend(&self.pixels[offset..(offset + w)]);
|
||||
}
|
||||
assert_eq!(pixels.len(), w * h);
|
||||
assert_eq!(
|
||||
pixels.len(),
|
||||
w * h,
|
||||
"pixels.len should be w * h, but got {}",
|
||||
pixels.len()
|
||||
);
|
||||
Self {
|
||||
size: [w, h],
|
||||
pixels,
|
||||
@@ -341,7 +397,7 @@ impl std::ops::Index<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &f32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
@@ -350,7 +406,7 @@ impl std::ops::IndexMut<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h);
|
||||
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
@@ -362,11 +418,6 @@ impl From<FontImage> for ImageData {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fast_round(r: f32) -> u8 {
|
||||
(r + 0.5) as _ // rust does a saturating cast since 1.45
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A change to an image.
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
|
||||
mod brush;
|
||||
pub mod color;
|
||||
mod corner_radius;
|
||||
mod corner_radius_f32;
|
||||
pub mod image;
|
||||
mod margin;
|
||||
mod marginf;
|
||||
mod margin_f32;
|
||||
mod mesh;
|
||||
pub mod mutex;
|
||||
mod rounding;
|
||||
mod roundingf;
|
||||
mod shadow;
|
||||
pub mod shape_transform;
|
||||
mod shapes;
|
||||
@@ -48,12 +48,12 @@ mod viewport;
|
||||
pub use self::{
|
||||
brush::Brush,
|
||||
color::ColorMode,
|
||||
corner_radius::CornerRadius,
|
||||
corner_radius_f32::CornerRadiusF32,
|
||||
image::{ColorImage, FontImage, ImageData, ImageDelta},
|
||||
margin::Margin,
|
||||
marginf::Marginf,
|
||||
margin_f32::*,
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
rounding::Rounding,
|
||||
roundingf::Roundingf,
|
||||
shadow::Shadow,
|
||||
shapes::{
|
||||
CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PaintCallbackInfo, PathShape,
|
||||
@@ -69,6 +69,9 @@ pub use self::{
|
||||
viewport::ViewportInPixels,
|
||||
};
|
||||
|
||||
#[deprecated = "Renamed to CornerRadius"]
|
||||
pub type Rounding = CornerRadius;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use tessellator::tessellate_shapes;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use emath::{vec2, Rect, Vec2};
|
||||
/// Use with care.
|
||||
///
|
||||
/// All values are stored as [`i8`] to keep the size of [`Margin`] small.
|
||||
/// If you want floats, use [`crate::Marginf`] instead.
|
||||
/// If you want floats, use [`crate::MarginF32`] instead.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Margin {
|
||||
|
||||
@@ -10,14 +10,17 @@ use crate::Margin;
|
||||
/// 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 struct MarginF32 {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
}
|
||||
|
||||
impl From<Margin> for Marginf {
|
||||
#[deprecated = "Renamed to MarginF32"]
|
||||
pub type Marginf = MarginF32;
|
||||
|
||||
impl From<Margin> for MarginF32 {
|
||||
#[inline]
|
||||
fn from(margin: Margin) -> Self {
|
||||
Self {
|
||||
@@ -29,9 +32,9 @@ impl From<Margin> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Marginf> for Margin {
|
||||
impl From<MarginF32> for Margin {
|
||||
#[inline]
|
||||
fn from(marginf: Marginf) -> Self {
|
||||
fn from(marginf: MarginF32) -> Self {
|
||||
Self {
|
||||
left: marginf.left as _,
|
||||
right: marginf.right as _,
|
||||
@@ -41,7 +44,7 @@ impl From<Marginf> for Margin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Marginf {
|
||||
impl MarginF32 {
|
||||
pub const ZERO: Self = Self {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
@@ -108,22 +111,22 @@ impl Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Marginf {
|
||||
impl From<f32> for MarginF32 {
|
||||
#[inline]
|
||||
fn from(v: f32) -> Self {
|
||||
Self::same(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec2> for Marginf {
|
||||
impl From<Vec2> for MarginF32 {
|
||||
#[inline]
|
||||
fn from(v: Vec2) -> Self {
|
||||
Self::symmetric(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf + Marginf`
|
||||
impl std::ops::Add for Marginf {
|
||||
/// `MarginF32 + MarginF32`
|
||||
impl std::ops::Add for MarginF32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
@@ -137,8 +140,8 @@ impl std::ops::Add for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf + f32`
|
||||
impl std::ops::Add<f32> for Marginf {
|
||||
/// `MarginF32 + f32`
|
||||
impl std::ops::Add<f32> for MarginF32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
@@ -153,7 +156,7 @@ impl std::ops::Add<f32> for Marginf {
|
||||
}
|
||||
|
||||
/// `Margind += f32`
|
||||
impl std::ops::AddAssign<f32> for Marginf {
|
||||
impl std::ops::AddAssign<f32> for MarginF32 {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, v: f32) {
|
||||
self.left += v;
|
||||
@@ -163,8 +166,8 @@ impl std::ops::AddAssign<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf * f32`
|
||||
impl std::ops::Mul<f32> for Marginf {
|
||||
/// `MarginF32 * f32`
|
||||
impl std::ops::Mul<f32> for MarginF32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
@@ -178,8 +181,8 @@ impl std::ops::Mul<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf *= f32`
|
||||
impl std::ops::MulAssign<f32> for Marginf {
|
||||
/// `MarginF32 *= f32`
|
||||
impl std::ops::MulAssign<f32> for MarginF32 {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, v: f32) {
|
||||
self.left *= v;
|
||||
@@ -189,8 +192,8 @@ impl std::ops::MulAssign<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf / f32`
|
||||
impl std::ops::Div<f32> for Marginf {
|
||||
/// `MarginF32 / f32`
|
||||
impl std::ops::Div<f32> for MarginF32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
@@ -204,8 +207,8 @@ impl std::ops::Div<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf /= f32`
|
||||
impl std::ops::DivAssign<f32> for Marginf {
|
||||
/// `MarginF32 /= f32`
|
||||
impl std::ops::DivAssign<f32> for MarginF32 {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, v: f32) {
|
||||
self.left /= v;
|
||||
@@ -215,8 +218,8 @@ impl std::ops::DivAssign<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf - Marginf`
|
||||
impl std::ops::Sub for Marginf {
|
||||
/// `MarginF32 - MarginF32`
|
||||
impl std::ops::Sub for MarginF32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
@@ -230,8 +233,8 @@ impl std::ops::Sub for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf - f32`
|
||||
impl std::ops::Sub<f32> for Marginf {
|
||||
/// `MarginF32 - f32`
|
||||
impl std::ops::Sub<f32> for MarginF32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
@@ -245,8 +248,8 @@ impl std::ops::Sub<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Marginf -= f32`
|
||||
impl std::ops::SubAssign<f32> for Marginf {
|
||||
/// `MarginF32 -= f32`
|
||||
impl std::ops::SubAssign<f32> for MarginF32 {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, v: f32) {
|
||||
self.left -= v;
|
||||
@@ -256,12 +259,12 @@ impl std::ops::SubAssign<f32> for Marginf {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect + Marginf`
|
||||
impl std::ops::Add<Marginf> for Rect {
|
||||
/// `Rect + MarginF32`
|
||||
impl std::ops::Add<MarginF32> for Rect {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, margin: Marginf) -> Self {
|
||||
fn add(self, margin: MarginF32) -> Self {
|
||||
Self::from_min_max(
|
||||
self.min - margin.left_top(),
|
||||
self.max + margin.right_bottom(),
|
||||
@@ -269,20 +272,20 @@ impl std::ops::Add<Marginf> for Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect += Marginf`
|
||||
impl std::ops::AddAssign<Marginf> for Rect {
|
||||
/// `Rect += MarginF32`
|
||||
impl std::ops::AddAssign<MarginF32> for Rect {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, margin: Marginf) {
|
||||
fn add_assign(&mut self, margin: MarginF32) {
|
||||
*self = *self + margin;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect - Marginf`
|
||||
impl std::ops::Sub<Marginf> for Rect {
|
||||
/// `Rect - MarginF32`
|
||||
impl std::ops::Sub<MarginF32> for Rect {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, margin: Marginf) -> Self {
|
||||
fn sub(self, margin: MarginF32) -> Self {
|
||||
Self::from_min_max(
|
||||
self.min + margin.left_top(),
|
||||
self.max - margin.right_bottom(),
|
||||
@@ -290,10 +293,10 @@ impl std::ops::Sub<Marginf> for Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Rect -= Marginf`
|
||||
impl std::ops::SubAssign<Marginf> for Rect {
|
||||
/// `Rect -= MarginF32`
|
||||
impl std::ops::SubAssign<MarginF32> for Rect {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, margin: Marginf) {
|
||||
fn sub_assign(&mut self, margin: MarginF32) {
|
||||
*self = *self - margin;
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,13 @@ impl Mesh {
|
||||
self.indices.is_empty() && self.vertices.is_empty()
|
||||
}
|
||||
|
||||
/// Iterate over the triangles of this mesh, returning vertex indices.
|
||||
pub fn triangles(&self) -> impl Iterator<Item = [u32; 3]> + '_ {
|
||||
self.indices
|
||||
.chunks_exact(3)
|
||||
.map(|chunk| [chunk[0], chunk[1], chunk[2]])
|
||||
}
|
||||
|
||||
/// Calculate a bounding rectangle.
|
||||
pub fn calc_bounds(&self) -> Rect {
|
||||
let mut bounds = Rect::NOTHING;
|
||||
@@ -112,7 +119,7 @@ impl Mesh {
|
||||
/// Panics when `other` mesh has a different texture.
|
||||
pub fn append(&mut self, other: Self) {
|
||||
profiling::function_scope!();
|
||||
debug_assert!(other.is_valid());
|
||||
debug_assert!(other.is_valid(), "Other mesh is invalid");
|
||||
|
||||
if self.is_empty() {
|
||||
*self = other;
|
||||
@@ -126,7 +133,7 @@ impl Mesh {
|
||||
///
|
||||
/// Panics when `other` mesh has a different texture.
|
||||
pub fn append_ref(&mut self, other: &Self) {
|
||||
debug_assert!(other.is_valid());
|
||||
debug_assert!(other.is_valid(), "Other mesh is invalid");
|
||||
|
||||
if self.is_empty() {
|
||||
self.texture_id = other.texture_id;
|
||||
@@ -148,7 +155,10 @@ impl Mesh {
|
||||
/// Panics when the mesh has assigned a texture.
|
||||
#[inline(always)]
|
||||
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
|
||||
debug_assert!(self.texture_id == TextureId::default());
|
||||
debug_assert!(
|
||||
self.texture_id == TextureId::default(),
|
||||
"Mesh has an assigned texture"
|
||||
);
|
||||
self.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
@@ -211,7 +221,10 @@ impl Mesh {
|
||||
/// Uniformly colored rectangle.
|
||||
#[inline(always)]
|
||||
pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) {
|
||||
debug_assert!(self.texture_id == TextureId::default());
|
||||
debug_assert!(
|
||||
self.texture_id == TextureId::default(),
|
||||
"Mesh has an assigned texture"
|
||||
);
|
||||
self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color);
|
||||
}
|
||||
|
||||
@@ -220,7 +233,7 @@ impl Mesh {
|
||||
/// Splits this mesh into many smaller meshes (if needed)
|
||||
/// where the smaller meshes have 16-bit indices.
|
||||
pub fn split_to_u16(self) -> Vec<Mesh16> {
|
||||
debug_assert!(self.is_valid());
|
||||
debug_assert!(self.is_valid(), "Mesh is invalid");
|
||||
|
||||
const MAX_SIZE: u32 = u16::MAX as u32;
|
||||
|
||||
@@ -273,7 +286,7 @@ impl Mesh {
|
||||
vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(),
|
||||
texture_id: self.texture_id,
|
||||
};
|
||||
debug_assert!(mesh.is_valid());
|
||||
debug_assert!(mesh.is_valid(), "Mesh is invalid");
|
||||
output.push(mesh);
|
||||
}
|
||||
output
|
||||
|
||||
@@ -190,7 +190,7 @@ mod rw_lock_impl {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for RwLockReadGuard<'a, T> {
|
||||
impl<T> Deref for RwLockReadGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -198,7 +198,7 @@ mod rw_lock_impl {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for RwLockReadGuard<'a, T> {
|
||||
impl<T> Drop for RwLockReadGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
let tid = std::thread::current().id();
|
||||
self.holders.lock().remove(&tid);
|
||||
@@ -229,7 +229,7 @@ mod rw_lock_impl {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for RwLockWriteGuard<'a, T> {
|
||||
impl<T> Deref for RwLockWriteGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -237,13 +237,13 @@ mod rw_lock_impl {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for RwLockWriteGuard<'a, T> {
|
||||
impl<T> DerefMut for RwLockWriteGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.guard.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for RwLockWriteGuard<'a, T> {
|
||||
impl<T> Drop for RwLockWriteGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
let tid = std::thread::current().id();
|
||||
self.holders.lock().remove(&tid);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Color32, Marginf, Rect, RectShape, Rounding, Vec2};
|
||||
use crate::{Color32, CornerRadius, MarginF32, Rect, RectShape, Vec2};
|
||||
|
||||
/// The color and fuzziness of a fuzzy shape.
|
||||
///
|
||||
@@ -44,7 +44,7 @@ impl Shadow {
|
||||
};
|
||||
|
||||
/// The argument is the rectangle of the shadow caster.
|
||||
pub fn as_shape(&self, rect: Rect, rounding: impl Into<Rounding>) -> RectShape {
|
||||
pub fn as_shape(&self, rect: Rect, corner_radius: impl Into<CornerRadius>) -> RectShape {
|
||||
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling
|
||||
|
||||
let Self {
|
||||
@@ -58,13 +58,13 @@ impl Shadow {
|
||||
let rect = rect
|
||||
.translate(Vec2::new(offset_x as _, offset_y as _))
|
||||
.expand(spread as _);
|
||||
let rounding = rounding.into() + Rounding::from(spread);
|
||||
let corner_radius = corner_radius.into() + CornerRadius::from(spread);
|
||||
|
||||
RectShape::filled(rect, rounding, color).with_blur_width(blur as _)
|
||||
RectShape::filled(rect, corner_radius, color).with_blur_width(blur as _)
|
||||
}
|
||||
|
||||
/// How much larger than the parent rect are we in each direction?
|
||||
pub fn margin(&self) -> Marginf {
|
||||
pub fn margin(&self) -> MarginF32 {
|
||||
let Self {
|
||||
offset,
|
||||
blur,
|
||||
@@ -74,7 +74,7 @@ impl Shadow {
|
||||
let spread = spread as f32;
|
||||
let blur = blur as f32;
|
||||
let [offset_x, offset_y] = offset;
|
||||
Marginf {
|
||||
MarginF32 {
|
||||
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,
|
||||
|
||||
@@ -60,9 +60,11 @@ pub fn adjust_colors(
|
||||
})
|
||||
| Shape::Rect(RectShape {
|
||||
rect: _,
|
||||
rounding: _,
|
||||
corner_radius: _,
|
||||
fill,
|
||||
stroke,
|
||||
stroke_kind: _,
|
||||
round_to_pixels: _,
|
||||
blur_width: _,
|
||||
brush: _,
|
||||
}) => {
|
||||
|
||||
@@ -8,20 +8,39 @@ use crate::*;
|
||||
pub struct RectShape {
|
||||
pub rect: Rect,
|
||||
|
||||
/// How rounded the corners are. Use `Rounding::ZERO` for no rounding.
|
||||
pub rounding: Rounding,
|
||||
/// How rounded the corners of the rectangle are.
|
||||
///
|
||||
/// Use [`CornerRadius::ZERO`] for for sharp corners.
|
||||
///
|
||||
/// This is the corner radii of the rectangle.
|
||||
/// If there is a stroke, then the stroke will have an inner and outer corner radius,
|
||||
/// and those will depend on [`StrokeKind`] and the stroke width.
|
||||
///
|
||||
/// For [`StrokeKind::Inside`], the outside of the stroke coincides with the rectangle,
|
||||
/// so the rounding will in this case specify the outer corner radius.
|
||||
pub corner_radius: CornerRadius,
|
||||
|
||||
/// 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`.
|
||||
/// Whether or not the stroke is inside or outside the edge of [`Self::rect`],
|
||||
/// is controlled by [`Self::stroke_kind`].
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// Is the stroke on the inside, outside, or centered on the rectangle?
|
||||
///
|
||||
/// If you want to perfectly tile rectangles, use [`StrokeKind::Inside`].
|
||||
pub stroke_kind: StrokeKind,
|
||||
|
||||
/// Snap the rectangle to pixels?
|
||||
///
|
||||
/// Rounding produces sharper rectangles.
|
||||
///
|
||||
/// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
|
||||
pub round_to_pixels: Option<bool>,
|
||||
|
||||
/// If larger than zero, the edges of the rectangle
|
||||
/// (for both fill and stroke) will be blurred.
|
||||
///
|
||||
@@ -50,19 +69,22 @@ fn rect_shape_size() {
|
||||
}
|
||||
|
||||
impl RectShape {
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
/// See also [`Self::filled`] and [`Self::stroke`].
|
||||
#[inline]
|
||||
pub fn new(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke_kind: StrokeKind,
|
||||
) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
corner_radius: corner_radius.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
stroke_kind,
|
||||
round_to_pixels: None,
|
||||
blur_width: 0.0,
|
||||
brush: Default::default(),
|
||||
}
|
||||
@@ -71,17 +93,45 @@ impl RectShape {
|
||||
#[inline]
|
||||
pub fn filled(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) -> Self {
|
||||
Self::new(rect, rounding, fill_color, Stroke::NONE)
|
||||
Self::new(
|
||||
rect,
|
||||
corner_radius,
|
||||
fill_color,
|
||||
Stroke::NONE,
|
||||
StrokeKind::Outside, // doesn't matter
|
||||
)
|
||||
}
|
||||
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
#[inline]
|
||||
pub fn stroke(rect: Rect, rounding: impl Into<Rounding>, stroke: impl Into<Stroke>) -> Self {
|
||||
pub fn stroke(
|
||||
rect: Rect,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke_kind: StrokeKind,
|
||||
) -> Self {
|
||||
let fill = Color32::TRANSPARENT;
|
||||
Self::new(rect, rounding, fill, stroke)
|
||||
Self::new(rect, corner_radius, fill, stroke, stroke_kind)
|
||||
}
|
||||
|
||||
/// Set if the stroke is on the inside, outside, or centered on the rectangle.
|
||||
#[inline]
|
||||
pub fn with_stroke_kind(mut self, stroke_kind: StrokeKind) -> Self {
|
||||
self.stroke_kind = stroke_kind;
|
||||
self
|
||||
}
|
||||
|
||||
/// Snap the rectangle to pixels?
|
||||
///
|
||||
/// Rounding produces sharper rectangles.
|
||||
///
|
||||
/// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
|
||||
#[inline]
|
||||
pub fn with_round_to_pixels(mut self, round_to_pixels: bool) -> Self {
|
||||
self.round_to_pixels = Some(round_to_pixels);
|
||||
self
|
||||
}
|
||||
|
||||
/// If larger than zero, the edges of the rectangle
|
||||
@@ -112,8 +162,12 @@ impl RectShape {
|
||||
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)
|
||||
let expand = match self.stroke_kind {
|
||||
StrokeKind::Inside => 0.0,
|
||||
StrokeKind::Middle => self.stroke.width / 2.0,
|
||||
StrokeKind::Outside => self.stroke.width,
|
||||
};
|
||||
self.rect.expand(expand + self.blur_width / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2};
|
||||
use crate::{
|
||||
stroke::PathStroke,
|
||||
text::{FontId, Fonts, Galley},
|
||||
Color32, Mesh, Rounding, Stroke, TextureId,
|
||||
Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -275,23 +275,25 @@ impl Shape {
|
||||
Self::Ellipse(EllipseShape::stroke(center, radius, stroke))
|
||||
}
|
||||
|
||||
/// See also [`Self::rect_stroke`].
|
||||
#[inline]
|
||||
pub fn rect_filled(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) -> Self {
|
||||
Self::Rect(RectShape::filled(rect, rounding, fill_color))
|
||||
Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
|
||||
}
|
||||
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
/// See also [`Self::rect_filled`].
|
||||
#[inline]
|
||||
pub fn rect_stroke(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke_kind: StrokeKind,
|
||||
) -> Self {
|
||||
Self::Rect(RectShape::stroke(rect, rounding, stroke))
|
||||
Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
@@ -337,7 +339,7 @@ impl Shape {
|
||||
#[inline]
|
||||
pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
|
||||
let mesh = mesh.into();
|
||||
debug_assert!(mesh.is_valid());
|
||||
debug_assert!(mesh.is_valid(), "Invalid mesh: {mesh:#?}");
|
||||
Self::Mesh(mesh)
|
||||
}
|
||||
|
||||
@@ -449,8 +451,9 @@ impl Shape {
|
||||
}
|
||||
Self::Rect(rect_shape) => {
|
||||
rect_shape.rect = transform * rect_shape.rect;
|
||||
rect_shape.corner_radius *= transform.scaling;
|
||||
rect_shape.stroke.width *= transform.scaling;
|
||||
rect_shape.rounding *= transform.scaling;
|
||||
rect_shape.blur_width *= transform.scaling;
|
||||
}
|
||||
Self::Text(text_shape) => {
|
||||
text_shape.pos = transform * text_shape.pos;
|
||||
@@ -471,17 +474,17 @@ impl Shape {
|
||||
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 {
|
||||
Self::QuadraticBezier(bezier) => {
|
||||
for p in &mut bezier.points {
|
||||
*p = transform * *p;
|
||||
}
|
||||
cubic_curve.stroke.width *= transform.scaling;
|
||||
bezier.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::CubicBezier(bezier) => {
|
||||
for p in &mut bezier.points {
|
||||
*p = transform * *p;
|
||||
}
|
||||
bezier.stroke.width *= transform.scaling;
|
||||
}
|
||||
Self::Callback(shape) => {
|
||||
shape.rect = transform * shape.rect;
|
||||
@@ -501,7 +504,7 @@ fn points_from_line(
|
||||
shapes: &mut Vec<Shape>,
|
||||
) {
|
||||
let mut position_on_segment = 0.0;
|
||||
path.windows(2).for_each(|window| {
|
||||
for window in path.windows(2) {
|
||||
let (start, end) = (window[0], window[1]);
|
||||
let vector = end - start;
|
||||
let segment_length = vector.length();
|
||||
@@ -511,7 +514,7 @@ fn points_from_line(
|
||||
position_on_segment += spacing;
|
||||
}
|
||||
position_on_segment -= segment_length;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates dashes from a line.
|
||||
@@ -523,12 +526,18 @@ fn dashes_from_line(
|
||||
shapes: &mut Vec<Shape>,
|
||||
dash_offset: f32,
|
||||
) {
|
||||
assert_eq!(dash_lengths.len(), gap_lengths.len());
|
||||
assert_eq!(
|
||||
dash_lengths.len(),
|
||||
gap_lengths.len(),
|
||||
"Mismatched dash and gap lengths, got dash_lengths: {}, gap_lengths: {}",
|
||||
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| {
|
||||
for window in path.windows(2) {
|
||||
let (start, end) = (window[0], window[1]);
|
||||
let vector = end - start;
|
||||
let segment_length = vector.length();
|
||||
@@ -559,5 +568,5 @@ fn dashes_from_line(
|
||||
}
|
||||
|
||||
position_on_segment -= segment_length;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,10 @@ impl AllocInfo {
|
||||
}
|
||||
|
||||
pub fn num_elements(&self) -> usize {
|
||||
assert!(self.element_size != ElementSize::Heterogenous);
|
||||
assert!(
|
||||
self.element_size != ElementSize::Heterogenous,
|
||||
"Heterogenous element size"
|
||||
);
|
||||
self.num_elements
|
||||
}
|
||||
|
||||
|
||||
@@ -56,29 +56,23 @@ impl std::hash::Hash for Stroke {
|
||||
}
|
||||
|
||||
/// Describes how the stroke of a shape should be painted.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum StrokeKind {
|
||||
/// The stroke should be painted entirely outside of the shape
|
||||
Outside,
|
||||
|
||||
/// The stroke should be painted entirely inside of the shape
|
||||
Inside,
|
||||
|
||||
/// The stroke should be painted right on the edge of the shape, half inside and half outside.
|
||||
Middle,
|
||||
}
|
||||
|
||||
impl Default for StrokeKind {
|
||||
fn default() -> Self {
|
||||
Self::Middle
|
||||
}
|
||||
/// The stroke should be painted entirely outside of the shape
|
||||
Outside,
|
||||
}
|
||||
|
||||
/// Describes the width and color of paths. The color can either be solid or provided by a callback. For more information, see [`ColorMode`]
|
||||
///
|
||||
/// The default stroke is the same as [`Stroke::NONE`].
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct PathStroke {
|
||||
pub width: f32,
|
||||
@@ -86,6 +80,13 @@ pub struct PathStroke {
|
||||
pub kind: StrokeKind,
|
||||
}
|
||||
|
||||
impl Default for PathStroke {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::NONE
|
||||
}
|
||||
}
|
||||
|
||||
impl PathStroke {
|
||||
/// Same as [`PathStroke::default`].
|
||||
pub const NONE: Self = Self {
|
||||
@@ -99,7 +100,7 @@ impl PathStroke {
|
||||
Self {
|
||||
width: width.into(),
|
||||
color: ColorMode::Solid(color.into()),
|
||||
kind: StrokeKind::default(),
|
||||
kind: StrokeKind::Middle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +115,17 @@ impl PathStroke {
|
||||
Self {
|
||||
width: width.into(),
|
||||
color: ColorMode::UV(Arc::new(callback)),
|
||||
kind: StrokeKind::default(),
|
||||
kind: StrokeKind::Middle,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_kind(self, kind: StrokeKind) -> Self {
|
||||
Self { kind, ..self }
|
||||
}
|
||||
|
||||
/// Set the stroke to be painted right on the edge of the shape, half inside and half outside.
|
||||
#[inline]
|
||||
pub fn middle(self) -> Self {
|
||||
Self {
|
||||
kind: StrokeKind::Middle,
|
||||
@@ -127,6 +134,7 @@ impl PathStroke {
|
||||
}
|
||||
|
||||
/// Set the stroke to be painted entirely outside of the shape
|
||||
#[inline]
|
||||
pub fn outside(self) -> Self {
|
||||
Self {
|
||||
kind: StrokeKind::Outside,
|
||||
@@ -135,6 +143,7 @@ impl PathStroke {
|
||||
}
|
||||
|
||||
/// Set the stroke to be painted entirely inside of the shape
|
||||
#[inline]
|
||||
pub fn inside(self) -> Self {
|
||||
Self {
|
||||
kind: StrokeKind::Inside,
|
||||
@@ -168,7 +177,7 @@ impl From<Stroke> for PathStroke {
|
||||
Self {
|
||||
width: value.width,
|
||||
color: ColorMode::Solid(value.color),
|
||||
kind: StrokeKind::default(),
|
||||
kind: StrokeKind::Middle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,13 +26,6 @@ impl CCursor {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cursor> for CCursor {
|
||||
#[inline]
|
||||
fn from(c: Cursor) -> Self {
|
||||
c.ccursor
|
||||
}
|
||||
}
|
||||
|
||||
/// Two `CCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for CCursor {
|
||||
@@ -76,10 +69,12 @@ impl std::ops::SubAssign<usize> for CCursor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Row Cursor
|
||||
/// Row/column cursor.
|
||||
///
|
||||
/// This refers to rows and columns in layout terms--text wrapping creates multiple rows.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct RCursor {
|
||||
pub struct LayoutCursor {
|
||||
/// 0 is first row, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
@@ -90,48 +85,3 @@ pub struct RCursor {
|
||||
/// When moving up/down it may again be within the next row.
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
/// Paragraph Cursor
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct PCursor {
|
||||
/// 0 is first paragraph, and so on.
|
||||
/// Note that a single paragraph can span multiple rows.
|
||||
/// (a paragraph is text separated by `\n`).
|
||||
pub paragraph: usize,
|
||||
|
||||
/// Character based (NOT bytes).
|
||||
/// It is fine if this points to something beyond the end of the current paragraph.
|
||||
/// When moving up/down it may again be within the next paragraph.
|
||||
pub offset: usize,
|
||||
|
||||
/// If this cursors sits right at the border of a wrapped row break (NOT paragraph break)
|
||||
/// do we prefer the next row?
|
||||
/// This is *almost* always what you want, *except* for when
|
||||
/// explicitly clicking the end of a row or pressing the end key.
|
||||
pub prefer_next_row: bool,
|
||||
}
|
||||
|
||||
/// Two `PCursor`s are considered equal if they refer to the same character boundary,
|
||||
/// even if one prefers the start of the next row.
|
||||
impl PartialEq for PCursor {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.paragraph == other.paragraph && self.offset == other.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// All different types of cursors together.
|
||||
///
|
||||
/// They all point to the same place, but in their own different ways.
|
||||
/// pcursor/rcursor can also point to after the end of the paragraph/row.
|
||||
/// Does not implement `PartialEq` because you must think which cursor should be equivalent.
|
||||
///
|
||||
/// The default cursor is the zero-cursor, to the first character.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Cursor {
|
||||
pub ccursor: CCursor,
|
||||
pub rcursor: RCursor,
|
||||
pub pcursor: PCursor,
|
||||
}
|
||||
|
||||
@@ -91,8 +91,14 @@ impl FontImpl {
|
||||
scale_in_pixels: f32,
|
||||
tweak: FontTweak,
|
||||
) -> Self {
|
||||
assert!(scale_in_pixels > 0.0);
|
||||
assert!(pixels_per_point > 0.0);
|
||||
assert!(
|
||||
scale_in_pixels > 0.0,
|
||||
"scale_in_pixels is smaller than 0, got: {scale_in_pixels:?}"
|
||||
);
|
||||
assert!(
|
||||
pixels_per_point > 0.0,
|
||||
"pixels_per_point must be greater than 0, got: {pixels_per_point:?}"
|
||||
);
|
||||
|
||||
use ab_glyph::{Font, ScaleFont};
|
||||
let scaled = ab_glyph_font.as_scaled(scale_in_pixels);
|
||||
@@ -264,7 +270,7 @@ impl FontImpl {
|
||||
}
|
||||
|
||||
fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo {
|
||||
assert!(glyph_id.0 != 0);
|
||||
assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0");
|
||||
use ab_glyph::{Font as _, ScaleFont};
|
||||
|
||||
let glyph = glyph_id.with_scale_and_position(
|
||||
|
||||
@@ -144,6 +144,12 @@ impl FontData {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for FontData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.font.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Extra scale and vertical tweak to apply to all text of a certain font.
|
||||
|
||||
@@ -543,7 +543,7 @@ fn halign_and_justify_row(
|
||||
(num_leading_spaces, row.glyphs.len() - num_trailing_spaces)
|
||||
};
|
||||
let num_glyphs_in_range = glyph_range.1 - glyph_range.0;
|
||||
assert!(num_glyphs_in_range > 0);
|
||||
assert!(num_glyphs_in_range > 0, "Should have at least one glyph");
|
||||
|
||||
let original_min_x = row.glyphs[glyph_range.0].logical_rect().min.x;
|
||||
let original_max_x = row.glyphs[glyph_range.1 - 1].logical_rect().max.x;
|
||||
@@ -777,10 +777,10 @@ fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut end_run = |start: Option<(Color32, Rect)>, stop_x: f32| {
|
||||
if let Some((color, start_rect)) = start {
|
||||
let mut end_run = |start: Option<(Color32, Rect, f32)>, stop_x: f32| {
|
||||
if let Some((color, start_rect, expand)) = start {
|
||||
let rect = Rect::from_min_max(start_rect.left_top(), pos2(stop_x, start_rect.bottom()));
|
||||
let rect = rect.expand(1.0); // looks better
|
||||
let rect = rect.expand(expand);
|
||||
mesh.add_colored_rect(rect, color);
|
||||
}
|
||||
};
|
||||
@@ -795,18 +795,19 @@ fn add_row_backgrounds(job: &LayoutJob, row: &Row, mesh: &mut Mesh) {
|
||||
|
||||
if color == Color32::TRANSPARENT {
|
||||
end_run(run_start.take(), last_rect.right());
|
||||
} else if let Some((existing_color, start)) = run_start {
|
||||
} else if let Some((existing_color, start, expand)) = run_start {
|
||||
if existing_color == color
|
||||
&& start.top() == rect.top()
|
||||
&& start.bottom() == rect.bottom()
|
||||
&& format.expand_bg == expand
|
||||
{
|
||||
// continue the same background rectangle
|
||||
} else {
|
||||
end_run(run_start.take(), last_rect.right());
|
||||
run_start = Some((color, rect));
|
||||
run_start = Some((color, rect, format.expand_bg));
|
||||
}
|
||||
} else {
|
||||
run_start = Some((color, rect));
|
||||
run_start = Some((color, rect, format.expand_bg));
|
||||
}
|
||||
|
||||
last_rect = rect;
|
||||
@@ -916,7 +917,10 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
|
||||
} else {
|
||||
// Thin lines often lost, so this is a bad idea
|
||||
|
||||
assert_eq!(start.y, stop.y);
|
||||
assert_eq!(
|
||||
start.y, stop.y,
|
||||
"Horizontal line must be horizontal, but got: {start:?} -> {stop:?}"
|
||||
);
|
||||
|
||||
let min_y = point_scale.round_to_pixel(start.y - 0.5 * stroke.width);
|
||||
let max_y = point_scale.round_to_pixel(min_y + stroke.width);
|
||||
@@ -1012,22 +1016,22 @@ impl RowBreakCandidates {
|
||||
punctuation,
|
||||
any,
|
||||
} = self;
|
||||
if space.map_or(false, |s| s < index) {
|
||||
if space.is_some_and(|s| s < index) {
|
||||
*space = None;
|
||||
}
|
||||
if cjk.map_or(false, |s| s < index) {
|
||||
if cjk.is_some_and(|s| s < index) {
|
||||
*cjk = None;
|
||||
}
|
||||
if pre_cjk.map_or(false, |s| s < index) {
|
||||
if pre_cjk.is_some_and(|s| s < index) {
|
||||
*pre_cjk = None;
|
||||
}
|
||||
if dash.map_or(false, |s| s < index) {
|
||||
if dash.is_some_and(|s| s < index) {
|
||||
*dash = None;
|
||||
}
|
||||
if punctuation.map_or(false, |s| s < index) {
|
||||
if punctuation.is_some_and(|s| s < index) {
|
||||
*punctuation = None;
|
||||
}
|
||||
if any.map_or(false, |s| s < index) {
|
||||
if any.is_some_and(|s| s < index) {
|
||||
*any = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{
|
||||
cursor::{CCursor, Cursor, PCursor, RCursor},
|
||||
cursor::{CCursor, LayoutCursor},
|
||||
font::UvRect,
|
||||
};
|
||||
use crate::{Color32, FontId, Mesh, Stroke};
|
||||
@@ -272,6 +272,11 @@ pub struct TextFormat {
|
||||
|
||||
pub background: Color32,
|
||||
|
||||
/// Amount to expand background fill by.
|
||||
///
|
||||
/// Default: 1.0
|
||||
pub expand_bg: f32,
|
||||
|
||||
pub italics: bool,
|
||||
|
||||
pub underline: Stroke,
|
||||
@@ -299,6 +304,7 @@ impl Default for TextFormat {
|
||||
line_height: None,
|
||||
color: Color32::GRAY,
|
||||
background: Color32::TRANSPARENT,
|
||||
expand_bg: 1.0,
|
||||
italics: false,
|
||||
underline: Stroke::NONE,
|
||||
strikethrough: Stroke::NONE,
|
||||
@@ -316,6 +322,7 @@ impl std::hash::Hash for TextFormat {
|
||||
line_height,
|
||||
color,
|
||||
background,
|
||||
expand_bg,
|
||||
italics,
|
||||
underline,
|
||||
strikethrough,
|
||||
@@ -328,6 +335,7 @@ impl std::hash::Hash for TextFormat {
|
||||
}
|
||||
color.hash(state);
|
||||
background.hash(state);
|
||||
emath::OrderedFloat(*expand_bg).hash(state);
|
||||
italics.hash(state);
|
||||
underline.hash(state);
|
||||
strikethrough.hash(state);
|
||||
@@ -793,53 +801,18 @@ impl Galley {
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_cursor(&self, cursor: &Cursor) -> Rect {
|
||||
self.pos_from_pcursor(cursor.pcursor) // pcursor is what TextEdit stores
|
||||
fn pos_from_layout_cursor(&self, layout_cursor: &LayoutCursor) -> Rect {
|
||||
let Some(row) = self.rows.get(layout_cursor.row) else {
|
||||
return self.end_pos();
|
||||
};
|
||||
|
||||
let x = row.x_offset(layout_cursor.column);
|
||||
Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect {
|
||||
let mut it = PCursor::default();
|
||||
|
||||
for row in &self.rows {
|
||||
if it.paragraph == pcursor.paragraph {
|
||||
// Right paragraph, but is it the right row in the paragraph?
|
||||
|
||||
if it.offset <= pcursor.offset
|
||||
&& (pcursor.offset <= it.offset + row.char_count_excluding_newline()
|
||||
|| row.ends_with_newline)
|
||||
{
|
||||
let column = pcursor.offset - it.offset;
|
||||
|
||||
let select_next_row_instead = pcursor.prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
if !select_next_row_instead {
|
||||
let x = row.x_offset(column);
|
||||
return Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if row.ends_with_newline {
|
||||
it.paragraph += 1;
|
||||
it.offset = 0;
|
||||
} else {
|
||||
it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
|
||||
self.end_pos()
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_ccursor(&self, ccursor: CCursor) -> Rect {
|
||||
self.pos_from_cursor(&self.from_ccursor(ccursor))
|
||||
}
|
||||
|
||||
/// Returns a 0-width Rect.
|
||||
pub fn pos_from_rcursor(&self, rcursor: RCursor) -> Rect {
|
||||
self.pos_from_cursor(&self.from_rcursor(rcursor))
|
||||
pub fn pos_from_cursor(&self, cursor: CCursor) -> Rect {
|
||||
self.pos_from_layout_cursor(&self.layout_from_cursor(cursor))
|
||||
}
|
||||
|
||||
/// Cursor at the given position within the galley.
|
||||
@@ -849,7 +822,7 @@ impl Galley {
|
||||
/// and a cursor below the galley is considered
|
||||
/// same as a cursor at the end.
|
||||
/// This allows implementing text-selection by dragging above/below the galley.
|
||||
pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor {
|
||||
pub fn cursor_from_pos(&self, pos: Vec2) -> CCursor {
|
||||
if let Some(first_row) = self.rows.first() {
|
||||
if pos.y < first_row.min_y() {
|
||||
return self.begin();
|
||||
@@ -862,12 +835,11 @@ impl Galley {
|
||||
}
|
||||
|
||||
let mut best_y_dist = f32::INFINITY;
|
||||
let mut cursor = Cursor::default();
|
||||
let mut cursor = CCursor::default();
|
||||
|
||||
let mut ccursor_index = 0;
|
||||
let mut pcursor_it = PCursor::default();
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
for row in &self.rows {
|
||||
let min_y = row.min_y();
|
||||
let max_y = row.max_y();
|
||||
|
||||
@@ -878,20 +850,9 @@ impl Galley {
|
||||
// char_at is `Row` not `PlacedRow` relative which means we have to subtract the pos.
|
||||
let column = row.char_at(pos.x - row.pos.x);
|
||||
let prefer_next_row = column < row.char_count_excluding_newline();
|
||||
cursor = Cursor {
|
||||
ccursor: CCursor {
|
||||
index: ccursor_index + column,
|
||||
prefer_next_row,
|
||||
},
|
||||
rcursor: RCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
},
|
||||
pcursor: PCursor {
|
||||
paragraph: pcursor_it.paragraph,
|
||||
offset: pcursor_it.offset + column,
|
||||
prefer_next_row,
|
||||
},
|
||||
cursor = CCursor {
|
||||
index: ccursor_index + column,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
if is_pos_within_row {
|
||||
@@ -899,12 +860,6 @@ impl Galley {
|
||||
}
|
||||
}
|
||||
ccursor_index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
|
||||
cursor
|
||||
@@ -915,15 +870,15 @@ impl Galley {
|
||||
impl Galley {
|
||||
/// Cursor to the first character.
|
||||
///
|
||||
/// This is the same as [`Cursor::default`].
|
||||
/// This is the same as [`CCursor::default`].
|
||||
#[inline]
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn begin(&self) -> Cursor {
|
||||
Cursor::default()
|
||||
pub fn begin(&self) -> CCursor {
|
||||
CCursor::default()
|
||||
}
|
||||
|
||||
/// Cursor to one-past last character.
|
||||
pub fn end(&self) -> Cursor {
|
||||
pub fn end(&self) -> CCursor {
|
||||
if self.rows.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
@@ -931,31 +886,47 @@ impl Galley {
|
||||
index: 0,
|
||||
prefer_next_row: true,
|
||||
};
|
||||
let mut pcursor = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row: true,
|
||||
};
|
||||
for row in &self.rows {
|
||||
let row_char_count = row.char_count_including_newline();
|
||||
ccursor.index += row_char_count;
|
||||
if row.ends_with_newline {
|
||||
pcursor.paragraph += 1;
|
||||
pcursor.offset = 0;
|
||||
} else {
|
||||
pcursor.offset += row_char_count;
|
||||
}
|
||||
}
|
||||
Cursor {
|
||||
ccursor,
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor,
|
||||
}
|
||||
ccursor
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Cursor conversions
|
||||
impl Galley {
|
||||
// The returned cursor is clamped.
|
||||
pub fn layout_from_cursor(&self, cursor: CCursor) -> LayoutCursor {
|
||||
let prefer_next_row = cursor.prefer_next_row;
|
||||
let mut ccursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
let row_char_count = row.char_count_excluding_newline();
|
||||
|
||||
if ccursor_it.index <= cursor.index && cursor.index <= ccursor_it.index + row_char_count
|
||||
{
|
||||
let column = cursor.index - ccursor_it.index;
|
||||
|
||||
let select_next_row_instead = prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
if !select_next_row_instead {
|
||||
return LayoutCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
};
|
||||
}
|
||||
}
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
}
|
||||
debug_assert!(ccursor_it == self.end(), "Cursor out of bounds");
|
||||
|
||||
pub fn end_rcursor(&self) -> RCursor {
|
||||
if let Some(last_row) = self.rows.last() {
|
||||
RCursor {
|
||||
LayoutCursor {
|
||||
row: self.rows.len() - 1,
|
||||
column: last_row.char_count_including_newline(),
|
||||
}
|
||||
@@ -963,270 +934,156 @@ impl Galley {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Cursor conversions
|
||||
impl Galley {
|
||||
// The returned cursor is clamped.
|
||||
pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor {
|
||||
let prefer_next_row = ccursor.prefer_next_row;
|
||||
let mut ccursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
let mut pcursor_it = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
let row_char_count = row.char_count_excluding_newline();
|
||||
|
||||
if ccursor_it.index <= ccursor.index
|
||||
&& ccursor.index <= ccursor_it.index + row_char_count
|
||||
{
|
||||
let column = ccursor.index - ccursor_it.index;
|
||||
|
||||
let select_next_row_instead = prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
if !select_next_row_instead {
|
||||
pcursor_it.offset += column;
|
||||
return Cursor {
|
||||
ccursor,
|
||||
rcursor: RCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
},
|
||||
pcursor: pcursor_it,
|
||||
};
|
||||
}
|
||||
}
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
debug_assert!(ccursor_it == self.end().ccursor);
|
||||
Cursor {
|
||||
ccursor: ccursor_it, // clamp
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor: pcursor_it,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rcursor(&self, rcursor: RCursor) -> Cursor {
|
||||
if rcursor.row >= self.rows.len() {
|
||||
fn cursor_from_layout(&self, layout_cursor: LayoutCursor) -> CCursor {
|
||||
if layout_cursor.row >= self.rows.len() {
|
||||
return self.end();
|
||||
}
|
||||
|
||||
let prefer_next_row =
|
||||
rcursor.column < self.rows[rcursor.row].char_count_excluding_newline();
|
||||
let mut ccursor_it = CCursor {
|
||||
layout_cursor.column < self.rows[layout_cursor.row].char_count_excluding_newline();
|
||||
let mut cursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
let mut pcursor_it = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
if row_nr == rcursor.row {
|
||||
ccursor_it.index += rcursor.column.at_most(row.char_count_excluding_newline());
|
||||
if row_nr == layout_cursor.row {
|
||||
cursor_it.index += layout_cursor
|
||||
.column
|
||||
.at_most(row.char_count_excluding_newline());
|
||||
|
||||
if row.ends_with_newline {
|
||||
// Allow offset to go beyond the end of the paragraph
|
||||
pcursor_it.offset += rcursor.column;
|
||||
} else {
|
||||
pcursor_it.offset += rcursor.column.at_most(row.char_count_excluding_newline());
|
||||
}
|
||||
return Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor,
|
||||
pcursor: pcursor_it,
|
||||
};
|
||||
}
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
return cursor_it;
|
||||
}
|
||||
cursor_it.index += row.char_count_including_newline();
|
||||
}
|
||||
Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor: pcursor_it,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(emilk): return identical cursor, or clamp?
|
||||
pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor {
|
||||
let prefer_next_row = pcursor.prefer_next_row;
|
||||
let mut ccursor_it = CCursor {
|
||||
index: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
let mut pcursor_it = PCursor {
|
||||
paragraph: 0,
|
||||
offset: 0,
|
||||
prefer_next_row,
|
||||
};
|
||||
|
||||
for (row_nr, row) in self.rows.iter().enumerate() {
|
||||
if pcursor_it.paragraph == pcursor.paragraph {
|
||||
// Right paragraph, but is it the right row in the paragraph?
|
||||
|
||||
if pcursor_it.offset <= pcursor.offset
|
||||
&& (pcursor.offset <= pcursor_it.offset + row.char_count_excluding_newline()
|
||||
|| row.ends_with_newline)
|
||||
{
|
||||
let column = pcursor.offset - pcursor_it.offset;
|
||||
|
||||
let select_next_row_instead = pcursor.prefer_next_row
|
||||
&& !row.ends_with_newline
|
||||
&& column >= row.char_count_excluding_newline();
|
||||
|
||||
if !select_next_row_instead {
|
||||
ccursor_it.index += column.at_most(row.char_count_excluding_newline());
|
||||
|
||||
return Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor: RCursor {
|
||||
row: row_nr,
|
||||
column,
|
||||
},
|
||||
pcursor,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ccursor_it.index += row.char_count_including_newline();
|
||||
if row.ends_with_newline {
|
||||
pcursor_it.paragraph += 1;
|
||||
pcursor_it.offset = 0;
|
||||
} else {
|
||||
pcursor_it.offset += row.char_count_including_newline();
|
||||
}
|
||||
}
|
||||
Cursor {
|
||||
ccursor: ccursor_it,
|
||||
rcursor: self.end_rcursor(),
|
||||
pcursor,
|
||||
}
|
||||
cursor_it
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Cursor positions
|
||||
impl Galley {
|
||||
pub fn cursor_left_one_character(&self, cursor: &Cursor) -> Cursor {
|
||||
if cursor.ccursor.index == 0 {
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn cursor_left_one_character(&self, cursor: &CCursor) -> CCursor {
|
||||
if cursor.index == 0 {
|
||||
Default::default()
|
||||
} else {
|
||||
let ccursor = CCursor {
|
||||
index: cursor.ccursor.index,
|
||||
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the begging of a row than at the end.
|
||||
};
|
||||
self.from_ccursor(ccursor - 1)
|
||||
CCursor {
|
||||
index: cursor.index - 1,
|
||||
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the beginning of a row than at the end.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_right_one_character(&self, cursor: &Cursor) -> Cursor {
|
||||
let ccursor = CCursor {
|
||||
index: cursor.ccursor.index,
|
||||
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the begging of a row than at the end.
|
||||
};
|
||||
self.from_ccursor(ccursor + 1)
|
||||
pub fn cursor_right_one_character(&self, cursor: &CCursor) -> CCursor {
|
||||
CCursor {
|
||||
index: (cursor.index + 1).min(self.end().index),
|
||||
prefer_next_row: true, // default to this when navigating. It is more often useful to put cursor at the beginning of a row than at the end.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_up_one_row(&self, cursor: &Cursor) -> Cursor {
|
||||
if cursor.rcursor.row == 0 {
|
||||
Cursor::default()
|
||||
pub fn cursor_up_one_row(
|
||||
&self,
|
||||
cursor: &CCursor,
|
||||
h_pos: Option<f32>,
|
||||
) -> (CCursor, Option<f32>) {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
|
||||
if layout_cursor.row == 0 {
|
||||
(CCursor::default(), None)
|
||||
} else {
|
||||
let new_row = cursor.rcursor.row - 1;
|
||||
let new_row = layout_cursor.row - 1;
|
||||
|
||||
let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
|
||||
>= self.rows[cursor.rcursor.row].char_count_excluding_newline();
|
||||
|
||||
let new_rcursor = if cursor_is_beyond_end_of_current_row {
|
||||
// keep same column
|
||||
RCursor {
|
||||
row: new_row,
|
||||
column: cursor.rcursor.column,
|
||||
}
|
||||
} else {
|
||||
let new_layout_cursor = {
|
||||
// keep same X coord
|
||||
let x = self.pos_from_cursor(cursor).center().x;
|
||||
let row = &self.rows[new_row];
|
||||
let column = if x > row.rect().right() {
|
||||
// beyond the end of this row - keep same column
|
||||
cursor.rcursor.column
|
||||
} else {
|
||||
row.char_at(x)
|
||||
};
|
||||
RCursor {
|
||||
let column = self.rows[new_row].char_at(h_pos);
|
||||
LayoutCursor {
|
||||
row: new_row,
|
||||
column,
|
||||
}
|
||||
};
|
||||
self.from_rcursor(new_rcursor)
|
||||
(self.cursor_from_layout(new_layout_cursor), Some(h_pos))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_down_one_row(&self, cursor: &Cursor) -> Cursor {
|
||||
if cursor.rcursor.row + 1 < self.rows.len() {
|
||||
let new_row = cursor.rcursor.row + 1;
|
||||
pub fn cursor_down_one_row(
|
||||
&self,
|
||||
cursor: &CCursor,
|
||||
h_pos: Option<f32>,
|
||||
) -> (CCursor, Option<f32>) {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
|
||||
if layout_cursor.row + 1 < self.rows.len() {
|
||||
let new_row = layout_cursor.row + 1;
|
||||
|
||||
let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
|
||||
>= self.rows[cursor.rcursor.row].char_count_excluding_newline();
|
||||
|
||||
let new_rcursor = if cursor_is_beyond_end_of_current_row {
|
||||
// keep same column
|
||||
RCursor {
|
||||
row: new_row,
|
||||
column: cursor.rcursor.column,
|
||||
}
|
||||
} else {
|
||||
let new_layout_cursor = {
|
||||
// keep same X coord
|
||||
let x = self.pos_from_cursor(cursor).center().x;
|
||||
let row = &self.rows[new_row];
|
||||
let column = if x > row.rect().right() {
|
||||
// beyond the end of the next row - keep same column
|
||||
cursor.rcursor.column
|
||||
} else {
|
||||
row.char_at(x)
|
||||
};
|
||||
RCursor {
|
||||
let column = self.rows[new_row].char_at(h_pos);
|
||||
LayoutCursor {
|
||||
row: new_row,
|
||||
column,
|
||||
}
|
||||
};
|
||||
|
||||
self.from_rcursor(new_rcursor)
|
||||
(self.cursor_from_layout(new_layout_cursor), Some(h_pos))
|
||||
} else {
|
||||
self.end()
|
||||
(self.end(), None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_begin_of_row(&self, cursor: &Cursor) -> Cursor {
|
||||
self.from_rcursor(RCursor {
|
||||
row: cursor.rcursor.row,
|
||||
pub fn cursor_begin_of_row(&self, cursor: &CCursor) -> CCursor {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
self.cursor_from_layout(LayoutCursor {
|
||||
row: layout_cursor.row,
|
||||
column: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor {
|
||||
self.from_rcursor(RCursor {
|
||||
row: cursor.rcursor.row,
|
||||
column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
|
||||
pub fn cursor_end_of_row(&self, cursor: &CCursor) -> CCursor {
|
||||
let layout_cursor = self.layout_from_cursor(*cursor);
|
||||
self.cursor_from_layout(LayoutCursor {
|
||||
row: layout_cursor.row,
|
||||
column: self.rows[layout_cursor.row].char_count_excluding_newline(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_begin_of_paragraph(&self, cursor: &CCursor) -> CCursor {
|
||||
let mut layout_cursor = self.layout_from_cursor(*cursor);
|
||||
layout_cursor.column = 0;
|
||||
|
||||
loop {
|
||||
let prev_row = layout_cursor
|
||||
.row
|
||||
.checked_sub(1)
|
||||
.and_then(|row| self.rows.get(row));
|
||||
|
||||
let Some(prev_row) = prev_row else {
|
||||
// This is the first row
|
||||
break;
|
||||
};
|
||||
|
||||
if prev_row.ends_with_newline {
|
||||
break;
|
||||
}
|
||||
|
||||
layout_cursor.row -= 1;
|
||||
}
|
||||
|
||||
self.cursor_from_layout(layout_cursor)
|
||||
}
|
||||
|
||||
pub fn cursor_end_of_paragraph(&self, cursor: &CCursor) -> CCursor {
|
||||
let mut layout_cursor = self.layout_from_cursor(*cursor);
|
||||
loop {
|
||||
let row = &self.rows[layout_cursor.row];
|
||||
if row.ends_with_newline || layout_cursor.row == self.rows.len() - 1 {
|
||||
layout_cursor.column = row.char_count_excluding_newline();
|
||||
break;
|
||||
}
|
||||
|
||||
layout_cursor.row += 1;
|
||||
}
|
||||
|
||||
self.cursor_from_layout(layout_cursor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,11 @@ impl TextureAtlas {
|
||||
|
||||
// Make the top left pixel fully white for `WHITE_UV`, i.e. painting something with solid color:
|
||||
let (pos, image) = atlas.allocate((1, 1));
|
||||
assert_eq!(pos, (0, 0));
|
||||
assert_eq!(
|
||||
pos,
|
||||
(0, 0),
|
||||
"Expected the first allocation to be at (0, 0), but was at {pos:?}"
|
||||
);
|
||||
image[pos] = 1.0;
|
||||
|
||||
// Allocate a series of anti-aliased discs used to render small filled circles:
|
||||
|
||||
Reference in New Issue
Block a user