pittore 0.2.2

Simple toolkit for 2D visualization based on wgpu.
Documentation
use std::path::Path;

use glam::{UVec2, Vec2};
use winit::{event::MouseButton, keyboard::KeyCode, window::Window};

use crate::{
    input::InputState,
    render::{
        render2d::{Instance2d, IntoRawInstance, MESH_CIRCLE, MESH_RECT},
        BufferItem, Camera, Color, Line2d, Mesh, View, PIPELINE_FILL, TEXTURE_EMPTY,
    },
    Handle, RenderCommand, RenderState, ViewportState,
};

pub struct Context<'a> {
    pub render_commands: &'a mut Vec<RenderCommand>,
    pub render_state: &'a mut RenderState,
    pub input_state: &'a InputState,
}

impl Context<'_> {
    /// Set active camera for subsequent drawing.
    pub fn set_camera(&mut self, camera: Camera) {
        let resolution = self.render_state.resolution();
        self.render_state
            .buffers
            .get_mut::<View>()
            .unwrap()
            .append_temporary(vec![camera.view(resolution)]);
        self.render_commands.push(RenderCommand::SetCamera);
    }

    /// Draw instances with custom pipeline.
    pub fn draw<I: BufferItem>(
        &mut self,
        _pipeline_id: u32,
        mesh_id: impl Into<Handle>,
        material_id: u32,
        instances: Vec<I>,
    ) {
        let instance_range = self
            .render_state
            .buffers
            .get_mut::<I>()
            .unwrap()
            .append_temporary(instances);
        let instance_range = instance_range.start..instance_range.end;

        self.render_commands.push(RenderCommand::Draw {
            // pipeline_id,
            mesh_handle: mesh_id.into(),
            material_handle: Handle::Persistent(material_id),
            instance_range,
        });
    }

    /// Draw instances with the builtin fill pipeline.
    #[inline]
    pub fn draw_fill<T: IntoRawInstance>(
        &mut self,
        mesh_id: impl Into<Handle>,
        texture_id: u32,
        instances: impl IntoIterator<Item = T>,
    ) {
        let texture_size = match self.render_state.textures.get(texture_id) {
            Some(texture) => texture.size(),
            None => return,
        };

        let instances = instances
            .into_iter()
            .map(|obj| obj.into_raw(texture_size))
            .collect();

        self.draw(PIPELINE_FILL, mesh_id, texture_id, instances);
    }

    /// Draw lines.
    pub fn draw_lines(&mut self, lines: impl IntoIterator<Item = Line2d>) {
        self.draw_fill(MESH_RECT, TEXTURE_EMPTY, lines);
    }

    /// Draw rectangles.
    pub fn draw_rects(&mut self, instances: impl IntoIterator<Item = Instance2d>) {
        self.draw_fill(MESH_RECT, TEXTURE_EMPTY, instances);
    }

    /// Draw circles.
    pub fn draw_circles(&mut self, instances: impl IntoIterator<Item = Instance2d>) {
        self.draw_fill(MESH_CIRCLE, TEXTURE_EMPTY, instances);
    }

    /// Draw texture sprites.
    pub fn draw_sprites(
        &mut self,
        texture_id: u32,
        instances: impl IntoIterator<Item = Instance2d>,
    ) {
        self.draw_fill(MESH_RECT, texture_id, instances);
    }

    /// Register a custom mesh.
    pub fn register_mesh<V: BufferItem>(&mut self, mesh_id: u32, mesh: Mesh<V>) {
        self.render_state.register_mesh(mesh_id, mesh);
    }

    /// Register a temporary mesh. Returns a mesh id which is valid during this frame only.
    pub fn register_mesh_temporary<V: BufferItem>(&mut self, mesh: Mesh<V>) -> Handle {
        self.render_state.register_mesh_temporary(mesh)
    }

    /// Register a texture.
    pub fn register_texture(&mut self, texture_id: u32, image: image::DynamicImage) {
        self.render_state.register_texture(texture_id, image);
    }

    /// Register a shader.
    // pub fn register_shader(
    //     &mut self,
    //     shader_id: u32,
    //     shader_module_descriptor: wgpu::ShaderModuleDescriptor,
    // ) {
    //     let shader = self
    //         .render_state
    //         .device
    //         .create_shader_module(shader_module_descriptor);
    //     self.render_state.shaders.insert(shader_id, shader);
    // }

    /// Register a custom render pipeline.
    // pub fn register_pipeline(&mut self, pipeline_id: u32, pipeline: PipelineSet) {
    //     todo!()
    // }

    /// Check if a specific key of the keyboard is pressed.
    pub fn key_pressed(&self, keycode: KeyCode) -> bool {
        self.input_state.pressed_keys.contains(&keycode)
    }

    /// Check if a specific key of the keyboard has just been pressed during this frame.
    pub fn key_just_pressed(&self, keycode: KeyCode) -> bool {
        self.input_state.just_pressed_keys.contains(&keycode)
    }

    /// Check if a specific key of the keyboard has just been released during this frame.
    pub fn key_just_released(&self, keycode: KeyCode) -> bool {
        self.input_state.just_released_keys.contains(&keycode)
    }

    /// Returns true if the left button of mouse has been clicked.
    pub fn clicked(&self) -> bool {
        self.input_state
            .clicked_mouse_buttons
            .contains(&MouseButton::Left)
    }

    /// Check if a specific button of the mouse is pressed.
    pub fn mouse_button_pressed(&self, button: MouseButton) -> bool {
        self.input_state.pressed_mouse_buttons.contains(&button)
    }

    /// Check if a specific button of the mouse is pressed.
    pub fn mouse_button_clicked(&self, button: MouseButton) -> bool {
        self.input_state.clicked_mouse_buttons.contains(&button)
    }

    /// Get the position of cursor if available.
    pub fn cursor_position(&self) -> Option<Vec2> {
        self.input_state.cursor_position
    }

    /// Get changes in scroll wheel state.
    pub fn mouse_wheel_delta(&self) -> Option<Vec2> {
        self.input_state.mouse_wheel_delta
    }

    /// Get delta changes if dragging.
    pub fn mouse_drage_delta(&self) -> Option<Vec2> {
        self.input_state.mouse_drag_delta
    }

    /// Get reference to the active winit [`Window`].
    pub fn window(&self) -> &Window {
        &self.render_state.window
    }

    pub fn set_background_color(&mut self, color: Color) {
        self.render_state.background_color = color.into();
    }

    /// Set rendering resolution.
    ///
    /// If this is set to `Some`, it will draw at the specified resolution.
    /// If the aspect ratio of the specified resolution differs from that of the window, the gaps will be filled with black.
    ///
    /// If set to `None`, it will follow the window size. (In fullscreen mode, it will match the screen resolution.)
    pub fn set_resolution(&mut self, resolution: Option<UVec2>) {
        let render_state = &mut self.render_state;
        let fixed_viewport_size = resolution.map(|v| wgpu::Extent3d {
            width: v.x,
            height: v.y,
            depth_or_array_layers: 1,
        });
        render_state.viewport = ViewportState::new(
            &render_state.device,
            &render_state.surface_config,
            fixed_viewport_size,
        );
    }

    /// Save screen shot.
    pub fn save_screen_shot(&mut self, path: impl AsRef<Path>) {
        self.render_state.capture_target = Some(path.as_ref().to_path_buf());
    }
}