Skip to content

Commit

Permalink
Merge pull request #976 from UC-Davis-molecular-computing/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
dave-doty authored Jul 24, 2024
2 parents c79287a + 04203b4 commit ebf29a6
Show file tree
Hide file tree
Showing 19 changed files with 493 additions and 107 deletions.
Binary file added lib/src/.DS_Store
Binary file not shown.
19 changes: 19 additions & 0 deletions lib/src/actions/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,25 @@ abstract class InvertYSet
static Serializer<InvertYSet> get serializer => _$invertYSetSerializer;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// dynamically update helices

abstract class DynamicHelixUpdateSet
with BuiltJsonSerializable
implements
Action,
SvgPngCacheInvalidatingAction,
Built<DynamicHelixUpdateSet, DynamicHelixUpdateSetBuilder> {
bool get dynamically_update_helices;

/************************ begin BuiltValue boilerplate ************************/
factory DynamicHelixUpdateSet({bool dynamically_update_helices}) = _$DynamicHelixUpdateSet._;

DynamicHelixUpdateSet._();

static Serializer<DynamicHelixUpdateSet> get serializer => _$dynamicHelixUpdateSetSerializer;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// warn on exit if unsaved

Expand Down
13 changes: 12 additions & 1 deletion lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import 'state/grid.dart';

// WARNING: Do not modify line below, except for the version string
// (and also add new version string to scadnano_versions_to_link).
const String CURRENT_VERSION = "0.19.2";
const String CURRENT_VERSION = "0.19.3";
const String INITIAL_VERSION = "0.1.0";

// scadnano versions that we deploy so that older versions can be used.
final scadnano_older_versions_to_link = [
"0.19.2",
"0.19.1",
"0.18.10",
"0.17.14",
Expand Down Expand Up @@ -473,3 +474,13 @@ const css_selector_context_menu_item_disabled = 'context_menu_item_disabled';

const default_vendor_scale = "25nm";
const default_vendor_purification = "STD";

enum strand_bounds_status {
helix_not_in_design,
helix_out_of_bounds,
min_offset_out_of_bounds,
max_offset_out_of_bounds,
in_bounds_with_min_offset_changes,
in_bounds_with_max_offset_changes,
in_bounds
}
39 changes: 36 additions & 3 deletions lib/src/middleware/export_svg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Map point_to_map(svg.Point point) {
}

// creates a new separate text svg for the jth character on a svg text element
TextElement create_portable_element(TextContentElement text_ele, int j) {
TextElement create_portable_text(TextContentElement text_ele, int j) {
TextElement char_ele = document.createElementNS("http://www.w3.org/2000/svg", "text");
char_ele.text = text_ele.text[j];
char_ele.setAttribute("style", text_ele.style.cssText);
Expand Down Expand Up @@ -223,19 +223,46 @@ TextElement create_portable_element(TextContentElement text_ele, int j) {
return char_ele;
}

// fix the 5' extension rectangle missing
RectElement create_portable_rect(RectElement ele) {
RectElement portableEle = document.createElementNS("http://www.w3.org/2000/svg", "rect");

portableEle.style.cssText = ele.style.cssText;
if (portableEle.style.transformOrigin != "") {
portableEle.style.transformOrigin = "";
}
// remove transform box attribute. dart doesn't support the normal way
portableEle.style.cssText = portableEle.style.cssText.replaceAll(RegExp(r'transform-box:[^;]+;'), '');
Rect pos = ele.getBBox();
portableEle.setAttribute("x", pos.x.toString());
portableEle.setAttribute("y", pos.y.toString());
portableEle.setAttribute("rx", ele.rx.baseVal.value.toString());
portableEle.setAttribute("ry", ele.ry.baseVal.value.toString());
portableEle.setAttribute("width", pos.width.toString());
portableEle.setAttribute("height", pos.height.toString());
for (int i = 0; i < ele.transform.baseVal.numberOfItems; ++i) {
Transform item = ele.transform.baseVal.getItem(i);
if (item.angle != 0) {
portableEle.setAttribute(
"transform", "rotate(${item.angle} ${pos.x + pos.width / 2} ${pos.y + pos.height / 2})");
}
}
return portableEle;
}

// makes a svg compatible for PowerPoint
Element make_portable(Element src) {
var src_children = src.querySelectorAll("*");
document.body.append(src);
for (int i = 0; i < src_children.length; ++i) {
if (src_children[i] is svg.TextContentElement) {
if (src_children[i] is TextContentElement) {
TextContentElement text_ele = src_children[i] as TextContentElement;
if (text_ele.children.length == 1 && text_ele.children[0].tagName == "textPath") {
continue;
}
List<TextContentElement> portable_eles = [];
for (int j = 0; j < text_ele.getNumberOfChars(); ++j) {
var char_ele = create_portable_element(text_ele, j);
var char_ele = create_portable_text(text_ele, j);
portable_eles.add(char_ele);
}
if (text_ele is TextPathElement) {
Expand All @@ -248,6 +275,10 @@ Element make_portable(Element src) {
}
portable_eles.forEach((v) => text_ele.parentNode.append(v));
text_ele.remove();
} else if (src_children[i] is RectElement) {
RectElement portableEle = create_portable_rect(src_children[i] as RectElement);
src_children[i].parentNode.append(portableEle);
src_children[i].remove();
}
}
src.remove();
Expand Down Expand Up @@ -337,6 +368,8 @@ const List<String> path_styles = [
'stroke-linecap',
'stroke-opacity',
'visibility',
'transform-box',
'transform-origin',
];

final relevant_styles = {
Expand Down
33 changes: 30 additions & 3 deletions lib/src/middleware/system_clipboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:scadnano/src/state/clipboard.dart';
import 'package:scadnano/src/state/group.dart';
import 'package:scadnano/src/state/modification.dart';
import 'package:scadnano/src/state/strand.dart';
import 'package:scadnano/src/reducers/strands_move_reducer.dart';

import '../reducers/strands_move_reducer.dart' as strands_move_reducer;
import '../actions/actions.dart' as actions;
Expand Down Expand Up @@ -78,9 +79,35 @@ void handle_autopaste_initiate(Store<AppState> store, actions.AutoPasteInitiate
strands_move = copy_info.create_strands_move(store.state, start_at_copied: false);
}

var paste_commit_action = actions.StrandsMoveCommit(strands_move: strands_move, autopaste: true);

store.dispatch(paste_commit_action);
List<actions.UndoableAction> batch_actions_list = [];
Map new_strand_moving_details = get_strand_bounds_details(store.state.design, strands_move);
if (store.state.ui_state.dynamically_update_helices) {
if (new_strand_moving_details['status'] == constants.strand_bounds_status.min_offset_out_of_bounds ||
new_strand_moving_details['status'] == constants.strand_bounds_status.max_offset_out_of_bounds) {
var helices_offset_change_action;
for (int helix_idx in new_strand_moving_details['offsets'].keys) {
if (new_strand_moving_details['status'] == constants.strand_bounds_status.min_offset_out_of_bounds)
helices_offset_change_action = actions.HelixOffsetChange(
helix_idx: helix_idx, min_offset: new_strand_moving_details['offsets'][helix_idx]);
else
helices_offset_change_action = actions.HelixOffsetChange(
helix_idx: helix_idx, max_offset: new_strand_moving_details['offsets'][helix_idx]);

batch_actions_list.add(helices_offset_change_action);
}
}
}
if (new_strand_moving_details['status'] == constants.strand_bounds_status.in_bounds ||
new_strand_moving_details['status'] ==
constants.strand_bounds_status.in_bounds_with_min_offset_changes ||
new_strand_moving_details['status'] ==
constants.strand_bounds_status.in_bounds_with_max_offset_changes) {
var paste_commit_action = actions.StrandsMoveCommit(strands_move: strands_move, autopaste: true);
batch_actions_list.add(paste_commit_action);
var batch_action =
actions.BatchAction(batch_actions_list, 'Changing helix offsets and then executing autopaste');
store.dispatch(batch_action);
}
}

// either
Expand Down
23 changes: 22 additions & 1 deletion lib/src/reducers/app_ui_state_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import 'package:built_collection/built_collection.dart';
import 'package:redux/redux.dart';
import 'package:scadnano/src/reducers/design_reducer.dart';
import 'package:scadnano/src/reducers/strands_copy_info_reducer.dart';
import 'package:scadnano/src/state/design.dart';
import 'package:scadnano/src/state/base_pair_display_type.dart';
import 'package:scadnano/src/state/dna_assign_options.dart';
import 'package:scadnano/src/state/modification.dart';
import 'package:scadnano/src/state/strand.dart';
import 'package:scadnano/src/state/copy_info.dart';
import 'package:scadnano/src/state/substrand.dart';
import 'package:scadnano/src/util.dart';
import 'package:tuple/tuple.dart';
import '../reducers/context_menu_reducer.dart';
import '../state/example_designs.dart';
import '../state/grid_position.dart';
Expand Down Expand Up @@ -188,6 +190,9 @@ bool show_unpaired_insertion_deletions_reducer(bool _, actions.ShowUnpairedInser

bool invert_y_reducer(bool _, actions.InvertYSet action) => action.invert_y;

bool dynamic_helix_update_reducer(bool _, actions.DynamicHelixUpdateSet action) =>
action.dynamically_update_helices;

bool warn_on_exit_if_unsaved_reducer(bool _, actions.WarnOnExitIfUnsavedSet action) => action.warn;

bool show_helix_circles_main_view_reducer(bool _, actions.ShowHelixCirclesMainViewSet action) =>
Expand Down Expand Up @@ -438,6 +443,7 @@ AppUIStateStorables app_ui_state_storable_local_reducer(AppUIStateStorables stor
..show_domain_name_mismatches = TypedReducer<bool, actions.ShowDomainNameMismatchesSet>(show_domain_name_mismatches_reducer)(storables.show_domain_name_mismatches, action)
..show_unpaired_insertion_deletions = TypedReducer<bool, actions.ShowUnpairedInsertionDeletionsSet>(show_unpaired_insertion_deletions_reducer)(storables.show_unpaired_insertion_deletions, action)
..invert_y = TypedReducer<bool, actions.InvertYSet>(invert_y_reducer)(storables.invert_y, action)
..dynamically_update_helices = TypedReducer<bool, actions.DynamicHelixUpdateSet>(dynamic_helix_update_reducer)(storables.dynamically_update_helices, action)
..warn_on_exit_if_unsaved = TypedReducer<bool, actions.WarnOnExitIfUnsavedSet>(warn_on_exit_if_unsaved_reducer)(storables.warn_on_exit_if_unsaved, action)
..show_helix_circles_main_view = TypedReducer<bool, actions.ShowHelixCirclesMainViewSet>(show_helix_circles_main_view_reducer)(storables.show_helix_circles_main_view, action)
..show_helix_components_main_view = TypedReducer<bool, actions.ShowHelixComponentsMainViewSet>(show_helix_components_main_view_reducer)(storables.show_helix_components_main_view, action)
Expand Down Expand Up @@ -601,7 +607,22 @@ AppUIState ui_state_global_reducer(AppUIState ui_state, AppState state, action)
..strands_move = strands_move_global_reducer(ui_state.strands_move, state, action)?.toBuilder()
..domains_move = domains_move_global_reducer(ui_state.domains_move, state, action)?.toBuilder()
..strand_creation = strand_creation_global_reducer(ui_state.strand_creation, state, action)?.toBuilder()
..copy_info = copy_info_global_reducer(ui_state.copy_info, state, action)?.toBuilder());
..copy_info = copy_info_global_reducer(ui_state.copy_info, state, action)?.toBuilder()
..original_helix_offsets =
original_helix_offsets_reducer(ui_state.original_helix_offsets, state, action).toBuilder());

BuiltMap<int, Tuple2> original_helix_offsets_reducer(
BuiltMap<int, Tuple2> original_helix_offsets, AppState state, action) {
if (action is actions.StrandsMoveStartSelectedStrands || action is actions.StrandCreateStart) {
var helix_offsets = original_helix_offsets.toMap();
for (int key in state.design.helices.keys) {
var helix = state.design.helices[key];
helix_offsets[state.design.helices[key].idx] = Tuple2.fromList([helix.min_offset, helix.max_offset]);
}
return helix_offsets.build();
}
return original_helix_offsets;
}

GlobalReducer<BuiltList<MouseoverData>, AppState> mouseover_datas_global_reducer = combineGlobalReducers([
TypedGlobalReducer<BuiltList<MouseoverData>, AppState, actions.HelixRollSetAtOther>(
Expand Down
141 changes: 141 additions & 0 deletions lib/src/reducers/helices_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import 'package:collection/collection.dart';
import 'package:redux/redux.dart';
import 'package:built_collection/built_collection.dart';
import 'package:scadnano/src/reducers/groups_reducer.dart';
import 'package:scadnano/src/reducers/strands_move_reducer.dart';
import 'package:scadnano/src/state/group.dart';
import 'package:scadnano/src/state/strand_creation.dart';
import 'package:scadnano/src/state/strands_move.dart';
import 'package:scadnano/src/state/substrand.dart';
import '../reducers/util_reducer.dart';
import '../state/app_state.dart';

Expand Down Expand Up @@ -51,6 +55,13 @@ GlobalReducer<BuiltMap<int, Helix>, AppState> helices_global_reducer = combineGl
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.HelixMaxOffsetSetByDomainsAllSameMax>(
helix_max_offset_set_by_domains_all_same_max_reducer,
),
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.StrandsMoveAdjustAddress>(
helix_offset_change_all_with_moving_strands_reducer),
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.StrandCreateAdjustOffset>(
helix_offset_change_all_while_creating_strand_reducer),
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.ReplaceStrands>(first_replace_strands_reducer),
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.SelectionsClear>(
reset_helices_offsets_after_selections_clear)
]);

BuiltMap<int, Helix> helix_individual_reducer(
Expand Down Expand Up @@ -186,6 +197,136 @@ Helix _change_offset_one_helix(Helix helix, int min_offset, int max_offset) => h
..min_offset = min_offset ?? helix.min_offset
..max_offset = max_offset ?? helix.max_offset);

BuiltMap<int, Helix> helix_offset_change_all_with_moving_strands_reducer(
BuiltMap<int, Helix> helices, AppState state, actions.StrandsMoveAdjustAddress action) {
if (state.ui_state.dynamically_update_helices) {
StrandsMove new_strands_move =
state.ui_state.strands_move.rebuild((b) => b..current_address.replace(action.address));
Map strand_bounds_details = get_strand_bounds_details(state.design, new_strands_move);
constants.strand_bounds_status status = strand_bounds_details['status'];

var offsets = strand_bounds_details['offsets'];
if (status == constants.strand_bounds_status.min_offset_out_of_bounds ||
status == constants.strand_bounds_status.in_bounds_with_min_offset_changes) {
Helix map_func(int idx, Helix helix) => _change_offset_one_helix(helix, offsets[idx], null);
helices = helices.map_values(map_func);
} else if (status == constants.strand_bounds_status.max_offset_out_of_bounds ||
status == constants.strand_bounds_status.in_bounds_with_max_offset_changes) {
Helix map_func(int idx, Helix helix) => _change_offset_one_helix(helix, null, offsets[idx]);
helices = helices.map_values(map_func);
}
}
return helices;
}

BuiltMap<int, Helix> helix_offset_change_all_while_creating_strand_reducer(
BuiltMap<int, Helix> helices, AppState state, actions.StrandCreateAdjustOffset action) {
if (state.ui_state.dynamically_update_helices) {
StrandCreation strand_creation = state.ui_state.strand_creation;
if (strand_creation != null) {
var helices_map = helices.toMap();
var original_helix_offsets = state.ui_state.original_helix_offsets;

// Increase helix size according to strand movement
if (helices_map[strand_creation.helix.idx].min_offset > action.offset) {
helices_map[strand_creation.helix.idx] =
helices_map[strand_creation.helix.idx].rebuild((b) => b..min_offset = action.offset);
return helices_map.build();
}
if (helices_map[strand_creation.helix.idx].max_offset <= action.offset) {
helices_map[strand_creation.helix.idx] =
helices_map[strand_creation.helix.idx].rebuild((b) => b..max_offset = action.offset + 1);
return helices_map.build();
}

// Decrease helix size according to strand movement
if (action.offset > helices_map[strand_creation.helix.idx].min_offset &&
helices_map[strand_creation.helix.idx].min_offset <
original_helix_offsets[strand_creation.helix.idx].item1) {
helices_map[strand_creation.helix.idx] =
helices_map[strand_creation.helix.idx].rebuild((b) => b..min_offset = action.offset);
return helices_map.build();
}
if (action.offset < helices_map[strand_creation.helix.idx].max_offset + 1 &&
helices_map[strand_creation.helix.idx].max_offset >
original_helix_offsets[strand_creation.helix.idx].item2) {
helices_map[strand_creation.helix.idx] =
helices_map[strand_creation.helix.idx].rebuild((b) => b.max_offset = action.offset + 1);
return helices_map.build();
}
}
}
return helices;
}

BuiltMap<int, Helix> first_replace_strands_reducer(
BuiltMap<int, Helix> helices, AppState state, actions.ReplaceStrands action) {
Map changed_strands = action.new_strands.toMap();
Map<int, int> min_offsets = {};
Map<int, int> max_offsets = {};
for (int key in changed_strands.keys) {
Strand strand = changed_strands[key];
List<Substrand> substrands = strand.substrands.toList();
for (var domain in substrands) {
if (domain is Domain) {
int helix_idx = domain.helix;
if (domain.start < helices[helix_idx].min_offset) {
if (min_offsets.containsKey(helix_idx))
min_offsets[helix_idx] = [min_offsets[helix_idx], domain.start].min;
else
min_offsets[helix_idx] = domain.start;
}
if (domain.end > helices[helix_idx].max_offset) {
if (max_offsets.containsKey(helix_idx))
max_offsets[helix_idx] = [max_offsets[helix_idx], domain.end].max;
else
max_offsets[helix_idx] = domain.end;
}
}
}
}
var helices_map = helices.toMap();
if (min_offsets.length > 0) {
for (int helix_idx in min_offsets.keys) {
helices_map[helix_idx] = helices_map[helix_idx].rebuild((b) => b..min_offset = min_offsets[helix_idx]);
}
}
if (max_offsets.length > 0) {
for (int helix_idx in max_offsets.keys) {
helices_map[helix_idx] = helices_map[helix_idx].rebuild((b) => b..max_offset = max_offsets[helix_idx]);
}
}
return helices_map.build();
}

BuiltMap<int, Helix> reset_helices_offsets(BuiltMap<int, Helix> helices, AppState state) {
var helices_updated = helices.toMap();
var original_helix_offsets = state.ui_state.original_helix_offsets;
for (int idx in original_helix_offsets.keys) {
int current_helix_min_offset = state.design.min_offset_of_strands_at(idx);
if (current_helix_min_offset >= original_helix_offsets[idx].item1) {
helices_updated[idx] =
helices_updated[idx].rebuild((b) => b.min_offset = original_helix_offsets[idx].item1);
}
int current_helix_max_offset = state.design.max_offset_of_strands_at(idx);
if (current_helix_max_offset <= original_helix_offsets[idx].item2) {
helices_updated[idx] =
helices_updated[idx].rebuild((b) => b.max_offset = original_helix_offsets[idx].item2);
}
}
return helices_updated.build();
}

BuiltMap<int, Helix> reset_helices_offsets_after_selections_clear(
BuiltMap<int, Helix> helices, AppState state, actions.SelectionsClear action) {
return reset_helices_offsets(helices, state);
}

// BuiltMap<int, Helix> reset_helices_offsets_after_creation(
// BuiltMap<int, Helix> helices, AppState state, actions.StrandCreateStop action) {
// return reset_helices_offsets(helices, state);
// }

BuiltMap<int, Helix> helix_offset_change_all_reducer(
BuiltMap<int, Helix> helices, AppState state, actions.HelixOffsetChangeAll action) {
Helix map_func(_, Helix helix) => _change_offset_one_helix(helix, action.min_offset, action.max_offset);
Expand Down
Loading

0 comments on commit ebf29a6

Please sign in to comment.