use std::collections::HashMap;
use macroquad::prelude::{Color, Vec2};
use crate::primitives::{Align, Entity, FontKind, Shape, StrokeStyle};
use crate::style::{Role, Theme};
#[derive(Debug, Clone, Default)]
pub struct Scene {
pub entities: Vec<Entity>,
index: HashMap<String, usize>,
}
impl Scene {
pub fn new() -> Self {
Scene::default()
}
pub fn add(&mut self, e: Entity) -> usize {
assert!(
!self.index.contains_key(&e.id),
"duplicate entity id {:?}",
e.id
);
let i = self.entities.len();
self.index.insert(e.id.clone(), i);
self.entities.push(e);
i
}
pub fn get(&self, id: &str) -> Option<&Entity> {
self.index.get(id).map(|&i| &self.entities[i])
}
pub fn get_mut(&mut self, id: &str) -> Option<&mut Entity> {
self.index
.get(id)
.copied()
.map(move |i| &mut self.entities[i])
}
pub fn contains(&self, id: &str) -> bool {
self.index.contains_key(id)
}
}
pub struct SceneBuilder<'a> {
scene: &'a mut Scene,
theme: Theme,
last: Option<usize>,
}
impl<'a> SceneBuilder<'a> {
pub(crate) fn new(scene: &'a mut Scene, theme: Theme) -> Self {
SceneBuilder {
scene,
theme,
last: None,
}
}
fn push(&mut self, e: Entity) -> &mut Self {
self.last = Some(self.scene.add(e));
self
}
fn last_mut(&mut self) -> &mut Entity {
let i = self
.last
.expect("modifier called before any shape was added");
&mut self.scene.entities[i]
}
pub fn circle(&mut self, id: &str, pos: Vec2, r: f32) -> &mut Self {
let mut e = Entity::new(id, Shape::Circle { r }, pos, self.theme.paper);
e.stroke = StrokeStyle {
fill: true,
outline: true,
outline_color: Some(self.theme.ink),
..Default::default()
};
self.push(e)
}
pub fn rect(&mut self, id: &str, pos: Vec2, w: f32, h: f32) -> &mut Self {
let mut e = Entity::new(id, Shape::Rect { w, h }, pos, self.theme.paper);
e.stroke = StrokeStyle {
fill: true,
outline: true,
outline_color: Some(self.theme.ink),
..Default::default()
};
self.push(e)
}
pub fn line(&mut self, id: &str, from: Vec2, to: Vec2) -> &mut Self {
self.push(Entity::new(id, Shape::Line { to }, from, self.theme.ink))
}
pub fn arrow(&mut self, id: &str, from: Vec2, to: Vec2) -> &mut Self {
self.push(Entity::new(id, Shape::Arrow { to }, from, self.theme.ink))
}
pub fn curve(&mut self, id: &str, from: Vec2, to: Vec2, bend: f32) -> &mut Self {
let mid = (from + to) / 2.0;
let d = to - from;
let len = d.length().max(1e-3);
let perp = Vec2::new(-d.y, d.x) / len;
let ctrl = mid + perp * bend;
self.push(Entity::new(
id,
Shape::Curve {
ctrl,
to,
arrow: false,
},
from,
self.theme.ink,
))
}
pub fn curve_arrow(&mut self, id: &str, from: Vec2, to: Vec2, bend: f32) -> &mut Self {
self.curve(id, from, to, bend);
if let Shape::Curve { arrow, .. } = &mut self.last_mut().shape {
*arrow = true;
}
self
}
pub fn polygon(&mut self, id: &str, pts: Vec<Vec2>) -> &mut Self {
let mut e = Entity::new(id, Shape::Polygon { pts }, Vec2::ZERO, self.theme.paper);
e.stroke = StrokeStyle {
fill: true,
outline: true,
outline_color: Some(self.theme.ink),
..Default::default()
};
self.push(e)
}
pub fn text(&mut self, id: &str, pos: Vec2, content: &str) -> &mut Self {
self.push(Entity::new(
id,
Shape::Text {
content: content.into(),
size: 28.0,
},
pos,
self.theme.ink,
))
}
pub fn cells(
&mut self,
prefix: &str,
n: usize,
center: Vec2,
cell: Vec2,
gap: f32,
labels: Option<&[&str]>,
) -> &mut Self {
let (shade, ink, faded) = (self.theme.paper_shade, self.theme.ink, self.theme.faded);
let stride = cell.x + gap;
let x0 = center.x - stride * (n as f32 - 1.0) / 2.0;
for i in 0..n {
let id = format!("{prefix}{i}");
let pos = Vec2::new(x0 + stride * i as f32, center.y);
self.rect(&id, pos, cell.x, cell.y)
.color(shade)
.outline_color(ink)
.stroke(2.0)
.tag(prefix)
.label(labels.map_or("", |l| l[i]));
self.text(&format!("{id}.idx"), Vec2::ZERO, &i.to_string())
.size(14.0)
.color(faded)
.follow(&id, Vec2::new(0.0, cell.y / 2.0 + 20.0));
}
self
}
pub fn code_block(&mut self, id: &str, pos: Vec2, lines: &[&str], size: f32) -> &mut Self {
for (i, line) in lines.iter().enumerate() {
self.text(
&format!("{id}.line{i}"),
Vec2::new(pos.x, pos.y + size * 1.6 * i as f32),
line,
)
.size(size)
.left()
.tag(id);
}
self
}
pub fn color(&mut self, c: Color) -> &mut Self {
self.last_mut().color = c;
self
}
pub fn role(&mut self, r: Role) -> &mut Self {
let c = self.theme.role(r);
self.color(c)
}
pub fn outlined(&mut self) -> &mut Self {
let e = self.last_mut();
e.stroke.fill = false;
e.stroke.outline = true;
self
}
pub fn filled(&mut self) -> &mut Self {
let e = self.last_mut();
e.stroke.fill = true;
e.stroke.outline = false;
self
}
pub fn stroke(&mut self, w: f32) -> &mut Self {
self.last_mut().stroke.width = w;
self
}
pub fn outline_color(&mut self, c: Color) -> &mut Self {
self.last_mut().stroke.outline_color = Some(c);
self
}
pub fn size(&mut self, s: f32) -> &mut Self {
if let Shape::Text { size, .. } = &mut self.last_mut().shape {
*size = s;
}
self
}
pub fn serif(&mut self) -> &mut Self {
self.last_mut().font = FontKind::Serif;
self
}
pub fn mono_bold(&mut self) -> &mut Self {
self.last_mut().font = FontKind::MonoBold;
self
}
pub fn z(&mut self, z: i32) -> &mut Self {
self.last_mut().z = z;
self
}
pub fn hidden(&mut self) -> &mut Self {
self.last_mut().opacity = 0.0;
self
}
pub fn opacity(&mut self, o: f32) -> &mut Self {
self.last_mut().opacity = o;
self
}
pub fn rot(&mut self, deg: f32) -> &mut Self {
self.last_mut().rot = deg;
self
}
pub fn wrap(&mut self, px: f32) -> &mut Self {
self.last_mut().wrap = Some(px);
self
}
pub fn left(&mut self) -> &mut Self {
self.last_mut().align = Align::Left;
self
}
pub fn untraced(&mut self) -> &mut Self {
self.last_mut().trace = 0.0;
self
}
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.last_mut().tags.push(tag.into());
self
}
pub fn sticky(&mut self) -> &mut Self {
self.last_mut().sticky = true;
self
}
pub fn follow(&mut self, id: &str, offset: Vec2) -> &mut Self {
self.last_mut().follow = Some((id.into(), offset));
self
}
pub fn label(&mut self, text: &str) -> &mut Self {
let (parent_id, parent_z, parent_sticky) = {
let e = self.last_mut();
(e.id.clone(), e.z, e.sticky)
};
let mut lbl = Entity::new(
format!("{parent_id}.label"),
Shape::Text {
content: text.into(),
size: 24.0,
},
Vec2::ZERO,
self.theme.ink,
);
lbl.font = FontKind::MonoBold;
lbl.z = parent_z + 1;
lbl.sticky = parent_sticky;
lbl.follow = Some((parent_id, Vec2::ZERO));
self.push(lbl)
}
}