use super::Layout;
use crate::core::{ObjectId, Rect};
#[derive(Debug)]
pub struct AspectRatioLayout {
child: Option<ObjectId>,
aspect_ratio: f32,
respect_parent: bool,
}
impl AspectRatioLayout {
pub fn new(aspect_ratio: f32, respect_parent: bool) -> Self {
assert!(aspect_ratio > 0.0, "AspectRatioLayout: aspect_ratio must be positive");
Self { child: None, aspect_ratio, respect_parent }
}
pub fn set_aspect_ratio(&mut self, ratio: f32) {
assert!(ratio > 0.0, "AspectRatioLayout: aspect_ratio must be positive");
self.aspect_ratio = ratio;
}
pub fn aspect_ratio(&self) -> f32 {
self.aspect_ratio
}
pub fn set_respect_parent(&mut self, respect: bool) {
self.respect_parent = respect;
}
pub fn respect_parent(&self) -> bool {
self.respect_parent
}
fn compute_child_rect(&self, parent: Rect) -> Rect {
let parent_w = parent.width as f32;
let parent_h = parent.height as f32;
let ratio = self.aspect_ratio;
let (child_w, child_h) = if self.respect_parent {
let by_width = (parent_w, parent_w / ratio);
let by_height = (parent_h * ratio, parent_h);
if by_width.1 <= parent_h {
(by_width.0 as u32, by_width.1 as u32)
} else {
(by_height.0 as u32, by_height.1 as u32)
}
} else {
let w = parent_w;
let h = w / ratio;
(w as u32, h as u32)
};
let x_offset = (parent.width.saturating_sub(child_w) / 2) as i32;
let y_offset = (parent.height.saturating_sub(child_h) / 2) as i32;
Rect::new(parent.x + x_offset, parent.y + y_offset, child_w, child_h)
}
}
impl Layout for AspectRatioLayout {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn add_widget(&mut self, widget_id: ObjectId, _stretch: u32) {
self.child = Some(widget_id);
}
fn remove_widget(&mut self, widget_id: ObjectId) {
if self.child == Some(widget_id) {
self.child = None;
}
}
fn child_ids(&self) -> Vec<ObjectId> {
self.child.into_iter().collect()
}
fn has_child(&self, id: ObjectId) -> bool {
self.child == Some(id)
}
fn clear(&mut self) {
self.child = None;
}
fn update(&self, rect: Rect, widgets: &mut dyn FnMut(ObjectId, Rect)) {
if let Some(child_id) = self.child {
let child_rect = self.compute_child_rect(rect);
widgets(child_id, child_rect);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aspect_ratio_fit_width() {
let layout = AspectRatioLayout::new_with_child(42, 2.0, true);
let mut out = None;
layout.update(Rect::new(0, 0, 200, 100), &mut |id, rect| {
if id == 42 {
out = Some(rect);
}
});
let rect = out.expect("child should be positioned");
assert_eq!(rect.width, 200);
assert_eq!(rect.height, 100);
}
#[test]
fn test_aspect_ratio_fit_height() {
let layout = AspectRatioLayout::new_with_child(42, 1.0, true);
let mut out = None;
layout.update(Rect::new(0, 0, 200, 50), &mut |id, rect| {
if id == 42 {
out = Some(rect);
}
});
let rect = out.expect("child should be positioned");
assert_eq!(rect.width, 50);
assert_eq!(rect.height, 50);
assert_eq!(rect.x, 75);
assert_eq!(rect.y, 0);
}
#[test]
fn test_aspect_ratio_does_not_respect_parent() {
let layout = AspectRatioLayout::new_with_child(1, 1.0, false);
let mut out = None;
layout.update(Rect::new(0, 0, 100, 100), &mut |id, rect| {
if id == 1 {
out = Some(rect);
}
});
let rect = out.expect("child should be positioned");
assert_eq!(rect.width, 100);
assert_eq!(rect.height, 100);
}
#[test]
fn test_aspect_ratio_remove_and_clear() {
let mut layout = AspectRatioLayout::new(2.0, true);
layout.add_widget(10, 0);
assert!(layout.has_child(10));
layout.remove_widget(10);
assert!(!layout.has_child(10));
layout.add_widget(20, 0);
layout.clear();
assert!(layout.child_ids().is_empty());
}
}
impl AspectRatioLayout {
pub fn new_with_child(child_id: ObjectId, aspect_ratio: f32, respect_parent: bool) -> Self {
assert!(aspect_ratio > 0.0, "AspectRatioLayout: aspect_ratio must be positive");
Self { child: Some(child_id), aspect_ratio, respect_parent }
}
}