polytope_playground/
row.rs1use 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}