pane/
lib.rs

1#![deny(missing_docs)]
2
3//! This crate provides a data structure for text alignment. Rectangular `Pane`s (which may have smaller, child `Pane`s)
4//! can be defined, and the positions of characters of text within them can be calculated.
5//!
6//! The `graphics` feature, which is on by default, allow the direct rendering of a `Pane` with the `piston2d-graphics` crate.
7
8pub mod math;
9mod text;
10/// A prelud containing commonly used items in `Pane`
11pub mod prelude {
12    pub use crate::color;
13    pub use crate::math::{Rectangle, Scalar, Vector2};
14    #[cfg(feature = "graphics")]
15    pub use crate::text::justified_text;
16    pub use crate::text::{Justification, TextFormat};
17    pub use crate::Contents;
18    pub use crate::Orientation;
19    pub use crate::Pane;
20}
21
22use std::{collections::HashMap, ops};
23
24#[cfg(feature = "graphics")]
25use graphics::{character::CharacterCache, math::Matrix2d, rectangle, Graphics, ImageSize};
26
27use crate::math::{Rectangle, Scalar, Vector2, ZeroOneTwo};
28
29pub use crate::text::*;
30
31/// Possible content of a `Pane`
32#[derive(Debug, Clone, PartialEq, PartialOrd)]
33pub enum Contents<S>
34where
35    S: Scalar,
36{
37    /// Text with some format
38    Text(String, TextFormat<S>),
39}
40
41impl<S> Contents<S>
42where
43    S: Scalar,
44{
45    /// Create a new `Context::Text`
46    #[allow(clippy::needless_pass_by_value)]
47    pub fn text<T, F>(text: T, format: F) -> Self
48    where
49        T: ToString,
50        F: Into<TextFormat<S>>,
51    {
52        Contents::Text(text.to_string(), format.into())
53    }
54}
55
56/// An orientation for splitting a `Pane`
57#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub enum Orientation {
59    /// Split the pane horizantally
60    Horizontal,
61    /// Split the pane vertically
62    Vertical,
63}
64
65impl Default for Orientation {
66    fn default() -> Self {
67        Orientation::Vertical
68    }
69}
70
71impl Orientation {
72    fn split_rect<R, W>(self, margin: R::Scalar, rect: R, weights: W) -> Vec<R>
73    where
74        R: Rectangle,
75        W: IntoIterator<Item = R::Scalar>,
76    {
77        let weights: Vec<R::Scalar> = weights.into_iter().collect();
78        let sum: R::Scalar = weights
79            .iter()
80            .cloned()
81            .fold(R::Scalar::ZERO, std::ops::Add::add);
82        let margin_fraction: R::Scalar = margin / (weights.len() as u32).into();
83        match self {
84            Orientation::Horizontal => {
85                let mut offset = rect.top_left().x();
86                weights
87                    .into_iter()
88                    .map(|w| {
89                        let top_left = R::Vector::new(offset, rect.top_left().y());
90                        let size =
91                            R::Vector::new(rect.width() * w / sum - margin_fraction, rect.height());
92                        offset = offset + size.x() + margin;
93                        R::new(top_left, size)
94                    })
95                    .collect()
96            }
97            Orientation::Vertical => {
98                let mut offset = rect.top_left().y();
99                weights
100                    .into_iter()
101                    .map(|w| {
102                        let top_left = R::Vector::new(rect.top_left().x(), offset);
103                        let size =
104                            R::Vector::new(rect.width(), rect.height() * w / sum - margin_fraction);
105                        offset = offset + size.y() + margin;
106                        R::new(top_left, size)
107                    })
108                    .collect()
109            }
110        }
111    }
112}
113
114/// A rectangle which automatically determines the positions and sizes
115/// of things withing it
116///
117/// A `Pane` can have any number of child `Panes`, each of which has a size
118/// constrained by their parent `Pane`. The size and position of each child
119/// pane depends on its weight relative to its siblings as well as the split
120/// `Orientation` of its parent. This allows panes to be resized while keeping
121/// all their child panes consistently sized.
122///
123/// A pane can also have optional contents. Contents will be resized to fit
124/// the `Pane`
125#[derive(Debug, Clone)]
126pub struct Pane<R = [f64; 4]>
127where
128    R: Rectangle,
129{
130    contents: Option<Contents<R::Scalar>>,
131    orientation: Orientation,
132    margin: R::Scalar,
133    names: HashMap<String, usize>,
134    rect: R,
135    children: Vec<(R::Scalar, Pane<R>)>,
136    color: Color,
137}
138
139impl<R> Default for Pane<R>
140where
141    R: Rectangle,
142{
143    fn default() -> Self {
144        Pane::new()
145    }
146}
147
148impl<R> Pane<R>
149where
150    R: Rectangle,
151{
152    /// Create a new `Pane`
153    pub fn new() -> Self {
154        Pane {
155            contents: None,
156            orientation: Orientation::default(),
157            margin: R::Scalar::ZERO,
158            names: HashMap::new(),
159            children: Vec::new(),
160            rect: R::new(
161                R::Vector::new(R::Scalar::ZERO, R::Scalar::ZERO),
162                R::Vector::new(R::Scalar::ONE, R::Scalar::ONE),
163            ),
164            color: color::TRANSPARENT,
165        }
166    }
167    /// Immutable iterate over the `Pane`'s children
168    pub fn children(&self) -> impl DoubleEndedIterator<Item = &Pane<R>> {
169        self.children.iter().map(|(_, pane)| pane)
170    }
171    /// Mutable iterate over the `Pane`'s children
172    pub fn children_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Pane<R>> {
173        self.children.iter_mut().map(|(_, pane)| pane)
174    }
175    /// Get the `Pane`'s contents
176    pub fn contents(&self) -> Option<&Contents<R::Scalar>> {
177        self.contents.as_ref()
178    }
179    /// Change the `Pane`'s contents
180    pub fn with_contents(mut self, contents: Contents<R::Scalar>) -> Self {
181        self.contents = Some(contents);
182        self
183    }
184    /// Remove the `Pane`'s contents
185    pub fn with_no_contents(mut self) -> Self {
186        self.contents = None;
187        self
188    }
189    /// Get the `Pane`'s rectangle
190    pub fn rect(&self) -> R {
191        self.rect
192    }
193    /// Set the `Pane`'s rectangle
194    pub fn with_rect(mut self, rect: R) -> Self {
195        self.rect = rect;
196        self
197    }
198    /// Get the `Pane`'s size
199    pub fn size(&self) -> R::Vector {
200        self.rect.size()
201    }
202    /// Set the `Pane`'s size
203    pub fn with_size<T, V>(mut self, size: V) -> Self
204    where
205        T: Scalar,
206        R::Scalar: From<T>,
207        V: Vector2<Scalar = T>,
208    {
209        self.rect = self.rect.with_size(size.map());
210        self.update_rects();
211        self
212    }
213    /// Get the position of the `Pane`'s top-left corner
214    pub fn top_left(&self) -> R::Vector {
215        self.rect.top_left()
216    }
217    /// Set the position of the `Pane`'s top-left corner
218    pub fn with_top_left<T, V>(mut self, top_left: V) -> Self
219    where
220        T: Scalar,
221        R::Scalar: From<T>,
222        V: Vector2<Scalar = T>,
223    {
224        self.rect = self.rect.with_top_left(top_left.map());
225        self.update_rects();
226        self
227    }
228    /// Set the `Pane`'s inner `Pane`s. Each inner `Pane` has a
229    /// weight which defines how it is resized relative to its
230    /// siblings. `Pane`s can also have optional names that can be
231    /// used to index their parent.
232    pub fn with_panes<'a, P, I>(mut self, panes: I) -> Self
233    where
234        P: NamedWeightedPane<'a, R>,
235        I: IntoIterator<Item = P>,
236    {
237        let mut new_names = HashMap::new();
238        self.children = panes
239            .into_iter()
240            .map(NamedWeightedPane::named_weighted_pane)
241            .enumerate()
242            .map(|(i, (name, weight, pane))| {
243                if let Some(name) = name {
244                    new_names.insert(name.to_string(), i);
245                }
246                (weight, pane)
247            })
248            .collect();
249        self.names = new_names;
250        self.update_rects();
251        self
252    }
253    /// Get the split orientation of the `Pane`'s children
254    pub fn orientation(&self) -> Orientation {
255        self.orientation
256    }
257    /// Set the split orientation of the `Pane`'s children
258    pub fn with_orientation(mut self, orientation: Orientation) -> Self {
259        self.orientation = orientation;
260        self.update_rects();
261        self
262    }
263    /// Get the `Pane`'s color
264    pub fn color(&self) -> Color {
265        self.color
266    }
267    /// Set the `Pane`'s color
268    pub fn with_color(mut self, color: Color) -> Self {
269        self.color = color;
270        self
271    }
272    /// Get the `Pane`'s margin
273    pub fn margin(&self) -> R::Scalar {
274        self.margin
275    }
276    /// Set the `Pane`'s margin
277    pub fn with_margin(mut self, margin: R::Scalar) -> Self {
278        self.margin = margin;
279        self.update_rects();
280        self
281    }
282    /// Get the inner rectangle created by the `Pane` and its margin
283    pub fn margin_rect(&self) -> R {
284        R::new(
285            self.rect
286                .top_left()
287                .add(R::Vector::new(self.margin, self.margin)),
288            self.rect
289                .size()
290                .sub(R::Vector::new(self.margin, self.margin).mul(R::Scalar::TWO)),
291        )
292    }
293    /// Update the size of all inner `Pane`s' rectangles
294    fn update_rects(&mut self) {
295        let margin_rect = self.margin_rect();
296        let new_rects = self.orientation.split_rect(
297            self.margin,
298            margin_rect,
299            self.children.iter().map(|(w, _)| *w),
300        );
301        for (pane, rect) in self.children.iter_mut().zip(new_rects) {
302            pane.1.rect = rect;
303            pane.1.update_rects();
304        }
305    }
306    /// Recursively fit the text of any `Contents::Text` in the `Pane`'s tree
307    pub fn fit_text<C>(mut self, glyphs: &mut C) -> Self
308    where
309        C: CharacterWidthCache<Scalar = R::Scalar>,
310    {
311        self.update_rects();
312        let margin_rect = self.margin_rect();
313        if let Some(Contents::Text(ref text, ref mut format)) = self.contents {
314            *format = format.resize_font(glyphs.fit_max_font_size(text, margin_rect, *format));
315        }
316        self.children = self
317            .children
318            .into_iter()
319            .map(|(w, pane)| (w, pane.fit_text(glyphs)))
320            .collect();
321        self
322    }
323}
324
325impl<R> Pane<R>
326where
327    R: Rectangle<Scalar = f64>,
328{
329    /// Draw the `Pane` and all its contents to something using
330    /// the `piston2d-graphics` crate
331    #[cfg(feature = "graphics")]
332    pub fn draw<T, C, G>(
333        &self,
334        glyphs: &mut C,
335        transform: Matrix2d,
336        graphics: &mut G,
337    ) -> Result<(), C::Error>
338    where
339        T: ImageSize,
340        C: CharacterCache<Texture = T>,
341        G: Graphics<Texture = T>,
342    {
343        rectangle(
344            self.color,
345            self.rect().map::<[f64; 4]>(),
346            transform,
347            graphics,
348        );
349        if let Some(ref contents) = self.contents {
350            match contents {
351                Contents::Text(text, format) => justified_text(
352                    text,
353                    self.margin_rect().map::<[f64; 4]>(),
354                    *format,
355                    glyphs,
356                    transform,
357                    graphics,
358                )?,
359            }
360        }
361        for (_, pane) in &self.children {
362            pane.draw(glyphs, transform, graphics)?;
363        }
364        Ok(())
365    }
366}
367
368impl<R> ops::Index<usize> for Pane<R>
369where
370    R: Rectangle,
371{
372    type Output = Pane<R>;
373    fn index(&self, index: usize) -> &Self::Output {
374        &self.children[index].1
375    }
376}
377
378impl<'a, R> ops::Index<&'a str> for Pane<R>
379where
380    R: Rectangle,
381{
382    type Output = Pane<R>;
383    fn index(&self, index: &'a str) -> &Self::Output {
384        let index = self.names[index];
385        &self[index]
386    }
387}
388
389trait Map<I> {
390    type Accessed;
391    fn map<F>(self, index: I, f: F) -> Self
392    where
393        F: Fn(Self::Accessed) -> Self::Accessed;
394}
395
396impl<R> Map<usize> for Pane<R>
397where
398    R: Rectangle,
399{
400    type Accessed = Pane<R>;
401    fn map<F>(mut self, index: usize, f: F) -> Self
402    where
403        F: Fn(Self::Accessed) -> Self::Accessed,
404    {
405        self.children = self
406            .children
407            .into_iter()
408            .enumerate()
409            .map(|(i, (w, pane))| (w, if i == index { f(pane) } else { pane }))
410            .collect();
411        self
412    }
413}
414
415impl<'a, R> Map<&'a str> for Pane<R>
416where
417    R: Rectangle,
418{
419    type Accessed = Pane<R>;
420    fn map<F>(self, index: &'a str, f: F) -> Self
421    where
422        F: Fn(Self::Accessed) -> Self::Accessed,
423    {
424        let index = self.names[index];
425        self.map(index, f)
426    }
427}
428
429/// Defines conversion into a child `Pane` with a weight and optional name
430pub trait NamedWeightedPane<'a, R>
431where
432    R: Rectangle,
433{
434    /// Converts into a child `Pane` with a weight and optional name
435    fn named_weighted_pane(self) -> (Option<&'a str>, R::Scalar, Pane<R>);
436}
437
438impl<'a, R> NamedWeightedPane<'a, R> for Pane<R>
439where
440    R: Rectangle,
441{
442    fn named_weighted_pane(self) -> (Option<&'a str>, R::Scalar, Pane<R>) {
443        (None, R::Scalar::ONE, self)
444    }
445}
446
447impl<'a, R> NamedWeightedPane<'a, R> for (R::Scalar, Pane<R>)
448where
449    R: Rectangle,
450{
451    fn named_weighted_pane(self) -> (Option<&'a str>, R::Scalar, Pane<R>) {
452        (None, self.0, self.1)
453    }
454}
455
456impl<'a, R> NamedWeightedPane<'a, R> for (Option<&'a str>, R::Scalar, Pane<R>)
457where
458    R: Rectangle,
459{
460    fn named_weighted_pane(self) -> (Option<&'a str>, R::Scalar, Pane<R>) {
461        self
462    }
463}
464
465impl<'a, R> NamedWeightedPane<'a, R> for (&'a str, R::Scalar, Pane<R>)
466where
467    R: Rectangle,
468{
469    fn named_weighted_pane(self) -> (Option<&'a str>, R::Scalar, Pane<R>) {
470        (Some(self.0), self.1, self.2)
471    }
472}
473
474impl<'a, R> NamedWeightedPane<'a, R> for &'a str
475where
476    R: Rectangle,
477{
478    fn named_weighted_pane(self) -> (Option<&'a str>, R::Scalar, Pane<R>) {
479        (Some(self), R::Scalar::ONE, Pane::new())
480    }
481}
482
483/// Defines serveral color constants
484pub mod color {
485    /// A 4-channel color
486    pub type Color = [f32; 4];
487
488    /// Red
489    pub const RED: Color = [1.0, 0.0, 0.0, 1.0];
490    /// Orange
491    pub const ORANGE: Color = [1.0, 0.5, 0.0, 1.0];
492    /// Yellow
493    pub const YELLOW: Color = [1.0, 1.0, 0.0, 1.0];
494    /// Green
495    pub const GREEN: Color = [0.0, 1.0, 0.0, 1.0];
496    /// Cyan
497    pub const CYAN: Color = [0.0, 1.0, 1.0, 1.0];
498    /// Blue
499    pub const BLUE: Color = [0.0, 0.0, 1.0, 1.0];
500    /// Purple
501    pub const PURPLE: Color = [0.5, 0.0, 0.5, 1.0];
502    /// Magenta
503    pub const MAGENTA: Color = [1.0, 0.0, 1.0, 1.0];
504    /// Black
505    pub const BLACK: Color = [0.0, 0.0, 0.0, 1.0];
506    /// Gray (same as `GREY`)
507    pub const GRAY: Color = [0.5, 0.5, 0.5, 1.0];
508    /// Grey (same as `GRAY`)
509    pub const GREY: Color = [0.5, 0.5, 0.5, 1.0];
510    /// White
511    pub const WHITE: Color = [1.0; 4];
512    /// Transparent
513    pub const TRANSPARENT: Color = [0.0; 4];
514}
515
516pub use self::color::Color;