Skip to content

Commit 544a297

Browse files
committed
Implement better snapping when resizing wires
1 parent 00f67f6 commit 544a297

File tree

2 files changed

+141
-57
lines changed

2 files changed

+141
-57
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ iter_filter_is_some = "warn"
140140
iter_not_returning_iterator = "warn"
141141
iter_on_empty_collections = "warn"
142142
iter_on_single_items = "warn"
143-
iter_over_hash_type = "warn"
144143
iter_without_into_iter = "warn"
145144
large_digit_groups = "warn"
146145
large_include_file = "warn"

src/app.rs

Lines changed: 141 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -157,24 +157,17 @@ impl Instance {
157157
}
158158
}
159159

160-
fn move_pin(&mut self, pin: Pin, new_pos: Pos2) {
161-
match &mut self.ty {
160+
fn move_pin_delta(&self, pin: Pin, new_pos: Pos2) -> Vec2 {
161+
match &self.ty {
162162
InstanceType::Wire(wire) => {
163-
// TODO: Maybe wire.start and wire.end can go to a vector of size 2?
164163
if pin.index == 0 {
165-
wire.start = new_pos;
164+
new_pos - wire.start
166165
} else {
167-
wire.end = new_pos;
166+
new_pos - wire.end
168167
}
169168
}
170-
InstanceType::Gate(gate) => {
171-
let move_vec = new_pos - pin.pos;
172-
gate.pos += move_vec;
173-
}
174-
InstanceType::Power(power) => {
175-
let move_vec = new_pos - pin.pos;
176-
power.pos += move_vec;
177-
}
169+
InstanceType::Gate(_gate) => new_pos - pin.pos,
170+
InstanceType::Power(_power) => new_pos - pin.pos,
178171
}
179172
}
180173
}
@@ -322,7 +315,7 @@ impl PanelDrag {
322315
}
323316
}
324317

325-
#[derive(serde::Deserialize, serde::Serialize, Default)]
318+
#[derive(serde::Deserialize, serde::Serialize, Debug, Default)]
326319
pub struct CanvasDrag {
327320
id: InstanceId,
328321
/// Offset from mouse pointer to gate center at drag start
@@ -342,22 +335,82 @@ pub struct Pin {
342335
pub index: u32,
343336
}
344337

338+
impl Pin {
339+
/// Compute this pin's current world position from its instance.
340+
/// This ignores the stored `pos` and derives from the instance's data.
341+
pub fn _position_from(&self, ins: &Instance) -> Pos2 {
342+
match ins.ty {
343+
InstanceType::Gate(g) => {
344+
let info = g.kind.graphics().pins[self.index as usize];
345+
g.pos + info.offset
346+
}
347+
InstanceType::Power(p) => {
348+
let info = p.graphics().pins[self.index as usize];
349+
p.pos + info.offset
350+
}
351+
InstanceType::Wire(w) => {
352+
if self.index == 0 {
353+
w.start
354+
} else {
355+
w.end
356+
}
357+
}
358+
}
359+
}
360+
}
361+
345362
impl Hash for Pin {
346363
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
347364
self.ins.hash(state);
348365
self.index.hash(state);
349366
}
350367
}
351368

352-
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
369+
#[derive(serde::Deserialize, serde::Serialize, Debug, Eq, Clone, Copy)]
353370
pub struct Connection {
354371
pin1: Pin,
355372
pin2: Pin,
356373
}
357374

358375
impl Connection {
359376
fn new(pin1: Pin, pin2: Pin) -> Self {
360-
Self { pin1, pin2 }
377+
let (a, b) = if (pin2.ins, pin2.index) < (pin1.ins, pin1.index) {
378+
(pin2, pin1)
379+
} else {
380+
(pin1, pin2)
381+
};
382+
Self { pin1: a, pin2: b }
383+
}
384+
385+
/// Return pins in the order that starts with pin from instance id
386+
fn get_pin(&self, moving_instance_id: InstanceId) -> Option<(Pin, Pin)> {
387+
if self.pin1.ins == moving_instance_id {
388+
Some((self.pin1, self.pin2))
389+
} else if self.pin2.ins == moving_instance_id {
390+
Some((self.pin2, self.pin1))
391+
} else {
392+
None
393+
}
394+
}
395+
}
396+
397+
impl PartialEq for Connection {
398+
fn eq(&self, other: &Self) -> bool {
399+
// Order-insensitive equality thanks to normalization
400+
self.pin1.ins == other.pin1.ins
401+
&& self.pin1.index == other.pin1.index
402+
&& self.pin2.ins == other.pin2.ins
403+
&& self.pin2.index == other.pin2.index
404+
}
405+
}
406+
407+
impl Hash for Connection {
408+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
409+
// Order-insensitive hash thanks to normalization
410+
self.pin1.ins.hash(state);
411+
self.pin1.index.hash(state);
412+
self.pin2.ins.hash(state);
413+
self.pin2.index.hash(state);
361414
}
362415
}
363416

