Raw File
controller.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/>.
*/
use super::{
    camera, DataPtr, Duration, ElementSelector, HandleDir, SceneElement, ViewPtr,
    WidgetRotationMode as RotationMode,
};
use crate::consts::*;
use crate::design::{Nucl, StrandBuilder};
use crate::{PhySize, PhysicalPosition, WindowEvent};
use iced_winit::winit::event::*;
use std::cell::RefCell;
use ultraviolet::{Rotor3, Vec3};

use camera::CameraController;

mod automata;
use automata::{NormalState, State, Transition};

/// The effect that draging the mouse have
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClickMode {
    TranslateCam,
    #[allow(dead_code)]
    RotateCam,
}

/// An object handling input and notification for the scene.
pub struct Controller {
    /// A pointer to the View
    view: ViewPtr,
    /// A pointer to the data
    data: DataPtr,
    /// The event that modify the camera are forwarded to the camera_controller
    camera_controller: CameraController,
    /// The size of the window
    window_size: PhySize,
    /// The size of the drawing area
    area_size: PhySize,
    /// The current modifiers
    current_modifiers: ModifiersState,
    /// The effect that dragging the mouse has
    click_mode: ClickMode,
    state: State,
    pub(super) pasting: bool,
}

pub enum Consequence {
    CameraMoved,
    CameraTranslated(f64, f64),
    XoverAtempt(Nucl, Nucl, usize),
    Translation(HandleDir, f64, f64),
    MovementEnded,
    Rotation(RotationMode, f64, f64),
    InitRotation(f64, f64),
    InitTranslation(f64, f64),
    Swing(f64, f64),
    Nothing,
    ToggleWidget,
    BuildEnded(u32, u32),
    Building(Box<StrandBuilder>, isize),
    Undo,
    Redo,
    Candidate(Option<super::SceneElement>),
    PivotElement(Option<super::SceneElement>),
    ElementSelected(Option<super::SceneElement>, bool),
    InitFreeXover(Nucl, usize, Vec3),
    MoveFreeXover(Option<super::SceneElement>, Vec3),
    EndFreeXover,
    BuildHelix {
        design_id: u32,
        grid_id: usize,
        position: isize,
        length: usize,
        x: isize,
        y: isize,
    },
    PasteCandidate(Option<super::SceneElement>),
    Paste(Option<super::SceneElement>),
    DoubleClick(Option<super::SceneElement>),
}

enum TransistionConsequence {
    Nothing,
    InitMovement,
    EndMovement,
}

impl Controller {
    pub fn new(view: ViewPtr, data: DataPtr, window_size: PhySize, area_size: PhySize) -> Self {
        let camera_controller = {
            let view = view.borrow();
            CameraController::new(
                4.0,
                BASE_SCROLL_SENSITIVITY,
                view.get_camera(),
                view.get_projection(),
            )
        };
        Self {
            view,
            data,
            camera_controller,
            window_size,
            area_size,
            current_modifiers: ModifiersState::empty(),
            click_mode: ClickMode::TranslateCam,
            state: automata::initial_state(),
            pasting: false,
        }
    }

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

    /// Replace the camera by a new one.
    pub fn teleport_camera(&mut self, position: Vec3, rotation: Rotor3) {
        self.camera_controller.teleport_camera(position, rotation);
        self.end_movement();
    }

    pub fn set_camera_position(&mut self, position: Vec3) {
        self.camera_controller.set_camera_position(position);
        self.end_movement();
    }

    /// Keep the camera orientation and make it face a given point.
    pub fn center_camera(&mut self, center: Vec3) {
        self.camera_controller.center_camera(center)
    }

    pub fn check_timers(&mut self) -> Consequence {
        let transition = self.state.borrow_mut().check_timers(&self);
        if let Some(state) = transition.new_state {
            println!("{}", state.display());
            let csq = self.state.borrow().transition_from(&self);
            self.transition_consequence(csq);
            self.state = RefCell::new(state);
            let csq = self.state.borrow().transition_to(&self);
            self.transition_consequence(csq);
        }
        transition.consequences
    }

