Raw File
left_panel.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 ensnano_organizer::{Organizer, OrganizerMessage, OrganizerTree};
use std::sync::{Arc, Mutex};

use iced::{
    button, pick_list, slider, text_input, Button, Checkbox, Color, Command, Element, Length,
    PickList, Scrollable, Slider, Text, TextInput,
};
use iced::{container, Background, Column, Container, Image, Row};
use iced_aw::{TabLabel, Tabs};
use iced_native::{clipboard::Null as NullClipboard, Program};
use iced_wgpu::{Backend, Renderer};
use iced_winit::winit::{
    dpi::{LogicalPosition, LogicalSize},
    event::ModifiersState,
};
use ultraviolet::Vec3;

use color_space::{Hsv, Rgb};

use crate::design::{DnaElement, DnaElementKey, ScaffoldInfo};
use crate::mediator::{ActionMode, Selection, SelectionMode};

use super::{
    icon_btn, slider_style::DesactivatedSlider, text_btn, ApplicationState, FogParameters as Fog,
    GridTypeDescr, OverlayType, Requests, UiSize,
};
mod color_picker;
use color_picker::ColorPicker;
mod sequence_input;
use sequence_input::SequenceInput;
use text_input_style::BadValue;
mod discrete_value;
use discrete_value::{FactoryId, RequestFactory, Requestable, ValueId};
mod tabs;
use crate::consts::*;
mod contextual_panel;
use contextual_panel::ContextualPanel;

use material_icons::{icon_to_char, Icon as MaterialIcon, FONT as MATERIALFONT};
use std::collections::BTreeMap;
use std::thread;
use tabs::{
    CameraShortcut, CameraTab, EditionTab, GridTab, ParametersTab, SequenceTab, SimulationTab,
};

const ICONFONT: iced::Font = iced::Font::External {
    name: "IconFont",
    bytes: MATERIALFONT,
};

pub(super) const ENSNANO_FONT: iced::Font = iced::Font::External {
    name: "EnsNanoFont",
    bytes: include_bytes!("../../font/ensnano.ttf"),
};

fn icon(icon: MaterialIcon, ui_size: &UiSize) -> iced::Text {
    iced::Text::new(format!("{}", icon_to_char(icon)))
        .font(ICONFONT)
        .size(ui_size.icon())
}

const CHECKBOXSPACING: u16 = 5;

pub struct LeftPanel {
    dialoging: Arc<Mutex<bool>>,
    logical_size: LogicalSize<f64>,
    #[allow(dead_code)]
    logical_position: LogicalPosition<f64>,
    #[allow(dead_code)]
    open_color: button::State,
    sequence_input: SequenceInput,
    requests: Arc<Mutex<Requests>>,
    #[allow(dead_code)]
    show_torsion: bool,
    selected_tab: usize,
    organizer: Organizer<DnaElement>,
    ui_size: UiSize,
    grid_tab: GridTab,
    edition_tab: EditionTab,
    camera_tab: CameraTab,
    simulation_tab: SimulationTab,
    sequence_tab: SequenceTab,
    parameters_tab: ParametersTab,
    contextual_panel: ContextualPanel,
    camera_shortcut: CameraShortcut,
    application_state: ApplicationState,
}

#[derive(Debug, Clone)]
pub enum Message {
    SelectionModeChanged(SelectionMode),
    Resized(LogicalSize<f64>, LogicalPosition<f64>),
    #[allow(dead_code)]
    OpenColor,
    MakeGrids,
    ActionModeChanged(ActionMode),
    SequenceChanged(String),
    SequenceFileRequested,
    StrandColorChanged(Color),
    HueChanged(f32),
    NewGrid(GridTypeDescr),
    FixPoint(Vec3, Vec3),
    RotateCam(f32, f32, f32),
    PositionHelicesChanged(String),
    LengthHelicesChanged(String),
    ScaffoldPositionInput(String),
    #[allow(dead_code)]
    ShowTorsion(bool),
    FogRadius(f32),
    FogLength(f32),
    SimRequest,
    NewDesign,
    DescreteValue {
        factory_id: FactoryId,
        value_id: ValueId,
        value: f32,
    },
    HelixRoll(f32),
    NewHyperboloid,
    FinalizeHyperboloid,
    RollTargeted(bool),
    RigidGridSimulation(bool),
    RigidHelicesSimulation(bool),
    VolumeExclusion(bool),
    TabSelected(usize),
    NewDnaElement(Vec<DnaElement>),
    NewSelection(Vec<DnaElementKey>),
    OrganizerMessage(OrganizerMessage<DnaElement>),
    Selection(Selection, Vec<String>),
    ModifiersChanged(ModifiersState),
    NewTreeApp(OrganizerTree<DnaElementKey>),
    UiSizeChanged(UiSize),
    UiSizePicked(UiSize),
    ScaffoldSequenceFile,
    StapplesRequested,
    CustomScaffoldRequested,
    DeffaultScaffoldRequested,
    ToggleText(bool),
    #[allow(dead_code)]
    CleanRequested,
    AddDoubleStrandHelix(bool),
    ToggleVisibility(bool),
    AllVisible,
    Redim2dHelices(bool),
    InvertScroll(bool),
    BrownianMotion(bool),
    Nothing,
    CancelHyperboloid,
    CanMakeGrid(bool),
    SelectionValueChanged(usize, String),
    SetSmallSpheres(bool),
    ScaffoldIdSet(usize, bool),
    NewScaffoldInfo(Option<ScaffoldInfo>),
    SelectScaffold,
    ForceHelp,
    ShowTutorial,
    RenderingMode(crate::mediator::RenderingMode),
    Background3D(crate::mediator::Background3D),
    OpenLink(&'static str),
    NewApplicationState(ApplicationState),
    FogChoice(tabs::FogChoice),
}

impl LeftPanel {
    pub fn new(
        requests: Arc<Mutex<Requests>>,
        logical_size: LogicalSize<f64>,
        logical_position: LogicalPosition<f64>,
        first_time: bool,
        dialoging: Arc<Mutex<bool>>,
    ) -> Self {
        let selected_tab = if first_time { 0 } else { 5 };
        let mut organizer = Organizer::new();
        organizer.set_width(logical_size.width as u16);
        Self {
            logical_size,
            logical_position,
            open_color: Default::default(),
            sequence_input: SequenceInput::new(),
            requests,
            show_torsion: false,
            selected_tab,
            organizer,
            ui_size: Default::default(),
            grid_tab: GridTab::new(),
            edition_tab: EditionTab::new(),
            camera_tab: CameraTab::new(),
            simulation_tab: SimulationTab::new(),
            sequence_tab: SequenceTab::new(),
            parameters_tab: ParametersTab::new(),
            dialoging,
            contextual_panel: ContextualPanel::new(logical_size.width as u32),
            camera_shortcut: CameraShortcut::new(),
            application_state: Default::default(),
        }
    }

