Skip to content

Commit

Permalink
fixes #725: moved domains should be reselected after move
Browse files Browse the repository at this point in the history
  • Loading branch information
dave-doty committed Jan 15, 2022
1 parent 80cb5dd commit 17991c1
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 28 deletions.
6 changes: 5 additions & 1 deletion lib/src/actions/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2172,8 +2172,12 @@ abstract class DomainsMoveStartSelectedDomains
implements Action, Built<DomainsMoveStartSelectedDomains, DomainsMoveStartSelectedDomainsBuilder> {
Address get address;

BuiltMap<int, int> get original_helices_view_order_inverse;

/************************ begin BuiltValue boilerplate ************************/
factory DomainsMoveStartSelectedDomains({Address address}) = _$DomainsMoveStartSelectedDomains._;
factory DomainsMoveStartSelectedDomains(
{Address address,
BuiltMap<int, int> original_helices_view_order_inverse}) = _$DomainsMoveStartSelectedDomains._;

DomainsMoveStartSelectedDomains._();

Expand Down
2 changes: 2 additions & 0 deletions lib/src/middleware/all_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'oxdna_export.dart';
import 'periodic_save_design_local_storage.dart';
import 'reselect_moved_dna_ends.dart';
import 'reselect_moved_copied_strands.dart';
import 'reselect_moved_domains.dart';
import 'save_file.dart';
import 'export_svg.dart';
import 'local_storage.dart';
Expand Down Expand Up @@ -60,6 +61,7 @@ final all_middleware = List<Middleware<AppState>>.unmodifiable([
export_dna_sequences_middleware,
reselect_moved_dna_ends_middleware,
reselect_moved_copied_strands_middleware,
reselect_moved_domains_middleware,
selections_intersect_box_compute_middleware,
insertion_deletion_batching_middleware,
adjust_grid_position_middleware,
Expand Down
70 changes: 70 additions & 0 deletions lib/src/middleware/reselect_moved_domains.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:redux/redux.dart';
import 'package:built_collection/built_collection.dart';

import '../reducers/domains_move_reducer.dart' as domains_move_reducer;
import '../state/domains_move.dart';
import '../state/design.dart';
import '../state/address.dart';
import '../state/domain.dart';
import '../state/dna_end.dart';
import '../state/strand.dart';
import '../state/strands_move.dart';
import '../actions/actions.dart' as actions;
import '../state/app_state.dart';

reselect_moved_domains_middleware(Store<AppState> store, action, NextDispatcher next) {
// only reselect if there is more than 1 selected
// otherwise, if the user repeatedly clicks and drags one at a time,
// this builds up many selected items as they click each new one, moving all of them
if ((action is actions.DomainsMoveCommit) && action.domains_move.domains_moving.length > 1) {
Design old_design = store.state.design;
DomainsMove domains_move = action.domains_move;

if (!(domains_move_reducer.in_bounds(old_design, domains_move) &&
domains_move_reducer.is_allowable(old_design, domains_move) &&
domains_move.is_nontrivial)) {
return;
}

List<Address> addresses = [];

var new_address_helix_idx = domains_move.current_address.helix_idx;
var new_helix = old_design.helices[new_address_helix_idx];
var new_group = old_design.groups[new_helix.group];

BuiltList<int> new_helices_view_order = new_group.helices_view_order;
BuiltMap<int, int> old_helices_view_order_inverse =
domains_move.original_helices_view_order_inverse;

// first collect old addresses while design.end_to_substrand is still valid, convert them to
// their new addresses so we can look them up
for (Domain old_domain in domains_move.domains_moving) {
// Domain old_domain = strand.first_domain;
DNAEnd old_5p_end = old_domain.dnaend_5p;
int old_helix_view_order = old_helices_view_order_inverse[old_domain.helix];
int new_helix_view_order = old_helix_view_order + domains_move.delta_view_order;
int new_helix_idx = new_helices_view_order[new_helix_view_order];
int new_offset = old_5p_end.offset_inclusive + domains_move.delta_offset;
var new_forward = domains_move.delta_forward != old_domain.forward;
var address = Address(helix_idx: new_helix_idx, offset: new_offset, forward: new_forward);
addresses.add(address);
}

// then apply action to commit the move
next(action);

// now find new ends at given addresses
List<Domain> new_domains = [];
Design new_design = store.state.design;
// if domain polarity switched, the 3' end of each domain will now be where the 5' end was
BuiltMap<Address, Domain> address_to_domain =
domains_move.delta_forward ? new_design.address_3p_to_domain : new_design.address_5p_to_domain;
for (var address in addresses) {
Domain new_domain = address_to_domain[address];
new_domains.add(new_domain);
}
store.dispatch(actions.SelectAll(selectables: new_domains.toBuiltList(), only: true));
} else {
next(action);
}
}
4 changes: 4 additions & 0 deletions lib/src/reducers/domains_move_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ DomainsMove domains_move_start_selected_domains_reducer(
domains_moving: selected_domains.toBuiltList(),
all_domains: state.design.all_domains,
strands_with_domains_moving: strands_of_selected_domains.toBuiltList(),
original_helices_view_order_inverse: action.original_helices_view_order_inverse,
helices: state.design.helices,
groups: state.design.groups,
original_address: action.address);
Expand All @@ -47,6 +48,9 @@ DomainsMove domains_move_stop_reducer(DomainsMove domains_move, actions.DomainsM
// other domains
// - is_allowable checks whether the domain overlaps other domains

bool in_bounds_and_allowable(Design design, DomainsMove domains_move) =>
in_bounds(design, domains_move) && is_allowable(design, domains_move);

DomainsMove domains_adjust_address_reducer(
DomainsMove domains_move, AppState state, actions.DomainsMoveAdjustAddress action) {
DomainsMove new_domains_move = domains_move.rebuild((b) => b..current_address.replace(action.address));
Expand Down
56 changes: 40 additions & 16 deletions lib/src/state/design.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,15 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
if (helices != null) {
helix_builders = helices.map((e) => e.toBuilder());
} else if (num_helices != null) {

helix_builders = [
for (int idx in Iterable<int>.generate(num_helices))
HelixBuilder()
..idx = idx
..grid = grid
..geometry = geometry.toBuilder()
..grid_position = (grid == Grid.none ? null : default_grid_position(idx).toBuilder())
..position_ = grid != Grid.none ? null : default_position(geometry, idx).toBuilder()
];
for (int idx in Iterable<int>.generate(num_helices))
HelixBuilder()
..idx = idx
..grid = grid
..geometry = geometry.toBuilder()
..grid_position = (grid == Grid.none ? null : default_grid_position(idx).toBuilder())
..position_ = grid != Grid.none ? null : default_position(geometry, idx).toBuilder()
];
} else if (helix_builders != null) {
// We will use the parameter directly
} else {
Expand All @@ -95,7 +94,6 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,

set_helices_min_max_offsets(helix_builders_map, strands);


if (groups == null) {
groups = _calculate_groups_from_helix_builder(helix_builders, grid);
}
Expand All @@ -106,9 +104,7 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
var helices_map = {for (var helix in helices) helix.idx: helix};

for (var key in helices_map.keys) {
helices_map[key] = helices_map[key].rebuild((b) => b
..geometry.replace(geometry)
);
helices_map[key] = helices_map[key].rebuild((b) => b..geometry.replace(geometry));
}

var design = Design.from((b) => b
Expand Down Expand Up @@ -631,6 +627,32 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
return map.build();
}

/// Gets Domain with 5p end at given address (helix,offset,forward)
/// Offset is inclusive, i.e., dna_end.offset_inclusive
@memoized
BuiltMap<Address, Domain> get address_5p_to_domain {
var map = Map<Address, Domain>();
for (Domain domain in domains_by_id.values) {
var key = Address(
helix_idx: domain.helix, offset: domain.dnaend_5p.offset_inclusive, forward: domain.forward);
map[key] = domain;
}
return map.build();
}

/// Gets Domain with 3p end at given address (helix,offset,forward)
/// Offset is inclusive, i.e., dna_end.offset_inclusive
@memoized
BuiltMap<Address, Domain> get address_3p_to_domain {
var map = Map<Address, Domain>();
for (Domain domain in domains_by_id.values) {
var key = Address(
helix_idx: domain.helix, offset: domain.dnaend_3p.offset_inclusive, forward: domain.forward);
map[key] = domain;
}
return map.build();
}

/// Maps Addresses to PotentialVerticalCrossovers.
/// The end on TOP (i.e., lower helix idx) has the address with the key in the map.
@memoized
Expand Down Expand Up @@ -1188,7 +1210,6 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
strands.add(strand);
}


// build groups
Map<String, HelixGroup> groups_map =
group_builders_map.map((key, value) => MapEntry<String, HelixGroup>(key, value.build()));
Expand Down Expand Up @@ -1887,7 +1908,8 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
}
}

return Design(helix_builders: helix_builders.values, strands: strands, grid: grid_type, invert_y: invert_y);
return Design(
helix_builders: helix_builders.values, strands: strands, grid: grid_type, invert_y: invert_y);
}

/// Routine that will follow a cadnano v2 strand accross helices and create
Expand Down Expand Up @@ -2072,7 +2094,8 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
}
}

