Skip to content

Commit 4634452

Browse files
committed
Better movements
1 parent e11694a commit 4634452

File tree

2 files changed

+174
-127
lines changed

2 files changed

+174
-127
lines changed

src/app.rs

Lines changed: 168 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub const PIN_HOVER_THRESHOLD: f32 = 10.0;
3737
pub const COLOR_POTENTIAL_CONN_HIGHLIGHT: Color32 = Color32::LIGHT_BLUE;
3838
pub const WIRE_HIT_DISTANCE: f32 = 10.0;
3939
pub const SNAP_THRESHOLD: f32 = 10.0;
40+
pub const PIN_MOVE_HINT_D: f32 = 10.0;
41+
pub const PIN_MOVE_HINT_COLOR: Color32 = Color32::GRAY;
4042

4143
pub const COLOR_SELECTION_HIGHLIGHT: Color32 = Color32::GRAY;
4244
pub const COLOR_SELECTION_BOX: Color32 = Color32::LIGHT_BLUE;
@@ -415,7 +417,8 @@ pub struct App {
415417
pub current_dirty: bool,
416418
pub show_debug: bool,
417419
// selection set and move preview
418-
pub selected: std::collections::HashSet<InstanceId>,
420+
pub selected: HashSet<InstanceId>,
421+
pub clicked_on: Option<InstanceId>,
419422
//Copied. Items with their offset compared to a middle point in the rectangle
420423
pub clipboard: Vec<InstancePosOffset>,
421424
// Where are we in the world
@@ -455,6 +458,7 @@ impl Default for App {
455458
current_dirty: true,
456459
show_debug: true,
457460
selected: Default::default(),
461+
clicked_on: Default::default(),
458462
clipboard: Default::default(),
459463
pending_load_json: None,
460464
viewport_offset: Vec2::ZERO,
@@ -554,7 +558,7 @@ impl App {
554558
ui.separator();
555559
ui.vertical(|ui| {
556560
ui.heading("Canvas");
557-
ui.label("press d to remove object");
561+
ui.label("press backspace/d to remove object");
558562
ui.label("right click on powers to toggle");
559563
ui.label("right click on canvas to drag");
560564
self.draw_canvas(ui);
@@ -654,26 +658,13 @@ impl App {
654658
resp
655659
}
656660

657-
fn draw_canvas(&mut self, ui: &mut Ui) {
658-
let (resp, _painter) = ui.allocate_painter(ui.available_size(), Sense::hover());
659-
let canvas_rect = resp.rect;
660-
661-
// Set clip rectangle to prevent canvas objects from drawing outside canvas bounds
662-
ui.set_clip_rect(canvas_rect);
663-
664-
Self::draw_grid(ui, canvas_rect, self.viewport_offset);
665-
666-
let mouse_up = ui.input(|i| i.pointer.any_released());
667-
let mouse_clicked = ui.input(|i| i.pointer.primary_down());
668-
let right_released = ui.input(|i| i.pointer.secondary_released());
669-
let right_down = ui.input(|i| i.pointer.secondary_down());
670-
let right_clicked = ui.input(|i| i.pointer.secondary_clicked());
671-
let mouse_pos_world = ui
672-
.ctx()
673-
.pointer_interact_pos()
674-
.map(|p| self.screen_to_world(p));
675-
let mouse_is_visible = ui.ctx().input(|i| i.pointer.has_pointer());
676-
661+
fn handle_panning(
662+
&mut self,
663+
ui: &Ui,
664+
right_down: bool,
665+
right_released: bool,
666+
mouse_is_visible: bool,
667+
) {
677668
if right_down && self.hovered.is_none() {
678669
self.panning = true;
679670
}
@@ -682,10 +673,12 @@ impl App {
682673
self.panning = false;
683674
}
684675

685-
if self.panning && right_down {
676+
if self.panning {
686677
self.viewport_offset += ui.input(|i| i.pointer.delta());
687678
}
679+
}
688680

681+
fn handle_copy_pasting(&mut self, ui: &Ui, mouse_pos_world: Option<Pos2>) {
689682
let mut copy_event_detected = false;
690683
let mut paste_event_detected = false;
691684
ui.ctx().input(|i| {
@@ -701,22 +694,6 @@ impl App {
701694
}
702695
});
703696

704-
let d_pressed = ui.input(|i| i.key_pressed(egui::Key::D));
705-
706-
if d_pressed && let Some(id) = self.hovered.take() {
707-
let id = match id {
708-
Hover::Pin(pin) => pin.ins,
709-
Hover::Instance(instance_id) => instance_id,
710-
};
711-
self.delete_instance(id);
712-
} else if d_pressed && self.hovered.is_none() && !self.selected.is_empty() {
713-
// Collect IDs to delete to avoid mutable borrow conflict
714-
let ids_to_delete: Vec<InstanceId> = self.selected.drain().collect();
715-
for id in ids_to_delete {
716-
self.delete_instance(id);
717-
}
718-
}
719-
720697
if copy_event_detected && !self.selected.is_empty() {
721698
self.copy_to_clipboard();
722699
}
@@ -727,22 +704,100 @@ impl App {
727704
{
728705
self.paste_from_clipboard(mouse);
729706
}
707+
}
708+
709+
fn handle_deletion(&mut self, ui: &Ui) {
710+
let bs_pressed = ui.input(|i| i.key_pressed(egui::Key::Backspace));
711+
let d_pressed = ui.input(|i| i.key_pressed(egui::Key::D));
712+
713+
if bs_pressed || d_pressed {
714+
if let Some(id) = self.hovered.take() {
715+
let id = match id {
716+
Hover::Pin(pin) => pin.ins,
717+
Hover::Instance(instance_id) => instance_id,
718+
};
719+
self.delete_instance(id);
720+
} else if self.hovered.is_none() && !self.selected.is_empty() {
721+
// Collect Iavoid mutable borrow conflict
722+
let ids_to_delete: Vec<InstanceId> = self.selected.drain().collect();
723+
for id in ids_to_delete {
724+
self.delete_instance(id);
725+
}
726+
}
727+
}
728+
}
729+
730+
pub fn delete_instance(&mut self, id: InstanceId) {
731+
self.db.instances.remove(id);
732+
self.db.types.remove(id);
733+
self.db.gates.remove(id);
734+
self.db.powers.remove(id);
735+
self.db.wires.remove(id);
736+
self.db.custom_circuits.remove(id);
737+
self.db.connections.retain(|c| !c.involves_instance(id));
738+
self.hovered.take();
739+
self.drag.take();
740+
self.selected.remove(&id);
741+
self.current.retain(|p| p.ins != id);
742+
self.current_dirty = true;
743+
}
730744

731-
if let Some(mouse_world) = mouse_pos_world {
745+
fn draw_canvas(&mut self, ui: &mut Ui) {
746+
let (resp, _painter) = ui.allocate_painter(ui.available_size(), Sense::hover());
747+
let canvas_rect = resp.rect;
748+
749+
// Set clip rectangle to prevent canvas objects from drawing outside canvas bounds
750+
ui.set_clip_rect(canvas_rect);
751+
752+
Self::draw_grid(ui, canvas_rect, self.viewport_offset);
753+
754+
let mouse_up = ui.input(|i| i.pointer.any_released());
755+
let mouse_clicked = ui.input(|i| i.pointer.primary_down());
756+
let right_released = ui.input(|i| i.pointer.secondary_released());
757+
let right_down = ui.input(|i| i.pointer.secondary_down());
758+
let right_clicked = ui.input(|i| i.pointer.secondary_clicked());
759+
let mouse_pos_world = ui
760+
.ctx()
761+
.pointer_interact_pos()
762+
.map(|p| self.screen_to_world(p));
763+
let mouse_is_visible = ui.ctx().input(|i| i.pointer.has_pointer());
764+
765+
self.handle_panning(ui, right_down, right_released, mouse_is_visible);
766+
self.handle_copy_pasting(ui, mouse_pos_world);
767+
self.handle_deletion(ui);
768+
769+
if let Some(mouse) = mouse_pos_world {
732770
if mouse_clicked {
733-
self.handle_drag_start_canvas(mouse_world);
771+
self.handle_drag_start_canvas(mouse);
734772
}
773+
735774
if self.drag.is_some() {
736-
self.handle_dragging(ui, mouse_world);
775+
self.handle_dragging(ui, mouse);
737776
} else {
738-
self.hovered = self.get_hovered(mouse_world);
777+
self.hovered = self.get_hovered(mouse);
739778
}
740-
}
741779

742-
if mouse_up && let Some(mouse) = mouse_pos_world {
743-
self.handle_drag_end(&canvas_rect, mouse);
780+
if mouse_clicked && let Some(Hover::Instance(instance_id)) = self.hovered {
781+
self.clicked_on = Some(instance_id);
782+
}
783+
if mouse_up
784+
&& let Some(clicked_on) = self.clicked_on
785+
&& let Some(hovered) = self.hovered
786+
&& clicked_on == hovered.instance()
787+
{
788+
self.selected.clear();
789+
self.selected.insert(clicked_on);
790+
}
791+
792+
if mouse_up {
793+
self.handle_drag_end(mouse);
794+
}
795+
}
796+
if self.selected.len() == 1 {
797+
self.highlight_selected_actions(ui, mouse_pos_world, mouse_clicked);
744798
}
745799

800+
// Toggle power
746801
if right_clicked
747802
&& let Some(id) = self.hovered.as_ref().map(|i| i.instance())
748803
&& matches!(self.db.ty(id), InstanceKind::Power)
@@ -765,6 +820,7 @@ impl App {
765820
self.recompute_current();
766821
}
767822

823+
// Draw world
768824
for (id, gate) in &self.db.gates {
769825
self.draw_gate(ui, id, gate);
770826
}
@@ -779,7 +835,9 @@ impl App {
779835
self.draw_wire(
780836
ui,
781837
*wire,
782-
self.hovered.as_ref().is_some_and(|f| f.instance() == id),
838+
self.hovered
839+
.as_ref()
840+
.is_some_and(|f| matches!(f, Hover::Instance(_)) && f.instance() == id),
783841
has_current,
784842
);
785843
}
@@ -1063,18 +1121,25 @@ impl App {
10631121
}
10641122

10651123
pub fn get_hovered(&self, mouse_pos: Pos2) -> Option<Hover> {
1066-
// Prioritize smaller/overlay items first: Power over Gates, then Wires
1067-
for (k, wire) in &self.db.wires {
1068-
for pin in self.db.pins_of(k) {
1069-
if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD {
1070-
return Some(Hover::Pin(pin));
1124+
// Give higher priority to selected instances when hovering
1125+
for selected in &self.selected {
1126+
match self.db.ty(*selected) {
1127+
InstanceKind::Wire => {
1128+
for pin in self.db.pins_of(*selected) {
1129+
if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD {
1130+
return Some(Hover::Pin(pin));
1131+
}
1132+
}
1133+
let wire = self.db.get_wire(*selected);
1134+
let dist = wire.distance_point_to_segment(mouse_pos);
1135+
if dist < WIRE_HIT_DISTANCE {
1136+
return Some(Hover::Instance(*selected));
1137+
}
10711138
}
1072-
}
1073-
let dist = wire.distance_point_to_segment(mouse_pos);
1074-
if dist < WIRE_HIT_DISTANCE {
1075-
return Some(Hover::Instance(k));
1139+
InstanceKind::Gate(_) | InstanceKind::Power | InstanceKind::CustomCircuit(_) => {}
10761140
}
10771141
}
1142+
10781143
for (k, power) in &self.db.powers {
10791144
let rect = Rect::from_center_size(power.pos, self.canvas_config.base_gate_size);
10801145
for pin in self.db.pins_of(k) {
@@ -1098,6 +1163,21 @@ impl App {
10981163
return Some(Hover::Instance(k));
10991164
}
11001165
}
1166+
for (k, wire) in &self.db.wires {
1167+
for pin in self.db.pins_of(k) {
1168+
if self.is_pin_connected(pin) {
1169+
continue;
1170+
}
1171+
if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD {
1172+
return Some(Hover::Pin(pin));
1173+
}
1174+
}
1175+
let dist = wire.distance_point_to_segment(mouse_pos);
1176+
if dist < WIRE_HIT_DISTANCE {
1177+
return Some(Hover::Instance(k));
1178+
}
1179+
}
1180+
11011181
None
11021182
}
11031183

@@ -1108,11 +1188,7 @@ impl App {
11081188

11091189
match hovered {
11101190
Hover::Pin(pin) => {
1111-
let color = if self.is_pin_connected(pin) {
1112-
COLOR_HOVER_PIN_DETACH
1113-
} else {
1114-
COLOR_HOVER_PIN_TO_WIRE
1115-
};
1191+
let color = COLOR_HOVER_PIN_TO_WIRE;
11161192
let pin_pos = self.db.pin_position(pin) - self.viewport_offset;
11171193
ui.painter()
11181194
.circle_filled(pin_pos, PIN_HOVER_THRESHOLD, color);
@@ -1700,6 +1776,37 @@ impl App {
17001776
self.show_right_click_actions_menu = false;
17011777
}
17021778
}
1779+
1780+
fn highlight_selected_actions(&mut self, ui: &Ui, mouse: Option<Pos2>, mouse_down: bool) {
1781+
let Some(selected) = self.selected.iter().next() else {
1782+
return;
1783+
};
1784+
let selected = *selected;
1785+
1786+
match self.db.ty(selected) {
1787+
InstanceKind::Wire => {
1788+
for pin in self.db.pins_of(selected) {
1789+
let pos = self.db.pin_position(pin);
1790+
ui.painter().circle_filled(
1791+
pos - self.viewport_offset,
1792+
PIN_MOVE_HINT_D,
1793+
PIN_MOVE_HINT_COLOR,
1794+
);
1795+
1796+
if let Some(mouse) = mouse
1797+
&& mouse_down
1798+
&& mouse.distance(pos) < PIN_MOVE_HINT_D
1799+
{
1800+
self.drag = Some(Drag::Resize {
1801+
id: selected,
1802+
start: pin.index == 0,
1803+
});
1804+
}
1805+
}
1806+
}
1807+
InstanceKind::Gate(_) | InstanceKind::Power | InstanceKind::CustomCircuit(_) => {}
1808+
}
1809+
}
17031810
}
17041811

17051812
fn get_icon<'a>(ui: &Ui, source: egui::ImageSource<'a>) -> Image<'a> {

0 commit comments

Comments
 (0)