#![allow(clippy::needless_update)]
#![allow(clippy::too_many_arguments)]
extern crate alloc;
#[cfg(feature = "std")]
use crate::app::{App, RendererFactory};
use crate::draw::command::DrawCommand;
use crate::draw::path::Path;
use crate::draw::renderer::Renderer;
use crate::ecs::{Entity, MonoClock, World};
#[cfg(feature = "std")]
use crate::plugins::StdInstantClockPlugin;
use crate::prelude::*;
#[cfg(feature = "std")]
use crate::surface::Surface;
use crate::widget::dirty::Dirty;
use crate::widget::view::{View, ViewCtx};
#[derive(Default)]
pub struct Butterfly {
pub start_ms: u32,
}
fn wing_path(cx: Fixed, cy: Fixed, span: Fixed, tilt: Fixed, side: i32, inner: bool) -> Path {
let s = Fixed::from_int(side);
let shrink = if inner {
Fixed::from_f32(0.6)
} else {
Fixed::ONE
};
let anchor_top = Point {
x: cx + tilt * Fixed::from_int(4),
y: cy - Fixed::from_int(8) * shrink,
};
let anchor_bot = Point {
x: cx - tilt * Fixed::from_int(4),
y: cy + Fixed::from_int(10) * shrink,
};
let forewing_tip = Point {
x: cx + Fixed::from_int(44) * s * span * shrink,
y: cy - Fixed::from_int(24) * shrink,
};
let hindwing_tip = Point {
x: cx + Fixed::from_int(34) * s * span * shrink,
y: cy + Fixed::from_int(22) * shrink,
};
let fw_out_c1 = Point {
x: cx + Fixed::from_int(18) * s * span * shrink,
y: anchor_top.y - Fixed::from_int(22) * shrink,
};
let fw_out_c2 = Point {
x: cx + Fixed::from_int(52) * s * span * shrink,
y: forewing_tip.y - Fixed::from_int(6) * shrink,
};
let fw_in_c1 = Point {
x: cx + Fixed::from_int(46) * s * span * shrink,
y: cy - Fixed::from_int(6) * shrink,
};
let fw_in_c2 = Point {
x: cx + Fixed::from_int(14) * s * span * shrink,
y: cy - Fixed::from_int(1) * shrink,
};
let notch = Point {
x: cx + Fixed::from_int(10) * s * span * shrink,
y: cy + Fixed::from_int(4) * shrink,
};
let hw_out_c1 = Point {
x: cx + Fixed::from_int(36) * s * span * shrink,
y: cy + Fixed::from_int(6) * shrink,
};
let hw_out_c2 = Point {
x: cx + Fixed::from_int(40) * s * span * shrink,
y: hindwing_tip.y - Fixed::from_int(2) * shrink,
};
let hw_in_c1 = Point {
x: cx + Fixed::from_int(26) * s * span * shrink,
y: cy + Fixed::from_int(18) * shrink,
};
let hw_in_c2 = Point {
x: cx + Fixed::from_int(8) * s * span * shrink,
y: cy + Fixed::from_int(14) * shrink,
};
let mut path = Path::new();
path.move_to(anchor_top);
path.cubic_to(fw_out_c1, fw_out_c2, forewing_tip);
path.cubic_to(fw_in_c1, fw_in_c2, notch);
path.cubic_to(hw_out_c1, hw_out_c2, hindwing_tip);
path.cubic_to(hw_in_c1, hw_in_c2, anchor_bot);
path.close();
path
}
fn fill_wing(
renderer: &mut dyn Renderer,
clip: &Rect,
transform: crate::types::Transform,
cx: Fixed,
cy: Fixed,
span: Fixed,
tilt: Fixed,
side: i32,
inner: bool,
) {
let path = wing_path(cx, cy, span, tilt, side, inner);
let color = if inner {
if side < 0 {
Color::rgb(130, 210, 240)
} else {
Color::rgb(150, 220, 245)
}
} else if side < 0 {
Color::rgb(40, 70, 160)
} else {
Color::rgb(50, 80, 170)
};
let opa = if inner { 210 } else { 240 };
renderer.draw(
&DrawCommand::FillPath {
path: &path,
transform,
color,
opa,
},
clip,
);
}
fn butterfly_render(
renderer: &mut dyn Renderer,
world: &World,
entity: Entity,
rect: &Rect,
ctx: &mut ViewCtx,
) {
let Some(state) = world.get::<Butterfly>(entity) else {
return;
};
let now_ms = world
.resource::<MonoClock>()
.map(|c| c.now_ms())
.unwrap_or(0);
let elapsed_ms = now_ms.wrapping_sub(state.start_ms) as i32;
let amp_x = rect.w / Fixed::from_int(4);
let amp_y = rect.h / Fixed::from_int(5);
let tx_deg = Fixed::from_int((elapsed_ms * 360 / 3100) % 360);
let ty_deg = Fixed::from_int((elapsed_ms * 360 / 1900) % 360);
let cx = rect.x + rect.w / Fixed::from_int(2) + Fixed::sin_deg(tx_deg) * amp_x;
let cy = rect.y + rect.h / Fixed::from_int(2) + Fixed::sin_deg(ty_deg) * amp_y;
let tilt = Fixed::cos_deg(tx_deg) * Fixed::from_f32(0.35);
let yaw_deg = Fixed::from_int((elapsed_ms * 360 / 2400) % 360);
let yaw = Fixed::sin_deg(yaw_deg) * Fixed::from_f32(0.55);
let flap_deg = Fixed::from_int((elapsed_ms * 360 / 280) % 360);
let raw = Fixed::sin_deg(flap_deg).abs();
let span_base = Fixed::from_f32(0.25) + raw * Fixed::from_f32(0.75);
let min_span = Fixed::from_f32(0.15);
let span_left = (span_base * (Fixed::ONE + yaw)).max(min_span);
let span_right = (span_base * (Fixed::ONE - yaw)).max(min_span);
fill_wing(
renderer,
ctx.clip,
ctx.transform,
cx,
cy,
span_left,
tilt,
-1,
false,
);
fill_wing(
renderer,
ctx.clip,
ctx.transform,
cx,
cy,
span_right,
tilt,
1,
false,
);
fill_wing(
renderer,
ctx.clip,
ctx.transform,
cx,
cy,
span_left,
tilt,
-1,
true,
);
fill_wing(
renderer,
ctx.clip,
ctx.transform,
cx,
cy,
span_right,
tilt,
1,
true,
);
let body_head = Point {
x: cx + tilt * Fixed::from_int(6),
y: cy - Fixed::from_int(14),
};
let body_tail = Point {
x: cx - tilt * Fixed::from_int(6),
y: cy + Fixed::from_int(16),
};
renderer.draw(
&DrawCommand::Line {
p1: body_head,
p2: body_tail,
transform: ctx.transform,
color: Color::rgb(30, 20, 40),
width: Fixed::from_int(2),
opa: 255,
},
ctx.clip,
);
renderer.draw(
&DrawCommand::Line {
p1: body_head,
p2: Point {
x: body_head.x - Fixed::from_int(5),
y: body_head.y - Fixed::from_int(10),
},
transform: ctx.transform,
color: Color::rgb(50, 40, 60),
width: Fixed::ONE,
opa: 220,
},
ctx.clip,
);
renderer.draw(
&DrawCommand::Line {
p1: body_head,
p2: Point {
x: body_head.x + Fixed::from_int(5),
y: body_head.y - Fixed::from_int(10),
},
transform: ctx.transform,
color: Color::rgb(50, 40, 60),
width: Fixed::ONE,
opa: 220,
},
ctx.clip,
);
}
pub fn butterfly_view() -> View {
View::new("Butterfly", 60, butterfly_render).with_filter::<Butterfly>()
}
#[mirui_macros::system(order = ANIMATION)]
pub fn butterfly_anim_system(world: &mut World) {
let mut buf = alloc::vec::Vec::new();
world.query::<Butterfly>().collect_into(&mut buf);
for e in buf {
world.insert(e, Dirty);
}
}
pub fn build_widgets(world: &mut World, parent: Entity, view_w: u16, view_h: u16) {
let now_ms = world
.resource::<MonoClock>()
.map(|c| c.now_ms())
.unwrap_or(0);
ui! {
:(
parent: parent
world: world
:)
Butterfly (
start_ms: now_ms,
width: view_w as i32,
height: view_h as i32,
position: Position::Absolute,
left: 0,
top: 0
)
};
}
#[cfg(feature = "std")]
pub fn setup_app<B, F>(app: &mut App<B, F>, parent: Entity)
where
B: Surface,
F: RendererFactory<B>,
{
let info = app.backend.display_info();
app.add_plugin(StdInstantClockPlugin);
app.with_widget(butterfly_view());
app.add_system(butterfly_anim_system::system());
build_widgets(&mut app.world, parent, info.width, info.height);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widget::Children;
use crate::widget::IdMap;
use crate::widget::builder::WidgetBuilder;
use crate::widget::view::ViewRegistry;
#[test]
fn build_widgets_smoke() {
let mut world = World::new();
world.insert_resource(IdMap::new());
let mut reg = ViewRegistry::with_builtins();
reg.insert(butterfly_view());
world.insert_resource(reg);
let parent = WidgetBuilder::new(&mut world).id();
build_widgets(&mut world, parent, 128, 128);
assert!(
world
.get::<Children>(parent)
.is_some_and(|c| !c.0.is_empty()),
);
}
}