From c9415e5cae3688f1f6fe81bd887f11790b8d434a Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Sun, 19 Oct 2025 20:29:12 -0400 Subject: [PATCH 01/15] make gates addable to frontend canvas --- src/frontend/canvas/components/mod.rs | 104 +++++++++++++++++++++++++ src/frontend/canvas/input.rs | 52 +++++++++++++ src/frontend/canvas/mod.rs | 2 + src/frontend/canvas/wiring/wire.rs | 1 + src/frontend/gui/component_selector.rs | 8 +- src/frontend/gui/component_utils.rs | 46 +++++------ src/frontend/gui/mod.rs | 42 ++++++---- src/frontend/gui/toolbar.rs | 26 +++---- src/frontend/main.rs | 19 ++++- 9 files changed, 242 insertions(+), 58 deletions(-) create mode 100644 src/frontend/canvas/components/mod.rs create mode 100644 src/frontend/canvas/input.rs diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs new file mode 100644 index 0000000..8a0a3b2 --- /dev/null +++ b/src/frontend/canvas/components/mod.rs @@ -0,0 +1,104 @@ +use std::collections::HashMap; +use egui_macroquad::macroquad::prelude::*; + +use crate::{canvas::camera::GridCamera, gui::component_utils::GateType}; + +pub enum ComponentData { + Gate { + gate_type: GateType, + bitsize: u8, + }, + Mux { + bitsize: u8, + }, +} + +pub struct Component { + /// Component-specific data + data: ComponentData, + /// Size of the component (width, height) + size: (u32, u32), + /// Offsets of input pins from top left + input_offsets: Vec<(u32, u32)>, +} + +#[derive(Default)] +pub struct ComponentSystem { + components: HashMap<(i32, i32), Component>, +} + +impl ComponentSystem { + pub fn new() -> Self { + let mut a = Self::default(); + a.components.insert((0, 0), Component { + data: ComponentData::Gate { + gate_type: GateType::And, + bitsize: 32, + }, + size: (3, 3), + input_offsets: vec![ + (0, 0), + (0, 2), + ], + }); + a + } + + pub fn handle_input(&mut self, camera: &GridCamera, selected_component: GateType) { + let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); + let mouse_world = camera.screen_to_world(mouse_screen); + let end_pos = (mouse_world.x.floor() as i32, mouse_world.y.floor() as i32); + + if is_mouse_button_pressed(MouseButton::Left) { + self.components.insert( + end_pos, + Component { + data: ComponentData::Gate { + gate_type: selected_component, + bitsize: 1, + }, + size: (3, 3), + input_offsets: vec![(0, 0), (0, 2)] + } + ); + } + } + + pub fn draw_components(&self, camera: &GridCamera) { + // TODO: cull components outside camera boundaries + // let (view_min, view_max) = camera.get_view_bounds(); + for ((x, y), component) in &self.components { + draw_rectangle( + *x as f32 + 0.5, + *y as f32, + component.size.0 as f32, + component.size.1 as f32, + WHITE + ); + draw_rectangle_lines( + *x as f32 + 0.5, + *y as f32, + component.size.0 as f32, + component.size.1 as f32, + 0.1, + BLACK + ); + const PORT_SIZE: f32 = 0.2; + for (dx, dy) in &component.input_offsets { + draw_circle( + (*x + *dx as i32) as f32 + 0.5, + (*y + *dy as i32) as f32 + 0.5, + PORT_SIZE, + BLUE + ); + // draw_rectangle( + // (*x + *dx as i32) as f32 + 0.5 - PORT_SIZE / 2., + // (*y + *dy as i32) as f32 + 0.5 - PORT_SIZE / 2., + // PORT_SIZE, + // PORT_SIZE, + // BLUE + // ); + } + } + } +} diff --git a/src/frontend/canvas/input.rs b/src/frontend/canvas/input.rs new file mode 100644 index 0000000..353aec7 --- /dev/null +++ b/src/frontend/canvas/input.rs @@ -0,0 +1,52 @@ +use egui_macroquad::macroquad::prelude::*; +use crate::canvas::components::ComponentData; + +use super::camera::GridCamera; + +#[derive(PartialEq, Eq, Debug)] +pub enum CanvasInputState { + Wire, + Component, + Select, + Idle, +} + +impl CanvasInputState { + pub fn new() -> Self { + Self::Idle + } + + pub fn handle_input(&mut self) { + if is_key_down(KeyCode::Escape) { + *self = Self::Idle; + } + if is_key_down(KeyCode::Q) { + *self = Self::Wire; + } + } +} + +// impl CanvasInputState { +// pub fn handle_input(mut self, camera: &GridCamera) { +// // Get mouse position and snap to grid +// let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); +// let mouse_world = camera.screen_to_world(mouse_screen); +// let end_pos = Vec2::new(mouse_world.x.floor(), mouse_world.y.floor()); +// +// if is_mouse_button_pressed(MouseButton::Left) { +// if is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift) { +// if let WireDrawState::StartSelected(start_pos) = self.draw_state { +// self.draw_wire_path(start_pos, end_pos); +// } +// } +// self.draw_state = WireDrawState::StartSelected(end_pos); +// } +// if is_mouse_button_pressed(MouseButton::Right) { +// self.draw_vertical_first = !self.draw_vertical_first; +// } +// +// if is_key_pressed(KeyCode::Escape) { +// self = CanvasInputState::Idle; +// } +// } +// } diff --git a/src/frontend/canvas/mod.rs b/src/frontend/canvas/mod.rs index e2811a7..5e09d5d 100644 --- a/src/frontend/canvas/mod.rs +++ b/src/frontend/canvas/mod.rs @@ -5,4 +5,6 @@ pub mod camera; pub mod grid; +pub mod input; pub mod wiring; +pub mod components; diff --git a/src/frontend/canvas/wiring/wire.rs b/src/frontend/canvas/wiring/wire.rs index d82731a..2794ed1 100644 --- a/src/frontend/canvas/wiring/wire.rs +++ b/src/frontend/canvas/wiring/wire.rs @@ -121,6 +121,7 @@ impl fmt::Display for WireVariant { } pub struct Wire { + // TODO: is it necessary to store position in the wire itself? pub position: Vec2, pub variant: WireVariant, } diff --git a/src/frontend/gui/component_selector.rs b/src/frontend/gui/component_selector.rs index 0af24c2..82bb9f6 100644 --- a/src/frontend/gui/component_selector.rs +++ b/src/frontend/gui/component_selector.rs @@ -1,6 +1,7 @@ +use crate::canvas::input::CanvasInputState; use crate::App; use crate::gui::component_utils::{ - CircuitComponentType, DrawInstruction, pos2_with_rect, + GateType, DrawInstruction, pos2_with_rect, }; use egui_macroquad::egui::{Color32, Response, Sense, Stroke, Ui}; use epaint::{CubicBezierShape, Pos2}; @@ -10,7 +11,8 @@ impl App { &mut self, ui: &mut Ui, size: egui::Vec2, - component_type: CircuitComponentType, + component_type: GateType, + input_state: &mut CanvasInputState ) -> Response { let (rect, response) = ui.allocate_exact_size(size, Sense::all()); @@ -54,7 +56,7 @@ impl App { ); if response.clicked() { - self.set_selected_component(Some(component_type)); + self.set_selected_component(Some(component_type), input_state); } if response.drag_stopped() { self.dragged_component = Some(component_type); diff --git a/src/frontend/gui/component_utils.rs b/src/frontend/gui/component_utils.rs index ccba9c8..d2d9208 100644 --- a/src/frontend/gui/component_utils.rs +++ b/src/frontend/gui/component_utils.rs @@ -13,26 +13,26 @@ pub fn pos2_with_rect(pos: &Pos2, rect: egui::Rect) -> Pos2 { } #[derive(PartialEq, Eq, Copy, Clone)] -pub enum CircuitComponentType { - AndGate, - OrGate, - NandGate, - NorGate, - XorGate, - XnorGate, - NotGate, +pub enum GateType { + And, + Or, + Nand, + Nor, + Xor, + Xnor, + Not, } -impl CircuitComponentType { +impl GateType { pub fn get_label(&self) -> &'static str { match self { - Self::AndGate => "AND Gate", - Self::OrGate => "OR Gate", - Self::NandGate => "NAND Gate", - Self::NorGate => "NOR Gate", - Self::XorGate => "XOR Gate", - Self::XnorGate => "XNOR Gate", - Self::NotGate => "NOT Gate", + Self::And => "AND Gate", + Self::Or => "OR Gate", + Self::Nand => "NAND Gate", + Self::Nor => "NOR Gate", + Self::Xor => "XOR Gate", + Self::Xnor => "XNOR Gate", + Self::Not => "NOT Gate", } } @@ -83,13 +83,13 @@ impl CircuitComponentType { pub fn get_draw_instructions(&self) -> &'static [DrawInstruction] { match self { - Self::AndGate => &Self::AND_GATE_DRAW_INSTRUCTIONS, - Self::OrGate => &Self::OR_GATE_DRAW_INSTRUCTIONS, - Self::NandGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::NorGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::XorGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::XnorGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::NotGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::And => &Self::AND_GATE_DRAW_INSTRUCTIONS, + Self::Or => &Self::OR_GATE_DRAW_INSTRUCTIONS, + Self::Nand => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::Nor => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::Xor => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::Xnor => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::Not => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, } } } diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index d155b3a..5cc6154 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -1,17 +1,19 @@ -use component_utils::CircuitComponentType; +use component_utils::GateType; use egui_macroquad::egui; use egui_macroquad::macroquad::prelude::*; +use crate::canvas::input::CanvasInputState; + mod component_selector; -mod component_utils; +pub mod component_utils; mod toolbar; pub struct App { expanded: bool, - selected_component: Option, - hotbar_selections: [Option; Self::NUM_HOTBAR_BUTTONS], + selected_component: Option, + hotbar_selections: [Option; Self::NUM_HOTBAR_BUTTONS], hovered_hotbar_button: Option, - dragged_component: Option, + dragged_component: Option, } impl App { @@ -28,15 +30,20 @@ impl App { } } - pub fn get_selected_component(&mut self) -> Option { + pub fn get_selected_component(&mut self) -> Option { self.selected_component } - pub fn set_selected_component(&mut self, component: Option) { + pub fn set_selected_component(&mut self, component: Option, input_state: &mut CanvasInputState) { self.selected_component = component; + if component.is_some() { + *input_state = CanvasInputState::Component; + } else { + *input_state = CanvasInputState::Idle; + } } - pub fn update(&mut self, ctx: &egui::Context) { + pub fn update(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState) { self.hovered_hotbar_button = None; self.dragged_component = None; use egui::*; @@ -59,9 +66,11 @@ impl App { "Native" }; let version = env!("CARGO_PKG_VERSION"); + let mode = format!("{:?}", input_state); ui.horizontal(|ui| { ui.label(format!("Build: {}", build)); ui.label(format!("Version: {}", version)); + ui.label(format!("Mode: {}", mode)); }); }); @@ -73,19 +82,20 @@ impl App { ui.label("Components"); CollapsingHeader::new("Gates").show(ui, |ui| { let gates = [ - CircuitComponentType::AndGate, - CircuitComponentType::OrGate, - CircuitComponentType::NandGate, - CircuitComponentType::NorGate, - CircuitComponentType::XorGate, - CircuitComponentType::XnorGate, - CircuitComponentType::NotGate, + GateType::And, + GateType::Or, + GateType::Nand, + GateType::Nor, + GateType::Xor, + GateType::Xnor, + GateType::Not, ]; for gate in gates { self.circuit_component_button( ui, egui::Vec2::new(ui.available_size().x, 60.0), gate, + input_state ); } }); @@ -120,7 +130,7 @@ impl App { ui.label("test1"); }); - self.render_toolbar(ctx); + self.render_toolbar(ctx, input_state); // Ensure that each component can only be in one hotbar slot at a time if let Some(hovered_index) = self.hovered_hotbar_button diff --git a/src/frontend/gui/toolbar.rs b/src/frontend/gui/toolbar.rs index 6b61153..744f831 100644 --- a/src/frontend/gui/toolbar.rs +++ b/src/frontend/gui/toolbar.rs @@ -1,3 +1,4 @@ +use crate::canvas::input::CanvasInputState; use crate::App; use crate::gui::component_utils::{DrawInstruction, pos2_with_rect}; use egui_macroquad::egui::{ @@ -6,27 +7,27 @@ use egui_macroquad::egui::{ use epaint::CubicBezierShape; impl App { - fn handle_hotbar_keys(&mut self, ctx: &egui::Context) { + fn handle_hotbar_keys(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState) { ctx.input(|i| { if i.key_down(Key::Num1) { - self.selected_component = self.hotbar_selections[0]; + self.set_selected_component(self.hotbar_selections[0], input_state); } if i.key_down(Key::Num2) { - self.selected_component = self.hotbar_selections[1]; + self.set_selected_component(self.hotbar_selections[1], input_state); } if i.key_down(Key::Num3) { - self.selected_component = self.hotbar_selections[2]; + self.set_selected_component(self.hotbar_selections[2], input_state); } if i.key_down(Key::Num4) { - self.selected_component = self.hotbar_selections[3]; + self.set_selected_component(self.hotbar_selections[3], input_state); } if i.key_down(Key::Num5) { - self.selected_component = self.hotbar_selections[4]; + self.set_selected_component(self.hotbar_selections[4], input_state); } }); } - fn hotbar_button(&mut self, size: egui::Vec2, ui: &mut Ui, index: usize) -> Response { + fn hotbar_button(&mut self, size: egui::Vec2, ui: &mut Ui, index: usize, input_state: &mut CanvasInputState) -> Response { let (rect, response) = ui.allocate_exact_size(size, Sense::all()); if ui.is_rect_visible(rect) { @@ -64,10 +65,9 @@ impl App { } if response.clicked() { - self.set_selected_component(button_contents); + self.set_selected_component(button_contents, input_state); } - if let Some(component) = self.get_selected_component() - && self.get_selected_component() == button_contents + if self.get_selected_component().is_some() && self.get_selected_component() == button_contents { painter.rect_filled(padded_rect, 4.0, Color32::from_white_alpha(20)); } else if response.contains_pointer() { @@ -99,14 +99,14 @@ impl App { response } - pub fn render_toolbar(&mut self, ctx: &egui::Context) { + pub fn render_toolbar(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState) { TopBottomPanel::top("toolbar").show(ctx, |ui| { menu::bar(ui, |ui| { for i in 0..Self::NUM_HOTBAR_BUTTONS { - self.hotbar_button(Vec2::splat(40.0), ui, i); + self.hotbar_button(Vec2::splat(40.0), ui, i, input_state); } }); }); - self.handle_hotbar_keys(ctx); + self.handle_hotbar_keys(ctx, input_state); } } diff --git a/src/frontend/main.rs b/src/frontend/main.rs index ba33f74..18fdf62 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -1,7 +1,9 @@ use egui_macroquad::macroquad::prelude::*; use crate::canvas::camera::GridCamera; +use crate::canvas::components::ComponentSystem; use crate::canvas::grid::GridDrawer; +use crate::canvas::input::CanvasInputState; use crate::canvas::wiring::WireSystem; use crate::gui::App; @@ -26,7 +28,9 @@ async fn main() { let mut camera = GridCamera::new(); let gd = GridDrawer::new(crate::canvas::grid::GridDrawOptions::Instanced, vec4(0.3, 0.3, 0.3, 0.3)); let mut gui = App::new(); + let mut input_state = CanvasInputState::new(); let mut ws = WireSystem::new(); + let mut cs = ComponentSystem::new(); request_new_screen_size(1280.0, 720.0); next_frame().await; @@ -39,10 +43,15 @@ async fn main() { profile_scope!("logic"); camera.handle_input(dt); camera.update(dt); - ws.handle_input(&camera); + input_state.handle_input(); + if input_state == CanvasInputState::Component { + cs.handle_input(&camera, gui.get_selected_component().unwrap()); + } else if input_state == CanvasInputState::Wire { + ws.handle_input(&camera); + } egui_macroquad::ui(|ctx| { - gui.update(ctx); + gui.update(ctx, &mut input_state); if enable_camera_debug { camera.draw_egui_ui(ctx); } @@ -58,8 +67,12 @@ async fn main() { clear_background(Color::new(0.1, 0.1, 0.1, 1.0)); set_camera(&camera); gd.draw_grid(&camera); - ws.draw_preview(&camera); + cs.draw_components(&camera); ws.draw_wires(&camera); + if input_state == CanvasInputState::Wire { + ws.draw_preview(&camera); + } else { + } gl_use_default_material(); set_default_camera(); From 3e11100ba4fe2544af377a1e2d0f002a087567d5 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Sun, 19 Oct 2025 21:59:43 -0400 Subject: [PATCH 02/15] add component preview --- src/frontend/canvas/components/mod.rs | 73 +++++++++++++++------------ src/frontend/canvas/input.rs | 28 ---------- src/frontend/main.rs | 19 +++++-- 3 files changed, 57 insertions(+), 63 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 8a0a3b2..563fbe6 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -13,57 +13,66 @@ pub enum ComponentData { }, } -pub struct Component { - /// Component-specific data - data: ComponentData, - /// Size of the component (width, height) - size: (u32, u32), - /// Offsets of input pins from top left - input_offsets: Vec<(u32, u32)>, +impl ComponentData { + pub fn get_size(&self) -> (u32, u32) { + match self { + Self::Gate { .. } => (3, 3), + Self::Mux { .. } => todo!(), + } + } + + pub fn get_input_offsets(&self) -> Vec<(u32, u32)> { + match self { + Self::Gate { .. } => vec![(0, 0), (0, 2)], + Self::Mux { .. } => todo!(), + } + } } #[derive(Default)] pub struct ComponentSystem { - components: HashMap<(i32, i32), Component>, + components: HashMap<(i32, i32), ComponentData>, } impl ComponentSystem { pub fn new() -> Self { let mut a = Self::default(); - a.components.insert((0, 0), Component { - data: ComponentData::Gate { - gate_type: GateType::And, - bitsize: 32, - }, - size: (3, 3), - input_offsets: vec![ - (0, 0), - (0, 2), - ], + a.components.insert((0, 0), ComponentData::Gate { + gate_type: GateType::And, + bitsize: 32, }); a } - pub fn handle_input(&mut self, camera: &GridCamera, selected_component: GateType) { + pub fn handle_input(&mut self, camera: &GridCamera, selected_component: ComponentData) { let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); let mouse_world = camera.screen_to_world(mouse_screen); let end_pos = (mouse_world.x.floor() as i32, mouse_world.y.floor() as i32); + let end_pos = (end_pos.0 - selected_component.get_size().0 as i32 / 2, end_pos.1 - selected_component.get_size().1 as i32 / 2); if is_mouse_button_pressed(MouseButton::Left) { self.components.insert( end_pos, - Component { - data: ComponentData::Gate { - gate_type: selected_component, - bitsize: 1, - }, - size: (3, 3), - input_offsets: vec![(0, 0), (0, 2)] - } + selected_component, ); } } + pub fn draw_preview(&self, camera: &GridCamera, selected_component: ComponentData) { + let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); + let mouse_world = camera.screen_to_world(mouse_screen); + let (x, y) = (mouse_world.x.floor() as i32, mouse_world.y.floor() as i32); + let (x, y) = (x - selected_component.get_size().0 as i32 / 2, y - selected_component.get_size().1 as i32 / 2); + draw_rectangle_lines( + x as f32 + 0.5, + y as f32, + selected_component.get_size().0 as f32, + selected_component.get_size().1 as f32, + 0.1, + GREEN, + ); + } + pub fn draw_components(&self, camera: &GridCamera) { // TODO: cull components outside camera boundaries // let (view_min, view_max) = camera.get_view_bounds(); @@ -71,20 +80,20 @@ impl ComponentSystem { draw_rectangle( *x as f32 + 0.5, *y as f32, - component.size.0 as f32, - component.size.1 as f32, + component.get_size().0 as f32, + component.get_size().1 as f32, WHITE ); draw_rectangle_lines( *x as f32 + 0.5, *y as f32, - component.size.0 as f32, - component.size.1 as f32, + component.get_size().0 as f32, + component.get_size().1 as f32, 0.1, BLACK ); const PORT_SIZE: f32 = 0.2; - for (dx, dy) in &component.input_offsets { + for (dx, dy) in &component.get_input_offsets() { draw_circle( (*x + *dx as i32) as f32 + 0.5, (*y + *dy as i32) as f32 + 0.5, diff --git a/src/frontend/canvas/input.rs b/src/frontend/canvas/input.rs index 353aec7..a0c4ab2 100644 --- a/src/frontend/canvas/input.rs +++ b/src/frontend/canvas/input.rs @@ -1,7 +1,4 @@ use egui_macroquad::macroquad::prelude::*; -use crate::canvas::components::ComponentData; - -use super::camera::GridCamera; #[derive(PartialEq, Eq, Debug)] pub enum CanvasInputState { @@ -25,28 +22,3 @@ impl CanvasInputState { } } } - -// impl CanvasInputState { -// pub fn handle_input(mut self, camera: &GridCamera) { -// // Get mouse position and snap to grid -// let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); -// let mouse_world = camera.screen_to_world(mouse_screen); -// let end_pos = Vec2::new(mouse_world.x.floor(), mouse_world.y.floor()); -// -// if is_mouse_button_pressed(MouseButton::Left) { -// if is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift) { -// if let WireDrawState::StartSelected(start_pos) = self.draw_state { -// self.draw_wire_path(start_pos, end_pos); -// } -// } -// self.draw_state = WireDrawState::StartSelected(end_pos); -// } -// if is_mouse_button_pressed(MouseButton::Right) { -// self.draw_vertical_first = !self.draw_vertical_first; -// } -// -// if is_key_pressed(KeyCode::Escape) { -// self = CanvasInputState::Idle; -// } -// } -// } diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 18fdf62..30b5de6 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -1,7 +1,7 @@ use egui_macroquad::macroquad::prelude::*; use crate::canvas::camera::GridCamera; -use crate::canvas::components::ComponentSystem; +use crate::canvas::components::{ComponentData, ComponentSystem}; use crate::canvas::grid::GridDrawer; use crate::canvas::input::CanvasInputState; use crate::canvas::wiring::WireSystem; @@ -45,7 +45,13 @@ async fn main() { camera.update(dt); input_state.handle_input(); if input_state == CanvasInputState::Component { - cs.handle_input(&camera, gui.get_selected_component().unwrap()); + cs.handle_input( + &camera, + ComponentData::Gate { + gate_type: gui.get_selected_component().unwrap(), + bitsize: 1, + } + ); } else if input_state == CanvasInputState::Wire { ws.handle_input(&camera); } @@ -71,7 +77,14 @@ async fn main() { ws.draw_wires(&camera); if input_state == CanvasInputState::Wire { ws.draw_preview(&camera); - } else { + } else if input_state == CanvasInputState::Component { + cs.draw_preview( + &camera, + ComponentData::Gate { + gate_type: gui.get_selected_component().unwrap(), + bitsize: 1, + } + ); } gl_use_default_material(); From 2e11776c8c9d999ed21bbb44ee494457b491d6ec Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Sun, 19 Oct 2025 23:04:22 -0400 Subject: [PATCH 03/15] refactor gui component selection code --- src/frontend/canvas/components/mod.rs | 13 +++- src/frontend/gui/component_selector.rs | 4 +- src/frontend/gui/component_utils.rs | 83 +++++++++++++++++++------- src/frontend/gui/mod.rs | 26 ++++---- src/frontend/main.rs | 10 +--- 5 files changed, 89 insertions(+), 47 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 563fbe6..1f6386a 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -1,7 +1,18 @@ use std::collections::HashMap; use egui_macroquad::macroquad::prelude::*; -use crate::{canvas::camera::GridCamera, gui::component_utils::GateType}; +use crate::canvas::camera::GridCamera; + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum GateType { + And, + Or, + Nand, + Nor, + Xor, + Xnor, + Not, +} pub enum ComponentData { Gate { diff --git a/src/frontend/gui/component_selector.rs b/src/frontend/gui/component_selector.rs index 82bb9f6..3e38775 100644 --- a/src/frontend/gui/component_selector.rs +++ b/src/frontend/gui/component_selector.rs @@ -1,7 +1,7 @@ use crate::canvas::input::CanvasInputState; use crate::App; use crate::gui::component_utils::{ - GateType, DrawInstruction, pos2_with_rect, + pos2_with_rect, DrawInstruction, GuiComponentType }; use egui_macroquad::egui::{Color32, Response, Sense, Stroke, Ui}; use epaint::{CubicBezierShape, Pos2}; @@ -11,7 +11,7 @@ impl App { &mut self, ui: &mut Ui, size: egui::Vec2, - component_type: GateType, + component_type: GuiComponentType, input_state: &mut CanvasInputState ) -> Response { let (rect, response) = ui.allocate_exact_size(size, Sense::all()); diff --git a/src/frontend/gui/component_utils.rs b/src/frontend/gui/component_utils.rs index d2d9208..9da8485 100644 --- a/src/frontend/gui/component_utils.rs +++ b/src/frontend/gui/component_utils.rs @@ -1,5 +1,7 @@ use epaint::Pos2; +use crate::canvas::components::{ComponentData, GateType}; + pub enum DrawInstruction { Line([Pos2; 2]), CubicBezierCurve([Pos2; 4]), @@ -13,26 +15,59 @@ pub fn pos2_with_rect(pos: &Pos2, rect: egui::Rect) -> Pos2 { } #[derive(PartialEq, Eq, Copy, Clone)] -pub enum GateType { - And, - Or, - Nand, - Nor, - Xor, - Xnor, - Not, +pub enum GuiComponentType { + AndGate, + OrGate, + NandGate, + NorGate, + XorGate, + XnorGate, + NotGate, } -impl GateType { +impl GuiComponentType { + pub fn to_component_data(&self) -> ComponentData { + match self { + Self::AndGate => ComponentData::Gate { + gate_type: GateType::And, + bitsize: 1, + }, + Self::OrGate => ComponentData::Gate { + gate_type: GateType::Or, + bitsize: 1, + }, + Self::NandGate => ComponentData::Gate { + gate_type: GateType::Nand, + bitsize: 1, + }, + Self::NorGate => ComponentData::Gate { + gate_type: GateType::Nor, + bitsize: 1, + }, + Self::XorGate => ComponentData::Gate { + gate_type: GateType::Xor, + bitsize: 1, + }, + Self::XnorGate => ComponentData::Gate { + gate_type: GateType::Xnor, + bitsize: 1, + }, + Self::NotGate => ComponentData::Gate { + gate_type: GateType::Not, + bitsize: 1, + }, + } + } + pub fn get_label(&self) -> &'static str { match self { - Self::And => "AND Gate", - Self::Or => "OR Gate", - Self::Nand => "NAND Gate", - Self::Nor => "NOR Gate", - Self::Xor => "XOR Gate", - Self::Xnor => "XNOR Gate", - Self::Not => "NOT Gate", + Self::AndGate => "AND Gate", + Self::OrGate => "OR Gate", + Self::NandGate => "NAND Gate", + Self::NorGate => "NOR Gate", + Self::XorGate => "XOR Gate", + Self::XnorGate => "XNOR Gate", + Self::NotGate => "NOT Gate", } } @@ -83,13 +118,15 @@ impl GateType { pub fn get_draw_instructions(&self) -> &'static [DrawInstruction] { match self { - Self::And => &Self::AND_GATE_DRAW_INSTRUCTIONS, - Self::Or => &Self::OR_GATE_DRAW_INSTRUCTIONS, - Self::Nand => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::Nor => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::Xor => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::Xnor => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, - Self::Not => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::AndGate => &Self::AND_GATE_DRAW_INSTRUCTIONS, + Self::OrGate => &Self::OR_GATE_DRAW_INSTRUCTIONS, + Self::NandGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::NorGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::XorGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::XnorGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + Self::NotGate => &Self::UNIMPLEMENTED_DRAW_INSTRUCTIONS, } } } + + diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index 5cc6154..a46f4ee 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -1,8 +1,8 @@ -use component_utils::GateType; use egui_macroquad::egui; use egui_macroquad::macroquad::prelude::*; use crate::canvas::input::CanvasInputState; +use crate::gui::component_utils::GuiComponentType; mod component_selector; pub mod component_utils; @@ -10,10 +10,10 @@ mod toolbar; pub struct App { expanded: bool, - selected_component: Option, - hotbar_selections: [Option; Self::NUM_HOTBAR_BUTTONS], + selected_component: Option, + hotbar_selections: [Option; Self::NUM_HOTBAR_BUTTONS], hovered_hotbar_button: Option, - dragged_component: Option, + dragged_component: Option, } impl App { @@ -30,11 +30,11 @@ impl App { } } - pub fn get_selected_component(&mut self) -> Option { + pub fn get_selected_component(&mut self) -> Option { self.selected_component } - pub fn set_selected_component(&mut self, component: Option, input_state: &mut CanvasInputState) { + pub fn set_selected_component(&mut self, component: Option, input_state: &mut CanvasInputState) { self.selected_component = component; if component.is_some() { *input_state = CanvasInputState::Component; @@ -82,13 +82,13 @@ impl App { ui.label("Components"); CollapsingHeader::new("Gates").show(ui, |ui| { let gates = [ - GateType::And, - GateType::Or, - GateType::Nand, - GateType::Nor, - GateType::Xor, - GateType::Xnor, - GateType::Not, + GuiComponentType::AndGate, + GuiComponentType::OrGate, + GuiComponentType::NandGate, + GuiComponentType::NorGate, + GuiComponentType::XorGate, + GuiComponentType::XnorGate, + GuiComponentType::NotGate, ]; for gate in gates { self.circuit_component_button( diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 30b5de6..85ac494 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -47,10 +47,7 @@ async fn main() { if input_state == CanvasInputState::Component { cs.handle_input( &camera, - ComponentData::Gate { - gate_type: gui.get_selected_component().unwrap(), - bitsize: 1, - } + gui.get_selected_component().unwrap().to_component_data() ); } else if input_state == CanvasInputState::Wire { ws.handle_input(&camera); @@ -80,10 +77,7 @@ async fn main() { } else if input_state == CanvasInputState::Component { cs.draw_preview( &camera, - ComponentData::Gate { - gate_type: gui.get_selected_component().unwrap(), - bitsize: 1, - } + gui.get_selected_component().unwrap().to_component_data() ); } From a0d0c3fb8fee7250ea3d6df1ca59d2531461c153 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 03:25:14 -0400 Subject: [PATCH 04/15] add basic selection logic --- src/frontend/canvas/components/mod.rs | 79 +++++++++++++++++---- src/frontend/canvas/input.rs | 98 +++++++++++++++++++++++++-- src/frontend/canvas/wiring/mod.rs | 2 +- src/frontend/gui/mod.rs | 71 ++++++++++++------- src/frontend/main.rs | 25 ++++--- 5 files changed, 221 insertions(+), 54 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 1f6386a..b54f435 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -38,6 +38,28 @@ impl ComponentData { Self::Mux { .. } => todo!(), } } + + pub fn intersects_selection(&self, comp_x: i32, comp_y: i32, c: ((i32, i32), (i32, i32))) -> bool { + let ((x1, y1), (x2, y2)) = c; + let sel_min_x = x1.min(x2); + let sel_max_x = x1.max(x2); + let sel_min_y = y1.min(y2); + let sel_max_y = y1.max(y2); + + let (w, h) = self.get_size(); + let comp_min_x = comp_x; + let comp_max_x = comp_x + w as i32 - 1; + let comp_min_y = comp_y; + let comp_max_y = comp_y + h as i32 - 1; + + // Check for intersection + let intersects = sel_min_x <= comp_max_x + && sel_max_x >= comp_min_x + && sel_min_y <= comp_max_y + && sel_max_y >= comp_min_y; + + intersects + } } #[derive(Default)] @@ -74,34 +96,46 @@ impl ComponentSystem { let mouse_world = camera.screen_to_world(mouse_screen); let (x, y) = (mouse_world.x.floor() as i32, mouse_world.y.floor() as i32); let (x, y) = (x - selected_component.get_size().0 as i32 / 2, y - selected_component.get_size().1 as i32 / 2); - draw_rectangle_lines( + draw_rectangle( x as f32 + 0.5, y as f32, selected_component.get_size().0 as f32, selected_component.get_size().1 as f32, - 0.1, - GREEN, + PURPLE.with_alpha(0.2), ); } - pub fn draw_components(&self, camera: &GridCamera) { + pub fn draw_components(&self, camera: &GridCamera, selection: Option<((i32, i32), (i32, i32))>) { // TODO: cull components outside camera boundaries - // let (view_min, view_max) = camera.get_view_bounds(); + let (view_min, view_max) = camera.get_view_bounds(); for ((x, y), component) in &self.components { + let (w, h) = component.get_size(); + if ((x + w as i32) as f32) < view_min.x + || (*x as f32) > view_max.x + || ((y + h as i32) as f32) < view_min.y + || (*y as f32) > view_max.y + + { + } draw_rectangle( *x as f32 + 0.5, *y as f32, - component.get_size().0 as f32, - component.get_size().1 as f32, + w as f32, + h as f32, WHITE ); + let border_color = if let Some(c) = selection && component.intersects_selection(*x, *y, c) { + ORANGE + } else { + BLACK + }; draw_rectangle_lines( *x as f32 + 0.5, *y as f32, component.get_size().0 as f32, component.get_size().1 as f32, - 0.1, - BLACK + camera.get_pixel_thickness() * 4.0, + border_color ); const PORT_SIZE: f32 = 0.2; for (dx, dy) in &component.get_input_offsets() { @@ -111,14 +145,31 @@ impl ComponentSystem { PORT_SIZE, BLUE ); - // draw_rectangle( - // (*x + *dx as i32) as f32 + 0.5 - PORT_SIZE / 2., - // (*y + *dy as i32) as f32 + 0.5 - PORT_SIZE / 2., - // PORT_SIZE, + // draw_circle_lines( + // (*x + *dx as i32) as f32 + 0.5, + // (*y + *dy as i32) as f32 + 0.5, // PORT_SIZE, - // BLUE + // camera.get_pixel_thickness() * 2.0, + // BLACK, // ); } } } + + pub fn get_selection_mut( + &mut self, + selection: Option<((i32, i32), (i32, i32))>, + ) -> Vec<&mut ComponentData> { + let mut selected = Vec::new(); + + if let Some(c) = selection { + for (&(comp_x, comp_y), component) in self.components.iter_mut() { + if component.intersects_selection(comp_x, comp_y, c) { + selected.push(component); + } + } + } + + selected + } } diff --git a/src/frontend/canvas/input.rs b/src/frontend/canvas/input.rs index a0c4ab2..3229c7e 100644 --- a/src/frontend/canvas/input.rs +++ b/src/frontend/canvas/input.rs @@ -1,24 +1,110 @@ use egui_macroquad::macroquad::prelude::*; +use crate::canvas::camera::GridCamera; + +// TODO: move canvas selection and selected component into this enum #[derive(PartialEq, Eq, Debug)] pub enum CanvasInputState { Wire, Component, - Select, Idle, } -impl CanvasInputState { +pub struct CanvasInput { + pub state: CanvasInputState, + pub selection: Option<((i32, i32), (i32, i32))>, + pub in_progress_selection: Option<((f32, f32), (f32, f32))>, +} + +impl CanvasInput { pub fn new() -> Self { - Self::Idle + Self { + state: CanvasInputState::Idle, + selection: None, + in_progress_selection: None, + } } - pub fn handle_input(&mut self) { + pub fn handle_input(&mut self, camera: &GridCamera, egui_wants_ptr: bool) { if is_key_down(KeyCode::Escape) { - *self = Self::Idle; + self.state = CanvasInputState::Idle; + self.in_progress_selection = None; + self.selection = None; + } + if self.state != CanvasInputState::Idle { + self.in_progress_selection = None; + self.selection = None; } if is_key_down(KeyCode::Q) { - *self = Self::Wire; + self.state = CanvasInputState::Wire; + } + + let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); + let mouse_world = camera.screen_to_world(mouse_screen); + let end_pos = (mouse_world.x, mouse_world.y); + + // Mouse release events should be processed even if the pointer is over an egui frame + if is_mouse_button_released(MouseButton::Left) && let Some(((x1, y1), (x2, y2))) = self.in_progress_selection { + let min_x = x1.min(x2).floor() as i32; + let min_y = y1.min(y2).floor() as i32; + let max_x = x1.max(x2).ceil() as i32; + let max_y = y1.max(y2).ceil() as i32; + + self.selection = Some( ((min_x, min_y), (max_x, max_y)) + ); + self.in_progress_selection = None; } + // So should mouse drag events + if is_mouse_button_down(MouseButton::Left) && self.in_progress_selection.is_some() { + let Some((c1, _)) = self.in_progress_selection else { + return; + }; + self.in_progress_selection = Some((c1, end_pos)); + } + if egui_wants_ptr { + return; + } + if is_mouse_button_pressed(MouseButton::Left) { + self.selection = None; + self.in_progress_selection = Some((end_pos, end_pos)) + } + } + + pub fn draw_selection(&self, camera: &GridCamera) { + if let Some((c1, c2)) = self.in_progress_selection { + draw_rectangle( + c1.0 as f32, + c1.1 as f32, + c2.0 - c1.0, + c2.1 - c1.1, + BLUE.with_alpha(0.2), + ); + } else if let Some((c1, c2)) = self.selection { + draw_rectangle( + c1.0 as f32, + c1.1 as f32, + c2.0 as f32 - c1.0 as f32, + c2.1 as f32 - c1.1 as f32, + BLUE.with_alpha(0.05), + ); + draw_rectangle_lines( + c1.0 as f32, + c1.1 as f32, + c2.0 as f32 - c1.0 as f32, + c2.1 as f32 - c1.1 as f32, + camera.get_pixel_thickness() * 4.0, + BLUE + ); + // let _ =((c1.0 as f32, c1.1 as f32), (c2.0 as f32, c2.1 as f32)); + } else { + return + }; + // draw_rectangle( + // c1.0 as f32, + // c1.1 as f32, + // c2.0 - c1.0, + // c2.1 - c1.1, + // BLUE.with_alpha(0.2), + // ); } } diff --git a/src/frontend/canvas/wiring/mod.rs b/src/frontend/canvas/wiring/mod.rs index 1e71a79..4ffb7b8 100644 --- a/src/frontend/canvas/wiring/mod.rs +++ b/src/frontend/canvas/wiring/mod.rs @@ -254,7 +254,7 @@ impl WireSystem { size, size, camera.get_pixel_thickness() * 2.0, - GREEN, + DARKGREEN, ); if is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift) { diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index a46f4ee..84f70e3 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -1,6 +1,7 @@ use egui_macroquad::egui; use egui_macroquad::macroquad::prelude::*; +use crate::canvas::components::ComponentData; use crate::canvas::input::CanvasInputState; use crate::gui::component_utils::GuiComponentType; @@ -43,7 +44,7 @@ impl App { } } - pub fn update(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState) { + pub fn update(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState, selection: &mut [&mut ComponentData]) { self.hovered_hotbar_button = None; self.dragged_component = None; use egui::*; @@ -79,24 +80,52 @@ impl App { .max_width(screen_width() / 6.) .resizable(false) .show_animated(ctx, self.expanded, |ui| { - ui.label("Components"); - CollapsingHeader::new("Gates").show(ui, |ui| { - let gates = [ - GuiComponentType::AndGate, - GuiComponentType::OrGate, - GuiComponentType::NandGate, - GuiComponentType::NorGate, - GuiComponentType::XorGate, - GuiComponentType::XnorGate, - GuiComponentType::NotGate, - ]; - for gate in gates { - self.circuit_component_button( - ui, - egui::Vec2::new(ui.available_size().x, 60.0), - gate, - input_state - ); + ui.allocate_ui(ui.available_size(), |ui| { + ui.label("Components"); + CollapsingHeader::new("Gates").show(ui, |ui| { + let gates = [ + GuiComponentType::AndGate, + GuiComponentType::OrGate, + GuiComponentType::NandGate, + GuiComponentType::NorGate, + GuiComponentType::XorGate, + GuiComponentType::XnorGate, + GuiComponentType::NotGate, + ]; + for gate in gates { + self.circuit_component_button( + ui, + egui::Vec2::new(ui.available_size().x, 60.0), + gate, + input_state + ); + } + }); + ui.separator(); + ui.label("Properties"); + fn bitsize_dropdown(ui: &mut Ui, data: &mut u8) { + ComboBox::from_label("Bitsize") + .selected_text(data.to_string()) + .show_ui(ui, |ui| { + for i in 1..=32 { + ui.selectable_value(data, i, i.to_string()); + } + }); + } + if selection.len() == 1 && let Some(c) = selection.get_mut(0) { + match *c { + ComponentData::Gate { + gate_type, + bitsize + } => { + bitsize_dropdown(ui, bitsize); + } + ComponentData::Mux { + bitsize + } => { + bitsize_dropdown(ui, bitsize); + } + } } }); }); @@ -126,10 +155,6 @@ impl App { ); }); - SidePanel::right("Right").show(ctx, |ui| { - ui.label("test1"); - }); - self.render_toolbar(ctx, input_state); // Ensure that each component can only be in one hotbar slot at a time diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 85ac494..09093b7 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -3,7 +3,7 @@ use egui_macroquad::macroquad::prelude::*; use crate::canvas::camera::GridCamera; use crate::canvas::components::{ComponentData, ComponentSystem}; use crate::canvas::grid::GridDrawer; -use crate::canvas::input::CanvasInputState; +use crate::canvas::input::{CanvasInput, CanvasInputState}; use crate::canvas::wiring::WireSystem; use crate::gui::App; @@ -28,7 +28,7 @@ async fn main() { let mut camera = GridCamera::new(); let gd = GridDrawer::new(crate::canvas::grid::GridDrawOptions::Instanced, vec4(0.3, 0.3, 0.3, 0.3)); let mut gui = App::new(); - let mut input_state = CanvasInputState::new(); + let mut input = CanvasInput::new(); let mut ws = WireSystem::new(); let mut cs = ComponentSystem::new(); @@ -38,53 +38,58 @@ async fn main() { loop { profile_scope!("frame"); let dt = get_frame_time(); + let mut egui_wants_ptr = false; { profile_scope!("logic"); camera.handle_input(dt); camera.update(dt); - input_state.handle_input(); - if input_state == CanvasInputState::Component { + if input.state == CanvasInputState::Component { cs.handle_input( &camera, gui.get_selected_component().unwrap().to_component_data() ); - } else if input_state == CanvasInputState::Wire { + } else if input.state == CanvasInputState::Wire { ws.handle_input(&camera); } egui_macroquad::ui(|ctx| { - gui.update(ctx, &mut input_state); + gui.update(ctx, &mut input.state, &mut cs.get_selection_mut(input.selection)); if enable_camera_debug { camera.draw_egui_ui(ctx); } if enable_profiler_debug { profiler::profile_update(ctx); } + egui_wants_ptr = ctx.is_pointer_over_area() }); } { profile_scope!("draw"); - clear_background(Color::new(0.1, 0.1, 0.1, 1.0)); + // clear_background(Color::new(0.1, 0.1, 0.1, 1.0)); + clear_background(Color::new(1.0, 1.0, 1.0, 1.0)); set_camera(&camera); gd.draw_grid(&camera); - cs.draw_components(&camera); + cs.draw_components(&camera, input.selection); ws.draw_wires(&camera); - if input_state == CanvasInputState::Wire { + if input.state == CanvasInputState::Wire { ws.draw_preview(&camera); - } else if input_state == CanvasInputState::Component { + } else if input.state == CanvasInputState::Component { cs.draw_preview( &camera, gui.get_selected_component().unwrap().to_component_data() ); + } else if input.state == CanvasInputState::Idle { + input.draw_selection(&camera); } gl_use_default_material(); set_default_camera(); egui_macroquad::draw(); } + input.handle_input(&camera, egui_wants_ptr); next_frame().await; } From 621b85854466cf7965c91d5b7e4b5c608ea9a082 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 03:58:58 -0400 Subject: [PATCH 05/15] add better selection logic (union select) --- Cargo.lock | 54 +++++++++++++++++++++- Cargo.toml | 1 + src/frontend/canvas/components/mod.rs | 66 +++++++++++++++++++-------- src/frontend/canvas/input.rs | 28 ++---------- src/frontend/main.rs | 1 + 5 files changed, 105 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0e660f..a879a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,7 @@ dependencies = [ "epaint", "macroquad", "miniquad", + "uuid", ] [[package]] @@ -304,7 +305,7 @@ dependencies = [ "bytemuck", "copypasta", "egui", - "getrandom", + "getrandom 0.2.16", "miniquad", "quad-rand", "quad-url", @@ -420,6 +421,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glam" version = "0.27.0" @@ -886,6 +899,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "redox_syscall" version = "0.5.17" @@ -921,6 +940,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "sapp-jsutils" version = "0.1.7" @@ -1057,6 +1082,17 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -1069,6 +1105,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1077,6 +1122,7 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] @@ -1481,6 +1527,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "x11-clipboard" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index a35075c..3864bbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ egui-macroquad = "0.17.3" epaint = "0.31.1" macroquad = "0.4.14" miniquad = "0.4.8" +uuid = { version = "1.18.1", features = ["v4", "js"] } [lib] name = "backend" diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index b54f435..9e70400 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use egui::ahash::HashSet; use egui_macroquad::macroquad::prelude::*; +use uuid::Uuid; use crate::canvas::camera::GridCamera; @@ -27,7 +29,7 @@ pub enum ComponentData { impl ComponentData { pub fn get_size(&self) -> (u32, u32) { match self { - Self::Gate { .. } => (3, 3), + Self::Gate { .. } => (4, 3), Self::Mux { .. } => todo!(), } } @@ -39,6 +41,13 @@ impl ComponentData { } } + pub fn get_output_offsets(&self) -> Vec<(u32, u32)> { + match self { + Self::Gate { .. } => vec![(3, 1)], + Self::Mux { .. } => todo!(), + } + } + pub fn intersects_selection(&self, comp_x: i32, comp_y: i32, c: ((i32, i32), (i32, i32))) -> bool { let ((x1, y1), (x2, y2)) = c; let sel_min_x = x1.min(x2); @@ -64,16 +73,17 @@ impl ComponentData { #[derive(Default)] pub struct ComponentSystem { - components: HashMap<(i32, i32), ComponentData>, + components: HashMap<(i32, i32), (Uuid, ComponentData)>, + selection: HashSet, } impl ComponentSystem { pub fn new() -> Self { let mut a = Self::default(); - a.components.insert((0, 0), ComponentData::Gate { + a.components.insert((0, 0), (Uuid::new_v4(), ComponentData::Gate { gate_type: GateType::And, bitsize: 32, - }); + })); a } @@ -86,7 +96,7 @@ impl ComponentSystem { if is_mouse_button_pressed(MouseButton::Left) { self.components.insert( end_pos, - selected_component, + (Uuid::new_v4(), selected_component), ); } } @@ -99,7 +109,7 @@ impl ComponentSystem { draw_rectangle( x as f32 + 0.5, y as f32, - selected_component.get_size().0 as f32, + selected_component.get_size().0 as f32 - 1., selected_component.get_size().1 as f32, PURPLE.with_alpha(0.2), ); @@ -108,7 +118,7 @@ impl ComponentSystem { pub fn draw_components(&self, camera: &GridCamera, selection: Option<((i32, i32), (i32, i32))>) { // TODO: cull components outside camera boundaries let (view_min, view_max) = camera.get_view_bounds(); - for ((x, y), component) in &self.components { + for ((x, y), (uuid, component)) in &self.components { let (w, h) = component.get_size(); if ((x + w as i32) as f32) < view_min.x || (*x as f32) > view_max.x @@ -120,11 +130,12 @@ impl ComponentSystem { draw_rectangle( *x as f32 + 0.5, *y as f32, - w as f32, + w as f32 - 1., h as f32, WHITE ); - let border_color = if let Some(c) = selection && component.intersects_selection(*x, *y, c) { + // let border_color = if let Some(c) = selection && component.intersects_selection(*x, *y, c) { + let border_color = if self.selection.contains(uuid) { ORANGE } else { BLACK @@ -132,8 +143,8 @@ impl ComponentSystem { draw_rectangle_lines( *x as f32 + 0.5, *y as f32, - component.get_size().0 as f32, - component.get_size().1 as f32, + w as f32 - 1., + h as f32, camera.get_pixel_thickness() * 4.0, border_color ); @@ -145,17 +156,34 @@ impl ComponentSystem { PORT_SIZE, BLUE ); - // draw_circle_lines( - // (*x + *dx as i32) as f32 + 0.5, - // (*y + *dy as i32) as f32 + 0.5, - // PORT_SIZE, - // camera.get_pixel_thickness() * 2.0, - // BLACK, - // ); + } + for (dx, dy) in &component.get_output_offsets() { + draw_circle( + (*x + *dx as i32) as f32 + 0.5, + (*y + *dy as i32) as f32 + 0.5, + PORT_SIZE, + BLUE + ); } } } + pub fn update_selection( + &mut self, + selection: Option<((i32, i32), (i32, i32))>, + ) { + if let Some(c) = selection { + for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { + if component.intersects_selection(comp_x, comp_y, c) { + self.selection.insert(*uuid); + } + } + } else { + self.selection.clear(); + } + + } + pub fn get_selection_mut( &mut self, selection: Option<((i32, i32), (i32, i32))>, @@ -163,7 +191,7 @@ impl ComponentSystem { let mut selected = Vec::new(); if let Some(c) = selection { - for (&(comp_x, comp_y), component) in self.components.iter_mut() { + for (&(comp_x, comp_y), (_, component)) in self.components.iter_mut() { if component.intersects_selection(comp_x, comp_y, c) { selected.push(component); } diff --git a/src/frontend/canvas/input.rs b/src/frontend/canvas/input.rs index 3229c7e..f449a6a 100644 --- a/src/frontend/canvas/input.rs +++ b/src/frontend/canvas/input.rs @@ -65,7 +65,9 @@ impl CanvasInput { return; } if is_mouse_button_pressed(MouseButton::Left) { - self.selection = None; + if !(is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift)) { + self.selection = None; + } self.in_progress_selection = Some((end_pos, end_pos)) } } @@ -79,32 +81,8 @@ impl CanvasInput { c2.1 - c1.1, BLUE.with_alpha(0.2), ); - } else if let Some((c1, c2)) = self.selection { - draw_rectangle( - c1.0 as f32, - c1.1 as f32, - c2.0 as f32 - c1.0 as f32, - c2.1 as f32 - c1.1 as f32, - BLUE.with_alpha(0.05), - ); - draw_rectangle_lines( - c1.0 as f32, - c1.1 as f32, - c2.0 as f32 - c1.0 as f32, - c2.1 as f32 - c1.1 as f32, - camera.get_pixel_thickness() * 4.0, - BLUE - ); - // let _ =((c1.0 as f32, c1.1 as f32), (c2.0 as f32, c2.1 as f32)); } else { return }; - // draw_rectangle( - // c1.0 as f32, - // c1.1 as f32, - // c2.0 - c1.0, - // c2.1 - c1.1, - // BLUE.with_alpha(0.2), - // ); } } diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 09093b7..f504526 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -90,6 +90,7 @@ async fn main() { egui_macroquad::draw(); } input.handle_input(&camera, egui_wants_ptr); + cs.update_selection(input.selection); next_frame().await; } From 7a6b7e09bcc7f4caa14d84637411a02260ac5ea1 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 05:05:28 -0400 Subject: [PATCH 06/15] add drag and drop --- src/frontend/canvas/components/mod.rs | 93 ++++++++++++++++++++++----- src/frontend/canvas/input.rs | 6 ++ src/frontend/canvas/wiring/mod.rs | 6 +- src/frontend/main.rs | 12 ++-- 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 9e70400..d3e88a9 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -48,6 +48,11 @@ impl ComponentData { } } + pub fn contains_point(&self, comp_x: i32, comp_y: i32, point: (f32, f32)) -> bool { + let (w, h) = self.get_size(); + comp_x as f32 <= point.0 && point.0 <= (comp_x + w as i32) as f32 && comp_y as f32 <= point.1 && point.1 <= (comp_y + h as i32) as f32 + } + pub fn intersects_selection(&self, comp_x: i32, comp_y: i32, c: ((i32, i32), (i32, i32))) -> bool { let ((x1, y1), (x2, y2)) = c; let sel_min_x = x1.min(x2); @@ -75,6 +80,7 @@ impl ComponentData { pub struct ComponentSystem { components: HashMap<(i32, i32), (Uuid, ComponentData)>, selection: HashSet, + drag_delta: (i32, i32), } impl ComponentSystem { @@ -101,7 +107,15 @@ impl ComponentSystem { } } - pub fn draw_preview(&self, camera: &GridCamera, selected_component: ComponentData) { + // TODO: this should be merged with handle_input when CanvasInputState is updated to contain + // selected_component + pub fn handle_delete(&mut self) { + if is_key_pressed(KeyCode::Backspace) || is_key_pressed(KeyCode::Delete) || is_key_pressed(KeyCode::X) { + self.components.retain(|_, (uuid, _)| !self.selection.contains(uuid)); + } + } + + pub fn draw_new_component_preview(&self, camera: &GridCamera, selected_component: ComponentData) { let mouse_screen = Vec2::new(mouse_position().0, mouse_position().1); let mouse_world = camera.screen_to_world(mouse_screen); let (x, y) = (mouse_world.x.floor() as i32, mouse_world.y.floor() as i32); @@ -111,12 +125,11 @@ impl ComponentSystem { y as f32, selected_component.get_size().0 as f32 - 1., selected_component.get_size().1 as f32, - PURPLE.with_alpha(0.2), + ORANGE.with_alpha(0.2), ); } - pub fn draw_components(&self, camera: &GridCamera, selection: Option<((i32, i32), (i32, i32))>) { - // TODO: cull components outside camera boundaries + pub fn draw_components(&self, camera: &GridCamera) { let (view_min, view_max) = camera.get_view_bounds(); for ((x, y), (uuid, component)) in &self.components { let (w, h) = component.get_size(); @@ -126,23 +139,44 @@ impl ComponentSystem { || (*y as f32) > view_max.y { + continue; + } + let shifted_x = *x as f32 + 0.5; + let shifted_y = *y as f32; + let target_x = shifted_x + if self.selection.contains(uuid) { + self.drag_delta.0 as f32 + } else { + 0. + }; + let target_y = shifted_y + if self.selection.contains(uuid) { + self.drag_delta.1 as f32 + } else { + 0. + }; + if self.drag_delta != (0, 0) && self.selection.contains(uuid) { + draw_rectangle( + shifted_x, + shifted_y, + w as f32 - 1., + h as f32, + ORANGE.with_alpha(0.2), + ); } draw_rectangle( - *x as f32 + 0.5, - *y as f32, + target_x, + target_y, w as f32 - 1., h as f32, - WHITE + WHITE, ); - // let border_color = if let Some(c) = selection && component.intersects_selection(*x, *y, c) { let border_color = if self.selection.contains(uuid) { ORANGE } else { BLACK }; draw_rectangle_lines( - *x as f32 + 0.5, - *y as f32, + target_x, + target_y, w as f32 - 1., h as f32, camera.get_pixel_thickness() * 4.0, @@ -151,16 +185,16 @@ impl ComponentSystem { const PORT_SIZE: f32 = 0.2; for (dx, dy) in &component.get_input_offsets() { draw_circle( - (*x + *dx as i32) as f32 + 0.5, - (*y + *dy as i32) as f32 + 0.5, + target_x + *dx as f32, + target_y + *dy as f32 + 0.5, PORT_SIZE, BLUE ); } for (dx, dy) in &component.get_output_offsets() { draw_circle( - (*x + *dx as i32) as f32 + 0.5, - (*y + *dy as i32) as f32 + 0.5, + target_x + *dx as f32, + target_y + *dy as f32 + 0.5, PORT_SIZE, BLUE ); @@ -168,20 +202,47 @@ impl ComponentSystem { } } + /// Returns false if selection preview should not be drawn, true otherwise. pub fn update_selection( &mut self, + in_progress_selection: Option<((f32, f32), (f32, f32))>, selection: Option<((i32, i32), (i32, i32))>, - ) { + ) -> bool { if let Some(c) = selection { + if self.drag_delta != (0, 0) { + let mut to_move = Vec::new(); + for (coords, (uuid, _)) in self.components.iter() { + if self.selection.contains(&uuid) { + to_move.push(coords.clone()); + } + } + for (x, y) in to_move { + if let Some(v) = self.components.remove(&(x, y)) { + self.components.insert((x + self.drag_delta.0, y + self.drag_delta.1), v); + } + } + self.drag_delta = (0, 0); + } for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { if component.intersects_selection(comp_x, comp_y, c) { self.selection.insert(*uuid); } } + } else if let Some((start, end)) = in_progress_selection { + for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { + if self.selection.contains(uuid) && component.contains_point(comp_x, comp_y, start) { + self.drag_delta = ( + (end.0 - start.0) as i32, + (end.1 - start.1) as i32, + ); + return false; + } + } + self.selection.clear(); } else { self.selection.clear(); } - + return true; } pub fn get_selection_mut( diff --git a/src/frontend/canvas/input.rs b/src/frontend/canvas/input.rs index f449a6a..1939481 100644 --- a/src/frontend/canvas/input.rs +++ b/src/frontend/canvas/input.rs @@ -70,6 +70,12 @@ impl CanvasInput { } self.in_progress_selection = Some((end_pos, end_pos)) } + // if let Some((c1, c2)) = self.in_progress_selection { + // self.selection = Some(( + // (c1.0 as i32, c1.1 as i32), + // (c2.0 as i32, c2.1 as i32), + // )); + // } } pub fn draw_selection(&self, camera: &GridCamera) { diff --git a/src/frontend/canvas/wiring/mod.rs b/src/frontend/canvas/wiring/mod.rs index 4ffb7b8..79c1553 100644 --- a/src/frontend/canvas/wiring/mod.rs +++ b/src/frontend/canvas/wiring/mod.rs @@ -300,7 +300,7 @@ impl WireSystem { line_end.x, line_end.y, width, - Color::new(0.0, 1.0, 0.0, 0.6), + DARKGREEN, ); } @@ -313,7 +313,7 @@ impl WireSystem { line_end.x, line_end.y, width, - Color::new(0.0, 1.0, 0.0, 0.6), + DARKGREEN, ); } @@ -325,7 +325,7 @@ impl WireSystem { size, size, width * 2.0, - Color::new(0.0, 1.0, 0.0, 0.8), + DARKGREEN, ); } } diff --git a/src/frontend/main.rs b/src/frontend/main.rs index f504526..78a9400 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -51,6 +51,8 @@ async fn main() { ); } else if input.state == CanvasInputState::Wire { ws.handle_input(&camera); + } else { + cs.handle_delete(); } egui_macroquad::ui(|ctx| { @@ -72,17 +74,20 @@ async fn main() { clear_background(Color::new(1.0, 1.0, 1.0, 1.0)); set_camera(&camera); gd.draw_grid(&camera); - cs.draw_components(&camera, input.selection); + cs.draw_components(&camera); ws.draw_wires(&camera); if input.state == CanvasInputState::Wire { ws.draw_preview(&camera); } else if input.state == CanvasInputState::Component { - cs.draw_preview( + cs.draw_new_component_preview( &camera, gui.get_selected_component().unwrap().to_component_data() ); } else if input.state == CanvasInputState::Idle { - input.draw_selection(&camera); + let draw_selection = cs.update_selection(input.in_progress_selection, input.selection); + if draw_selection { + input.draw_selection(&camera); + } } gl_use_default_material(); @@ -90,7 +95,6 @@ async fn main() { egui_macroquad::draw(); } input.handle_input(&camera, egui_wants_ptr); - cs.update_selection(input.selection); next_frame().await; } From 45d13eb6b7d57497af6c48fba5428dcae2d96d72 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 05:13:41 -0400 Subject: [PATCH 07/15] fix small bug in displaying properties --- src/frontend/canvas/components/mod.rs | 9 +++------ src/frontend/main.rs | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index d3e88a9..cb2bc3c 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -247,15 +247,12 @@ impl ComponentSystem { pub fn get_selection_mut( &mut self, - selection: Option<((i32, i32), (i32, i32))>, ) -> Vec<&mut ComponentData> { let mut selected = Vec::new(); - if let Some(c) = selection { - for (&(comp_x, comp_y), (_, component)) in self.components.iter_mut() { - if component.intersects_selection(comp_x, comp_y, c) { - selected.push(component); - } + for (_, (uuid, component)) in self.components.iter_mut() { + if self.selection.contains(uuid) { + selected.push(component); } } diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 78a9400..7fe6935 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -56,7 +56,7 @@ async fn main() { } egui_macroquad::ui(|ctx| { - gui.update(ctx, &mut input.state, &mut cs.get_selection_mut(input.selection)); + gui.update(ctx, &mut input.state, &mut cs.get_selection_mut()); if enable_camera_debug { camera.draw_egui_ui(ctx); } From 324b409a0b103a6aa6a07aab9c362a64d51cb9f9 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 17:26:04 -0400 Subject: [PATCH 08/15] dragging components no longer adds intersected components to selection --- src/frontend/canvas/components/mod.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index cb2bc3c..6d01d9b 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -81,6 +81,7 @@ pub struct ComponentSystem { components: HashMap<(i32, i32), (Uuid, ComponentData)>, selection: HashSet, drag_delta: (i32, i32), + drag_handled: bool, } impl ComponentSystem { @@ -208,6 +209,9 @@ impl ComponentSystem { in_progress_selection: Option<((f32, f32), (f32, f32))>, selection: Option<((i32, i32), (i32, i32))>, ) -> bool { + if in_progress_selection.is_some() { + self.drag_handled = false; + } if let Some(c) = selection { if self.drag_delta != (0, 0) { let mut to_move = Vec::new(); @@ -222,10 +226,12 @@ impl ComponentSystem { } } self.drag_delta = (0, 0); - } - for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { - if component.intersects_selection(comp_x, comp_y, c) { - self.selection.insert(*uuid); + self.drag_handled = true; + } else if !self.drag_handled { + for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { + if component.intersects_selection(comp_x, comp_y, c) { + self.selection.insert(*uuid); + } } } } else if let Some((start, end)) = in_progress_selection { @@ -241,6 +247,7 @@ impl ComponentSystem { self.selection.clear(); } else { self.selection.clear(); + self.drag_handled = false; } return true; } From cb18808e2e8434718dae712df37e95b7524a0923 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 17:50:00 -0400 Subject: [PATCH 09/15] add gate switcher dropdown and NOT gate --- src/frontend/canvas/components/mod.rs | 49 +++++++++++++++++++++++++-- src/frontend/gui/mod.rs | 18 ++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 6d01d9b..4163f04 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -16,6 +16,20 @@ pub enum GateType { Not, } +impl GateType { + pub fn get_name(&self) -> &'static str { + match self { + Self::And => "AND", + Self::Or => "OR", + Self::Nand => "NAND", + Self::Nor => "NOR", + Self::Xor => "XOR", + Self::Xnor => "XNOR", + Self::Not => "NOT", + } + } +} + pub enum ComponentData { Gate { gate_type: GateType, @@ -29,21 +43,33 @@ pub enum ComponentData { impl ComponentData { pub fn get_size(&self) -> (u32, u32) { match self { - Self::Gate { .. } => (4, 3), + Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { + (3, 1) + } else { + (4, 3) + }, Self::Mux { .. } => todo!(), } } pub fn get_input_offsets(&self) -> Vec<(u32, u32)> { match self { - Self::Gate { .. } => vec![(0, 0), (0, 2)], + Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { + vec![(0, 0)] + } else { + vec![(0, 0), (0, 2)] + }, Self::Mux { .. } => todo!(), } } pub fn get_output_offsets(&self) -> Vec<(u32, u32)> { match self { - Self::Gate { .. } => vec![(3, 1)], + Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { + vec![(2, 0)] + } else { + vec![(3, 1)] + } Self::Mux { .. } => todo!(), } } @@ -74,6 +100,23 @@ impl ComponentData { intersects } + + pub fn get_name(&self) -> &'static str { + match self { + Self::Gate { gate_type, .. } => { + match gate_type { + GateType::And => "AND gate", + GateType::Or => "OR gate", + GateType::Nand => "NAND gate", + GateType::Nor => "NOR gate", + GateType::Xor => "XOR gate", + GateType::Xnor => "XNOR gate", + GateType::Not => "NOT gate", + } + } + Self::Mux { .. } => "Multiplexer", + } + } } #[derive(Default)] diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index 84f70e3..a93b073 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -1,7 +1,7 @@ use egui_macroquad::egui; use egui_macroquad::macroquad::prelude::*; -use crate::canvas::components::ComponentData; +use crate::canvas::components::{ComponentData, GateType}; use crate::canvas::input::CanvasInputState; use crate::gui::component_utils::GuiComponentType; @@ -102,7 +102,19 @@ impl App { } }); ui.separator(); - ui.label("Properties"); + fn gate_type_dropdown(ui: &mut Ui, data: &mut GateType) { + ComboBox::from_label("Gate type") + .selected_text(data.get_name()) + .show_ui(ui, |ui| { + ui.selectable_value(data, GateType::And, "AND"); + ui.selectable_value(data, GateType::Or, "OR"); + ui.selectable_value(data, GateType::Nand, "NAND"); + ui.selectable_value(data, GateType::Nor, "NOR"); + ui.selectable_value(data, GateType::Xor, "XOR"); + ui.selectable_value(data, GateType::Xnor, "XNOR"); + ui.selectable_value(data, GateType::Not, "NOT"); + }); + } fn bitsize_dropdown(ui: &mut Ui, data: &mut u8) { ComboBox::from_label("Bitsize") .selected_text(data.to_string()) @@ -113,11 +125,13 @@ impl App { }); } if selection.len() == 1 && let Some(c) = selection.get_mut(0) { + ui.label(c.get_name()); match *c { ComponentData::Gate { gate_type, bitsize } => { + gate_type_dropdown(ui, gate_type); bitsize_dropdown(ui, bitsize); } ComponentData::Mux { From f15ee4045aee287497957e378ab3bcd6a75ad019 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 20:48:16 -0400 Subject: [PATCH 10/15] draw components with bezier curves --- src/frontend/canvas/components/mod.rs | 233 +++++++++++++++++--------- src/frontend/gui/component_utils.rs | 91 +++++++++- src/frontend/gui/mod.rs | 25 ++- 3 files changed, 256 insertions(+), 93 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 4163f04..8bcbf6d 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -1,9 +1,9 @@ -use std::collections::HashMap; +use std::{collections::HashMap, ops::Neg}; use egui::ahash::HashSet; use egui_macroquad::macroquad::prelude::*; use uuid::Uuid; -use crate::canvas::camera::GridCamera; +use crate::{canvas::camera::GridCamera, gui::component_utils::{macroquad_draw_curve, GuiComponentType}}; #[derive(PartialEq, Eq, Copy, Clone)] pub enum GateType { @@ -41,7 +41,7 @@ pub enum ComponentData { } impl ComponentData { - pub fn get_size(&self) -> (u32, u32) { + pub fn get_size(&self) -> (i32, i32) { match self { Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { (3, 1) @@ -52,7 +52,7 @@ impl ComponentData { } } - pub fn get_input_offsets(&self) -> Vec<(u32, u32)> { + pub fn get_input_offsets(&self) -> Vec<(i32, i32)> { match self { Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { vec![(0, 0)] @@ -63,7 +63,7 @@ impl ComponentData { } } - pub fn get_output_offsets(&self) -> Vec<(u32, u32)> { + pub fn get_output_offsets(&self) -> Vec<(i32, i32)> { match self { Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { vec![(2, 0)] @@ -74,11 +74,62 @@ impl ComponentData { } } - pub fn contains_point(&self, comp_x: i32, comp_y: i32, point: (f32, f32)) -> bool { - let (w, h) = self.get_size(); - comp_x as f32 <= point.0 && point.0 <= (comp_x + w as i32) as f32 && comp_y as f32 <= point.1 && point.1 <= (comp_y + h as i32) as f32 + pub fn get_name(&self) -> &'static str { + match self { + Self::Gate { gate_type, .. } => { + match gate_type { + GateType::And => "AND gate", + GateType::Or => "OR gate", + GateType::Nand => "NAND gate", + GateType::Nor => "NOR gate", + GateType::Xor => "XOR gate", + GateType::Xnor => "XNOR gate", + GateType::Not => "NOT gate", + } + } + Self::Mux { .. } => "Multiplexer", + } + } +} + +/// Orientation::One = pi / 2 CCW, ... +#[derive(Default, PartialEq, Eq, Copy, Clone)] +pub enum Orientation { + #[default] + Zero, + One, + Two, + Three +} + +impl Orientation { + pub fn get_name(&self) -> &'static str { + match self { + Self::Zero => "North", + Self::One => "West", + Self::Two => "South", + Self::Three => "East", + } } +} + +pub fn rotate_point_ccw>((x, y): (T, T), orientation: Orientation) -> (T, T) { + match orientation { + Orientation::Zero => (x, y), + Orientation::One => (-y, x), + Orientation::Two => (-x, -y), + Orientation::Three => (y, -x), + } +} +pub struct Component { + uuid: Uuid, + pub orientation: Orientation, + pub label: String, + pub data: ComponentData +} + +impl Component { pub fn intersects_selection(&self, comp_x: i32, comp_y: i32, c: ((i32, i32), (i32, i32))) -> bool { let ((x1, y1), (x2, y2)) = c; let sel_min_x = x1.min(x2); @@ -86,11 +137,15 @@ impl ComponentData { let sel_min_y = y1.min(y2); let sel_max_y = y1.max(y2); - let (w, h) = self.get_size(); - let comp_min_x = comp_x; - let comp_max_x = comp_x + w as i32 - 1; - let comp_min_y = comp_y; - let comp_max_y = comp_y + h as i32 - 1; + let (w, h) = rotate_point_ccw(self.data.get_size(), self.orientation); + let old_comp_min_x = comp_x; + let old_comp_max_x = comp_x + w as i32 - 1; + let old_comp_min_y = comp_y; + let old_comp_max_y = comp_y + h as i32 - 1; + let comp_min_x = old_comp_min_x.min(old_comp_max_x); + let comp_max_x = old_comp_min_x.max(old_comp_max_x); + let comp_min_y = old_comp_min_y.min(old_comp_max_y); + let comp_max_y = old_comp_min_y.max(old_comp_max_y); // Check for intersection let intersects = sel_min_x <= comp_max_x @@ -101,27 +156,23 @@ impl ComponentData { intersects } - pub fn get_name(&self) -> &'static str { - match self { - Self::Gate { gate_type, .. } => { - match gate_type { - GateType::And => "AND gate", - GateType::Or => "OR gate", - GateType::Nand => "NAND gate", - GateType::Nor => "NOR gate", - GateType::Xor => "XOR gate", - GateType::Xnor => "XNOR gate", - GateType::Not => "NOT gate", - } - } - Self::Mux { .. } => "Multiplexer", - } + pub fn contains_point(&self, comp_x: i32, comp_y: i32, point: (f32, f32)) -> bool { + let (w, h) = rotate_point_ccw(self.data.get_size(), self.orientation); + let x1 = comp_x; + let x2 = comp_x + w; + let y1 = comp_y; + let y2 = comp_y + h; + let comp_min_x = x1.min(x2); + let comp_max_x = x1.max(x2); + let comp_min_y = y1.min(y2); + let comp_max_y = y1.max(y2); + comp_min_x as f32 <= point.0 && point.0 <= comp_max_x as f32 && comp_min_y as f32 <= point.1 && point.1 <= comp_max_y as f32 } } #[derive(Default)] pub struct ComponentSystem { - components: HashMap<(i32, i32), (Uuid, ComponentData)>, + components: HashMap<(i32, i32), Component>, selection: HashSet, drag_delta: (i32, i32), drag_handled: bool, @@ -130,10 +181,15 @@ pub struct ComponentSystem { impl ComponentSystem { pub fn new() -> Self { let mut a = Self::default(); - a.components.insert((0, 0), (Uuid::new_v4(), ComponentData::Gate { - gate_type: GateType::And, - bitsize: 32, - })); + a.components.insert((0, 0), Component { + uuid: Uuid::new_v4(), + orientation: Orientation::Zero, + label: String::new(), + data: ComponentData::Gate { + gate_type: GateType::And, + bitsize: 32, + }, + }); a } @@ -146,7 +202,12 @@ impl ComponentSystem { if is_mouse_button_pressed(MouseButton::Left) { self.components.insert( end_pos, - (Uuid::new_v4(), selected_component), + Component { + uuid: Uuid::new_v4(), + orientation: Orientation::Zero, + label: String::new(), + data: selected_component, + }, ); } } @@ -155,7 +216,7 @@ impl ComponentSystem { // selected_component pub fn handle_delete(&mut self) { if is_key_pressed(KeyCode::Backspace) || is_key_pressed(KeyCode::Delete) || is_key_pressed(KeyCode::X) { - self.components.retain(|_, (uuid, _)| !self.selection.contains(uuid)); + self.components.retain(|_, component| !self.selection.contains(&component.uuid)); } } @@ -165,9 +226,9 @@ impl ComponentSystem { let (x, y) = (mouse_world.x.floor() as i32, mouse_world.y.floor() as i32); let (x, y) = (x - selected_component.get_size().0 as i32 / 2, y - selected_component.get_size().1 as i32 / 2); draw_rectangle( - x as f32 + 0.5, + x as f32, y as f32, - selected_component.get_size().0 as f32 - 1., + selected_component.get_size().0 as f32, selected_component.get_size().1 as f32, ORANGE.with_alpha(0.2), ); @@ -175,70 +236,86 @@ impl ComponentSystem { pub fn draw_components(&self, camera: &GridCamera) { let (view_min, view_max) = camera.get_view_bounds(); - for ((x, y), (uuid, component)) in &self.components { - let (w, h) = component.get_size(); - if ((x + w as i32) as f32) < view_min.x + // let stroke_width = camera.get_pixel_thickness() * 4.0; + let stroke_width = 0.1; + for ((x, y), component) in &self.components { + let (w, h) = component.data.get_size(); + // let (w, h) = rotate_point_ccw(component.data.get_size(), component.orientation); + // return; + if ((x + w) as f32) < view_min.x || (*x as f32) > view_max.x - || ((y + h as i32) as f32) < view_min.y + || ((y + h) as f32) < view_min.y || (*y as f32) > view_max.y { continue; } - let shifted_x = *x as f32 + 0.5; + let shifted_x = *x as f32; let shifted_y = *y as f32; - let target_x = shifted_x + if self.selection.contains(uuid) { + let target_x = shifted_x + if self.selection.contains(&component.uuid) { self.drag_delta.0 as f32 } else { 0. }; - let target_y = shifted_y + if self.selection.contains(uuid) { + let target_y = shifted_y + if self.selection.contains(&component.uuid) { self.drag_delta.1 as f32 } else { 0. }; - if self.drag_delta != (0, 0) && self.selection.contains(uuid) { + let (rot_w, rot_h) = rotate_point_ccw((w, h), component.orientation); + if self.drag_delta != (0, 0) && self.selection.contains(&component.uuid) { draw_rectangle( shifted_x, shifted_y, - w as f32 - 1., - h as f32, + rot_w as f32, + rot_h as f32, ORANGE.with_alpha(0.2), ); } - draw_rectangle( - target_x, - target_y, - w as f32 - 1., - h as f32, - WHITE, - ); - let border_color = if self.selection.contains(uuid) { + let b_target_x = x + if self.selection.contains(&component.uuid) { + self.drag_delta.0 + } else { 0 }; + let b_target_y = y + if self.selection.contains(&component.uuid) { + self.drag_delta.1 + } else { 0 }; + let border_color = if self.selection.contains(&component.uuid) { ORANGE } else { BLACK }; - draw_rectangle_lines( - target_x, - target_y, - w as f32 - 1., - h as f32, - camera.get_pixel_thickness() * 4.0, - border_color - ); + for i in GuiComponentType::AND_GATE_DRAW_INSTRUCTIONS { + macroquad_draw_curve( + i, + epaint::Rect::from_two_pos( + epaint::Pos2 { x: b_target_x as f32, y: b_target_y as f32 }, + epaint::Pos2 { x: (b_target_x + w) as f32, y: (b_target_y + h) as f32 }, + ), + stroke_width, + border_color, + component.orientation + ); + } const PORT_SIZE: f32 = 0.2; - for (dx, dy) in &component.get_input_offsets() { + for (dx, dy) in component.data.get_input_offsets() { + let (dx, dy) = rotate_point_ccw((dx as f32 + 0.5, dy as f32 + 0.5), component.orientation); draw_circle( - target_x + *dx as f32, - target_y + *dy as f32 + 0.5, + target_x + dx, + target_y + dy, PORT_SIZE, BLUE ); } - for (dx, dy) in &component.get_output_offsets() { + for (dx, dy) in component.data.get_output_offsets() { + let (dx, dy) = rotate_point_ccw((dx as f32 + 0.5, dy as f32 + 0.5), component.orientation); + draw_circle( + target_x + dx, + target_y + dy, + PORT_SIZE, + BLUE + ); draw_circle( - target_x + *dx as f32, - target_y + *dy as f32 + 0.5, + target_x + dx as f32, + target_y + dy as f32, PORT_SIZE, BLUE ); @@ -258,8 +335,8 @@ impl ComponentSystem { if let Some(c) = selection { if self.drag_delta != (0, 0) { let mut to_move = Vec::new(); - for (coords, (uuid, _)) in self.components.iter() { - if self.selection.contains(&uuid) { + for (coords, component) in self.components.iter() { + if self.selection.contains(&component.uuid) { to_move.push(coords.clone()); } } @@ -271,15 +348,15 @@ impl ComponentSystem { self.drag_delta = (0, 0); self.drag_handled = true; } else if !self.drag_handled { - for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { + for (&(comp_x, comp_y), component) in self.components.iter_mut() { if component.intersects_selection(comp_x, comp_y, c) { - self.selection.insert(*uuid); + self.selection.insert(component.uuid); } } } } else if let Some((start, end)) = in_progress_selection { - for (&(comp_x, comp_y), (uuid, component)) in self.components.iter_mut() { - if self.selection.contains(uuid) && component.contains_point(comp_x, comp_y, start) { + for (&(comp_x, comp_y), component) in self.components.iter_mut() { + if self.selection.contains(&component.uuid) && component.contains_point(comp_x, comp_y, start) { self.drag_delta = ( (end.0 - start.0) as i32, (end.1 - start.1) as i32, @@ -297,11 +374,11 @@ impl ComponentSystem { pub fn get_selection_mut( &mut self, - ) -> Vec<&mut ComponentData> { + ) -> Vec<&mut Component> { let mut selected = Vec::new(); - for (_, (uuid, component)) in self.components.iter_mut() { - if self.selection.contains(uuid) { + for (_, component) in self.components.iter_mut() { + if self.selection.contains(&component.uuid) { selected.push(component); } } diff --git a/src/frontend/gui/component_utils.rs b/src/frontend/gui/component_utils.rs index 9da8485..f3aa30f 100644 --- a/src/frontend/gui/component_utils.rs +++ b/src/frontend/gui/component_utils.rs @@ -1,12 +1,83 @@ -use epaint::Pos2; - -use crate::canvas::components::{ComponentData, GateType}; +use crate::canvas::components::{ComponentData, GateType, Orientation}; +use epaint::{Pos2, Rect}; +use egui_macroquad::macroquad::prelude::*; pub enum DrawInstruction { Line([Pos2; 2]), CubicBezierCurve([Pos2; 4]), } +pub fn macroquad_draw_curve( + instruction: DrawInstruction, + frame: Rect, + width: f32, + color: Color, + orientation: Orientation, +) { + match instruction { + DrawInstruction::Line([start, end]) => { + let p0 = rotate_around_top_left(map_to_frame(start, frame), frame, orientation); + let p1 = rotate_around_top_left(map_to_frame(end, frame), frame, orientation); + draw_line(p0.x, p0.y, p1.x, p1.y, width, color); + } + + DrawInstruction::CubicBezierCurve([p0, p1, p2, p3]) => { + let p0 = rotate_around_top_left(map_to_frame(p0, frame), frame, orientation); + let p1 = rotate_around_top_left(map_to_frame(p1, frame), frame, orientation); + let p2 = rotate_around_top_left(map_to_frame(p2, frame), frame, orientation); + let p3 = rotate_around_top_left(map_to_frame(p3, frame), frame, orientation); + + let steps = 32; + let mut prev = p0; + for i in 1..=steps { + let t = i as f32 / steps as f32; + let point = cubic_bezier_point(p0, p1, p2, p3, t); + draw_line(prev.x, prev.y, point.x, point.y, width, color); + prev = point; + } + } + } +} + +/// Map normalized (0..1) Pos2 into a position inside the given `frame` Rect +fn map_to_frame(pos: Pos2, frame: Rect) -> Pos2 { + Pos2 { + x: frame.left() + pos.x * frame.width(), + y: frame.top() + pos.y * frame.height(), + } +} + +/// Rotate a point around the top-left corner of the frame using orientation +fn rotate_around_top_left(pos: Pos2, frame: Rect, orientation: Orientation) -> Pos2 { + let origin = frame.left_top(); + let local = pos - origin; + + let rotated = match orientation { + Orientation::Zero => local, + Orientation::One => egui::Vec2::new(-local.y, local.x), + Orientation::Two => egui::Vec2::new(-local.x, -local.y), + Orientation::Three => egui::Vec2::new(local.y, -local.x), + }; + + origin + rotated +} + +/// Compute a point on a cubic Bezier curve at parameter t ∈ [0, 1] +fn cubic_bezier_point(p0: Pos2, p1: Pos2, p2: Pos2, p3: Pos2, t: f32) -> Pos2 { + let u = 1.0 - t; + let tt = t * t; + let uu = u * u; + let uuu = uu * u; + let ttt = tt * t; + + let mut p = Pos2::ZERO; + p += p0.to_vec2() * uuu; + p += p1.to_vec2() * 3.0 * uu * t; + p += p2.to_vec2() * 3.0 * u * tt; + p += p3.to_vec2() * ttt; + p +} + pub fn pos2_with_rect(pos: &Pos2, rect: egui::Rect) -> Pos2 { Pos2 { x: rect.min.x + pos.x * rect.width(), @@ -72,14 +143,18 @@ impl GuiComponentType { } const UNIMPLEMENTED_DRAW_INSTRUCTIONS: [DrawInstruction; 0] = []; - const AND_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { + pub const AND_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { const BOX_WIDTH: f32 = 0.5; - const OFFSET_Y: f32 = 0.1; + const OFFSET_Y: f32 = 0.0; + const OFFSET_X: f32 = 0.125; [ - DrawInstruction::Line([Pos2::new(0.0, OFFSET_Y), Pos2::new(0.0, 1.0 - OFFSET_Y)]), - DrawInstruction::Line([Pos2::new(0.0, OFFSET_Y), Pos2::new(0.5, OFFSET_Y)]), + DrawInstruction::Line([Pos2::new(OFFSET_X, OFFSET_Y), Pos2::new(OFFSET_X, 1.0 - OFFSET_Y)]), + DrawInstruction::Line([ + Pos2::new(OFFSET_X, OFFSET_Y), + Pos2::new(BOX_WIDTH, OFFSET_Y) + ]), DrawInstruction::Line([ - Pos2::new(0.0, 1.0 - OFFSET_Y), + Pos2::new(OFFSET_X, 1.0 - OFFSET_Y), Pos2::new(BOX_WIDTH, 1.0 - OFFSET_Y), ]), DrawInstruction::CubicBezierCurve([ diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index a93b073..d4e3bad 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -1,7 +1,7 @@ use egui_macroquad::egui; use egui_macroquad::macroquad::prelude::*; -use crate::canvas::components::{ComponentData, GateType}; +use crate::canvas::components::{Component, ComponentData, GateType, Orientation}; use crate::canvas::input::CanvasInputState; use crate::gui::component_utils::GuiComponentType; @@ -44,7 +44,7 @@ impl App { } } - pub fn update(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState, selection: &mut [&mut ComponentData]) { + pub fn update(&mut self, ctx: &egui::Context, input_state: &mut CanvasInputState, selection: &mut [&mut Component]) { self.hovered_hotbar_button = None; self.dragged_component = None; use egui::*; @@ -102,6 +102,16 @@ impl App { } }); ui.separator(); + fn orientation_dropdown(ui: &mut Ui, data: &mut Orientation) { + ComboBox::from_label("Orientation") + .selected_text(data.get_name()) + .show_ui(ui, |ui| { + ui.selectable_value(data, Orientation::Zero, Orientation::Zero.get_name()); + ui.selectable_value(data, Orientation::One, Orientation::One.get_name()); + ui.selectable_value(data, Orientation::Two, Orientation::Two.get_name()); + ui.selectable_value(data, Orientation::Three, Orientation::Three.get_name()); + }); + } fn gate_type_dropdown(ui: &mut Ui, data: &mut GateType) { ComboBox::from_label("Gate type") .selected_text(data.get_name()) @@ -125,17 +135,18 @@ impl App { }); } if selection.len() == 1 && let Some(c) = selection.get_mut(0) { - ui.label(c.get_name()); - match *c { + ui.label(c.data.get_name()); + orientation_dropdown(ui, &mut c.orientation); + match (*c).data { ComponentData::Gate { - gate_type, - bitsize + ref mut gate_type, + ref mut bitsize } => { gate_type_dropdown(ui, gate_type); bitsize_dropdown(ui, bitsize); } ComponentData::Mux { - bitsize + ref mut bitsize } => { bitsize_dropdown(ui, bitsize); } From a4b834aaf4e5965b85f77bf693f482f4fb8c1798 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 20:52:00 -0400 Subject: [PATCH 11/15] make selection intersection comparisons for lower bound exclusive to prevent unintentional selection --- src/frontend/canvas/components/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 8bcbf6d..65ba231 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -149,9 +149,9 @@ impl Component { // Check for intersection let intersects = sel_min_x <= comp_max_x - && sel_max_x >= comp_min_x + && sel_max_x > comp_min_x && sel_min_y <= comp_max_y - && sel_max_y >= comp_min_y; + && sel_max_y > comp_min_y; intersects } From 608d5e5a6cf827ac0b2135b12f20847da07b5a23 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 21:35:31 -0400 Subject: [PATCH 12/15] add drawings for OR and NOT gates --- src/frontend/canvas/components/draw.rs | 81 ++++++++++++++++++++++++++ src/frontend/canvas/components/mod.rs | 22 ++++++- src/frontend/gui/component_selector.rs | 1 + src/frontend/gui/component_utils.rs | 14 ++++- src/frontend/gui/toolbar.rs | 1 + src/frontend/main.rs | 12 +++- 6 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 src/frontend/canvas/components/draw.rs diff --git a/src/frontend/canvas/components/draw.rs b/src/frontend/canvas/components/draw.rs new file mode 100644 index 0000000..0932999 --- /dev/null +++ b/src/frontend/canvas/components/draw.rs @@ -0,0 +1,81 @@ +use egui::Pos2; + +use crate::gui::component_utils::DrawInstruction; + + +pub const UNIMPLEMENTED_DRAW_INSTRUCTIONS: [DrawInstruction; 0] = []; + +pub const AND_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { + const BOX_WIDTH: f32 = 0.5; + const OFFSET_Y: f32 = 0.0; + const OFFSET_X: f32 = 0.125; + [ + DrawInstruction::Line([Pos2::new(OFFSET_X, OFFSET_Y), Pos2::new(OFFSET_X, 1.0 - OFFSET_Y)]), + DrawInstruction::Line([ + Pos2::new(OFFSET_X, OFFSET_Y), + Pos2::new(BOX_WIDTH, OFFSET_Y) + ]), + DrawInstruction::Line([ + Pos2::new(OFFSET_X, 1.0 - OFFSET_Y), + Pos2::new(BOX_WIDTH, 1.0 - OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(BOX_WIDTH, 1.0 - OFFSET_Y), + Pos2::new(1.0, 1.0 - OFFSET_Y), + Pos2::new(1.0, OFFSET_Y), + Pos2::new(BOX_WIDTH, OFFSET_Y), + ]), + ] +}; + +pub const OR_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 3] = { + const OFFSET_Y: f32 = 0.0; + const MID_OFFSET_Y: f32 = 0.1; + const ARC_START_X: f32 = 0.1; + const ARC_END_X: f32 = 0.875; + [ + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, OFFSET_Y), + Pos2::new(ARC_START_X, OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, OFFSET_Y), + Pos2::new(0.55, MID_OFFSET_Y * 0.3), + Pos2::new(0.8, MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(0.55, 1.0 - MID_OFFSET_Y * 0.3), + Pos2::new(0.8, 1.0 - MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + ] +}; + +pub const NOT_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { + const OFFSET_Y: f32 = 0.1; + const ARC_START_X: f32 = 1. / 6.; + const ARC_END_X: f32 = 1. - 2. * ARC_START_X; + [ + DrawInstruction::Line([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X, OFFSET_Y), + ]), + DrawInstruction::Line([ + Pos2::new(ARC_START_X, OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::Line([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::Ellipse( + Pos2::new(4.2 * ARC_START_X, 0.5), + ARC_START_X / 3., + 0.5 / 3., + ), + ] +}; diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 65ba231..a821922 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -3,7 +3,9 @@ use egui::ahash::HashSet; use egui_macroquad::macroquad::prelude::*; use uuid::Uuid; -use crate::{canvas::camera::GridCamera, gui::component_utils::{macroquad_draw_curve, GuiComponentType}}; +use crate::{canvas::{camera::GridCamera, components::draw::AND_GATE_DRAW_INSTRUCTIONS}, gui::component_utils::{macroquad_draw_curve, DrawInstruction, GuiComponentType}}; + +mod draw; #[derive(PartialEq, Eq, Copy, Clone)] pub enum GateType { @@ -90,6 +92,20 @@ impl ComponentData { Self::Mux { .. } => "Multiplexer", } } + + pub fn get_draw_data(&self) -> &[DrawInstruction] { + match self { + Self::Gate { gate_type, .. } => { + match gate_type { + GateType::And => &draw::AND_GATE_DRAW_INSTRUCTIONS, + GateType::Or => &draw::OR_GATE_DRAW_INSTRUCTIONS, + GateType::Not => &draw::NOT_GATE_DRAW_INSTRUCTIONS, + _ => &draw::UNIMPLEMENTED_DRAW_INSTRUCTIONS, + } + } + Self::Mux { .. } => todo!(), + } + } } /// Orientation::One = pi / 2 CCW, ... @@ -186,7 +202,7 @@ impl ComponentSystem { orientation: Orientation::Zero, label: String::new(), data: ComponentData::Gate { - gate_type: GateType::And, + gate_type: GateType::Not, bitsize: 32, }, }); @@ -283,7 +299,7 @@ impl ComponentSystem { } else { BLACK }; - for i in GuiComponentType::AND_GATE_DRAW_INSTRUCTIONS { + for i in component.data.get_draw_data() { macroquad_draw_curve( i, epaint::Rect::from_two_pos( diff --git a/src/frontend/gui/component_selector.rs b/src/frontend/gui/component_selector.rs index 3e38775..891e113 100644 --- a/src/frontend/gui/component_selector.rs +++ b/src/frontend/gui/component_selector.rs @@ -44,6 +44,7 @@ impl App { ); painter.add(shape); } + DrawInstruction::Ellipse(_, _, _) => todo!() } } diff --git a/src/frontend/gui/component_utils.rs b/src/frontend/gui/component_utils.rs index f3aa30f..a1de8d1 100644 --- a/src/frontend/gui/component_utils.rs +++ b/src/frontend/gui/component_utils.rs @@ -1,14 +1,15 @@ -use crate::canvas::components::{ComponentData, GateType, Orientation}; +use crate::canvas::components::{rotate_point_ccw, ComponentData, GateType, Orientation}; use epaint::{Pos2, Rect}; use egui_macroquad::macroquad::prelude::*; pub enum DrawInstruction { Line([Pos2; 2]), CubicBezierCurve([Pos2; 4]), + Ellipse(Pos2, f32, f32), } pub fn macroquad_draw_curve( - instruction: DrawInstruction, + instruction: &DrawInstruction, frame: Rect, width: f32, color: Color, @@ -36,11 +37,18 @@ pub fn macroquad_draw_curve( prev = point; } } + DrawInstruction::Ellipse(c, r_h, r_v) => { + let c = rotate_around_top_left(map_to_frame(c, frame), frame, orientation); + let r_h = r_h * frame.width(); + let r_v = r_v * frame.height(); + draw_ellipse_lines(c.x, c.y, r_h, r_v, 0., width, color); + // draw_ellipse(c.x, c.y, r_h, r_v, 0., color); + } } } /// Map normalized (0..1) Pos2 into a position inside the given `frame` Rect -fn map_to_frame(pos: Pos2, frame: Rect) -> Pos2 { +fn map_to_frame(pos: &Pos2, frame: Rect) -> Pos2 { Pos2 { x: frame.left() + pos.x * frame.width(), y: frame.top() + pos.y * frame.height(), diff --git a/src/frontend/gui/toolbar.rs b/src/frontend/gui/toolbar.rs index 744f831..e2777e1 100644 --- a/src/frontend/gui/toolbar.rs +++ b/src/frontend/gui/toolbar.rs @@ -60,6 +60,7 @@ impl App { ); painter.add(shape); } + DrawInstruction::Ellipse(_, _, _) => todo!(), } } } diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 7fe6935..ac4486c 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -12,7 +12,17 @@ mod canvas; mod gui; mod util; -#[macroquad::main("circuitsim")] +fn window_conf() -> Conf { + Conf { + window_title: "Circuitsim".to_owned(), + // fullscreen: true, + sample_count: 4, + ..Default::default() + } +} + + +#[macroquad::main(window_conf)] async fn main() { let args: Vec = std::env::args().collect(); let mut enable_camera_debug = false; From f1d9ca0679ce8fed0f5666a71ea381ff8a5f5729 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Mon, 20 Oct 2025 21:54:39 -0400 Subject: [PATCH 13/15] add drawings for the rest of the gates --- src/frontend/canvas/components/draw.rs | 140 ++++++++++++++++++++++++- src/frontend/canvas/components/mod.rs | 6 +- 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/frontend/canvas/components/draw.rs b/src/frontend/canvas/components/draw.rs index 0932999..c682d5c 100644 --- a/src/frontend/canvas/components/draw.rs +++ b/src/frontend/canvas/components/draw.rs @@ -43,18 +43,154 @@ pub const OR_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 3] = { DrawInstruction::CubicBezierCurve([ Pos2::new(ARC_START_X, OFFSET_Y), Pos2::new(0.55, MID_OFFSET_Y * 0.3), - Pos2::new(0.8, MID_OFFSET_Y), + Pos2::new(0.7, MID_OFFSET_Y), Pos2::new(ARC_END_X, 0.5), ]), DrawInstruction::CubicBezierCurve([ Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), Pos2::new(0.55, 1.0 - MID_OFFSET_Y * 0.3), - Pos2::new(0.8, 1.0 - MID_OFFSET_Y), + Pos2::new(0.7, 1.0 - MID_OFFSET_Y), Pos2::new(ARC_END_X, 0.5), ]), ] }; +pub const NAND_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 5] = { + const BOX_WIDTH: f32 = 0.41; + const OFFSET_Y: f32 = 0.0; + const OFFSET_X: f32 = 0.125; + const BUBBLE_R: f32 = 0.5 / 9.; + [ + DrawInstruction::Line([Pos2::new(OFFSET_X, OFFSET_Y), Pos2::new(OFFSET_X, 1.0 - OFFSET_Y)]), + DrawInstruction::Line([ + Pos2::new(OFFSET_X, OFFSET_Y), + Pos2::new(BOX_WIDTH, OFFSET_Y) + ]), + DrawInstruction::Line([ + Pos2::new(OFFSET_X, 1.0 - OFFSET_Y), + Pos2::new(BOX_WIDTH, 1.0 - OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(BOX_WIDTH, 1.0 - OFFSET_Y), + Pos2::new(0.8, 1.0 - OFFSET_Y), + Pos2::new(0.8, OFFSET_Y), + Pos2::new(BOX_WIDTH, OFFSET_Y), + ]), + DrawInstruction::Ellipse( + Pos2::new(0.78, 0.5), + BUBBLE_R, + BUBBLE_R * 4. / 3., + ), + ] +}; + +pub const NOR_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { + const OFFSET_Y: f32 = 0.0; + const MID_OFFSET_Y: f32 = 0.1; + const ARC_START_X: f32 = 0.1; + const ARC_END_X: f32 = 0.715; + const BUBBLE_R: f32 = 0.5 / 9.; + [ + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, OFFSET_Y), + Pos2::new(ARC_START_X, OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, OFFSET_Y), + Pos2::new(0.55, MID_OFFSET_Y * 0.3), + Pos2::new(0.6, MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(0.55, 1.0 - MID_OFFSET_Y * 0.3), + Pos2::new(0.6, 1.0 - MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::Ellipse( + Pos2::new(0.78, 0.5), + BUBBLE_R, + BUBBLE_R * 4. / 3., + ), + ] +}; + +pub const XOR_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { + const OFFSET_Y: f32 = 0.0; + const MID_OFFSET_Y: f32 = 0.1; + const ARC_START_X: f32 = 0.1; + const DOUBLE_STROKE_OFFSET: f32 = 0.09; + const ARC_END_X: f32 = 0.875; + [ + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, OFFSET_Y), + Pos2::new(ARC_START_X, OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET + 0.3, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET + 0.3, OFFSET_Y), + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, OFFSET_Y), + Pos2::new(0.55, MID_OFFSET_Y * 0.3), + Pos2::new(0.7, MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, 1.0 - OFFSET_Y), + Pos2::new(0.55, 1.0 - MID_OFFSET_Y * 0.3), + Pos2::new(0.7, 1.0 - MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + ] +}; + +pub const XNOR_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 5] = { + const OFFSET_Y: f32 = 0.0; + const MID_OFFSET_Y: f32 = 0.1; + const ARC_START_X: f32 = 0.1; + const DOUBLE_STROKE_OFFSET: f32 = 0.09; + const ARC_END_X: f32 = 0.715; + const BUBBLE_R: f32 = 0.5 / 9.; + [ + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + 0.3, OFFSET_Y), + Pos2::new(ARC_START_X, OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET + 0.3, 1.0 - OFFSET_Y), + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET + 0.3, OFFSET_Y), + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, OFFSET_Y), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, OFFSET_Y), + Pos2::new(0.55, MID_OFFSET_Y * 0.3), + Pos2::new(0.6, MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::CubicBezierCurve([ + Pos2::new(ARC_START_X + DOUBLE_STROKE_OFFSET, 1.0 - OFFSET_Y), + Pos2::new(0.55, 1.0 - MID_OFFSET_Y * 0.3), + Pos2::new(0.6, 1.0 - MID_OFFSET_Y), + Pos2::new(ARC_END_X, 0.5), + ]), + DrawInstruction::Ellipse( + Pos2::new(0.78, 0.5), + BUBBLE_R, + BUBBLE_R * 4. / 3., + ), + ] +}; + pub const NOT_GATE_DRAW_INSTRUCTIONS: [DrawInstruction; 4] = { const OFFSET_Y: f32 = 0.1; const ARC_START_X: f32 = 1. / 6.; diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index a821922..31b8c1e 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -99,6 +99,10 @@ impl ComponentData { match gate_type { GateType::And => &draw::AND_GATE_DRAW_INSTRUCTIONS, GateType::Or => &draw::OR_GATE_DRAW_INSTRUCTIONS, + GateType::Nand => &draw::NAND_GATE_DRAW_INSTRUCTIONS, + GateType::Nor => &draw::NOR_GATE_DRAW_INSTRUCTIONS, + GateType::Xor => &draw::XOR_GATE_DRAW_INSTRUCTIONS, + GateType::Xnor => &draw::XNOR_GATE_DRAW_INSTRUCTIONS, GateType::Not => &draw::NOT_GATE_DRAW_INSTRUCTIONS, _ => &draw::UNIMPLEMENTED_DRAW_INSTRUCTIONS, } @@ -202,7 +206,7 @@ impl ComponentSystem { orientation: Orientation::Zero, label: String::new(), data: ComponentData::Gate { - gate_type: GateType::Not, + gate_type: GateType::Xnor, bitsize: 32, }, }); From 8725d0f5164e5aea28d6bb44bed6273ac44091c4 Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Sat, 25 Oct 2025 18:45:45 -0400 Subject: [PATCH 14/15] add gates with multiple inputs --- src/frontend/canvas/components/mod.rs | 38 +++++++++----------- src/frontend/canvas/grid/instanced_drawer.rs | 2 -- src/frontend/canvas/input.rs | 2 +- src/frontend/gui/component_utils.rs | 11 ++++-- src/frontend/gui/mod.rs | 17 +++++++-- src/frontend/main.rs | 4 +-- 6 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index 31b8c1e..a233547 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -3,7 +3,7 @@ use egui::ahash::HashSet; use egui_macroquad::macroquad::prelude::*; use uuid::Uuid; -use crate::{canvas::{camera::GridCamera, components::draw::AND_GATE_DRAW_INSTRUCTIONS}, gui::component_utils::{macroquad_draw_curve, DrawInstruction, GuiComponentType}}; +use crate::{canvas::{camera::GridCamera}, gui::component_utils::{macroquad_draw_curve, DrawInstruction}}; mod draw; @@ -15,7 +15,6 @@ pub enum GateType { Nor, Xor, Xnor, - Not, } impl GateType { @@ -27,7 +26,6 @@ impl GateType { Self::Nor => "NOR", Self::Xor => "XOR", Self::Xnor => "XNOR", - Self::Not => "NOT", } } } @@ -36,6 +34,10 @@ pub enum ComponentData { Gate { gate_type: GateType, bitsize: u8, + num_inputs: u8, + }, + NotGate { + bitsize: u8, }, Mux { bitsize: u8, @@ -45,33 +47,27 @@ pub enum ComponentData { impl ComponentData { pub fn get_size(&self) -> (i32, i32) { match self { - Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { - (3, 1) - } else { - (4, 3) - }, + Self::Gate { .. } => (4, 3), + Self::NotGate { .. } => (3,1), Self::Mux { .. } => todo!(), } } pub fn get_input_offsets(&self) -> Vec<(i32, i32)> { match self { - Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { - vec![(0, 0)] - } else { - vec![(0, 0), (0, 2)] - }, + Self::Gate { num_inputs, .. } => Vec::from_iter( + (0..*num_inputs) + .map(|x| (0, 2 * (x as i32) - (*num_inputs as i32 - 2))) + ), + Self::NotGate { .. } => vec![(0, 0)], Self::Mux { .. } => todo!(), } } pub fn get_output_offsets(&self) -> Vec<(i32, i32)> { match self { - Self::Gate { gate_type, .. } => if *gate_type == GateType::Not { - vec![(2, 0)] - } else { - vec![(3, 1)] - } + Self::Gate { .. } => vec![(3, 1)], + Self::NotGate { .. } => vec![(2, 0)], Self::Mux { .. } => todo!(), } } @@ -86,9 +82,9 @@ impl ComponentData { GateType::Nor => "NOR gate", GateType::Xor => "XOR gate", GateType::Xnor => "XNOR gate", - GateType::Not => "NOT gate", } } + Self::NotGate { .. } => "NOT gate", Self::Mux { .. } => "Multiplexer", } } @@ -103,10 +99,9 @@ impl ComponentData { GateType::Nor => &draw::NOR_GATE_DRAW_INSTRUCTIONS, GateType::Xor => &draw::XOR_GATE_DRAW_INSTRUCTIONS, GateType::Xnor => &draw::XNOR_GATE_DRAW_INSTRUCTIONS, - GateType::Not => &draw::NOT_GATE_DRAW_INSTRUCTIONS, - _ => &draw::UNIMPLEMENTED_DRAW_INSTRUCTIONS, } } + Self::NotGate { .. } => &draw::NOT_GATE_DRAW_INSTRUCTIONS, Self::Mux { .. } => todo!(), } } @@ -208,6 +203,7 @@ impl ComponentSystem { data: ComponentData::Gate { gate_type: GateType::Xnor, bitsize: 32, + num_inputs: 2, }, }); a diff --git a/src/frontend/canvas/grid/instanced_drawer.rs b/src/frontend/canvas/grid/instanced_drawer.rs index acde20b..6d8f644 100644 --- a/src/frontend/canvas/grid/instanced_drawer.rs +++ b/src/frontend/canvas/grid/instanced_drawer.rs @@ -133,9 +133,7 @@ impl InstancedDrawer { fn generate_grid_lines(&self, camera: &GridCamera) -> Vec { let (view_min, view_max) = camera.get_view_bounds(); - let start_x = view_min.x.floor(); let end_x = view_max.x.ceil(); - let start_y = view_min.y.floor(); let end_y = view_max.y.ceil(); let mut instances = Vec::new(); diff --git a/src/frontend/canvas/input.rs b/src/frontend/canvas/input.rs index 1939481..f468c20 100644 --- a/src/frontend/canvas/input.rs +++ b/src/frontend/canvas/input.rs @@ -78,7 +78,7 @@ impl CanvasInput { // } } - pub fn draw_selection(&self, camera: &GridCamera) { + pub fn draw_selection(&self) { if let Some((c1, c2)) = self.in_progress_selection { draw_rectangle( c1.0 as f32, diff --git a/src/frontend/gui/component_utils.rs b/src/frontend/gui/component_utils.rs index a1de8d1..00e3f63 100644 --- a/src/frontend/gui/component_utils.rs +++ b/src/frontend/gui/component_utils.rs @@ -1,4 +1,4 @@ -use crate::canvas::components::{rotate_point_ccw, ComponentData, GateType, Orientation}; +use crate::canvas::components::{ComponentData, GateType, Orientation}; use epaint::{Pos2, Rect}; use egui_macroquad::macroquad::prelude::*; @@ -110,29 +110,34 @@ impl GuiComponentType { Self::AndGate => ComponentData::Gate { gate_type: GateType::And, bitsize: 1, + num_inputs: 2, }, Self::OrGate => ComponentData::Gate { gate_type: GateType::Or, bitsize: 1, + num_inputs: 2, }, Self::NandGate => ComponentData::Gate { gate_type: GateType::Nand, bitsize: 1, + num_inputs: 2, }, Self::NorGate => ComponentData::Gate { gate_type: GateType::Nor, bitsize: 1, + num_inputs: 2, }, Self::XorGate => ComponentData::Gate { gate_type: GateType::Xor, bitsize: 1, + num_inputs: 2, }, Self::XnorGate => ComponentData::Gate { gate_type: GateType::Xnor, bitsize: 1, + num_inputs: 2, }, - Self::NotGate => ComponentData::Gate { - gate_type: GateType::Not, + Self::NotGate => ComponentData::NotGate { bitsize: 1, }, } diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index d4e3bad..b6ca8c7 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -122,7 +122,6 @@ impl App { ui.selectable_value(data, GateType::Nor, "NOR"); ui.selectable_value(data, GateType::Xor, "XOR"); ui.selectable_value(data, GateType::Xnor, "XNOR"); - ui.selectable_value(data, GateType::Not, "NOT"); }); } fn bitsize_dropdown(ui: &mut Ui, data: &mut u8) { @@ -134,16 +133,30 @@ impl App { } }); } + fn num_inputs_dropdown(ui: &mut Ui, data: &mut u8) { + ComboBox::from_label("# inputs") + .selected_text(data.to_string()) + .show_ui(ui, |ui| { + for i in 1..=32 { + ui.selectable_value(data, i, i.to_string()); + } + }); + } if selection.len() == 1 && let Some(c) = selection.get_mut(0) { ui.label(c.data.get_name()); orientation_dropdown(ui, &mut c.orientation); match (*c).data { ComponentData::Gate { ref mut gate_type, - ref mut bitsize + ref mut bitsize, + ref mut num_inputs, } => { gate_type_dropdown(ui, gate_type); bitsize_dropdown(ui, bitsize); + num_inputs_dropdown(ui, num_inputs); + } + ComponentData::NotGate { ref mut bitsize } => { + bitsize_dropdown(ui, bitsize); } ComponentData::Mux { ref mut bitsize diff --git a/src/frontend/main.rs b/src/frontend/main.rs index ac4486c..5b36a82 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -1,7 +1,7 @@ use egui_macroquad::macroquad::prelude::*; use crate::canvas::camera::GridCamera; -use crate::canvas::components::{ComponentData, ComponentSystem}; +use crate::canvas::components::ComponentSystem; use crate::canvas::grid::GridDrawer; use crate::canvas::input::{CanvasInput, CanvasInputState}; use crate::canvas::wiring::WireSystem; @@ -96,7 +96,7 @@ async fn main() { } else if input.state == CanvasInputState::Idle { let draw_selection = cs.update_selection(input.in_progress_selection, input.selection); if draw_selection { - input.draw_selection(&camera); + input.draw_selection(); } } From 4c9dbab727525a4565784193110412a613081dce Mon Sep 17 00:00:00 2001 From: Ambareesh Shyam Sundar Date: Sat, 25 Oct 2025 20:29:45 -0400 Subject: [PATCH 15/15] fix wasm build --- Cargo.lock | 54 +-------------------------- Cargo.toml | 1 - index.html | 2 +- src/frontend/canvas/components/mod.rs | 37 ++++++++++++------ src/frontend/main.rs | 11 +++++- wasm_build.sh | 2 +- 6 files changed, 39 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a879a35..c0e660f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,7 +169,6 @@ dependencies = [ "epaint", "macroquad", "miniquad", - "uuid", ] [[package]] @@ -305,7 +304,7 @@ dependencies = [ "bytemuck", "copypasta", "egui", - "getrandom 0.2.16", + "getrandom", "miniquad", "quad-rand", "quad-url", @@ -421,18 +420,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - [[package]] name = "glam" version = "0.27.0" @@ -899,12 +886,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "redox_syscall" version = "0.5.17" @@ -940,12 +921,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "sapp-jsutils" version = "0.1.7" @@ -1082,17 +1057,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "version_check" version = "0.9.5" @@ -1105,15 +1069,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1122,7 +1077,6 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", - "rustversion", "wasm-bindgen-macro", ] @@ -1527,12 +1481,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - [[package]] name = "x11-clipboard" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 3864bbf..a35075c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ egui-macroquad = "0.17.3" epaint = "0.31.1" macroquad = "0.4.14" miniquad = "0.4.8" -uuid = { version = "1.18.1", features = ["v4", "js"] } [lib] name = "backend" diff --git a/index.html b/index.html index 17cefec..238a296 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,7 @@ - + diff --git a/src/frontend/canvas/components/mod.rs b/src/frontend/canvas/components/mod.rs index a233547..274bf15 100644 --- a/src/frontend/canvas/components/mod.rs +++ b/src/frontend/canvas/components/mod.rs @@ -1,7 +1,6 @@ -use std::{collections::HashMap, ops::Neg}; +use std::{collections::HashMap, ops::{Neg, Shl}}; use egui::ahash::HashSet; -use egui_macroquad::macroquad::prelude::*; -use uuid::Uuid; +use egui_macroquad::macroquad::{prelude::*, rand::{srand, rand}}; use crate::{canvas::{camera::GridCamera}, gui::component_utils::{macroquad_draw_curve, DrawInstruction}}; @@ -55,10 +54,22 @@ impl ComponentData { pub fn get_input_offsets(&self) -> Vec<(i32, i32)> { match self { - Self::Gate { num_inputs, .. } => Vec::from_iter( - (0..*num_inputs) - .map(|x| (0, 2 * (x as i32) - (*num_inputs as i32 - 2))) - ), + Self::Gate { num_inputs, .. } => if *num_inputs == 2 { + vec![(0, 0), (0, 2)] + } else if num_inputs % 2 != 0 { + Vec::from_iter( + (0..*num_inputs) + .map(|x| (0, (x as i32) - (*num_inputs as i32 / 2 - 1))) + ) + } else { + let top = (0..*num_inputs / 2) + .map(|x| (0, (x as i32) + 2)); + let bot = (0..*num_inputs / 2) + .map(|x| (0, -(x as i32))); + Vec::from_iter( + top.chain(bot) + ) + }, Self::NotGate { .. } => vec![(0, 0)], Self::Mux { .. } => todo!(), } @@ -138,7 +149,7 @@ pub fn rotate_point_ccw>((x, y): (T, T), orientation: Orienta } pub struct Component { - uuid: Uuid, + uuid: u64, pub orientation: Orientation, pub label: String, pub data: ComponentData @@ -188,16 +199,20 @@ impl Component { #[derive(Default)] pub struct ComponentSystem { components: HashMap<(i32, i32), Component>, - selection: HashSet, + selection: HashSet, drag_delta: (i32, i32), drag_handled: bool, } +fn random_u64() -> u64 { + ((rand() as u64) << 32) + rand() as u64 +} + impl ComponentSystem { pub fn new() -> Self { let mut a = Self::default(); a.components.insert((0, 0), Component { - uuid: Uuid::new_v4(), + uuid: random_u64(), orientation: Orientation::Zero, label: String::new(), data: ComponentData::Gate { @@ -219,7 +234,7 @@ impl ComponentSystem { self.components.insert( end_pos, Component { - uuid: Uuid::new_v4(), + uuid: random_u64(), orientation: Orientation::Zero, label: String::new(), data: selected_component, diff --git a/src/frontend/main.rs b/src/frontend/main.rs index 5b36a82..f7ce85b 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -1,4 +1,6 @@ -use egui_macroquad::macroquad::prelude::*; +use std::time; + +use egui_macroquad::macroquad::{prelude::*, rand::srand}; use crate::canvas::camera::GridCamera; use crate::canvas::components::ComponentSystem; @@ -24,6 +26,13 @@ fn window_conf() -> Conf { #[macroquad::main(window_conf)] async fn main() { + // let millis = time::SystemTime::now() + // .duration_since(time::UNIX_EPOCH) + // .expect("Time went backwards") + // .as_millis() as u64; + srand(1); + + let args: Vec = std::env::args().collect(); let mut enable_camera_debug = false; let mut enable_profiler_debug = false; diff --git a/wasm_build.sh b/wasm_build.sh index c372f14..7b40bb1 100644 --- a/wasm_build.sh +++ b/wasm_build.sh @@ -1,3 +1,3 @@ cargo build --target wasm32-unknown-unknown; -cp target/wasm32-unknown-unknown/debug/circuitsim.wasm .; +cp target/wasm32-unknown-unknown/debug/frontend.wasm .; python -m http.server 8000;