1use js_sys::JsString;
2use serde::Serialize;
3
4use crate::local::RoomName;
5
6#[derive(Debug, Clone, Default, Serialize)]
7#[serde(rename_all = "camelCase")]
8pub struct CircleStyle {
9 #[serde(skip_serializing_if = "Option::is_none")]
10 radius: Option<f32>,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 fill: Option<String>,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 opacity: Option<f32>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 stroke: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 stroke_width: Option<f32>,
19}
20
21impl CircleStyle {
22 pub fn radius(mut self, val: f32) -> CircleStyle {
23 self.radius = Some(val);
24 self
25 }
26
27 pub fn fill(mut self, val: &str) -> CircleStyle {
28 self.fill = Some(val.to_string());
29 self
30 }
31
32 pub fn opacity(mut self, val: f32) -> CircleStyle {
33 self.opacity = Some(val);
34 self
35 }
36
37 pub fn stroke(mut self, val: &str) -> CircleStyle {
38 self.stroke = Some(val.to_string());
39 self
40 }
41
42 pub fn stroke_width(mut self, val: f32) -> CircleStyle {
43 self.stroke_width = Some(val);
44 self
45 }
46}
47
48#[derive(Debug, Clone, Serialize)]
49pub struct CircleData {
50 x: f32,
51 y: f32,
52 #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
53 style: Option<CircleStyle>,
54}
55
56#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub enum LineDrawStyle {
59 #[default]
60 Solid,
61 Dashed,
62 Dotted,
63}
64
65impl LineDrawStyle {
66 pub fn is_solid(&self) -> bool {
67 matches!(self, LineDrawStyle::Solid)
68 }
69}
70
71#[derive(Debug, Clone, Default, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct LineStyle {
74 #[serde(skip_serializing_if = "Option::is_none")]
75 width: Option<f32>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 color: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 opacity: Option<f32>,
80 #[serde(skip_serializing_if = "LineDrawStyle::is_solid")]
81 line_style: LineDrawStyle,
82}
83
84impl LineStyle {
85 pub fn width(mut self, val: f32) -> LineStyle {
86 self.width = Some(val);
87 self
88 }
89
90 pub fn color(mut self, val: &str) -> LineStyle {
91 self.color = Some(val.to_string());
92 self
93 }
94
95 pub fn opacity(mut self, val: f32) -> LineStyle {
96 self.opacity = Some(val);
97 self
98 }
99
100 pub fn line_style(mut self, val: LineDrawStyle) -> LineStyle {
101 self.line_style = val;
102 self
103 }
104}
105
106#[derive(Debug, Clone, Serialize)]
107pub struct LineData {
108 x1: f32,
109 y1: f32,
110 x2: f32,
111 y2: f32,
112 #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
113 style: Option<LineStyle>,
114}
115
116#[derive(Debug, Clone, Default, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub struct RectStyle {
119 #[serde(skip_serializing_if = "Option::is_none")]
120 fill: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 opacity: Option<f32>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 stroke: Option<String>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 stroke_width: Option<f32>,
127 #[serde(skip_serializing_if = "LineDrawStyle::is_solid")]
128 line_style: LineDrawStyle,
129}
130
131impl RectStyle {
132 pub fn fill(mut self, val: &str) -> RectStyle {
133 self.fill = Some(val.to_string());
134 self
135 }
136
137 pub fn opacity(mut self, val: f32) -> RectStyle {
138 self.opacity = Some(val);
139 self
140 }
141
142 pub fn stroke(mut self, val: &str) -> RectStyle {
143 self.stroke = Some(val.to_string());
144 self
145 }
146
147 pub fn stroke_width(mut self, val: f32) -> RectStyle {
148 self.stroke_width = Some(val);
149 self
150 }
151
152 pub fn line_style(mut self, val: LineDrawStyle) -> RectStyle {
153 self.line_style = val;
154 self
155 }
156}
157
158#[derive(Debug, Clone, Serialize)]
159pub struct RectData {
160 x: f32,
161 y: f32,
162 #[serde(rename = "w")]
163 width: f32,
164 #[serde(rename = "h")]
165 height: f32,
166 #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
167 style: Option<RectStyle>,
168}
169
170#[derive(Debug, Clone, Default, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct PolyStyle {
173 #[serde(skip_serializing_if = "Option::is_none")]
174 fill: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 opacity: Option<f32>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 stroke: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 stroke_width: Option<f32>,
181 #[serde(skip_serializing_if = "LineDrawStyle::is_solid")]
182 line_style: LineDrawStyle,
183}
184
185impl PolyStyle {
186 pub fn fill(mut self, val: &str) -> PolyStyle {
187 self.fill = Some(val.to_string());
188 self
189 }
190
191 pub fn opacity(mut self, val: f32) -> PolyStyle {
192 self.opacity = Some(val);
193 self
194 }
195
196 pub fn stroke(mut self, val: &str) -> PolyStyle {
197 self.stroke = Some(val.to_string());
198 self
199 }
200
201 pub fn stroke_width(mut self, val: f32) -> PolyStyle {
202 self.stroke_width = Some(val);
203 self
204 }
205
206 pub fn line_style(mut self, val: LineDrawStyle) -> PolyStyle {
207 self.line_style = val;
208 self
209 }
210}
211
212#[derive(Debug, Clone, Serialize)]
213pub struct PolyData {
214 points: Vec<(f32, f32)>,
215 #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
216 style: Option<PolyStyle>,
217}
218
219#[derive(Debug, Clone, Serialize)]
220#[serde(untagged)]
221pub enum FontStyle {
222 Size(f32),
223 Custom(String),
224}
225
226#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
227#[serde(rename_all = "camelCase")]
228pub enum TextAlign {
229 #[default]
230 Center,
231 Left,
232 Right,
233}
234
235impl TextAlign {
236 pub fn is_center(&self) -> bool {
237 matches!(self, TextAlign::Center)
238 }
239}
240
241#[derive(Debug, Clone, Default, Serialize)]
242#[serde(rename_all = "camelCase")]
243pub struct TextStyle {
244 #[serde(skip_serializing_if = "Option::is_none")]
245 color: Option<String>,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 font: Option<FontStyle>,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 stroke: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 stroke_width: Option<f32>,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 background_color: Option<String>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 background_padding: Option<f32>,
256 #[serde(skip_serializing_if = "TextAlign::is_center")]
257 align: TextAlign,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 opacity: Option<f32>,
260}
261
262impl TextStyle {
263 pub fn color(mut self, val: &str) -> TextStyle {
264 self.color = Some(val.to_string());
265 self
266 }
267
268 pub fn font(mut self, val: f32) -> TextStyle {
269 self.font = Some(FontStyle::Size(val));
270 self
271 }
272
273 pub fn custom_font(mut self, val: &str) -> TextStyle {
274 self.font = Some(FontStyle::Custom(val.to_string()));
275 self
276 }
277
278 pub fn stroke(mut self, val: &str) -> TextStyle {
279 self.stroke = Some(val.to_string());
280 self
281 }
282
283 pub fn stroke_width(mut self, val: f32) -> TextStyle {
284 self.stroke_width = Some(val);
285 self
286 }
287
288 pub fn background_color(mut self, val: &str) -> TextStyle {
289 self.background_color = Some(val.to_string());
290 self
291 }
292
293 pub fn background_padding(mut self, val: f32) -> TextStyle {
294 self.background_padding = Some(val);
295 self
296 }
297
298 pub fn align(mut self, val: TextAlign) -> TextStyle {
299 self.align = val;
300 self
301 }
302
303 pub fn opacity(mut self, val: f32) -> TextStyle {
304 self.opacity = Some(val);
305 self
306 }
307}
308
309#[derive(Debug, Clone, Serialize)]
310pub struct TextData {
311 text: String,
312 x: f32,
313 y: f32,
314 #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
315 style: Option<TextStyle>,
316}
317
318#[derive(Debug, Clone, Serialize)]
319#[serde(tag = "t")]
320pub enum Visual {
321 #[serde(rename = "c")]
322 Circle(CircleData),
323 #[serde(rename = "l")]
324 Line(LineData),
325 #[serde(rename = "r")]
326 Rect(RectData),
327 #[serde(rename = "p")]
328 Poly(PolyData),
329 #[serde(rename = "t")]
330 Text(TextData),
331}
332
333impl Visual {
334 pub fn circle(x: f32, y: f32, style: Option<CircleStyle>) -> Visual {
335 Visual::Circle(CircleData { x, y, style })
336 }
337
338 pub fn line(from: (f32, f32), to: (f32, f32), style: Option<LineStyle>) -> Visual {
339 Visual::Line(LineData {
340 x1: from.0,
341 y1: from.1,
342 x2: to.0,
343 y2: to.1,
344 style,
345 })
346 }
347
348 pub fn rect(x: f32, y: f32, width: f32, height: f32, style: Option<RectStyle>) -> Visual {
349 Visual::Rect(RectData {
350 x,
351 y,
352 width,
353 height,
354 style,
355 })
356 }
357
358 pub fn poly(points: Vec<(f32, f32)>, style: Option<PolyStyle>) -> Visual {
359 Visual::Poly(PolyData { points, style })
360 }
361
362 pub fn text(x: f32, y: f32, text: String, style: Option<TextStyle>) -> Visual {
363 Visual::Text(TextData { x, y, text, style })
364 }
365}
366
367#[derive(Debug)]
368pub struct RoomVisual {
369 room_name: Option<RoomName>,
370}
371
372impl RoomVisual {
373 pub fn new(room_name: Option<RoomName>) -> RoomVisual {
374 RoomVisual { room_name }
375 }
376
377 pub fn draw(&self, visual: &Visual) {
378 let name: Option<JsString> = self.room_name.map(|name| name.to_string().into());
379 let val = serde_wasm_bindgen::to_value(visual).expect("expect convert visual to value");
380
381 crate::console::add_visual(name.as_ref(), &val);
382 }
383
384 pub fn draw_multi(&self, visuals: &[Visual]) {
385 let name: Option<JsString> = self.room_name.map(|name| name.to_string().into());
386
387 for visual in visuals {
388 let val = serde_wasm_bindgen::to_value(visual).expect("expect convert visual to value");
389
390 crate::console::add_visual(name.as_ref(), &val);
391 }
392 }
393
394 pub fn circle(&self, x: f32, y: f32, style: Option<CircleStyle>) {
395 self.draw(&Visual::circle(x, y, style));
396 }
397
398 pub fn line(&self, from: (f32, f32), to: (f32, f32), style: Option<LineStyle>) {
399 self.draw(&Visual::line(from, to, style));
400 }
401
402 pub fn rect(&self, x: f32, y: f32, width: f32, height: f32, style: Option<RectStyle>) {
403 self.draw(&Visual::rect(x, y, width, height, style));
404 }
405
406 pub fn poly(&self, points: Vec<(f32, f32)>, style: Option<PolyStyle>) {
407 self.draw(&Visual::poly(points, style));
408 }
409
410 pub fn text(&self, x: f32, y: f32, text: String, style: Option<TextStyle>) {
411 self.draw(&Visual::text(x, y, text, style));
412 }
413}