    pub fn resize(
        &mut self,
        logical_size: LogicalSize<f64>,
        logical_position: LogicalPosition<f64>,
    ) {
        self.logical_size = logical_size;
        self.logical_position = logical_position;
        self.contextual_panel.new_width(logical_size.width as u32);
        self.organizer.set_width(logical_size.width as u16);
    }

    fn organizer_message(&mut self, m: OrganizerMessage<DnaElement>) -> Option<Message> {
        match m {
            OrganizerMessage::InternalMessage(m) => {
                return self
                    .organizer
                    .message(&m)
                    .map(|m_| Message::OrganizerMessage(m_))
            }
            OrganizerMessage::Selection(s) => {
                self.requests.lock().unwrap().organizer_selection = Some(s)
            }
            OrganizerMessage::NewAttribute(a, keys) => {
                self.requests.lock().unwrap().new_attribute = Some((a, keys.into_iter().collect()))
            }
            OrganizerMessage::NewTree(tree) => self.requests.lock().unwrap().new_tree = Some(tree),
            OrganizerMessage::Candidates(candidates) => {
                self.requests.lock().unwrap().organizer_candidates = Some(candidates)
            }
            _ => (),
        }
        None
    }

    pub fn has_keyboard_priority(&self) -> bool {
        self.sequence_input.has_keyboard_priority()
            || self.grid_tab.has_keyboard_priority()
            || self.organizer.has_keyboard_priority()
            || self.sequence_tab.has_keyboard_priority()
    }
}

impl Program for LeftPanel {
    type Renderer = Renderer;
    type Message = Message;
    type Clipboard = NullClipboard;

