diff --git a/Cargo.lock b/Cargo.lock index efbd764..70e9652 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5029,7 +5029,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "polyblade" -version = "0.4.2" +version = "0.4.3" dependencies = [ "bytemuck", "cfg-if", @@ -5601,9 +5601,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "once_cell", "ring", @@ -6476,7 +6476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index c27e388..41d3216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "polyblade" -version = "0.4.2" +version = "0.4.3" edition = "2024" description = "Make shapes dance." readme = "README.md" diff --git a/assets/main.css b/assets/main.css index 12d9529..11c3b2d 100644 --- a/assets/main.css +++ b/assets/main.css @@ -1,17 +1,8 @@ -body { - background-color: #0f1116; - color: #ffffff; - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; - margin: 20px; -} - -#header { - max-width: 1200px; -} - body { background: #0f172a; color: #d1d5db; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + margin: 0; } .menu-bar { @@ -24,6 +15,13 @@ body { .menu-group { position: relative; + z-index: 3; +} + +.menu-backdrop { + position: fixed; + inset: 0; + z-index: 2; } .menu-group.top-right { @@ -42,8 +40,10 @@ body { cursor: pointer; } -.menu-btn:hover { - background: #4b5563; +@media (hover: hover) { + .menu-btn:hover { + background: #4b5563; + } } .dropdown { @@ -56,7 +56,7 @@ body { display: none; } -.menu-group:hover .dropdown { +.dropdown.open { display: block; } @@ -66,8 +66,10 @@ body { position: relative; } -.item:hover { - background: #4b5563; +@media (hover: hover) { + .item:hover { + background: #4b5563; + } } .shortcut { @@ -85,8 +87,10 @@ body { display: none; } -.item:hover .submenu { - display: block; +@media (hover: hover) { + .item:hover .submenu { + display: block; + } } .has-sub::after { @@ -104,7 +108,6 @@ canvas, position: absolute; left: 0; top: 0; - background_color: #ff00ff; } #wgpu-canvas { @@ -118,6 +121,7 @@ canvas, .main-div { margin: 0; height: 100vh; + height: 100dvh; display: flex; flex-direction: column; } @@ -136,3 +140,41 @@ canvas, flex: 1; touch-action: none; } + +/* On phones the open dropdown becomes a bottom sheet and submenus flatten inline. */ +@media (max-width: 640px) { + .menu-group .dropdown, + .menu-group.top-right .dropdown { + position: fixed; + left: 0; + right: 0; + top: auto; + bottom: 0; + max-height: 45vh; + overflow-y: auto; + min-width: 0; + background: rgb(55 65 81 / 0.65); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + } + + .submenu { + position: static; + display: block; + min-width: 0; + padding-left: 16px; + background: none; + } + + .has-sub::after { + content: "▾"; + } + + .shortcut { + display: none; + } + + .item { + padding: 12px 16px; + } +} diff --git a/src/components/menu.rs b/src/components/menu.rs index af14d15..4057ef1 100644 --- a/src/components/menu.rs +++ b/src/components/menu.rs @@ -56,7 +56,7 @@ fn SizedPresetMenu(name: String, make: Callback) -> Elemen /// Polls the backend-published Schlegel face-type options, rendering one button per type. /// Only rendered when Schlegel mode is on. #[component] -fn SchlegelFaceMenu() -> Element { +fn SchlegelFaceMenu(open: Signal>) -> Element { let mut options = use_signal(Vec::::new); use_future(move || async move { @@ -77,8 +77,8 @@ fn SchlegelFaceMenu() -> Element { rsx! { div { class: "menu-group top-right", - div { class: "menu-btn", "Schlegel Face" } - div { class: "dropdown", + div { class: "menu-btn", onclick: move |_| toggle(open, "schlegel"), "Schlegel Face" } + div { class: if open() == Some("schlegel") { "dropdown open" } else { "dropdown" }, for option in options() { div { class: "item", @@ -98,12 +98,26 @@ fn SchlegelFaceMenu() -> Element { } } +/// Toggles which top-level menu is open, closing it if already open. +fn toggle(mut open: Signal>, id: &'static str) { + let next = if *open.peek() == Some(id) { + None + } else { + Some(id) + }; + open.set(next); +} + #[component] pub fn MenuBar(mut schlegel: Signal) -> Element { + let mut open = use_signal(|| None::<&'static str>); rsx! { + if open().is_some() { + div { class: "menu-backdrop", onclick: move |_| open.set(None) } + } div { class: "menu-group", - div { class: "menu-btn", "Preset" } - div { class: "dropdown", + div { class: "menu-btn", onclick: move |_| toggle(open, "preset"), "Preset" } + div { class: if open() == Some("preset") { "dropdown open" } else { "dropdown" }, div { class: "item has-sub", "Platonic solids" div { class: "submenu", @@ -123,8 +137,8 @@ pub fn MenuBar(mut schlegel: Signal) -> Element { } } div { class: "menu-group", - div { class: "menu-btn", "Conway" } - div { class: "dropdown", + div { class: "menu-btn", onclick: move |_| toggle(open, "conway"), "Conway" } + div { class: if open() == Some("conway") { "dropdown open" } else { "dropdown" }, for op in ConwayMessage::iter() { div { class: "item", @@ -138,8 +152,8 @@ pub fn MenuBar(mut schlegel: Signal) -> Element { } } div { class: "menu-group", - div { class: "menu-btn", "Render" } - div { class: "dropdown", + div { class: "menu-btn", onclick: move |_| toggle(open, "render"), "Render" } + div { class: if open() == Some("render") { "dropdown open" } else { "dropdown" }, div { class: "item", onclick: move |_| { @@ -153,7 +167,7 @@ pub fn MenuBar(mut schlegel: Signal) -> Element { } } if schlegel() { - SchlegelFaceMenu {} + SchlegelFaceMenu { open } } } }