1use crate::{
2 algorithms::Bounded,
3 components::{
4 BoundingBox, DrawingObject, Geometry, Layer, LineStyle, PointStyle,
5 Viewport, WindowStyle,
6 },
7 CanvasSpace, DrawingSpace, Line, Point,
8};
9use euclid::{Point2D, Scale, Size2D};
10use kurbo::Circle;
11use piet::RenderContext;
12use shred_derive::SystemData;
13use specs::{join::MaybeJoin, prelude::*};
14use std::{cmp::Reverse, collections::BTreeMap};
15
16#[derive(Debug, Clone, PartialEq)]
18pub struct Window(pub Entity);
19
20impl Window {
21 pub fn create(world: &mut World) -> Self {
23 let ent = world
24 .create_entity()
25 .with(Viewport {
26 centre: Point::zero(),
27 pixels_per_drawing_unit: Scale::new(1.0),
28 })
29 .with(LineStyle::default())
30 .with(PointStyle::default())
31 .with(WindowStyle::default())
32 .build();
33
34 Window(ent)
35 }
36
37 pub fn render_system<'a, R>(
45 &'a self,
46 backend: R,
47 window_size: Size2D<f64, CanvasSpace>,
48 ) -> impl System<'a> + 'a
49 where
50 R: RenderContext + 'a,
51 {
52 RenderSystem {
53 backend,
54 window_size,
55 window: self,
56 }
57 }
58}
59
60macro_rules! components {
61 ($( $get:ident, $get_mut:ident, $component_name:expr => $component_type:ty ),* $(,)?) => {
62 $(
63 #[doc = "Get a reference to the [`Window`]'s [`"]
64 #[doc = $component_name]
65 #[doc = "`] component."]
66 pub fn $get<'a>(&self, storage: &'a ReadStorage<'a, $component_type>) -> &'a $component_type
67 {
68 storage
69 .get(self.0)
70 .expect(concat!("The window should always have a ", stringify!($component_type), " component"))
71 }
72
73 #[doc = "Get a mutable reference to the [`Window`]'s [`"]
74 #[doc = $component_name]
75 #[doc = "`] component."]
76 pub fn $get_mut<'a, 'world: 'a>(&self, storage: &'a mut WriteStorage<'world, $component_type>) -> &'a mut $component_type
77 {
78 storage
79 .get_mut(self.0)
80 .expect(concat!("The window should always have a ", stringify!($component_type), " component"))
81 }
82 )*
83 };
84}
85
86impl Window {
88 components! {
89 viewport, viewport_mut, stringify!(Viewport) => Viewport,
90 default_point_style, default_point_style_mut, stringify!(PointStyle) => PointStyle,
91 default_line_style, default_line_style_mut, stringify!(LineStyle) => LineStyle,
92 style, style_mut, stringify!(WindowStyle) => WindowStyle,
93 }
94}
95
96#[derive(Debug)]
101struct RenderSystem<'window, B> {
102 backend: B,
103 window_size: Size2D<f64, CanvasSpace>,
104 window: &'window Window,
105}
106
107impl<'window, B> RenderSystem<'window, B> {
108 fn viewport_dimensions(&self, viewport: &Viewport) -> BoundingBox {
110 let window_size = viewport
111 .pixels_per_drawing_unit
112 .inv()
113 .transform_size(self.window_size);
114
115 BoundingBox::from_centre_and_size(viewport.centre, window_size)
116 }
117}
118
119impl<'window, B: RenderContext> RenderSystem<'window, B> {
120 fn render(
121 &mut self,
122 ent: Entity,
123 drawing_object: &DrawingObject,
124 styles: &Styling,
125 viewport: &Viewport,
126 ) {
127 match drawing_object.geometry {
128 Geometry::Point(point) => {
129 self.render_point(
130 ent,
131 point,
132 drawing_object.layer,
133 styles,
134 viewport,
135 );
136 },
137 Geometry::Line(ref line) => {
138 self.render_line(
139 ent,
140 line,
141 drawing_object.layer,
142 styles,
143 viewport,
144 );
145 },
146 _ => unimplemented!(),
147 }
148 }
149
150 fn render_point(
152 &mut self,
153 entity: Entity,
154 point: Point,
155 layer: Entity,
156 styles: &Styling,
157 viewport: &Viewport,
158 ) {
159 let style = resolve_point_style(styles, self.window, entity, layer);
160
161 let centre = self.to_canvas_coordinates(point, viewport);
162 let shape = Circle {
163 center: kurbo::Point::new(centre.x, centre.y),
164 radius: style.radius.in_pixels(viewport.pixels_per_drawing_unit),
165 };
166 log::trace!("Drawing {:?} as {:?} using {:?}", point, shape, style);
167
168 self.backend.fill(shape, &style.colour);
169 }
170
171 fn render_line(
172 &mut self,
173 entity: Entity,
174 line: &Line,
175 layer: Entity,
176 styles: &Styling,
177 viewport: &Viewport,
178 ) {
179 let style = resolve_line_style(styles, self.window, entity, layer);
180
181 let start = self.to_canvas_coordinates(line.start, viewport);
182 let end = self.to_canvas_coordinates(line.end, viewport);
183 let shape = kurbo::Line::new(start.to_tuple(), end.to_tuple());
184 let stroke_width =
185 style.width.in_pixels(viewport.pixels_per_drawing_unit);
186 log::trace!("Drawing {:?} as {:?} using {:?}", line, shape, style);
187
188 self.backend.stroke(shape, &style.stroke, stroke_width);
189 }
190
191 fn to_canvas_coordinates(
194 &self,
195 point: Point2D<f64, DrawingSpace>,
196 viewport: &Viewport,
197 ) -> Point2D<f64, CanvasSpace> {
198 super::to_canvas_coordinates(point, viewport, self.window_size)
199 }
200}
201
202impl<'window, 'world, B: RenderContext> System<'world>
203 for RenderSystem<'window, B>
204{
205 type SystemData = (
206 DrawOrder<'world>,
207 Styling<'world>,
208 ReadStorage<'world, Viewport>,
209 );
210
211 fn run(&mut self, data: Self::SystemData) {
212 let (draw_order, styling, viewports) = data;
213
214 let window_style = self.window.style(&styling.window_styles);
215 let viewport = self.window.viewport(&viewports);
216
217 self.backend.clear(window_style.background_colour.clone());
219
220 let viewport_dimensions = self.viewport_dimensions(&viewport);
221
222 for (ent, obj) in draw_order.calculate(viewport_dimensions) {
223 self.render(ent, obj, &styling, viewport);
224 }
225 }
226}
227
228#[derive(SystemData)]
230struct Styling<'world> {
231 point_styles: ReadStorage<'world, PointStyle>,
232 line_styles: ReadStorage<'world, LineStyle>,
233 window_styles: ReadStorage<'world, WindowStyle>,
234}
235
236fn resolve_point_style<'a>(
237 styling: &'a Styling,
238 window: &'a Window,
239 point: Entity,
240 layer: Entity,
241) -> &'a PointStyle {
242 styling
243 .point_styles
244 .get(point)
246 .or_else(|| styling.point_styles.get(layer))
248 .unwrap_or_else(|| window.default_point_style(&styling.point_styles))
250}
251
252fn resolve_line_style<'a>(
253 styling: &'a Styling,
254 window: &'a Window,
255 line: Entity,
256 layer: Entity,
257) -> &'a LineStyle {
258 styling
259 .line_styles
260 .get(line)
261 .or_else(|| styling.line_styles.get(layer))
262 .unwrap_or_else(|| window.default_line_style(&styling.line_styles))
263}
264
265#[derive(SystemData)]
268struct DrawOrder<'world> {
269 entities: Entities<'world>,
270 drawing_objects: ReadStorage<'world, DrawingObject>,
271 layers: ReadStorage<'world, Layer>,
272 bounding_boxes: ReadStorage<'world, BoundingBox>,
273}
274
275impl<'world> DrawOrder<'world> {
276 fn calculate(
277 &self,
278 viewport_dimensions: BoundingBox,
279 ) -> impl Iterator<Item = (Entity, &'_ DrawingObject)> + '_ {
280 type EntitiesByZLevel<'a> =
281 BTreeMap<Reverse<usize>, Vec<(Entity, &'a DrawingObject)>>;
282
283 let mut drawing_objects = EntitiesByZLevel::new();
287
288 for (ent, obj, bounds) in (
296 &self.entities,
297 &self.drawing_objects,
298 MaybeJoin(&self.bounding_boxes),
299 )
300 .join()
301 {
302 let Layer { z_level, visible } = self
303 .layers
304 .get(obj.layer)
305 .expect("The object's layer was deleted");
306
307 let bounds = bounds
309 .copied()
310 .unwrap_or_else(|| obj.geometry.bounding_box());
311
312 if *visible && viewport_dimensions.intersects_with(bounds) {
313 drawing_objects
314 .entry(Reverse(*z_level))
315 .or_default()
316 .push((ent, obj));
317 }
318 }
319
320 drawing_objects.into_iter().flat_map(|(_, items)| items)
321 }
322}