Raw File
multiplexer.rs
/*
ENSnano, a 3d graphical application for DNA nanostructures.
    Copyright (C) 2021  Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
//! This modules handles the separation of the window into different regions.
//!
//! The layout manager split the window into different regions and attribute each region to an
//! an application or a gui component.
//!
//! In addition, the multiplexer holds a Vec of overlays which are floating regions.
//!
//! When an event is recieved by the window, the multiplexer is in charge of forwarding it to the
//! appropriate application, gui component or overlay. The multiplexer also handles some events
//! like resizing events of keyboard input that should be handled independently of the foccussed
//! region.
//!
//!
//!
//! The multiplexer is also in charge of drawing to the frame.
use super::{save_before_open, save_before_quit};
use crate::gui::{Requests, UiSize};
use crate::mediator::{ActionMode, SelectionMode};
use crate::utils::texture::SampledTexture;
use crate::PhySize;
use iced_wgpu::wgpu;
use iced_winit::winit;
use iced_winit::winit::event::*;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use wgpu::Device;
use winit::{
    dpi::{PhysicalPosition, PhysicalSize},
    event::{ElementState, WindowEvent},
    window::CursorIcon,
};

mod layout_manager;
use layout_manager::{LayoutTree, PixelRegion};

/// A structure that represents an area on which an element can be drawn
#[derive(Clone, Copy, Debug)]
pub struct DrawArea {
    /// The top left corner of the element
    pub position: PhysicalPosition<u32>,
    /// The *physical* size of the element
    pub size: PhySize,
}

/// The different elements represented on the scene. Each element is instanciated once.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum ElementType {
    /// The top menu bar
    TopBar,
    /// The 3D scene
    Scene,
    /// The flat Scene
    FlatScene,
    /// The Left Panel
    LeftPanel,
    /// The status bar
    StatusBar,
    GridPanel,
    /// An overlay area
    Overlay(usize),
    /// An area that has not been attributed to an element
    Unattributed,
}

impl ElementType {
    pub fn is_gui(&self) -> bool {
        match self {
            ElementType::TopBar | ElementType::LeftPanel | ElementType::StatusBar => true,
            _ => false,
        }
    }

    pub fn is_scene(&self) -> bool {
        match self {
            ElementType::Scene | ElementType::FlatScene => true,
            _ => false,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SplitMode {
    Flat,
    Scene3D,
    Both,
}

/// A structure that handles the division of the window into different `DrawArea`
pub struct Multiplexer {
    /// The *physical* size of the window
    pub window_size: PhySize,
    /// The scale factor of the window
    pub scale_factor: f64,
    /// The object mapping pixels to drawing areas
    layout_manager: LayoutTree,
    /// The Element on which the mouse cursor is currently on.
    focus: Option<ElementType>,
    /// The *physical* position of the cursor on the focus area
    cursor_position: PhysicalPosition<f64>,
    /// The area that are drawn on top of the application
    overlays: Vec<Overlay>,
    /// The texture on which the scene is rendered
    scene_texture: Option<SampledTexture>,
    /// The texture on which the top bar gui is rendered
    top_bar_texture: Option<SampledTexture>,
    /// The texture on which the left pannel is rendered
    left_pannel_texture: Option<SampledTexture>,
    /// The textures on which the overlays are rendered
    overlays_textures: Vec<SampledTexture>,
    /// The texture on wich the grid is rendered
    grid_panel_texture: Option<SampledTexture>,
    /// The texutre on which the flat scene is rendered,
    status_bar_texture: Option<SampledTexture>,
    flat_scene_texture: Option<SampledTexture>,
    /// The pointer the node that separate the left pannel from the scene
    left_pannel_split: usize,
    /// The pointer to the node that separate the top bar from the scene
    top_bar_split: usize,
    /// The pointer to the node that separtate the status bar from the scene
    status_bar_split: usize,
    device: Rc<Device>,
    pipeline: Option<wgpu::RenderPipeline>,
    split_mode: SplitMode,
    requests: Arc<Mutex<Requests>>,
    state: State,
    modifiers: ModifiersState,
    ui_size: UiSize,
    pub invert_y_scroll: bool,
}

const MAX_LEFT_PANNEL_WIDTH: f64 = 200.;
const MAX_STATUS_BAR_HEIGHT: f64 = 50.;

impl Multiplexer {
    /// Create a new multiplexer for a window with size `window_size`.
    pub fn new(
        window_size: PhySize,
        scale_factor: f64,
        device: Rc<Device>,
        requests: Arc<Mutex<Requests>>,
    ) -> Self {
        let ui_size: UiSize = Default::default();
        let mut layout_manager = LayoutTree::new();
        let top_pannel_prop =
            exact_proportion(ui_size.top_bar() * scale_factor, window_size.height as f64);
        let top_bar_split = 0;
        let (top_bar, scene) = layout_manager.hsplit(0, top_pannel_prop, false);
        let left_pannel_split = scene;
        let left_pannel_prop = proportion(
            0.2,
            MAX_LEFT_PANNEL_WIDTH * scale_factor,
            window_size.width as f64,
        );
        let (left_pannel, scene) = layout_manager.vsplit(scene, left_pannel_prop, true);
        let scene_height = (1. - top_pannel_prop) * window_size.height as f64;
        let status_bar_prop = exact_proportion(MAX_STATUS_BAR_HEIGHT * scale_factor, scene_height);
        let status_bar_split = scene;
        let (scene, status_bar) = layout_manager.hsplit(scene, 1. - status_bar_prop, false);
        //let (scene, grid_panel) = layout_manager.hsplit(scene, 0.8);
        layout_manager.attribute_element(top_bar, ElementType::TopBar);
        layout_manager.attribute_element(scene, ElementType::Scene);
        layout_manager.attribute_element(status_bar, ElementType::StatusBar);
        layout_manager.attribute_element(left_pannel, ElementType::LeftPanel);
        //layout_manager.attribute_element(grid_panel, ElementType::GridPanel);
        let mut ret = Self {
            window_size,
            scale_factor,
            layout_manager,
            focus: None,
            cursor_position: PhysicalPosition::new(-1., -1.),
            scene_texture: None,
            flat_scene_texture: None,
            top_bar_texture: None,
            left_pannel_texture: None,
            grid_panel_texture: None,
            status_bar_texture: None,
            overlays: Vec::new(),
            overlays_textures: Vec::new(),
            device,
            pipeline: None,
            split_mode: SplitMode::Scene3D,
            requests,
            left_pannel_split,
            status_bar_split,
            top_bar_split,
            state: State::Normal {
                mouse_position: PhysicalPosition::new(-1., -1.),
            },
            modifiers: ModifiersState::empty(),
            ui_size,
            invert_y_scroll: false,
        };
        ret.generate_textures();
        ret
    }

    /// Return a view of the texture on which the element must be rendered
    pub fn get_texture_view(&self, element_type: ElementType) -> Option<&wgpu::TextureView> {
        match element_type {
            ElementType::Scene => self.scene_texture.as_ref().map(|t| &t.view),
            ElementType::LeftPanel => self.left_pannel_texture.as_ref().map(|t| &t.view),
            ElementType::TopBar => self.top_bar_texture.as_ref().map(|t| &t.view),
            ElementType::Overlay(n) => Some(&self.overlays_textures[n].view),
            ElementType::GridPanel => self.grid_panel_texture.as_ref().map(|t| &t.view),
            ElementType::FlatScene => self.flat_scene_texture.as_ref().map(|t| &t.view),
            ElementType::StatusBar => self.status_bar_texture.as_ref().map(|t| &t.view),
            ElementType::Unattributed => unreachable!(),
        }
    }

    pub fn update_modifiers(&mut self, modifiers: ModifiersState) {
        self.modifiers = modifiers
    }

    pub fn draw(&mut self, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView) {
        if self.pipeline.is_none() {
            let bg_layout = &self.top_bar_texture.as_ref().unwrap().bg_layout;
            self.pipeline = Some(create_pipeline(self.device.as_ref(), bg_layout));
        }
        let clear_color = wgpu::Color {
            r: 0.,
            g: 0.,
            b: 0.,
            a: 1.,
        };

        let msaa_texture = None;

        let attachment = if msaa_texture.is_some() {
            msaa_texture.as_ref().unwrap()
        } else {
            target
        };

        let resolve_target = if msaa_texture.is_some() {
            Some(target)
        } else {
            None
        };

        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: None,
            color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                attachment,
                resolve_target,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Clear(clear_color),
                    store: true,
                },
            }],
            depth_stencil_attachment: None,
        });
        if self.window_size.width > 0 && self.window_size.height > 0 {
            for element in [
                ElementType::TopBar,
                ElementType::LeftPanel,
                ElementType::GridPanel,
                ElementType::Scene,
                ElementType::FlatScene,
                ElementType::StatusBar,
            ]
            .iter()
            {
                if let Some(area) = self.get_draw_area(*element) {
                    render_pass.set_bind_group(0, self.get_bind_group(element), &[]);

                    render_pass.set_viewport(
                        area.position.x as f32,
                        area.position.y as f32,
                        area.size.width as f32,
                        area.size.height as f32,
                        0.0,
                        1.0,
                    );
                    let width = area
                        .size
                        .width
                        .min(self.window_size.width - area.position.x);
                    let height = area
                        .size
                        .height
                        .min(self.window_size.height - area.position.y);
                    render_pass.set_scissor_rect(area.position.x, area.position.y, width, height);
                    render_pass.set_pipeline(self.pipeline.as_ref().unwrap());
                    render_pass.draw(0..4, 0..1);
                }
            }
        }
    }

    fn get_bind_group(&self, element_type: &ElementType) -> &wgpu::BindGroup {
        match element_type {
            ElementType::TopBar => &self.top_bar_texture.as_ref().unwrap().bind_group,
            ElementType::LeftPanel => &self.left_pannel_texture.as_ref().unwrap().bind_group,
            ElementType::Scene => &self.scene_texture.as_ref().unwrap().bind_group,
            ElementType::FlatScene => &self.flat_scene_texture.as_ref().unwrap().bind_group,
            ElementType::GridPanel => &self.grid_panel_texture.as_ref().unwrap().bind_group,
            ElementType::Overlay(n) => &self.overlays_textures[*n].bind_group,
            ElementType::StatusBar => &self.status_bar_texture.as_ref().unwrap().bind_group,
            ElementType::Unattributed => unreachable!(),
        }
    }

    /// Return the drawing area attributed to an element.
    pub fn get_draw_area(&self, element_type: ElementType) -> Option<DrawArea> {
        use ElementType::Overlay;
        let (position, size) = if let Overlay(n) = element_type {
            (self.overlays[n].position, self.overlays[n].size)
        } else {
            let (left, top, right, bottom) = self.layout_manager.get_area(element_type)?;
            let top = top * self.window_size.height as f64;
            let left = left * self.window_size.width as f64;
            let bottom = bottom * self.window_size.height as f64;
            let right = right * self.window_size.width as f64;

            (
                PhysicalPosition::new(left, top).cast::<u32>(),
                PhysicalSize::new(right - left, bottom - top).cast::<u32>(),
            )
        };
        Some(DrawArea { position, size })
    }

    pub fn check_scale_factor(&mut self, window: &crate::Window) -> bool {
        if self.scale_factor != window.scale_factor() {
            self.scale_factor = window.scale_factor();
            self.window_size = window.inner_size();
            self.resize(self.window_size, self.scale_factor);

            if self.window_size.width > 0 && self.window_size.height > 0 {
                self.generate_textures();
            }
            true
        } else {
            false
        }
    }

    /// Forwards event to the elment on which they happen.
    pub fn event(
        &mut self,
        mut event: WindowEvent<'static>,
        resized: &mut bool,
        scale_factor_changed: &mut bool,
    ) -> (
        Option<(WindowEvent<'static>, ElementType)>,
        Option<CursorIcon>,
    ) {
        let mut icon = None;
        let mut captured = false;
        match &mut event {
            WindowEvent::CursorMoved { position, .. } => match &mut self.state {
                State::Resizing {
                    region,
                    mouse_position,
                    clicked_position,
                    old_proportion,
                } => {
                    *mouse_position = *position;
                    let mut position = position.clone();
                    position.x /= self.window_size.width as f64;
                    position.y /= self.window_size.height as f64;
                    *resized = true;
                    self.layout_manager.resize_click(
                        *region,
                        &position,
                        &clicked_position,
                        *old_proportion,
                    );
                    icon = Some(CursorIcon::EwResize);
                    captured = true;
                }

                State::Normal { mouse_position, .. } => {
                    *mouse_position = *position;
                    let &mut PhysicalPosition { x, y } = position;
                    if x > 0.0 || y > 0.0 {
                        let element = self.pixel_to_element(*position);
                        let area = match element {
                            PixelRegion::Resize(_) => {
                                icon = Some(CursorIcon::EwResize);
                                None
                            }
                            PixelRegion::Element(element) => {
                                icon = Some(CursorIcon::Arrow);
                                self.focus = Some(element);
                                self.get_draw_area(element)
                            }
                            PixelRegion::Area(_) => unreachable!(),
                        }
                        .or(self.focus.and_then(|e| self.get_draw_area(e)));

                        if let Some(area) = area {
                            self.cursor_position.x = position.x - area.position.cast::<f64>().x;
                            self.cursor_position.y = position.y - area.position.cast::<f64>().y;
                        }
                    }
                }
                State::Interacting {
                    mouse_position,
                    element,
                } => {
                    *mouse_position = *position;
                    let element = element.clone();
                    let area = self.get_draw_area(element);
                    if let Some(area) = area {
                        self.cursor_position.x = position.x - area.position.cast::<f64>().x;
                        self.cursor_position.y = position.y - area.position.cast::<f64>().y;
                    }
                }
            },
            WindowEvent::Resized(new_size) => {
                self.window_size = *new_size;
                self.resize(*new_size, self.scale_factor);
                *resized = true;
                if self.window_size.width > 0 && self.window_size.height > 0 {
                    self.generate_textures();
                }
            }
            WindowEvent::ScaleFactorChanged {
                scale_factor,
                new_inner_size,
            } => {
                self.scale_factor = *scale_factor;
                self.window_size = **new_inner_size;
                self.resize(self.window_size, self.scale_factor);
                *resized = true;
                *scale_factor_changed = true;
                if self.window_size.width > 0 && self.window_size.height > 0 {
                    self.generate_textures();
                }
            }
            WindowEvent::MouseInput { state, .. } => {
                let element = self.pixel_to_element(self.state.mouse_position());
                let mouse_position = self.state.mouse_position();
                match element {
                    PixelRegion::Resize(n) if *state == ElementState::Pressed => {
                        let mut clicked_position = mouse_position.clone();
                        clicked_position.x /= self.window_size.width as f64;
                        clicked_position.y /= self.window_size.height as f64;
                        let old_proportion = self.layout_manager.get_proportion(n).unwrap();
                        self.state = State::Resizing {
                            mouse_position,
                            clicked_position,
                            region: n,
                            old_proportion,
                        };
                    }
                    PixelRegion::Resize(_) => {
                        self.state = State::Normal { mouse_position };
                    }
                    PixelRegion::Element(element) => match state {
                        ElementState::Pressed => {
                            self.state = State::Interacting {
                                mouse_position,
                                element,
                            };
                        }
                        ElementState::Released => {
                            self.state = State::Normal { mouse_position };
                        }
                    },
                    _ => unreachable!(),
                }
            }
            WindowEvent::KeyboardInput {
                input:
                    KeyboardInput {
                        virtual_keycode: Some(key),
                        state: ElementState::Pressed,
                        ..
                    },
                ..
            } => {
                captured = true;
                match *key {
                    VirtualKeyCode::Escape => {
                        self.requests.lock().unwrap().action_mode = Some(ActionMode::Normal)
                    }
                    VirtualKeyCode::C if ctrl(&self.modifiers) => {
                        self.requests.lock().unwrap().copy = true;
                    }
                    VirtualKeyCode::V if ctrl(&self.modifiers) => {
                        self.requests.lock().unwrap().paste = true;
                    }
                    VirtualKeyCode::J if ctrl(&self.modifiers) => {
                        self.requests.lock().unwrap().duplication = true;
                    }
                    VirtualKeyCode::L if ctrl(&self.modifiers) => {
                        self.requests.lock().unwrap().anchor = true;
                    }
                    VirtualKeyCode::R if !ctrl(&self.modifiers) => {
                        self.requests.lock().unwrap().action_mode = Some(ActionMode::Rotate)
                    }
                    VirtualKeyCode::T => {
                        self.requests.lock().unwrap().action_mode = Some(ActionMode::Translate)
                    }
                    VirtualKeyCode::N => {
                        self.requests.lock().unwrap().selection_mode =
                            Some(SelectionMode::Nucleotide)
                    }
                    VirtualKeyCode::H => {
                        self.requests.lock().unwrap().selection_mode = Some(SelectionMode::Helix)
                    }
                    VirtualKeyCode::S if ctrl(&self.modifiers) => {
                        self.requests.lock().unwrap().save_shortcut = Some(());
                    }
                    VirtualKeyCode::O if ctrl(&self.modifiers) => {
                        save_before_open(self.requests.clone());
                    }
                    VirtualKeyCode::Q if ctrl(&self.modifiers) && cfg!(target_os = "macos") => {
                        save_before_quit(self.requests.clone());
                    }
                    VirtualKeyCode::S => {
                        self.requests.lock().unwrap().selection_mode = Some(SelectionMode::Strand)
                    }
                    /*
                    VirtualKeyCode::K => {
                        self.requests.lock().unwrap().recolor_stapples = true;
                    }*/
                    VirtualKeyCode::Delete | VirtualKeyCode::Back => {
                        self.requests.lock().unwrap().delete_selection = true;
                    }
                    _ => captured = false,
                }
            }
            WindowEvent::MouseWheel { delta, .. } => {
                if self.invert_y_scroll {
                    match delta {
                        MouseScrollDelta::LineDelta(_, y) => {
                            *y *= -1.;
                        }
                        MouseScrollDelta::PixelDelta(position) => {
                            position.y *= -1.;
                        }
                    }
                }
            }
            _ => {}
        }

        if let Some(focus) = self.focus.filter(|_| !captured) {
            (Some((event, focus)), icon)
        } else {
            (None, icon)
        }
    }

    pub fn change_ui_size(&mut self, ui_size: UiSize, window: &iced_winit::winit::window::Window) {
        self.ui_size = ui_size;
        self.resize(window.inner_size(), window.scale_factor());
        self.generate_textures();
    }

    pub fn change_split(&mut self, split_mode: SplitMode) {
        if split_mode != self.split_mode {
            match self.split_mode {
                SplitMode::Both => {
                    let new_type = match split_mode {
                        SplitMode::Scene3D => ElementType::Scene,
                        SplitMode::Flat => ElementType::FlatScene,
                        SplitMode::Both => unreachable!(),
                    };
                    self.layout_manager.merge(ElementType::Scene, new_type);
                }
                SplitMode::Scene3D | SplitMode::Flat => {
                    let id = self
                        .layout_manager
                        .get_area_id(ElementType::Scene)
                        .or(self.layout_manager.get_area_id(ElementType::FlatScene))
                        .unwrap();
                    match split_mode {
                        SplitMode::Both => {
                            let (scene, flat_scene) = self.layout_manager.vsplit(id, 0.5, true);
                            self.layout_manager
                                .attribute_element(scene, ElementType::Scene);
                            self.layout_manager
                                .attribute_element(flat_scene, ElementType::FlatScene);
                        }
                        SplitMode::Scene3D => self
                            .layout_manager
                            .attribute_element(id, ElementType::Scene),
                        SplitMode::Flat => self
                            .layout_manager
                            .attribute_element(id, ElementType::FlatScene),
                    }
                }
            }
        }
        self.split_mode = split_mode;
        self.generate_textures();
    }

    fn resize(&mut self, window_size: PhySize, scale_factor: f64) {
        let top_pannel_prop = exact_proportion(
            self.ui_size.top_bar() * scale_factor,
            window_size.height as f64,
        );
        let left_pannel_prop = proportion(
            0.2,
            MAX_LEFT_PANNEL_WIDTH * scale_factor,
            window_size.width as f64,
        );
        let scene_height = (1. - top_pannel_prop) * window_size.height as f64;
        let status_bar_prop = exact_proportion(MAX_STATUS_BAR_HEIGHT * scale_factor, scene_height);
        self.layout_manager
            .resize(self.left_pannel_split, left_pannel_prop);
        self.layout_manager
            .resize(self.top_bar_split, top_pannel_prop);
        self.layout_manager
            .resize(self.status_bar_split, 1. - status_bar_prop);
    }

    fn texture(&mut self, element_type: ElementType) -> Option<SampledTexture> {
        self.get_draw_area(element_type)
            .filter(|a| a.size.height > 0 && a.size.width > 0)
            .map(|a| SampledTexture::create_target_texture(self.device.as_ref(), &a.size))
    }

    pub fn generate_textures(&mut self) {
        self.scene_texture = self.texture(ElementType::Scene);
        self.top_bar_texture = self.texture(ElementType::TopBar);
        self.left_pannel_texture = self.texture(ElementType::LeftPanel);
        self.grid_panel_texture = self.texture(ElementType::GridPanel);
        self.flat_scene_texture = self.texture(ElementType::FlatScene);
        self.status_bar_texture = self.texture(ElementType::StatusBar);

        self.overlays_textures.clear();
        for overlay in self.overlays.iter() {
            let size = overlay.size;
            self.overlays_textures
                .push(SampledTexture::create_target_texture(
                    self.device.as_ref(),
                    &size,
                ));
        }
    }

    /// Maps *physical* pixels to an element
    fn pixel_to_element(&self, pixel: PhysicalPosition<f64>) -> PixelRegion {
        let pixel_u32 = pixel.cast::<u32>();
        for (n, overlay) in self.overlays.iter().enumerate() {
            if overlay.contains_pixel(pixel_u32) {
                return PixelRegion::Element(ElementType::Overlay(n));
            }
        }
        self.layout_manager.get_area_pixel(
            pixel.x / self.window_size.width as f64,
            pixel.y / self.window_size.height as f64,
        )
    }

    /// Get the drawing area attributed to an element.
    pub fn get_element_area(&self, element: ElementType) -> Option<DrawArea> {
        self.get_draw_area(element)
    }

    /// Return the *physical* position of the cursor, in the foccused element coordinates
    pub fn get_cursor_position(&self) -> PhysicalPosition<f64> {
        self.cursor_position
    }

    /// Return the foccused element
    pub fn foccused_element(&self) -> Option<ElementType> {
        self.focus
    }

    pub fn set_overlays(&mut self, overlays: Vec<Overlay>) {
        self.overlays = overlays;
        self.overlays_textures.clear();
        for overlay in self.overlays.iter_mut() {
            let size = overlay.size;
            self.overlays_textures
                .push(SampledTexture::create_target_texture(
                    self.device.as_ref(),
                    &size,
                ));
        }
    }

    pub fn is_showing(&self, area: &ElementType) -> bool {
        match area {
            ElementType::LeftPanel | ElementType::TopBar | ElementType::StatusBar => true,
            ElementType::Scene => {
                self.split_mode == SplitMode::Scene3D || self.split_mode == SplitMode::Both
            }
            ElementType::FlatScene => {
                self.split_mode == SplitMode::Flat || self.split_mode == SplitMode::Both
            }
            _ => false,
        }
    }
}