    pub fn input(
        &mut self,
        event: &WindowEvent,
        position: PhysicalPosition<f64>,
        pixel_reader: &mut ElementSelector,
    ) -> Consequence {
        let transition = if let WindowEvent::Focused(false) = event {
            Transition {
                new_state: Some(Box::new(NormalState {
                    mouse_position: PhysicalPosition::new(-1., -1.),
                })),
                consequences: Consequence::Nothing,
            }
        } else if let WindowEvent::MouseWheel { delta, .. } = event {
            let mouse_x = position.x / self.area_size.width as f64;
            let mouse_y = position.y / self.area_size.height as f64;
            self.camera_controller
                .process_scroll(delta, mouse_x as f32, mouse_y as f32);
            Transition::consequence(Consequence::CameraMoved)
        } else if let WindowEvent::KeyboardInput {
            input:
                KeyboardInput {
                    state,
                    virtual_keycode: Some(key),
                    ..
                },
            ..
        } = event
        {
            let csq = match *key {
                VirtualKeyCode::Z
                    if ctrl(&self.current_modifiers) && *state == ElementState::Pressed =>
                {
                    Consequence::Undo
                }
                VirtualKeyCode::R
                    if ctrl(&self.current_modifiers) && *state == ElementState::Pressed =>
                {
                    Consequence::Redo
                }
                VirtualKeyCode::Space if *state == ElementState::Pressed => {
                    Consequence::ToggleWidget
                }
                _ => {
                    if self.camera_controller.process_keyboard(*key, *state) {
                        Consequence::CameraMoved
                    } else {
                        Consequence::Nothing
                    }
                }
            };
            Transition::consequence(csq)
        } else {
            self.state
                .borrow_mut()
                .input(event, position, &self, pixel_reader)
        };

        if let Some(state) = transition.new_state {
            println!("{}", state.display());
            let csq = self.state.borrow().transition_from(&self);
            self.transition_consequence(csq);
            self.state = RefCell::new(state);
            let csq = self.state.borrow().transition_to(&self);
            self.transition_consequence(csq);
        }
        transition.consequences
    }

    fn transition_consequence(&mut self, csq: TransistionConsequence) {
        match csq {
            TransistionConsequence::Nothing => (),
            TransistionConsequence::InitMovement => self.init_movement(),
            TransistionConsequence::EndMovement => self.end_movement(),
        }
    }

    /// True if the camera is moving and its position must be updated before next frame
    pub fn camera_is_moving(&self) -> bool {
        self.camera_controller.is_moving()
    }

    /// Set the pivot point of the camera
    pub fn set_pivot_point(&mut self, point: Option<Vec3>) {
        self.camera_controller.set_pivot_point(point)
    }

    /// Swing the camera arround its pivot point
    pub fn swing(&mut self, x: f64, y: f64) {
        self.camera_controller.swing(x, y);
    }

    /// Moves the camera according to its speed and the time elapsed since previous frame
    pub fn update_camera(&mut self, dt: Duration) {
        self.camera_controller.update_camera(dt, self.click_mode);
    }

    /// Handles a resizing of the window and/or drawing area
    pub fn resize(&mut self, window_size: PhySize, area_size: PhySize) {
        self.window_size = window_size;
        self.area_size = area_size;
        self.camera_controller.resize(area_size);
        // the view needs the window size to build a depth texture
        self.view
            .borrow_mut()
            .update(super::view::ViewUpdate::Size(window_size));
    }

    pub fn get_window_size(&self) -> PhySize {
        self.window_size
    }

    fn init_movement(&mut self) {
        self.camera_controller.init_movement();
    }

    fn end_movement(&mut self) {
        self.camera_controller.end_movement();
    }

    pub fn change_sensitivity(&mut self, sensitivity: f32) {
        self.camera_controller.sensitivity = 10f32.powf(sensitivity / 10.) * BASE_SCROLL_SENSITIVITY
    }

    pub fn set_camera_target(&mut self, target: Vec3, up: Vec3, pivot: Option<Vec3>) {
        self.camera_controller
            .look_at_orientation(target, up, pivot);
        self.shift_cam();
    }

    pub fn translate_camera(&mut self, dx: f64, dy: f64) {
        self.camera_controller.process_mouse(dx, dy)
    }

    pub fn rotate_camera(&mut self, xz: f32, yz: f32, xy: f32, pivot: Option<Vec3>) {
        self.camera_controller.rotate_camera(xz, yz, pivot);
        self.camera_controller.tilt_camera(xy);
        self.shift_cam();
    }

    fn shift_cam(&mut self) {
        self.camera_controller.shift()
    }
}

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