    fn update(&mut self, message: Message, _cb: &mut NullClipboard) -> Command<Message> {
        match message {
            Message::SelectionModeChanged(selection_mode) => {
                if selection_mode != self.application_state.selection_mode {
                    self.requests.lock().unwrap().selection_mode = Some(selection_mode);
                }
            }
            Message::ActionModeChanged(action_mode) => {
                if self.application_state.action_mode != action_mode {
                    self.requests.lock().unwrap().action_mode = Some(action_mode)
                } else {
                    match action_mode {
                        ActionMode::Rotate | ActionMode::Translate => {
                            self.requests.lock().unwrap().toggle_widget = true;
                        }
                        _ => (),
                    }
                }
            }
            Message::SequenceChanged(s) => {
                self.requests.lock().unwrap().sequence_change = Some(s.clone());
                self.sequence_input.update_sequence(s);
            }
            Message::SequenceFileRequested => {
                let dialog = rfd::AsyncFileDialog::new().pick_file();
                let requests = self.requests.clone();
                std::thread::spawn(move || {
                    let save_op = async move {
                        let file = dialog.await;
                        if let Some(handle) = file {
                            let content = std::fs::read_to_string(handle.path());
                            if let Ok(content) = content {
                                requests.lock().unwrap().sequence_input = Some(content);
                            }
                        }
                    };
                    futures::executor::block_on(save_op);
                });
            }
            Message::OpenColor => {
                self.requests.lock().unwrap().overlay_opened = Some(OverlayType::Color)
            }
            Message::StrandColorChanged(color) => {
                let color_request = &mut self.requests.lock().unwrap().strand_color_change;
                self.edition_tab.strand_color_change(color, color_request);
            }
            Message::HueChanged(x) => self.edition_tab.change_hue(x),
            Message::Resized(size, position) => self.resize(size, position),
            Message::NewGrid(grid_type) => {
                self.requests.lock().unwrap().new_grid = Some(grid_type);
                let action_mode = self.grid_tab.get_build_helix_mode();
                self.requests.lock().unwrap().action_mode = Some(action_mode);
            }
            Message::RotateCam(xz, yz, xy) => {
                self.camera_shortcut
                    .set_angles(xz as isize, yz as isize, xy as isize);
                self.requests.lock().unwrap().camera_rotation = Some((xz, yz, xy));
            }
            Message::FixPoint(point, up) => {
                self.requests.lock().unwrap().camera_target = Some((point, up));
                self.camera_shortcut.reset_angles();
            }
            Message::LengthHelicesChanged(length_str) => {
                let action_mode = self.grid_tab.update_length_str(length_str.clone());
                if self.application_state.action_mode != action_mode {
                    self.requests.lock().unwrap().action_mode = Some(action_mode)
                }
            }
            Message::PositionHelicesChanged(position_str) => {
                let action_mode = self.grid_tab.update_pos_str(position_str.clone());
                if self.application_state.action_mode != action_mode {
                    self.requests.lock().unwrap().action_mode = Some(action_mode)
                }
            }
            Message::ScaffoldPositionInput(position_str) => {
                if let Some(n) = self.sequence_tab.update_pos_str(position_str) {
                    self.requests.lock().unwrap().scaffold_shift = Some(n);
                }
            }
            Message::ShowTorsion(b) => {
                self.requests.lock().unwrap().show_torsion_request = Some(b);
                self.show_torsion = b;
            }
            Message::FogLength(length) => {
                self.camera_tab.fog_length(length);
                let request = self.camera_tab.get_fog_request();
                self.requests.lock().unwrap().fog = Some(request);
            }
            Message::FogRadius(radius) => {
                self.camera_tab.fog_radius(radius);
                let request = self.camera_tab.get_fog_request();
                self.requests.lock().unwrap().fog = Some(request);
            }
            Message::NewDesign => {
                self.show_torsion = false;
                self.camera_tab.notify_new_design();
                self.edition_tab.notify_new_design();
                self.grid_tab.notify_new_design();
                self.organizer.reset();
            }
            Message::SimRequest => {
                let request = self.simulation_tab.get_physical_simulation_request();
                self.requests.lock().unwrap().roll_request = Some(request);
            }
            Message::FogChoice(choice) => {
                let (visble, from_camera) = choice.to_param();
                self.camera_tab.fog_camera(from_camera);
                self.camera_tab.fog_visible(visble);
                let request = self.camera_tab.get_fog_request();
                self.requests.lock().unwrap().fog = Some(request);
            }
            Message::DescreteValue {
                factory_id,
                value_id,
                value,
            } => match factory_id {
                FactoryId::Scroll => {
                    let request = &mut self.requests.lock().unwrap().scroll_sensitivity;
                    self.parameters_tab
                        .update_scroll_request(value_id, value, request);
                }
                FactoryId::HelixRoll => {
                    let request = &mut self.requests.lock().unwrap().helix_roll;
                    self.edition_tab
                        .update_roll_request(value_id, value, request);
                }
                FactoryId::Hyperboloid => {
                    let request = &mut self.requests.lock().unwrap().hyperboloid_update;
                    self.grid_tab
                        .update_hyperboloid_request(value_id, value, request);
                }
                FactoryId::RigidBody => {
                    let request = &mut self.requests.lock().unwrap().rigid_body_parameters;
                    self.simulation_tab.update_request(value_id, value, request);
                }
                FactoryId::Brownian => {
                    let request = &mut self.requests.lock().unwrap().rigid_body_parameters;
                    self.simulation_tab
                        .update_brownian(value_id, value, request);
                }
            },
            Message::VolumeExclusion(b) => {
                self.simulation_tab.set_volume_exclusion(b);
                let request = &mut self.requests.lock().unwrap().rigid_body_parameters;
                self.simulation_tab.make_rigid_body_request(request);
            }
            Message::BrownianMotion(b) => {
                self.simulation_tab.set_brownian_motion(b);
                let request = &mut self.requests.lock().unwrap().rigid_body_parameters;
                self.simulation_tab.make_rigid_body_request(request);
            }
            Message::HelixRoll(roll) => {
                self.edition_tab.update_roll(roll);
            }
            Message::NewHyperboloid => {
                let request = &mut self.requests.lock().unwrap().new_hyperboloid;
                self.grid_tab.new_hyperboloid(request);
            }
            Message::FinalizeHyperboloid => {
                self.requests.lock().unwrap().finalize_hyperboloid = true;
                self.grid_tab.finalize_hyperboloid();
            }
            Message::RigidGridSimulation(_) => {
                let request = &mut self.requests.lock().unwrap().rigid_grid_simulation;
                self.simulation_tab.make_rigid_body_request(request);
            }
            Message::RigidHelicesSimulation(_) => {
                let request = &mut self.requests.lock().unwrap().rigid_helices_simulation;
                self.simulation_tab.make_rigid_body_request(request);
            }
            Message::MakeGrids => self.requests.lock().unwrap().make_grids = true,
            Message::RollTargeted(b) => {
                if b {
                    let simulation_request = self.edition_tab.get_roll_request();
                    self.requests.lock().unwrap().roll_request = simulation_request;
                } else {
                    self.requests.lock().unwrap().stop_roll = true;
                }
            }
            Message::TabSelected(n) => {
                if let ActionMode::BuildHelix { .. } = self.application_state.action_mode {
                    if n != 0 {
                        let action_mode = ActionMode::Normal;
                        self.requests.lock().unwrap().action_mode = Some(action_mode);
                    }
                }
                if n != 0 {
                    if self.grid_tab.is_building_hyperboloid() {
                        self.requests.lock().unwrap().finalize_hyperboloid = true;
                        self.grid_tab.finalize_hyperboloid();
                    }
                }
                if n == 0 {
                    let action_mode = self.grid_tab.get_build_helix_mode();
                    self.requests.lock().unwrap().action_mode = Some(action_mode);
                }
                if self.selected_tab == 3 && n != 3 {
                    println!("leaving simulation tab");
                    self.simulation_tab
                        .leave_tab(self.requests.clone(), &self.application_state);
                }
                self.selected_tab = n;
            }
            Message::NewDnaElement(elements) => self.organizer.update_elements(elements),
            Message::OrganizerMessage(m) => {
                let next_message = self.organizer_message(m);
                if let Some(message) = next_message {
                    self.update(message, _cb);
                }
            }
            Message::ModifiersChanged(modifiers) => self
                .organizer
                .new_modifiers(iced_winit::conversion::modifiers(modifiers)),
            Message::NewSelection(keys) => {
                self.edition_tab.update_selection(&keys);
                self.sequence_tab.update_selection(&keys);
                self.organizer.notify_selection(keys);
            }
            Message::CanMakeGrid(b) => {
                self.grid_tab.can_make_grid = b;
            }
            Message::NewTreeApp(tree) => self.organizer.read_tree(tree),
            Message::UiSizePicked(ui_size) => {
                self.requests.lock().unwrap().new_ui_size = Some(ui_size)
            }
            Message::UiSizeChanged(ui_size) => self.ui_size = ui_size,
            Message::DeffaultScaffoldRequested => {
                let sequence = include_str!("p7249-Tilibit.txt");
                self.requests.lock().unwrap().scaffold_sequence =
                    Some((sequence.to_string(), self.sequence_tab.get_scaffold_pos()))
            }
            Message::CustomScaffoldRequested => {
                *self.dialoging.lock().unwrap() = true;
                let requests = self.requests.clone();
                let dialog = rfd::AsyncFileDialog::new().pick_file();
                let dialoging = self.dialoging.clone();
                let scaffold_shift = self.sequence_tab.get_scaffold_pos();
                thread::spawn(move || {
                    let save_op = async move {
                        let file = dialog.await;
                        if let Some(handle) = file {
                            let mut content = std::fs::read_to_string(handle.path()).unwrap();
                            content.make_ascii_uppercase();
                            if let Some(n) = content.find(|c: char| {
                                c != 'A' && c != 'T' && c != 'G' && c != 'C' && !c.is_whitespace()
                            }) {
                                let msg = format!(
                                    "This text file does not contain a valid DNA sequence.\n
                                        First invalid char at position {}",
                                    n
                                );
                                crate::utils::message(msg.into(), rfd::MessageLevel::Error);
                            } else {
                                requests.lock().unwrap().scaffold_sequence =
                                    Some((content, scaffold_shift))
                            }
                        }
                        *dialoging.lock().unwrap() = false;
                    };
                    futures::executor::block_on(save_op);
                });
            }
            Message::ScaffoldSequenceFile => {
                use_default_scaffold(self.requests.clone());
            }
            Message::StapplesRequested => self.requests.lock().unwrap().stapples_request = true,
            Message::ToggleText(b) => {
                self.requests.lock().unwrap().toggle_text = Some(b);
                self.sequence_tab.toggle_text_value(b);
            }
            Message::CleanRequested => self.requests.lock().unwrap().clean_requests = true,
            Message::AddDoubleStrandHelix(b) => {
                self.grid_tab.set_show_strand(b);
                if let ActionMode::BuildHelix { .. } = self.application_state.action_mode {
                    let action_mode = self.grid_tab.get_build_helix_mode();
                    self.requests.lock().unwrap().action_mode = Some(action_mode);
                }
            }
            Message::ToggleVisibility(b) => {
                self.requests.lock().unwrap().toggle_visibility = Some(b)
            }
            Message::AllVisible => self.requests.lock().unwrap().all_visible = true,
            Message::Redim2dHelices(b) => self.requests.lock().unwrap().redim_2d_helices = Some(b),
            Message::InvertScroll(b) => {
                self.requests.lock().unwrap().invert_scroll = Some(b);
                self.parameters_tab.invert_y_scroll = b;
            }
            Message::CancelHyperboloid => {
                self.grid_tab.finalize_hyperboloid();
                self.requests.lock().unwrap().cancel_hyperboloid = true;
            }
            Message::SelectionValueChanged(n, s) => {
                self.contextual_panel
                    .selection_value_changed(n, s, self.requests.clone());
            }
            Message::SetSmallSpheres(b) => {
                self.contextual_panel
                    .set_small_sphere(b, self.requests.clone());
            }
            Message::ScaffoldIdSet(n, b) => {
                self.contextual_panel
                    .scaffold_id_set(n, b, self.requests.clone());
            }
            Message::Selection(selection, info_values) => self
                .contextual_panel
                .update_selection(selection, info_values),
            Message::NewScaffoldInfo(info) => self.sequence_tab.set_scaffold_info(info),
            Message::SelectScaffold => self.requests.lock().unwrap().select_scaffold = Some(()),
            Message::RenderingMode(mode) => {
                self.requests.lock().unwrap().rendering_mode = Some(mode.clone());
                self.camera_tab.rendering_mode = mode;
            }
            Message::Background3D(bg) => {
                self.requests.lock().unwrap().background3d = Some(bg.clone());
                self.camera_tab.background3d = bg;
            }
            Message::ForceHelp => {
                self.contextual_panel.force_help = true;
                self.contextual_panel.show_tutorial = false;
            }
            Message::ShowTutorial => {
                self.contextual_panel.show_tutorial ^= true;
                self.contextual_panel.force_help = false;
            }
            Message::OpenLink(link) => {
                // ATM we continue even in case of error, later any error will be promted to user
                let _ = open::that(link);
            }
            Message::NewApplicationState(state) => {
                self.application_state = state;
            }
            Message::Nothing => (),
        };
        Command::none()
    }

