Skip to content

Commit ebf29a6

Browse files
authored
Merge pull request #976 from UC-Davis-molecular-computing/dev
Dev
2 parents c79287a + 04203b4 commit ebf29a6

19 files changed

+493
-107
lines changed

lib/src/.DS_Store

6 KB
Binary file not shown.

lib/src/actions/actions.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,25 @@ abstract class InvertYSet
883883
static Serializer<InvertYSet> get serializer => _$invertYSetSerializer;
884884
}
885885

886+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
887+
// dynamically update helices
888+
889+
abstract class DynamicHelixUpdateSet
890+
with BuiltJsonSerializable
891+
implements
892+
Action,
893+
SvgPngCacheInvalidatingAction,
894+
Built<DynamicHelixUpdateSet, DynamicHelixUpdateSetBuilder> {
895+
bool get dynamically_update_helices;
896+
897+
/************************ begin BuiltValue boilerplate ************************/
898+
factory DynamicHelixUpdateSet({bool dynamically_update_helices}) = _$DynamicHelixUpdateSet._;
899+
900+
DynamicHelixUpdateSet._();
901+
902+
static Serializer<DynamicHelixUpdateSet> get serializer => _$dynamicHelixUpdateSetSerializer;
903+
}
904+
886905
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
887906
// warn on exit if unsaved
888907

lib/src/constants.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import 'state/grid.dart';
88

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

