mooeye 0.4.1

A small UI library designed on top of the ggez game library. WORK IN PROGRESS
Documentation
use ggez::{graphics::Color, *};
use mooeye::{scene_manager, ui, ui::UiContainer, ui::UiContent};

// # Containers
// In this example, we learn about the 4 main types of containers provided with mooeye and use them to create a UI containing multiple elements.

/// A very basic scene struct, once again only holding the root element of our GUI.
pub struct DScene {
    /// The root element of DScene's GUI.
    gui: ui::UiElement<()>,
}

impl DScene {
    /// Creates a new DScene.
    /// As you can see, for scenes that do not change based on parameters, we would like to use something like ```DScene::default()``` to
    /// communicate that this is the standard way for a ```DScene``` to come into existence.
    /// However, we cannot derive Default as the passing of a parameter ```ctx: &Context``` is almost always neccessary,
    /// so we have to use ```new(ctx: &Context)``` instead.
    pub fn new(ctx: &Context) -> Result<Self, GameError> {
        // Predefine some visuals so we don't have to do it for every element.

        let vis = ui::Visuals::new(
            Color::from_rgb(180, 120, 60),
            Color::from_rgb(18, 12, 6),
            1.,
            0.,
        );

        // You can also create 'custom' visuals that allow you to set the thickness of each border & radius of each corner separately.

        let vis2 = ui::Visuals::new_custom(
            Color::from_rgb(180, 120, 60),
            Color::from_rgb(18, 12, 6),
            [4., 1., 4., 1.],
            [2., 2., 2., 2.],
        );

        let hover_vis = ui::Visuals::new(
            Color::from_rgb(160, 100, 40),
            Color::from_rgb(18, 12, 6),
            3.,
            0.,
        );

        let cont_vis = ui::Visuals::new_custom(
            Color::from_rgb(60, 120, 180),
            Color::from_rgb(180, 180, 190),
            [16., 8., 8., 8.],
            [12., 2., 2., 12.],
        );

        // Note that the constructor now returns a Result.
        // This is neccessary as the 'add' function used to add UI elements to grid containers can fail, thus failing the constructor.

        // The first container we use is a vertical box simply laying out elements from top to bottom.
        let mut ver_box = ui::containers::VerticalBox::new();
        // We can manually change the spacing between elements in the box
        ver_box.spacing = 10.;
        // first, we need to add all the children to this vertical box.
        // We'll just use TextElement for now, but these can also be images, sprites, placeholders or more containers.
        for i in 0..8 {
            // Create an element.
            let element = graphics::Text::new(format!("{}", i))
                .set_font("Bahnschrift")
                .set_scale(28.)
                .to_owned()
                .to_element_builder(0, ctx)
                .with_visuals(vis2)
                .build();
            // Add the element to the box. This cannot fail for non-grid containers, if ver_box were not an actual container
            // or a container that requires a special method for adding, like e.g. GridBox, it would simply consume the element and do nothing.
            ver_box.add(element);
        }
        // After adding all children, we can convert to a UiElement and style the box like we would style an other element. The usual pattern here is to shadow the variable to avoid use-after-move.
        let ver_box = ver_box
            .to_element_builder(0, ctx)
            .with_visuals(cont_vis)
            // Using larger padding to accomodate our thick borders.
            .with_padding((24., 16., 16., 16.))
            .build();

        // Another container we can use is GridBox. A GridBox needs to be initialized with a set height and width and cannot be extended.
        let mut grid = ui::containers::GridBox::new(4, 4);
        // The contents of a grid box are initialized as empty elements.  We'll add buttons to the diagonal of the grid.
        for i in 0..4 {
            // Create an element.
            let element = graphics::Text::new(format!("{}", i))
                .set_font("Bahnschrift")
                .set_scale(28.)
                .to_owned()
                .to_element_builder(0, ctx)
                .with_visuals(vis)
                // Elements can be given alignment and will align within their respecitive cell in the grid.
                .with_alignment(ui::Alignment::Max, None)
                .build();
            // Add the element to the box. This can fail, if ver_box were not an actual container
            // or a container that requires a special method for adding, like e.g. GridBox.
            grid.add(element, i, i)?;
        }

        // We'll also create our usual back button and put it into the top right of the grid.

        let back = graphics::Text::new("Back!")
            .set_font("Bahnschrift")
            .set_scale(28.)
            .to_owned()
            .to_element_builder(1, ctx)
            .with_visuals(vis)
            .with_hover_visuals(hover_vis)
            .build();

        // This time, we'll enhance our back button a bit by using an icon that is displayed over the top left corner.
        // To achieve this, we'll use a StackBox.
        let mut stack = ui::containers::StackBox::new();
        stack.add(back);
        // The add_top function adds something to the top of a stack box. Creating and adding an element can be done inline.
        stack.add_top(
            graphics::Image::from_path(ctx, "/moo.png")?
                .to_element_builder(0, ctx)
                // We'll align the icon to the top right
                .with_alignment(ui::Alignment::Min, ui::Alignment::Min)
                // and offset it slightly
                .with_offset(-10., -10.)
                .build(),
        )?;
        // to_element is a shorthand for to_element_builder().build() if we want to simply take the default builder and not change anything.
        let stack = stack.to_element(0, ctx);

        // Now, we add the stack to the grid.
        grid.add(stack, 3, 0)?;

        // And finally build the grid.
        let grid = grid
            .to_element_builder(0, ctx)
            .with_visuals(cont_vis)
            .with_padding((24., 16., 16., 16.))
            .build();

        // The horizontal box is exactly the same as the vertical box except for orientation.
        // We will use a horizontal box to contain the boxes created so far.
        // if you don't want to create multiple variables, adding of children can be done inline for non-grid
        // containers by using .with_child.
        Ok(Self {
            gui: ui::containers::HorizontalBox::new()
                .to_element_builder(0, ctx)
                .with_child(ver_box)
                .with_child(grid)
                .build(),
        })
    }
}

impl scene_manager::Scene for DScene {
    fn update(&mut self, ctx: &mut Context) -> Result<scene_manager::SceneSwitch, GameError> {
        // Nothing much to do here, except implement the back button functionality.

        let messages = self.gui.manage_messages(ctx, None);

        if messages.contains(&ui::UiMessage::Triggered(1)) {
            // If it is, we end the current scene (and return to the previous one) by popping it off the stack.
            return Ok(scene_manager::SceneSwitch::pop(1));
        }

        Ok(scene_manager::SceneSwitch::None)
    }

    fn draw(&mut self, ctx: &mut Context, mouse_listen: bool) -> Result<(), GameError> {
        // Once again, we first create a canvas and set a pixel sampler. Note that this time, we dont clear the background.

        let mut canvas = ggez::graphics::Canvas::from_frame(ctx, None);
        // Since we don't set the sampler to 'nearest', our corners will look more round, but the pixel-cow will look blurry.
        //canvas.set_sampler(ggez::graphics::Sampler::nearest_clamp());

        self.gui.draw_to_screen(ctx, &mut canvas, mouse_listen);

        canvas.finish(ctx)?;

        Ok(())
    }
}