@@ -37,6 +37,8 @@ pub const PIN_HOVER_THRESHOLD: f32 = 10.0;
3737pub const COLOR_POTENTIAL_CONN_HIGHLIGHT : Color32 = Color32 :: LIGHT_BLUE ;
3838pub const WIRE_HIT_DISTANCE : f32 = 10.0 ;
3939pub 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
4143pub const COLOR_SELECTION_HIGHLIGHT : Color32 = Color32 :: GRAY ;
4244pub 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
17051812fn get_icon < ' a > ( ui : & Ui , source : egui:: ImageSource < ' a > ) -> Image < ' a > {
0 commit comments