screeps/objects/impls/map_visual.rs
1use js_sys::JsString;
2use serde::Serialize;
3
4use crate::{
5 local::{Position, RoomCoordinate, RoomName},
6 objects::{CircleStyle, LineStyle, PolyStyle, RectStyle},
7 TextAlign,
8};
9
10#[derive(Clone, Serialize)]
11pub struct MapCircleData {
12 x: RoomCoordinate,
13 y: RoomCoordinate,
14 n: RoomName,
15 #[serde(rename = "s")]
16 style: CircleStyle,
17}
18
19#[derive(Clone, Serialize)]
20pub struct MapLineData {
21 x1: RoomCoordinate,
22 y1: RoomCoordinate,
23 n1: RoomName,
24 x2: RoomCoordinate,
25 y2: RoomCoordinate,
26 n2: RoomName,
27 #[serde(rename = "s")]
28 style: LineStyle,
29}
30
31#[derive(Clone, Serialize)]
32pub struct MapRectData {
33 x: RoomCoordinate,
34 y: RoomCoordinate,
35 n: RoomName,
36 #[serde(rename = "w")]
37 width: u32,
38 #[serde(rename = "h")]
39 height: u32,
40 #[serde(rename = "s")]
41 style: RectStyle,
42}
43
44#[derive(Clone, Serialize)]
45pub struct MapPolyPoint {
46 x: RoomCoordinate,
47 y: RoomCoordinate,
48 n: RoomName,
49}
50
51impl From<&Position> for MapPolyPoint {
52 fn from(pos: &Position) -> MapPolyPoint {
53 MapPolyPoint {
54 x: pos.x(),
55 y: pos.y(),
56 n: pos.room_name(),
57 }
58 }
59}
60
61#[derive(Clone, Serialize)]
62pub struct MapPolyData {
63 points: Vec<MapPolyPoint>,
64 #[serde(rename = "s")]
65 style: PolyStyle,
66}
67
68/// The font style for map text visuals.
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
70#[serde(rename_all = "lowercase")]
71pub enum MapFontStyle {
72 /// Use the normal font face.
73 #[default]
74 Normal,
75 /// Use the italic font face.
76 Italic,
77 /// Use the oblique font face.
78 Oblique,
79}
80
81impl MapFontStyle {
82 pub fn is_normal(&self) -> bool {
83 matches!(self, MapFontStyle::Normal)
84 }
85}
86
87/// The font variant for map text visuals.
88#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
89#[serde(rename_all = "kebab-case")]
90pub enum MapFontVariant {
91 /// No variant for the font, text rendered normally.
92 #[default]
93 Normal,
94 /// Render all lowercase characters in small caps.
95 SmallCaps,
96}
97
98impl MapFontVariant {
99 pub fn is_normal(&self) -> bool {
100 matches!(self, MapFontVariant::Normal)
101 }
102}
103
104/// Settings for text visuals on the map, used with [`MapVisual::text`].
105///
106/// This is different than [`TextStyle`] which is used with [`RoomVisual::text`]
107/// because the two methods take data in a slightly different format. Notably,
108/// room visuals accept colors in any web format and take use a shorthond for
109/// font data, where map visuals require stricter color formats and have
110/// different font options.
111///
112/// <div class="warning">
113/// <b>Warning</b>
114///
115/// The `backgroundPadding` setting in the Screeps docs does not function in
116/// game so it is not present in this API.
117/// </div><br/>
118///
119/// See also: [Screeps docs](https://docs.screeps.com/api/#Game.map-visual.text).
120///
121/// [`TextStyle`]: crate::objects::visual::TextStyle
122/// [`RoomVisual::text`]: crate::objects::visual::RoomVisual::text
123#[derive(Clone, Default, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct MapTextStyle {
126 #[serde(skip_serializing_if = "Option::is_none")]
127 color: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 font_family: Option<String>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 font_size: Option<f32>,
132 #[serde(skip_serializing_if = "MapFontStyle::is_normal")]
133 font_style: MapFontStyle,
134 #[serde(skip_serializing_if = "MapFontVariant::is_normal")]
135 font_variant: MapFontVariant,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 stroke: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 stroke_width: Option<f32>,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 background_color: Option<String>,
142 // This setting does not do anything, even though it's documented.
143 // #[serde(skip_serializing_if = "Option::is_none")]
144 // background_padding: Option<f32>,
145 #[serde(skip_serializing_if = "TextAlign::is_center")]
146 align: TextAlign,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 opacity: Option<f32>,
149}
150
151impl MapTextStyle {
152 /// Sets the color of the text style.
153 ///
154 /// **Unlike room visuals, only hex triplets with a leading `#` are valid.**
155 ///
156 /// The default value is `#FFFFFF`.
157 ///
158 /// # Examples
159 ///
160 /// ```rust
161 /// use screeps::MapTextStyle;
162 ///
163 /// // Bright pink! We really need to see this text!
164 /// let style = MapTextStyle::default().color("#FF00FF");
165 /// ```
166 pub fn color(mut self, val: impl Into<String>) -> Self {
167 self.color = Some(val.into());
168 self
169 }
170
171 /// Sets the font family of the text style. Font families with spaces in
172 /// their name must be quoted. Normally this involves escaping quotes in a
173 /// string literal.
174 ///
175 /// The default value is `sans-serif`.
176 ///
177 /// # Examples
178 ///
179 /// ```rust
180 /// use screeps::MapTextStyle;
181 /// let monospace_style = MapTextStyle::default().font_family("monospace");
182 ///
183 /// let font_family_style = MapTextStyle::default().font_family("\"Comic Sans MS\"");
184 ///
185 /// let fallback_style = MapTextStyle::default().font_family("\"Comic Sans MS\", Times, serif");
186 /// ```
187 ///
188 /// For more information about font families, see the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family).
189 pub fn font_family(mut self, val: impl Into<String>) -> Self {
190 self.font_family = Some(val.into());
191 self
192 }
193
194 /// Sets the size of the font, in *game coordinates*. This means that a
195 /// `font_size` of 1 corresponds to the same size as one coordinate on the
196 /// map. This does **not** support any other units for size.
197 ///
198 /// The default value is `10`.
199 ///
200 /// # Examples
201 /// ```rust
202 /// use screeps::MapTextStyle;
203 ///
204 /// let tiny_text = MapTextStyle::default().font_size(2_f32);
205 ///
206 /// let room_height = MapTextStyle::default().font_size(50_f32);
207 /// ```
208 pub fn font_size(mut self, val: f32) -> Self {
209 self.font_size = Some(val);
210 self
211 }
212
213 /// Sets the style of the font. This controls whether a font should be
214 /// styled with a normal, italic, or oblique face.
215 ///
216 /// The default value is [`MapFontStyle::Normal`].
217 ///
218 /// `oblique <angle>` is not supported.
219 ///
220 /// # Examples
221 /// ```rust
222 /// use screeps::{MapFontStyle, MapTextStyle};
223 ///
224 /// let normal = MapTextStyle::default().font_style(MapFontStyle::Normal);
225 /// let italic = MapTextStyle::default().font_style(MapFontStyle::Italic);
226 /// ```
227 ///
228 /// For more information about font styles, see the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
229 ///
230 /// [`MapFontStyle::Normal`]: self::MapFontStyle#variant.Normal
231 pub fn font_style(mut self, val: MapFontStyle) -> Self {
232 self.font_style = val;
233 self
234 }
235
236 /// Sets the variant of the font. This controls whether or not text is
237 /// rendered as small caps.
238 ///
239 /// The default value is [`MapFontVariant::Normal`].
240 ///
241 /// # Examples
242 /// ```rust
243 /// use screeps::{MapFontVariant, MapTextStyle};
244 ///
245 /// let normal = MapTextStyle::default().font_variant(MapFontVariant::Normal);
246 /// let small_caps = MapTextStyle::default().font_variant(MapFontVariant::SmallCaps);
247 /// ```
248 ///
249 /// [`MapFontVariant::Normal`]: self::MapFontVariant#variant.Normal
250 pub fn font_variant(mut self, val: MapFontVariant) -> Self {
251 self.font_variant = val;
252 self
253 }
254
255 /// **Unlike room visuals, only hex triplets with a leading `#` are valid.**
256 pub fn stroke_color(mut self, val: Option<impl Into<String>>) -> Self {
257 self.stroke = val.map(Into::into);
258 self
259 }
260
261 /// Sets the width of the stroke for the text, if the stroke is enabled.
262 ///
263 /// A stroke width of `0` results in no stroke. Negative values are invalid.
264 ///
265 /// The default value is `0.15`.
266 ///
267 /// # Examples
268 /// ```rust
269 /// use screeps::MapTextStyle;
270 ///
271 /// let thin = MapTextStyle::default().stroke_width(0.05_f32);
272 /// let wide = MapTextStyle::default().stroke_width(2.0_f32);
273 /// ```
274 pub fn stroke_width(mut self, val: f32) -> Self {
275 self.stroke_width = Some(val);
276 self
277 }
278
279 /// Sets or removes the background color for the text visual. When a
280 /// background is enabled, the text's vertical align is set to `middle`
281 /// instead of `baseline`.
282 ///
283 /// **Unlike room visuals, only hex triplets with a leading `#` are valid.**
284 ///
285 /// The default value is `None`.
286 ///
287 /// # Examples
288 /// ```rust
289 /// use screeps::MapTextStyle;
290 ///
291 /// let lavender = MapTextStyle::default().background_color(Some("#b373de"));
292 /// // Even though this is the default, it's possible to unset the background color.
293 /// let unset = MapTextStyle::default().background_color(None);
294 /// ```
295 // This only takes `&str` to avoid issues where passing `None` fails to infer a
296 // type.
297 pub fn background_color(mut self, val: Option<&str>) -> Self {
298 self.background_color = val.map(String::from);
299 self
300 }
301
302 /// Sets the horizontal alignment of the text.
303 ///
304 /// The default value is [`TextAlign::Center`].
305 ///
306 /// # Examples
307 /// ```rust
308 /// use screeps::{MapTextStyle, TextAlign};
309 ///
310 /// let left = MapTextStyle::default().align(TextAlign::Left);
311 /// ```
312 ///
313 /// [`TextAlign::Center`]: crate::objects::visual::TextAlign#variant.Center
314 pub fn align(mut self, val: TextAlign) -> Self {
315 self.align = val;
316 self
317 }
318
319 /// Sets the opacity of the text visual. Valid values are in the range
320 /// `0.0..=1.0`.
321 ///
322 /// The default value is `0.5`.
323 ///
324 /// # Examples
325 ///
326 /// ```rust
327 /// use screeps::MapTextStyle;
328 ///
329 /// let opaque = MapTextStyle::default().opacity(1.0_f32);
330 /// let ghost = MapTextStyle::default().opacity(0.05_f32);
331 /// ```
332 pub fn opacity(mut self, val: f32) -> Self {
333 self.opacity = Some(val);
334 self
335 }
336}
337
338#[derive(Clone, Serialize)]
339pub struct MapTextData {
340 text: String,
341 x: RoomCoordinate,
342 y: RoomCoordinate,
343 n: RoomName,
344 #[serde(rename = "s")]
345 style: MapTextStyle,
346}
347
348#[derive(Clone, Serialize)]
349#[serde(tag = "t")]
350pub enum MapVisualShape {
351 #[serde(rename = "c")]
352 Circle(MapCircleData),
353 #[serde(rename = "l")]
354 Line(MapLineData),
355 #[serde(rename = "r")]
356 Rect(MapRectData),
357 #[serde(rename = "p")]
358 Poly(MapPolyData),
359 #[serde(rename = "t")]
360 Text(MapTextData),
361}
362
363impl MapVisualShape {
364 pub fn circle(center: Position, style: CircleStyle) -> MapVisualShape {
365 MapVisualShape::Circle(MapCircleData {
366 x: center.x(),
367 y: center.y(),
368 n: center.room_name(),
369 style,
370 })
371 }
372
373 pub fn line(from: Position, to: Position, style: LineStyle) -> MapVisualShape {
374 MapVisualShape::Line(MapLineData {
375 x1: from.x(),
376 y1: from.y(),
377 n1: from.room_name(),
378 x2: to.x(),
379 y2: to.y(),
380 n2: to.room_name(),
381 style,
382 })
383 }
384
385 pub fn rect(top_left: Position, width: u32, height: u32, style: RectStyle) -> MapVisualShape {
386 MapVisualShape::Rect(MapRectData {
387 x: top_left.x(),
388 y: top_left.y(),
389 n: top_left.room_name(),
390 width,
391 height,
392 style,
393 })
394 }
395
396 pub fn poly(points: Vec<MapPolyPoint>, style: PolyStyle) -> MapVisualShape {
397 MapVisualShape::Poly(MapPolyData { points, style })
398 }
399
400 pub fn text(pos: Position, text: String, style: MapTextStyle) -> MapVisualShape {
401 MapVisualShape::Text(MapTextData {
402 x: pos.x(),
403 y: pos.y(),
404 n: pos.room_name(),
405 text,
406 style,
407 })
408 }
409}
410
411pub struct MapVisual {}
412
413impl MapVisual {
414 pub fn draw(visual: &MapVisualShape) {
415 let val = serde_wasm_bindgen::to_value(visual).expect("expect convert visual to value");
416
417 crate::console::add_visual(Some(&JsString::from("map")), &val);
418 }
419
420 pub fn draw_multi(visuals: &[MapVisualShape]) {
421 for visual in visuals {
422 let val = serde_wasm_bindgen::to_value(visual).expect("expect convert visual to value");
423
424 crate::console::add_visual(Some(&JsString::from("map")), &val);
425 }
426 }
427
428 pub fn circle(pos: Position, style: CircleStyle) {
429 Self::draw(&MapVisualShape::circle(pos, style));
430 }
431
432 pub fn line(from: Position, to: Position, style: LineStyle) {
433 Self::draw(&MapVisualShape::line(from, to, style));
434 }
435
436 pub fn rect(top_left: Position, width: u32, height: u32, style: RectStyle) {
437 Self::draw(&MapVisualShape::rect(top_left, width, height, style));
438 }
439
440 pub fn poly(points: Vec<Position>, style: PolyStyle) {
441 let points = points.iter().map(Into::into).collect();
442 Self::draw(&MapVisualShape::poly(points, style));
443 }
444
445 pub fn text(pos: Position, text: String, style: MapTextStyle) {
446 Self::draw(&MapVisualShape::text(pos, text, style));
447 }
448}