flatscene.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 module handles the 2D view
use crate::design::{Design, DesignNotification, DesignNotificationContent, Nucl};
use crate::mediator;
use crate::{DrawArea, Duration, PhySize, WindowEvent};
use iced_wgpu::wgpu;
use iced_winit::winit;
use mediator::{
ActionMode, AppId, Application, CrossCut, Cut, Mediator, Notification, RawHelixCreation,
RmStrand, Selection, StrandConstruction, Xover,
};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, Mutex, RwLock};
use wgpu::{Device, Queue};
use winit::dpi::PhysicalPosition;
use crate::utils::camera2d as camera;
use crate::utils::PhantomElement;
mod controller;
mod data;
mod flattypes;
mod view;
use camera::{Camera, Globals};
use controller::Controller;
use data::Data;
use flattypes::*;
use std::time::Instant;
use view::View;
type ViewPtr = Rc<RefCell<View>>;
type DataPtr = Rc<RefCell<Data>>;
type CameraPtr = Rc<RefCell<Camera>>;
/// A Flatscene handles one design at a time
pub struct FlatScene {
/// Handle the data to send to the GPU
view: Vec<ViewPtr>,
/// Handle the data representing the design
data: Vec<DataPtr>,
/// Handle the inputs
controller: Vec<Controller>,
/// The area on which the flatscene is displayed
area: DrawArea,
/// The size of the window on which the flatscene is displayed
window_size: PhySize,
/// The identifer of the design being drawn
selected_design: usize,
device: Rc<Device>,
queue: Rc<Queue>,
mediator: Arc<Mutex<Mediator>>,
last_update: Instant,
splited: bool,
}
impl FlatScene {
pub fn new(
device: Rc<Device>,
queue: Rc<Queue>,
window_size: PhySize,
area: DrawArea,
mediator: Arc<Mutex<Mediator>>,
) -> Self {
Self {
view: Vec::new(),
data: Vec::new(),
controller: Vec::new(),
area,
window_size,
selected_design: 0,
device,
queue,
mediator,
last_update: Instant::now(),
splited: false,
}
}
/// Add a design to the scene. This creates a new `View`, a new `Data` and a new `Controller`
fn add_design(&mut self, design: Arc<RwLock<Design>>) {
let height = if self.splited {
self.area.size.height as f32 / 2.
} else {
self.area.size.height as f32
};
let globals_top = Globals {
resolution: [self.area.size.width as f32, height],
scroll_offset: [-1., -1.],
zoom: 80.,
_padding: 0.,
};
let globals_bottom = Globals {
resolution: [self.area.size.width as f32, height],
scroll_offset: [-1., -1.],
zoom: 80.,
_padding: 0.,
};
let camera_top = Rc::new(RefCell::new(Camera::new(globals_top, false)));
let camera_bottom = Rc::new(RefCell::new(Camera::new(globals_bottom, true)));
let view = Rc::new(RefCell::new(View::new(
self.device.clone(),
self.queue.clone(),
self.area,
camera_top.clone(),
camera_bottom.clone(),
self.splited,
)));
let data = Rc::new(RefCell::new(Data::new(view.clone(), design, 0)));
data.borrow_mut().perform_update();
let controller = Controller::new(
view.clone(),
data.clone(),
self.window_size,
self.area.size,
camera_top,
camera_bottom,
self.mediator.clone(),
self.splited,
);
if self.view.len() > 0 {
self.view[0] = view;
self.data[0] = data;
self.controller[0] = controller;
} else {
self.view.push(view);
self.data.push(data);
self.controller.push(controller);
}
}
/// Draw the view of the currently selected design
fn draw_view(&mut self, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView) {
if let Some(view) = self.view.get(self.selected_design) {
self.data[self.selected_design]
.borrow_mut()
.perform_update();
view.borrow_mut().draw(encoder, target, self.area);
}
}
/// This function must be called when the drawing area of the flatscene is modified
fn resize(&mut self, window_size: PhySize, area: DrawArea) {
self.window_size = window_size;
self.area = area;
for view in self.view.iter() {
view.borrow_mut().resize(area);
}
for controller in self.controller.iter_mut() {
controller.resize(window_size, area.size);
}
}
/// Change the action beign performed by the user
fn change_action_mode(&mut self, action_mode: ActionMode) {
if let Some(controller) = self.controller.get_mut(self.selected_design) {
controller.set_action_mode(action_mode)
}
}
/// Handle an input that happend while the cursor was on the flatscene drawing area
fn input(&mut self, event: &WindowEvent, cursor_position: PhysicalPosition<f64>) {
if let Some(controller) = self.controller.get_mut(self.selected_design) {
let consequence = controller.input(event, cursor_position);
self.read_consequence(consequence);
}
}
fn read_consequence(&mut self, consequence: controller::Consequence) {
use controller::Consequence;
match consequence {
Consequence::Xover(nucl1, nucl2) => {
let (prime5_id, prime3_id) =
self.data[self.selected_design].borrow().xover(nucl1, nucl2);
let strand_5prime = self.data[self.selected_design]
.borrow()
.get_strand(prime5_id)
.unwrap();
let strand_3prime = self.data[self.selected_design]
.borrow()
.get_strand(prime3_id)
.unwrap();
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(Xover {
strand_3prime,
strand_5prime,
prime3_id,
prime5_id,
undo: false,
design_id: self.selected_design,
}))
}
Consequence::Cut(nucl) => {
let strand_id = self.data[self.selected_design].borrow().get_strand_id(nucl);
if let Some(strand_id) = strand_id {
println!("cutting");
let strand = self.data[self.selected_design]
.borrow()
.get_strand(strand_id)
.unwrap();
let nucl = nucl.to_real();
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(Cut {
nucl,
strand_id,
strand,
undo: false,
design_id: self.selected_design,
}))
}
}
Consequence::FreeEnd(free_end) => self.data[self.selected_design]
.borrow_mut()
.set_free_end(free_end),
Consequence::CutFreeEnd(nucl, free_end) => {
let strand_id = self.data[self.selected_design].borrow().get_strand_id(nucl);
if let Some(strand_id) = strand_id {
println!("cutting");
let strand = self.data[self.selected_design]
.borrow()
.get_strand(strand_id)
.unwrap();
let nucl = nucl.to_real();
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(Cut {
nucl,
strand_id,
strand,
undo: false,
design_id: self.selected_design,
}))
}
self.data[self.selected_design]
.borrow_mut()
.set_free_end(free_end);
}
Consequence::CutCross(from, to) => {
if from.helix != to.helix {
// CrossCut with source and target on the same helix are forbidden
let op_var = self.data[self.selected_design].borrow().cut_cross(from, to);
if let Some((source_id, target_id, target_3prime)) = op_var {
let source_strand = self.data[self.selected_design]
.borrow()
.get_strand(source_id)
.unwrap();
let target_strand = self.data[self.selected_design]
.borrow()
.get_strand(target_id)
.unwrap();
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(CrossCut {
source_strand,
target_strand,
source_id,
target_id,
target_3prime,
nucl: to.to_real(),
undo: false,
design_id: self.selected_design,
}))
}
}
}
Consequence::NewCandidate(candidate) => {
let phantom = candidate.map(|n| PhantomElement {
position: n.position as i32,
helix_id: n.helix.real as u32,
forward: n.forward,
bound: false,
design_id: self.selected_design as u32,
});
let mut other = candidate.and_then(|candidate| {
self.data[self.selected_design]
.borrow()
.get_best_suggestion(candidate)
});
other = other.or(candidate.and_then(|n| {
self.data[self.selected_design]
.borrow()
.can_make_auto_xover(n)
}));
self.view[self.selected_design]
.borrow_mut()
.set_candidate_suggestion(candidate, other);
let candidate = if let Some(selection) = phantom.and_then(|p| {
self.data[self.selected_design]
.borrow()
.phantom_to_selection(p)
}) {
Some(selection)
} else {
phantom.map(|p| Selection::Phantom(p))
};
self.mediator.lock().unwrap().set_candidate(
phantom,
candidate.iter().cloned().collect(),
AppId::FlatScene,
)
}
Consequence::RmStrand(nucl) => {
let strand_id = self.data[self.selected_design].borrow().get_strand_id(nucl);
if let Some(strand_id) = strand_id {
println!("removing strand");
let strand = self.data[self.selected_design]
.borrow()
.get_strand(strand_id)
.unwrap();
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(RmStrand {
strand,
strand_id,
undo: false,
design_id: self.selected_design,
}))
}
}
Consequence::RmHelix(h_id) => {
let helix = self.data[self.selected_design]
.borrow_mut()
.can_delete_helix(h_id);
if let Some((helix, helix_id)) = helix {
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(RawHelixCreation {
helix,
helix_id,
design_id: self.selected_design,
delete: true,
}))
}
}
Consequence::Built(builder) => {
let color = builder.get_strand_color();
self.mediator
.lock()
.unwrap()
.update_opperation(Arc::new(StrandConstruction {
redo: Some(color),
color,
builder,
}));
}
Consequence::FlipVisibility(helix, apply_to_other) => self.data[self.selected_design]
.borrow_mut()
.flip_visibility(helix, apply_to_other),
Consequence::FlipGroup(helix) => self.data[self.selected_design]
.borrow_mut()
.flip_group(helix),
Consequence::FollowingSuggestion(nucl, double) => {
let nucl2 = self.data[self.selected_design]
.borrow()
.get_best_suggestion(nucl)
.or(self.data[self.selected_design]
.borrow()
.can_make_auto_xover(nucl));
if let Some(nucl2) = nucl2 {
self.attempt_xover(nucl, nucl2);
if double {
self.attempt_xover(nucl.prime3(), nucl2.prime5());
}
}
}
Consequence::Centering(nucl, bottom) => {
self.view[self.selected_design]
.borrow_mut()
.center_nucl(nucl, bottom);
let nucl = nucl.to_real();
self.mediator
.lock()
.unwrap()
.request_centering(nucl, self.selected_design)
}
Consequence::DrawingSelection(c1, c2) => self.view[self.selected_design]
.borrow_mut()
.update_rectangle(c1, c2),
Consequence::ReleasedSelection(_, _) => {
self.view[self.selected_design]
.borrow_mut()
.clear_rectangle();
//self.data[self.selected_design].borrow().get_helices_in_rect(c1, c2, camera);
self.mediator.lock().unwrap().notify_multiple_selection(
self.data[self.selected_design].borrow().selection.clone(),
AppId::FlatScene,
);
}
Consequence::PasteRequest(nucl) => {
self.mediator
.lock()
.unwrap()
.attempt_paste(nucl.map(|n| n.to_real()));
}
Consequence::AddClick(click, add) => {
self.data[self.selected_design]
.borrow_mut()
.add_selection(click, add);
self.mediator.lock().unwrap().notify_multiple_selection(
self.data[self.selected_design].borrow().selection.clone(),
AppId::FlatScene,
);
}
Consequence::SelectionChanged => {
self.mediator.lock().unwrap().notify_multiple_selection(
self.data[self.selected_design].borrow().selection.clone(),
AppId::FlatScene,
);
}
Consequence::ClearSelection => {
self.data[self.selected_design]
.borrow_mut()
.set_selection(vec![]);
self.mediator.lock().unwrap().notify_multiple_selection(
self.data[self.selected_design].borrow().selection.clone(),
AppId::FlatScene,
);
}
Consequence::DoubleClick(click) => {
let selection = self.data[self.selected_design]
.borrow()
.double_click_to_selection(click);
if let Some(selection) = selection {
self.mediator
.lock()
.unwrap()
.request_center_selection(selection, AppId::FlatScene)
}
}
_ => (),
}
}
fn check_timers(&mut self) {
let consequence = self.controller[self.selected_design].check_timers();
self.read_consequence(consequence);
}
fn attempt_xover(&self, nucl1: FlatNucl, nucl2: FlatNucl) {
let source = nucl1.to_real();
let target = nucl2.to_real();
self.mediator
.lock()
.unwrap()
.xover_request(source, target, self.selected_design);
}
/// Ask the view if it has been modified since the last drawing
fn needs_redraw_(&mut self) -> bool {
self.check_timers();
if let Some(view) = self.view.get(self.selected_design) {
self.data[self.selected_design]
.borrow_mut()
.perform_update();
view.borrow().needs_redraw()
} else {
false
}
}
fn toggle_split(&mut self) {
self.splited ^= true;
for v in self.view.iter_mut() {
v.borrow_mut().set_splited(self.splited);
}
for c in self.controller.iter_mut() {
c.set_splited(self.splited);
}
}
fn split_and_center(&mut self, n1: FlatNucl, n2: FlatNucl) {
self.splited = true;
for v in self.view.iter_mut() {
v.borrow_mut().set_splited(self.splited);
}
for c in self.controller.iter_mut() {
c.set_splited(self.splited);
}
self.view[self.selected_design]
.borrow_mut()
.center_split(n1, n2);
}
}
impl Application for FlatScene {
fn on_notify(&mut self, notification: Notification) {
match notification {
Notification::NewDesign(design) => self.add_design(design),
Notification::NewActionMode(am) => self.change_action_mode(am),
Notification::DesignNotification(DesignNotification { design_id, content }) => {
self.data[design_id].borrow_mut().notify_update();
if let DesignNotificationContent::ViewNeedReset = content {
self.data[design_id].borrow_mut().notify_reset();
}
}
Notification::FitRequest => self.controller[self.selected_design].fit(),
Notification::Selection3D(selection, app_id) => match app_id {
AppId::FlatScene => (),
_ => {
self.needs_redraw(Duration::from_nanos(1));
self.data[self.selected_design]
.borrow_mut()
.set_selection(selection);
self.data[self.selected_design].borrow_mut().notify_update();
let pivots = self.data[self.selected_design]
.borrow_mut()
.get_pivot_of_selected_helices(
&self.controller[self.selected_design].get_camera(0f64),
);
if let Some((translation_pivots, rotation_pivots)) = pivots {
self.controller[self.selected_design]
.select_pivots(translation_pivots, rotation_pivots);
}
}
},
Notification::Save(d_id) => self.data[d_id].borrow_mut().save_isometry(),
Notification::ToggleText(b) => {
self.view[self.selected_design].borrow_mut().set_show_sec(b)
}
Notification::ShowTorsion(b) => {
for v in self.view.iter() {
v.borrow_mut().set_show_torsion(b);
}
}
Notification::Pasting(b) => {
for c in self.controller.iter_mut() {
c.set_pasting(b)
}
}
Notification::CameraTarget(_) => (),
Notification::NewSelectionMode(selection_mode) => {
for data in self.data.iter() {
data.borrow_mut().change_selection_mode(selection_mode);
}
}
Notification::AppNotification(_) => (),
Notification::NewSensitivity(_) => (),
Notification::ClearDesigns => (),
Notification::NewCandidate(candidates, app_id) => match app_id {
AppId::FlatScene => (),
_ => self.data[self.selected_design]
.borrow_mut()
.set_candidate(candidates),
},
Notification::Centering(_, _) => (),
Notification::CenterSelection(selection, app_id) => {
if app_id != AppId::FlatScene {
self.data[self.selected_design]
.borrow_mut()
.set_selection(vec![selection]);
let xover = self.view[self.selected_design]
.borrow_mut()
.center_selection();
if let Some((n1, n2)) = xover {
self.split_and_center(n1, n2);
}
}
}
Notification::CameraRotation(_, _, _) => (),
Notification::ModifersChanged(modifiers) => {
for c in self.controller.iter_mut() {
c.update_modifiers(modifiers.clone())
}
}
Notification::Split2d => self.toggle_split(),
Notification::Redim2dHelices(b) => self.data[self.selected_design]
.borrow_mut()
.redim_helices(b),
Notification::ToggleWidget(_) => (),
Notification::RenderingMode(_) => (),
Notification::Background3D(_) => (),
}
}
fn on_resize(&mut self, window_size: PhySize, area: DrawArea) {
self.resize(window_size, area)
}
fn on_event(&mut self, event: &WindowEvent, cursor_position: PhysicalPosition<f64>) {
self.input(event, cursor_position)
}
fn on_redraw_request(
&mut self,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
_dt: Duration,
) {
//println!("draw flatscene");
self.draw_view(encoder, target)
}
fn needs_redraw(&mut self, _: Duration) -> bool {
let now = Instant::now();
if (now - self.last_update).as_millis() < 25 {
false
} else {
self.last_update = now;
self.needs_redraw_()
}
}
}