Skip to content

Commit

Permalink
Many updates for PCR and cloning, including bugfixes, feature preserv…
Browse files Browse the repository at this point in the history
…ation, and selecting insert from opened files
  • Loading branch information
David O'Connor committed Sep 5, 2024
1 parent 9a238f5 commit e8fcf39
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 127 deletions.
39 changes: 35 additions & 4 deletions src/cloning.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
gui::navigation::{Page, PageSeq},
primer::make_cloning_primers,
sequence::{Feature, FeatureDirection, FeatureType, Seq},
sequence::{seq_to_str, Feature, FeatureDirection, FeatureType, Nucleotide, Seq},
util::RangeIncl,
Selection, State,
};
Expand All @@ -17,6 +17,38 @@ pub struct CloningInsertData {
pub seq_input: String,
}

/// Given a set of freatures and the sequence their ranges map to, set up our
/// insert sequences.
pub fn setup_insert_seqs(state: &mut State, features: Vec<Feature>, seq: Seq) {
// todo: Unecessary cloning if loading from file.
state.ui.cloning_insert.features_loaded = features;
state.ui.cloning_insert.seq_loaded = seq;

// Choose the initial insert as the CDS or gene with the largest len.
let mut best = None;
let mut best_len = 0;
for (i, feature) in state.ui.cloning_insert.features_loaded.iter().enumerate() {
let len = feature.len(state.generic[state.active].seq.len());
if (feature.feature_type == FeatureType::CodingRegion
|| feature.feature_type == FeatureType::Gene)
&& len > best_len
{
best_len = len;
best = Some(i);
}
}

if let Some(feat_i) = best {
let feature = &state.ui.cloning_insert.features_loaded[feat_i];

if let Some(seq_this_ft) = feature.range.index_seq(&state.ui.cloning_insert.seq_loaded) {
state.ui.cloning_insert.feature_selected = best;
state.ui.cloning_insert.seq_insert = seq_this_ft.to_owned();
state.ui.cloning_insert.seq_input = seq_to_str(&seq_this_ft);
}
}
}