    fn view(&mut self) -> Element<Message> {
        let width = self.logical_size.cast::<u16>().width;
        let tabs: Tabs<Message, Backend> = Tabs::new(self.selected_tab, Message::TabSelected)
            .push(
                TabLabel::Text(format!("{}", icon_to_char(MaterialIcon::GridOn))),
                self.grid_tab
                    .view(self.ui_size.clone(), width, &self.application_state),
            )
            .push(
                TabLabel::Text(format!("{}", icon_to_char(MaterialIcon::Edit))),
                self.edition_tab
                    .view(self.ui_size.clone(), width, &self.application_state),
            )
            .push(
                TabLabel::Text(format!("{}", icon_to_char(MaterialIcon::Videocam))),
                self.camera_tab.view(self.ui_size.clone()),
            )
            .push(
                TabLabel::Icon(ICON_PHYSICAL_ENGINE),
                self.simulation_tab
                    .view(self.ui_size.clone(), &self.application_state),
            )
            .push(
                TabLabel::Icon(ICON_ATGC),
                self.sequence_tab.view(self.ui_size.clone()),
            )
            .push(
                TabLabel::Text(format!("{}", icon_to_char(MaterialIcon::Settings))),
                self.parameters_tab
                    .view(self.ui_size.clone(), &self.application_state),
            )
            .text_size(self.ui_size.icon())
            .text_font(ICONFONT)
            .icon_font(ENSNANO_FONT)
            .icon_size(self.ui_size.icon())
            .tab_bar_height(Length::Units(self.ui_size.button()))
            .tab_bar_style(TabStyle)
            .width(Length::Units(width))
            .height(Length::Fill);
        let camera_shortcut = self.camera_shortcut.view(self.ui_size.clone(), width);
        let contextual_menu = self.contextual_panel.view(self.ui_size.clone());
        let organizer = self.organizer.view().map(|m| Message::OrganizerMessage(m));

        Container::new(
            Column::new()
                .width(Length::Fill)
                .push(Container::new(tabs).height(Length::FillPortion(2)))
                .push(iced::Rule::horizontal(5))
                .push(Container::new(camera_shortcut).height(Length::FillPortion(1)))
                .push(iced::Rule::horizontal(5))
                .push(Container::new(contextual_menu).height(Length::FillPortion(1)))
                .push(iced::Rule::horizontal(5))
                .push(Container::new(organizer).height(Length::FillPortion(2)))
                .padding(3),
        )
        .style(TopBarStyle)
        .height(Length::Units(self.logical_size.height as u16))
        .into()
    }
}

struct TopBarStyle;
impl container::StyleSheet for TopBarStyle {
    fn style(&self) -> container::Style {
        container::Style {
            background: Some(Background::Color(BACKGROUND)),
            text_color: Some(Color::WHITE),
            ..container::Style::default()
        }
    }
}

pub const BACKGROUND: Color = Color::from_rgb(
    0x23 as f32 / 255.0,
    0x27 as f32 / 255.0,
    0x2A as f32 / 255.0,
);

pub struct ColorOverlay {
    logical_size: LogicalSize<f64>,
    color_picker: ColorPicker,
    close_button: iced::button::State,
    requests: Arc<Mutex<Requests>>,
}

impl ColorOverlay {
    pub fn new(requests: Arc<Mutex<Requests>>, logical_size: LogicalSize<f64>) -> Self {
        Self {
            logical_size,
            close_button: Default::default(),
            color_picker: ColorPicker::new(),
            requests,
        }
    }

