use crate::op::LayoutUnit;
use serde::{Deserialize, Serialize};
use std::hash::Hash;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ViewportTransform {
pub translation: [LayoutUnit; 2],
pub scale: f32,
}
impl ViewportTransform {
pub const IDENTITY: Self = Self {
translation: [0.0, 0.0],
scale: 1.0,
};
pub fn new(translation_x: LayoutUnit, translation_y: LayoutUnit, scale: f32) -> Self {
Self {
translation: [translation_x, translation_y],
scale,
}
.normalized()
}
pub fn normalized(self) -> Self {
Self {
translation: [
finite_or_zero(self.translation[0]),
finite_or_zero(self.translation[1]),
],
scale: if self.scale.is_finite() && self.scale > 0.0 {
self.scale
} else {
1.0
},
}
}
pub fn world_to_screen(self, world: [LayoutUnit; 2]) -> [LayoutUnit; 2] {
let transform = self.normalized();
[
world[0] * transform.scale + transform.translation[0],
world[1] * transform.scale + transform.translation[1],
]
}
pub fn screen_to_world(self, screen: [LayoutUnit; 2]) -> [LayoutUnit; 2] {
let transform = self.normalized();
[
(screen[0] - transform.translation[0]) / transform.scale,
(screen[1] - transform.translation[1]) / transform.scale,
]
}
pub fn with_scale_around(self, screen_focal_point: [LayoutUnit; 2], scale: f32) -> Self {
let current = self.normalized();
let world_focal_point = current.screen_to_world(screen_focal_point);
let next_scale = if scale.is_finite() && scale > 0.0 {
scale
} else {
current.scale
};
Self {
translation: [
screen_focal_point[0] - world_focal_point[0] * next_scale,
screen_focal_point[1] - world_focal_point[1] * next_scale,
],
scale: next_scale,
}
}
}
impl Default for ViewportTransform {
fn default() -> Self {
Self::IDENTITY
}
}
impl Hash for ViewportTransform {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.translation[0].to_bits().hash(state);
self.translation[1].to_bits().hash(state);
self.scale.to_bits().hash(state);
}
}
fn finite_or_zero(value: LayoutUnit) -> LayoutUnit {
if value.is_finite() {
value
} else {
0.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum ViewportPanAxis {
None,
Horizontal,
Vertical,
#[default]
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub struct ViewportMargin {
pub left: LayoutUnit,
pub right: LayoutUnit,
pub top: LayoutUnit,
pub bottom: LayoutUnit,
}
impl ViewportMargin {
pub const ZERO: Self = Self {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
};
pub fn all(value: LayoutUnit) -> Self {
let value = non_negative_finite(value);
Self {
left: value,
right: value,
top: value,
bottom: value,
}
}
pub fn normalized(self) -> Self {
Self {
left: non_negative_finite(self.left),
right: non_negative_finite(self.right),
top: non_negative_finite(self.top),
bottom: non_negative_finite(self.bottom),
}
}
}
impl Hash for ViewportMargin {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.left.to_bits().hash(state);
self.right.to_bits().hash(state);
self.top.to_bits().hash(state);
self.bottom.to_bits().hash(state);
}
}
fn non_negative_finite(value: LayoutUnit) -> LayoutUnit {
if value.is_finite() {
value.max(0.0)
} else {
0.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub enum ViewportBoundary {
#[default]
Unbounded,
Finite {
min_x: LayoutUnit,
min_y: LayoutUnit,
max_x: LayoutUnit,
max_y: LayoutUnit,
margin: ViewportMargin,
},
}
impl ViewportBoundary {
pub fn finite(
min_x: LayoutUnit,
min_y: LayoutUnit,
max_x: LayoutUnit,
max_y: LayoutUnit,
margin: ViewportMargin,
) -> Self {
Self::Finite {
min_x,
min_y,
max_x,
max_y,
margin,
}
.normalized()
}
pub fn normalized(self) -> Self {
match self {
Self::Unbounded => Self::Unbounded,
Self::Finite {
min_x,
min_y,
max_x,
max_y,
margin,
} if min_x.is_finite()
&& min_y.is_finite()
&& max_x.is_finite()
&& max_y.is_finite() =>
{
Self::Finite {
min_x: min_x.min(max_x),
min_y: min_y.min(max_y),
max_x: min_x.max(max_x),
max_y: min_y.max(max_y),
margin: margin.normalized(),
}
}
Self::Finite { .. } => Self::Unbounded,
}
}
}
impl Hash for ViewportBoundary {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Unbounded => 0_u8.hash(state),
Self::Finite {
min_x,
min_y,
max_x,
max_y,
margin,
} => {
1_u8.hash(state);
min_x.to_bits().hash(state);
min_y.to_bits().hash(state);
max_x.to_bits().hash(state);
max_y.to_bits().hash(state);
margin.hash(state);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum ViewportClip {
None,
#[default]
HardEdge,
AntiAlias,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum ViewportZoomPolicy {
Disabled,
PinchOnly,
#[default]
WheelWithModifier,
WheelAndTrackpad,
}