use crate::geometry::{Insets, Point, Size};
const BLOW_SHAPE_TEXT_GAP: f32 = 8.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextPositioningStrategy {
InContent,
BelowShape,
}
impl TextPositioningStrategy {
pub fn calculate_text_position(
&self,
total_position: Point,
shape_size: Size,
text_size: Size,
shape_to_container_offset: Point,
shape_to_container_offset_no_top_padding: Point,
has_inner_content: bool,
) -> Point {
let total_size = self.calculate_total_size(shape_size, text_size);
match self {
Self::InContent => {
let bounds = total_position.to_bounds(total_size);
let content_offset = if has_inner_content {
shape_to_container_offset_no_top_padding
} else {
shape_to_container_offset
};
total_position
.with_y(bounds.min_y() + content_offset.y() + text_size.height() / 2.0)
}
Self::BelowShape => {
let text_y = total_position.y() + (total_size.height() - text_size.height()) / 2.0;
total_position.with_y(text_y)
}
}
}
pub fn calculate_total_size(&self, shape_size: Size, text_size: Size) -> Size {
match self {
Self::InContent => {
shape_size
}
Self::BelowShape => {
if text_size.is_zero() {
return shape_size;
}
let text_with_gap =
text_size.add_padding(Insets::new(BLOW_SHAPE_TEXT_GAP, 0.0, 0.0, 0.0));
shape_size.merge_vertical(text_with_gap)
}
}
}
pub fn text_affects_shape_content(&self) -> bool {
match self {
Self::InContent => true,
Self::BelowShape => false,
}
}
pub fn calculate_inner_content_min_point(&self, base_point: Point, text_size: Size) -> Point {
match self {
Self::InContent => base_point.with_y(base_point.y() + text_size.height()),
Self::BelowShape => base_point,
}
}
pub fn calculate_shape_position(
&self,
total_position: Point,
shape_size: Size,
text_size: Size,
) -> Point {
match self {
Self::InContent => {
total_position
}
Self::BelowShape => {
let total_size = self.calculate_total_size(shape_size, text_size);
let shape_y =
total_position.y() - (total_size.height() - shape_size.height()) / 2.0;
total_position.with_y(shape_y)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_in_content_total_size() {
let strategy = TextPositioningStrategy::InContent;
let shape_size = Size::new(100.0, 50.0);
let text_size = Size::new(30.0, 10.0);
let total_size = strategy.calculate_total_size(shape_size, text_size);
assert_eq!(total_size.width(), shape_size.width());
assert_eq!(total_size.height(), shape_size.height());
}
#[test]
fn test_below_shape_total_size() {
let strategy = TextPositioningStrategy::BelowShape;
let shape_size = Size::new(50.0, 40.0);
let text_size = Size::new(30.0, 10.0);
let total_size = strategy.calculate_total_size(shape_size, text_size);
assert_eq!(total_size.width(), 50.0);
assert_eq!(total_size.height(), 58.0);
}
#[test]
fn test_below_shape_total_size_wide_text() {
let strategy = TextPositioningStrategy::BelowShape;
let shape_size = Size::new(30.0, 40.0);
let text_size = Size::new(80.0, 10.0);
let total_size = strategy.calculate_total_size(shape_size, text_size);
assert_eq!(total_size.width(), 80.0);
assert_eq!(total_size.height(), 58.0);
}
#[test]
fn test_below_shape_total_size_zero_text() {
let strategy = TextPositioningStrategy::BelowShape;
let shape_size = Size::new(50.0, 40.0);
let text_size = Size::new(0.0, 0.0);
let total_size = strategy.calculate_total_size(shape_size, text_size);
assert_eq!(total_size.width(), shape_size.width());
assert_eq!(total_size.height(), shape_size.height());
}
#[test]
fn test_text_affects_shape_content() {
assert!(
TextPositioningStrategy::InContent.text_affects_shape_content(),
"InContent should return true - text affects shape content"
);
assert!(
!TextPositioningStrategy::BelowShape.text_affects_shape_content(),
"BelowShape should return false - text doesn't affect shape content"
);
}
#[test]
fn test_calculate_shape_position_in_content() {
let strategy = TextPositioningStrategy::InContent;
let total_position = Point::new(100.0, 100.0);
let shape_size = Size::new(80.0, 60.0);
let text_size = Size::new(40.0, 12.0);
let shape_position =
strategy.calculate_shape_position(total_position, shape_size, text_size);
assert_eq!(shape_position, total_position);
}
#[test]
fn test_calculate_shape_position_below_shape() {
let strategy = TextPositioningStrategy::BelowShape;
let total_position = Point::new(100.0, 100.0);
let shape_size = Size::new(50.0, 40.0);
let text_size = Size::new(30.0, 10.0);
let shape_position =
strategy.calculate_shape_position(total_position, shape_size, text_size);
assert_eq!(shape_position, Point::new(100.0, 91.0));
}
#[test]
fn test_calculate_inner_content_min_point() {
let base_point = Point::new(10.0, 20.0);
let text_size = Size::new(50.0, 15.0);
let in_content_result = TextPositioningStrategy::InContent
.calculate_inner_content_min_point(base_point, text_size);
assert_eq!(in_content_result, Point::new(10.0, 35.0));
let below_shape_result = TextPositioningStrategy::BelowShape
.calculate_inner_content_min_point(base_point, text_size);
assert_eq!(below_shape_result, base_point);
}
}