    pub fn resize(&mut self, logical_size: LogicalSize<f64>) {
        self.logical_size = logical_size;
    }
}

#[derive(Debug, Clone)]
pub enum ColorMessage {
    StrandColorChanged(Color),
    HueChanged(f32),
    #[allow(dead_code)]
    Resized(LogicalSize<f64>),
    Closed,
}

impl Program for ColorOverlay {
    type Renderer = Renderer;
    type Message = ColorMessage;
    type Clipboard = NullClipboard;

    fn update(&mut self, message: ColorMessage, _cb: &mut NullClipboard) -> Command<ColorMessage> {
        match message {
            ColorMessage::StrandColorChanged(color) => {
                let red = ((color.r * 255.) as u32) << 16;
                let green = ((color.g * 255.) as u32) << 8;
                let blue = (color.b * 255.) as u32;
                self.color_picker.update_color(color);
                let hue = Hsv::from(Rgb::new(
                    color.r as f64 * 255.,
                    color.g as f64 * 255.,
                    color.b as f64 * 255.,
                ))
                .h;
                self.color_picker.change_hue(hue as f32);
                let color = red + green + blue;
                self.requests.lock().unwrap().strand_color_change = Some(color);
            }
            ColorMessage::HueChanged(x) => self.color_picker.change_hue(x),
            ColorMessage::Closed => {
                self.requests.lock().unwrap().overlay_closed = Some(OverlayType::Color)
            }
            ColorMessage::Resized(size) => self.resize(size),
        };
        Command::none()
    }