Map<String, HelixGroup> _calculate_groups_from_helix_builder(Iterable<HelixBuilder> helix_builders, Grid grid) {
Map<String, HelixGroup> _calculate_groups_from_helix_builder(
Iterable<HelixBuilder> helix_builders, Grid grid) {
if (helix_builders.isEmpty) {
return {constants.default_group_name: HelixGroup(grid: grid, helices_view_order: [])};
}
Expand Down Expand Up @@ -2336,6 +2359,7 @@ class StrandError extends IllegalDesignError {
class HelixPitchYaw {
double pitch;
double yaw;

// Helix idx of the first helix found with this pitch and yaw value
int helix_idx;

Expand Down
16 changes: 13 additions & 3 deletions lib/src/state/domains_move.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,26 @@ abstract class DomainsMove with BuiltJsonSerializable implements Built<DomainsMo
factory DomainsMove(
{BuiltList<Domain> domains_moving,
BuiltList<Domain> all_domains,
BuiltList<Strand> strands_with_domains_moving,
BuiltList<Strand> strands_with_domains_moving,
BuiltMap<int, Helix> helices,
BuiltMap<String, HelixGroup> groups,
BuiltMap<int, int> original_helices_view_order_inverse,
Address original_address,
bool copy = false,
bool keep_color = true}) {
var domains_fixed =
copy ? all_domains : [for (var domain in all_domains) if (!domains_moving.contains(domain)) domain];
var domains_fixed = copy
? all_domains
: [
for (var domain in all_domains)
if (!domains_moving.contains(domain)) domain
];
return DomainsMove.from((b) => b
..domains_moving.replace(domains_moving)
..domains_fixed.replace(domains_fixed)
..strands_with_domains_moving.replace(strands_with_domains_moving)
..helices.replace(helices)
..groups.replace(groups)
..original_helices_view_order_inverse.replace(original_helices_view_order_inverse)
..original_address.replace(original_address)
..current_address.replace(original_address)
..copy = copy
Expand All @@ -57,6 +63,10 @@ abstract class DomainsMove with BuiltJsonSerializable implements Built<DomainsMo

BuiltList<Strand> get strands_with_domains_moving;

// Since copied Domains may come from a different Design with different groups, we need to
// store this to know how to position them in new HelixGroups.
BuiltMap<int, int> get original_helices_view_order_inverse;

Address get original_address;

Address get current_address;
Expand Down
24 changes: 16 additions & 8 deletions lib/src/view/design_main_strand_domain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ mixin DesignMainDomainPropsMixin on UiProps {
Point<num> helix_svg_position;

List<ContextMenuItem> Function(Strand strand,
{@required Domain domain, @required Address address, @required ModificationType type}) context_menu_strand;
{@required Domain domain,
@required Address address,
@required ModificationType type}) context_menu_strand;

bool selected;

Expand All @@ -61,8 +63,10 @@ class DesignMainDomainComponent extends UiComponent2<DesignMainDomainProps>
Domain domain = props.domain;
String id = domain.id;

Point<num> start_svg = props.helix.svg_base_pos(domain.offset_5p, domain.forward, props.helix_svg_position.y);
Point<num> end_svg = props.helix.svg_base_pos(domain.offset_3p, domain.forward, props.helix_svg_position.y);
Point<num> start_svg =
props.helix.svg_base_pos(domain.offset_5p, domain.forward, props.helix_svg_position.y);
Point<num> end_svg =
props.helix.svg_base_pos(domain.offset_3p, domain.forward, props.helix_svg_position.y);

var classname = constants.css_selector_domain;
if (props.selected) {
Expand Down Expand Up @@ -131,9 +135,13 @@ class DesignMainDomainComponent extends UiComponent2<DesignMainDomainProps>
if (domain_selectable(props.domain)) {
// select/deselect
props.domain.handle_selection_mouse_down(event);
// set up drag detection for moving DNA ends
var address = util.find_closest_address(event, [props.helix], props.groups, props.geometry, {props.helix.idx: props.helix_svg_position}.build());
app.dispatch(actions.DomainsMoveStartSelectedDomains(address: address));
// set up drag detection for moving domains
var address = util.find_closest_address(event, [props.helix], props.groups, props.geometry,
{props.helix.idx: props.helix_svg_position}.build());
HelixGroup group = app.state.design.group_of_domain(props.domain);
var view_order_inverse = group.helices_view_order_inverse;
app.dispatch(actions.DomainsMoveStartSelectedDomains(
address: address, original_helices_view_order_inverse: view_order_inverse));
}
}
}
Expand Down Expand Up @@ -173,8 +181,8 @@ class DesignMainDomainComponent extends UiComponent2<DesignMainDomainProps>
if (!event.shiftKey) {
event.preventDefault();
event.stopPropagation();
Address address =
util.get_address_on_helix(event, props.helix, props.groups[props.helix.group], props.geometry, props.helix_svg_position);
Address address = util.get_address_on_helix(
event, props.helix, props.groups[props.helix.group], props.geometry, props.helix_svg_position);
app.dispatch(actions.ContextMenuShow(
context_menu: ContextMenu(
items: props.context_menu_strand(props.strand, domain: props.domain, address: address).build(),
Expand Down

0 comments on commit 17991c1

Please sign in to comment.