1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
mod button;
mod checkbox;
mod radio;
mod slider;
mod text;

use crate::graphics::{Batch, Color, Font, Frame, Image, Mesh, Shape};
use crate::load::{Join, Task};
use crate::ui::core;

use std::cell::RefCell;
use std::rc::Rc;

/// A renderer capable of drawing all the [built-in widgets].
///
/// It can be configured using [`Configuration`] and
/// [`UserInterface::configuration`].
///
/// [built-in widgets]: widget/index.html
/// [`Configuration`]: struct.Configuration.html
/// [`UserInterface::configuration`]: trait.UserInterface.html#method.configuration
pub struct Renderer {
    pub(crate) sprites: Batch,
    pub(crate) font: Rc<RefCell<Font>>,
    explain_mesh: Mesh,
}

impl std::fmt::Debug for Renderer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Renderer")
            .field("sprites", &self.sprites)
            .finish()
    }
}

impl core::Renderer for Renderer {
    type Configuration = Configuration;

    fn load(config: Configuration) -> Task<Renderer> {
        (config.sprites, config.font)
            .join()
            .map(|(sprites, font)| Renderer {
                sprites: Batch::new(sprites),
                font: Rc::new(RefCell::new(font)),
                explain_mesh: Mesh::new(),
            })
    }

    fn explain(&mut self, layout: &core::Layout<'_>, color: Color) {
        self.explain_mesh
            .stroke(Shape::Rectangle(layout.bounds()), color, 1);

        layout
            .children()
            .for_each(|layout| self.explain(&layout, color));
    }

    fn flush(&mut self, frame: &mut Frame<'_>) {
        let target = &mut frame.as_target();

        self.sprites.draw(target);
        self.sprites.clear();

        self.font.borrow_mut().draw(target);

        if !self.explain_mesh.is_empty() {
            self.explain_mesh.draw(target);
            self.explain_mesh = Mesh::new();
        }
    }
}

/// The [`Renderer`] configuration.
///
/// You can implement [`UserInterface::configuration`] and return your own
/// [`Configuration`] to customize the built-in [`Renderer`].
///
/// [`Renderer`]: struct.Renderer.html
/// [`UserInterface::configuration`]: trait.UserInterface.html#method.configuration
/// [`Configuration`]: struct.Configuration.html
///
/// # Example
/// ```no_run
/// use coffee::graphics::Image;
/// use coffee::ui::Configuration;
///
/// Configuration {
///     sprites: Image::load("resources/my_ui_sprites.png"),
///     ..Configuration::default()
/// };
/// ```
#[derive(Debug)]
pub struct Configuration {
    /// The spritesheet used to render the [different widgets] of the user interface.
    ///
    /// The spritesheet needs to be structured like [the default spritesheet].
    ///
    /// [different widgets]: widget/index.html
    /// [the default spritesheet]: https://raw.githubusercontent.com/hecrj/coffee/92aa6b64673116fdc49d8694a10ee5bf53afb1b5/resources/ui.png
    pub sprites: Task<Image>,

    /// The font used to render [`Text`].
    ///
    /// By default, it uses [Inconsolata Regular].
    ///
    /// [`Text`]: widget/text/struct.Text.html
    /// [Inconsolata Regular]: https://fonts.google.com/specimen/Inconsolata
    pub font: Task<Font>,
}

impl Default for Configuration {
    fn default() -> Self {
        Self {
            sprites: Task::using_gpu(|gpu| {
                Image::from_image(
                    gpu,
                    image::load_from_memory(include_bytes!(
                        "../../resources/ui.png"
                    ))?,
                )
            }),
            font: Font::load_from_bytes(include_bytes!(
                "../../resources/font/Inconsolata-Regular.ttf"
            )),
        }
    }
}