    fn view(&mut self) -> Element<ColorMessage> {
        let width = self.logical_size.cast::<u16>().width;

        let widget = Column::new()
            .width(Length::Units(width))
            .height(Length::Fill)
            .spacing(5)
            .push(self.color_picker.new_view())
            .spacing(5)
            .push(
                Button::new(&mut self.close_button, Text::new("Close"))
                    .on_press(ColorMessage::Closed),
            );

        Container::new(widget)
            .style(FloatingStyle)
            .height(Length::Fill)
            .into()
    }
}

struct FloatingStyle;
impl container::StyleSheet for FloatingStyle {
    fn style(&self) -> container::Style {
        container::Style {
            background: Some(Background::Color(BACKGROUND)),
            text_color: Some(Color::WHITE),
            border_width: 3_f32,
            border_radius: 3_f32,
            border_color: Color::BLACK,
            ..container::Style::default()
        }
    }
}

struct ButtonStyle(bool);

impl iced_wgpu::button::StyleSheet for ButtonStyle {
    fn active(&self) -> iced_wgpu::button::Style {
        iced_wgpu::button::Style {
            border_width: if self.0 { 3_f32 } else { 1_f32 },
            border_radius: if self.0 { 3_f32 } else { 2_f32 },
            border_color: if self.0 {
                Color::BLACK
            } else {
                [0.7, 0.7, 0.7].into()
            },
            background: Some(Background::Color([0.87, 0.87, 0.87].into())),
            //background: Some(Background::Color(BACKGROUND)),
            ..Default::default()
        }
    }
}

struct ButtonColor(iced::Color);

impl ButtonColor {
    fn red_green(active: bool) -> Self {
        if active {
            Self(iced::Color::from_rgb(1., 0., 0.))
        } else {
            Self(iced::Color::from_rgb(0., 1., 0.))
        }
    }
}

impl iced_wgpu::button::StyleSheet for ButtonColor {
    fn active(&self) -> iced_wgpu::button::Style {
        iced_wgpu::button::Style {
            background: Some(Background::Color(self.0)),
            //background: Some(Background::Color(BACKGROUND)),
            border_radius: 2.0,
            border_width: 1.0,
            border_color: [0.7, 0.7, 0.7].into(),
            text_color: Color::BLACK,
            ..Default::default()
        }
    }

    fn hovered(&self) -> iced_wgpu::button::Style {
        let active = self.active();
        iced_wgpu::button::Style {
            background: active.background.map(|background| match background {
                Background::Color(color) => Background::Color(Color {
                    a: color.a * 0.75,
                    ..color
                }),
            }),
            ..active
        }
    }
}

#[derive(Default, Debug, Clone)]
struct SelectionModeState {
    pub nucleotide: button::State,
    pub strand: button::State,
    pub helix: button::State,
    pub grid: button::State,
}

impl SelectionModeState {
    fn get_states<'a>(&'a mut self) -> BTreeMap<SelectionMode, &'a mut button::State> {
        let mut ret = BTreeMap::new();
        ret.insert(SelectionMode::Nucleotide, &mut self.nucleotide);
        ret.insert(SelectionMode::Strand, &mut self.strand);
        ret.insert(SelectionMode::Helix, &mut self.helix);
        ret.insert(SelectionMode::Grid, &mut self.grid);
        ret
    }
}

#[derive(Default, Debug, Clone)]
struct ActionModeState {
    pub select: button::State,
    pub translate: button::State,
    pub rotate: button::State,
    pub build: button::State,
    pub cut: button::State,
    pub add_grid: button::State,
    pub add_hyperboloid: button::State,
}

impl ActionModeState {
    fn get_states<'a>(
        &'a mut self,
        len_helix: usize,
        position_helix: isize,
        make_strands: bool,
    ) -> BTreeMap<ActionMode, &'a mut button::State> {
        let mut ret = BTreeMap::new();
        ret.insert(ActionMode::Normal, &mut self.select);
        ret.insert(ActionMode::Translate, &mut self.translate);
        ret.insert(ActionMode::Rotate, &mut self.rotate);
        let (position, length) = if make_strands {
            (position_helix, len_helix)
        } else {
            (0, 0)
        };
        ret.insert(ActionMode::BuildHelix { position, length }, &mut self.build);
        ret
    }
}

fn target_message(i: usize) -> Message {
    match i {
        0 => Message::FixPoint(Vec3::unit_x(), Vec3::unit_y()),
        1 => Message::FixPoint(-Vec3::unit_x(), Vec3::unit_y()),
        2 => Message::FixPoint(Vec3::unit_y(), Vec3::unit_z()),
        3 => Message::FixPoint(-Vec3::unit_y(), -Vec3::unit_z()),
        4 => Message::FixPoint(Vec3::unit_z(), Vec3::unit_y()),
        _ => Message::FixPoint(-Vec3::unit_z(), Vec3::unit_y()),
    }
}

fn rotation_message(i: usize, _xz: isize, _yz: isize, _xy: isize) -> Message {
    let angle_xz = match i {
        0 => 15f32.to_radians(),
        1 => -15f32.to_radians(),
        _ => 0f32,
    };
    let angle_yz = match i {
        2 => -15f32.to_radians(),
        3 => 15f32.to_radians(),
        _ => 0f32,
    };
    let angle_xy = match i {
        4 => 15f32.to_radians(),
        5 => -15f32.to_radians(),
        _ => 0f32,
    };
    Message::RotateCam(angle_xz, angle_yz, angle_xy)
}

