use std::{fmt::Display, path::PathBuf};
use bincode::{Decode, Encode};
use eframe::egui::{Color32, RichText, Ui};
use na_seq::seq_to_str_lower;
use crate::{
gui::{COL_SPACING, ROW_SPACING, select_color_text, set_window_title},
state::State,
};
pub const NAV_BUTTON_COLOR: Color32 = Color32::from_rgb(0, 0, 110);
pub const TAB_BUTTON_COLOR: Color32 = Color32::from_rgb(40, 80, 110);
pub const DEFAULT_TAB_NAME: &str = "New plasmid";
const PATH_ABBREV_MAX_LEN: usize = 16;
#[derive(Encode, Decode, Clone, Default, Debug)]
pub struct Tab {
pub path: Option<PathBuf>,
pub ab1: bool, }
pub fn get_tab_names(
tabs: &[Tab],
plasmid_names: &[&str],
abbrev_name: bool,
) -> Vec<(String, usize)> {
let mut result = Vec::new();
for (i, p) in tabs.iter().enumerate() {
let name = name_from_path(&p.path, plasmid_names[i], abbrev_name);
result.push((name, i));
}
result
}
#[derive(Clone, Copy, PartialEq, Encode, Decode)]
pub enum Page {
Sequence,
Map,
Features,
Primers,
Proteins,
Pcr,
Alignment,
Portions,
Metadata,
Ligation,
Cloning,
Ab1,
}
impl Default for Page {
fn default() -> Self {
Self::Sequence
}
}
impl Display for Page {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::Sequence => "Sequence",
Self::Map => "Map",
Self::Features => "Features",
Self::Primers => "Primers",
Self::Proteins => "Proteins",
Self::Pcr => "PCR",
Self::Alignment => "Align",
Self::Portions => "Mixing",
Self::Metadata => "Data",
Self::Ligation => "Digest",
Self::Cloning => "Clone",
Self::Ab1 => "AB1",
}
.to_owned();
write!(f, "{}", str)
}
}
pub fn tab_selector(state: &mut State, ui: &mut Ui) {
let mut tab_removed = None;
ui.horizontal(|ui| {
if ui
.button("New")
.on_hover_text("Create and open a new file. (Ctrl + N)")
.clicked()
{
state.add_tab();
state.tabs_open.push(Default::default());
}
ui.add_space(COL_SPACING);
let plasmid_names: &Vec<_> = &state
.generic
.iter()
.map(|v| v.metadata.plasmid_name.as_str())
.collect();
for (name, i) in get_tab_names(&state.tabs_open, plasmid_names, false) {
let button = ui.button(
select_color_text(&name, i == state.active).background_color(TAB_BUTTON_COLOR),
);
if button.clicked() {
state.active = i;
set_window_title(&state.tabs_open[i], ui);
state.ui.seq_input = seq_to_str_lower(state.get_seq()); }
if button.middle_clicked() {
tab_removed = Some(i);
}
ui.add_space(COL_SPACING / 2.);
}
ui.add_space(2. * ROW_SPACING);
if ui
.button(
RichText::new("Close active tab")
.color(Color32::WHITE)
.background_color(Color32::DARK_RED),
)
.on_hover_text("Shortcut: Middle click the tab to close it.")
.clicked()
{
tab_removed = Some(state.active)
};
});
if let Some(i) = tab_removed {
state.remove_tab(i);
}
}
pub fn page_selector(state: &mut State, ui: &mut Ui) {
ui.horizontal(|ui| {
page_button(&mut state.ui.page, Page::Sequence, ui, true);
page_button(&mut state.ui.page, Page::Map, ui, true);
page_button(&mut state.ui.page, Page::Features, ui, true);
page_button(&mut state.ui.page, Page::Primers, ui, true);
page_button(&mut state.ui.page, Page::Proteins, ui, true);
page_button(&mut state.ui.page, Page::Cloning, ui, true);
page_button(&mut state.ui.page, Page::Pcr, ui, true);
page_button(&mut state.ui.page, Page::Alignment, ui, true);
page_button(&mut state.ui.page, Page::Ligation, ui, true);
page_button(&mut state.ui.page, Page::Metadata, ui, true);
page_button(&mut state.ui.page, Page::Portions, ui, true);
});
}
pub fn page_button<T: PartialEq + ToString>(page_state: &mut T, page: T, ui: &mut Ui, space: bool) {
if ui
.button(
select_color_text(&page.to_string(), *page_state == page)
.background_color(NAV_BUTTON_COLOR),
)
.clicked()
{
*page_state = page;
}
if space {
ui.add_space(COL_SPACING / 2.);
}
}
#[derive(Clone, Copy, PartialEq, Encode, Decode)]
pub enum PageSeq {
EditRaw,
View,
}
impl Default for PageSeq {
fn default() -> Self {
Self::View
}
}
impl Display for PageSeq {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::EditRaw => "Edit raw",
Self::View => "Sequence",
}
.to_owned();
write!(f, "{}", str)
}
}
pub fn page_seq_selector(state: &mut State, ui: &mut Ui) {
ui.horizontal(|ui| {
page_button(&mut state.ui.page_seq, PageSeq::EditRaw, ui, true);
page_button(&mut state.ui.page_seq, PageSeq::View, ui, true);
});
}
#[derive(Clone, Copy, PartialEq, Encode, Decode)]
pub enum PageSeqTop {
Primers,
Features,
None,
}
impl Default for PageSeqTop {
fn default() -> Self {
Self::None
}
}
impl Display for PageSeqTop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::Primers => "Primers",
Self::Features => "Features",
Self::None => "None",
}
.to_owned();
write!(f, "{}", str)
}
}
pub fn page_seq_top_selector(state: &mut State, ui: &mut Ui) {
ui.horizontal(|ui| {
ui.label("Display above sequence:");
page_button(&mut state.ui.page_seq_top, PageSeqTop::None, ui, true);
page_button(&mut state.ui.page_seq_top, PageSeqTop::Features, ui, true);
page_button(&mut state.ui.page_seq_top, PageSeqTop::Primers, ui, true);
});
}
pub fn name_from_path(path: &Option<PathBuf>, plasmid_name: &str, abbrev_name: bool) -> String {
let mut name = match path {
Some(path) => path
.file_name()
.and_then(|name| name.to_str())
.map(|name_str| name_str.to_string())
.unwrap(),
None => {
if !plasmid_name.is_empty() {
plasmid_name.to_owned()
} else {
DEFAULT_TAB_NAME.to_owned()
}
}
};
if abbrev_name && name.len() > PATH_ABBREV_MAX_LEN {
name = format!("{}...", &name[..PATH_ABBREV_MAX_LEN].to_string())
}
name
}