blob: 93ce4016fde5762430547cab971b277dc0f7055e [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::any::type_name;
use std::fmt;
use euclid::point2;
use euclid::size2;
use euclid::Size2D;
use num_traits::NumCast;
use winapi::shared::windef::LPPOINT;
use winapi::shared::windef::POINT;
use winapi::shared::windef::RECT;
use super::HostWindowSpace;
pub type Point = euclid::Point2D<i32, HostWindowSpace>;
pub type Rect = euclid::Rect<i32, HostWindowSpace>;
pub type Size = euclid::Size2D<i32, HostWindowSpace>;
pub trait SizeExtension {
fn create_and_enforce_aspect_ratio(
original_size: &Self,
expected_aspect_ratio: f32,
should_adjust_width: bool,
) -> Self;
fn get_largest_inner_rect_size(original_size: &Self, expected_aspect_ratio: f32) -> Self;
fn scale(&self, ratio: f32) -> Self;
fn transpose(&self) -> Self;
fn shorter_edge(&self) -> i32;
fn aspect_ratio(&self) -> f32;
fn is_square(&self) -> bool;
fn is_landscape(&self) -> bool;
}
impl SizeExtension for Size {
fn create_and_enforce_aspect_ratio(
original_size: &Self,
expected_aspect_ratio: f32,
should_adjust_width: bool,
) -> Self {
let mut size = *original_size;
if should_adjust_width {
size.width = (size.height as f32 * expected_aspect_ratio).round() as i32;
} else {
size.height = (size.width as f32 / expected_aspect_ratio).round() as i32;
}
size
}
fn get_largest_inner_rect_size(original_size: &Self, expected_aspect_ratio: f32) -> Self {
Size::create_and_enforce_aspect_ratio(
original_size,
expected_aspect_ratio,
/* should_adjust_width */ original_size.aspect_ratio() > expected_aspect_ratio,
)
}
#[inline]
fn scale(&self, ratio: f32) -> Self {
size2(
(self.width as f32 * ratio) as i32,
(self.height as f32 * ratio) as i32,
)
}
#[inline]
fn transpose(&self) -> Self {
size2(self.height, self.width)
}
#[inline]
fn shorter_edge(&self) -> i32 {
std::cmp::min(self.width, self.height)
}
#[inline]
fn aspect_ratio(&self) -> f32 {
self.width as f32 / self.height as f32
}
#[inline]
fn is_square(&self) -> bool {
self.width == self.height
}
#[inline]
fn is_landscape(&self) -> bool {
self.width > self.height
}
}
pub trait RectExtension {
fn to_sys_rect(&self) -> RECT;
}
impl RectExtension for Rect {
#[inline]
fn to_sys_rect(&self) -> RECT {
RECT {
left: self.min_x(),
top: self.min_y(),
right: self.max_x(),
bottom: self.max_y(),
}
}
}
pub trait SysRectExtension {
fn to_rect(&self) -> Rect;
}
impl SysRectExtension for RECT {
#[inline]
fn to_rect(&self) -> Rect {
Rect::new(
point2(self.left, self.top),
size2(self.right - self.left, self.bottom - self.top),
)
}
}
pub trait PointExtension {
fn to_sys_point(&self) -> POINT;
}
impl PointExtension for Point {
#[inline]
fn to_sys_point(&self) -> POINT {
POINT {
x: self.x,
y: self.y,
}
}
}
pub trait SysPointExtension {
fn to_point(&self) -> Point;
fn as_mut_ptr(&mut self) -> LPPOINT;
}
impl SysPointExtension for POINT {
#[inline]
fn to_point(&self) -> Point {
point2(self.x, self.y)
}
#[inline]
fn as_mut_ptr(&mut self) -> LPPOINT {
self as LPPOINT
}
}
pub trait Size2DCheckedCast<U>: Sized {
fn checked_cast<T: NumCast>(self) -> Size2D<T, U>;
}
impl<T, U> Size2DCheckedCast<U> for Size2D<T, U>
where
T: NumCast + Copy + fmt::Debug,
{
fn checked_cast<NewT: NumCast>(self) -> Size2D<NewT, U> {
self.try_cast::<NewT>().unwrap_or_else(|| {
panic!(
"Cannot cast {:?} from {} to {}",
self,
type_name::<T>(),
type_name::<NewT>(),
)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn largest_inner_rect_size_when_outer_is_wider() {
assert_eq!(
Size::get_largest_inner_rect_size(
/* original_size */ &size2(1600, 900),
/* expected_aspect_ratio */ 0.5
),
size2(450, 900)
);
}
#[test]
fn largest_inner_rect_size_when_outer_is_taller() {
assert_eq!(
Size::get_largest_inner_rect_size(
/* original_size */ &size2(900, 1600),
/* expected_aspect_ratio */ 3.0
),
size2(900, 300)
);
}
}