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/>.
*/
//! The `Controller` struct handles the event that happens on the drawing area of the scene.
//!
//! The `Controller` is internally implemented as a finite automata that transitions when a event
//! happens. In addition to the transistion in the automat, a `Consequence` is returned to the
//! scene, that describes the consequences that the input must have on the view or the data held by
//! the scene.
use self::automata::ReleasedPivot;
use super::data::{ClickResult, FreeEnd};
use super::{
ActionMode, Arc, CameraPtr, DataPtr, FlatHelix, FlatNucl, Mediator, Mutex, PhySize,
PhysicalPosition, ViewPtr, WindowEvent,
};
use crate::design::StrandBuilder;
use iced_winit::winit::event::*;
use std::cell::RefCell;
use ultraviolet::Vec2;
mod automata;
use automata::{ControllerState, NormalState, Transition};
pub struct Controller {
#[allow(dead_code)]
view: ViewPtr,
data: DataPtr,
#[allow(dead_code)]
window_size: PhySize,
area_size: PhySize,
camera_top: CameraPtr,
camera_bottom: CameraPtr,
splited: bool,
state: RefCell<Box<dyn ControllerState>>,
action_mode: ActionMode,
mediator: Arc<Mutex<Mediator>>,
pasting: bool,
modifiers: ModifiersState,
}
pub enum Consequence {
#[allow(dead_code)]
GlobalsChanged,
Nothing,
Xover(FlatNucl, FlatNucl),
Cut(FlatNucl),
CutCross(FlatNucl, FlatNucl),
FreeEnd(Option<FreeEnd>),
CutFreeEnd(FlatNucl, Option<FreeEnd>),
NewCandidate(Option<FlatNucl>),
RmStrand(FlatNucl),
RmHelix(FlatHelix),
FlipVisibility(FlatHelix, bool),
Built(Box<StrandBuilder>),
FlipGroup(FlatHelix),
FollowingSuggestion(FlatNucl, bool),
Centering(FlatNucl, bool),
DrawingSelection(PhysicalPosition<f64>, PhysicalPosition<f64>),
ReleasedSelection(Vec2, Vec2),
PasteRequest(Option<FlatNucl>),
AddClick(ClickResult, bool),
SelectionChanged,
ClearSelection,
DoubleClick(ClickResult),
}
impl Controller {
pub fn new(
view: ViewPtr,
data: DataPtr,
window_size: PhySize,
area_size: PhySize,
camera_top: CameraPtr,
camera_bottom: CameraPtr,
mediator: Arc<Mutex<Mediator>>,
splited: bool,
) -> Self {
Self {
view,
data,
window_size,
area_size,
camera_top,
camera_bottom,
state: RefCell::new(Box::new(NormalState {
mouse_position: PhysicalPosition::new(-1., -1.),
})),
splited,
action_mode: ActionMode::Normal,
mediator,
pasting: false,
modifiers: ModifiersState::empty(),
}
}
pub fn update_modifiers(&mut self, modifiers: ModifiersState) {
self.modifiers = modifiers;
}
pub fn resize(&mut self, window_size: PhySize, area_size: PhySize) {
self.area_size = area_size;
self.window_size = window_size;
self.update_globals();
}
pub fn set_splited(&mut self, splited: bool) {
self.splited = splited;
self.update_globals();
}
fn update_globals(&mut self) {
if self.splited {
self.camera_top.borrow_mut().resize(
self.area_size.width as f32,
self.area_size.height as f32 / 2.,
);
self.camera_bottom.borrow_mut().resize(
self.area_size.width as f32,
self.area_size.height as f32 / 2.,
);
} else {
self.camera_top
.borrow_mut()
.resize(self.area_size.width as f32, self.area_size.height as f32);
}
}
pub fn get_camera(&self, y: f64) -> CameraPtr {
if self.splited {
if y > self.area_size.height as f64 / 2. {
self.camera_bottom.clone()
} else {
self.camera_top.clone()
}
} else {
self.camera_top.clone()
}
}
pub fn fit(&mut self) {
let rectangle = self.data.borrow().get_fit_rectangle();
self.camera_top.borrow_mut().fit(rectangle);
self.camera_bottom.borrow_mut().fit(rectangle);
}
pub fn input(&mut self, event: &WindowEvent, position: PhysicalPosition<f64>) -> 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 {
self.state.borrow_mut().input(event, position, self)
};
if let Some(state) = transition.new_state {
println!("{}", state.display());
self.state.borrow().transition_from(&self);
self.state = RefCell::new(state);
self.state.borrow().transition_to(&self);
}
transition.consequences
}
pub fn set_pasting(&mut self, pasting: bool) {
let change = self.pasting != pasting;
self.pasting = pasting;
if change {
let transition = Transition {
new_state: Some(Box::new(NormalState {
mouse_position: PhysicalPosition::new(-1., -1.),
})),
consequences: Consequence::Nothing,
};
self.state.borrow().transition_from(&self);
self.state = RefCell::new(transition.new_state.unwrap());
self.state.borrow().transition_to(&self);
}
}
pub fn select_pivots(&mut self, translation_pivots: Vec<FlatNucl>, rotation_pivots: Vec<Vec2>) {
let transition = Transition {
new_state: Some(Box::new(ReleasedPivot {
translation_pivots,
rotation_pivots,
mouse_position: PhysicalPosition::new(-1., -1.),
})),
consequences: Consequence::Nothing,
};
self.state.borrow().transition_from(&self);
self.state = RefCell::new(transition.new_state.unwrap());
self.state.borrow().transition_to(&self);
}
pub fn set_action_mode(&mut self, action_mode: ActionMode) {
self.action_mode = action_mode;
}
pub fn process_keyboard(&self, event: &WindowEvent) {
if let WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
} = event
{
match *key {
// ZOOMING in and out is temporarilly disabled because of split view
/*
VirtualKeyCode::Up => {
self.camera.borrow_mut().zoom_in();
}
VirtualKeyCode::Down => {
self.camera.borrow_mut().zoom_out();
}
*/
VirtualKeyCode::J => {
self.data.borrow_mut().move_helix_backward();
}
VirtualKeyCode::K => {
self.data.borrow_mut().move_helix_forward();
}
VirtualKeyCode::Z if ctrl(&self.modifiers) => self.mediator.lock().unwrap().undo(),
VirtualKeyCode::R if ctrl(&self.modifiers) => self.mediator.lock().unwrap().redo(),
_ => (),
}
}
}
fn end_movement(&self) {
self.camera_top.borrow_mut().end_movement();
self.camera_bottom.borrow_mut().end_movement();
}
fn get_height(&self) -> u32 {
if self.splited {
self.area_size.height / 2
} else {
self.area_size.height
}
}
fn is_bottom(&self, y: f64) -> bool {
if self.splited {
y > self.area_size.height as f64 / 2.
} else {
false
}
}
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());
self.state.borrow().transition_from(&self);
self.state = RefCell::new(state);
self.state.borrow().transition_to(&self);
}
transition.consequences
}
}
fn ctrl(modifiers: &ModifiersState) -> bool {
if cfg!(target_os = "macos") {
modifiers.logo()
} else {
modifiers.ctrl()
}
}