/// Create a new tab containing of the cloning product.
pub fn make_product_tab(state: &mut State) {
// Note: This segment is almost a duplicate of `State::add_tab`, but retaining the generic data.
Expand All @@ -28,7 +60,6 @@ pub fn make_product_tab(state: &mut State) {
state.portions.push(Default::default());

state.active = state.generic.len() - 1;
// state.add_tab();

// Make sure to create cloning primers before performing the insert, or the result will be wrong.
make_cloning_primers(state);
Expand All @@ -46,8 +77,8 @@ pub fn make_product_tab(state: &mut State) {
// todo: Use the already existing data instead.
state.generic[state.active].features.push(Feature {
range: RangeIncl::new(
state.cloning_insert_loc + 1,
state.cloning_insert_loc + state.ui.cloning_insert.seq_insert.len(),
state.cloning_insert_loc,
state.cloning_insert_loc + state.ui.cloning_insert.seq_insert.len() - 1,
),
label,
feature_type: FeatureType::CodingRegion,
Expand Down
61 changes: 0 additions & 61 deletions src/cloning.rs~

This file was deleted.

117 changes: 65 additions & 52 deletions src/gui/cloning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use eframe::{
};

use crate::{
cloning::{make_product_tab, CloningInsertData},
cloning::{make_product_tab, setup_insert_seqs, CloningInsertData},
file_io::save::load_import,
gui::{COL_SPACING, ROW_SPACING},
sequence::{seq_from_str, seq_to_str, FeatureType},
gui::{navigation::DEFAULT_TAB_NAME, COL_SPACING, ROW_SPACING},
sequence::{seq_from_str, seq_to_str},
State,
};

Expand Down Expand Up @@ -59,6 +59,65 @@ fn insert_selector(data: &mut CloningInsertData, ui: &mut Ui) {
}
}

fn insert_file_section(state: &mut State, ui: &mut Ui) {
ui.horizontal(|ui| {
ui.label("Choose insert from:");

// Add buttons for each opened tab
for gen in &mut state.generic {
let name = &gen.metadata.plasmid_name;
let btn_name = if name.len() > 20 {
format!("{}...", &name[..20].to_string())
} else if name.is_empty() {
DEFAULT_TAB_NAME.to_owned()
} else {
name.to_owned()
};

if ui
// todo: Consider something like the actual tab name system, that uses file name,
// todo or a default A/R
.button(btn_name)
.on_hover_text("Select an insert from this sequence")
.clicked()
{
// This setup, including the break and variables, prevents borrow errors.
let g = gen.features.clone();
let s = gen.seq.clone();
setup_insert_seqs(state, g, s);
break;
}
}

ui.add_space(COL_SPACING);
if ui
.button("Pick insert from file")
.on_hover_text(
"Choose a GenBank, PlasCAD, SnapGene, or FASTA file to \
select an insert from. FASTA files require manual index selection.",
)
.clicked()
{
state.ui.file_dialogs.cloning_load.select_file();
}

ui.add_space(COL_SPACING);

state.ui.file_dialogs.cloning_load.update(ui.ctx());

if let Some(path) = state.ui.file_dialogs.cloning_load.take_selected() {
if let Some(state_loaded) = load_import(&path) {
// todo: Is there a way to do this without cloning?
setup_insert_seqs(
state,
state_loaded.generic.features.clone(),
state_loaded.generic.seq.clone(),
);
}
}
});
}

pub fn seq_editor_slic(state: &mut State, ui: &mut Ui) {
ui.heading("SLIC and FastCloning");

Expand Down Expand Up @@ -89,54 +148,6 @@ pub fn seq_editor_slic(state: &mut State, ui: &mut Ui) {

ui.add_space(COL_SPACING);

if ui
.button("Pick insert from file")
.on_hover_text(
"Choose a GenBank, PlasCAD, SnapGene, or FASTA file to \
select an insert from. FASTA files require manual index selection.",
)
.clicked()
{
state.ui.file_dialogs.cloning_load.select_file();
}

ui.add_space(COL_SPACING);

state.ui.file_dialogs.cloning_load.update(ui.ctx());

if let Some(path) = state.ui.file_dialogs.cloning_load.take_selected() {
if let Some(state_loaded) = load_import(&path) {
state.ui.cloning_insert.features_loaded = state_loaded.generic.features;
state.ui.cloning_insert.seq_loaded = state_loaded.generic.seq;

// Choose the initial insert as the CDS or gene with the largest len.
let mut best = None;
let mut best_len = 0;
for (i, feature) in state.ui.cloning_insert.features_loaded.iter().enumerate() {
let len = feature.len(state.generic[state.active].seq.len());
if (feature.feature_type == FeatureType::CodingRegion
|| feature.feature_type == FeatureType::Gene)
&& len > best_len
{
best_len = len;
best = Some(i);
}
}

if let Some(feat_i) = best {
let feature = &state.ui.cloning_insert.features_loaded[feat_i];

if let Some(seq_this_ft) =
feature.range.index_seq(&state.ui.cloning_insert.seq_loaded)
{
state.ui.cloning_insert.feature_selected = best;
state.ui.cloning_insert.seq_insert = seq_this_ft.to_owned();
state.ui.cloning_insert.seq_input = seq_to_str(&seq_this_ft);
}
}
}
}

if state.ui.cloning_insert.seq_insert.len() > 6 {
if ui
.button(RichText::new("Clone").color(Color32::GOLD))
Expand All @@ -151,10 +162,12 @@ pub fn seq_editor_slic(state: &mut State, ui: &mut Ui) {
});

ui.add_space(ROW_SPACING);
insert_selector(&mut state.ui.cloning_insert, ui);
insert_file_section(state, ui);

ui.add_space(ROW_SPACING);
insert_selector(&mut state.ui.cloning_insert, ui);

ui.add_space(ROW_SPACING);
ui.horizontal(|ui| {
ui.heading("Insert:");
ui.label(&format!(
Expand Down
19 changes: 17 additions & 2 deletions src/gui/navigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use bincode::{Decode, Encode};
use eframe::egui::{Color32, RichText, Ui};

use crate::{
gui::{set_window_title, COL_SPACING},
gui::{set_window_title, COL_SPACING, ROW_SPACING},
State,
};

pub const NAV_BUTTON_COLOR: Color32 = Color32::from_rgb(0, 00, 110);
const DEFAULT_TAB_NAME: &str = "New plasmid";
pub const DEFAULT_TAB_NAME: &str = "New plasmid";

#[derive(Clone, Copy, PartialEq, Encode, Decode)]
pub enum Page {
Expand Down Expand Up @@ -113,6 +113,21 @@ pub fn tab_selector(state: &mut State, ui: &mut Ui) {

ui.add_space(COL_SPACING / 2.);
}

// todo: Right-align?
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 {
Expand Down
2 changes: 1 addition & 1 deletion src/gui/pcr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ fn pcr_sim(state: &mut State, ui: &mut Ui) {
.to_vec() // todo unwrap is dicey.
};

make_amplicon_tab(state, product_seq, fwd_primer, rev_primer);
make_amplicon_tab(state, product_seq, range_combined, fwd_primer, rev_primer);
}
} else {
ui.label("There must be exactly one match for each primer");
Expand Down
25 changes: 18 additions & 7 deletions src/pcr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
use bincode::{Decode, Encode};

use crate::{
gui::navigation::{Page, PageSeq},
primer::Primer,
sequence::{Nucleotide, Seq},
util::RangeIncl,
PcrUi, State,
};

Expand Down Expand Up @@ -86,31 +88,40 @@ impl PcrParams {
pub fn make_amplicon_tab(
state: &mut State,
product_seq: Seq,
range: RangeIncl,
fwd_primer: Primer,
rev_primer: Primer,
) {
let mut product_features = Vec::new();
for feature in &state.generic[state.active].features {
// todo: Handle circle wraps etc.
if range.start < feature.range.start && range.end > feature.range.end {
let mut product_feature = feature.clone();
// Find the indexes in the new product sequence.
product_feature.range.start -= range.start - 1;
product_feature.range.end -= range.start - 1;

product_features.push(product_feature);
}
}

state.add_tab();

// if let Some(seq) = range_combined.index_seq(&state.generic.seq) {
state.generic[state.active].seq = product_seq;
let product_features = Vec::new();

// Include the primers used for PCR, and features that are included in the new segment.
// note that the feature indexes will need to change.

// todo: Syntax.
let product_primers = vec![fwd_primer, rev_primer];

// for feature in &state.generic[state.active].features {
// todo: Implement.
// }

state.generic[state.active].features = product_features;
state.generic[state.active].primers = product_primers;
state.generic[state.active].metadata.plasmid_name = "PCR amplicon".to_owned();

state.sync_seq_related(None);
// state.sync_primer_metrics();

// save_new_product("PCR product", state, ui);
state.ui.page = Page::Sequence;
state.ui.page_seq = PageSeq::View;
}

0 comments on commit e8fcf39

Please sign in to comment.