fn rotation_text(i: usize, ui_size: UiSize) -> Text {
    match i {
        0 => icon(MaterialIcon::ArrowBack, &ui_size),
        1 => icon(MaterialIcon::ArrowForward, &ui_size),
        2 => icon(MaterialIcon::ArrowUpward, &ui_size),
        3 => icon(MaterialIcon::ArrowDownward, &ui_size),
        4 => icon(MaterialIcon::Undo, &ui_size),
        _ => icon(MaterialIcon::Redo, &ui_size),
    }
}

fn target_text(i: usize) -> String {
    match i {
        0 => "Right".to_string(),
        1 => "Left".to_string(),
        2 => "Top".to_string(),
        3 => "Bottom".to_string(),
        4 => "Back".to_string(),
        _ => "Front".to_string(),
    }
}

mod text_input_style {
    use iced::{Background, Color};
    use iced_wgpu::text_input::*;
    pub struct BadValue(pub bool);
    impl iced_wgpu::text_input::StyleSheet for BadValue {
        fn active(&self) -> Style {
            Style {
                background: Background::Color(Color::WHITE),
                border_radius: 5.0,
                border_width: 1.0,
                border_color: Color::from_rgb(0.7, 0.7, 0.7),
            }
        }

        fn focused(&self) -> Style {
            Style {
                border_color: Color::from_rgb(0.5, 0.5, 0.5),
                ..self.active()
            }
        }

        fn placeholder_color(&self) -> Color {
            Color::from_rgb(0.7, 0.7, 0.7)
        }

        fn value_color(&self) -> Color {
            if self.0 {
                Color::from_rgb(0.3, 0.3, 0.3)
            } else {
                Color::from_rgb(1., 0.3, 0.3)
            }
        }

        fn selection_color(&self) -> Color {
            Color::from_rgb(0.8, 0.8, 1.0)
        }
    }
}

#[derive(Clone)]
pub struct SimulationRequest {
    pub roll: bool,
    pub springs: bool,
    pub target_helices: Option<Vec<usize>>,
}

#[derive(Clone)]
pub struct HyperboloidRequest {
    pub radius: usize,
    pub length: f32,
    pub shift: f32,
    pub radius_shift: f32,
}

pub struct Hyperboloid_ {}

impl Requestable for Hyperboloid_ {
    type Request = HyperboloidRequest;
    fn request_from_values(&self, values: &[f32]) -> HyperboloidRequest {
        HyperboloidRequest {
            radius: values[0].round() as usize,
            length: values[1],
            shift: values[2],
            radius_shift: values[3],
        }
    }
    fn nb_values(&self) -> usize {
        4
    }
    fn initial_value(&self, n: usize) -> f32 {
        match n {
            0 => 10f32,
            1 => 30f32,
            2 => 0f32,
            3 => 0.2f32,
            _ => unreachable!(),
        }
    }
    fn min_val(&self, n: usize) -> f32 {
        use std::f32::consts::PI;
        match n {
            0 => 5f32,
            1 => 1f32,
            2 => -PI + 1f32.to_radians(),
            3 => 0.,
            _ => unreachable!(),
        }
    }

    fn max_val(&self, n: usize) -> f32 {
        use std::f32::consts::PI;
        match n {
            0 => 60f32,
            1 => 200f32,
            2 => PI - 1f32.to_radians(),
            3 => 1f32,
            _ => unreachable!(),
        }
    }
    fn step_val(&self, n: usize) -> f32 {
        match n {
            0 => 1f32,
            1 => 1f32,
            2 => 1f32.to_radians(),
            3 => 0.01,
            _ => unreachable!(),
        }
    }
    fn name_val(&self, n: usize) -> String {
        match n {
            0 => String::from("Nb helices"),
            1 => String::from("Strands length"),
            2 => String::from("Angle shift"),
            3 => String::from("Tube radius"),
            _ => unreachable!(),
        }
    }

    fn hidden(&self, n: usize) -> bool {
        n == 2 || n == 3
    }
}

struct ScrollSentivity {}

impl Requestable for ScrollSentivity {
    type Request = f32;
    fn request_from_values(&self, values: &[f32]) -> f32 {
        values[0]
    }
    fn nb_values(&self) -> usize {
        1
    }
    fn initial_value(&self, n: usize) -> f32 {
        if n == 0 {
            0f32
        } else {
            unreachable!()
        }
    }
    fn min_val(&self, n: usize) -> f32 {
        if n == 0 {
            -10f32
        } else {
            unreachable!()
        }
    }
    fn max_val(&self, n: usize) -> f32 {
        if n == 0 {
            10f32
        } else {
            unreachable!()
        }
    }
    fn step_val(&self, n: usize) -> f32 {
        if n == 0 {
            0.5f32
        } else {
            unreachable!()
        }
    }
    fn name_val(&self, n: usize) -> String {
        if n == 0 {
            String::from("Sentivity")
        } else {
            unreachable!()
        }
    }
}

struct HelixRoll {}

