Skip to main content

polytope_playground/
row.rs

1use loam::math::EuclideanR4;
2use loam::runtime::{Dispatch, DomainHandle, EdgeShading, Instance, Pose, Rejection, SpawnBundle};
3
4use crate::catalog::ShapeEntry;
5use crate::mode::Mode;
6use crate::{display, rest_of, Card, Playground, Slot};
7
8pub(crate) fn add_shape(
9    dispatch: &mut Dispatch<'_, Playground>,
10    domain: DomainHandle<EuclideanR4>,
11    entry: ShapeEntry,
12    card: Card,
13) -> Result<(), Rejection> {
14    if *dispatch.app.mode.get() != Mode::Rotate {
15        return Err(Rejection::Unsupported("shapes can only be added in Rotate"));
16    }
17    let index = dispatch.app.slots.len();
18    let rest = rest_of(index, index + 1);
19    let fallback = display::rotor_at(dispatch.app, *dispatch.app.time.get());
20    let r4 = dispatch.domains.typed(domain)?;
21    let frame = dispatch
22        .app
23        .slots
24        .iter()
25        .find_map(|(entity, _)| r4.poses().get(entity).map(|pose| pose.frame))
26        .unwrap_or(fallback);
27    let mut bundle = SpawnBundle::new()
28        .at(domain, Pose { point: rest, frame })
29        .row(Slot { index, entry, rest });
30    if let Some(geometry) = card.geometry {
31        let display = *dispatch.app.display.get();
32        let shading = card.shades.map_or(EdgeShading::Material, |shades| {
33            shades.of(*dispatch.app.color.get())
34        });
35        bundle = bundle.instance(
36            Instance::new(geometry, card.material)
37                .sectioned(card.cut)
38                .shaded(shading)
39                .line_style(display.wireframe_width_px, display.wireframe_opacity),
40        );
41    }
42    dispatch.spawn(bundle)?;
43    reflow(dispatch, domain)?;
44    dispatch.app.active.set(index);
45    Ok(())
46}
47
48pub(crate) fn reorder_shape(
49    dispatch: &mut Dispatch<'_, Playground>,
50    domain: DomainHandle<EuclideanR4>,
51    from: usize,
52    to: usize,
53) -> Result<(), Rejection> {
54    if *dispatch.app.mode.get() != Mode::Rotate {
55        return Err(Rejection::Unsupported(
56            "shapes can only be reordered in Rotate",
57        ));
58    }
59    let count = dispatch.app.slots.len();
60    if to > count {
61        return Err(Rejection::Unsupported("no such row gap"));
62    }
63    if !dispatch
64        .app
65        .slots
66        .iter()
67        .any(|(_, slot)| slot.index == from)
68    {
69        return Err(Rejection::Unsupported("no such slot"));
70    }
71    let destination = if to > from { to - 1 } else { to };
72    if destination == from {
73        return Ok(());
74    }
75    let active = *dispatch.app.active.get();
76    let active_entity = dispatch
77        .app
78        .slots
79        .iter()
80        .find(|(_, slot)| slot.index == active)
81        .map(|(entity, _)| entity);
82    for (_, slot) in dispatch.app.slots.iter_mut() {
83        slot.index = reordered_index(slot.index, from, destination);
84    }
85    reflow(dispatch, domain)?;
86    if let Some(slot) = active_entity.and_then(|entity| dispatch.app.slots.get(entity)) {
87        dispatch.app.active.set(slot.index);
88    }
89    Ok(())
90}
91
92pub(crate) fn reordered_index(index: usize, from: usize, destination: usize) -> usize {
93    if index == from {
94        destination
95    } else if destination <= index && index < from {
96        index + 1
97    } else if from < index && index <= destination {
98        index - 1
99    } else {
100        index
101    }
102}
103
104pub(crate) fn remove_shape(
105    dispatch: &mut Dispatch<'_, Playground>,
106    domain: DomainHandle<EuclideanR4>,
107    slot: usize,
108) -> Result<(), Rejection> {
109    if *dispatch.app.mode.get() != Mode::Rotate {
110        return Err(Rejection::Unsupported(
111            "shapes can only be removed in Rotate",
112        ));
113    }
114    let entity = dispatch
115        .app
116        .slots
117        .iter()
118        .find(|(_, held)| held.index == slot)
119        .map(|(entity, _)| entity)
120        .ok_or(Rejection::Unsupported("no such slot"))?;
121    dispatch.despawn(entity)?;
122    for (_, held) in dispatch.app.slots.iter_mut() {
123        if held.index > slot {
124            held.index -= 1;
125        }
126    }
127    reflow(dispatch, domain)?;
128    let active = *dispatch.app.active.get();
129    dispatch.app.active.set(
130        active
131            .saturating_sub(usize::from(active > slot))
132            .min(dispatch.app.slots.len().saturating_sub(1)),
133    );
134    Ok(())
135}
136
137fn reflow(
138    dispatch: &mut Dispatch<'_, Playground>,
139    domain: DomainHandle<EuclideanR4>,
140) -> Result<(), Rejection> {
141    let count = dispatch.app.slots.len();
142    let r4 = dispatch.domains.typed(domain)?;
143    for (entity, slot) in dispatch.app.slots.iter_mut() {
144        slot.rest = rest_of(slot.index, count);
145        if r4.poses().contains(entity) {
146            r4.set_point(entity, slot.rest)?;
147        }
148    }
149    Ok(())
150}