1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 13:50:04 -04:00

Move egui/math into new crate emath

This commit is contained in:
Emil Ernerfeldt
2021-01-10 11:37:47 +01:00
parent aee1474b6e
commit a0b0f36d29
38 changed files with 187 additions and 97 deletions

View File

@@ -19,6 +19,8 @@ include = [
[lib]
[dependencies]
emath = { path = "../emath" }
ahash = { version = "0.6", features = ["std"], default-features = false }
atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using Egui in a single thread. About as fast as parking_lot. Panics on multi-threaded use of egui::Context.
parking_lot = { version = "0.11", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios
@@ -27,6 +29,7 @@ serde = { version = "1", features = ["derive", "rc"], optional = true }
[features]
default = ["atomic_refcell", "default_fonts"]
persistence = ["serde", "emath/serde"]
# If set, egui will use `include_bytes!` to bundle some fonts.
# If you plan on specifying your own fonts you may disable this feature.

View File

@@ -8,7 +8,7 @@ use crate::*;
/// State that is persisted between frames
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
/// Last known pos
pub pos: Pos2,

View File

@@ -7,8 +7,8 @@ use crate::{
};
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub(crate) struct State {
open: bool,
@@ -114,7 +114,7 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
let rect = Rect::from_center_size(rect.center(), vec2(rect.width(), rect.height()) * 0.75);
let mut points = vec![rect.left_top(), rect.right_top(), rect.center_bottom()];
use std::f32::consts::TAU;
let rotation = Rot2::from_angle(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
let rotation = math::Rot2::from_angle(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
for p in &mut points {
*p = rect.center() + rotation * (*p - rect.center());
}

View File

@@ -1,7 +1,7 @@
use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
/// This is the size that the user has picked by dragging the resize handles.
/// This may be smaller and/or larger than the actual size.

View File

@@ -1,8 +1,8 @@
use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub(crate) struct State {
/// Positive offset means scrolling down/right
offset: Vec2,
@@ -10,7 +10,7 @@ pub(crate) struct State {
show_scroll: bool,
/// Momentum, used for kinetic scrolling
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub vel: Vec2,
/// Mouse offset relative to the top of the handle when started moving the handle.
scroll_start_offset_from_top: Option<f32>,
@@ -177,7 +177,7 @@ impl Prepared {
// We take the scroll target so only this ScrollArea will use it.
let scroll_target = content_ui.ctx().frame_state().scroll_target.take();
if let Some((scroll_y, align)) = scroll_target {
let center_factor = align.scroll_center_factor();
let center_factor = align.to_factor();
let top = content_ui.min_rect().top();
let visible_range = top..=top + content_ui.clip_rect().height();

View File

@@ -720,7 +720,7 @@ impl TitleBar {
self.title_label = self.title_label.text_color(style.fg_stroke.color);
let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
let text_pos = align::center_size_in_rect(self.title_galley.size, full_top_rect);
let text_pos = math::align::center_size_in_rect(self.title_galley.size, full_top_rect);
let text_pos = text_pos.left_top() - 2.0 * Vec2::Y; // HACK: center on x-height of text (looks better)
self.title_label
.paint_galley(ui, text_pos, self.title_galley);

View File

@@ -28,7 +28,7 @@ use std::hash::Hash;
/// Then there are widgets that need no identifiers at all, like labels,
/// because they have no state nor are interacted with.
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Id(u64);
impl Id {

View File

@@ -1,6 +1,5 @@
//! uis for egui types.
use crate::{
math::*,
paint::{self, PaintCmd, Texture, Triangles},
*,
};

View File

@@ -4,7 +4,7 @@ use crate::{math::Rect, paint::PaintCmd, Id, *};
/// Different layer categories
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub enum Order {
/// Painted behind all floating windows
Background,
@@ -40,7 +40,7 @@ impl Order {
/// An identifier for a paint layer.
/// Also acts as an identifier for [`Area`]:s.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct LayerId {
pub order: Order,
pub id: Id,

View File

@@ -66,8 +66,8 @@ impl Region {
/// Layout direction, one of `LeftToRight`, `RightToLeft`, `TopDown`, `BottomUp`.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(rename_all = "snake_case"))]
pub enum Direction {
LeftToRight,
RightToLeft,
@@ -95,7 +95,7 @@ impl Direction {
/// The layout of a [`Ui`][`crate::Ui`], e.g. "vertical & centered".
#[derive(Clone, Copy, Debug, PartialEq)]
// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
// #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Layout {
/// Main axis direction
main_dir: Direction,

View File

@@ -86,7 +86,6 @@ mod input;
mod introspection;
mod layers;
mod layout;
pub mod math;
mod memory;
pub mod menu;
pub mod paint;
@@ -97,6 +96,8 @@ mod ui;
pub mod util;
pub mod widgets;
pub use emath as math;
pub use {
containers::*,
context::{Context, CtxRef},
@@ -104,7 +105,7 @@ pub use {
input::*,
layers::*,
layout::*,
math::*,
math::{clamp, lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2},
memory::Memory,
paint::{
color, Color32, FontDefinitions, FontFamily, PaintCmd, PaintJobs, Rgba, Stroke, TextStyle,

View File

@@ -1,115 +0,0 @@
//! One- and two-dimensional alignment ([`Align::Center`], [`LEFT_TOP`] etc).
use crate::math::*;
/// left/center/right or top/center/bottom alignment for e.g. anchors and `Layout`s.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Align {
/// Left or top.
Min,
/// Horizontal or vertical center.
Center,
/// Right or bottom.
Max,
}
impl Align {
/// Convenience for [`Self::Min`]
pub fn left() -> Self {
Self::Min
}
/// Convenience for [`Self::Max`]
pub fn right() -> Self {
Self::Max
}
/// Convenience for [`Self::Min`]
pub fn top() -> Self {
Self::Min
}
/// Convenience for [`Self::Max`]
pub fn bottom() -> Self {
Self::Max
}
pub(crate) fn scroll_center_factor(&self) -> f32 {
match self {
Self::Min => 0.0,
Self::Center => 0.5,
Self::Max => 1.0,
}
}
}
impl Default for Align {
fn default() -> Align {
Align::Min
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub struct Align2(pub [Align; 2]);
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]);
}
impl Align2 {
pub fn x(self) -> Align {
self.0[0]
}
pub fn y(self) -> Align {
self.0[1]
}
/// 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
pub(crate) fn anchor_rect(self, rect: Rect) -> Rect {
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 = match self.x() {
Align::Min => frame.left(),
Align::Center => frame.center().x - size.x / 2.0,
Align::Max => frame.right() - size.x,
};
let y = match self.y() {
Align::Min => frame.top(),
Align::Center => frame.center().y - size.y / 2.0,
Align::Max => frame.bottom() - size.y,
};
Rect::from_min_size(Pos2::new(x, y), size)
}
}
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
}

View File

@@ -1,266 +0,0 @@
//! Vectors, positions, rectangles etc.
//!
//! Conventions (unless otherwise specified):
//! * All angles are in radians
//! * All metrics are in points (logical pixels)
use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
// ----------------------------------------------------------------------------
pub mod align;
mod pos2;
mod rect;
mod rot2;
pub mod smart_aim;
mod vec2;
pub use {
align::{Align, Align2},
pos2::*,
rect::*,
rot2::*,
vec2::*,
};
// ----------------------------------------------------------------------------
/// Helper trait to implement [`lerp`] and [`remap`].
pub trait One {
fn one() -> Self;
}
impl One for f32 {
fn one() -> Self {
1.0
}
}
impl One for f64 {
fn one() -> Self {
1.0
}
}
/// Helper trait to implement [`lerp`] and [`remap`].
pub trait Real:
Copy
+ PartialEq
+ PartialOrd
+ One
+ Add<Self, Output = Self>
+ Sub<Self, Output = Self>
+ Mul<Self, Output = Self>
+ Div<Self, Output = Self>
{
}
impl Real for f32 {}
impl Real for f64 {}
// ----------------------------------------------------------------------------
/// Linear interpolation.
pub fn lerp<R, T>(range: RangeInclusive<R>, t: T) -> R
where
T: Real + Mul<R, Output = R>,
R: Copy + Add<R, Output = R>,
{
(T::one() - t) * *range.start() + t * *range.end()
}
/// Linearly remap a value from one range to another,
/// so that when `x == from.start()` returns `to.start()`
/// and when `x == from.end()` returns `to.end()`.
pub fn remap<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
where
T: Real,
{
#![allow(clippy::float_cmp)]
debug_assert!(from.start() != from.end());
let t = (x - *from.start()) / (*from.end() - *from.start());
lerp(to, t)
}
/// Like `remap`, but also clamps the value so that the returned value is always in the `to` range.
pub fn remap_clamp<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
where
T: Real,
{
#![allow(clippy::float_cmp)]
if from.end() < from.start() {
return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start());
}
if x <= *from.start() {
*to.start()
} else if *from.end() <= x {
*to.end()
} else {
debug_assert!(from.start() != from.end());
let t = (x - *from.start()) / (*from.end() - *from.start());
// Ensure no numerical inaccuracies sneak in:
if T::one() <= t {
*to.end()
} else {
lerp(to, t)
}
}
}
/// Returns `range.start()` if `x <= range.start()`,
/// returns `range.end()` if `x >= range.end()`
/// and returns `x` elsewhen.
pub fn clamp<T>(x: T, range: RangeInclusive<T>) -> T
where
T: Copy + PartialOrd,
{
debug_assert!(range.start() <= range.end());
if x <= *range.start() {
*range.start()
} else if *range.end() <= x {
*range.end()
} else {
x
}
}
/// Round a value to the given number of decimal places.
pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 {
// This is a stupid way of doing this, but stupid works.
format!("{:.*}", decimal_places, value)
.parse()
.unwrap_or(value)
}
pub(crate) fn format_with_minimum_decimals(value: f64, decimals: usize) -> String {
format_with_decimals_in_range(value, decimals..=6)
}
pub(crate) fn format_with_decimals_in_range(
value: f64,
decimal_range: RangeInclusive<usize>,
) -> String {
let min_decimals = *decimal_range.start();
let max_decimals = *decimal_range.end();
debug_assert!(min_decimals <= max_decimals);
debug_assert!(max_decimals < 100);
let max_decimals = max_decimals.min(16);
let min_decimals = min_decimals.min(max_decimals);
if min_decimals == max_decimals {
format!("{:.*}", max_decimals, value)
} else {
// Ugly/slow way of doing this. TODO: clean up precision.
for decimals in min_decimals..max_decimals {
let text = format!("{:.*}", decimals, value);
let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
if almost_equal(text.parse::<f32>().unwrap(), value as f32, epsilon) {
// Enough precision to show the value accurately - good!
return text;
}
}
// The value has more precision than we expected.
// Probably the value was set not by the slider, but from outside.
// In any case: show the full value
format!("{:.*}", max_decimals, value)
}
}
/// Return true when arguments are the same within some rounding error.
///
/// For instance `almost_equal(x, x.to_degrees().to_radians(), f32::EPSILON)` should hold true for all x.
/// The `epsilon` can be `f32::EPSILON` to handle simple transforms (like degrees -> radians)
/// but should be higher to handle more complex transformations.
pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool {
#![allow(clippy::float_cmp)]
if a == b {
true // handle infinites
} else {
let abs_max = a.abs().max(b.abs());
abs_max <= epsilon || ((a - b).abs() / abs_max) <= epsilon
}
}
#[allow(clippy::approx_constant)]
#[test]
fn test_format() {
assert_eq!(format_with_minimum_decimals(1_234_567.0, 0), "1234567");
assert_eq!(format_with_minimum_decimals(1_234_567.0, 1), "1234567.0");
assert_eq!(format_with_minimum_decimals(3.14, 2), "3.14");
assert_eq!(format_with_minimum_decimals(3.14, 3), "3.140");
assert_eq!(
format_with_minimum_decimals(std::f64::consts::PI, 2),
"3.14159"
);
}
#[test]
fn test_almost_equal() {
for &x in &[
0.0_f32,
f32::MIN_POSITIVE,
1e-20,
1e-10,
f32::EPSILON,
0.1,
0.99,
1.0,
1.001,
1e10,
f32::MAX / 100.0,
// f32::MAX, // overflows in rad<->deg test
f32::INFINITY,
] {
for &x in &[-x, x] {
for roundtrip in &[
|x: f32| x.to_degrees().to_radians(),
|x: f32| x.to_radians().to_degrees(),
] {
let epsilon = f32::EPSILON;
assert!(
almost_equal(x, roundtrip(x), epsilon),
"{} vs {}",
x,
roundtrip(x)
);
}
}
}
}
#[allow(clippy::float_cmp)]
#[test]
fn test_remap() {
assert_eq!(remap_clamp(1.0, 0.0..=1.0, 0.0..=16.0), 16.0);
assert_eq!(remap_clamp(1.0, 1.0..=0.0, 16.0..=0.0), 16.0);
assert_eq!(remap_clamp(0.5, 1.0..=0.0, 16.0..=0.0), 8.0);
}
// ----------------------------------------------------------------------------
/// Extends `f32`, `Vec2` etc with `at_least` and `at_most` as aliases for `max` and `min`.
pub trait NumExt {
/// More readable version of `self.max(lower_limit)`
fn at_least(self, lower_limit: Self) -> Self;
/// More readable version of `self.min(upper_limit)`
fn at_most(self, upper_limit: Self) -> Self;
}
macro_rules! impl_num_ext {
($t: ty) => {
impl NumExt for $t {
fn at_least(self, lower_limit: Self) -> Self {
self.max(lower_limit)
}
fn at_most(self, upper_limit: Self) -> Self {
self.min(upper_limit)
}
}
};
}
impl_num_ext!(f32);
impl_num_ext!(f64);
impl_num_ext!(usize);
impl_num_ext!(Vec2);
impl_num_ext!(Pos2);

View File

@@ -1,153 +0,0 @@
use std::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign};
use crate::math::*;
/// A position on screen.
///
/// Normally given in points (logical pixels).
///
/// Mathematically this is known as a "point", but the term position was chosen so not to
/// conflict with the unit (one point = X physical pixels).
#[derive(Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Pos2 {
pub x: f32,
pub y: f32,
// implicit w = 1
}
/// `pos2(x,y) == Pos2::new(x, y)`
#[inline(always)]
pub const fn pos2(x: f32, y: f32) -> Pos2 {
Pos2 { x, y }
}
impl From<[f32; 2]> for Pos2 {
fn from(v: [f32; 2]) -> Self {
Self { x: v[0], y: v[1] }
}
}
impl From<&[f32; 2]> for Pos2 {
fn from(v: &[f32; 2]) -> Self {
Self { x: v[0], y: v[1] }
}
}
impl Pos2 {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
/// The vector from origin to this position.
/// `p.to_vec2()` is equivalent to `p - Pos2::default()`.
pub fn to_vec2(self) -> Vec2 {
Vec2 {
x: self.x,
y: self.y,
}
}
pub fn distance(self, other: Self) -> f32 {
(self - other).length()
}
pub fn distance_sq(self, other: Self) -> f32 {
(self - other).length_sq()
}
pub fn floor(self) -> Self {
pos2(self.x.floor(), self.y.floor())
}
pub fn round(self) -> Self {
pos2(self.x.round(), self.y.round())
}
pub fn ceil(self) -> Self {
pos2(self.x.ceil(), self.y.ceil())
}
pub fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
#[must_use]
pub fn min(self, other: Self) -> Self {
pos2(self.x.min(other.x), self.y.min(other.y))
}
#[must_use]
pub fn max(self, other: Self) -> Self {
pos2(self.x.max(other.x), self.y.max(other.y))
}
#[must_use]
pub fn clamp(self, range: RangeInclusive<Self>) -> Self {
Self {
x: clamp(self.x, range.start().x..=range.end().x),
y: clamp(self.y, range.start().y..=range.end().y),
}
}
}
impl PartialEq for Pos2 {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y
}
}
impl Eq for Pos2 {}
impl AddAssign<Vec2> for Pos2 {
fn add_assign(&mut self, rhs: Vec2) {
*self = Pos2 {
x: self.x + rhs.x,
y: self.y + rhs.y,
};
}
}
impl SubAssign<Vec2> for Pos2 {
fn sub_assign(&mut self, rhs: Vec2) {
*self = Pos2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
};
}
}
impl Add<Vec2> for Pos2 {
type Output = Pos2;
fn add(self, rhs: Vec2) -> Pos2 {
Pos2 {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl Sub for Pos2 {
type Output = Vec2;
fn sub(self, rhs: Pos2) -> Vec2 {
Vec2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl Sub<Vec2> for Pos2 {
type Output = Pos2;
fn sub(self, rhs: Vec2) -> Pos2 {
Pos2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl std::fmt::Debug for Pos2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{:.1} {:.1}]", self.x, self.y)
}
}

View File

@@ -1,233 +0,0 @@
use std::ops::RangeInclusive;
use crate::math::*;
/// A rectangular region of space.
///
/// Normally given in points, e.g. logical pixels.
#[derive(Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Rect {
pub min: Pos2,
pub max: Pos2,
}
impl Rect {
/// Infinite rectangle that contains everything
pub fn everything() -> Self {
let inf = f32::INFINITY;
Self {
min: pos2(-inf, -inf),
max: pos2(inf, inf),
}
}
pub fn nothing() -> Self {
let inf = f32::INFINITY;
Self {
min: pos2(inf, inf),
max: pos2(-inf, -inf),
}
}
/// invalid, NAN filled Rect.
pub fn invalid() -> Self {
Self {
min: pos2(f32::NAN, f32::NAN),
max: pos2(f32::NAN, f32::NAN),
}
}
pub fn from_min_max(min: Pos2, max: Pos2) -> Self {
Rect { min, max }
}
pub fn from_min_size(min: Pos2, size: Vec2) -> Self {
Rect {
min,
max: min + size,
}
}
pub fn from_center_size(center: Pos2, size: Vec2) -> Self {
Rect {
min: center - size * 0.5,
max: center + size * 0.5,
}
}
pub fn from_x_y_ranges(x_range: RangeInclusive<f32>, y_range: RangeInclusive<f32>) -> Self {
Rect {
min: pos2(*x_range.start(), *y_range.start()),
max: pos2(*x_range.end(), *y_range.end()),
}
}
/// Expand by this much in each direction, keeping the center
#[must_use]
pub fn expand(self, amnt: f32) -> Self {
self.expand2(Vec2::splat(amnt))
}
/// Expand by this much in each direction, keeping the center
#[must_use]
pub fn expand2(self, amnt: Vec2) -> Self {
Rect::from_min_max(self.min - amnt, self.max + amnt)
}
/// Shrink by this much in each direction, keeping the center
#[must_use]
pub fn shrink(self, amnt: f32) -> Self {
self.shrink2(Vec2::splat(amnt))
}
/// Shrink by this much in each direction, keeping the center
#[must_use]
pub fn shrink2(self, amnt: Vec2) -> Self {
Rect::from_min_max(self.min + amnt, self.max - amnt)
}
#[must_use]
pub fn translate(self, amnt: Vec2) -> Self {
Rect::from_min_size(self.min + amnt, self.size())
}
#[must_use]
pub fn intersect(self, other: Rect) -> Self {
Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
}
}
#[must_use]
pub fn intersects(self, other: Rect) -> bool {
self.min.x <= other.max.x
&& other.min.x <= self.max.x
&& self.min.y <= other.max.y
&& other.min.y <= self.max.y
}
/// keep min
pub fn set_width(&mut self, w: f32) {
self.max.x = self.min.x + w;
}
/// keep min
pub fn set_height(&mut self, h: f32) {
self.max.y = self.min.y + h;
}
/// Keep size
pub fn set_center(&mut self, center: Pos2) {
*self = self.translate(center - self.center());
}
#[must_use]
pub fn contains(&self, p: Pos2) -> bool {
self.min.x <= p.x
&& p.x <= self.min.x + self.size().x
&& self.min.y <= p.y
&& p.y <= self.min.y + self.size().y
}
pub fn extend_with(&mut self, p: Pos2) {
self.min = self.min.min(p);
self.max = self.max.max(p);
}
pub fn union(self, other: Rect) -> Rect {
Rect {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
pub fn center(&self) -> Pos2 {
Pos2 {
x: self.min.x + self.size().x / 2.0,
y: self.min.y + self.size().y / 2.0,
}
}
pub fn size(&self) -> Vec2 {
self.max - self.min
}
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
pub fn area(&self) -> f32 {
self.width() * self.height()
}
pub fn x_range(&self) -> RangeInclusive<f32> {
self.min.x..=self.max.x
}
pub fn y_range(&self) -> RangeInclusive<f32> {
self.min.y..=self.max.y
}
pub fn bottom_up_range(&self) -> RangeInclusive<f32> {
self.max.y..=self.min.y
}
pub fn is_empty(&self) -> bool {
self.max.x < self.min.x || self.max.y < self.min.y
}
pub fn is_finite(&self) -> bool {
self.min.is_finite() && self.max.is_finite()
}
// Convenience functions (assumes origin is towards left top):
pub fn left(&self) -> f32 {
self.min.x
}
pub fn right(&self) -> f32 {
self.max.x
}
pub fn top(&self) -> f32 {
self.min.y
}
pub fn bottom(&self) -> f32 {
self.max.y
}
pub fn left_top(&self) -> Pos2 {
pos2(self.left(), self.top())
}
pub fn center_top(&self) -> Pos2 {
pos2(self.center().x, self.top())
}
pub fn right_top(&self) -> Pos2 {
pos2(self.right(), self.top())
}
pub fn left_center(&self) -> Pos2 {
pos2(self.left(), self.center().y)
}
pub fn right_center(&self) -> Pos2 {
pos2(self.right(), self.center().y)
}
pub fn left_bottom(&self) -> Pos2 {
pos2(self.left(), self.bottom())
}
pub fn center_bottom(&self) -> Pos2 {
pos2(self.center().x, self.bottom())
}
pub fn right_bottom(&self) -> Pos2 {
pos2(self.right(), self.bottom())
}
}
impl std::fmt::Debug for Rect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{:?} - {:?}]", self.min, self.max)
}
}
/// from (min, max) or (left top, right bottom)
impl From<[Pos2; 2]> for Rect {
fn from([min, max]: [Pos2; 2]) -> Self {
Self { min, max }
}
}

View File

@@ -1,183 +0,0 @@
use super::Vec2;
// {s,c} represents the rotation matrix:
//
// | c -s |
// | s c |
//
// `vec2(c,s)` represents where the X axis will end up after rotation.
//
/// Represents a rotation in the 2D plane.
/// A rotation of 𝞃/4 = 90° rotates the X axis to the Y axis.
/// Normally a `Rot2` is normalized (unit-length).
/// If not, it will also scale vectors.
#[derive(Clone, Copy, PartialEq)]
pub struct Rot2 {
/// angle.sin()
s: f32,
/// angle.cos()
c: f32,
}
/// Identity rotation
impl Default for Rot2 {
/// Identity rotation
fn default() -> Self {
Self { s: 0.0, c: 1.0 }
}
}
impl Rot2 {
pub fn identity() -> Self {
Self { s: 0.0, c: 1.0 }
}
/// A 𝞃/4 = 90° rotation means rotating the X axis to the Y axis.
pub fn from_angle(angle: f32) -> Self {
let (s, c) = angle.sin_cos();
Self { s, c }
}
pub fn angle(self) -> f32 {
self.s.atan2(self.c)
}
/// The factor by which vectors will be scaled.
pub fn length(self) -> f32 {
self.c.hypot(self.s)
}
pub fn length_squared(self) -> f32 {
self.c.powi(2) + self.s.powi(2)
}
pub fn is_finite(self) -> bool {
self.c.is_finite() && self.s.is_finite()
}
#[must_use]
pub fn inverse(self) -> Rot2 {
Self {
s: -self.s,
c: self.c,
} / self.length_squared()
}
#[must_use]
pub fn normalized(self) -> Self {
let l = self.length();
let ret = Self {
c: self.c / l,
s: self.s / l,
};
debug_assert!(ret.is_finite());
ret
}
}
impl std::fmt::Debug for Rot2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Rot2 {{ angle: {:.1}°, length: {} }}",
self.angle().to_degrees(),
self.length()
)
}
}
impl std::ops::Mul<Rot2> for Rot2 {
type Output = Rot2;
fn mul(self, r: Rot2) -> Rot2 {
/*
|lc -ls| * |rc -rs|
|ls lc| |rs rc|
*/
Rot2 {
c: self.c * r.c - self.s * r.s,
s: self.s * r.c + self.c * r.s,
}
}
}
impl std::ops::Mul<Vec2> for Rot2 {
type Output = Vec2;
fn mul(self, v: Vec2) -> Vec2 {
Vec2 {
x: self.c * v.x - self.s * v.y,
y: self.s * v.x + self.c * v.y,
}
}
}
impl std::ops::Mul<Rot2> for f32 {
type Output = Rot2;
fn mul(self, r: Rot2) -> Rot2 {
Rot2 {
c: self * r.c,
s: self * r.s,
}
}
}
impl std::ops::Mul<f32> for Rot2 {
type Output = Rot2;
fn mul(self, r: f32) -> Rot2 {
Rot2 {
c: self.c * r,
s: self.s * r,
}
}
}
impl std::ops::Div<f32> for Rot2 {
type Output = Rot2;
fn div(self, r: f32) -> Rot2 {
Rot2 {
c: self.c / r,
s: self.s / r,
}
}
}
#[cfg(test)]
mod test {
use super::Rot2;
use crate::vec2;
#[test]
fn test_rotation2() {
{
let angle = std::f32::consts::TAU / 6.0;
let rot = Rot2::from_angle(angle);
assert!((rot.angle() - angle).abs() < 1e-5);
assert!((rot * rot.inverse()).angle().abs() < 1e-5);
assert!((rot.inverse() * rot).angle().abs() < 1e-5);
}
{
let angle = std::f32::consts::TAU / 4.0;
let rot = Rot2::from_angle(angle);
assert!(((rot * vec2(1.0, 0.0)) - vec2(0.0, 1.0)).length() < 1e-5);
}
{
// Test rotation and scaling
let angle = std::f32::consts::TAU / 4.0;
let rot = 3.0 * Rot2::from_angle(angle);
let rotated = rot * vec2(1.0, 0.0);
let expected = vec2(0.0, 3.0);
assert!(
(rotated - expected).length() < 1e-5,
"Expected {:?} to equal {:?}. rot: {:?}",
rotated,
expected,
rot,
);
let undone = rot.inverse() * rot;
assert!(undone.angle().abs() < 1e-5);
assert!((undone.length() - 1.0).abs() < 1e-5,);
}
}
}

View File

@@ -1,159 +0,0 @@
//! Find "simple" numbers is some range. Used by sliders.
#![allow(clippy::float_cmp)] // I know what I'm doing
const NUM_DECIMALS: usize = 15;
/// Find the "simplest" number in a closed range [min, max], i.e. the one with the fewest decimal digits.
///
/// So in the range `[0.83, 1.354]` you will get `1.0`, and for `[0.37, 0.48]` you will get `0.4`.
/// This is used when dragging sliders etc to get the values that users are most likely to desire.
/// This assumes a decimal centric user.
pub fn best_in_range_f64(min: f64, max: f64) -> f64 {
// Avoid NaN if we can:
if min.is_nan() {
return max;
}
if max.is_nan() {
return min;
}
if max < min {
return best_in_range_f64(max, min);
}
if min == max {
return min;
}
if min <= 0.0 && 0.0 <= max {
return 0.0; // always prefer zero
}
if min < 0.0 {
return -best_in_range_f64(-max, -min);
}
// Prefer finite numbers:
if !max.is_finite() {
return min;
}
debug_assert!(min.is_finite() && max.is_finite());
let min_exponent = min.log10();
let max_exponent = max.log10();
if min_exponent.floor() != max_exponent.floor() {
// pick the geometric center of the two:
let exponent = (min_exponent + max_exponent) / 2.0;
return 10.0_f64.powi(exponent.round() as i32);
}
if is_integer(min_exponent) {
return 10.0_f64.powf(min_exponent);
}
if is_integer(max_exponent) {
return 10.0_f64.powf(max_exponent);
}
let exp_factor = 10.0_f64.powi(max_exponent.floor() as i32);
let min_str = to_decimal_string(min / exp_factor);
let max_str = to_decimal_string(max / exp_factor);
// eprintln!("min_str: {:?}", min_str);
// eprintln!("max_str: {:?}", max_str);
let mut ret_str = [0; NUM_DECIMALS];
// Select the common prefix:
let mut i = 0;
while i < NUM_DECIMALS && max_str[i] == min_str[i] {
ret_str[i] = max_str[i];
i += 1;
}
if i < NUM_DECIMALS {
// Pick the deciding digit.
// Note that "to_decimal_string" rounds down, so we that's why we add 1 here
ret_str[i] = simplest_digit_closed_range(min_str[i] + 1, max_str[i]);
}
from_decimal_string(&ret_str) * exp_factor
}
fn is_integer(f: f64) -> bool {
f.round() == f
}
fn to_decimal_string(v: f64) -> [i32; NUM_DECIMALS] {
debug_assert!(v < 10.0, "{:?}", v);
let mut digits = [0; NUM_DECIMALS];
let mut v = v.abs();
for r in digits.iter_mut() {
let digit = v.floor();
*r = digit as i32;
v -= digit;
v *= 10.0;
}
digits
}
fn from_decimal_string(s: &[i32]) -> f64 {
let mut ret: f64 = 0.0;
for (i, &digit) in s.iter().enumerate() {
ret += (digit as f64) * 10.0_f64.powi(-(i as i32));
}
ret
}
/// Find the simplest integer in the range [min, max]
fn simplest_digit_closed_range(min: i32, max: i32) -> i32 {
debug_assert!(1 <= min && min <= max && max <= 9);
if min <= 5 && 5 <= max {
5
} else {
(min + max) / 2
}
}
#[allow(clippy::approx_constant)]
#[test]
fn test_aim() {
assert_eq!(best_in_range_f64(-0.2, 0.0), 0.0, "Prefer zero");
assert_eq!(best_in_range_f64(-10_004.23, 3.14), 0.0, "Prefer zero");
assert_eq!(best_in_range_f64(-0.2, 100.0), 0.0, "Prefer zero");
assert_eq!(best_in_range_f64(0.2, 0.0), 0.0, "Prefer zero");
assert_eq!(best_in_range_f64(7.8, 17.8), 10.0);
assert_eq!(best_in_range_f64(99.0, 300.0), 100.0);
assert_eq!(best_in_range_f64(-99.0, -300.0), -100.0);
assert_eq!(best_in_range_f64(0.4, 0.9), 0.5, "Prefer ending on 5");
assert_eq!(best_in_range_f64(14.1, 19.99), 15.0, "Prefer ending on 5");
assert_eq!(best_in_range_f64(12.3, 65.9), 50.0, "Prefer leading 5");
assert_eq!(best_in_range_f64(493.0, 879.0), 500.0, "Prefer leading 5");
assert_eq!(best_in_range_f64(0.37, 0.48), 0.40);
// assert_eq!(best_in_range_f64(123.71, 123.76), 123.75); // TODO: we get 123.74999999999999 here
// assert_eq!(best_in_range_f32(123.71, 123.76), 123.75);
assert_eq!(best_in_range_f64(7.5, 16.3), 10.0);
assert_eq!(best_in_range_f64(7.5, 76.3), 10.0);
assert_eq!(best_in_range_f64(7.5, 763.3), 100.0);
assert_eq!(best_in_range_f64(7.5, 1_345.0), 100.0);
assert_eq!(best_in_range_f64(7.5, 123_456.0), 1000.0, "Geometric mean");
assert_eq!(best_in_range_f64(9.9999, 99.999), 10.0);
assert_eq!(best_in_range_f64(10.000, 99.999), 10.0);
assert_eq!(best_in_range_f64(10.001, 99.999), 50.0);
assert_eq!(best_in_range_f64(10.001, 100.000), 100.0);
assert_eq!(best_in_range_f64(99.999, 100.000), 100.0);
assert_eq!(best_in_range_f64(10.001, 100.001), 100.0);
use std::f64::{INFINITY, NAN, NEG_INFINITY};
assert!(best_in_range_f64(NAN, NAN).is_nan());
assert_eq!(best_in_range_f64(NAN, 1.2), 1.2);
assert_eq!(best_in_range_f64(NAN, INFINITY), INFINITY);
assert_eq!(best_in_range_f64(1.2, NAN), 1.2);
assert_eq!(best_in_range_f64(1.2, INFINITY), 1.2);
assert_eq!(best_in_range_f64(INFINITY, 1.2), 1.2);
assert_eq!(best_in_range_f64(NEG_INFINITY, 1.2), 0.0);
assert_eq!(best_in_range_f64(NEG_INFINITY, -2.7), -2.7);
assert_eq!(best_in_range_f64(INFINITY, INFINITY), INFINITY);
assert_eq!(best_in_range_f64(NEG_INFINITY, NEG_INFINITY), NEG_INFINITY);
assert_eq!(best_in_range_f64(NEG_INFINITY, INFINITY), 0.0);
assert_eq!(best_in_range_f64(INFINITY, NEG_INFINITY), 0.0);
}

View File

@@ -1,237 +0,0 @@
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, RangeInclusive, Sub, SubAssign};
use crate::math::*;
/// A vector has a direction and length.
/// A [`Vec2`] is often used to represent a size.
///
/// Egui represents positions using [`Pos2`].
///
/// Normally the units are points (logical pixels).
#[derive(Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
/// `vec2(x,y) == Vec2::new(x, y)`
#[inline(always)]
pub const fn vec2(x: f32, y: f32) -> Vec2 {
Vec2 { x, y }
}
impl From<[f32; 2]> for Vec2 {
fn from(v: [f32; 2]) -> Self {
Self { x: v[0], y: v[1] }
}
}
impl From<&[f32; 2]> for Vec2 {
fn from(v: &[f32; 2]) -> Self {
Self { x: v[0], y: v[1] }
}
}
impl Vec2 {
pub const X: Vec2 = Vec2 { x: 1.0, y: 0.0 };
pub const Y: Vec2 = Vec2 { x: 0.0, y: 1.0 };
pub fn zero() -> Self {
Self { x: 0.0, y: 0.0 }
}
pub fn infinity() -> Self {
Self {
x: f32::INFINITY,
y: f32::INFINITY,
}
}
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn splat(v: impl Into<f32>) -> Self {
let v: f32 = v.into();
Self { x: v, y: v }
}
#[must_use]
pub fn normalized(self) -> Self {
let len = self.length();
if len <= 0.0 {
self
} else {
self / len
}
}
/// Rotates the vector by 90°, i.e positive X to positive Y
/// (clockwise in Egui coordinates).
#[inline(always)]
pub fn rot90(self) -> Self {
vec2(self.y, -self.x)
}
pub fn length(self) -> f32 {
self.x.hypot(self.y)
}
pub fn length_sq(self) -> f32 {
self.x * self.x + self.y * self.y
}
/// Create a unit vector with the given angle (in radians).
/// * An angle of zero gives the unit X axis.
/// * An angle of 𝞃/4 = 90° gives the unit Y axis.
pub fn angled(angle: f32) -> Self {
vec2(angle.cos(), angle.sin())
}
#[must_use]
pub fn floor(self) -> Self {
vec2(self.x.floor(), self.y.floor())
}
#[must_use]
pub fn round(self) -> Self {
vec2(self.x.round(), self.y.round())
}
#[must_use]
pub fn ceil(self) -> Self {
vec2(self.x.ceil(), self.y.ceil())
}
/// True if all members are also finite.
pub fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
#[must_use]
pub fn min(self, other: Self) -> Self {
vec2(self.x.min(other.x), self.y.min(other.y))
}
#[must_use]
pub fn max(self, other: Self) -> Self {
vec2(self.x.max(other.x), self.y.max(other.y))
}
/// Returns the minimum of `self.x` and `self.y`.
#[must_use]
pub fn min_elem(self) -> f32 {
self.x.min(self.y)
}
/// Returns the maximum of `self.x` and `self.y`.
#[must_use]
pub fn max_elem(self) -> f32 {
self.x.max(self.y)
}
#[must_use]
pub fn clamp(self, range: RangeInclusive<Self>) -> Self {
Self {
x: clamp(self.x, range.start().x..=range.end().x),
y: clamp(self.y, range.start().y..=range.end().y),
}
}
}
impl PartialEq for Vec2 {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y
}
}
impl Eq for Vec2 {}
impl Neg for Vec2 {
type Output = Vec2;
fn neg(self) -> Vec2 {
vec2(-self.x, -self.y)
}
}
impl AddAssign for Vec2 {
fn add_assign(&mut self, rhs: Vec2) {
*self = Vec2 {
x: self.x + rhs.x,
y: self.y + rhs.y,
};
}
}
impl SubAssign for Vec2 {
fn sub_assign(&mut self, rhs: Vec2) {
*self = Vec2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
};
}
}
impl Add for Vec2 {
type Output = Vec2;
fn add(self, rhs: Vec2) -> Vec2 {
Vec2 {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl Sub for Vec2 {
type Output = Vec2;
fn sub(self, rhs: Vec2) -> Vec2 {
Vec2 {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl MulAssign<f32> for Vec2 {
fn mul_assign(&mut self, rhs: f32) {
self.x *= rhs;
self.y *= rhs;
}
}
impl Mul<f32> for Vec2 {
type Output = Vec2;
fn mul(self, factor: f32) -> Vec2 {
Vec2 {
x: self.x * factor,
y: self.y * factor,
}
}
}
impl Mul<Vec2> for f32 {
type Output = Vec2;
fn mul(self, vec: Vec2) -> Vec2 {
Vec2 {
x: self * vec.x,
y: self * vec.y,
}
}
}
impl Div<f32> for Vec2 {
type Output = Vec2;
fn div(self, factor: f32) -> Vec2 {
Vec2 {
x: self.x / factor,
y: self.y / factor,
}
}
}
impl std::fmt::Debug for Vec2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{:.1} {:.1}]", self.x, self.y)
}
}

View File

@@ -18,50 +18,50 @@ use crate::{
///
/// If you want this to persist when closing your app you should serialize `Memory` and store it.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Memory {
pub(crate) options: Options,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) interaction: Interaction,
// states of various types of widgets
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) menu_bar: HashMap<Id, menu::BarState>,
pub(crate) resize: HashMap<Id, resize::State>,
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
pub(crate) text_edit: HashMap<Id, text_edit::State>,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) window_interaction: Option<window::WindowInteraction>,
/// For temporary edit of e.g. a slider value.
/// Couples with [`Interaction::kb_focus_id`].
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) temp_edit_string: Option<String>,
pub(crate) areas: Areas,
/// Used by color picker
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) color_cache: Cache<Color32, Hsva>,
/// Which popup-window is open (if any)?
/// Could be a combo box, color picker, menu etc.
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
popup: Option<Id>,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
everything_is_visible: bool,
}
// ----------------------------------------------------------------------------
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub(crate) struct Options {
/// The default style for new `Ui`:s.
pub(crate) style: std::sync::Arc<Style>,
@@ -304,8 +304,8 @@ impl Memory {
/// Keeps track of `Area`s, which are free-floating `Ui`s.
/// These `Area`s can be in any `Order`.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Areas {
areas: HashMap<Id, area::State>,
/// Top is last

View File

@@ -8,7 +8,7 @@ use crate::math::clamp;
/// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha.
/// Alpha channel is in linear space.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Color32(pub(crate) [u8; 4]);
impl std::ops::Index<usize> for Color32 {
@@ -130,7 +130,7 @@ impl Color32 {
/// 0-1 linear space `RGBA` color with premultiplied alpha.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Rgba(pub(crate) [f32; 4]);
impl std::ops::Index<usize> for Rgba {

View File

@@ -192,7 +192,7 @@ impl PaintCmd {
/// Describes the width and color of a line.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Stroke {
pub width: f32,
pub color: Color32,

View File

@@ -14,8 +14,8 @@ use super::{
// TODO: rename
/// One of a few categories of styles of text, e.g. body, button or heading.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(rename_all = "snake_case"))]
pub enum TextStyle {
/// Used when small text is needed.
Small,
@@ -45,8 +45,8 @@ impl TextStyle {
/// Which style of font: [`Monospace`][`FontFamily::Monospace`] or [`Proportional`][`FontFamily::Proportional`].
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(rename_all = "snake_case"))]
pub enum FontFamily {
/// A font where each character is the same width (`w` is the same width as `i`).
Monospace,
@@ -81,15 +81,15 @@ fn rusttype_font_from_font_data(name: &str, data: &FontData) -> rusttype::Font<'
/// ctx.set_fonts(fonts);
/// ```
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct FontDefinitions {
/// List of font names and their definitions.
/// The definition must be the contents of either a `.ttf` or `.otf` font file.
///
/// Egui has built-in-default for these,
/// but you can override them if you like.
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
pub font_data: BTreeMap<String, FontData>,
/// Which fonts (names) to use for each [`FontFamily`].

View File

@@ -20,7 +20,7 @@ use crate::math::{pos2, NumExt, Rect, Vec2};
/// Character cursor
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct CCursor {
/// Character offset (NOT byte offset!).
pub index: usize,
@@ -71,7 +71,7 @@ impl std::ops::Sub<usize> for CCursor {
/// Row Cursor
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct RCursor {
/// 0 is first row, and so on.
/// Note that a single paragraph can span multiple rows.
@@ -86,7 +86,7 @@ pub struct RCursor {
/// Paragraph Cursor
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct PCursor {
/// 0 is first paragraph, and so on.
/// Note that a single paragraph can span multiple rows.
@@ -118,7 +118,7 @@ impl PartialEq for PCursor {
/// 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.
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Cursor {
pub ccursor: CCursor,
pub rcursor: RCursor,

View File

@@ -1,7 +1,7 @@
use super::*;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Shadow {
// The shadow extends this much outside the rect.
pub extrusion: f32,

View File

@@ -446,8 +446,8 @@ use self::PathType::{Closed, Open};
/// Tessellation quality options
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct TessellationOptions {
/// Size of a pixel in points, e.g. 0.5
pub aa_size: f32,

View File

@@ -1,3 +1,5 @@
//! Egui theme (spacing, colors, etc).
#![allow(clippy::if_same_then_else)]
use crate::{
@@ -9,8 +11,8 @@ use crate::{
/// Specifies the look and feel of a [`Ui`].
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Style {
/// Default `TextStyle` for normal text (i.e. for `Label` and `TextEdit`).
pub body_text_style: TextStyle,
@@ -39,8 +41,8 @@ impl Style {
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Spacing {
/// Horizontal and vertical spacing between widgets
pub item_spacing: Vec2,
@@ -95,8 +97,8 @@ impl Spacing {
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Interaction {
/// Mouse must be the close to the side of a window to resize
pub resize_grab_radius_side: f32,
@@ -106,8 +108,8 @@ pub struct Interaction {
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Visuals {
/// Override default text color for all text.
///
@@ -168,16 +170,16 @@ impl Visuals {
/// Selected text, selected elements etc
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Selection {
pub bg_fill: Color32,
pub stroke: Stroke,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Widgets {
/// For an interactive widget that is being interacted with
pub active: WidgetVisuals,
@@ -207,7 +209,7 @@ impl Widgets {
/// bg = background, fg = foreground.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct WidgetVisuals {
/// Background color of widget
pub bg_fill: Color32,

View File

@@ -5,7 +5,7 @@ use crate::{lerp, math::Rect, Align, CtxRef, Id, LayerId, Ui};
/// What Egui emits each frame.
/// The backend should use this.
#[derive(Clone, Default)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize))]
// #[cfg_attr(feature = "persistence", derive(serde::Serialize))]
pub struct Output {
/// Set the cursor to this icon.
pub cursor_icon: CursorIcon,
@@ -26,8 +26,8 @@ pub struct Output {
///
/// Egui emits a `CursorIcond` in [`Output`] each frame as a request to the integration.
#[derive(Clone, Copy)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize))]
// #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
// #[cfg_attr(feature = "persistence", derive(serde::Serialize))]
// #[cfg_attr(feature = "persistence", serde(rename_all = "snake_case"))]
pub enum CursorIcon {
Default,
/// Pointing hand, used for e.g. web links
@@ -187,7 +187,7 @@ impl Response {
/// });
/// ```
pub fn scroll_to_me(&self, align: Align) {
let scroll_target = lerp(self.rect.y_range(), align.scroll_center_factor());
let scroll_target = lerp(self.rect.y_range(), align.to_factor());
self.ctx.frame_state().scroll_target = Some((scroll_target, align));
}
}
@@ -254,7 +254,7 @@ impl std::ops::BitOrAssign for Response {
/// What sort of interaction is a widget sensitive to?
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize))]
// #[cfg_attr(feature = "persistence", derive(serde::Serialize))]
pub struct Sense {
/// buttons, sliders, windows ...
pub click: bool,

View File

@@ -1,7 +1,7 @@
use std::collections::VecDeque;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Settings {
/// Maximum number of undos.
/// If your state is resource intensive, you should keep this low.
@@ -48,7 +48,7 @@ impl Default for Settings {
/// Rule 1) will make sure an undo point is not created until you _stop_ dragging that slider.
/// Rule 2) will make sure that you will get some undo points even if you are constantly changing the state.
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct Undoer<State> {
settings: Settings,
@@ -57,7 +57,7 @@ pub struct Undoer<State> {
/// The latest undo point may (often) be the current state.
undos: VecDeque<State>,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
flux: Option<Flux<State>>,
}

View File

@@ -149,7 +149,7 @@ impl<'a> Widget for DragValue<'a> {
let auto_decimals = (aim_rad / speed.abs()).log10().ceil().at_least(0.0) as usize;
let max_decimals = max_decimals.unwrap_or(auto_decimals + 2);
let auto_decimals = clamp(auto_decimals, min_decimals..=max_decimals);
let value_text = format_with_decimals_in_range(value, auto_decimals..=max_decimals);
let value_text = math::format_with_decimals_in_range(value, auto_decimals..=max_decimals);
let kb_edit_id = ui.auto_id_with("edit");
let is_kb_editing = ui.memory().has_kb_focus(kb_edit_id);
@@ -193,7 +193,7 @@ impl<'a> Widget for DragValue<'a> {
let delta_value = speed * delta_points;
if delta_value != 0.0 {
let new_value = value + delta_value as f64;
let new_value = round_to_decimals(new_value, auto_decimals);
let new_value = math::round_to_decimals(new_value, auto_decimals);
let new_value = clamp(new_value, range);
set(&mut value_function, new_value);
// TODO: To make use or `smart_aim` for `DragValue` we need to store some state somewhere,

View File

@@ -2,7 +2,7 @@
use std::ops::RangeInclusive;
use crate::{math::NumExt, paint::*, widgets::Label, *};
use crate::{paint::*, widgets::Label, *};
// ----------------------------------------------------------------------------
@@ -209,7 +209,7 @@ impl<'a> Slider<'a> {
fn set_value(&mut self, mut value: f64) {
if let Some(max_decimals) = self.max_decimals {
value = round_to_decimals(value, max_decimals);
value = math::round_to_decimals(value, max_decimals);
}
set(&mut self.get_set_value, value);
}
@@ -366,13 +366,13 @@ impl<'a> Slider<'a> {
let auto_decimals = clamp(auto_decimals, min_decimals..=max_decimals);
if min_decimals == max_decimals {
format_with_minimum_decimals(value, max_decimals)
math::format_with_minimum_decimals(value, max_decimals)
} else if value == 0.0 {
"0".to_owned()
} else if range == 0.0 {
value.to_string()
} else {
format_with_decimals_in_range(value, auto_decimals..=max_decimals)
math::format_with_decimals_in_range(value, auto_decimals..=max_decimals)
}
}
}

View File

@@ -1,17 +1,17 @@
use crate::{paint::*, util::undoer::Undoer, *};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub(crate) struct State {
cursorp: Option<CursorPair>,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "persistence", serde(skip))]
undoer: Undoer<(CCursorPair, String)>,
}
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct CursorPair {
/// When selecting with a mouse, this is where the mouse was released.
/// When moving with e.g. shift+arrows, this is what moves.
@@ -75,7 +75,7 @@ impl CursorPair {
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct CCursorPair {
/// When selecting with a mouse, this is where the mouse was released.
/// When moving with e.g. shift+arrows, this is what moves.