egui/crates/emath/src/align.rs

266 lines
9.5 KiB
Rust
Raw Normal View History

2021-01-10 10:37:47 +00:00
//! One- and two-dimensional alignment ([`Align::Center`], [`Align2::LEFT_TOP`] etc).
2021-01-10 10:37:47 +00:00
use crate::*;
2021-01-17 09:52:01 +00:00
/// left/center/right or top/center/bottom alignment for e.g. anchors and layouts.
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Align {
/// Left or top.
Min,
/// Horizontal or vertical center.
Center,
/// Right or bottom.
Max,
}
impl Align {
/// Convenience for [`Self::Min`]
pub const LEFT: Self = Self::Min;
/// Convenience for [`Self::Max`]
pub const RIGHT: Self = Self::Max;
/// Convenience for [`Self::Min`]
pub const TOP: Self = Self::Min;
/// Convenience for [`Self::Max`]
pub const BOTTOM: Self = Self::Max;
2021-01-10 10:37:47 +00:00
/// Convert `Min => 0.0`, `Center => 0.5` or `Max => 1.0`.
#[inline(always)]
2021-06-22 21:25:54 +00:00
pub fn to_factor(self) -> f32 {
match self {
Self::Min => 0.0,
Self::Center => 0.5,
Self::Max => 1.0,
}
}
/// Convert `Min => -1.0`, `Center => 0.0` or `Max => 1.0`.
#[inline(always)]
2021-06-22 21:25:54 +00:00
pub fn to_sign(self) -> f32 {
match self {
Self::Min => -1.0,
Self::Center => 0.0,
Self::Max => 1.0,
}
}
/// Returns a range of given size within a specified range.
///
/// If the requested `size` is bigger than the size of `range`, then the returned
/// range will not fit into the available `range`. The extra space will be allocated
/// from:
///
/// |Align |Side |
/// |------|------------|
/// |Min |right (end) |
/// |Center|both |
/// |Max |left (start)|
///
/// # Examples
/// ```
/// use std::f32::{INFINITY, NEG_INFINITY};
/// use emath::Align::*;
///
/// // The size is smaller than a range
/// assert_eq!(Min .align_size_within_range(2.0, 10.0..=20.0), 10.0..=12.0);
/// assert_eq!(Center.align_size_within_range(2.0, 10.0..=20.0), 14.0..=16.0);
/// assert_eq!(Max .align_size_within_range(2.0, 10.0..=20.0), 18.0..=20.0);
///
/// // The size is bigger than a range
/// assert_eq!(Min .align_size_within_range(20.0, 10.0..=20.0), 10.0..=30.0);
/// assert_eq!(Center.align_size_within_range(20.0, 10.0..=20.0), 5.0..=25.0);
/// assert_eq!(Max .align_size_within_range(20.0, 10.0..=20.0), 0.0..=20.0);
///
/// // The size is infinity, but range is finite - a special case of a previous example
/// assert_eq!(Min .align_size_within_range(INFINITY, 10.0..=20.0), 10.0..=INFINITY);
/// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=INFINITY);
/// assert_eq!(Max .align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=20.0);
/// ```
///
/// The infinity-sized ranges can produce a surprising results, if the size is also infinity,
/// use such ranges with carefully!
///
/// ```
/// use std::f32::{INFINITY, NEG_INFINITY};
/// use emath::Align::*;
///
/// // Allocating a size aligned for infinity bound will lead to empty ranges!
/// assert_eq!(Min .align_size_within_range(2.0, 10.0..=INFINITY), 10.0..=12.0);
/// assert_eq!(Center.align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
/// assert_eq!(Max .align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
///
/// assert_eq!(Min .align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
/// assert_eq!(Center.align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
/// assert_eq!(Max .align_size_within_range(2.0, NEG_INFINITY..=20.0), 18.0..=20.0);
///
///
/// // The infinity size will always return the given range if it has at least one infinity bound
/// assert_eq!(Min .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
/// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
/// assert_eq!(Max .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
///
/// assert_eq!(Min .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
/// assert_eq!(Center.align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
/// assert_eq!(Max .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
/// ```
#[inline]
pub fn align_size_within_range(
self,
size: f32,
range: RangeInclusive<f32>,
) -> RangeInclusive<f32> {
let min = *range.start();
let max = *range.end();
if max - min == f32::INFINITY && size == f32::INFINITY {
return range;
}
match self {
Self::Min => min..=min + size,
Self::Center => {
if size == f32::INFINITY {
f32::NEG_INFINITY..=f32::INFINITY
} else {
let left = (min + max) / 2.0 - size / 2.0;
left..=left + size
}
}
Self::Max => max - size..=max,
}
}
}
impl Default for Align {
#[inline(always)]
fn default() -> Align {
Align::Min
}
}
2021-01-10 09:51:16 +00:00
// ----------------------------------------------------------------------------
2021-01-10 10:37:47 +00:00
/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
New text layout (#682) This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`. This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor. One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley. ## Performance Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!). Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that. All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2021-01-10 09:51:16 +00:00
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Align2(pub [Align; 2]);
2021-01-10 09:51:16 +00:00
impl Align2 {
pub const LEFT_BOTTOM: Align2 = Align2([Align::Min, Align::Max]);
pub const LEFT_CENTER: Align2 = Align2([Align::Min, Align::Center]);
pub const LEFT_TOP: Align2 = Align2([Align::Min, Align::Min]);
pub const CENTER_BOTTOM: Align2 = Align2([Align::Center, Align::Max]);
pub const CENTER_CENTER: Align2 = Align2([Align::Center, Align::Center]);
pub const CENTER_TOP: Align2 = Align2([Align::Center, Align::Min]);
pub const RIGHT_BOTTOM: Align2 = Align2([Align::Max, Align::Max]);
pub const RIGHT_CENTER: Align2 = Align2([Align::Max, Align::Center]);
pub const RIGHT_TOP: Align2 = Align2([Align::Max, Align::Min]);
}
2021-01-10 09:41:04 +00:00
2021-01-10 09:51:16 +00:00
impl Align2 {
/// Returns an alignment by the X (horizontal) axis
#[inline(always)]
2021-01-10 09:51:16 +00:00
pub fn x(self) -> Align {
self.0[0]
}
/// Returns an alignment by the Y (vertical) axis
#[inline(always)]
2021-01-10 09:51:16 +00:00
pub fn y(self) -> Align {
self.0[1]
}
2021-01-10 09:41:04 +00:00
/// -1, 0, or +1 for each axis
2021-06-22 21:25:54 +00:00
pub fn to_sign(self) -> Vec2 {
vec2(self.x().to_sign(), self.y().to_sign())
}
2021-01-10 09:51:16 +00:00
/// Used e.g. to anchor a piece of text to a part of the rectangle.
/// Give a position within the rect, specified by the aligns
2021-01-10 10:37:47 +00:00
pub fn anchor_rect(self, rect: Rect) -> Rect {
2021-01-10 09:51:16 +00:00
let x = match self.x() {
Align::Min => rect.left(),
Align::Center => rect.left() - 0.5 * rect.width(),
Align::Max => rect.left() - rect.width(),
};
let y = match self.y() {
Align::Min => rect.top(),
Align::Center => rect.top() - 0.5 * rect.height(),
Align::Max => rect.top() - rect.height(),
};
Rect::from_min_size(pos2(x, y), rect.size())
}
/// e.g. center a size within a given frame
pub fn align_size_within_rect(self, size: Vec2, frame: Rect) -> Rect {
let x_range = self.x().align_size_within_range(size.x, frame.x_range());
let y_range = self.y().align_size_within_range(size.y, frame.y_range());
Rect::from_x_y_ranges(x_range, y_range)
2021-01-10 09:51:16 +00:00
}
/// Returns the point on the rect's frame or in the center of a rect according
/// to the alignments of this object.
///
/// ```text
/// (*)-----------+------(*)------+-----------(*)--> X
/// | | | |
/// | Min, Min | Center, Min | Max, Min |
/// | | | |
/// +------------+---------------+------------+
/// | | | |
/// (*)Min, Center|Center(*)Center|Max, Center(*)
/// | | | |
/// +------------+---------------+------------+
/// | | | |
/// | Min, Max | Center, Max | Max, Max |
/// | | | |
/// (*)-----------+------(*)------+-----------(*)
/// |
/// Y
/// ```
pub fn pos_in_rect(self, frame: &Rect) -> Pos2 {
let x = match self.x() {
Align::Min => frame.left(),
Align::Center => frame.center().x,
Align::Max => frame.right(),
};
let y = match self.y() {
Align::Min => frame.top(),
Align::Center => frame.center().y,
Align::Max => frame.bottom(),
};
pos2(x, y)
}
2021-01-10 09:41:04 +00:00
}
impl std::ops::Index<usize> for Align2 {
type Output = Align;
#[inline(always)]
fn index(&self, index: usize) -> &Align {
&self.0[index]
}
}
impl std::ops::IndexMut<usize> for Align2 {
#[inline(always)]
fn index_mut(&mut self, index: usize) -> &mut Align {
&mut self.0[index]
}
}
/// Allocates a rectangle of the specified `size` inside the `frame` rectangle
/// around of its center.
///
/// If `size` is bigger than the `frame`s size the returned rect will bounce out
/// of the `frame`.
2021-01-10 09:41:04 +00:00
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
2021-01-10 09:51:16 +00:00
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
2021-01-10 09:41:04 +00:00
}