diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 99cfd5b..40df41c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,7 +12,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.89 override: true - uses: actions-rs/cargo@v1 with: @@ -26,7 +26,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.89 target: wasm32-unknown-unknown override: true - uses: actions-rs/cargo@v1 @@ -41,7 +41,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.89 override: true - run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev - uses: actions-rs/cargo@v1 @@ -56,7 +56,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.89 override: true components: rustfmt - uses: actions-rs/cargo@v1 @@ -71,7 +71,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.89 override: true components: clippy - uses: actions-rs/cargo@v1 @@ -86,7 +86,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.89.0 + toolchain: 1.89 target: wasm32-unknown-unknown override: true - name: Download and install Trunk binary @@ -118,7 +118,7 @@ jobs: - uses: actions/checkout@master - uses: actions-rs/toolchain@v1.0.1 with: - toolchain: stable + toolchain: 1.89 target: ${{ matrix.TARGET }} override: true - uses: actions-rs/cargo@v1 diff --git a/Cargo.toml b/Cargo.toml index f7e5bb1..45dc747 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_f unused_extern_crates = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" +unused_variables = "allow" trivial_casts = "allow" unused_qualifications = "allow" @@ -232,7 +233,8 @@ string_lit_chars_any = "warn" string_to_string = "warn" suspicious_command_arg_space = "warn" suspicious_xor_used_as_pow = "warn" -todo = "warn" +# Let todo for development +todo = "allow" too_long_first_doc_paragraph = "warn" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" @@ -249,16 +251,16 @@ unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unused_peekable = "warn" unused_rounding = "warn" -# unused_self = "warn" unused_trait_names = "warn" unwrap_used = "warn" +# allow unused when developing +unused_self = "allow" use_self = "warn" useless_transmute = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" - manual_range_contains = "allow" map_unwrap_or = "allow" needless_return = "allow" diff --git a/fix.sh b/fix.sh new file mode 100755 index 0000000..87de9c1 --- /dev/null +++ b/fix.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# This scripts runs various CI-like checks in a convenient way. +set -eu + +cargo fmt --all -q +cargo fix --lib -p simu --allow-dirty diff --git a/src/app.rs b/src/app.rs index 8217e97..4eed4c1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,20 +1,24 @@ +use crate::db::{ + Clock, DB, Gate, GateKind, InstanceId, InstanceKind, Label, LabelId, Lamp, ModuleDefId, Pin, + Power, Wire, +}; use std::collections::HashSet; -use std::fmt::{Display, Write as _}; -use std::hash::Hash; +use std::fmt::Write as _; use egui::{ Align, Button, Color32, CornerRadius, Image, Layout, Pos2, Rect, Response, Sense, Stroke, StrokeKind, Ui, Vec2, Widget as _, pos2, vec2, }; -use slotmap::{SecondaryMap, SlotMap}; +use crate::assets::PinKind; +use crate::drag::CanvasDrag; use crate::simulator::{SimulationStatus, Simulator, Value}; use crate::{ assets::{self}, config::CanvasConfig, connection_manager::{Connection, ConnectionManager}, - custom_circuit::{self, Module, ModuleDefinition}, drag::Drag, + module::Module, }; pub const PANEL_BUTTON_MAX_HEIGHT: f32 = 50.0; @@ -36,16 +40,17 @@ pub const COLOR_WIRE_HOVER: Color32 = Color32::GRAY; pub const COLOR_HOVER_INSTANCE_OUTLINE: Color32 = Color32::GRAY; pub const COLOR_HOVER_PIN_TO_WIRE: Color32 = Color32::GRAY; pub const COLOR_HOVER_PIN_DETACH: Color32 = Color32::RED; -pub const PIN_HOVER_THRESHOLD: f32 = 8.0; +pub const PIN_HOVER_THRESHOLD: f32 = 10.0; +pub const INSTANEC_OUTLINE_EXPAND: f32 = 6.0; pub const INSTANEC_OUTLINE: Vec2 = vec2(6.0, 6.0); -pub const INSTANEC_OUTLINE_TICKNESS: f32 = 2.0; +pub const INSTANEC_OUTLINE_THICKNESS: f32 = 2.0; pub const NEW_PIN_ON_WIRE_THRESHOLD: f32 = 10.0; // Connections pub const COLOR_POTENTIAL_CONN_HIGHLIGHT: Color32 = Color32::LIGHT_BLUE; -pub const WIRE_HIT_DISTANCE: f32 = 10.0; +pub const WIRE_HIT_DISTANCE: f32 = 8.0; pub const SNAP_THRESHOLD: f32 = 10.0; pub const PIN_MOVE_HINT_D: f32 = 10.0; pub const PIN_MOVE_HINT_COLOR: Color32 = Color32::GRAY; @@ -55,26 +60,6 @@ pub const COLOR_SELECTION_BOX: Color32 = Color32::LIGHT_BLUE; pub const MIN_WIRE_SIZE: f32 = 40.0; -slotmap::new_key_type! { - pub struct InstanceId; -} - -impl Display for InstanceId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{:?}", self.0)) - } -} - -impl From for InstanceId { - fn from(value: u32) -> Self { - Self(slotmap::KeyData::from_ffi(value as u64)) - } -} - -slotmap::new_key_type! { - pub struct LabelId; -} - #[derive(serde::Deserialize, serde::Serialize, Eq, PartialEq, Hash, Copy, Debug, Clone)] pub enum Hover { Pin(Pin), @@ -98,863 +83,10 @@ pub enum ClipBoardItem { Lamp(Vec2), Clock(Vec2), // Index to definition - CustomCircuit(usize, Vec2), + Module(ModuleDefId, Vec2), Label(String, Vec2), } -#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] -pub enum InstanceKind { - Gate(GateKind), - Power, - Wire, - Lamp, - Clock, - CustomCircuit(usize), -} - -// A specific pin on an instance -#[derive( - serde::Deserialize, serde::Serialize, Copy, Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, -)] -pub struct Pin { - pub ins: InstanceId, - pub index: u32, -} - -impl Pin { - pub fn display(&self, db: &DB) -> String { - let instance_display = match db.ty(self.ins) { - InstanceKind::Gate(_) => { - let gate = db.get_gate(self.ins); - gate.display(db) - } - InstanceKind::Power => { - let power = db.get_power(self.ins); - power.display(db) - } - InstanceKind::Wire => { - let wire = db.get_wire(self.ins); - wire.display(db) - } - InstanceKind::Lamp => { - let lamp = db.get_lamp(self.ins); - lamp.display(db) - } - InstanceKind::Clock => { - let clock = db.get_clock(self.ins); - clock.display(db) - } - InstanceKind::CustomCircuit(_) => format!("CustomCircuit {{ id: {:?} }}", self.ins), - }; - let pin_info = db.pin_info(*self); - format!( - "{:?} pin#{} in {} ", - pin_info.kind, self.index, instance_display, - ) - } -} - -/// Information about a pin's direction -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PinInfo { - pub kind: crate::assets::PinKind, -} - -// Gate - -#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] -pub struct Gate { - // Center position - pub pos: Pos2, - pub kind: GateKind, -} - -#[derive(serde::Deserialize, serde::Serialize, PartialEq, Eq, Copy, Debug, Clone)] -pub enum GateKind { - And, - Nand, - Or, - Nor, - Xor, - Xnor, -} - -impl GateKind { - pub fn graphics(&self) -> &assets::InstanceGraphics { - match self { - Self::Nand => &assets::NAND_GRAPHICS, - Self::And => &assets::AND_GRAPHICS, - Self::Or => &assets::OR_GRAPHICS, - Self::Nor => &assets::NOR_GRAPHICS, - Self::Xor => &assets::XOR_GRAPHICS, - Self::Xnor => &assets::XNOR_GRAPHICS, - } - } -} - -impl Gate { - pub fn display(&self, db: &DB) -> String { - // Find the InstanceId for this gate in the database - for (id, gate) in &db.gates { - if gate.pos == self.pos && gate.kind == self.kind { - return format!("({:?} ({}))", self.kind, id); - } - } - format!( - "Gate {{ kind: {:?}, pos: ({:.1}, {:.1}) }} - not found in DB", - self.kind, self.pos.x, self.pos.y - ) - } -} - -// Gate end - -// Power - -#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] -pub struct Power { - // Center position - pub pos: Pos2, - pub on: bool, -} - -impl Power { - pub fn display(&self, db: &DB) -> String { - // Find the InstanceId for this power in the database - for (id, power) in &db.powers { - if power.pos == self.pos && power.on == self.on { - return format!("Power {{ id: {}, on: {}}}", id, self.on); - } - } - format!( - "Power {{ on: {}, pos: ({:.1}, {:.1}) }} - not found in DB", - self.on, self.pos.x, self.pos.y - ) - } - - fn graphics(&self) -> &assets::InstanceGraphics { - if self.on { - &assets::POWER_ON_GRAPHICS - } else { - &assets::POWER_OFF_GRAPHICS - } - } -} - -// Power end - -// Lamp - -#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] -pub struct Lamp { - pub pos: Pos2, -} - -impl Lamp { - pub fn display(&self, db: &DB) -> String { - // Find the InstanceId for this lamp in the database - for (id, lamp) in &db.lamps { - if lamp.pos == self.pos { - return format!("Lamp {{ id: {id}}}"); - } - } - format!( - "Lamp {{ pos: ({:.1}, {:.1}) }} - not found in DB", - self.pos.x, self.pos.y - ) - } - - fn graphics(&self) -> &assets::InstanceGraphics { - &assets::LAMP_GRAPHICS - } -} - -// Lamp end - -// Clock - -#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] -pub struct Clock { - pub pos: Pos2, - pub period: u32, // Placeholder for future use -} - -impl Clock { - pub fn display(&self, db: &DB) -> String { - for (id, clock) in &db.clocks { - if clock.pos == self.pos { - return format!("Clock {{ id: {id}}}"); - } - } - format!( - "Clock {{ pos: ({:.1}, {:.1}) }} - not found in DB", - self.pos.x, self.pos.y - ) - } - - fn graphics(&self) -> &assets::InstanceGraphics { - &assets::CLOCK_GRAPHICS - } -} - -// Clock end - -// Label - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct Label { - pub pos: Pos2, - pub text: String, -} - -/// Label is a visual annotation that user can place anywhere in the canvas. -/// Therefore it is not an instance like Gate because it does nothing. -/// It is handled separately in the code. -impl Label { - pub fn new(pos: Pos2) -> Self { - Self { - pos, - text: String::from("Label"), - } - } -} - -// Label end - -#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] -pub struct Wire { - pub start: Pos2, - pub end: Pos2, -} - -impl Wire { - pub fn display(&self, db: &DB) -> String { - // Find the InstanceId for this wire in the database - for (id, wire) in &db.wires { - if wire.start == self.start && wire.end == self.end { - return format!("Wire {id}"); - } - } - format!( - "Wire {{ start: ({:.1}, {:.1}), end: ({:.1}, {:.1}) }} - not found in DB", - self.start.x, self.start.y, self.end.x, self.end.y - ) - } - - pub fn new_at(pos: Pos2) -> Self { - Self::new(pos2(pos.x - 30.0, pos.y), pos2(pos.x + 30.0, pos.y)) - } - pub fn new(start: Pos2, end: Pos2) -> Self { - Self { start, end } - } - - pub fn closest_point_on_line(&self, p: Pos2) -> Pos2 { - let a = self.start; - let b = self.end; - let ab: Vec2 = b - a; - let ap: Vec2 = p - a; - - let ab_len2 = ab.x * ab.x + ab.y * ab.y; - if ab_len2 == 0.0 { - return a; - } - - let t = ((ap.x * ab.x + ap.y * ab.y) / ab_len2).clamp(0.0, 1.0); - - a + ab * t - } - - pub fn dist_to_closest_point_on_line(&self, p: Pos2) -> f32 { - let closest = self.closest_point_on_line(p); - (p - closest).length() - } - - pub fn center(&self) -> Pos2 { - pos2( - (self.start.x + self.end.x) * 0.5, - (self.start.y + self.end.y) * 0.5, - ) - } -} - -#[derive(Default, serde::Deserialize, serde::Serialize, Clone)] -pub struct DB { - // Primary key allocator; ensures unique keys across all instance kinds - pub instances: SlotMap, - // Type registry for each instance id - pub types: SecondaryMap, - // Per-kind payloads keyed off the primary key space - pub gates: SecondaryMap, - pub powers: SecondaryMap, - pub wires: SecondaryMap, - pub lamps: SecondaryMap, - pub clocks: SecondaryMap, - pub modules: SecondaryMap, - // Definition of custom circuits created by the user - pub module_definitions: Vec, - pub connections: HashSet, - // Labels - pub labels: SlotMap, -} - -impl DB { - pub fn new() -> Self { - Self::default() - } - pub fn new_gate(&mut self, g: Gate) -> InstanceId { - let k = self.instances.insert(()); - self.gates.insert(k, g); - let kind = self - .gates - .get(k) - .expect("gate must exist right after insertion") - .kind; - self.types.insert(k, InstanceKind::Gate(kind)); - k - } - - pub fn new_power(&mut self, p: Power) -> InstanceId { - let k = self.instances.insert(()); - self.powers.insert(k, p); - self.types.insert(k, InstanceKind::Power); - k - } - - pub fn new_wire(&mut self, w: Wire) -> InstanceId { - let k = self.instances.insert(()); - self.wires.insert(k, w); - self.types.insert(k, InstanceKind::Wire); - k - } - - pub fn new_lamp(&mut self, l: Lamp) -> InstanceId { - let k = self.instances.insert(()); - self.lamps.insert(k, l); - self.types.insert(k, InstanceKind::Lamp); - k - } - - pub fn new_clock(&mut self, c: Clock) -> InstanceId { - let k = self.instances.insert(()); - self.clocks.insert(k, c); - self.types.insert(k, InstanceKind::Clock); - k - } - - pub fn new_custom_circuit(&mut self, c: crate::custom_circuit::Module) -> InstanceId { - let k = self.instances.insert(()); - let definition_index = c.definition_index; - self.modules.insert(k, c); - self.types - .insert(k, InstanceKind::CustomCircuit(definition_index)); - k - } - - pub fn ty(&self, id: InstanceId) -> InstanceKind { - self.types - .get(id) - .copied() - .unwrap_or_else(|| panic!("instance type missing for id: {id:?}")) - } - - pub fn get_gate(&self, id: InstanceId) -> &Gate { - self.gates.get(id).expect("gate not found") - } - - pub fn get_gate_mut(&mut self, id: InstanceId) -> &mut Gate { - self.gates.get_mut(id).expect("gate not found (mut)") - } - - pub fn get_power(&self, id: InstanceId) -> &Power { - self.powers.get(id).expect("power not found") - } - - pub fn get_power_mut(&mut self, id: InstanceId) -> &mut Power { - self.powers.get_mut(id).expect("power not found (mut)") - } - - pub fn get_wire(&self, id: InstanceId) -> &Wire { - self.wires.get(id).expect("wire not found") - } - - pub fn get_wire_mut(&mut self, id: InstanceId) -> &mut Wire { - self.wires.get_mut(id).expect("wire not found (mut)") - } - - pub fn get_lamp(&self, id: InstanceId) -> &Lamp { - self.lamps.get(id).expect("lamp not found") - } - - pub fn get_lamp_mut(&mut self, id: InstanceId) -> &mut Lamp { - self.lamps.get_mut(id).expect("lamp not found (mut)") - } - - pub fn get_clock(&self, id: InstanceId) -> &Clock { - self.clocks.get(id).expect("clock not found") - } - - pub fn get_clock_mut(&mut self, id: InstanceId) -> &mut Clock { - self.clocks.get_mut(id).expect("clock not found (mut)") - } - - pub fn get_custom_circuit(&self, id: InstanceId) -> &crate::custom_circuit::Module { - self.modules.get(id).expect("custom circuit not found") - } - - pub fn get_custom_circuit_mut(&mut self, id: InstanceId) -> &mut crate::custom_circuit::Module { - self.modules - .get_mut(id) - .expect("custom circuit not found (mut)") - } - - // Pin helper methods with type checking - - // Gates - generic versions - pub fn gate_inp_n(&self, id: InstanceId, n: u32) -> Pin { - self.get_gate(id); // Type check - assert!(n < 2, "Gates only have 2 inputs (0 and 1)"); - Pin { - ins: id, - index: if n == 0 { 0 } else { 2 }, - } - } - - pub fn gate_output_n(&self, id: InstanceId, n: u32) -> Pin { - self.get_gate(id); // Type check - assert!(n == 0, "Gates only have 1 output"); - Pin { ins: id, index: 1 } - } - - pub fn gate_inp1(&self, id: InstanceId) -> Pin { - self.gate_inp_n(id, 0) - } - - pub fn gate_inp2(&self, id: InstanceId) -> Pin { - self.gate_inp_n(id, 1) - } - - pub fn gate_output(&self, id: InstanceId) -> Pin { - self.gate_output_n(id, 0) - } - - pub fn wire_pin_n(&self, id: InstanceId, n: u32) -> Pin { - self.get_wire(id); // Type check - assert!(n < 2, "Wires only have 2 pins (0 and 1)"); - Pin { ins: id, index: n } - } - - pub fn wire_start(&self, id: InstanceId) -> Pin { - self.wire_pin_n(id, 0) - } - - pub fn wire_end(&self, id: InstanceId) -> Pin { - self.wire_pin_n(id, 1) - } - - pub fn power_output(&self, id: InstanceId) -> Pin { - self.get_power(id); - Pin { ins: id, index: 0 } - } - - pub fn lamp_input(&self, id: InstanceId) -> Pin { - self.get_lamp(id); - Pin { ins: id, index: 0 } - } - - pub fn clock_output(&self, id: InstanceId) -> Pin { - self.get_clock(id); - Pin { ins: id, index: 0 } - } - - // Custom circuits (variable pins) - pub fn custom_circuit_pin(&self, id: InstanceId, n: u32) -> Pin { - let cc = self.get_custom_circuit(id); - let def = &self.module_definitions[cc.definition_index]; - assert!( - (n as usize) < def.external_pins.len(), - "Pin index out of bounds for custom circuit" - ); - Pin { ins: id, index: n } - } - - /// Get the base pin kind without considering wire connections (avoids recursion) - fn pin_kind_base(&self, pin: Pin) -> assets::PinKind { - match self.ty(pin.ins) { - InstanceKind::Gate(gk) => { - let graphics = gk.graphics(); - graphics.pins[pin.index as usize].kind - } - InstanceKind::Power => { - let graphics = &assets::POWER_ON_GRAPHICS; - graphics.pins[pin.index as usize].kind - } - InstanceKind::Wire => { - // For wires, return Input by default to avoid recursion - assets::PinKind::Input - } - InstanceKind::Lamp => { - let graphics = &assets::LAMP_GRAPHICS; - graphics.pins[pin.index as usize].kind - } - InstanceKind::Clock => { - let graphics = &assets::CLOCK_GRAPHICS; - graphics.pins[pin.index as usize].kind - } - InstanceKind::CustomCircuit(_) => { - let cc = self.get_custom_circuit(pin.ins); - let def = &self.module_definitions[cc.definition_index]; - def.external_pins[pin.index as usize].kind - } - } - } - - /// Get information about a pin's direction (input or output) - pub fn pin_info(&self, pin: Pin) -> PinInfo { - let kind = match self.ty(pin.ins) { - InstanceKind::Wire => { - // Determine if this wire pin is input or output based on connections - // The head of wire that is connected to another output is the input pin of the wire. - // When both heads are not connected start is the input. - let start = self.wire_start(pin.ins); - let end = self.wire_end(pin.ins); - - let start_conns = self.connected_pins(start); - let mut start_connected_to_output = false; - for conn in start_conns { - if self.pin_kind_base(conn) == assets::PinKind::Output { - start_connected_to_output = true; - break; - } - } - - let end_conns = self.connected_pins(end); - let mut end_connected_to_output = false; - for conn in end_conns { - if self.pin_kind_base(conn) == assets::PinKind::Output { - end_connected_to_output = true; - break; - } - } - - // Determine kind based on which pin this is - if pin.index == 0 { - // This is the start pin - if start_connected_to_output { - assets::PinKind::Input - } else if end_connected_to_output { - assets::PinKind::Output - } else { - // Default: start is input - assets::PinKind::Input - } - } else { - // This is the end pin - if end_connected_to_output { - assets::PinKind::Input - } else if start_connected_to_output { - assets::PinKind::Output - } else { - // Default: end is output (since start is input) - assets::PinKind::Output - } - } - } - _ => self.pin_kind_base(pin), - }; - PinInfo { kind } - } - - pub fn new_label(&mut self, label: Label) -> LabelId { - self.labels.insert(label) - } - - pub fn get_label(&self, id: LabelId) -> &Label { - self.labels.get(id).expect("label not found") - } - - pub fn get_label_mut(&mut self, id: LabelId) -> &mut Label { - self.labels.get_mut(id).expect("label not found (mut)") - } - - pub fn pins_of(&self, id: InstanceId) -> Vec { - match self.ty(id) { - InstanceKind::Gate(gk) => { - let n = gk.graphics().pins.len(); - (0..n as u32).map(|i| Pin { ins: id, index: i }).collect() - } - InstanceKind::Power => { - let n = assets::POWER_ON_GRAPHICS.pins.len(); - (0..n as u32).map(|i| Pin { ins: id, index: i }).collect() - } - InstanceKind::Wire => vec![Pin { ins: id, index: 0 }, Pin { ins: id, index: 1 }], - InstanceKind::Lamp => { - let n = assets::LAMP_GRAPHICS.pins.len(); - (0..n as u32).map(|i| Pin { ins: id, index: i }).collect() - } - InstanceKind::Clock => { - let n = assets::CLOCK_GRAPHICS.pins.len(); - (0..n as u32).map(|i| Pin { ins: id, index: i }).collect() - } - InstanceKind::CustomCircuit(_) => { - let cc = self.get_custom_circuit(id); - if cc.definition_index < self.module_definitions.len() { - let def = &self.module_definitions[cc.definition_index]; - (0..def.external_pins.len() as u32) - .map(|i| Pin { ins: id, index: i }) - .collect() - } else { - Vec::new() - } - } - } - } - - pub fn pin_position(&self, pin: Pin) -> Pos2 { - match self.ty(pin.ins) { - InstanceKind::Gate(gk) => { - let g = self.get_gate(pin.ins); - let info = gk.graphics().pins[pin.index as usize]; - g.pos + info.offset - } - InstanceKind::Power => { - let p = self.get_power(pin.ins); - let info = p.graphics().pins[pin.index as usize]; - p.pos + info.offset - } - InstanceKind::Wire => { - let w = self.get_wire(pin.ins); - if pin.index == 0 { w.start } else { w.end } - } - InstanceKind::Lamp => { - let l = self.get_lamp(pin.ins); - let info = l.graphics().pins[pin.index as usize]; - l.pos + info.offset - } - InstanceKind::Clock => { - let c = self.get_clock(pin.ins); - let info = c.graphics().pins[pin.index as usize]; - c.pos + info.offset - } - InstanceKind::CustomCircuit(_) => { - let cc = self.get_custom_circuit(pin.ins); - cc.pos + self.pin_offset(pin) - } - } - } - - pub fn pin_offset(&self, pin: Pin) -> Vec2 { - match self.ty(pin.ins) { - InstanceKind::Gate(gk) => { - let info = gk.graphics().pins[pin.index as usize]; - info.offset - } - InstanceKind::Power => { - let p = self.get_power(pin.ins); - let info = p.graphics().pins[pin.index as usize]; - info.offset - } - InstanceKind::Wire => { - let w = self.get_wire(pin.ins); - let center = w.center(); - if pin.index == 0 { - center - w.start - } else { - center - w.end - } - } - InstanceKind::Lamp => { - let l = self.get_lamp(pin.ins); - let info = l.graphics().pins[pin.index as usize]; - info.offset - } - InstanceKind::Clock => { - let c = self.get_clock(pin.ins); - let info = c.graphics().pins[pin.index as usize]; - info.offset - } - InstanceKind::CustomCircuit(_) => { - let cc = self.get_custom_circuit(pin.ins); - let def = &self.module_definitions[cc.definition_index]; - def.external_pins[pin.index as usize].offset - } - } - } - - pub fn connected_pins_of_instance(&self, id: InstanceId) -> Vec { - let mut out = Vec::new(); - for c in &self.connections { - if c.a.ins == id { - out.push(c.b); - } else if c.b.ins == id { - out.push(c.a); - } - } - out - } - - // Connected pins to this pin - pub fn connected_pins(&self, pin: Pin) -> Vec { - let mut res = Vec::new(); - for c in &self.connections { - if let Some((_, other)) = c.get_pin_first(pin) { - res.push(other); - } - } - res - } - - pub fn connected_insntances(&self, id: InstanceId) -> Vec { - let mut out = vec![id]; - for c in &self.connections { - if c.a.ins == id { - out.push(c.b.ins); - } else if c.b.ins == id { - out.push(c.a.ins); - } - } - out - } - - pub fn move_nonwires_and_resize_wires(&mut self, ids: &[InstanceId], delta: Vec2) { - let ids_set: HashSet = ids.iter().copied().collect(); - - for id in ids { - match self.ty(*id) { - InstanceKind::Gate(_) => { - let g = self.get_gate_mut(*id); - g.pos += delta; - } - InstanceKind::Power => { - let p = self.get_power_mut(*id); - p.pos += delta; - } - InstanceKind::Wire => { - let w = self.get_wire_mut(*id); - w.start += delta; - w.end += delta; - } - InstanceKind::Lamp => { - let l = self.get_lamp_mut(*id); - l.pos += delta; - } - InstanceKind::Clock => { - let c = self.get_clock_mut(*id); - c.pos += delta; - } - InstanceKind::CustomCircuit(_) => { - let cc = self.get_custom_circuit_mut(*id); - cc.pos += delta; - } - } - } - - for id in ids { - for pin in self.connected_pins_of_instance(*id) { - if matches!(self.ty(pin.ins), InstanceKind::Wire) && !ids_set.contains(&pin.ins) { - // Otherwise resize the wire - let w = self.get_wire_mut(pin.ins); - if pin.index == 0 { - w.start += delta; - } else { - w.end += delta; - } - } - } - } - } - - pub fn move_instance_and_propagate(&mut self, id: InstanceId, delta: Vec2) { - let mut visited = HashSet::new(); - self.move_instance_and_propagate_recursive(id, delta, &mut visited); - } - - fn move_instance_and_propagate_recursive( - &mut self, - id: InstanceId, - delta: Vec2, - visited: &mut HashSet, - ) { - if !visited.insert(id) { - return; - } - - // Move this instance - match self.ty(id) { - InstanceKind::Gate(_) => { - let g = self.get_gate_mut(id); - g.pos += delta; - } - InstanceKind::Power => { - let p = self.get_power_mut(id); - p.pos += delta; - } - InstanceKind::Wire => { - let w = self.get_wire_mut(id); - w.start += delta; - w.end += delta; - } - InstanceKind::Lamp => { - let l = self.get_lamp_mut(id); - l.pos += delta; - } - InstanceKind::Clock => { - let c = self.get_clock_mut(id); - c.pos += delta; - } - InstanceKind::CustomCircuit(_) => { - let cc = self.get_custom_circuit_mut(id); - cc.pos += delta; - } - } - - // Get connected instances before we recurse - let connected = self.connected_insntances(id); - - // Process each connected instance - for connected_id in connected { - if connected_id == id || visited.contains(&connected_id) { - continue; - } - - match self.ty(connected_id) { - InstanceKind::Wire => { - // For wires, resize them to stay connected - // Find which pin of the wire is connected to our moved instance - let wire_pins = self.pins_of(connected_id); - for wire_pin in wire_pins { - // Check if this wire pin is connected to any pin of our moved instance - for moved_pin in self.pins_of(id) { - if self - .connections - .contains(&Connection::new(wire_pin, moved_pin)) - { - // Update the wire endpoint to match the new pin position - let new_pin_pos = self.pin_position(moved_pin); - let w = self.get_wire_mut(connected_id); - if wire_pin.index == 0 { - w.start = new_pin_pos; - } else { - w.end = new_pin_pos; - } - } - } - } - // Mark as visited but don't propagate further (wires are endpoints) - visited.insert(connected_id); - } - InstanceKind::Gate(_) - | InstanceKind::Power - | InstanceKind::Lamp - | InstanceKind::Clock - | InstanceKind::CustomCircuit(_) => { - // For non-wires, propagate the same delta - self.move_instance_and_propagate_recursive(connected_id, delta, visited); - } - } - } - } -} - pub fn current_dirty() -> bool { true } @@ -1002,7 +134,6 @@ pub struct App { // selection set and move preview // TODO: Selection is not handling labels. pub selected: HashSet, - pub drag_had_movement: bool, //Copied. Items with their offset compared to a middle point in the rectangle pub clipboard: Vec, // Where are we in the world @@ -1019,6 +150,13 @@ pub struct App { pub editing_label: Option, #[serde(skip)] pub label_edit_buffer: String, + // Module creation dialog state + #[serde(skip)] + pub creating_module: bool, + #[serde(skip)] + pub module_name_buffer: String, + #[serde(skip)] + pub module_creation_error: Option, // Simulation service - holds simulation state and results #[serde(skip)] pub simulator: Simulator, @@ -1029,11 +167,12 @@ pub struct App { impl Default for App { fn default() -> Self { + let canvas_config = CanvasConfig::default(); let db = DB::default(); - let c = ConnectionManager::new(&db); + let c = ConnectionManager::new(&db, &canvas_config); Self { db, - canvas_config: Default::default(), + canvas_config, drag: Default::default(), hovered: Default::default(), connection_manager: c, @@ -1041,7 +180,6 @@ impl Default for App { current_dirty: true, show_debug: true, selected: Default::default(), - drag_had_movement: false, clipboard: Default::default(), pending_load_json: None, viewport_offset: Vec2::ZERO, @@ -1049,6 +187,9 @@ impl Default for App { panel_width: 0.0, editing_label: None, label_edit_buffer: String::new(), + creating_module: false, + module_name_buffer: String::new(), + module_creation_error: None, simulator: Simulator::default(), clock_controller: ClockController::default(), } @@ -1180,10 +321,6 @@ impl App { *v == Value::One } - pub fn screen_to_world(&self, pos: Pos2) -> Pos2 { - pos + self.viewport_offset - } - pub fn draw_main(&mut self, ui: &mut Ui) { self.process_pending_load(); @@ -1192,6 +329,52 @@ impl App { egui_logger::logger_ui().show(ui); }); } + + if self.creating_module { + egui::Window::new("Create Module") + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .show(ui.ctx(), |ui| { + ui.vertical(|ui| { + ui.label("Enter module name:"); + let response = ui.text_edit_singleline(&mut self.module_name_buffer); + + // Auto-focus the text input when dialog opens + response.request_focus(); + + // Check for Enter key to confirm + if ui.input(|i| i.key_pressed(egui::Key::Enter)) + && !self.module_name_buffer.trim().is_empty() + { + self.confirm_module_creation(); + } + + // Check for Escape key to cancel + if ui.input(|i| i.key_pressed(egui::Key::Escape)) { + self.creating_module = false; + self.module_name_buffer.clear(); + self.module_creation_error = None; + } + + // Show error message if any + if let Some(error) = &self.module_creation_error { + ui.colored_label(egui::Color32::RED, error); + } + + ui.horizontal(|ui| { + if ui.button("Create").clicked() { + self.confirm_module_creation(); + } + if ui.button("Cancel").clicked() { + self.creating_module = false; + self.module_name_buffer.clear(); + self.module_creation_error = None; + } + }); + }); + }); + } ui.with_layout(Layout::left_to_right(Align::Min), |ui| { self.canvas_config = CanvasConfig::default(); if self.show_debug { @@ -1241,12 +424,11 @@ impl App { if !self.db.module_definitions.is_empty() { ui.add_space(8.0); - ui.label("Custom Circuits:"); + ui.label("Modules:"); } - let custom_circuit_indices: Vec = - (0..self.db.module_definitions.len()).collect(); - for i in custom_circuit_indices { - self.draw_panel_button(ui, InstanceKind::CustomCircuit(i)); + let keys: Vec = self.db.module_definitions.keys().collect(); + for i in keys { + self.draw_panel_button(ui, InstanceKind::Module(i)); } ui.add_space(8.0); @@ -1260,7 +442,7 @@ impl App { self.hovered = None; self.selected.clear(); self.drag = None; - self.connection_manager = ConnectionManager::new(&self.db); + self.connection_manager = ConnectionManager::new(&self.db, &self.canvas_config); self.simulator = Simulator::new(); } }); @@ -1311,16 +493,13 @@ impl App { .sense(Sense::click_and_drag()) .min_size(vec2(78.0, 30.0)), ), - InstanceKind::CustomCircuit(i) => ui.add( - Button::new(format!("Custom {}", i)) + InstanceKind::Module(i) => ui.add( + Button::new(self.db.get_module_def(i).name.clone()) .sense(Sense::click_and_drag()) .min_size(vec2(78.0, 30.0)), ), }; - let mouse_pos_world = ui - .ctx() - .pointer_interact_pos() - .map(|p| self.screen_to_world(p)); + let mouse_pos_world = self.mouse_pos_world(ui); if resp.drag_started() && let Some(pos) = mouse_pos_world @@ -1331,12 +510,12 @@ impl App { InstanceKind::Wire => self.db.new_wire(Wire::new_at(pos)), InstanceKind::Lamp => self.db.new_lamp(Lamp { pos }), InstanceKind::Clock => self.db.new_clock(Clock { pos, period: 1 }), - InstanceKind::CustomCircuit(c) => self.db.new_custom_circuit(Module { + InstanceKind::Module(c) => self.db.new_module(Module { pos, definition_index: c, }), }; - self.drag = Some(Drag::Canvas(crate::drag::CanvasDrag::Single { + self.set_drag(Drag::Canvas(crate::drag::CanvasDrag::Single { id, offset: Vec2::ZERO, })); @@ -1345,7 +524,7 @@ impl App { let d_pressed = ui.input(|i| i.key_pressed(egui::Key::D)); if resp.hovered() && d_pressed - && let InstanceKind::CustomCircuit(i) = kind + && let InstanceKind::Module(i) = kind { let mut ids = Vec::new(); for (id, m) in &self.db.modules { @@ -1370,16 +549,13 @@ impl App { .min_size(vec2(78.0, 30.0)), ); - let mouse_pos_world = ui - .ctx() - .pointer_interact_pos() - .map(|p| self.screen_to_world(p)); + let mouse = self.mouse_pos_world(ui); if resp.drag_started() - && let Some(pos) = mouse_pos_world + && let Some(pos) = mouse { let id = self.db.new_label(Label::new(pos)); - self.drag = Some(Drag::Label { + self.set_drag(Drag::Label { id, offset: Vec2::ZERO, }); @@ -1390,6 +566,10 @@ impl App { } fn handle_copy_pasting(&mut self, ui: &Ui, mouse_pos_world: Option) { + if self.creating_module { + return; + } + let mut copy_event_detected = false; let mut paste_event_detected = false; ui.ctx().input(|i| { @@ -1419,6 +599,10 @@ impl App { } fn handle_deletion(&mut self, ui: &Ui) { + if self.creating_module { + return; + } + let bs_pressed = ui.input(|i| i.key_pressed(egui::Key::Backspace)); let d_pressed = ui.input(|i| i.key_pressed(egui::Key::D)); @@ -1438,7 +622,6 @@ impl App { } pub fn delete_instance(&mut self, id: InstanceId) { - self.db.instances.remove(id); self.db.types.remove(id); self.db.gates.remove(id); self.db.powers.remove(id); @@ -1474,16 +657,14 @@ impl App { Self::draw_grid(ui, canvas_rect, self.viewport_offset); - let mouse_clicked_canvas_or_gates = resp.clicked(); + let mouse_clicked_canvas = resp.clicked(); + let mouse_dragging_canvas = resp.dragged(); let double_clicked = ui.input(|i| { i.pointer .button_double_clicked(egui::PointerButton::Primary) }); let mouse_is_visible = resp.contains_pointer(); - let mouse_pos_world = ui - .ctx() - .pointer_hover_pos() - .map(|p| self.screen_to_world(p)); + let mouse_pos_world = self.mouse_pos_world(ui); let mouse_up = ui.input(|i| i.pointer.any_released()); // To use the canvas clicked we need to set everything on objects. Right now some stuff are @@ -1496,77 +677,75 @@ impl App { let enter_pressed = ui.input(|i| i.key_pressed(egui::Key::Enter)); let esc_pressed = ui.input(|i| i.key_released(egui::Key::Escape)); - if right_down && mouse_is_visible && self.hovered.is_none() { - self.panning = true; - } - if right_released || !mouse_is_visible { - self.panning = false; - } - if self.panning { - self.viewport_offset += ui.input(|i| i.pointer.delta()); - } - - self.handle_copy_pasting(ui, mouse_pos_world); - self.handle_deletion(ui); - - if let Some(editing_id) = self.editing_label { - let label = self.db.get_label_mut(editing_id); - label.text = self.label_edit_buffer.clone(); - if mouse_up || enter_pressed || esc_pressed { - self.editing_label = None; - self.label_edit_buffer.clear(); + if !self.creating_module { + if right_down && mouse_is_visible && self.hovered.is_none() { + self.panning = true; + } + if right_released || !mouse_is_visible { + self.panning = false; + } + if self.panning { + self.viewport_offset += ui.input(|i| i.pointer.delta()); } - } - - // Handle double-click on empty canvas to create new label - if double_clicked - && self.hovered.is_none() - && !self.drag_had_movement - && let Some(mouse) = mouse_pos_world - { - let id = self.db.new_label(Label::new(mouse)); - self.editing_label = Some(id); - self.label_edit_buffer = String::from("Label"); - } - if let Some(mouse) = mouse_pos_world { - let instance_dragging = self.drag.is_some(); - self.hovered = self.get_hovered(mouse); + self.handle_copy_pasting(ui, mouse_pos_world); + self.handle_deletion(ui); - if mouse_clicked { - self.handle_drag_start(mouse); + if let Some(editing_id) = self.editing_label { + let label = self.db.get_label_mut(editing_id); + label.text = self.label_edit_buffer.clone(); + if mouse_up || enter_pressed || esc_pressed { + self.editing_label = None; + self.label_edit_buffer.clear(); + } } - if instance_dragging { - self.handle_dragging(ui, mouse); + if double_clicked + && self.hovered.is_none() + && let Some(mouse) = mouse_pos_world + { + let id = self.db.new_label(Label::new(mouse)); + self.editing_label = Some(id); + self.label_edit_buffer = String::from("Label"); } - if mouse_up && instance_dragging { - self.handle_drag_end(mouse); + if let Some(mouse) = mouse_pos_world { + if mouse_dragging_canvas { + self.set_drag(Drag::Selecting { start: mouse }); + } + let instance_dragging = self.drag.is_some(); - if self.connection_manager.update_connections(&mut self.db) { - // self.current_dirty = true; + if instance_dragging { + self.handle_dragging(ui, mouse); + } + + if mouse_up && instance_dragging { + self.handle_drag_end(mouse); + + if self.connection_manager.update_connections(&mut self.db) { + // self.current_dirty = true; + } } } } - if self.selected.len() == 1 { - self.highlight_selected_actions(ui, mouse_pos_world, mouse_clicked_canvas_or_gates); - } - if mouse_clicked_canvas_or_gates { - self.selected.clear(); - if let Some(hovered) = self.hovered { - self.selected.insert(hovered.instance()); + if !self.creating_module { + if self.selected.len() == 1 { + self.highlight_selected_actions(ui, mouse_pos_world, mouse_clicked); + } + + if mouse_clicked_canvas { + self.selected.clear(); } - } - if right_clicked - && let Some(id) = self.hovered.as_ref().map(|i| i.instance()) - && matches!(self.db.ty(id), InstanceKind::Power) - { - let p = self.db.get_power_mut(id); - p.on = !p.on; - self.current_dirty = true; + if right_clicked + && let Some(id) = self.hovered.as_ref().map(|i| i.instance()) + && matches!(self.db.ty(id), InstanceKind::Power) + { + let p = self.db.get_power_mut(id); + p.on = !p.on; + self.current_dirty = true; + } } if self.current_dirty { @@ -1575,27 +754,27 @@ impl App { } // Draw world - // TODO: Remove the clones. We need to modify the selected, hovered things - for (id, gate) in self.db.gates.clone() { - self.draw_gate(ui, id, &gate); + self.hovered = None; + for id in self.db.gate_ids() { + self.draw_gate(ui, id); } - for (id, power) in &self.db.powers.clone() { - self.draw_power(ui, id, &power); + for id in self.db.power_ids() { + self.draw_power(ui, id); } - for (id, lamp) in &self.db.lamps.clone() { - self.draw_lamp(ui, id, &lamp); + for id in self.db.lamp_ids() { + self.draw_lamp(ui, id); } - for (id, clock) in &self.db.clocks.clone() { - self.draw_clock(ui, id, &clock); + for id in self.db.clock_ids() { + self.draw_clock(ui, id); } - for (id, custom_circuit) in &self.db.modules.clone() { - self.draw_module(ui, id, custom_circuit); + for id in self.db.module_ids() { + self.draw_module(ui, id); } - for (id, wire) in &self.db.wires { + for id in self.db.wire_ids() { let has_current = self.is_on(self.db.wire_start(id)); self.draw_wire( ui, - wire, + id, self.hovered .as_ref() .is_some_and(|f| matches!(f, Hover::Instance(_)) && f.instance() == id), @@ -1603,20 +782,14 @@ impl App { ); } // Collect labels to avoid borrowing issues - let labels: Vec<(LabelId, Label)> = self - .db - .labels - .iter() - .map(|(id, label)| (id, label.clone())) - .collect(); - for (id, label) in labels { - self.draw_label(ui, id, &label); + for id in self.db.label_ids() { + self.draw_label(ui, id); } for c in &self.potential_connections { // Highlight the pin that it's going to attach. The stable pin. let pin_to_highlight = c.b; - let p = self.db.pin_position(pin_to_highlight); + let p = self.db.pin_position(pin_to_highlight, &self.canvas_config); ui.painter().circle_filled( p - self.viewport_offset, SNAP_THRESHOLD, @@ -1628,21 +801,6 @@ impl App { self.highlight_hovered(ui); } self.draw_selection_highlight(ui); - - // Preview wire branching - if !self.selected.is_empty() - && let Some(hovered) = self.hovered - && let Some(mouse) = mouse_pos_world - && self.drag.is_none() - && let Hover::Instance(instance_id) = hovered - && let Some(split_point) = self.wire_branching_action_point(mouse, instance_id) - { - ui.painter().circle_filled( - split_point - self.viewport_offset, - PIN_HOVER_THRESHOLD, - COLOR_HOVER_PIN_TO_WIRE, - ); - } } fn draw_grid(ui: &Ui, canvas_rect: Rect, viewport_offset: Vec2) { @@ -1683,30 +841,66 @@ impl App { } } - fn draw_instance_graphics( - &self, + fn draw_instance_graphics_new( + &mut self, ui: &mut Ui, - graphics: &assets::InstanceGraphics, - screen_center: Pos2, - highlight_pin: F, - ) -> Rect - where - F: Fn(usize) -> bool, - { - let rect = Rect::from_center_size(screen_center, self.canvas_config.base_gate_size); - draw_icon_canvas(ui, graphics.svg.clone(), rect); + graphics: assets::InstanceGraphics, + pos: Pos2, + id: InstanceId, + ) -> Rect { + let rect = Rect::from_center_size(pos, self.canvas_config.base_gate_size); + let image = get_icon(ui, graphics.svg) + .fit_to_exact_size(rect.size()) + .sense(Sense::click_and_drag()); + let rect = rect.expand(INSTANEC_OUTLINE_EXPAND); + let response = ui.put(rect, image); + + if response.clicked() { + self.selected.clear(); + self.selected.insert(id); + } + if response.hovered() { + self.hovered = Some(Hover::Instance(id)); + } + if response.dragged() + && let Some(mouse) = ui.ctx().pointer_interact_pos() + { + self.selected.clear(); + self.set_drag(Drag::Canvas(CanvasDrag::Single { + id, + offset: pos - mouse, + })); + } for (i, pin) in graphics.pins.iter().enumerate() { - let pin_offset = pin.offset; - let pin_pos = screen_center + pin_offset; + let pin_pos = pos + pin.offset; let color = match pin.kind { assets::PinKind::Input => self.canvas_config.base_input_pin_color, assets::PinKind::Output => self.canvas_config.base_output_pin_color, }; + + let rect = Rect::from_center_size( + pin_pos, + Vec2::splat(self.canvas_config.base_pin_size + PIN_HOVER_THRESHOLD), + ); + let pin_resp = ui.allocate_rect(rect, Sense::drag()); ui.painter() .circle_filled(pin_pos, self.canvas_config.base_pin_size, color); - if highlight_pin(i) { + let pin = Pin::new(id, i as u32, pin.kind); + if pin_resp.hovered() { + self.hovered = Some(Hover::Pin(pin)); + } + + if pin_resp.dragged() { + self.selected.clear(); + self.set_drag(Drag::PinToWire { + source_pin: pin, + start_pos: pin_pos, + }); + } + + if self.is_on(pin) { ui.painter().circle_stroke( pin_pos, self.canvas_config.base_pin_size + 3.0, @@ -1717,252 +911,321 @@ impl App { rect } - fn set_selected(&mut self, ui: &mut Ui, rect: Rect, id: InstanceId) { - let response = ui.allocate_rect(rect, Sense::click_and_drag()); - if response.clicked() { - self.selected.clear(); - self.selected.insert(id); - } - } - - fn draw_gate(&mut self, ui: &mut Ui, id: InstanceId, gate: &Gate) { - let screen_center = gate.pos - self.viewport_offset; - let rect = - self.draw_instance_graphics(ui, gate.kind.graphics(), screen_center, |pin_index| { - self.is_on(Pin { - ins: id, - index: pin_index as u32, - }) - }); - self.set_selected(ui, rect, id); - } - - pub fn draw_gate_preview(&self, ui: &mut Ui, gate_kind: GateKind, pos: Pos2) { - let screen_center = pos - self.viewport_offset; - self.draw_instance_graphics(ui, gate_kind.graphics(), screen_center, |_| false); - } - - fn draw_power(&mut self, ui: &mut Ui, id: InstanceId, power: &Power) { - let screen_center = power.pos - self.viewport_offset; - let rect = self.draw_instance_graphics(ui, power.graphics(), screen_center, |pin_index| { - self.is_on(Pin { - ins: id, - index: pin_index as u32, - }) - }); - self.set_selected(ui, rect, id); + fn draw_gate(&mut self, ui: &mut Ui, id: InstanceId) { + let (pos, kind) = { + let gate = self.db.get_gate(id); + (gate.pos, gate.kind) + }; + self.draw_instance_graphics_new(ui, kind.graphics(), self.adjusted_pos(pos), id); } - pub fn draw_power_preview(&self, ui: &mut Ui, pos: Pos2) { - let power = Power { pos, on: true }; - let screen_center = pos - self.viewport_offset; - self.draw_instance_graphics(ui, power.graphics(), screen_center, |_| false); + fn draw_power(&mut self, ui: &mut Ui, id: InstanceId) { + let (pos, graphics) = { + let power = self.db.get_power(id); + (power.pos, power.graphics()) + }; + self.draw_instance_graphics_new(ui, graphics, self.adjusted_pos(pos), id); } - fn draw_lamp(&mut self, ui: &mut Ui, id: InstanceId, lamp: &Lamp) { + fn draw_lamp(&mut self, ui: &mut Ui, id: InstanceId) { let has_current = self.is_on(self.db.lamp_input(id)); - let screen_center = lamp.pos - self.viewport_offset; + let (pos, graphics) = { + let lamp = self.db.get_lamp(id); + (lamp.pos, lamp.graphics()) + }; + let pos = self.adjusted_pos(pos); if has_current { - let glow_radius = 60.0; + let glow_radius = 40.0; let gradient_steps = 30; for i in 0..gradient_steps { let t = i as f32 / gradient_steps as f32; let radius = glow_radius * (1.0 - t); let alpha = (255.0 * (1.0 - t) * 0.4) as u8; ui.painter().circle_filled( - screen_center, + pos + vec2(0.0, -25.0), radius, Color32::from_rgba_unmultiplied(255, 255, 0, alpha), ); } } - let rect = self.draw_instance_graphics(ui, lamp.graphics(), screen_center, |pin_index| { - self.is_on(Pin { - ins: id, - index: pin_index as u32, - }) - }); - self.set_selected(ui, rect, id); - - if has_current { - let rect = Rect::from_center_size(screen_center, self.canvas_config.base_gate_size); - ui.painter().rect_filled( - rect, - CornerRadius::default(), - Color32::from_rgba_unmultiplied(255, 255, 0, 80), - ); - } - } - - pub fn draw_lamp_preview(&self, ui: &mut Ui, pos: Pos2) { - let lamp = Lamp { pos }; - let screen_center = pos - self.viewport_offset; - self.draw_instance_graphics(ui, lamp.graphics(), screen_center, |_| false); + self.draw_instance_graphics_new(ui, graphics, pos, id); } - fn draw_clock(&mut self, ui: &mut Ui, id: InstanceId, clock: &Clock) { - let screen_center = clock.pos - self.viewport_offset; - - let rect = self.draw_instance_graphics(ui, clock.graphics(), screen_center, |pin_index| { - self.is_on(Pin { - ins: id, - index: pin_index as u32, - }) - }); - - self.set_selected(ui, rect, id); + fn draw_clock(&mut self, ui: &mut Ui, id: InstanceId) { + let (pos, graphics) = { + let clock = self.db.get_clock(id); + (clock.pos, clock.graphics()) + }; + let pos = self.adjusted_pos(pos); + self.draw_instance_graphics_new(ui, graphics, pos, id); } - pub fn draw_clock_preview(&self, ui: &mut Ui, pos: Pos2) { - let clock = Clock { pos, period: 1 }; + fn draw_module(&mut self, ui: &mut Ui, id: InstanceId) { + let (pos, definition_index) = { + let module = self.db.get_module(id); + (module.pos, module.definition_index) + }; let screen_center = pos - self.viewport_offset; - self.draw_instance_graphics(ui, clock.graphics(), screen_center, |_| false); - } - - fn draw_module( - &mut self, - ui: &mut Ui, - id: InstanceId, - custom_circuit: &crate::custom_circuit::Module, - ) { - let screen_center = custom_circuit.pos - self.viewport_offset; { - // Get the definition for this custom circuit - let Some(definition) = self - .db - .module_definitions - .get(custom_circuit.definition_index) - else { - return; - }; + let definition = self.db.get_module(id).definition(&self.db); + // TODO: Pins for modules + let name = definition.name.clone(); + let pins = []; - // Draw as a dark blue rectangle with the name let rect = Rect::from_center_size(screen_center, self.canvas_config.base_gate_size); ui.painter() .rect_filled(rect, CornerRadius::default(), egui::Color32::DARK_BLUE); - // Draw the name ui.painter().text( rect.center(), egui::Align2::CENTER_CENTER, - &definition.name, + &name, egui::FontId::default(), egui::Color32::WHITE, ); - // Draw external pins - for (pin_index, ext_pin) in definition.external_pins.iter().enumerate() { - let pin_world_pos = custom_circuit.pos + ext_pin.offset; - let pin_screen_pos = pin_world_pos - self.viewport_offset; + let response = ui.allocate_rect(rect, Sense::click_and_drag()); - // Determine pin color based on whether it has current - let has_current = self.is_on(Pin { - ins: id, - index: pin_index as u32, - }); + if response.clicked() { + self.selected.clear(); + self.selected.insert(id); + } + if response.hovered() { + self.hovered = Some(Hover::Instance(id)); + } + if response.dragged() + && let Some(mouse) = ui.ctx().pointer_interact_pos() + { + self.selected.clear(); + self.set_drag(Drag::Canvas(CanvasDrag::Single { + id, + offset: screen_center - mouse, + })); + } + + // Collect input and output pin indices + let mut input_indices = vec![]; + let mut output_indices = vec![]; + for (i, &kind) in pins.iter().enumerate() { + match kind { + crate::assets::PinKind::Input => input_indices.push(i), + crate::assets::PinKind::Output => output_indices.push(i), + } + } - let pin_color = match ext_pin.kind { - crate::assets::PinKind::Input => egui::Color32::LIGHT_GREEN, - crate::assets::PinKind::Output => egui::Color32::LIGHT_RED, + // Base size for layout + let base_size = self.canvas_config.base_gate_size; + let left_x = screen_center.x - base_size.x / 2.0; + let right_x = screen_center.x + base_size.x / 2.0; + let top_y = screen_center.y - base_size.y / 2.0; + let _bottom_y = screen_center.y + base_size.y / 2.0; + + // Helper function to place pins + let mut place_pins = |indices: &Vec, x: f32| { + if indices.is_empty() { + return; + } + let num = indices.len(); + let spacing = if num == 1 { + 0.0 + } else { + base_size.y / (num - 1) as f32 }; + for (local_i, &pin_index) in indices.iter().enumerate() { + let y = if num == 1 { + screen_center.y + } else { + top_y + local_i as f32 * spacing + }; + let pin_pos_world = egui::Pos2::new(x, y); + let pin_screen_pos = self.adjusted_pos(pin_pos_world); - // Draw the pin - ui.painter().circle_filled( - pin_screen_pos, - self.canvas_config.base_pin_size, - pin_color, - ); + let pin_color = match pins[pin_index] { + crate::assets::PinKind::Input => egui::Color32::LIGHT_RED, + crate::assets::PinKind::Output => egui::Color32::LIGHT_GREEN, + }; + + ui.painter().circle_filled( + pin_screen_pos, + self.canvas_config.base_pin_size, + pin_color, + ); - // Add outline if it has current - if has_current { - ui.painter().circle_stroke( + let has_current = self.is_on(Pin::new(id, pin_index as u32, pins[pin_index])); + + if has_current { + ui.painter().circle_stroke( + pin_screen_pos, + self.canvas_config.base_pin_size + 3.0, + egui::Stroke::new(2.0, COLOR_PIN_POWERED_OUTLINE), + ); + } + + let pin_rect = Rect::from_center_size( pin_screen_pos, - self.canvas_config.base_pin_size + 3.0, - egui::Stroke::new(2.0, COLOR_PIN_POWERED_OUTLINE), + Vec2::splat(self.canvas_config.base_pin_size + PIN_HOVER_THRESHOLD), ); + let pin_resp = ui.allocate_rect(pin_rect, Sense::drag()); + let pin = Pin::new(id, pin_index as u32, pins[pin_index]); + if pin_resp.hovered() { + self.hovered = Some(Hover::Pin(pin)); + } + if pin_resp.dragged() { + self.selected.clear(); + self.set_drag(Drag::PinToWire { + source_pin: pin, + start_pos: pin_pos_world, + }); + } } - } + }; - self.set_selected(ui, rect, id); + place_pins(&input_indices, left_x); + place_pins(&output_indices, right_x); } } - pub fn draw_custom_circuit_preview(&self, ui: &Ui, definition_index: usize, pos: Pos2) { - let screen_center = pos - self.viewport_offset; + pub fn draw_wire(&mut self, ui: &mut Ui, id: InstanceId, hovered: bool, has_current: bool) { + let mut color = if has_current { + COLOR_WIRE_POWERED + } else { + COLOR_WIRE_IDLE + }; + if hovered { + color = COLOR_WIRE_HOVER; + } + let wire = *self.db.get_wire(id); + let mut pin_interact = false; - // Get the definition for this custom circuit - if let Some(definition) = self.db.module_definitions.get(definition_index) { - // Draw as a dark blue rectangle with the name - let rect = Rect::from_center_size(screen_center, self.canvas_config.base_gate_size); + for (i, pin_pos) in [wire.start, wire.end].iter().enumerate() { + let pin_pos = self.adjusted_pos(*pin_pos); + let kind = if i == wire.input_index as usize { + PinKind::Input + } else { + PinKind::Output + }; + let pin = Pin::new(id, i as u32, kind); + let rect = Rect::from_center_size( + pin_pos, + Vec2::splat(self.canvas_config.base_pin_size + PIN_HOVER_THRESHOLD), + ); + let pin_resp = ui.allocate_rect(rect, Sense::click_and_drag()); + if pin_resp.hovered() { + self.hovered = Some(Hover::Pin(pin)); + pin_interact = true; + } + if pin_resp.clicked() { + self.selected.clear(); + self.selected.insert(id); + pin_interact = true; + } + if pin_resp.dragged() { + if self.selected.contains(&id) { + self.set_drag(Drag::Resize { + id: pin.ins, + start: pin.index != 1, + }); + } else { + self.set_drag(Drag::PinToWire { + source_pin: pin, + start_pos: pin_pos, + }); + } + pin_interact = true; + } + let mut pin_color = if i == wire.input_index as usize { + Color32::RED + } else { + Color32::GREEN + }; + // Only show red/green if pin is not connected + let is_connected = self + .db + .connections + .iter() + .any(|conn| conn.a == pin || conn.b == pin); + if is_connected { + pin_color = color; + } ui.painter() - .rect_filled(rect, CornerRadius::default(), egui::Color32::DARK_BLUE); + .circle(pin_pos, PIN_HOVER_THRESHOLD / 2.0, pin_color, Stroke::NONE); + } - // Draw the name - ui.painter().text( - rect.center(), - egui::Align2::CENTER_CENTER, - &definition.name, - egui::FontId::default(), - egui::Color32::WHITE, - ); + let start = self.adjusted_pos(wire.start); + let end = self.adjusted_pos(wire.end); - // Draw external pins (no current highlighting in preview) - for ext_pin in &definition.external_pins { - let pin_world_pos = pos + ext_pin.offset; - let pin_screen_pos = pin_world_pos - self.viewport_offset; + let hit_wire = if pin_interact { + false + } else if let Some(mouse_world) = self.mouse_pos_world(ui) { + let dist = wire.dist_to_closest_point_on_line(mouse_world); + dist < WIRE_HIT_DISTANCE + } else { + false + }; - let pin_color = match ext_pin.kind { - crate::assets::PinKind::Input => egui::Color32::LIGHT_GREEN, - crate::assets::PinKind::Output => egui::Color32::LIGHT_RED, - }; + if hit_wire { + self.hovered = Some(Hover::Instance(id)); + color = COLOR_WIRE_HOVER; + + if ui.input(|i| i.pointer.primary_clicked()) { + self.selected.clear(); + self.selected.insert(id); + } - // Draw the pin + if self.selected.len() == 1 + && self.selected.contains(&id) + && let Some(mouse) = self.mouse_pos_world(ui) + && let Some(split_point) = self.wire_branching_action_point(mouse, id) + { ui.painter().circle_filled( - pin_screen_pos, - self.canvas_config.base_pin_size, - pin_color, + split_point, + PIN_HOVER_THRESHOLD, + COLOR_HOVER_PIN_TO_WIRE, ); } - } - } - - pub fn draw_wire(&self, ui: &Ui, wire: &Wire, hovered: bool, has_current: bool) { - let mut color = if has_current { - COLOR_WIRE_POWERED - } else { - COLOR_WIRE_IDLE - }; - if hovered { - color = COLOR_WIRE_HOVER; + if ui.input(|i| i.pointer.primary_down()) + && let Some(mouse) = self.mouse_pos_world(ui) + { + if self.selected.len() == 1 + && self.selected.contains(&id) + && let Some(split_point) = self.wire_branching_action_point(mouse, id) + { + ui.painter().circle_filled( + split_point, + PIN_HOVER_THRESHOLD, + COLOR_HOVER_PIN_TO_WIRE, + ); + self.set_drag(Drag::BranchWire { + original_wire_id: id, + split_point, + start_mouse_pos: mouse, + }); + } else { + let wire_center = pos2( + (wire.start.x + wire.end.x) * 0.5, + (wire.start.y + wire.end.y) * 0.5, + ); + let offset = wire_center - mouse; + self.set_drag(Drag::Canvas(CanvasDrag::Single { id, offset })); + } + } } ui.painter().line_segment( - [ - wire.start - self.viewport_offset, - wire.end - self.viewport_offset, - ], + [start, end], Stroke::new(self.canvas_config.wire_thickness, color), ); - ui.painter().circle( - wire.start - self.viewport_offset, - PIN_HOVER_THRESHOLD / 2.0, - color, - Stroke::NONE, - ); - ui.painter().circle( - wire.end - self.viewport_offset, - PIN_HOVER_THRESHOLD / 2.0, - color, - Stroke::NONE, - ); } - - fn draw_label(&mut self, ui: &mut Ui, id: LabelId, label: &Label) { - let screen_pos = label.pos - self.viewport_offset; + fn draw_label(&mut self, ui: &mut Ui, id: LabelId) { + let (pos, text) = { + let label = self.db.get_label(id); + (label.pos, label.text.clone()) + }; + let screen_pos = pos - self.viewport_offset; let is_editing = matches!(self.editing_label, Some(editing_id) if editing_id == id); @@ -1994,7 +1257,7 @@ impl App { let text_size = ui .painter() .layout_no_wrap( - label.text.clone(), + text.clone(), egui::FontId::proportional(LABEL_DISPLAY_TEXT_SIZE), text_color, ) @@ -2007,14 +1270,14 @@ impl App { ui.painter().text( screen_pos, egui::Align2::CENTER_CENTER, - &label.text, + &text, egui::FontId::proportional(LABEL_DISPLAY_TEXT_SIZE), text_color, ); if response.double_clicked() { self.editing_label = Some(id); - self.label_edit_buffer = label.text.clone(); + self.label_edit_buffer = text; } if response.hovered() && ui.input(|i| i.key_pressed(egui::Key::D)) { self.delete_label(id); @@ -2022,164 +1285,6 @@ impl App { } } - pub fn get_hovered(&self, mouse_pos: Pos2) -> Option { - if let Some(v) = self.drag { - match v { - Drag::Canvas(canvas_drag) => match canvas_drag { - crate::drag::CanvasDrag::Single { id, offset: _ } => { - return Some(Hover::Instance(id)); - } - crate::drag::CanvasDrag::Selected { .. } => {} - }, - Drag::Resize { id, start } => { - let pin = if start { - self.db.wire_start(id) - } else { - self.db.wire_end(id) - }; - return Some(Hover::Pin(pin)); - } - Drag::PinToWire { - source_pin, - start_pos: _, - } => { - // Source pin is being dragged - return Some(Hover::Pin(source_pin)); - } - Drag::BranchWire { - original_wire_id, - split_point: _, - start_mouse_pos: _, - } => { - return Some(Hover::Instance(original_wire_id)); - } - Drag::Selecting { .. } | Drag::Label { .. } => {} - } - } - for selected in &self.selected { - match self.db.ty(*selected) { - InstanceKind::Wire => { - for pin in self.db.pins_of(*selected) { - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - let wire = self.db.get_wire(*selected); - let dist = wire.dist_to_closest_point_on_line(mouse_pos); - if dist < WIRE_HIT_DISTANCE { - return Some(Hover::Instance(*selected)); - } - } - InstanceKind::Gate(_) - | InstanceKind::Power - | InstanceKind::Lamp - | InstanceKind::Clock - | InstanceKind::CustomCircuit(_) => {} - } - } - - // First pins - for (k, _) in &self.db.powers { - for pin in self.db.pins_of(k) { - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - } - - for (k, _) in &self.db.gates { - for pin in self.db.pins_of(k) { - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - } - - for (k, _) in &self.db.lamps { - for pin in self.db.pins_of(k) { - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - } - - for (k, _) in &self.db.clocks { - for pin in self.db.pins_of(k) { - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - } - - for (k, _) in &self.db.wires { - for pin in self.db.pins_of(k) { - if self.is_pin_connected(pin) { - continue; - } - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - } - - for (k, _) in &self.db.modules { - for pin in self.db.pins_of(k) { - if self.is_pin_connected(pin) { - continue; - } - if self.db.pin_position(pin).distance(mouse_pos) < PIN_HOVER_THRESHOLD { - return Some(Hover::Pin(pin)); - } - } - } - - // Then instances - for (k, wire) in &self.db.wires { - let dist = wire.dist_to_closest_point_on_line(mouse_pos); - if dist < WIRE_HIT_DISTANCE { - return Some(Hover::Instance(k)); - } - } - for (k, power) in &self.db.powers { - let rect = Rect::from_center_size(power.pos, self.canvas_config.base_gate_size); - if rect.contains(mouse_pos) { - return Some(Hover::Instance(k)); - } - } - for (k, gate) in &self.db.gates { - let rect = Rect::from_center_size(gate.pos, self.canvas_config.base_gate_size); - if rect.contains(mouse_pos) { - return Some(Hover::Instance(k)); - } - } - for (k, lamp) in &self.db.lamps { - let rect = Rect::from_center_size(lamp.pos, self.canvas_config.base_gate_size); - if rect.contains(mouse_pos) { - return Some(Hover::Instance(k)); - } - } - for (k, clock) in &self.db.clocks { - let rect = Rect::from_center_size(clock.pos, self.canvas_config.base_gate_size); - if rect.contains(mouse_pos) { - return Some(Hover::Instance(k)); - } - } - for (k, module) in &self.db.modules { - let rect = Rect::from_center_size(module.pos, self.canvas_config.base_gate_size); - if rect.contains(mouse_pos) { - return Some(Hover::Instance(k)); - } - } - for (k, wire) in &self.db.wires { - let dist = wire.dist_to_closest_point_on_line(mouse_pos); - if dist < WIRE_HIT_DISTANCE { - return Some(Hover::Instance(k)); - } - } - - None - } - fn highlight_hovered(&self, ui: &Ui) { let Some(hovered) = self.hovered else { return; @@ -2188,7 +1293,7 @@ impl App { match hovered { Hover::Pin(pin) => { let color = COLOR_HOVER_PIN_TO_WIRE; - let pin_pos = self.db.pin_position(pin) - self.viewport_offset; + let pin_pos = self.db.pin_position(pin, &self.canvas_config) - self.viewport_offset; ui.painter() .circle_filled(pin_pos, PIN_HOVER_THRESHOLD, color); } @@ -2202,7 +1307,7 @@ impl App { ui.painter().rect_stroke( outer, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), StrokeKind::Middle, ); } @@ -2215,7 +1320,7 @@ impl App { ui.painter().rect_stroke( outer, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), StrokeKind::Middle, ); } @@ -2228,7 +1333,7 @@ impl App { ui.painter().rect_stroke( outer, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), StrokeKind::Middle, ); } @@ -2241,14 +1346,14 @@ impl App { ui.painter().rect_stroke( outer, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), StrokeKind::Middle, ); } // Wire is highlighted when drawing InstanceKind::Wire => {} - InstanceKind::CustomCircuit(_) => { - let cc = self.db.get_custom_circuit(hovered); + InstanceKind::Module(_) => { + let cc = self.db.get_module(hovered); let outer = Rect::from_center_size( cc.pos - self.viewport_offset, self.canvas_config.base_gate_size + INSTANEC_OUTLINE, @@ -2256,7 +1361,7 @@ impl App { ui.painter().rect_stroke( outer, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_HOVER_INSTANCE_OUTLINE), StrokeKind::Middle, ); } @@ -2280,7 +1385,7 @@ impl App { ui.painter().rect_stroke( r, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_SELECTION_HIGHLIGHT), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_SELECTION_HIGHLIGHT), StrokeKind::Outside, ); } @@ -2293,7 +1398,7 @@ impl App { ui.painter().rect_stroke( r, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_SELECTION_HIGHLIGHT), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_SELECTION_HIGHLIGHT), StrokeKind::Outside, ); } @@ -2306,7 +1411,7 @@ impl App { ui.painter().rect_stroke( r, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_SELECTION_HIGHLIGHT), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_SELECTION_HIGHLIGHT), StrokeKind::Outside, ); } @@ -2319,13 +1424,13 @@ impl App { ui.painter().rect_stroke( r, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_SELECTION_HIGHLIGHT), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_SELECTION_HIGHLIGHT), StrokeKind::Outside, ); } InstanceKind::Wire => { for pin in self.db.pins_of(id) { - let pos = self.db.pin_position(pin); + let pos = self.db.pin_position(pin, &self.canvas_config); ui.painter().circle_filled( pos - self.viewport_offset, PIN_MOVE_HINT_D, @@ -2333,8 +1438,8 @@ impl App { ); } } - InstanceKind::CustomCircuit(_) => { - let cc = self.db.get_custom_circuit(id); + InstanceKind::Module(_) => { + let cc = self.db.get_module(id); let r = Rect::from_center_size( cc.pos - self.viewport_offset, self.canvas_config.base_gate_size + INSTANEC_OUTLINE, @@ -2342,7 +1447,7 @@ impl App { ui.painter().rect_stroke( r, CornerRadius::default(), - Stroke::new(INSTANEC_OUTLINE_TICKNESS, COLOR_SELECTION_HIGHLIGHT), + Stroke::new(INSTANEC_OUTLINE_THICKNESS, COLOR_SELECTION_HIGHLIGHT), StrokeKind::Outside, ); } @@ -2354,7 +1459,7 @@ impl App { let mut out = String::new(); writeln!( out, - "counts: gates={}, powers={}, lamps={}, clocks={}, wires={}, custom_circuits={}, custom_defs={}, conns={}", + "counts: gates={}, powers={}, lamps={}, clocks={}, wires={}, modules={}, custom_defs={}, conns={}", self.db.gates.len(), self.db.powers.len(), self.db.lamps.len(), @@ -2365,10 +1470,7 @@ impl App { self.db.connections.len() ) .ok(); - let mouse_pos_world = ui - .ctx() - .pointer_interact_pos() - .map(|p| self.screen_to_world(p)); + let mouse_pos_world = self.mouse_pos_world(ui); writeln!(out, "mouse: {mouse_pos_world:?}").ok(); writeln!(out, "hovered: {:?}", self.hovered).ok(); @@ -2423,21 +1525,20 @@ impl App { for (id, g) in &self.db.gates { writeln!(out, " {}", g.display(&self.db)).ok(); // pins - for (i, pin) in g.kind.graphics().pins.iter().enumerate() { - let pin_offset = pin.offset; - let p = g.pos + pin_offset; - let pin_instance = Pin { - ins: id, - index: i as u32, - }; - writeln!( - out, - " {} at ({:.1},{:.1})", - pin_instance.display(&self.db), - p.x, - p.y - ) - .ok(); + if true { + for (i, pin) in g.kind.graphics().pins.iter().enumerate() { + let pin_offset = pin.offset; + let p = g.pos + pin_offset; + let pin_instance = Pin::new(id, i as u32, pin.kind); + writeln!( + out, + " {} at ({:.1},{:.1})", + pin_instance.display(&self.db), + p.x, + p.y + ) + .ok(); + } } } @@ -2447,10 +1548,7 @@ impl App { for (i, pin) in p.graphics().pins.iter().enumerate() { let pin_offset = pin.offset; let pp = p.pos + pin_offset; - let pin_instance = Pin { - ins: id, - index: i as u32, - }; + let pin_instance = Pin::new(id, i as u32, pin.kind); writeln!( out, " {} at ({:.1},{:.1})", @@ -2469,10 +1567,7 @@ impl App { for (i, pin) in lamp.graphics().pins.iter().enumerate() { let pin_offset = pin.offset; let p = lamp.pos + pin_offset; - let pin_instance = Pin { - ins: id, - index: i as u32, - }; + let pin_instance = Pin::new(id, i as u32, pin.kind); writeln!( out, " {} at ({:.1},{:.1})", @@ -2491,10 +1586,7 @@ impl App { for (i, pin) in clock.graphics().pins.iter().enumerate() { let pin_offset = pin.offset; let p = clock.pos + pin_offset; - let pin_instance = Pin { - ins: id, - index: i as u32, - }; + let pin_instance = Pin::new(id, i as u32, pin.kind); writeln!( out, " {} at ({:.1},{:.1})", @@ -2507,8 +1599,13 @@ impl App { } writeln!(out, "\nWires:").ok(); - for (_id, w) in &self.db.wires { + for (id, w) in &self.db.wires { writeln!(out, " {}", w.display(&self.db)).ok(); + if true { + for pin in self.db.pins_of(id) { + writeln!(out, "{}", pin.display_alone()).ok(); + } + } } writeln!(out, "\nModules:").ok(); @@ -2516,6 +1613,11 @@ impl App { writeln!(out, " {}", m.display(&self.db, id)).ok(); } + writeln!(out, "\nModule Def:").ok(); + for (_id, m) in &self.db.module_definitions { + writeln!(out, " {}", m.display_definition()).ok(); + } + writeln!(out, "\nConnections:").ok(); for c in &self.db.connections { writeln!(out, " {}", c.display(&self.db)).ok(); @@ -2563,8 +1665,8 @@ impl App { let c = self.db.get_clock(id); points.push(c.pos); } - InstanceKind::CustomCircuit(_) => { - let cc = self.db.get_custom_circuit(id); + InstanceKind::Module(_) => { + let cc = self.db.get_module(id); points.push(cc.pos); } } @@ -2597,12 +1699,9 @@ impl App { let c = self.db.get_clock(id); object_pos.push(ClipBoardItem::Clock(center - c.pos)); } - InstanceKind::CustomCircuit(_) => { - let cc = self.db.get_custom_circuit(id); - object_pos.push(ClipBoardItem::CustomCircuit( - cc.definition_index, - center - cc.pos, - )); + InstanceKind::Module(_) => { + let cc = self.db.get_module(id); + object_pos.push(ClipBoardItem::Module(cc.definition_index, center - cc.pos)); } } } @@ -2652,8 +1751,8 @@ impl App { self.connection_manager.mark_instance_dirty(id); self.selected.insert(id); } - ClipBoardItem::CustomCircuit(def_index, offset) => { - let id = self.db.new_custom_circuit(custom_circuit::Module { + ClipBoardItem::Module(def_index, offset) => { + let id = self.db.new_module(Module { pos: mouse - offset, definition_index: def_index, }); @@ -2694,7 +1793,7 @@ impl App { match self.db.ty(selected) { InstanceKind::Wire => { for pin in self.db.pins_of(selected) { - let pos = self.db.pin_position(pin); + let pos = self.db.pin_position(pin, &self.canvas_config); ui.painter().circle_filled( pos - self.viewport_offset, PIN_MOVE_HINT_D, @@ -2705,7 +1804,7 @@ impl App { && mouse_down && mouse.distance(pos) < PIN_MOVE_HINT_D { - self.drag = Some(Drag::Resize { + self.set_drag(Drag::Resize { id: selected, start: pin.index == 0, }); @@ -2716,7 +1815,7 @@ impl App { | InstanceKind::Power | InstanceKind::Lamp | InstanceKind::Clock - | InstanceKind::CustomCircuit(_) => {} + | InstanceKind::Module(_) => {} } } @@ -2761,19 +1860,45 @@ impl App { } fn create_module(&mut self) { - // Generate a unique name for the custom circuit - let circuit_name = format!("module {}", self.db.module_definitions.len() + 1); + self.creating_module = true; + self.module_name_buffer = format!("module {}", self.db.module_definitions.len() + 1); + self.module_creation_error = None; + } + + fn confirm_module_creation(&mut self) { + // Validate the module name and clone it to avoid borrow issues + let name = self.module_name_buffer.trim().to_owned(); + if name.is_empty() { + self.module_creation_error = Some("Module name cannot be empty".to_owned()); + return; + } - match self.create_custom_circuit(circuit_name, &self.selected.clone()) { + match self.create_module_definition(name.clone(), &self.selected.clone()) { Ok(()) => { - log::info!("Custom circuit created successfully"); + log::info!("module created successfully: {name}"); self.selected.clear(); + // Close the dialog + self.creating_module = false; + self.module_name_buffer.clear(); + self.module_creation_error = None; } Err(e) => { - log::error!("Failed to create custom circuit: {e}"); + log::error!("Failed to create module: {e}"); + self.module_creation_error = Some(format!("Failed to create module: {e}")); } } } + + // Adjust position of an object to this screen + fn adjusted_pos(&self, pos: Pos2) -> Pos2 { + pos - self.viewport_offset + } + + fn mouse_pos_world(&self, ui: &Ui) -> Option { + ui.ctx() + .pointer_interact_pos() + .map(|p| p + self.viewport_offset) + } } fn get_icon<'a>(ui: &Ui, source: egui::ImageSource<'a>) -> Image<'a> { @@ -2785,9 +1910,3 @@ fn get_icon<'a>(ui: &Ui, source: egui::ImageSource<'a>) -> Image<'a> { image } - -fn draw_icon_canvas(ui: &mut Ui, source: egui::ImageSource<'_>, rect: Rect) { - let image = get_icon(ui, source).fit_to_exact_size(rect.size()); - - ui.put(rect, image); -} diff --git a/src/assets.rs b/src/assets.rs index 37075b2..70dfc70 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,13 +1,16 @@ use std::fmt::Display; use egui::{ImageSource, Vec2, include_image}; +#[derive(Debug, Clone)] pub struct InstanceGraphics { // TODO: Figure out what is the correct way to deal with images pub svg: ImageSource<'static>, pub pins: &'static [PinGraphics], } -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, PartialEq, Eq)] +#[derive( + serde::Deserialize, serde::Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, +)] pub enum PinKind { Input, Output, diff --git a/src/config.rs b/src/config.rs index 3466c68..6487c11 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use egui::{Color32, Vec2, vec2}; -#[derive(serde::Deserialize, serde::Serialize)] +#[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct CanvasConfig { pub base_gate_size: Vec2, pub base_pin_size: f32, diff --git a/src/connection_manager.rs b/src/connection_manager.rs index 25268b2..ab18ad7 100644 --- a/src/connection_manager.rs +++ b/src/connection_manager.rs @@ -1,6 +1,8 @@ #![allow(clippy::allow_attributes)] -use crate::app::{DB, InstanceId, InstanceKind, Pin, SNAP_THRESHOLD}; +use crate::app::SNAP_THRESHOLD; use crate::assets; +use crate::config::CanvasConfig; +use crate::db::{DB, InstanceId, InstanceKind, Pin}; use egui::Pos2; use std::collections::{HashMap, HashSet}; @@ -88,11 +90,18 @@ pub struct ConnectionManager { /// Cache of pin positions to detect when pins move pin_position_cache: HashMap, + + canvas_config: CanvasConfig, } impl ConnectionManager { - pub fn new(db: &DB) -> Self { - let mut new = Self::default(); + pub fn new(db: &DB, canvas_config: &CanvasConfig) -> Self { + let mut new = Self { + dirty_instances: Default::default(), + spatial_index: Default::default(), + pin_position_cache: Default::default(), + canvas_config: canvas_config.clone(), + }; new.rebuild_spatial_index(db); new } @@ -115,9 +124,9 @@ impl ConnectionManager { self.pin_position_cache.clear(); // Index all pins by their grid cell - for (instance_id, _) in &db.instances { + for (instance_id, _) in &db.types { for pin in db.pins_of(instance_id) { - let pos = db.pin_position(pin); + let pos = db.pin_position(pin, &self.canvas_config); let cell = GridCell::from_pos(pos); self.spatial_index.entry(cell).or_default().push(pin); @@ -130,7 +139,7 @@ impl ConnectionManager { /// Update spatial index for specific pins that have moved fn update_spatial_index_for_pins(&mut self, db: &DB, pins: &[Pin]) { for &pin in pins { - let new_pos = db.pin_position(pin); + let new_pos = db.pin_position(pin, &self.canvas_config); // Remove from old cell if position changed if let Some(old_pos) = self.pin_position_cache.get(&pin) @@ -153,8 +162,9 @@ impl ConnectionManager { /// Find potential connections for a pin using spatial indexing pub fn find_connections_for_pin(&self, db: &DB, pin: Pin) -> Vec { - let mut connections = Vec::new(); - let pin_pos = db.pin_position(pin); + let mut wire_connections = Vec::new(); + let mut non_wire_connections = Vec::new(); + let pin_pos = db.pin_position(pin, &self.canvas_config); let cell = GridCell::from_pos(pin_pos); // Check this cell and neighboring cells @@ -165,7 +175,7 @@ impl ConnectionManager { continue; } - let other_pos = db.pin_position(other_pin); + let other_pos = db.pin_position(other_pin, &self.canvas_config); let distance = (pin_pos - other_pos).length(); // First pin will move to attach @@ -176,13 +186,25 @@ impl ConnectionManager { }; if distance <= SNAP_THRESHOLD && self.validate_connection(db, connection) { - connections.push(connection); + let is_wire = matches!(db.ty(other_pin.ins), InstanceKind::Wire); + if is_wire { + wire_connections.push(connection); + } else { + non_wire_connections.push(connection); + } } } } } - connections + // For wire pins, prefer non-wire connections over wire connections + if matches!(db.ty(pin.ins), InstanceKind::Wire) && !non_wire_connections.is_empty() { + non_wire_connections + } else { + let mut all = non_wire_connections; + all.extend(wire_connections); + all + } } /// Validate if a connection between two pins is allowed @@ -195,12 +217,16 @@ impl ConnectionManager { return false; } + // One pin must be input, the other output + if c.a.kind == c.b.kind { + return false; + } true } /// Snap a pin to match the position of another pin fn snap_pin_to_other(&self, db: &mut DB, src: Pin, dst: Pin) { - let target = db.pin_position(dst); + let target = db.pin_position(dst, &self.canvas_config); match db.ty(src.ins) { InstanceKind::Wire => { if src.index == 0 { @@ -219,7 +245,7 @@ impl ConnectionManager { let pin_offset = info.offset; let current = g.pos + pin_offset; let desired = target - current; - db.move_instance_and_propagate(src.ins, desired); + db.move_instance_and_propagate(src.ins, desired, &self.canvas_config); } InstanceKind::Power => { let p = db.get_power_mut(src.ins); @@ -227,7 +253,7 @@ impl ConnectionManager { let pin_offset = info.offset; let current = p.pos + pin_offset; let desired = target - current; - db.move_instance_and_propagate(src.ins, desired); + db.move_instance_and_propagate(src.ins, desired, &self.canvas_config); } InstanceKind::Lamp => { let l = db.get_lamp_mut(src.ins); @@ -235,7 +261,7 @@ impl ConnectionManager { let pin_offset = info.offset; let current = l.pos + pin_offset; let desired = target - current; - db.move_instance_and_propagate(src.ins, desired); + db.move_instance_and_propagate(src.ins, desired, &self.canvas_config); } InstanceKind::Clock => { let c = db.get_clock_mut(src.ins); @@ -243,14 +269,14 @@ impl ConnectionManager { let pin_offset = info.offset; let current = c.pos + pin_offset; let desired = target - current; - db.move_instance_and_propagate(src.ins, desired); + db.move_instance_and_propagate(src.ins, desired, &self.canvas_config); } - InstanceKind::CustomCircuit(_) => { - let pin_offset = db.pin_offset(src); - let cc = db.get_custom_circuit_mut(src.ins); + InstanceKind::Module(_) => { + let pin_offset = db.pin_offset(src, &self.canvas_config); + let cc = db.get_module_mut(src.ins); let current = cc.pos + pin_offset; let desired = target - current; - db.move_instance_and_propagate(src.ins, desired); + db.move_instance_and_propagate(src.ins, desired, &self.canvas_config); } } } @@ -266,7 +292,7 @@ impl ConnectionManager { pins_to_update.sort_unstable(); pins_to_update.dedup(); - if pins_to_update.len() > db.instances.len() / 4 { + if pins_to_update.len() > db.types.len() / 4 { self.rebuild_spatial_index(db); } else { self.update_spatial_index_for_pins(db, &pins_to_update); @@ -291,8 +317,8 @@ impl ConnectionManager { && !self.dirty_instances.contains(&connection.b.ins); if keep_connection { - let p1 = db.pin_position(connection.a); - let p2 = db.pin_position(connection.b); + let p1 = db.pin_position(connection.a, &self.canvas_config); + let p2 = db.pin_position(connection.b, &self.canvas_config); if (p1 - p2).length() <= SNAP_THRESHOLD { connections_to_keep.insert(*connection); } @@ -326,16 +352,19 @@ impl ConnectionManager { #[cfg(test)] mod tests { use super::Connection; - use crate::app::{InstanceId, Pin}; + use crate::{ + assets::PinKind, + db::{InstanceId, Pin}, + }; use std::collections::HashSet; fn create_test_pins() -> (Pin, Pin, Pin) { let id1 = InstanceId::from(1); let id2 = InstanceId::from(2); let id3 = InstanceId::from(3); - let pin1 = Pin { ins: id1, index: 0 }; - let pin2 = Pin { ins: id2, index: 0 }; - let pin3 = Pin { ins: id3, index: 0 }; + let pin1 = Pin::new(id1, 1, PinKind::Input); + let pin2 = Pin::new(id2, 2, PinKind::Input); + let pin3 = Pin::new(id3, 3, PinKind::Input); (pin1, pin2, pin3) } diff --git a/src/custom_circuit.rs b/src/custom_circuit.rs deleted file mode 100644 index 0afb37d..0000000 --- a/src/custom_circuit.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::collections::HashSet; - -use egui::{Pos2, Rect, Vec2}; - -use crate::{ - app::{App, DB, InstanceId, InstanceKind, Pin}, - assets, - connection_manager::Connection, -}; - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct ModuleDefinition { - pub name: String, - pub internal_components: Vec, - pub internal_connections: Vec, - pub external_pins: Vec, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy)] -pub struct CustomCircuitPin { - pub kind: assets::PinKind, - pub offset: Vec2, - pub internal_pin: Pin, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct Module { - pub pos: Pos2, - pub definition_index: usize, -} - -impl Module { - pub fn name(&self) -> String { - format!("module {}", self.definition_index) - } - - pub fn display(&self, db: &DB, id: InstanceId) -> String { - let mut sb = format!("{}", self.name()); - for pin in db.pins_of(id) { - sb += "\n"; - sb += pin.display(db).as_str(); - } - - sb - } -} - -impl App { - pub fn find_free_pins(&self, instances: &HashSet) -> Vec { - let mut free_pins = Vec::new(); - - for &id in instances { - for pin in self.db.pins_of(id) { - if !self.is_pin_connected(pin) { - free_pins.push(pin); - } - } - } - - free_pins - } - - pub fn create_custom_circuit( - &mut self, - name: String, - instances: &HashSet, - ) -> Result<(), String> { - if instances.is_empty() { - return Err("No components selected".to_owned()); - } - - let mut internal_components = Vec::new(); - for instance in instances { - internal_components.push(self.db.ty(*instance)); - } - - let mut internal_connections = Vec::new(); - for connection in &self.db.connections { - if instances.contains(&connection.a.ins) && instances.contains(&connection.b.ins) { - internal_connections.push(*connection); - } - } - - let free_pins = self.find_free_pins(instances); - if free_pins.is_empty() { - return Err("Selected components have no free pins to expose".to_owned()); - } - - let external_pins = self.create_external_pins(&free_pins); - - let definition = ModuleDefinition { - name, - internal_components, - internal_connections, - external_pins, - }; - - self.db.module_definitions.push(definition); - - Ok(()) - } - - pub fn create_external_pins(&self, free_pins: &[Pin]) -> Vec { - let mut external_pins = Vec::new(); - let gate_size = self.canvas_config.base_gate_size * 2.0; - - let mut input_pins = Vec::new(); - let mut output_pins = Vec::new(); - - for &pin in free_pins { - let pin_kind = self.db.pin_info(pin).kind; - match pin_kind { - assets::PinKind::Input => input_pins.push(pin), - assets::PinKind::Output => output_pins.push(pin), - } - } - - let input_spacing = if input_pins.len() > 1 { - gate_size.y / (input_pins.len() as f32 + 1.0) - } else { - 0.0 - }; - - for (i, &pin) in input_pins.iter().enumerate() { - let y_offset = if input_pins.len() == 1 { - 0.0 - } else { - -gate_size.y / 2.0 + (i + 1) as f32 * input_spacing - }; - - external_pins.push(CustomCircuitPin { - kind: assets::PinKind::Input, - offset: Vec2::new(-gate_size.x / 2.0, y_offset), - internal_pin: pin, - }); - } - - let output_spacing = if output_pins.len() > 1 { - gate_size.y / (output_pins.len() as f32 + 1.0) - } else { - 0.0 - }; - - for (i, &pin) in output_pins.iter().enumerate() { - let y_offset = if output_pins.len() == 1 { - 0.0 - } else { - -gate_size.y / 2.0 + (i + 1) as f32 * output_spacing - }; - - external_pins.push(CustomCircuitPin { - kind: assets::PinKind::Output, - offset: Vec2::new(gate_size.x / 2.0, y_offset), - internal_pin: pin, - }); - } - - external_pins - } -} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..a7aab19 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,887 @@ +use std::collections::HashSet; +use std::fmt::Display; +use std::hash::Hash; + +use egui::{Pos2, Vec2, pos2}; +use slotmap::{SecondaryMap, SlotMap}; + +use crate::assets::PinKind; +use crate::{ + assets::{self}, + config::CanvasConfig, + connection_manager::Connection, + module::{Module, ModuleDefinition}, +}; + +slotmap::new_key_type! { + pub struct InstanceId; +} + +impl Display for InstanceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{:?}", self.0)) + } +} + +impl From for InstanceId { + fn from(value: u32) -> Self { + Self(slotmap::KeyData::from_ffi(value as u64)) + } +} + +slotmap::new_key_type! { + pub struct LabelId; +} + +impl From for LabelId { + fn from(value: u32) -> Self { + Self(slotmap::KeyData::from_ffi(value as u64)) + } +} + +slotmap::new_key_type! { + pub struct ModuleDefId; +} + +impl From for ModuleDefId { + fn from(value: u32) -> Self { + Self(slotmap::KeyData::from_ffi(value as u64)) + } +} + +#[derive(Default, serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct Circuit {} + +#[derive(Default, serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct DB { + pub circuit: Circuit, + // Type registry for each instance id + pub types: SlotMap, + // Per-kind payloads keyed off the primary key space + pub gates: SecondaryMap, + pub powers: SecondaryMap, + pub wires: SecondaryMap, + pub lamps: SecondaryMap, + pub clocks: SecondaryMap, + pub modules: SecondaryMap, + pub connections: HashSet, + // Labels + pub labels: SlotMap, + // Definition of modules created by the user + pub module_definitions: SlotMap, +} + +impl DB { + pub fn new() -> Self { + Self::default() + } + pub fn new_gate(&mut self, g: Gate) -> InstanceId { + let k = self.types.insert(InstanceKind::Gate(g.kind)); + self.gates.insert(k, g); + let kind = self + .gates + .get(k) + .expect("gate must exist right after insertion") + .kind; + k + } + + pub fn new_power(&mut self, p: Power) -> InstanceId { + let k = self.types.insert(InstanceKind::Power); + self.powers.insert(k, p); + k + } + + pub fn new_wire(&mut self, w: Wire) -> InstanceId { + let k = self.types.insert(InstanceKind::Wire); + self.wires.insert(k, w); + k + } + + pub fn new_lamp(&mut self, l: Lamp) -> InstanceId { + let k = self.types.insert(InstanceKind::Lamp); + self.lamps.insert(k, l); + k + } + + pub fn new_clock(&mut self, c: Clock) -> InstanceId { + let k = self.types.insert(InstanceKind::Clock); + self.clocks.insert(k, c); + k + } + + pub fn new_module(&mut self, c: crate::module::Module) -> InstanceId { + let k = self.types.insert(InstanceKind::Module(c.definition_index)); + self.modules.insert(k, c); + k + } + + pub fn ty(&self, id: InstanceId) -> InstanceKind { + self.types + .get(id) + .copied() + .unwrap_or_else(|| panic!("instance type missing for id: {id:?}")) + } + + pub fn get_gate(&self, id: InstanceId) -> &Gate { + self.gates.get(id).expect("gate not found") + } + + pub fn get_gate_mut(&mut self, id: InstanceId) -> &mut Gate { + self.gates.get_mut(id).expect("gate not found (mut)") + } + + pub fn get_power(&self, id: InstanceId) -> &Power { + self.powers.get(id).expect("power not found") + } + + pub fn get_power_mut(&mut self, id: InstanceId) -> &mut Power { + self.powers.get_mut(id).expect("power not found (mut)") + } + + pub fn get_wire(&self, id: InstanceId) -> &Wire { + self.wires.get(id).expect("wire not found") + } + + pub fn get_wire_mut(&mut self, id: InstanceId) -> &mut Wire { + self.wires.get_mut(id).expect("wire not found (mut)") + } + + pub fn get_lamp(&self, id: InstanceId) -> &Lamp { + self.lamps.get(id).expect("lamp not found") + } + + pub fn get_lamp_mut(&mut self, id: InstanceId) -> &mut Lamp { + self.lamps.get_mut(id).expect("lamp not found (mut)") + } + + pub fn get_clock(&self, id: InstanceId) -> &Clock { + self.clocks.get(id).expect("clock not found") + } + + pub fn get_clock_mut(&mut self, id: InstanceId) -> &mut Clock { + self.clocks.get_mut(id).expect("clock not found (mut)") + } + + pub fn get_module(&self, id: InstanceId) -> &Module { + self.modules.get(id).expect("module not found") + } + + pub fn get_module_mut(&mut self, id: InstanceId) -> &mut Module { + self.modules.get_mut(id).expect("modules not found (mut)") + } + + pub fn get_module_def(&self, def_index: ModuleDefId) -> &ModuleDefinition { + self.module_definitions + .get(def_index) + .expect("module def not found") + } + + // Pin helper methods with type checking + + // Gates - generic versions + pub fn gate_inp_n(&self, id: InstanceId, n: u32) -> Pin { + self.get_gate(id); // Type check + assert!(n < 2, "Gates only have 2 inputs (0 and 1)"); + Pin::new(id, if n == 0 { 0 } else { 2 }, PinKind::Input) + } + + pub fn gate_output_n(&self, id: InstanceId, n: u32) -> Pin { + self.get_gate(id); // Type check + assert!(n == 0, "Gates only have 1 output"); + Pin::new(id, 1, PinKind::Output) + } + + pub fn gate_inp1(&self, id: InstanceId) -> Pin { + self.gate_inp_n(id, 0) + } + + pub fn gate_inp2(&self, id: InstanceId) -> Pin { + self.gate_inp_n(id, 1) + } + + pub fn gate_output(&self, id: InstanceId) -> Pin { + self.gate_output_n(id, 0) + } + + pub fn wire_pin_n(&self, id: InstanceId, n: u32) -> Pin { + self.get_wire(id); // Type check + assert!(n < 2, "Wires only have 2 pins (0 and 1)"); + Pin::new( + id, + n, + if n == 0 { + PinKind::Input + } else { + PinKind::Output + }, + ) + } + + pub fn wire_start(&self, id: InstanceId) -> Pin { + self.wire_pin_n(id, 0) + } + + pub fn wire_end(&self, id: InstanceId) -> Pin { + self.wire_pin_n(id, 1) + } + + pub fn power_output(&self, id: InstanceId) -> Pin { + self.get_power(id); + Pin::new(id, 0, PinKind::Output) + } + + pub fn lamp_input(&self, id: InstanceId) -> Pin { + self.get_lamp(id); + Pin::new(id, 0, PinKind::Input) + } + + pub fn clock_output(&self, id: InstanceId) -> Pin { + self.get_clock(id); + Pin::new(id, 0, PinKind::Output) + } + + pub fn new_label(&mut self, label: Label) -> LabelId { + self.labels.insert(label) + } + + pub fn get_label(&self, id: LabelId) -> &Label { + self.labels.get(id).expect("label not found") + } + + pub fn get_label_mut(&mut self, id: LabelId) -> &mut Label { + self.labels.get_mut(id).expect("label not found (mut)") + } + + pub fn gate_ids(&self) -> Vec { + self.gates.keys().collect() + } + + pub fn power_ids(&self) -> Vec { + self.powers.keys().collect() + } + + pub fn lamp_ids(&self) -> Vec { + self.lamps.keys().collect() + } + + pub fn clock_ids(&self) -> Vec { + self.clocks.keys().collect() + } + + pub fn module_ids(&self) -> Vec { + self.modules.keys().collect() + } + + pub fn wire_ids(&self) -> Vec { + self.wires.keys().collect() + } + + pub fn label_ids(&self) -> Vec { + self.labels.keys().collect() + } + + pub fn pins_of(&self, id: InstanceId) -> Vec { + match self.ty(id) { + InstanceKind::Gate(gk) => { + let graphics = gk.graphics(); + graphics + .pins + .iter() + .enumerate() + .map(|(i, p)| Pin::new(id, i as u32, p.kind)) + .collect() + } + InstanceKind::Power => { + let graphics = assets::POWER_OFF_GRAPHICS.clone(); + graphics + .pins + .iter() + .enumerate() + .map(|(i, p)| Pin::new(id, i as u32, p.kind)) + .collect() + } + InstanceKind::Wire => { + let wire = self.get_wire(id); + vec![ + Pin::new( + id, + 0, + if wire.input_index == 0 { + assets::PinKind::Input + } else { + assets::PinKind::Output + }, + ), + Pin::new( + id, + 1, + if wire.input_index == 1 { + assets::PinKind::Input + } else { + assets::PinKind::Output + }, + ), + ] + } + InstanceKind::Lamp => { + let graphics = assets::LAMP_GRAPHICS.clone(); + graphics + .pins + .iter() + .enumerate() + .map(|(i, p)| Pin::new(id, i as u32, p.kind)) + .collect() + } + InstanceKind::Clock => { + let graphics = assets::CLOCK_GRAPHICS.clone(); + graphics + .pins + .iter() + .enumerate() + .map(|(i, p)| Pin::new(id, i as u32, p.kind)) + .collect() + } + InstanceKind::Module(_) => { + // TODO: Modules + vec![] + } + } + } + + pub fn pin_position(&self, pin: Pin, canvas_config: &CanvasConfig) -> Pos2 { + match self.ty(pin.ins) { + InstanceKind::Gate(gk) => { + let g = self.get_gate(pin.ins); + let info = gk.graphics().pins[pin.index as usize]; + g.pos + info.offset + } + InstanceKind::Power => { + let p = self.get_power(pin.ins); + let info = p.graphics().pins[pin.index as usize]; + p.pos + info.offset + } + InstanceKind::Wire => { + let w = self.get_wire(pin.ins); + if pin.index == 0 { w.start } else { w.end } + } + InstanceKind::Lamp => { + let l = self.get_lamp(pin.ins); + let info = l.graphics().pins[pin.index as usize]; + l.pos + info.offset + } + InstanceKind::Clock => { + let c = self.get_clock(pin.ins); + let info = c.graphics().pins[pin.index as usize]; + c.pos + info.offset + } + InstanceKind::Module(_) => { + let cc = self.get_module(pin.ins); + cc.pos + self.pin_offset(pin, canvas_config) + } + } + } + + pub fn pin_offset(&self, pin: Pin, canvas_config: &CanvasConfig) -> Vec2 { + match self.ty(pin.ins) { + InstanceKind::Gate(gk) => { + let info = gk.graphics().pins[pin.index as usize]; + info.offset + } + InstanceKind::Power => { + let p = self.get_power(pin.ins); + let info = p.graphics().pins[pin.index as usize]; + info.offset + } + InstanceKind::Wire => { + let w = self.get_wire(pin.ins); + let center = w.center(); + if pin.index == 0 { + center - w.start + } else { + center - w.end + } + } + InstanceKind::Lamp => { + let l = self.get_lamp(pin.ins); + let info = l.graphics().pins[pin.index as usize]; + info.offset + } + InstanceKind::Clock => { + let c = self.get_clock(pin.ins); + let info = c.graphics().pins[pin.index as usize]; + info.offset + } + InstanceKind::Module(_) => { + // TODO: Modules + Vec2::ZERO + } + } + } + + pub fn connected_pins_of_instance(&self, id: InstanceId) -> Vec { + let mut out = Vec::new(); + for c in &self.connections { + if c.a.ins == id { + out.push(c.b); + } else if c.b.ins == id { + out.push(c.a); + } + } + out + } + + // Connected pins to this pin + pub fn connected_pins(&self, pin: Pin) -> Vec { + let mut res = Vec::new(); + for c in &self.connections { + if let Some((_, other)) = c.get_pin_first(pin) { + res.push(other); + } + } + res + } + + pub fn connected_insntances(&self, id: InstanceId) -> Vec { + let mut out = vec![id]; + for c in &self.connections { + if c.a.ins == id { + out.push(c.b.ins); + } else if c.b.ins == id { + out.push(c.a.ins); + } + } + out + } + + pub fn move_nonwires_and_resize_wires(&mut self, ids: &[InstanceId], delta: Vec2) { + let ids_set: HashSet = ids.iter().copied().collect(); + + for id in ids { + match self.ty(*id) { + InstanceKind::Gate(_) => { + let g = self.get_gate_mut(*id); + g.pos += delta; + } + InstanceKind::Power => { + let p = self.get_power_mut(*id); + p.pos += delta; + } + InstanceKind::Wire => { + let w = self.get_wire_mut(*id); + w.start += delta; + w.end += delta; + } + InstanceKind::Lamp => { + let l = self.get_lamp_mut(*id); + l.pos += delta; + } + InstanceKind::Clock => { + let c = self.get_clock_mut(*id); + c.pos += delta; + } + InstanceKind::Module(_) => { + let cc = self.get_module_mut(*id); + cc.pos += delta; + } + } + } + + for id in ids { + for pin in self.connected_pins_of_instance(*id) { + if matches!(self.ty(pin.ins), InstanceKind::Wire) && !ids_set.contains(&pin.ins) { + // Otherwise resize the wire + let w = self.get_wire_mut(pin.ins); + if pin.index == 0 { + w.start += delta; + } else { + w.end += delta; + } + } + } + } + } + + pub fn move_instance_and_propagate( + &mut self, + id: InstanceId, + delta: Vec2, + canvas_config: &CanvasConfig, + ) { + let mut visited = HashSet::new(); + self.move_instance_and_propagate_recursive(id, delta, &mut visited, canvas_config); + } + + fn move_instance_and_propagate_recursive( + &mut self, + id: InstanceId, + delta: Vec2, + visited: &mut HashSet, + canvas_config: &CanvasConfig, + ) { + if !visited.insert(id) { + return; + } + + // Move this instance + match self.ty(id) { + InstanceKind::Gate(_) => { + let g = self.get_gate_mut(id); + g.pos += delta; + } + InstanceKind::Power => { + let p = self.get_power_mut(id); + p.pos += delta; + } + InstanceKind::Wire => { + let w = self.get_wire_mut(id); + w.start += delta; + w.end += delta; + } + InstanceKind::Lamp => { + let l = self.get_lamp_mut(id); + l.pos += delta; + } + InstanceKind::Clock => { + let c = self.get_clock_mut(id); + c.pos += delta; + } + InstanceKind::Module(_) => { + let cc = self.get_module_mut(id); + cc.pos += delta; + } + } + + // Get connected instances before we recurse + let connected = self.connected_insntances(id); + + // Process each connected instance + for connected_id in connected { + if connected_id == id || visited.contains(&connected_id) { + continue; + } + + match self.ty(connected_id) { + InstanceKind::Wire => { + // For wires, resize them to stay connected + // Find which pin of the wire is connected to our moved instance + let wire_pins = self.pins_of(connected_id); + for wire_pin in wire_pins { + // Check if this wire pin is connected to any pin of our moved instance + for moved_pin in self.pins_of(id) { + if self + .connections + .contains(&Connection::new(wire_pin, moved_pin)) + { + let new_pin_pos = self.pin_position(moved_pin, canvas_config); + let w = self.get_wire_mut(connected_id); + if wire_pin.index == 0 { + w.start = new_pin_pos; + } else { + w.end = new_pin_pos; + } + } + } + } + // Mark as visited but don't propagate further (wires are endpoints) + visited.insert(connected_id); + } + InstanceKind::Gate(_) + | InstanceKind::Power + | InstanceKind::Lamp + | InstanceKind::Clock + | InstanceKind::Module(_) => { + // For non-wires, propagate the same delta + self.move_instance_and_propagate_recursive( + connected_id, + delta, + visited, + canvas_config, + ); + } + } + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] +pub enum InstanceKind { + Gate(GateKind), + Power, + Wire, + Lamp, + Clock, + Module(ModuleDefId), +} + +#[derive(serde::Deserialize, serde::Serialize, PartialEq, Eq, Copy, Debug, Clone)] +pub enum GateKind { + And, + Nand, + Or, + Nor, + Xor, + Xnor, +} + +#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] +pub struct Gate { + // Center position + pub pos: Pos2, + pub kind: GateKind, +} + +impl GateKind { + pub fn graphics(&self) -> assets::InstanceGraphics { + match self { + Self::Nand => assets::NAND_GRAPHICS.clone(), + Self::And => assets::AND_GRAPHICS.clone(), + Self::Or => assets::OR_GRAPHICS.clone(), + Self::Nor => assets::NOR_GRAPHICS.clone(), + Self::Xor => assets::XOR_GRAPHICS.clone(), + Self::Xnor => assets::XNOR_GRAPHICS.clone(), + } + } +} + +impl Gate { + pub fn display(&self, db: &DB) -> String { + // Find the InstanceId for this gate in the database + for (id, gate) in &db.gates { + if gate.pos == self.pos && gate.kind == self.kind { + return format!("{:?} {}", self.kind, id); + } + } + format!( + "Gate {{ kind: {:?}, pos: ({:.1}, {:.1}) }} - not found in DB", + self.kind, self.pos.x, self.pos.y + ) + } +} + +// Gate end + +// Power + +#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] +pub struct Power { + // Center position + pub pos: Pos2, + pub on: bool, +} + +impl Power { + pub fn display(&self, db: &DB) -> String { + // Find the InstanceId for this power in the database + for (id, power) in &db.powers { + if power.pos == self.pos && power.on == self.on { + return format!("Power {{ id: {}, on: {}}}", id, self.on); + } + } + format!( + "Power {{ on: {}, pos: ({:.1}, {:.1}) }} - not found in DB", + self.on, self.pos.x, self.pos.y + ) + } + + pub fn graphics(&self) -> assets::InstanceGraphics { + if self.on { + assets::POWER_ON_GRAPHICS.clone() + } else { + assets::POWER_OFF_GRAPHICS.clone() + } + } +} + +// Power end + +// Lamp + +#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] +pub struct Lamp { + pub pos: Pos2, +} + +impl Lamp { + pub fn display(&self, db: &DB) -> String { + // Find the InstanceId for this lamp in the database + for (id, lamp) in &db.lamps { + if lamp.pos == self.pos { + return format!("Lamp {{ id: {id}}}"); + } + } + format!( + "Lamp {{ pos: ({:.1}, {:.1}) }} - not found in DB", + self.pos.x, self.pos.y + ) + } + + pub fn graphics(&self) -> assets::InstanceGraphics { + assets::LAMP_GRAPHICS.clone() + } +} + +// Lamp end + +// Clock + +#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] +pub struct Clock { + pub pos: Pos2, + pub period: u32, // Placeholder for future use +} + +impl Clock { + pub fn display(&self, db: &DB) -> String { + for (id, clock) in &db.clocks { + if clock.pos == self.pos { + return format!("Clock {{ id: {id}}}"); + } + } + format!( + "Clock {{ pos: ({:.1}, {:.1}) }} - not found in DB", + self.pos.x, self.pos.y + ) + } + + pub fn graphics(&self) -> assets::InstanceGraphics { + assets::CLOCK_GRAPHICS.clone() + } +} + +// Clock end + +// Label + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct Label { + pub pos: Pos2, + pub text: String, +} + +/// Label is a visual annotation that user can place anywhere in the canvas. +/// Therefore it is not an instance like Gate because it does nothing. +/// It is handled separately in the code. +impl Label { + pub fn new(pos: Pos2) -> Self { + Self { + pos, + text: String::from("Label"), + } + } +} + +// Label end + +#[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] +pub struct Wire { + pub start: Pos2, + pub end: Pos2, + pub input_index: u32, +} + +impl Wire { + pub fn display(&self, db: &DB) -> String { + for (id, wire) in &db.wires { + if wire.start == self.start + && wire.end == self.end + && wire.input_index == self.input_index + { + return format!("Wire {id}"); + } + } + format!( + "Wire {{ start: ({:.1}, {:.1}), end: ({:.1}, {:.1}) }} - not found in DB", + self.start.x, self.start.y, self.end.x, self.end.y + ) + } + + pub fn new_at(pos: Pos2) -> Self { + Self::new(pos2(pos.x - 30.0, pos.y), pos2(pos.x + 30.0, pos.y)) + } + pub fn new(start: Pos2, end: Pos2) -> Self { + Self { + start, + end, + input_index: 0, + } + } + + pub fn closest_point_on_line(&self, p: Pos2) -> Pos2 { + let a = self.start; + let b = self.end; + let ab: Vec2 = b - a; + let ap: Vec2 = p - a; + + let ab_len2 = ab.x * ab.x + ab.y * ab.y; + if ab_len2 == 0.0 { + return a; + } + + let t = ((ap.x * ab.x + ap.y * ab.y) / ab_len2).clamp(0.0, 1.0); + + a + ab * t + } + + pub fn dist_to_closest_point_on_line(&self, p: Pos2) -> f32 { + let closest = self.closest_point_on_line(p); + (p - closest).length() + } + + pub fn center(&self) -> Pos2 { + pos2( + (self.start.x + self.end.x) * 0.5, + (self.start.y + self.end.y) * 0.5, + ) + } +} + +// A specific pin on an instance +#[derive( + serde::Deserialize, serde::Serialize, Copy, Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, +)] +pub struct Pin { + pub ins: InstanceId, + pub index: u32, + pub kind: PinKind, +} + +impl Pin { + pub fn display(&self, db: &DB) -> String { + let instance_display = match db.ty(self.ins) { + InstanceKind::Gate(_) => { + let gate = db.get_gate(self.ins); + gate.display(db) + } + InstanceKind::Power => { + let power = db.get_power(self.ins); + power.display(db) + } + InstanceKind::Wire => { + let wire = db.get_wire(self.ins); + wire.display(db) + } + InstanceKind::Lamp => { + let lamp = db.get_lamp(self.ins); + lamp.display(db) + } + InstanceKind::Clock => { + let clock = db.get_clock(self.ins); + clock.display(db) + } + InstanceKind::Module(_) => db.get_module(self.ins).display(db, self.ins), + }; + format!( + "{:?} pin#{} in {} ", + self.kind, self.index, instance_display, + ) + } + + pub fn display_alone(&self) -> String { + format!("pin#{} {:?}", self.index, self.kind) + } + + pub fn new(ins: InstanceId, index: u32, kind: PinKind) -> Self { + Self { ins, index, kind } + } +} diff --git a/src/drag.rs b/src/drag.rs index cfa84ab..9023057 100644 --- a/src/drag.rs +++ b/src/drag.rs @@ -2,10 +2,9 @@ use std::collections::HashSet; use egui::{CornerRadius, Pos2, Rect, Stroke, StrokeKind, Ui, Vec2, pos2}; -use crate::app::{ - App, COLOR_HOVER_PIN_TO_WIRE, COLOR_SELECTION_BOX, Hover, InstanceId, InstanceKind, LabelId, - MIN_WIRE_SIZE, Pin, Wire, -}; +use crate::app::{App, COLOR_HOVER_PIN_TO_WIRE, COLOR_SELECTION_BOX, MIN_WIRE_SIZE}; + +use crate::db::{InstanceId, InstanceKind, LabelId, Pin, Wire}; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy)] pub enum CanvasDrag { @@ -46,110 +45,11 @@ pub enum Drag { } impl App { - pub fn handle_drag_start(&mut self, mouse: Pos2) { + pub fn set_drag(&mut self, drag: Drag) { if self.drag.is_some() { return; } - - self.drag_had_movement = false; - - if self.selected.len() == 1 - && let Some(wire_id) = self.selected.iter().next() - && let Some(split_point) = self.wire_branching_action_point(mouse, *wire_id) - { - self.drag = Some(Drag::BranchWire { - original_wire_id: *wire_id, - split_point, - start_mouse_pos: mouse, - }); - return; - } - - let Some(hovered) = self.hovered else { - self.drag = Some(Drag::Selecting { start: mouse }); - self.potential_connections.clear(); - return; - }; - - match hovered { - Hover::Pin(pin) => { - if self.selected.contains(&pin.ins) - && matches!(self.db.ty(pin.ins), InstanceKind::Wire) - { - self.drag = Some(Drag::Resize { - id: pin.ins, - start: pin.index != 1, - }); - return; - } - let pin_pos = self.db.pin_position(pin); - self.drag = Some(Drag::PinToWire { - source_pin: pin, - start_pos: pin_pos, - }); - } - Hover::Instance(instance) => { - if self.selected.contains(&instance) { - self.drag = Some(Drag::Canvas(CanvasDrag::Selected { start: mouse })); - return; - } - match self.db.ty(instance) { - InstanceKind::Gate(_) => { - let gate = self.db.get_gate(instance); - let offset = gate.pos - mouse; - self.drag = Some(Drag::Canvas(CanvasDrag::Single { - id: instance, - offset, - })); - } - InstanceKind::Power => { - let power = self.db.get_power(instance); - let offset = power.pos - mouse; - self.drag = Some(Drag::Canvas(CanvasDrag::Single { - id: instance, - offset, - })); - } - InstanceKind::Wire => { - let wire = self.db.get_wire(instance); - - let wire_center = pos2( - (wire.start.x + wire.end.x) * 0.5, - (wire.start.y + wire.end.y) * 0.5, - ); - let offset = wire_center - mouse; - self.drag = Some(Drag::Canvas(CanvasDrag::Single { - id: instance, - offset, - })); - } - InstanceKind::Lamp => { - let lamp = self.db.get_lamp(instance); - let offset = lamp.pos - mouse; - self.drag = Some(Drag::Canvas(CanvasDrag::Single { - id: instance, - offset, - })); - } - InstanceKind::Clock => { - let clock = self.db.get_clock(instance); - let offset = clock.pos - mouse; - self.drag = Some(Drag::Canvas(CanvasDrag::Single { - id: instance, - offset, - })); - } - InstanceKind::CustomCircuit(_) => { - let cc = self.db.get_custom_circuit(instance); - let offset = cc.pos - mouse; - self.drag = Some(Drag::Canvas(CanvasDrag::Single { - id: instance, - offset, - })); - } - } - } - } + self.drag = Some(drag); } pub fn handle_dragging(&mut self, ui: &mut Ui, mouse: Pos2) { @@ -172,30 +72,28 @@ impl App { Stroke::new(1.5, COLOR_SELECTION_BOX), StrokeKind::Outside, ); - if (mouse - start).length_sq() > 0.0 { - self.drag_had_movement = true; - } } Some(Drag::Canvas(canvas_drag)) => match canvas_drag { CanvasDrag::Single { id, offset } => { let new_pos = mouse + offset; - let mut moved = false; - match self.db.ty(id) { + let moved = match self.db.ty(id) { InstanceKind::Gate(_) | InstanceKind::Power | InstanceKind::Lamp + | InstanceKind::Module(_) | InstanceKind::Clock => { let current_pos = match self.db.ty(id) { InstanceKind::Gate(_) => self.db.get_gate(id).pos, InstanceKind::Power => self.db.get_power(id).pos, InstanceKind::Lamp => self.db.get_lamp(id).pos, InstanceKind::Clock => self.db.get_clock(id).pos, - _ => unreachable!(), + InstanceKind::Module(_) => self.db.get_module(id).pos, + InstanceKind::Wire => unreachable!(), }; let desired = new_pos - current_pos; let ids = [id]; self.db.move_nonwires_and_resize_wires(&ids, desired); - moved = desired.length_sq() > 0.0; + desired.length_sq() > 0.0 } InstanceKind::Wire => { let w = self.db.get_wire_mut(id); @@ -204,20 +102,12 @@ impl App { let desired = new_pos - center; w.start += desired; w.end += desired; - moved = desired.length_sq() > 0.0; - } - InstanceKind::CustomCircuit(_) => { - let cc = self.db.get_custom_circuit_mut(id); - if cc.pos != new_pos { - cc.pos = new_pos; - moved = true; - } + desired.length_sq() > 0.0 } - } + }; if moved { self.connection_manager.mark_instance_dirty(id); - self.drag_had_movement = true; } } CanvasDrag::Selected { start } => { @@ -231,7 +121,6 @@ impl App { self.db.move_nonwires_and_resize_wires(&group, desired); if desired.length_sq() > 0.0 { self.connection_manager.mark_instances_dirty(&group); - self.drag_had_movement = true; } } }, @@ -257,7 +146,6 @@ impl App { if moved { self.connection_manager.mark_instance_dirty(id); - self.drag_had_movement = true; } } Some(Drag::PinToWire { @@ -275,7 +163,6 @@ impl App { start: false, }); - self.drag_had_movement = true; self.connection_manager.mark_instance_dirty(wire_id); } else if drag_distance > 2.0 { ui.painter().line_segment( @@ -304,7 +191,6 @@ impl App { start: false, }); - self.drag_had_movement = true; self.connection_manager.mark_instance_dirty(branch_wire_id); } else if drag_distance > 2.0 { ui.painter().line_segment( @@ -321,7 +207,6 @@ impl App { let label = self.db.get_label_mut(id); if label.pos != new_pos { label.pos = new_pos; - self.drag_had_movement = true; } } None => {} @@ -413,7 +298,6 @@ impl App { } self.connection_manager.rebuild_spatial_index(&self.db); self.potential_connections.clear(); - self.drag_had_movement = false; } pub fn compute_potential_connections(&mut self) { diff --git a/src/lib.rs b/src/lib.rs index f616bc0..8848033 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,9 @@ pub mod app; pub mod assets; pub mod config; pub mod connection_manager; -pub mod custom_circuit; +pub mod db; pub mod drag; +pub mod module; pub mod save_load; pub use app::App; pub mod simulator; diff --git a/src/module.rs b/src/module.rs new file mode 100644 index 0000000..34fbcb7 --- /dev/null +++ b/src/module.rs @@ -0,0 +1,95 @@ +use std::collections::HashSet; + +use egui::Pos2; + +use crate::{ + app::App, + assets::PinGraphics, + db::{DB, InstanceId, ModuleDefId, Pin}, +}; + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct ModuleDefinition { + pub name: String, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct Module { + pub pos: Pos2, + pub definition_index: ModuleDefId, +} + +impl Module { + pub fn name(&self, db: &DB) -> String { + db.get_module_def(self.definition_index).name.clone() + } + + pub fn definition<'a>(&self, db: &'a DB) -> &'a ModuleDefinition { + db.get_module_def(self.definition_index) + } + + pub fn display(&self, db: &DB, id: InstanceId) -> String { + let mut sb = self.name(db); + sb += &format!(" {id}"); + sb + } +} + +impl ModuleDefinition { + pub fn display_definition(&self) -> String { + let sb = format!("Module: {}\n", self.name); + sb + } +} + +impl App { + pub fn find_free_pins(&self, instances: &HashSet) -> Vec { + let mut free_pins = Vec::new(); + + for &id in instances { + for pin in self.db.pins_of(id) { + if !self.is_pin_connected(pin) { + free_pins.push(pin); + } + } + } + + free_pins + } + + pub fn get_pins(&self, pins: Vec) -> &'static [PinGraphics] { + Box::leak(pins.into_boxed_slice()) + } + + pub fn create_module_definition( + &mut self, + name: String, + instances: &HashSet, + ) -> Result<(), String> { + if instances.is_empty() { + return Err("No components selected".to_owned()); + } + + let mut internal_components = Vec::new(); + for instance in instances { + internal_components.push(self.db.ty(*instance)); + } + + let mut internal_connections = Vec::new(); + for connection in &self.db.connections { + if instances.contains(&connection.a.ins) && instances.contains(&connection.b.ins) { + internal_connections.push(*connection); + } + } + + let free_pins = self.find_free_pins(instances); + if free_pins.is_empty() { + return Err("Selected components have no free pins to expose".to_owned()); + } + let definition = ModuleDefinition { name }; + + self.db.module_definitions.insert(definition); + + Ok(()) + } +} diff --git a/src/simulator.rs b/src/simulator.rs index f9d30a6..2f6d88b 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -3,14 +3,14 @@ use std::collections::{HashMap, HashSet}; use log; use crate::{ - app::{DB, GateKind, InstanceId, InstanceKind, Pin}, assets::PinKind, + db::{DB, GateKind, InstanceId, InstanceKind, Pin}, }; const MAX_ITERATIONS: usize = 1000; const STABILIZATION_THRESHOLD: usize = 3; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum Value { Zero, One, @@ -169,7 +169,7 @@ impl Simulator { InstanceKind::Lamp => { self.evaluate_lamp(db, id); } - InstanceKind::Power | InstanceKind::CustomCircuit(_) => {} + InstanceKind::Power | InstanceKind::Module(_) => {} InstanceKind::Clock => { if self.clocks_on { self.current.insert(db.clock_output(id), Value::One); @@ -192,7 +192,7 @@ impl Simulator { let start = db.wire_start(id); let end = db.wire_end(id); - if db.pin_info(start).kind == PinKind::Input { + if start.kind == PinKind::Input { start } else { end @@ -248,7 +248,7 @@ impl Simulator { let mut result = Value::Zero; for other in connected { - if db.pin_info(other).kind != PinKind::Output { + if other.kind != PinKind::Output { continue; } @@ -264,8 +264,8 @@ impl Simulator { #[cfg(test)] mod tests { use super::*; - use crate::app::{Gate, GateKind, Lamp, Power}; use crate::connection_manager::Connection; + use crate::db::{Clock, Gate, GateKind, Lamp, Power}; use egui::{Pos2, pos2}; fn create_test_db() -> DB { @@ -299,7 +299,7 @@ mod tests { #[expect(dead_code)] fn new_clock(db: &mut DB) -> InstanceId { - db.new_clock(crate::app::Clock { + db.new_clock(Clock { pos: Pos2::ZERO, period: 1, }) diff --git a/todo.md b/todo.md index 19585d6..e937a30 100644 --- a/todo.md +++ b/todo.md @@ -1 +1 @@ -- [ ] Test for +- Test for