use super::CARD_PAD;
use crate::app::palette;
use bevy::color::Mix;
use bevy::prelude::*;
use bevy::ui::widget::NodeImageMode;
#[derive(Component)]
pub struct ShoreCard;
#[derive(Component)]
pub struct Framed;
const FRAMED_RADIUS: f32 = 16.0;
const FRAME_BAND: f32 = 32.0;
const FRAME_SLICE: f32 = 56.0;
const _: () = assert!(
FRAME_SLICE > FRAME_BAND,
"the cut must hold each corner whole"
);
pub const FRAME_PX: f32 = 13.0;
const FRAME_CORNER: f32 = FRAME_PX / FRAME_BAND;
fn frame_bundle(art: &crate::app::art::Art, edge: Option<Color>) -> impl Bundle {
let tint = frame_tint(edge);
(
ImageNode {
image: art.card_frame.clone(),
color: tint,
image_mode: NodeImageMode::Sliced(TextureSlicer {
border: BorderRect::all(FRAME_SLICE),
max_corner_scale: FRAME_CORNER,
..default()
}),
..default()
},
Node {
position_type: PositionType::Absolute,
left: Val::Px(-1.0),
right: Val::Px(-1.0),
top: Val::Px(-1.0),
bottom: Val::Px(-1.0),
..default()
},
Pickable::IGNORE,
)
}
fn frame_tint(edge: Option<Color>) -> Color {
match edge {
Some(color) if color != palette::CARD_EDGE => {
Color::WHITE.mix(&color.with_alpha(1.0), 0.55)
}
Some(_) | None => Color::WHITE,
}
}
type NewCard = (Entity, &'static mut Node, Option<&'static BorderColor>);
pub fn dress_cards(
mut commands: Commands,
art: Res<crate::app::art::Art>,
mut cards: Query<NewCard, Added<ShoreCard>>,
mut framed: Query<NewCard, (Added<Framed>, Without<ShoreCard>)>,
) {
for (card, mut node, edge) in &mut framed {
let frame = frame_card(&mut commands, &art, &mut node, edge);
commands.entity(card).insert_children(0, &[frame]);
}
for (card, mut node, edge) in &mut cards {
let frame = frame_card(&mut commands, &art, &mut node, edge);
let dry = Val::Px(FOAM_DEPTH + 4.0);
if px_of(node.padding.bottom).unwrap_or(0.0) < FOAM_DEPTH {
node.padding.bottom = dry;
}
let tide = commands.spawn(tide_bundle(&art.foam)).id();
commands.entity(card).insert_children(0, &[tide, frame]);
}
}
fn frame_card(
commands: &mut Commands,
art: &crate::app::art::Art,
node: &mut Node,
edge: Option<&BorderColor>,
) -> Entity {
node.border_radius = BorderRadius::all(Val::Px(FRAMED_RADIUS));
clear_the_wood(node);
commands
.spawn(frame_bundle(art, edge.map(|edge| edge.top)))
.id()
}
fn clear_the_wood(node: &mut Node) {
for side in [
&mut node.padding.top,
&mut node.padding.left,
&mut node.padding.right,
] {
if px_of(*side).is_some_and(|px| px < CARD_PAD) {
*side = Val::Px(CARD_PAD);
}
}
}
fn px_of(v: Val) -> Option<f32> {
match v {
Val::Px(px) => Some(px),
Val::Auto
| Val::Percent(_)
| Val::Vw(_)
| Val::Vh(_)
| Val::VMin(_)
| Val::VMax(_)
| Val::Em(_)
| Val::Rem(_) => None,
}
}
pub const FOAM_DEPTH: f32 = 20.0;
pub fn tide_line(
panel: &mut bevy::ecs::relationship::RelatedSpawnerCommands<ChildOf>,
foam: &Handle<Image>,
) {
panel.spawn(tide_bundle(foam));
}
fn tide_bundle(foam: &Handle<Image>) -> (ImageNode, Node) {
(
ImageNode {
image: foam.clone(),
color: palette::FOAM_LINE,
flip_y: true,
image_mode: NodeImageMode::Stretch,
..default()
},
Node {
position_type: PositionType::Absolute,
left: Val::Px(0.0),
right: Val::Px(0.0),
bottom: Val::Px(0.0),
height: Val::Px(FOAM_DEPTH),
border_radius: BorderRadius::bottom(Val::Px(10.0)),
..default()
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_frame_takes_the_cards_own_colour_and_only_that() {
assert_eq!(frame_tint(None), Color::WHITE);
assert_eq!(frame_tint(Some(palette::CARD_EDGE)), Color::WHITE);
let red = frame_tint(Some(palette::INK_RAID.with_alpha(0.6)));
assert_ne!(red, Color::WHITE, "stained");
assert_eq!(red.alpha(), 1.0, "at full strength");
assert_eq!(
red,
frame_tint(Some(palette::INK_RAID)),
"whatever the edge's alpha"
);
}
#[test]
fn a_frame_is_its_own_thickness() {
assert!((FRAME_BAND * FRAME_CORNER - FRAME_PX).abs() < 1e-4);
}
}