use rapier2d::prelude::{ColliderHandle, RigidBodyHandle};
use ratatui::style::Color;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ShapeType {
Circle,
Triangle,
Square,
Star,
LineStraight,
LineVertical,
}
impl ShapeType {
pub fn all() -> [ShapeType; 6] {
[
ShapeType::Circle,
ShapeType::Triangle,
ShapeType::Square,
ShapeType::Star,
ShapeType::LineStraight,
ShapeType::LineVertical,
]
}
pub fn name(&self) -> &'static str {
match self {
ShapeType::Circle => "Circle",
ShapeType::Triangle => "Triangle",
ShapeType::Square => "Square",
ShapeType::Star => "Star",
ShapeType::LineStraight => "Line",
ShapeType::LineVertical => "VertLine",
}
}
pub fn short_name(&self) -> char {
match self {
ShapeType::Circle => 'O',
ShapeType::Triangle => 'A',
ShapeType::Square => '#',
ShapeType::Star => '*',
ShapeType::LineStraight => '-',
ShapeType::LineVertical => '|',
}
}
pub fn from_grid_index(index: usize) -> Option<ShapeType> {
ShapeType::all().get(index).copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShapeColor {
color: Color,
}
impl ShapeColor {
pub const COLORS: [Color; 6] = [
Color::Red,
Color::Green,
Color::Yellow,
Color::Blue,
Color::Magenta,
Color::Cyan,
];
pub fn new(color: Color) -> Self {
Self { color }
}
pub fn random() -> Self {
use rand::Rng;
let mut rng = rand::thread_rng();
let idx = rng.gen_range(0..Self::COLORS.len());
Self {
color: Self::COLORS[idx],
}
}
pub fn green() -> Self {
Self {
color: Color::Green,
}
}
pub fn highlighted(&self) -> Color {
match self.color {
Color::Green => Color::LightGreen,
Color::Red => Color::LightRed,
Color::Blue => Color::LightBlue,
Color::Yellow => Color::LightYellow,
Color::Magenta => Color::LightMagenta,
Color::Cyan => Color::LightCyan,
other => other,
}
}
pub fn color(&self) -> Color {
self.color
}
pub fn cycle_forward(&mut self) {
let current_idx = Self::COLORS
.iter()
.position(|&c| c == self.color)
.unwrap_or(0);
let next_idx = (current_idx + 1) % Self::COLORS.len();
self.color = Self::COLORS[next_idx];
}
pub fn cycle_backward(&mut self) {
let current_idx = Self::COLORS
.iter()
.position(|&c| c == self.color)
.unwrap_or(0);
let prev_idx = if current_idx == 0 {
Self::COLORS.len() - 1
} else {
current_idx - 1
};
self.color = Self::COLORS[prev_idx];
}
}
impl Default for ShapeColor {
fn default() -> Self {
Self {
color: Color::Green,
}
}
}
#[derive(Debug, Clone)]
pub struct Shape {
id: u32,
shape_type: ShapeType,
position: (f32, f32),
rotation_degrees: i32,
color: ShapeColor,
selected: bool,
rigid_body_handle: Option<RigidBodyHandle>,
collider_handle: Option<ColliderHandle>,
}
impl Shape {
pub fn new(id: u32, shape_type: ShapeType, x: f32, y: f32) -> Self {
Self {
id,
shape_type,
position: (x, y),
rotation_degrees: 0,
color: ShapeColor::random(),
selected: false,
rigid_body_handle: None,
collider_handle: None,
}
}
pub fn id(&self) -> u32 {
self.id
}
pub fn shape_type(&self) -> ShapeType {
self.shape_type
}
pub fn position(&self) -> (f32, f32) {
self.position
}
pub fn set_position(&mut self, x: f32, y: f32) {
self.position = (x, y);
}
pub fn rotation_degrees(&self) -> i32 {
self.rotation_degrees
}
pub fn rotation_radians(&self) -> f32 {
(self.rotation_degrees as f32).to_radians()
}
pub fn rotate_clockwise(&mut self) {
self.rotation_degrees = (self.rotation_degrees + 90) % 360;
}
pub fn rotate_counter_clockwise(&mut self) {
self.rotation_degrees = (self.rotation_degrees - 90 + 360) % 360;
}
pub fn color(&self) -> &ShapeColor {
&self.color
}
pub fn set_color(&mut self, color: ShapeColor) {
self.color = color;
}
pub fn cycle_color_forward(&mut self) {
self.color.cycle_forward();
}
pub fn cycle_color_backward(&mut self) {
self.color.cycle_backward();
}
pub fn is_selected(&self) -> bool {
self.selected
}
pub fn set_selected(&mut self, selected: bool) {
self.selected = selected;
}
pub fn rigid_body_handle(&self) -> Option<RigidBodyHandle> {
self.rigid_body_handle
}
pub fn collider_handle(&self) -> Option<ColliderHandle> {
self.collider_handle
}
pub fn set_physics_handles(
&mut self,
body_handle: RigidBodyHandle,
collider_handle: ColliderHandle,
) {
self.rigid_body_handle = Some(body_handle);
self.collider_handle = Some(collider_handle);
}
pub fn clear_physics_handles(&mut self) {
self.rigid_body_handle = None;
self.collider_handle = None;
}
pub fn render_color(&self) -> Color {
if self.selected {
self.color.highlighted()
} else {
self.color.color()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shape_type_all() {
let types = ShapeType::all();
assert_eq!(types.len(), 6);
assert_eq!(types[0], ShapeType::Circle);
assert_eq!(types[5], ShapeType::LineVertical);
}
#[test]
fn test_shape_rotation() {
let mut shape = Shape::new(1, ShapeType::Square, 10.0, 10.0);
assert_eq!(shape.rotation_degrees(), 0);
shape.rotate_clockwise();
assert_eq!(shape.rotation_degrees(), 90);
shape.rotate_clockwise();
assert_eq!(shape.rotation_degrees(), 180);
shape.rotate_counter_clockwise();
assert_eq!(shape.rotation_degrees(), 90);
shape.rotation_degrees = 270;
shape.rotate_clockwise();
assert_eq!(shape.rotation_degrees(), 0);
}
#[test]
fn test_shape_color_default() {
let color = ShapeColor::default();
assert_eq!(color.color(), Color::Green);
}
#[test]
fn test_shape_selection() {
let mut shape = Shape::new(1, ShapeType::Circle, 5.0, 5.0);
assert!(!shape.is_selected());
let color = shape.render_color();
assert!(ShapeColor::COLORS.contains(&color));
shape.set_color(ShapeColor::green());
assert_eq!(shape.render_color(), Color::Green);
shape.set_selected(true);
assert!(shape.is_selected());
assert_eq!(shape.render_color(), Color::LightGreen);
}
#[test]
fn test_shape_color_cycling() {
let mut color = ShapeColor::new(Color::Red);
assert_eq!(color.color(), Color::Red);
color.cycle_forward();
assert_eq!(color.color(), Color::Green);
color.cycle_forward();
assert_eq!(color.color(), Color::Yellow);
color.cycle_backward();
assert_eq!(color.color(), Color::Green);
color = ShapeColor::new(Color::Cyan);
color.cycle_forward();
assert_eq!(color.color(), Color::Red);
color.cycle_backward();
assert_eq!(color.color(), Color::Cyan);
}
}