diff --git a/src/components/atoms/button.rs b/src/components/atoms/button.rs index 2e54634..fb5991b 100644 --- a/src/components/atoms/button.rs +++ b/src/components/atoms/button.rs @@ -24,6 +24,10 @@ pub fn Button( /// If the button is an overlay button. #[prop(optional)] overlay: bool, + /// If the button has been selected. + #[prop(optional)] + #[prop(into)] + active: Signal, ) -> impl IntoView { let color = if danger { "red" @@ -38,7 +42,7 @@ pub fn Button( let base_active = base + 200; let dark = base + 200; let dark_hover = dark + 100; - let dark_active = dark + 200; + let dark_active = if dark >= 800 { 950 } else { dark + 200 }; let mut class = "inline-block px-4 \ py-1.5 text-center uppercase \ @@ -60,32 +64,39 @@ pub fn Button( hover:border-{color}-{base_hover} \ active:text-{color}-{base_active} \ active:border-{color}-{base_active} \ + focus:text-{color}-{base_active} \ + focus:border-{color}-{base_active} \ dark:text-{color}-{dark} \ dark:border-{color}-{dark} \ dark:hover:text-{color}-{dark_hover} \ dark:hover:border-{color}-{dark_hover} \ dark:active:text-{color}-{dark_active} \ - dark:active:border-{color}-{dark_active}" + dark:active:border-{color}-{dark_active} \ + dark:focus:text-{color}-{dark_active} \ + dark:focus:border-{color}-{dark_active}" ); } else { class += &format!( " text-white bg-{color}-{base} \ hover:bg-{color}-{base_hover} \ active:bg-{color}-{base_active} \ + focus:bg-{color}-{base_active} \ dark:bg-{color}-{dark} \ dark:hover:bg-{color}-{dark_hover} \ - dark:active:bg-{color}-{dark_active}" + dark:active:bg-{color}-{dark_active} \ + dark:focus:bg-{color}-{dark_active}" ); } class = class.replace("1000", "950"); view! { - + } } diff --git a/src/components/molecules/canvas.rs b/src/components/molecules/canvas.rs index 6951b37..ff1ef6c 100644 --- a/src/components/molecules/canvas.rs +++ b/src/components/molecules/canvas.rs @@ -17,6 +17,7 @@ use wasm_bindgen::{ use crate::{ components::{ + state::RemoveType, CanvasState, MapState, }, @@ -155,6 +156,23 @@ fn on_mouse_down(map_state: &mut MapState, ev: &UiEvent) { return; } + // Handle a click while having a remove operation selected + if let Some(remove_type) = map_state.get_selected_remove() { + if let Some(station) = map.station_at_node(mouse_pos) { + match remove_type { + RemoveType::Station => { + map.remove_station(station); + }, + RemoveType::Line => { + // Not currently supported + }, + } + map_state.set_map(map); + } + map_state.clear_selected_remove(); + return; + } + if let Some(mut selected_station) = map .station_at_node(mouse_pos) .and_then(|s| map.get_station(s)) diff --git a/src/components/organisms/sidebar.rs b/src/components/organisms/sidebar.rs index fae2419..1692443 100644 --- a/src/components/organisms/sidebar.rs +++ b/src/components/organisms/sidebar.rs @@ -10,6 +10,7 @@ use crate::{ ButtonProps, NumberInput, }, + state::RemoveType, MapState, }, models::{ @@ -38,6 +39,18 @@ pub fn Sidebar() -> impl IntoView { state.set_selected_line(line) }) }; + let remove_station = move |_| { + map_state.update(|state| { + state.set_selected_remove(RemoveType::Station); + }); + }; + let remove_station_selected = move || { + map_state + .get() + .get_selected_remove() + == Some(RemoveType::Station) + }; + let update_square_size = move |mut n: f64| { if n < 1.0 { n = 1.0; @@ -60,13 +73,28 @@ pub fn Sidebar() -> impl IntoView { on_input=update_square_size/> } diff --git a/src/components/state/map.rs b/src/components/state/map.rs index fdcee94..3c69a91 100644 --- a/src/components/state/map.rs +++ b/src/components/state/map.rs @@ -15,6 +15,13 @@ use crate::{ }, }; +/// The type of remove operation that is currently selected. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RemoveType { + Station, + Line, +} + /// Holds all the state of the current map and canvas. #[derive(Clone, Debug)] pub struct MapState { @@ -24,6 +31,8 @@ pub struct MapState { selected_station: Option, /// The currently selected [`Line`]. selected_line: Option, + /// The type of remove operation that is currently selected. + selected_remove: Option, /// The state of the canvas. canvas: CanvasState, } @@ -36,6 +45,7 @@ impl MapState { map, selected_station: None, selected_line: None, + selected_remove: None, canvas: CanvasState::default(), } } @@ -71,6 +81,21 @@ impl MapState { self.selected_station = None; } + /// A getter method for the selected remove operation. + pub fn get_selected_remove(&self) -> Option { + self.selected_remove + } + + /// A setter method for the selected remove operation. + pub fn set_selected_remove(&mut self, operation: RemoveType) { + self.selected_remove = Some(operation); + } + + /// Set the selected remove operation to None. + pub fn clear_selected_remove(&mut self) { + self.selected_remove = None; + } + /// A mutable getter method for the selected line. pub fn get_mut_selected_line(&mut self) -> Option<&mut SelectedLine> { self.selected_line diff --git a/src/components/state/mod.rs b/src/components/state/mod.rs index 80f206f..5af1c81 100644 --- a/src/components/state/mod.rs +++ b/src/components/state/mod.rs @@ -6,7 +6,10 @@ mod canvas; mod map; pub use canvas::CanvasState; -pub use map::MapState; +pub use map::{ + MapState, + RemoveType, +}; use crate::models::Map; diff --git a/src/models/edge.rs b/src/models/edge.rs index c321f53..9ab9641 100644 --- a/src/models/edge.rs +++ b/src/models/edge.rs @@ -223,7 +223,7 @@ impl Edge { let color_offset = if color_count == 1 { 0.0 } else { - ((i as f64) * width + 0.1) - ((color_count as f64 * width + 0.1) / 2.0) + ((i as f64) * width) - ((color_count as f64 * width) / 2.0) + (width / 2.0) }; draw_edge( diff --git a/src/models/line.rs b/src/models/line.rs index d68b627..2df5249 100644 --- a/src/models/line.rs +++ b/src/models/line.rs @@ -138,6 +138,50 @@ impl Line { } } + /// Remove a station from the line. + pub fn remove_station(&mut self, map: &mut Map, station: StationID) { + if let Some(index) = self + .stations + .iter() + .position(|s| s == &station) + { + self.stations + .remove(index); + } + + let mut start = None; + let mut end = None; + let edges = self + .edges + .clone(); + for edge_id in edges { + let edge = map + .get_edge(edge_id) + .expect("removing invalid edge id from line"); + + if edge.get_to() == station { + start = Some(edge.get_from()); + + self.edges + .retain(|e| *e != edge_id); + map.removed_edge(edge_id, self.get_id()); + } else if edge.get_from() == station { + end = Some(edge.get_to()); + + self.edges + .retain(|e| *e != edge_id); + map.removed_edge(edge_id, self.get_id()); + } + } + + if start.is_some() && end.is_some() { + self.add_edge( + map.get_edge_id_between(start.unwrap(), end.unwrap()), + map, + ); + } + } + /// Add an edge that is being used by this line. pub fn add_edge(&mut self, edge_id: EdgeID, map: &mut Map) { let edge = map diff --git a/src/models/map.rs b/src/models/map.rs index 8c3bc01..e22045e 100644 --- a/src/models/map.rs +++ b/src/models/map.rs @@ -137,6 +137,22 @@ impl Map { .insert(station.get_id(), station); } + /// Remove a station from the map. + pub fn remove_station(&mut self, id: StationID) { + let lines: Vec<_> = self + .lines + .values() + .cloned() + .collect(); + for mut line in lines.into_iter() { + line.remove_station(self, id); + self.add_line(line); + } + + self.stations + .remove(&id); + } + /// Add a line to the map. pub fn add_line(&mut self, line: Line) { for edge_id in line.get_edges() { diff --git a/tailwind.config.js b/tailwind.config.js index 565729b..d0ae5f0 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -8,7 +8,7 @@ module.exports = { .flatMap((s) => ["400", "500", "600", "700", "800", "900", "950"].map((n) => `${s}-${n}`) ) - .flatMap((s) => ["", "hover:", "active:"].map((m) => `${m}${s}`)) + .flatMap((s) => ["", "hover:", "active:", "focus:"].map((m) => `${m}${s}`)) .flatMap((s) => ["", "dark:"].map((m) => `${m}${s}`)), theme: { extend: {