use kurbo::Rect as KRect;
use peniko::ImageData;
use crate::display::{Frame, List};
use crate::proto;
use crate::render::{Damage, Drawing, Renderer, Undrawable};
use crate::scene::{self, Applied, Edits, Scene, Theme, Uses};
use crate::tiles::{self, Level, View};
pub struct Design {
scene: Scene,
uses: Uses,
themes: [Shown; 2],
damage: Vec<(Theme, Damage)>,
}
#[derive(Default)]
struct Shown {
drawing: Option<Drawing>,
stale: bool,
broken: Option<Undrawable>,
good: Option<Drawing>,
}
const THEMES: [Theme; 2] = [Theme::Light, Theme::Dark];
fn index(theme: Theme) -> usize {
match theme {
Theme::Light => 0,
Theme::Dark => 1,
}
}
impl Design {
pub fn new(scene: Scene) -> Design {
Design {
uses: Uses::of(&scene.root),
scene,
themes: Default::default(),
damage: THEMES.iter().map(|&t| (t, Damage::All)).collect(),
}
}
pub fn scene(&self) -> &Scene {
&self.scene
}
pub fn replace(&mut self, renderer: &mut Renderer, scene: Scene) {
for shown in &mut self.themes {
if shown.broken.is_some() {
shown.drawing = None;
} else if let Some(drawing) = shown.drawing.as_mut().filter(|_| !shown.stale) {
drawing.prepare(renderer, &self.scene, everywhere());
}
shown.stale = true;
shown.broken = None;
}
self.uses = Uses::of(&scene.root);
self.scene = scene;
self.damage.extend(THEMES.iter().map(|&t| (t, Damage::All)));
}
pub fn apply(&mut self, renderer: &mut Renderer, edits: &Edits) -> Result<Applied, String> {
let (applied, undo) = scene::applied(&mut self.scene, &mut self.uses, edits)?;
let mut breaking = Vec::new();
for theme in THEMES {
let shown = &mut self.themes[index(theme)];
let Some(drawing) = shown.drawing.as_mut().filter(|_| !shown.stale) else {
shown.broken = None;
continue;
};
match drawing.update(renderer, &self.scene, &applied) {
Ok(damage) => {
let damage = if shown.broken.take().is_some() {
shown.good = None;
Damage::All
} else {
damage
};
self.damage.push((theme, damage));
}
Err(undrawable) => {
if shown.broken.replace(undrawable).is_none() {
breaking.push(theme);
}
}
}
}
if !breaking.is_empty() {
undo.undo(&mut self.scene, &mut self.uses);
for theme in breaking {
self.themes[index(theme)].good = renderer.drawing(&self.scene, theme, None).ok();
}
scene::apply(&mut self.scene, &mut self.uses, edits)
.expect("edits apply again to the scene they were just undone from");
}
Ok(applied)
}
pub fn patch(&mut self, renderer: &mut Renderer, patch: &proto::Patch) -> Result<(), String> {
let edits = crate::wire::edits(patch, &self.scene)?;
self.apply(renderer, &edits).map(|_| ())
}
pub fn draw(&mut self, renderer: &mut Renderer, theme: Theme) -> Result<(), &Undrawable> {
let i = index(theme);
let shown = &self.themes[i];
if shown.drawing.is_some() && !shown.stale || shown.broken.is_some() {
return match &self.themes[i].broken {
Some(undrawable) => Err(undrawable),
None => Ok(()),
};
}
let last = self.themes[i]
.drawing
.take()
.or_else(|| self.themes[1 - i].drawing.take());
let made = {
let after = last.as_ref().or(self.themes[i].good.as_ref());
renderer.drawing(&self.scene, theme, after)
};
let last = match last {
Some(other) if other.theme() != theme => {
self.themes[1 - i].drawing = Some(other);
None
}
last => last,
};
let shown = &mut self.themes[i];
match made {
Ok(drawing) => {
shown.drawing = Some(drawing);
shown.stale = false;
if shown.good.take().is_some() {
self.damage.push((theme, Damage::All));
}
Ok(())
}
Err(undrawable) => {
if shown.good.is_none() {
shown.good = last;
}
Err(shown.broken.insert(undrawable))
}
}
}
pub fn forget(&mut self, theme: Theme) {
self.themes[index(theme)] = Shown::default();
self.damage.retain(|(t, _)| *t != theme);
}
pub fn problem(&self, theme: Theme) -> Option<&Undrawable> {
self.themes[index(theme)].broken.as_ref()
}
pub fn take_damage(&mut self) -> Vec<(Theme, Damage)> {
std::mem::take(&mut self.damage)
}
pub fn drawing(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<&Drawing> {
let drawn = self.draw(renderer, theme).is_ok();
let shown = &self.themes[index(theme)];
if drawn {
shown.drawing.as_ref()
} else {
shown.good.as_ref()
}
}
pub fn area(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<Frame> {
self.drawing(renderer, theme).map(Drawing::area)
}
pub fn action_at(
&mut self,
renderer: &mut Renderer,
theme: Theme,
x: f64,
y: f64,
) -> Option<&str> {
let drawing = self.prepared(renderer, theme, KRect::new(x, y, x, y))?;
drawing.action_at(x as f32, y as f32)
}
pub fn view_tiles(
&mut self,
renderer: &mut Renderer,
theme: Theme,
view: &View,
) -> Vec<(u32, u32)> {
let Some(a) = self.area(renderer, theme) else {
return Vec::new();
};
let area = KRect::new(
a.x as f64,
a.y as f64,
(a.x + a.width) as f64,
(a.y + a.height) as f64,
);
view.tiles(area, Level::of(view.zoom))
}
pub fn images(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<&[ImageData]> {
self.drawing(renderer, theme).map(|d| d.images.as_slice())
}
pub fn tile(
&mut self,
renderer: &mut Renderer,
theme: Theme,
scale: f64,
col: u32,
row: u32,
) -> Option<&List> {
self.painted(renderer, theme, |list| {
tiles::tile_rect(list, scale, col, row)
})
}
pub fn strip(
&mut self,
renderer: &mut Renderer,
theme: Theme,
col: u32,
y: u32,
rows: u32,
) -> Option<&List> {
self.painted(renderer, theme, |list| {
let (x0, y0) = (list.area.x as f64, list.area.y as f64);
KRect::new(
x0 + (col * tiles::TILE) as f64,
y0 + y as f64,
x0 + ((col + 1) * tiles::TILE) as f64,
y0 + (y + rows) as f64,
)
})
}
pub fn whole(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<&List> {
self.painted(renderer, theme, |_| everywhere())
}
fn painted(
&mut self,
renderer: &mut Renderer,
theme: Theme,
area: impl FnOnce(&List) -> KRect,
) -> Option<&List> {
let rect = area(&self.drawing(renderer, theme)?.list);
self.prepared(renderer, theme, rect).map(|d| &d.list)
}
fn prepared(&mut self, renderer: &mut Renderer, theme: Theme, rect: KRect) -> Option<&Drawing> {
if self.draw(renderer, theme).is_err() {
return self.themes[index(theme)].good.as_ref();
}
let drawing = self.themes[index(theme)].drawing.as_mut()?;
drawing.prepare(renderer, &self.scene, rect);
Some(drawing)
}
}
fn everywhere() -> KRect {
KRect::new(-1e12, -1e12, 1e12, 1e12)
}
#[cfg(test)]
impl Design {
pub(crate) fn held(&mut self, theme: Theme) -> &mut Drawing {
self.themes[index(theme)].drawing.as_mut().expect("drawn")
}
}
#[cfg(test)]
mod tests;