#[derive(Clone)]
pub struct Overlay {
    pub position: PhysicalPosition<u32>,
    pub size: PhysicalSize<u32>,
}

impl Overlay {
    pub fn contains_pixel(&self, pixel: PhysicalPosition<u32>) -> bool {
        pixel.x >= self.position.x
            && pixel.y >= self.position.y
            && pixel.x < self.position.x + self.size.width
            && pixel.y < self.position.y + self.size.height
    }
}

fn create_pipeline(device: &Device, bg_layout: &wgpu::BindGroupLayout) -> wgpu::RenderPipeline {
    let vs_module =
        &device.create_shader_module(&wgpu::include_spirv!("multiplexer/draw.vert.spv"));
    let fs_module =
        &device.create_shader_module(&wgpu::include_spirv!("multiplexer/draw.frag.spv"));
    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        bind_group_layouts: &[bg_layout],
        push_constant_ranges: &[],
        label: None,
    });

    let targets = &[wgpu::ColorTargetState {
        format: wgpu::TextureFormat::Bgra8UnormSrgb,
        color_blend: wgpu::BlendState::REPLACE,
        alpha_blend: wgpu::BlendState::REPLACE,
        write_mask: wgpu::ColorWrite::ALL,
    }];

    let primitive = wgpu::PrimitiveState {
        topology: wgpu::PrimitiveTopology::TriangleStrip,
        strip_index_format: Some(wgpu::IndexFormat::Uint16),
        front_face: wgpu::FrontFace::Ccw,
        cull_mode: wgpu::CullMode::None,
        ..Default::default()
    };

    let desc = wgpu::RenderPipelineDescriptor {
        layout: Some(&pipeline_layout),
        vertex: wgpu::VertexState {
            module: &vs_module,
            entry_point: "main",
            buffers: &[],
        },
        fragment: Some(wgpu::FragmentState {
            module: &fs_module,
            entry_point: "main",
            targets,
        }),
        primitive,
        depth_stencil: None,
        multisample: wgpu::MultisampleState {
            count: 1,
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        label: None,
    };

    device.create_render_pipeline(&desc)
}

fn proportion(min_prop: f64, max_size: f64, length: f64) -> f64 {
    let max_prop = max_size / length;
    max_prop.min(min_prop)
}

fn exact_proportion(size: f64, length: f64) -> f64 {
    size / length
}

enum State {
    Resizing {
        mouse_position: PhysicalPosition<f64>,
        clicked_position: PhysicalPosition<f64>,
        region: usize,
        old_proportion: f64,
    },
    Normal {
        mouse_position: PhysicalPosition<f64>,
    },
    Interacting {
        mouse_position: PhysicalPosition<f64>,
        element: ElementType,
    },
}

impl State {
    fn mouse_position(&self) -> PhysicalPosition<f64> {
        match self {
            Self::Resizing { mouse_position, .. }
            | Self::Normal { mouse_position }
            | Self::Interacting { mouse_position, .. } => *mouse_position,
        }
    }
}

fn ctrl(modifiers: &ModifiersState) -> bool {
    if cfg!(target_os = "macos") {
        modifiers.logo()
    } else {
        modifiers.ctrl()
    }
}
back to top