use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
#[derive(
EnumString, EnumIter, AsRefStr, Display, Debug, Eq, PartialEq, Hash, Clone, Copy, Default,
)]
pub enum SushiShape {
#[default]
Circular,
Square,
Triangular,
Oval,
}
#[derive(
EnumString, EnumIter, AsRefStr, Display, Debug, Eq, PartialEq, Hash, Clone, Copy, Default,
)]
pub enum SushiState {
#[default]
Rolled,
Exploded,
}
#[derive(
EnumString, EnumIter, AsRefStr, Display, Debug, Eq, PartialEq, Hash, Clone, Copy, Default,
)]
pub enum SushiView {
#[default]
Top,
Front,
}
#[derive(
EnumString, EnumIter, AsRefStr, Display, Debug, Eq, PartialEq, Hash, Clone, Copy, Default,
)]
pub enum TopEdgeType {
#[default]
None,
RedCaviar,
BlackCaviar,
Sesame,
Herbs,
Tobiko,
}
#[derive(EnumString, EnumIter, AsRefStr, Display, Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum Ingredient {
Salmon,
Tuna,
Avocado,
Egg,
RedCaviar,
BlackCaviar,
Cucumber,
Crab,
Shrimp,
Mango,
CreamCheese,
Wasabi,
}
#[derive(Debug, Clone)]
pub struct IngredientMeta {
pub fill: &'static str,
pub stroke: &'static str,
pub label: &'static str,
pub emoji: &'static str,
pub light: &'static str,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SushiSize {
pub width: f64,
pub height: f64,
}
impl Default for SushiSize {
fn default() -> Self {
Self {
width: 120.0,
height: 120.0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SushiOuterSheet {
pub color: String,
pub thickness: f64,
}
impl Default for SushiOuterSheet {
fn default() -> Self {
Self {
color: NORI_COLOR.to_string(),
thickness: 10.0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TopEdgeDecoration {
pub edge_type: TopEdgeType,
pub color: Option<String>,
}
impl Default for TopEdgeDecoration {
fn default() -> Self {
Self {
edge_type: TopEdgeType::None,
color: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SushiData {
pub id: String,
pub name: String,
pub shape: SushiShape,
pub state: SushiState,
pub view: SushiView,
pub ingredients: Vec<Ingredient>,
pub outer_sheet: SushiOuterSheet,
pub size: SushiSize,
pub rice_color: Option<String>,
pub top_edge: Option<TopEdgeDecoration>,
pub description: Option<String>,
}
impl Default for SushiData {
fn default() -> Self {
Self {
id: "sushi-default".into(),
name: "Custom Roll".into(),
shape: SushiShape::Circular,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Salmon],
outer_sheet: SushiOuterSheet::default(),
size: SushiSize::default(),
rice_color: None,
top_edge: None,
description: None,
}
}
}
pub const NORI_COLOR: &str = "#0D1F08";
pub const RICE_COLOR: &str = "#F7F3E8";
pub fn ingredient_meta(ingredient: Ingredient) -> IngredientMeta {
match ingredient {
Ingredient::Salmon => IngredientMeta {
fill: "#FA8072",
stroke: "#E05A4A",
light: "#FFAAA0",
label: "Salmon",
emoji: "🐟",
},
Ingredient::Tuna => IngredientMeta {
fill: "#C0392B",
stroke: "#992D22",
light: "#D9574A",
label: "Tuna",
emoji: "🐠",
},
Ingredient::Avocado => IngredientMeta {
fill: "#5D9E2F",
stroke: "#4A7C25",
light: "#7EC84A",
label: "Avocado",
emoji: "🥑",
},
Ingredient::Egg => IngredientMeta {
fill: "#FFD700",
stroke: "#E5BE00",
light: "#FFED6A",
label: "Tamago Egg",
emoji: "🥚",
},
Ingredient::RedCaviar => IngredientMeta {
fill: "#E74C3C",
stroke: "#C0392B",
light: "#FF7F6E",
label: "Red Caviar",
emoji: "🔴",
},
Ingredient::BlackCaviar => IngredientMeta {
fill: "#2C2C2C",
stroke: "#444444",
light: "#555555",
label: "Black Caviar",
emoji: "⚫",
},
Ingredient::Cucumber => IngredientMeta {
fill: "#7AC74C",
stroke: "#5E9B37",
light: "#A0E066",
label: "Cucumber",
emoji: "🥒",
},
Ingredient::Crab => IngredientMeta {
fill: "#E8733A",
stroke: "#C5562A",
light: "#FF9A68",
label: "Crab",
emoji: "🦀",
},
Ingredient::Shrimp => IngredientMeta {
fill: "#FFB5A0",
stroke: "#E89080",
light: "#FFD5C8",
label: "Shrimp",
emoji: "🍤",
},
Ingredient::Mango => IngredientMeta {
fill: "#FF9800",
stroke: "#E58800",
light: "#FFB84D",
label: "Mango",
emoji: "🥭",
},
Ingredient::CreamCheese => IngredientMeta {
fill: "#FFFCF0",
stroke: "#E0D8C0",
light: "#FFFFFF",
label: "Cream Cheese",
emoji: "🧀",
},
Ingredient::Wasabi => IngredientMeta {
fill: "#5CB85C",
stroke: "#4A9A4A",
light: "#7ACF7A",
label: "Wasabi",
emoji: "🌿",
},
}
}
pub fn ingredient_texture_id(ingredient: Ingredient, uid: &str) -> String {
match ingredient {
Ingredient::Salmon => format!("url(#salmon-p-{uid})"),
Ingredient::RedCaviar => format!("url(#rcav-p-{uid})"),
Ingredient::BlackCaviar => format!("url(#bcav-p-{uid})"),
_ => format!("url(#sheen-p-{uid})"),
}
}
pub fn polar_to_cart(cx: f64, cy: f64, r: f64, deg: f64) -> (f64, f64) {
let rad = (deg - 90.0) * std::f64::consts::PI / 180.0;
(cx + r * rad.cos(), cy + r * rad.sin())
}
pub fn describe_arc(cx: f64, cy: f64, r: f64, start_deg: f64, end_deg: f64) -> String {
let (x1, y1) = polar_to_cart(cx, cy, r, end_deg);
let (x2, y2) = polar_to_cart(cx, cy, r, start_deg);
let large = if end_deg - start_deg <= 180.0 { 0 } else { 1 };
format!(
"M {cx} {cy} L {x1} {y1} A {r} {r} 0 {large} 0 {x2} {y2} Z",
cx = cx,
cy = cy,
x1 = x1,
y1 = y1,
r = r,
large = large,
x2 = x2,
y2 = y2
)
}
pub fn default_sushi_gallery() -> Vec<SushiData> {
vec![
SushiData {
id: "salmon-maki".into(),
name: "Salmon Maki".into(),
shape: SushiShape::Circular,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Salmon],
outer_sheet: SushiOuterSheet {
color: NORI_COLOR.into(),
thickness: 10.0,
},
size: SushiSize {
width: 120.0,
height: 120.0,
},
rice_color: None,
top_edge: None,
description: Some("Classic salmon maki roll with fresh Atlantic salmon.".into()),
},
SushiData {
id: "tuna-maki".into(),
name: "Tuna Maki".into(),
shape: SushiShape::Circular,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Tuna],
outer_sheet: SushiOuterSheet {
color: NORI_COLOR.into(),
thickness: 10.0,
},
size: SushiSize {
width: 120.0,
height: 120.0,
},
rice_color: None,
top_edge: None,
description: Some("Bluefin tuna wrapped in nori seaweed.".into()),
},
SushiData {
id: "tamago-square".into(),
name: "Tamago Square".into(),
shape: SushiShape::Square,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Egg],
outer_sheet: SushiOuterSheet {
color: NORI_COLOR.into(),
thickness: 8.0,
},
size: SushiSize {
width: 120.0,
height: 120.0,
},
rice_color: None,
top_edge: None,
description: Some("Sweet Japanese tamago egg in a seaweed square wrap.".into()),
},
SushiData {
id: "tuna-onigiri".into(),
name: "Tuna Onigiri".into(),
shape: SushiShape::Triangular,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Tuna],
outer_sheet: SushiOuterSheet {
color: NORI_COLOR.into(),
thickness: 6.0,
},
size: SushiSize {
width: 110.0,
height: 120.0,
},
rice_color: None,
top_edge: None,
description: Some("Traditional triangular rice ball filled with tuna.".into()),
},
SushiData {
id: "salmon-nigiri".into(),
name: "Salmon Nigiri".into(),
shape: SushiShape::Oval,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Salmon],
outer_sheet: SushiOuterSheet {
color: "transparent".into(),
thickness: 0.0,
},
size: SushiSize {
width: 130.0,
height: 100.0,
},
rice_color: None,
top_edge: None,
description: Some("Hand-pressed salmon draped over seasoned shari rice.".into()),
},
SushiData {
id: "rainbow-roll".into(),
name: "Rainbow Roll".into(),
shape: SushiShape::Circular,
state: SushiState::Rolled,
view: SushiView::Top,
ingredients: vec![Ingredient::Salmon, Ingredient::Avocado, Ingredient::Tuna],
outer_sheet: SushiOuterSheet {
color: NORI_COLOR.into(),
thickness: 10.0,
},
size: SushiSize {
width: 120.0,
height: 120.0,
},
rice_color: None,
top_edge: Some(TopEdgeDecoration {
edge_type: TopEdgeType::Sesame,
color: None,
}),
description: Some("Colorful rainbow roll with premium multi-topping.".into()),
},
]
}