use eframe::egui::{Color32, FontFamily, FontId, RichText, ScrollArea, TextEdit, Ui};
use na_seq::{seq_aa_from_str, seq_aa_to_str, seq_from_str, seq_to_str_lower};
use crate::{
alignment::{AlignmentMode, align_pairwise_aa, align_pairwise_nt, distance_aa, distance_nt},
gui::{
COL_SPACING, ROW_SPACING,
theme::{COLOR_ACTION, COLOR_INFO},
},
state::State,
};
fn mode_btn(state: &mut State, mode: AlignmentMode, name: &str, ui: &mut Ui) {
let color = if state.alignment.mode == mode {
Color32::LIGHT_BLUE
} else {
Color32::WHITE
};
if ui.button(RichText::new(name).color(color)).clicked() {
state.alignment.mode = mode;
state.alignment.seq_a = Vec::new();
state.alignment.seq_aa_a = Vec::new();
state.alignment.seq_a_input = String::new();
state.alignment.seq_b = Vec::new();
state.alignment.seq_aa_b = Vec::new();
state.alignment.seq_b_input = String::new();
}
}
fn input_area(state: &mut State, seq_b: bool, ui: &mut Ui) {
let (seq, seq_aa, seq_input) = if seq_b {
(
&mut state.alignment.seq_a,
&mut state.alignment.seq_aa_a,
&mut state.alignment.seq_a_input,
)
} else {
(
&mut state.alignment.seq_b,
&mut state.alignment.seq_aa_b,
&mut state.alignment.seq_b_input,
)
};
let active_seq = state.generic[state.active].seq.clone();
ui.horizontal(|ui| {
ui.label(if seq_b { "Seq B" } else { "Seq A" });
ui.add_space(COL_SPACING);
if let AlignmentMode::Dna = state.alignment.mode {
if ui
.button(RichText::new("Load active tab's sequence").color(Color32::LIGHT_BLUE))
.clicked()
{
*seq = active_seq.clone(); *seq_input = seq_to_str_lower(seq);
}
}
});
let response = ui.add(TextEdit::multiline(seq_input).desired_width(800.));
if response.changed() {
match state.alignment.mode {
AlignmentMode::Dna => {
*seq = seq_from_str(seq_input);
*seq_input = seq_to_str_lower(seq);
}
AlignmentMode::AminoAcid => {
*seq_aa = seq_aa_from_str(seq_input);
*seq_input = seq_aa_to_str(seq_aa);
}
}
}
}
pub fn alignment_page(state: &mut State, ui: &mut Ui) {
ui.add_space(ROW_SPACING);
ui.horizontal(|ui| {
ui.heading("Alignment (Work in Progress)");
ui.add_space(COL_SPACING * 2.);
mode_btn(state, AlignmentMode::Dna, "DNA", ui);
mode_btn(state, AlignmentMode::AminoAcid, "Amino acid", ui);
});
ui.add_space(ROW_SPACING);
ScrollArea::vertical().id_salt(200).show(ui, |ui| {
input_area(state, false, ui);
ui.add_space(ROW_SPACING);
input_area(state, true, ui);
ui.add_space(ROW_SPACING);
if ui
.button(RichText::new("Align").color(COLOR_ACTION))
.clicked()
{
let alignment = match state.alignment.mode {
AlignmentMode::Dna => {
align_pairwise_nt(&state.alignment.seq_a, &state.alignment.seq_b)
}
AlignmentMode::AminoAcid => {
align_pairwise_aa(&state.alignment.seq_aa_a, &state.alignment.seq_aa_b)
}
};
state.alignment.alignment_result = Some(alignment.0);
state.alignment.text_display = alignment.1;
state.alignment.dist_result = Some(match state.alignment.mode {
AlignmentMode::Dna => distance_nt(&state.alignment.seq_a, &state.alignment.seq_b),
AlignmentMode::AminoAcid => {
distance_aa(&state.alignment.seq_aa_a, &state.alignment.seq_aa_b)
}
});
}
ui.add_space(ROW_SPACING);
if let Some(alignment) = &state.alignment.alignment_result {
ui.heading(&format!("Alignment score: {:?}", alignment.score));
ui.add_space(ROW_SPACING);
ui.label(
RichText::new(&state.alignment.text_display)
.color(COLOR_INFO)
.font(FontId::new(16., FontFamily::Monospace)),
);
}
if let Some(dist) = &state.alignment.dist_result {
ui.horizontal(|ui| {
ui.heading(&format!("Distance score: {:?}", dist));
let dist_type_text = if state.alignment.seq_a.len() == state.alignment.seq_b.len() {
"(Hamming)"
} else {
"(Levenshtein)"
};
ui.label(dist_type_text);
});
}
});
}