@@ -421,8 +474,8 @@ impl TemplateApp {
421474
ui.add_sized(
422475
vec2(200.0, 50.0),
423476
egui::TextEdit::multiline(&mut format!(
424-
"world {:#?}\n conn: {:#?}",
425-
self.instances, self.connections
477+
"world {:#?}\n conn: {:#?}\n moving: {:#?}\n\n\n",
478+
self.instances, self.connections, self.canvas_drag
426479
)),
427480
)
428481
});
@@ -652,67 +705,63 @@ impl TemplateApp {
652705
let mut possible_connections = HashSet::new();
653706
for self_ins in &self.instances {
654707
log::info!("{self_ins:#?}");
655-
let self_pins = self_ins.pins();
656-
for self_pin in self_pins {
708+
for self_pin in self_ins.pins() {
657709
for other_ins in &self.instances {
658710
if self_ins.id == other_ins.id {
659711
continue;
660712
}
661-
662713
for other_pin in other_ins.pins() {
663714
if self_pin.pos.distance(other_pin.pos) > threshold {
664715
continue;
665716
}
666717

667718
log::info!("{self_pin:#?}, {other_pin:#?}");
668719
let t = Connection::new(self_pin, other_pin);
669-
let t_other = Connection::new(other_pin, self_pin);
670-
// TODO: Use the hashset hashing instead of this check
671-
let found = possible_connections.contains(&t)
672-
|| possible_connections.contains(&t_other);
720+
// To prevent pin1, pin2 and pin2, pin1 connections
721+
let found = possible_connections.contains(&t);
673722
if !found {
674723
possible_connections.insert(t);
675724
}
676725
}
677726
}
678727
}
679728
}
680-
// paint connected pins (iterate in a stable order)
681-
let mut conns: Vec<_> = possible_connections.iter().collect();
682-
conns.sort_by_key(|c| {
683-
(
684-
c.pin1.pos.x.to_bits(),
685-
c.pin1.pos.y.to_bits(),
686-
c.pin2.pos.x.to_bits(),
687-
c.pin2.pos.y.to_bits(),
688-
)
689-
});
690-
for conn in conns {
691-
if conn.pin1.ins == moving_instance_id {
692-
ui.painter()
693-
.circle_filled(conn.pin1.pos, 10.0, Color32::LIGHT_YELLOW);
694-
} else if conn.pin2.ins == moving_instance_id {
729+
// TODO: inefficient but okay for now. We want to highlight connections for moving
730+
// object.
731+
for conn in &possible_connections {
732+
if let Some((pin, _)) = conn.get_pin(moving_instance_id) {
695733
ui.painter()
696-
.circle_filled(conn.pin2.pos, 10.0, Color32::LIGHT_YELLOW);
734+
.circle_filled(pin.pos, 10.0, Color32::LIGHT_YELLOW);
697735
}
698736
}
699-
// snap connections together
700737
if mouse_up {
701-
// snap in a stable order
702-
let mut conns: Vec<_> = possible_connections.iter().collect();
703-
conns.sort_by_key(|c| {
704-
(
705-
c.pin1.pos.x.to_bits(),
706-
c.pin1.pos.y.to_bits(),
707-
c.pin2.pos.x.to_bits(),
708-
c.pin2.pos.y.to_bits(),
709-
)
710-
});
711-
for conn in conns {
712-
let instance = self.get_instance_mut(conn.pin1.ins);
713-
instance.move_pin(conn.pin1, conn.pin2.pos);
738+
for conn in &possible_connections {
739+
let (pin1, pin2) = if let Some((pin1, pin2)) = conn.get_pin(moving_instance_id)
740+
{
741+
(pin1, pin2)
742+
} else {
743+
(conn.pin1, conn.pin2)
744+
};
745+
let instance = self.get_instance(pin1.ins);
746+
if let InstanceType::Wire(_) = instance.ty {
747+
let wire = self.get_wire_mut(instance.id);
748+
if pin1.index == 0 {
749+
wire.start = pin2.pos;
750+
} else {
751+
wire.end = pin2.pos;
752+
};
753+
self.resize = None;
754+
} else {
755+
let delta = { instance.move_pin_delta(pin1, pin2.pos) };
756+
self.mov_component_with_connected(pin1.ins, delta, canvas_rect);
757+
}
714758
}
715-
self.connections = possible_connections;
759+
for conn in &possible_connections {
760+
self.connections.insert(*conn);
761+
}
762+
// Remove any connections not present in possible_connections
763+
self.connections
764+
.retain(|conn| possible_connections.contains(conn));
716765
}
717766
}
718767

@@ -863,6 +912,10 @@ impl TemplateApp {
863912
.expect("should not happen")
864913
}
865914

915+
fn get_instance(&self, id: InstanceId) -> &Instance {
916+
self.instances.get(id.usize()).expect("should not happen")
917+
}
918+
866919
fn get_connected_instances(&self, id: InstanceId) -> Vec<InstanceId> {
867920
let mut connecteds = Vec::new();
868921
let mut conns: Vec<_> = self.connections.iter().collect();
@@ -1019,3 +1072,35 @@ fn rotate_point(point: Pos2, origin: Pos2, angle: f32) -> Pos2 {
10191072

10201073
Pos2::new(xnew + origin.x, ynew + origin.y)
10211074
}
1075+
1076+
#[cfg(test)]
1077+
mod tests {
1078+
use super::*;
1079+
1080+
#[test]
1081+
fn connection_normalization_and_hash_eq() {
1082+
let a = Pin {
1083+
pos: pos2(0.0, 0.0),
1084+
ins: InstanceId(1),
1085+
index: 0,
1086+
};
1087+
let b = Pin {
1088+
pos: pos2(10.0, 0.0),
1089+
ins: InstanceId(2),
1090+
index: 1,
1091+
};
1092+
1093+
let c1 = Connection::new(a, b);
1094+
let c2 = Connection::new(b, a); // swapped
1095+
1096+
assert_eq!(c1, c2);
1097+
1098+
let mut set: HashSet<Connection> = HashSet::new();
1099+
let inserted1 = set.insert(c1);
1100+
let inserted2 = set.insert(c2);
1101+
1102+
assert!(inserted1);
1103+
assert!(!inserted2);
1104+
assert_eq!(set.len(), 1);
1105+
}
1106+
}

0 commit comments

Comments
 (0)