n18token/
lib.rs

1use cairo::Context;
2use n18hex::consts::*;
3use n18hex::{Colour, Hex};
4
5/// The collection of tokens associated with each company.
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
7pub struct Tokens {
8    names: Vec<String>,
9    tokens: Vec<Token>,
10    count: usize,
11}
12
13impl From<Vec<(String, Token)>> for Tokens {
14    fn from(src: Vec<(String, Token)>) -> Self {
15        let mut names = vec![];
16        let mut tokens = vec![];
17        let count = src.len();
18        for (name, token) in src.into_iter() {
19            names.push(name);
20            tokens.push(token);
21        }
22        Self {
23            names,
24            tokens,
25            count,
26        }
27    }
28}
29
30impl From<Tokens> for Vec<(String, Token)> {
31    fn from(src: Tokens) -> Self {
32        src.names.into_iter().zip(src.tokens.into_iter()).collect()
33    }
34}
35
36impl Tokens {
37    pub fn new(src: Vec<(String, Token)>) -> Self {
38        src.into()
39    }
40
41    pub fn tokens(&self) -> &[Token] {
42        &self.tokens
43    }
44
45    pub fn names(&self) -> &[String] {
46        &self.names
47    }
48
49    pub fn count(&self) -> usize {
50        self.count
51    }
52
53    pub fn first_token(&self) -> Token {
54        self.tokens[0]
55    }
56
57    pub fn last_token(&self) -> Token {
58        self.tokens[self.count - 1]
59    }
60
61    pub fn prev_token(&self, token: &Token) -> Option<Token> {
62        self.tokens
63            .iter()
64            .enumerate()
65            .find(|(_ix, tok)| tok == &token)
66            .map(|(ix, _tok)| if ix > 0 { ix - 1 } else { self.count - 1 })
67            .map(|ix| self.tokens[ix])
68    }
69
70    pub fn next_token(&self, token: &Token) -> Option<Token> {
71        self.tokens
72            .iter()
73            .enumerate()
74            .find(|(_ix, tok)| tok == &token)
75            .map(|(ix, _tok)| if ix < self.count - 1 { ix + 1 } else { 0 })
76            .map(|ix| self.tokens[ix])
77    }
78
79    pub fn token(&self, name: &str) -> Option<&Token> {
80        self.names
81            .iter()
82            .enumerate()
83            .find(|(_ix, n)| n == &name)
84            .map(|(ix, _n)| &self.tokens[ix])
85    }
86
87    pub fn name(&self, token: &Token) -> Option<&str> {
88        self.tokens
89            .iter()
90            .enumerate()
91            .find(|(_ix, t)| t == &token)
92            .map(|(ix, _t)| self.names[ix].as_str())
93    }
94}
95
96/// A token that may occupy a token space on a `Tile`.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
98pub struct Token {
99    pub style: TokenStyle,
100    pub x_pcnt: usize,
101    pub y_pcnt: usize,
102}
103
104/// Define the appearance of each token.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
106pub enum TokenStyle {
107    // TODO: add TopArcs, SideSquares, TopSquares, SideLines, TopLines, etc.
108    // See https://boardgamegeek.com/image/5033873/18611867 for ideas.
109    SideArcs {
110        bg: Colour,
111        fg: Colour,
112        text: Colour,
113    },
114    TopArcs {
115        bg: Colour,
116        fg: Colour,
117        text: Colour,
118    },
119    TopSquares {
120        bg: Colour,
121        fg: Colour,
122        text: Colour,
123    },
124    TopLines {
125        bg: Colour,
126        fg: Colour,
127        text: Colour,
128    },
129    TopTriangles {
130        bg: Colour,
131        fg: Colour,
132        text: Colour,
133    },
134    TripleTriangles {
135        bg: Colour,
136        fg: Colour,
137        text: Colour,
138    },
139    TribandV {
140        sides: Colour,
141        middle: Colour,
142        text: Colour,
143    },
144    TribandH {
145        sides: Colour,
146        middle: Colour,
147        text: Colour,
148    },
149    TricolourV {
150        left: Colour,
151        middle: Colour,
152        right: Colour,
153        text: Colour,
154    },
155    TricolourH {
156        top: Colour,
157        middle: Colour,
158        bottom: Colour,
159        text: Colour,
160    },
161}
162
163impl TokenStyle {
164    fn draw_background(&self, hex: &Hex, ctx: &Context) {
165        use TokenStyle::*;
166
167        let radius = hex.theme.token_space_radius.absolute(hex);
168        let rmax = 1.1 * radius;
169        let dx = 0.45 * radius;
170
171        match self {
172            SideArcs { fg, bg, .. } => {
173                bg.apply_colour(ctx);
174                ctx.fill_preserve().unwrap();
175
176                ctx.clip_preserve();
177                ctx.new_path();
178                ctx.arc(-1.5 * radius, 0.0, 1.0 * radius, 0.0, 2.0 * PI);
179                ctx.arc(1.5 * radius, 0.0, 1.0 * radius, 0.0, 2.0 * PI);
180
181                fg.apply_colour(ctx);
182                ctx.fill().unwrap();
183            }
184            TopArcs { fg, bg, .. } => {
185                bg.apply_colour(ctx);
186                ctx.fill_preserve().unwrap();
187
188                ctx.clip_preserve();
189                ctx.new_path();
190                ctx.arc(0.0, -1.5 * radius, 1.0 * radius, 0.0, 2.0 * PI);
191                ctx.arc(0.0, 1.5 * radius, 1.0 * radius, 0.0, 2.0 * PI);
192
193                fg.apply_colour(ctx);
194                ctx.fill().unwrap();
195            }
196            TopSquares { fg, bg, .. } => {
197                bg.apply_colour(ctx);
198                ctx.fill_preserve().unwrap();
199                ctx.clip_preserve();
200                ctx.new_path();
201                ctx.move_to(-0.4 * radius, -dx);
202                ctx.line_to(0.4 * radius, -dx);
203                ctx.line_to(0.4 * radius, -rmax);
204                ctx.line_to(-0.4 * radius, -rmax);
205                fg.apply_colour(ctx);
206                ctx.fill().unwrap();
207                ctx.new_path();
208                ctx.move_to(-0.4 * radius, dx);
209                ctx.line_to(0.4 * radius, dx);
210                ctx.line_to(0.4 * radius, rmax);
211                ctx.line_to(-0.4 * radius, rmax);
212                fg.apply_colour(ctx);
213                ctx.fill().unwrap();
214            }
215            TopLines { fg, bg, .. } => {
216                bg.apply_colour(ctx);
217                ctx.fill_preserve().unwrap();
218                ctx.clip_preserve();
219                ctx.new_path();
220                ctx.move_to(-0.5 * radius, -dx);
221                ctx.line_to(-0.3 * radius, -dx);
222                ctx.line_to(-0.3 * radius, -rmax);
223                ctx.line_to(-0.5 * radius, -rmax);
224                ctx.move_to(-0.1 * radius, -dx);
225                ctx.line_to(0.1 * radius, -dx);
226                ctx.line_to(0.1 * radius, -rmax);
227                ctx.line_to(-0.1 * radius, -rmax);
228                ctx.move_to(0.5 * radius, -dx);
229                ctx.line_to(0.3 * radius, -dx);
230                ctx.line_to(0.3 * radius, -rmax);
231                ctx.line_to(0.5 * radius, -rmax);
232                fg.apply_colour(ctx);
233                ctx.fill().unwrap();
234                ctx.new_path();
235                ctx.move_to(-0.5 * radius, dx);
236                ctx.line_to(-0.3 * radius, dx);
237                ctx.line_to(-0.3 * radius, rmax);
238                ctx.line_to(-0.5 * radius, rmax);
239                ctx.move_to(-0.1 * radius, dx);
240                ctx.line_to(0.1 * radius, dx);
241                ctx.line_to(0.1 * radius, rmax);
242                ctx.line_to(-0.1 * radius, rmax);
243                ctx.move_to(0.5 * radius, dx);
244                ctx.line_to(0.3 * radius, dx);
245                ctx.line_to(0.3 * radius, rmax);
246                ctx.line_to(0.5 * radius, rmax);
247                fg.apply_colour(ctx);
248                ctx.fill().unwrap();
249            }
250            TopTriangles { fg, bg, .. } => {
251                bg.apply_colour(ctx);
252                ctx.fill_preserve().unwrap();
253                ctx.clip_preserve();
254                ctx.new_path();
255                ctx.move_to(-dx, -rmax);
256                ctx.line_to(0.0, -dx);
257                ctx.line_to(dx, -rmax);
258                fg.apply_colour(ctx);
259                ctx.fill().unwrap();
260                ctx.new_path();
261                ctx.move_to(-dx, rmax);
262                ctx.line_to(0.0, dx);
263                ctx.line_to(dx, rmax);
264                fg.apply_colour(ctx);
265                ctx.fill().unwrap();
266            }
267            TripleTriangles { fg, bg, .. } => {
268                bg.apply_colour(ctx);
269                ctx.fill_preserve().unwrap();
270                ctx.clip_preserve();
271                ctx.new_path();
272                ctx.move_to(-1.5 * radius, -rmax);
273                ctx.line_to(-0.3 * radius, -dx);
274                ctx.line_to(-0.3 * radius, -rmax);
275                ctx.line_to(0.0, -0.5 * radius);
276                ctx.line_to(0.3 * radius, -rmax);
277                ctx.line_to(0.3 * radius, -dx);
278                ctx.line_to(1.5 * radius, -rmax);
279                fg.apply_colour(ctx);
280                ctx.fill().unwrap();
281                ctx.new_path();
282                ctx.move_to(-1.5 * radius, rmax);
283                ctx.line_to(-0.3 * radius, dx);
284                ctx.line_to(-0.3 * radius, rmax);
285                ctx.line_to(0.0, 0.5 * radius);
286                ctx.line_to(0.3 * radius, rmax);
287                ctx.line_to(0.3 * radius, dx);
288                ctx.line_to(1.5 * radius, rmax);
289                fg.apply_colour(ctx);
290                ctx.fill().unwrap();
291            }
292            TribandV { sides, middle, .. } => {
293                sides.apply_colour(ctx);
294                ctx.fill_preserve().unwrap();
295                ctx.clip_preserve();
296                ctx.new_path();
297                ctx.move_to(-dx, rmax);
298                ctx.line_to(-dx, -rmax);
299                ctx.line_to(dx, -rmax);
300                ctx.line_to(dx, rmax);
301                ctx.line_to(-dx, rmax);
302                middle.apply_colour(ctx);
303                ctx.fill().unwrap();
304            }
305            TribandH { sides, middle, .. } => {
306                sides.apply_colour(ctx);
307                ctx.fill_preserve().unwrap();
308                ctx.clip_preserve();
309                ctx.new_path();
310                ctx.move_to(rmax, -dx);
311                ctx.line_to(-rmax, -dx);
312                ctx.line_to(-rmax, dx);
313                ctx.line_to(rmax, dx);
314                ctx.line_to(rmax, -dx);
315                middle.apply_colour(ctx);
316                ctx.fill().unwrap();
317            }
318            TricolourV {
319                left,
320                middle,
321                right,
322                ..
323            } => {
324                // Fill the entire region with the middle colour.
325                middle.apply_colour(ctx);
326                ctx.fill_preserve().unwrap();
327                ctx.clip_preserve();
328                // Define the left region and fill with the left colour.
329                ctx.new_path();
330                ctx.move_to(-rmax, -rmax);
331                ctx.line_to(-rmax, rmax);
332                ctx.line_to(-dx, rmax);
333                ctx.line_to(-dx, -rmax);
334                left.apply_colour(ctx);
335                ctx.fill().unwrap();
336                // Define the right region and fill with the right colour.
337                ctx.new_path();
338                ctx.move_to(rmax, -rmax);
339                ctx.line_to(rmax, rmax);
340                ctx.line_to(dx, rmax);
341                ctx.line_to(dx, -rmax);
342                right.apply_colour(ctx);
343                ctx.fill().unwrap();
344            }
345            TricolourH {
346                top,
347                middle,
348                bottom,
349                ..
350            } => {
351                // Fill the entire region with the middle colour.
352                middle.apply_colour(ctx);
353                ctx.fill_preserve().unwrap();
354                ctx.clip_preserve();
355                // Define the top region and fill with the top colour.
356                ctx.new_path();
357                ctx.move_to(-rmax, -dx);
358                ctx.line_to(-rmax, -rmax);
359                ctx.line_to(rmax, -rmax);
360                ctx.line_to(rmax, -dx);
361                top.apply_colour(ctx);
362                ctx.fill().unwrap();
363                // Define the bottom region and fill with the bottom colour.
364                ctx.new_path();
365                ctx.move_to(-rmax, dx);
366                ctx.line_to(-rmax, rmax);
367                ctx.line_to(rmax, rmax);
368                ctx.line_to(rmax, dx);
369                bottom.apply_colour(ctx);
370                ctx.fill().unwrap();
371            }
372        }
373    }
374
375    pub fn text_colour(&self) -> &Colour {
376        use TokenStyle::*;
377
378        match self {
379            SideArcs { text, .. } => text,
380            TopArcs { text, .. } => text,
381            TopSquares { text, .. } => text,
382            TopLines { text, .. } => text,
383            TopTriangles { text, .. } => text,
384            TripleTriangles { text, .. } => text,
385            TribandV { text, .. } => text,
386            TribandH { text, .. } => text,
387            TricolourV { text, .. } => text,
388            TricolourH { text, .. } => text,
389        }
390    }
391}
392
393impl Token {
394    pub fn new(style: TokenStyle) -> Self {
395        Self {
396            style,
397            x_pcnt: 50,
398            y_pcnt: 50,
399        }
400    }
401
402    pub fn shift_text(mut self, x_pcnt: usize, y_pcnt: usize) -> Self {
403        self.x_pcnt = x_pcnt;
404        self.y_pcnt = y_pcnt;
405        self
406    }
407
408    fn draw_text(&self, hex: &Hex, ctx: &Context, text: &str) {
409        // Draw the token text using the appropriate theme settings.
410        let mut labeller = hex.theme.token_label.labeller(ctx, hex);
411
412        // Ensure the text is centred and has the desired colour.
413        labeller.halign(n18hex::theme::AlignH::Centre);
414        labeller.valign(n18hex::theme::AlignV::Middle);
415        labeller.colour(*self.style.text_colour());
416
417        // Identify the location of the text centre, noting that the current
418        // point is the token centre.
419        let (x, y) = ctx.current_point().unwrap();
420        let radius = hex.theme.token_space_radius.absolute(hex);
421        let dx = radius * ((self.x_pcnt as f64 - 50.0) / 50.0);
422        let dy = radius * ((self.y_pcnt as f64 - 50.0) / 50.0);
423        let text_centre = n18hex::Coord::from((x + dx, y + dy));
424
425        labeller.draw(text, text_centre);
426    }
427
428    /// Draws the token so that it fills the current path.
429    ///
430    /// Define the token boundary before calling this function.
431    pub fn draw(&self, hex: &Hex, ctx: &Context, text: &str, rotn: f64) {
432        // Locate the centre of the token.
433        let (x0, y0, x1, y1) = ctx.fill_extents().unwrap();
434        let x = 0.5 * (x0 + x1);
435        let y = 0.5 * (y0 + y1);
436
437        let m = ctx.matrix();
438        ctx.save().unwrap();
439
440        // NOTE: move to the token centre and apply the inverse rotation.
441        ctx.translate(x, y);
442        ctx.rotate(-rotn);
443
444        let stroke_path = ctx.copy_path().unwrap();
445        self.style.draw_background(hex, ctx);
446        self.draw_text(hex, ctx, text);
447
448        // Redraw the outer black circle.
449        ctx.new_path();
450        ctx.append_path(&stroke_path);
451        hex.theme.token_space_inner.apply_line_and_stroke(ctx, hex);
452        ctx.stroke_preserve().unwrap();
453
454        ctx.restore().unwrap();
455        ctx.set_matrix(m);
456    }
457}