1#![deny(missing_docs)]
2
3pub mod math;
9mod text;
10pub 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#[derive(Debug, Clone, PartialEq, PartialOrd)]
33pub enum Contents<S>
34where
35 S: Scalar,
36{
37 Text(String, TextFormat<S>),
39}
40
41impl<S> Contents<S>
42where
43 S: Scalar,
44{
45 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub enum Orientation {
59 Horizontal,
61 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#[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 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 pub fn children(&self) -> impl DoubleEndedIterator<Item = &Pane<R>> {
169 self.children.iter().map(|(_, pane)| pane)
170 }
171 pub fn children_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Pane<R>> {
173 self.children.iter_mut().map(|(_, pane)| pane)
174 }
175 pub fn contents(&self) -> Option<&Contents<R::Scalar>> {
177 self.contents.as_ref()
178 }
179 pub fn with_contents(mut self, contents: Contents<R::Scalar>) -> Self {
181 self.contents = Some(contents);
182 self
183 }
184 pub fn with_no_contents(mut self) -> Self {
186 self.contents = None;
187 self
188 }
189 pub fn rect(&self) -> R {
191 self.rect
192 }
193 pub fn with_rect(mut self, rect: R) -> Self {
195 self.rect = rect;
196 self
197 }
198 pub fn size(&self) -> R::Vector {
200 self.rect.size()
201 }
202 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 pub fn top_left(&self) -> R::Vector {
215 self.rect.top_left()
216 }
217 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 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 pub fn orientation(&self) -> Orientation {
255 self.orientation
256 }
257 pub fn with_orientation(mut self, orientation: Orientation) -> Self {
259 self.orientation = orientation;
260 self.update_rects();
261 self
262 }
263 pub fn color(&self) -> Color {
265 self.color
266 }
267 pub fn with_color(mut self, color: Color) -> Self {
269 self.color = color;
270 self
271 }
272 pub fn margin(&self) -> R::Scalar {
274 self.margin
275 }
276 pub fn with_margin(mut self, margin: R::Scalar) -> Self {
278 self.margin = margin;
279 self.update_rects();
280 self
281 }
282 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 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 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 #[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
429pub trait NamedWeightedPane<'a, R>
431where
432 R: Rectangle,
433{
434 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
483pub mod color {
485 pub type Color = [f32; 4];
487
488 pub const RED: Color = [1.0, 0.0, 0.0, 1.0];
490 pub const ORANGE: Color = [1.0, 0.5, 0.0, 1.0];
492 pub const YELLOW: Color = [1.0, 1.0, 0.0, 1.0];
494 pub const GREEN: Color = [0.0, 1.0, 0.0, 1.0];
496 pub const CYAN: Color = [0.0, 1.0, 1.0, 1.0];
498 pub const BLUE: Color = [0.0, 0.0, 1.0, 1.0];
500 pub const PURPLE: Color = [0.5, 0.0, 0.5, 1.0];
502 pub const MAGENTA: Color = [1.0, 0.0, 1.0, 1.0];
504 pub const BLACK: Color = [0.0, 0.0, 0.0, 1.0];
506 pub const GRAY: Color = [0.5, 0.5, 0.5, 1.0];
508 pub const GREY: Color = [0.5, 0.5, 0.5, 1.0];
510 pub const WHITE: Color = [1.0; 4];
512 pub const TRANSPARENT: Color = [0.0; 4];
514}
515
516pub use self::color::Color;