1414
// scadnano versions that we deploy so that older versions can be used.
1515
final scadnano_older_versions_to_link = [
16+
"0.19.2",
1617
"0.19.1",
1718
"0.18.10",
1819
"0.17.14",
@@ -473,3 +474,13 @@ const css_selector_context_menu_item_disabled = 'context_menu_item_disabled';
473474

474475
const default_vendor_scale = "25nm";
475476
const default_vendor_purification = "STD";
477+
478+
enum strand_bounds_status {
479+
helix_not_in_design,
480+
helix_out_of_bounds,
481+
min_offset_out_of_bounds,
482+
max_offset_out_of_bounds,
483+
in_bounds_with_min_offset_changes,
484+
in_bounds_with_max_offset_changes,
485+
in_bounds
486+
}

lib/src/middleware/export_svg.dart

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Map point_to_map(svg.Point point) {
186186
}
187187

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

226+
// fix the 5' extension rectangle missing
227+
RectElement create_portable_rect(RectElement ele) {
228+
RectElement portableEle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
229+
230+
portableEle.style.cssText = ele.style.cssText;
231+
if (portableEle.style.transformOrigin != "") {
232+
portableEle.style.transformOrigin = "";
233+
}
234+
// remove transform box attribute. dart doesn't support the normal way
235+
portableEle.style.cssText = portableEle.style.cssText.replaceAll(RegExp(r'transform-box:[^;]+;'), '');
236+
Rect pos = ele.getBBox();
237+
portableEle.setAttribute("x", pos.x.toString());
238+
portableEle.setAttribute("y", pos.y.toString());
239+
portableEle.setAttribute("rx", ele.rx.baseVal.value.toString());
240+
portableEle.setAttribute("ry", ele.ry.baseVal.value.toString());
241+
portableEle.setAttribute("width", pos.width.toString());
242+
portableEle.setAttribute("height", pos.height.toString());
243+
for (int i = 0; i < ele.transform.baseVal.numberOfItems; ++i) {
244+
Transform item = ele.transform.baseVal.getItem(i);
245+
if (item.angle != 0) {
246+
portableEle.setAttribute(
247+
"transform", "rotate(${item.angle} ${pos.x + pos.width / 2} ${pos.y + pos.height / 2})");
248+
}
249+
}
250+
return portableEle;
251+
}
252+
226253
// makes a svg compatible for PowerPoint
227254
Element make_portable(Element src) {
228255
var src_children = src.querySelectorAll("*");
229256
document.body.append(src);
230257
for (int i = 0; i < src_children.length; ++i) {
231-
if (src_children[i] is svg.TextContentElement) {
258+
if (src_children[i] is TextContentElement) {
232259
TextContentElement text_ele = src_children[i] as TextContentElement;
233260
if (text_ele.children.length == 1 && text_ele.children[0].tagName == "textPath") {
234261
continue;
235262
}
236263
List<TextContentElement> portable_eles = [];
237264
for (int j = 0; j < text_ele.getNumberOfChars(); ++j) {
238-
var char_ele = create_portable_element(text_ele, j);
265+
var char_ele = create_portable_text(text_ele, j);
239266
portable_eles.add(char_ele);
240267
}
241268
if (text_ele is TextPathElement) {
@@ -248,6 +275,10 @@ Element make_portable(Element src) {
248275
}
249276
portable_eles.forEach((v) => text_ele.parentNode.append(v));
250277
text_ele.remove();
278+
} else if (src_children[i] is RectElement) {
279+
RectElement portableEle = create_portable_rect(src_children[i] as RectElement);
280+
src_children[i].parentNode.append(portableEle);
281+
src_children[i].remove();
251282
}
252283
}
253284
src.remove();
@@ -337,6 +368,8 @@ const List<String> path_styles = [
337368
'stroke-linecap',
338369
'stroke-opacity',
339370
'visibility',
371+
'transform-box',
372+
'transform-origin',
340373
];
341374

342375
final relevant_styles = {

lib/src/middleware/system_clipboard.dart

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:scadnano/src/state/clipboard.dart';
88
import 'package:scadnano/src/state/group.dart';
99
import 'package:scadnano/src/state/modification.dart';
1010
import 'package:scadnano/src/state/strand.dart';
11+
import 'package:scadnano/src/reducers/strands_move_reducer.dart';
1112

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

81-
var paste_commit_action = actions.StrandsMoveCommit(strands_move: strands_move, autopaste: true);
82-
83-
store.dispatch(paste_commit_action);
82+
List<actions.UndoableAction> batch_actions_list = [];
83+
Map new_strand_moving_details = get_strand_bounds_details(store.state.design, strands_move);
84+
if (store.state.ui_state.dynamically_update_helices) {
85+
if (new_strand_moving_details['status'] == constants.strand_bounds_status.min_offset_out_of_bounds ||
86+
new_strand_moving_details['status'] == constants.strand_bounds_status.max_offset_out_of_bounds) {
87+
var helices_offset_change_action;
88+
for (int helix_idx in new_strand_moving_details['offsets'].keys) {
89+
if (new_strand_moving_details['status'] == constants.strand_bounds_status.min_offset_out_of_bounds)
90+
helices_offset_change_action = actions.HelixOffsetChange(
91+
helix_idx: helix_idx, min_offset: new_strand_moving_details['offsets'][helix_idx]);
92+
else
93+
helices_offset_change_action = actions.HelixOffsetChange(
94+
helix_idx: helix_idx, max_offset: new_strand_moving_details['offsets'][helix_idx]);
95+
96+
batch_actions_list.add(helices_offset_change_action);
97+
}
98+
}
99+
}
100+
if (new_strand_moving_details['status'] == constants.strand_bounds_status.in_bounds ||
101+
new_strand_moving_details['status'] ==
102+
constants.strand_bounds_status.in_bounds_with_min_offset_changes ||
103+
new_strand_moving_details['status'] ==
104+
constants.strand_bounds_status.in_bounds_with_max_offset_changes) {
105+
var paste_commit_action = actions.StrandsMoveCommit(strands_move: strands_move, autopaste: true);
106+
batch_actions_list.add(paste_commit_action);
107+
var batch_action =
108+
actions.BatchAction(batch_actions_list, 'Changing helix offsets and then executing autopaste');
109+
store.dispatch(batch_action);
110+
}
84111
}
85112

86113
// either

lib/src/reducers/app_ui_state_reducer.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import 'package:built_collection/built_collection.dart';
44
import 'package:redux/redux.dart';
55
import 'package:scadnano/src/reducers/design_reducer.dart';
66
import 'package:scadnano/src/reducers/strands_copy_info_reducer.dart';
7+
import 'package:scadnano/src/state/design.dart';
78
import 'package:scadnano/src/state/base_pair_display_type.dart';
89
import 'package:scadnano/src/state/dna_assign_options.dart';
910
import 'package:scadnano/src/state/modification.dart';
1011
import 'package:scadnano/src/state/strand.dart';
1112
import 'package:scadnano/src/state/copy_info.dart';
1213
import 'package:scadnano/src/state/substrand.dart';
1314
import 'package:scadnano/src/util.dart';
15+
import 'package:tuple/tuple.dart';
1416
import '../reducers/context_menu_reducer.dart';
1517
import '../state/example_designs.dart';
1618
import '../state/grid_position.dart';
@@ -188,6 +190,9 @@ bool show_unpaired_insertion_deletions_reducer(bool _, actions.ShowUnpairedInser
188190

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

193+
bool dynamic_helix_update_reducer(bool _, actions.DynamicHelixUpdateSet action) =>
194+
action.dynamically_update_helices;
195+
191196
bool warn_on_exit_if_unsaved_reducer(bool _, actions.WarnOnExitIfUnsavedSet action) => action.warn;
192197

193198
bool show_helix_circles_main_view_reducer(bool _, actions.ShowHelixCirclesMainViewSet action) =>
@@ -438,6 +443,7 @@ AppUIStateStorables app_ui_state_storable_local_reducer(AppUIStateStorables stor
438443
..show_domain_name_mismatches = TypedReducer<bool, actions.ShowDomainNameMismatchesSet>(show_domain_name_mismatches_reducer)(storables.show_domain_name_mismatches, action)
439444
..show_unpaired_insertion_deletions = TypedReducer<bool, actions.ShowUnpairedInsertionDeletionsSet>(show_unpaired_insertion_deletions_reducer)(storables.show_unpaired_insertion_deletions, action)
440445
..invert_y = TypedReducer<bool, actions.InvertYSet>(invert_y_reducer)(storables.invert_y, action)
446+
..dynamically_update_helices = TypedReducer<bool, actions.DynamicHelixUpdateSet>(dynamic_helix_update_reducer)(storables.dynamically_update_helices, action)
441447
..warn_on_exit_if_unsaved = TypedReducer<bool, actions.WarnOnExitIfUnsavedSet>(warn_on_exit_if_unsaved_reducer)(storables.warn_on_exit_if_unsaved, action)
442448
..show_helix_circles_main_view = TypedReducer<bool, actions.ShowHelixCirclesMainViewSet>(show_helix_circles_main_view_reducer)(storables.show_helix_circles_main_view, action)
443449
..show_helix_components_main_view = TypedReducer<bool, actions.ShowHelixComponentsMainViewSet>(show_helix_components_main_view_reducer)(storables.show_helix_components_main_view, action)
@@ -601,7 +607,22 @@ AppUIState ui_state_global_reducer(AppUIState ui_state, AppState state, action)
601607
..strands_move = strands_move_global_reducer(ui_state.strands_move, state, action)?.toBuilder()
602608
..domains_move = domains_move_global_reducer(ui_state.domains_move, state, action)?.toBuilder()
603609
..strand_creation = strand_creation_global_reducer(ui_state.strand_creation, state, action)?.toBuilder()
604-
..copy_info = copy_info_global_reducer(ui_state.copy_info, state, action)?.toBuilder());
610+
..copy_info = copy_info_global_reducer(ui_state.copy_info, state, action)?.toBuilder()
611+
..original_helix_offsets =
612+
original_helix_offsets_reducer(ui_state.original_helix_offsets, state, action).toBuilder());
613+
614+
BuiltMap<int, Tuple2> original_helix_offsets_reducer(
615+
BuiltMap<int, Tuple2> original_helix_offsets, AppState state, action) {
616+
if (action is actions.StrandsMoveStartSelectedStrands || action is actions.StrandCreateStart) {
617+
var helix_offsets = original_helix_offsets.toMap();
618+
for (int key in state.design.helices.keys) {
619+
var helix = state.design.helices[key];
620+
helix_offsets[state.design.helices[key].idx] = Tuple2.fromList([helix.min_offset, helix.max_offset]);
621+
}
622+
return helix_offsets.build();
623+
}
624+
return original_helix_offsets;
625+
}
605626

606627
GlobalReducer<BuiltList<MouseoverData>, AppState> mouseover_datas_global_reducer = combineGlobalReducers([
607628
TypedGlobalReducer<BuiltList<MouseoverData>, AppState, actions.HelixRollSetAtOther>(

lib/src/reducers/helices_reducer.dart

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import 'package:collection/collection.dart';
44
import 'package:redux/redux.dart';
55
import 'package:built_collection/built_collection.dart';
66
import 'package:scadnano/src/reducers/groups_reducer.dart';
7+
import 'package:scadnano/src/reducers/strands_move_reducer.dart';
78
import 'package:scadnano/src/state/group.dart';
9+
import 'package:scadnano/src/state/strand_creation.dart';
10+
import 'package:scadnano/src/state/strands_move.dart';
11+
import 'package:scadnano/src/state/substrand.dart';
812
import '../reducers/util_reducer.dart';
913
import '../state/app_state.dart';
1014

@@ -51,6 +55,13 @@ GlobalReducer<BuiltMap<int, Helix>, AppState> helices_global_reducer = combineGl
5155
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.HelixMaxOffsetSetByDomainsAllSameMax>(
5256
helix_max_offset_set_by_domains_all_same_max_reducer,
5357
),
58+
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.StrandsMoveAdjustAddress>(
59+
helix_offset_change_all_with_moving_strands_reducer),
60+
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.StrandCreateAdjustOffset>(
61+
helix_offset_change_all_while_creating_strand_reducer),
62+
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.ReplaceStrands>(first_replace_strands_reducer),
63+
TypedGlobalReducer<BuiltMap<int, Helix>, AppState, actions.SelectionsClear>(
64+
reset_helices_offsets_after_selections_clear)
5465
]);
5566

5667
BuiltMap<int, Helix> helix_individual_reducer(
@@ -186,6 +197,136 @@ Helix _change_offset_one_helix(Helix helix, int min_offset, int max_offset) => h
186197
..min_offset = min_offset ?? helix.min_offset
187198
..max_offset = max_offset ?? helix.max_offset);
188199

200+
BuiltMap<int, Helix> helix_offset_change_all_with_moving_strands_reducer(
201+
BuiltMap<int, Helix> helices, AppState state, actions.StrandsMoveAdjustAddress action) {
202+
if (state.ui_state.dynamically_update_helices) {
203+
StrandsMove new_strands_move =
204+
state.ui_state.strands_move.rebuild((b) => b..current_address.replace(action.address));
205+
Map strand_bounds_details = get_strand_bounds_details(state.design, new_strands_move);
206+
constants.strand_bounds_status status = strand_bounds_details['status'];
207+
208+
var offsets = strand_bounds_details['offsets'];
209+
if (status == constants.strand_bounds_status.min_offset_out_of_bounds ||
210+
status == constants.strand_bounds_status.in_bounds_with_min_offset_changes) {
211+
Helix map_func(int idx, Helix helix) => _change_offset_one_helix(helix, offsets[idx], null);
212+
helices = helices.map_values(map_func);
213+
} else if (status == constants.strand_bounds_status.max_offset_out_of_bounds ||
214+
status == constants.strand_bounds_status.in_bounds_with_max_offset_changes) {
215+
Helix map_func(int idx, Helix helix) => _change_offset_one_helix(helix, null, offsets[idx]);
216+
helices = helices.map_values(map_func);
217+
}
218+
}
219+
return helices;
220+
}
221+
222+
BuiltMap<int, Helix> helix_offset_change_all_while_creating_strand_reducer(
223+
BuiltMap<int, Helix> helices, AppState state, actions.StrandCreateAdjustOffset action) {
224+
if (state.ui_state.dynamically_update_helices) {
225+
StrandCreation strand_creation = state.ui_state.strand_creation;
226+
if (strand_creation != null) {
227+
var helices_map = helices.toMap();
228+
var original_helix_offsets = state.ui_state.original_helix_offsets;
229+
230+
// Increase helix size according to strand movement
231+
if (helices_map[strand_creation.helix.idx].min_offset > action.offset) {
232+
helices_map[strand_creation.helix.idx] =
233+
helices_map[strand_creation.helix.idx].rebuild((b) => b..min_offset = action.offset);
234+
return helices_map.build();
235+
}
236+
if (helices_map[strand_creation.helix.idx].max_offset <= action.offset) {
237+
helices_map[strand_creation.helix.idx] =
238+
helices_map[strand_creation.helix.idx].rebuild((b) => b..max_offset = action.offset + 1);
239+
return helices_map.build();
240+
}
241+
242+
// Decrease helix size according to strand movement
243+
if (action.offset > helices_map[strand_creation.helix.idx].min_offset &&
244+
helices_map[strand_creation.helix.idx].min_offset <
245+
original_helix_offsets[strand_creation.helix.idx].item1) {
246+
helices_map[strand_creation.helix.idx] =
247+
helices_map[strand_creation.helix.idx].rebuild((b) => b..min_offset = action.offset);
248+
return helices_map.build();
249+
}
250+
if (action.offset < helices_map[strand_creation.helix.idx].max_offset + 1 &&
251+
helices_map[strand_creation.helix.idx].max_offset >
252+
original_helix_offsets[strand_creation.helix.idx].item2) {
253+
helices_map[strand_creation.helix.idx] =
254+
helices_map[strand_creation.helix.idx].rebuild((b) => b.max_offset = action.offset + 1);
255+
return helices_map.build();
256+
}
257+
}
258+
}
259+
return helices;
260+
}
261+
262+
BuiltMap<int, Helix> first_replace_strands_reducer(
263+
BuiltMap<int, Helix> helices, AppState state, actions.ReplaceStrands action) {
264+
Map changed_strands = action.new_strands.toMap();
265+
Map<int, int> min_offsets = {};
266+
Map<int, int> max_offsets = {};
267+
for (int key in changed_strands.keys) {
268+
Strand strand = changed_strands[key];
269+
List<Substrand> substrands = strand.substrands.toList();
270+
for (var domain in substrands) {
271+
if (domain is Domain) {
272+
int helix_idx = domain.helix;
273+
if (domain.start < helices[helix_idx].min_offset) {
274+
if (min_offsets.containsKey(helix_idx))
275+
min_offsets[helix_idx] = [min_offsets[helix_idx], domain.start].min;
276+
else
277+
min_offsets[helix_idx] = domain.start;
278+
}
279+
if (domain.end > helices[helix_idx].max_offset) {
280+
if (max_offsets.containsKey(helix_idx))
281+
max_offsets[helix_idx] = [max_offsets[helix_idx], domain.end].max;
282+
else
283+
max_offsets[helix_idx] = domain.end;
284+
}
285+
}
286+
}
287+
}
288+
var helices_map = helices.toMap();
289+
if (min_offsets.length > 0) {
290+
for (int helix_idx in min_offsets.keys) {
291+
helices_map[helix_idx] = helices_map[helix_idx].rebuild((b) => b..min_offset = min_offsets[helix_idx]);
292+
}
293+
}
294+
if (max_offsets.length > 0) {
295+
for (int helix_idx in max_offsets.keys) {
296+
helices_map[helix_idx] = helices_map[helix_idx].rebuild((b) => b..max_offset = max_offsets[helix_idx]);
297+
}
298+
}
299+
return helices_map.build();
300+
}
301+
302+
BuiltMap<int, Helix> reset_helices_offsets(BuiltMap<int, Helix> helices, AppState state) {
303+
var helices_updated = helices.toMap();
304+
var original_helix_offsets = state.ui_state.original_helix_offsets;
305+
for (int idx in original_helix_offsets.keys) {
306+
int current_helix_min_offset = state.design.min_offset_of_strands_at(idx);
307+
if (current_helix_min_offset >= original_helix_offsets[idx].item1) {
308+
helices_updated[idx] =
309+
helices_updated[idx].rebuild((b) => b.min_offset = original_helix_offsets[idx].item1);
310+
}
311+
int current_helix_max_offset = state.design.max_offset_of_strands_at(idx);
312+
if (current_helix_max_offset <= original_helix_offsets[idx].item2) {
313+
helices_updated[idx] =
314+
helices_updated[idx].rebuild((b) => b.max_offset = original_helix_offsets[idx].item2);
315+
}
316+
}
317+
return helices_updated.build();
318+
}
319+
320+
BuiltMap<int, Helix> reset_helices_offsets_after_selections_clear(
321+
BuiltMap<int, Helix> helices, AppState state, actions.SelectionsClear action) {
322+
return reset_helices_offsets(helices, state);
323+
}
324+
325+
// BuiltMap<int, Helix> reset_helices_offsets_after_creation(
326+
// BuiltMap<int, Helix> helices, AppState state, actions.StrandCreateStop action) {
327+
// return reset_helices_offsets(helices, state);
328+
// }
329+
189330
BuiltMap<int, Helix> helix_offset_change_all_reducer(
190331
BuiltMap<int, Helix> helices, AppState state, actions.HelixOffsetChangeAll action) {
191332
Helix map_func(_, Helix helix) => _change_offset_one_helix(helix, action.min_offset, action.max_offset);

0 commit comments

Comments
 (0)