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/draw.rs b/src/frontend/canvas/components/draw.rs new file mode 100644 index 0000000..c682d5c --- /dev/null +++ b/src/frontend/canvas/components/draw.rs @@ -0,0 +1,217 @@ +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.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.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.; + 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 new file mode 100644 index 0000000..274bf15 --- /dev/null +++ b/src/frontend/canvas/components/mod.rs @@ -0,0 +1,419 @@ +use std::{collections::HashMap, ops::{Neg, Shl}}; +use egui::ahash::HashSet; +use egui_macroquad::macroquad::{prelude::*, rand::{srand, rand}}; + +use crate::{canvas::{camera::GridCamera}, gui::component_utils::{macroquad_draw_curve, DrawInstruction}}; + +mod draw; + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum GateType { + And, + Or, + Nand, + Nor, + Xor, + Xnor, +} + +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", + } + } +} + +pub enum ComponentData { + Gate { + gate_type: GateType, + bitsize: u8, + num_inputs: u8, + }, + NotGate { + bitsize: u8, + }, + Mux { + bitsize: u8, + }, +} + +impl ComponentData { + pub fn get_size(&self) -> (i32, i32) { + match self { + Self::Gate { .. } => (4, 3), + Self::NotGate { .. } => (3,1), + Self::Mux { .. } => todo!(), + } + } + + pub fn get_input_offsets(&self) -> Vec<(i32, i32)> { + match self { + 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!(), + } + } + + pub fn get_output_offsets(&self) -> Vec<(i32, i32)> { + match self { + Self::Gate { .. } => vec![(3, 1)], + Self::NotGate { .. } => vec![(2, 0)], + Self::Mux { .. } => todo!(), + } + } + + 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", + } + } + Self::NotGate { .. } => "NOT gate", + 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::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, + } + } + Self::NotGate { .. } => &draw::NOT_GATE_DRAW_INSTRUCTIONS, + Self::Mux { .. } => todo!(), + } + } +} + +/// 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: u64, + 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); + let sel_max_x = x1.max(x2); + let sel_min_y = y1.min(y2); + let sel_max_y = y1.max(y2); + + 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 + && sel_max_x > comp_min_x + && sel_min_y <= comp_max_y + && sel_max_y > comp_min_y; + + intersects + } + + 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), Component>, + 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: random_u64(), + orientation: Orientation::Zero, + label: String::new(), + data: ComponentData::Gate { + gate_type: GateType::Xnor, + bitsize: 32, + num_inputs: 2, + }, + }); + a + } + + 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 { + uuid: random_u64(), + orientation: Orientation::Zero, + label: String::new(), + data: selected_component, + }, + ); + } + } + + // 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(|_, component| !self.selection.contains(&component.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); + 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, + y as f32, + selected_component.get_size().0 as f32, + selected_component.get_size().1 as f32, + ORANGE.with_alpha(0.2), + ); + } + + pub fn draw_components(&self, camera: &GridCamera) { + let (view_min, view_max) = camera.get_view_bounds(); + // 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 f32) < view_min.y + || (*y as f32) > view_max.y + + { + continue; + } + let shifted_x = *x as f32; + let shifted_y = *y as f32; + 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(&component.uuid) { + self.drag_delta.1 as f32 + } else { + 0. + }; + 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, + rot_w as f32, + rot_h as f32, + ORANGE.with_alpha(0.2), + ); + } + 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 + }; + for i in component.data.get_draw_data() { + 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.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, + target_y + dy, + PORT_SIZE, + BLUE + ); + } + 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, + PORT_SIZE, + BLUE + ); + } + } + } + + /// 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 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(); + for (coords, component) in self.components.iter() { + if self.selection.contains(&component.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); + self.drag_handled = true; + } else if !self.drag_handled { + for (&(comp_x, comp_y), component) in self.components.iter_mut() { + if component.intersects_selection(comp_x, comp_y, c) { + self.selection.insert(component.uuid); + } + } + } + } else if let Some((start, end)) = in_progress_selection { + 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, + ); + return false; + } + } + self.selection.clear(); + } else { + self.selection.clear(); + self.drag_handled = false; + } + return true; + } + + pub fn get_selection_mut( + &mut self, + ) -> Vec<&mut Component> { + let mut selected = Vec::new(); + + for (_, component) in self.components.iter_mut() { + if self.selection.contains(&component.uuid) { + selected.push(component); + } + } + + selected + } +} 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 new file mode 100644 index 0000000..f468c20 --- /dev/null +++ b/src/frontend/canvas/input.rs @@ -0,0 +1,94 @@ +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, + Idle, +} + +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 { + state: CanvasInputState::Idle, + selection: None, + in_progress_selection: None, + } + } + + pub fn handle_input(&mut self, camera: &GridCamera, egui_wants_ptr: bool) { + if is_key_down(KeyCode::Escape) { + 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.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) { + if !(is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift)) { + self.selection = None; + } + 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) { + 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 { + return + }; + } +} 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/mod.rs b/src/frontend/canvas/wiring/mod.rs index 1e71a79..79c1553 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) { @@ -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/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..891e113 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, + pos2_with_rect, DrawInstruction, GuiComponentType }; 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: GuiComponentType, + input_state: &mut CanvasInputState ) -> Response { let (rect, response) = ui.allocate_exact_size(size, Sense::all()); @@ -42,6 +44,7 @@ impl App { ); painter.add(shape); } + DrawInstruction::Ellipse(_, _, _) => todo!() } } @@ -54,7 +57,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..00e3f63 100644 --- a/src/frontend/gui/component_utils.rs +++ b/src/frontend/gui/component_utils.rs @@ -1,8 +1,89 @@ -use epaint::Pos2; +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]), + Ellipse(Pos2, f32, f32), +} + +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; + } + } + 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 { + 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 { @@ -13,7 +94,7 @@ pub fn pos2_with_rect(pos: &Pos2, rect: egui::Rect) -> Pos2 { } #[derive(PartialEq, Eq, Copy, Clone)] -pub enum CircuitComponentType { +pub enum GuiComponentType { AndGate, OrGate, NandGate, @@ -23,7 +104,45 @@ pub enum CircuitComponentType { NotGate, } -impl CircuitComponentType { +impl GuiComponentType { + pub fn to_component_data(&self) -> ComponentData { + match self { + 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::NotGate { + bitsize: 1, + }, + } + } + pub fn get_label(&self) -> &'static str { match self { Self::AndGate => "AND Gate", @@ -37,14 +156,18 @@ impl CircuitComponentType { } 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(0.0, 1.0 - OFFSET_Y), + 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([ @@ -93,3 +216,5 @@ impl CircuitComponentType { } } } + + diff --git a/src/frontend/gui/mod.rs b/src/frontend/gui/mod.rs index d155b3a..b6ca8c7 100644 --- a/src/frontend/gui/mod.rs +++ b/src/frontend/gui/mod.rs @@ -1,17 +1,20 @@ -use component_utils::CircuitComponentType; use egui_macroquad::egui; use egui_macroquad::macroquad::prelude::*; +use crate::canvas::components::{Component, ComponentData, GateType, Orientation}; +use crate::canvas::input::CanvasInputState; +use crate::gui::component_utils::GuiComponentType; + 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 +31,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, selection: &mut [&mut Component]) { self.hovered_hotbar_button = None; self.dragged_component = None; use egui::*; @@ -59,9 +67,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)); }); }); @@ -70,23 +80,90 @@ 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 = [ - CircuitComponentType::AndGate, - CircuitComponentType::OrGate, - CircuitComponentType::NandGate, - CircuitComponentType::NorGate, - CircuitComponentType::XorGate, - CircuitComponentType::XnorGate, - CircuitComponentType::NotGate, - ]; - for gate in gates { - self.circuit_component_button( - ui, - egui::Vec2::new(ui.available_size().x, 60.0), - gate, - ); + 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(); + 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()) + .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"); + }); + } + 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()); + } + }); + } + 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 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 + } => { + bitsize_dropdown(ui, bitsize); + } + } } }); }); @@ -116,11 +193,7 @@ impl App { ); }); - SidePanel::right("Right").show(ctx, |ui| { - 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..e2777e1 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) { @@ -59,15 +60,15 @@ impl App { ); painter.add(shape); } + DrawInstruction::Ellipse(_, _, _) => todo!(), } } } 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 +100,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..f7ce85b 100644 --- a/src/frontend/main.rs +++ b/src/frontend/main.rs @@ -1,7 +1,11 @@ -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; use crate::canvas::grid::GridDrawer; +use crate::canvas::input::{CanvasInput, CanvasInputState}; use crate::canvas::wiring::WireSystem; use crate::gui::App; @@ -10,8 +14,25 @@ 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 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; @@ -26,7 +47,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 = CanvasInput::new(); let mut ws = WireSystem::new(); + let mut cs = ComponentSystem::new(); request_new_screen_size(1280.0, 720.0); next_frame().await; @@ -34,37 +57,63 @@ 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); - ws.handle_input(&camera); + if input.state == CanvasInputState::Component { + cs.handle_input( + &camera, + gui.get_selected_component().unwrap().to_component_data() + ); + } else if input.state == CanvasInputState::Wire { + ws.handle_input(&camera); + } else { + cs.handle_delete(); + } egui_macroquad::ui(|ctx| { - gui.update(ctx); + gui.update(ctx, &mut input.state, &mut cs.get_selection_mut()); 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); - ws.draw_preview(&camera); + 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_new_component_preview( + &camera, + gui.get_selected_component().unwrap().to_component_data() + ); + } else if input.state == CanvasInputState::Idle { + let draw_selection = cs.update_selection(input.in_progress_selection, input.selection); + if draw_selection { + input.draw_selection(); + } + } gl_use_default_material(); set_default_camera(); egui_macroquad::draw(); } + input.handle_input(&camera, egui_wants_ptr); next_frame().await; } 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;