good-web-game 0.6.1

An alternative implementation of the ggez game engine, based on miniquad
Documentation
//! An example drawing semi-transparent venn diagrams
//! using different blend modes.
//!
//! It also shows why you'd usually want to draw canvases
//! using the `Premultiplied` blend mode
//! (for more explanations on this see https://github.com/ggez/ggez/issues/694#issuecomment-853724926)

extern crate good_web_game as ggez;

use ggez::event::EventHandler;
use ggez::graphics::{self, BlendMode, Color, DrawParam, Drawable};
use ggez::miniquad;
use ggez::{Context, GameResult};
use glam::Vec2;
use std::env;
use std::path;

struct MainState {
    circle: graphics::Mesh,
    canvas: graphics::Canvas,
    font: graphics::Font,
}

impl MainState {
    fn new(ctx: &mut Context, quad_ctx: &mut miniquad::GraphicsContext) -> GameResult<MainState> {
        let circle = graphics::Mesh::new_circle(
            ctx,
            quad_ctx,
            graphics::DrawMode::fill(),
            Vec2::new(0.0, 0.0),
            40.0,
            0.5,
            Color::WHITE,
        )?;

        let mut canvas = graphics::Canvas::with_window_size(ctx, quad_ctx)?;
        canvas.set_blend_mode(Some(BlendMode::Alpha));

        let font = graphics::Font::new(ctx, "/LiberationMono-Regular.ttf")?;

        let s = Self {
            circle,
            canvas,
            font,
        };
        Ok(s)
    }

    fn draw_venn(
        &self,
        ctx: &mut Context,
        quad_ctx: &mut miniquad::GraphicsContext,
        pos: Vec2,
        name: &str,
    ) -> GameResult<()> {
        const TRI_COLORS: [Color; 3] = [
            Color::new(0.8, 0., 0., 0.5),
            Color::new(0., 0.8, 0., 0.5),
            Color::new(0., 0., 0.8, 0.5),
        ];

        const OFFSET: f32 = 20.;
        const REL_POSITIONS: [[f32; 2]; 3] = [
            [-OFFSET, -OFFSET / 2.],
            [OFFSET, -OFFSET / 2.],
            [0., OFFSET],
        ];

        // draw the diagram
        for i in 0..3 {
            self.circle.draw(
                ctx,
                quad_ctx,
                DrawParam::default()
                    .dest(pos + Vec2::from(REL_POSITIONS[i]))
                    .color(TRI_COLORS[i]),
            )?;
        }

        // draw text naming the blend mode
        let text = graphics::Text::new((name, self.font, 20.0));
        let text_offset = Vec2::new(0., -90.);
        graphics::draw(
            ctx,
            quad_ctx,
            &text,
            graphics::DrawParam::new()
                .dest(pos + text_offset)
                .color(Color::WHITE)
                .offset(Vec2::new(0.5, 0.5)),
        )?;

        Ok(())
    }

    fn draw_venn_diagrams(
        &mut self,
        ctx: &mut Context,
        quad_ctx: &mut miniquad::GraphicsContext,
    ) -> GameResult<()> {
        let (w, h) = graphics::drawable_size(quad_ctx);
        let y = h / 4.;
        const MODE_COUNT: usize = 5;
        let x_step = w / (MODE_COUNT + 1) as f32;

        // draw with Alpha
        self.circle.set_blend_mode(Some(BlendMode::Alpha));
        self.draw_venn(ctx, quad_ctx, [x_step, y].into(), "Alpha")?;

        // draw with Add
        self.circle.set_blend_mode(Some(BlendMode::Add));
        self.draw_venn(ctx, quad_ctx, [x_step * 2., y].into(), "Add")?;

        // draw with Sub
        self.circle.set_blend_mode(Some(BlendMode::Subtract));
        self.draw_venn(ctx, quad_ctx, [x_step * 3., y].into(), "Subtract")?;

        // draw with Multiply
        self.circle.set_blend_mode(Some(BlendMode::Multiply));
        self.draw_venn(ctx, quad_ctx, [x_step * 4., y].into(), "Multiply")?;

        // draw with Replace
        self.circle.set_blend_mode(Some(BlendMode::Replace));
        self.draw_venn(ctx, quad_ctx, [x_step * 5., y].into(), "Replace")?;

        Ok(())
    }
}

impl EventHandler<ggez::GameError> for MainState {
    fn update(
        &mut self,
        _: &mut Context,
        _quad_ctx: &mut miniquad::GraphicsContext,
    ) -> GameResult<()> {
        Ok(())
    }

    fn draw(
        &mut self,
        ctx: &mut Context,
        quad_ctx: &mut miniquad::GraphicsContext,
    ) -> GameResult<()> {
        // also draw everything onto the canvas
        graphics::set_canvas(ctx, Some(&self.canvas));
        graphics::clear(ctx, quad_ctx, Color::new(0., 0., 0., 0.));
        self.draw_venn_diagrams(ctx, quad_ctx)?;

        // draw the canvas onto the screen
        graphics::set_canvas(ctx, None);
        graphics::clear(ctx, quad_ctx, Color::new(0.3, 0.3, 0.3, 1.0));
        // draw everything directly onto the screen once
        self.draw_venn_diagrams(ctx, quad_ctx)?;

        let (_, height) = graphics::drawable_size(quad_ctx);
        self.canvas.draw(
            ctx,
            quad_ctx,
            DrawParam::default().dest(mint::Point2 {
                x: 0.,
                y: height / 2.,
            }),
        )?;

        // draw text pointing out which is which
        let (_w, h) = graphics::drawable_size(quad_ctx);
        let y = h / 2.;

        let text = graphics::Text::new(("drawn directly:", self.font, 20.0));
        graphics::draw(
            ctx,
            quad_ctx,
            &text,
            graphics::DrawParam::new()
                .dest(Vec2::new(8., 4.))
                .color(Color::WHITE),
        )?;
        let text =
            graphics::Text::new(("drawn onto a (transparent black) canvas:", self.font, 20.0));
        graphics::draw(
            ctx,
            quad_ctx,
            &text,
            graphics::DrawParam::new()
                .dest(Vec2::new(8., 4. + y))
                .color(Color::WHITE),
        )?;

        graphics::present(ctx, quad_ctx)?;
        Ok(())
    }

    fn key_down_event(
        &mut self,
        _ctx: &mut Context,
        _quad_ctx: &mut miniquad::GraphicsContext,
        _keycode: ggez::event::KeyCode,
        _keymod: ggez::event::KeyMods,
        repeat: bool,
    ) {
        if !repeat {
            if let Some(BlendMode::Alpha) = self.canvas.blend_mode() {
                self.canvas.set_blend_mode(Some(BlendMode::Premultiplied));
                println!("Drawing canvas with premultiplied alpha mode");
            } else {
                self.canvas.set_blend_mode(Some(BlendMode::Alpha));
                println!("Drawing canvas with default alpha mode");
            }
        }
    }
}

pub fn main() -> GameResult {
    let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
        let mut path = path::PathBuf::from(manifest_dir);
        path.push("resources");
        path
    } else {
        path::PathBuf::from("./resources")
    };

    ggez::start(
        ggez::conf::Conf::default()
            .cache(Some(include_bytes!("resources.tar")))
            .physical_root_dir(Some(resource_dir)),
        |mut context, quad_ctx| Box::new(MainState::new(&mut context, quad_ctx).unwrap()),
    )
}