impl Requestable for HelixRoll {
    type Request = f32;
    fn request_from_values(&self, values: &[f32]) -> f32 {
        values[0]
    }
    fn nb_values(&self) -> usize {
        1
    }
    fn initial_value(&self, n: usize) -> f32 {
        match n {
            0 => 0f32,
            _ => unreachable!(),
        }
    }
    fn min_val(&self, n: usize) -> f32 {
        use std::f32::consts::PI;
        match n {
            0 => -PI,
            _ => unreachable!(),
        }
    }
    fn max_val(&self, n: usize) -> f32 {
        use std::f32::consts::PI;
        match n {
            0 => PI,
            _ => unreachable!(),
        }
    }
    fn step_val(&self, n: usize) -> f32 {
        match n {
            0 => 1f32.to_radians(),
            _ => unreachable!(),
        }
    }
    fn name_val(&self, n: usize) -> String {
        match n {
            0 => String::from("Roll helix"),
            _ => unreachable!(),
        }
    }
}

#[derive(Clone)]
pub struct RigidBodyParametersRequest {
    pub k_springs: f32,
    pub k_friction: f32,
    pub mass_factor: f32,
    pub volume_exclusion: bool,
    pub brownian_motion: bool,
    pub brownian_rate: f32,
    pub brownian_amplitude: f32,
}

struct RigidBodyFactory {
    pub volume_exclusion: bool,
    pub brownian_motion: bool,
    pub brownian_parameters: BrownianParametersFactory,
}

#[derive(Clone)]
struct BrownianParametersFactory {
    pub rate: f32,
    pub amplitude: f32,
}

impl Requestable for BrownianParametersFactory {
    type Request = Self;
    fn request_from_values(&self, values: &[f32]) -> Self {
        Self {
            rate: values[0],
            amplitude: values[1],
        }
    }

    fn nb_values(&self) -> usize {
        2
    }

    fn initial_value(&self, n: usize) -> f32 {
        match n {
            0 => 0.,
            1 => 0.08,
            _ => unreachable!(),
        }
    }

    fn min_val(&self, n: usize) -> f32 {
        match n {
            0 => -2.,
            1 => 0.,
            _ => unreachable!(),
        }
    }

    fn max_val(&self, n: usize) -> f32 {
        match n {
            0 => 2.,
            1 => 0.2,
            _ => unreachable!(),
        }
    }

    fn step_val(&self, n: usize) -> f32 {
        match n {
            0 => 0.1,
            1 => 0.02,
            _ => unreachable!(),
        }
    }

    fn name_val(&self, n: usize) -> String {
        match n {
            0 => "Rate (log scale)".to_owned(),
            1 => "Range".to_owned(),
            _ => unreachable!(),
        }
    }
}

impl Requestable for RigidBodyFactory {
    type Request = RigidBodyParametersRequest;
    fn request_from_values(&self, values: &[f32]) -> RigidBodyParametersRequest {
        RigidBodyParametersRequest {
            k_springs: values[0],
            k_friction: values[1],
            mass_factor: values[2],
            volume_exclusion: self.volume_exclusion,
            brownian_motion: self.brownian_motion,
            brownian_rate: self.brownian_parameters.rate,
            brownian_amplitude: self.brownian_parameters.amplitude,
        }
    }
    fn nb_values(&self) -> usize {
        3
    }
    fn initial_value(&self, n: usize) -> f32 {
        match n {
            0 => 0f32,
            1 => 0f32,
            2 => 0f32,
            _ => unreachable!(),
        }
    }
    fn min_val(&self, n: usize) -> f32 {
        match n {
            0 => -4.,
            1 => -4.,
            2 => -4.,
            3 => -4.,
            _ => unreachable!(),
        }
    }
    fn max_val(&self, n: usize) -> f32 {
        match n {
            0 => 4.,
            1 => 4.,
            2 => 4.,
            3 => 4.,
            _ => unreachable!(),
        }
    }
    fn step_val(&self, n: usize) -> f32 {
        match n {
            0 => 0.1f32,
            1 => 0.1f32,
            2 => 0.1f32,
            3 => 0.1f32,
            _ => unreachable!(),
        }
    }
    fn name_val(&self, n: usize) -> String {
        match n {
            0 => String::from("Stiffness (log scale)"),
            1 => String::from("Friction (log scale)"),
            2 => String::from("Mass (log scale)"),
            _ => unreachable!(),
        }
    }
}

use super::KeepProceed;
fn use_default_scaffold(requests: Arc<Mutex<Requests>>) {
    crate::utils::yes_no_dialog(
        "Use default m13 sequence".into(),
        requests,
        KeepProceed::DefaultScaffold,
        Some(KeepProceed::CustomScaffold),
    )
}

#[derive(Clone, Copy, Debug)]
struct TabStyle;

impl iced_aw::style::tab_bar::StyleSheet for TabStyle {
    fn active(&self, is_active: bool) -> iced_aw::style::tab_bar::Style {
        iced_aw::style::tab_bar::Style {
            background: None,
            border_color: None,
            border_width: 0.0,
            tab_label_background: if !is_active {
                Background::Color([0.9, 0.9, 0.9].into())
            } else {
                Background::Color([0.6, 0.6, 0.6].into())
            },
            tab_label_border_color: [0.7, 0.7, 0.7].into(),
            tab_label_border_width: 1.0,
            icon_color: Color::BLACK,
            text_color: Color::BLACK,
        }
    }

    fn hovered(&self, is_active: bool) -> iced_aw::style::tab_bar::Style {
        iced_aw::style::tab_bar::Style {
            tab_label_background: Background::Color([0.6, 0.6, 0.6].into()),
            ..self.active(is_active)
        }
    }
}
back to top