From 35386e84f10cddac53988523322185ad8cc60cf4 Mon Sep 17 00:00:00 2001 From: server2 Date: Tue, 13 Oct 2026 06:09:06 +0200 Subject: [PATCH] Maps fit beside the media island The map menus' width follows the monitor: from its right edge to the island's compact pill, less a 16 px gap (the pill's pop is 15 px a side); 822 px on 1920. AREA_H 480, no alternating rings (simulated: no label overlaps up to 8 nodes at this size). The password popup centres on it. --- README.md | 2 +- src/control.rs | 40 +++++++++++++++++++++++++--------------- src/island.rs | 2 +- src/map.rs | 12 +++--------- src/prompt.rs | 3 +-- 5 files changed, 31 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index f07e1a5..fe8a1e4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The panel has: - *Hotspot* (iwd access point): see [Hotspot setup](#hotspot-setup). - *Ethernet*: every real wired card in `/sys/class/net` (a device behind it, not wireless): connected or no cable, and the link speed it negotiated (`speed`, as 1 Gb/s). A click turns the panel into its details (it morphs like into any menu; the back button returns): the interface and driver, status (speed, duplex), every IPv4 and IPv6 address, gateway, DNS servers, MAC, MTU and traffic since boot (`ip -o addr`, `ip -o route`, `/etc/resolv.conf`, sysfs). Addresses, gateway, DNS and MAC have a Copy chip; a click anywhere on such a row copies it (addresses without their prefix). - **Popups:** questions (a password, PIN or passkey), a code to confirm, a saved password or why something failed, in a small card over the menu. Only a popup with a field takes the keyboard (Enter answers, Escape cancels); the others leave keys to other windows until clicked. -- **Menus:** one shape the whole way: the panel fades its content out and takes the menu's size (the Wi-Fi and Bluetooth maps are 920 × 460 px, with at most 8 networks or devices, the strongest first, every other one a little further in so neighbouring labels don't meet), then the menu's content fades in over it from a window of its own (so the maps' animation redraws only that window). The back button returns to the panel. Moving the pointer off both closes the menu where it is: its content fades out and it shrinks straight into the pill, without the panel growing back first. +- **Menus:** one shape the whole way: the panel fades its content out and takes the menu's size (the Wi-Fi and Bluetooth maps reach from the monitor's right edge to the media island's pill, leaving a 16 px gap, room for the pill's pop: 822 × 480 px on a 1920 px monitor; at most 8 networks or devices, the strongest first, so their labels never meet), then the menu's content fades in over it from a window of its own (so the maps' animation redraws only that window). The back button returns to the panel. Moving the pointer off both closes the menu where it is: its content fades out and it shrinks straight into the pill, without the panel growing back first. - **Sound:** the default output's volume (drag or scroll the slider); the value next to the title is the mute button. With `extra_output` in `shell.conf` a second slider follows for the output whose name contains it (here `NR515`: the `[TX-NR515]` receiver on HDMI 1). Clicking the block elsewhere opens the Sound menu: every output with its own slider and mute; click a name to make it the default output. - **Display:** the laptop screen's brightness and the keyboard light (`tpacpi::kbd_backlight`: Off, Low, High), written straight to their sysfs files (a udev rule makes them writable by everyone on this laptop), else through logind, which refuses a shell without a seat (one started over ssh). For external monitors see [External monitor brightness](#external-monitor-brightness). - **Performance:** the firmware profiles in `/sys/firmware/acpi/platform_profile_choices` (Low power, Balanced, Performance), and the battery's state and time left. Picking one sets the platform profile and the CPU governor and energy preference with it: Low power = `powersave` + `power`, Balanced = `powersave` + `balance_performance`, Performance = the `performance` governor. diff --git a/src/control.rs b/src/control.rs index eca7a83..3fbb140 100644 --- a/src/control.rs +++ b/src/control.rs @@ -18,7 +18,7 @@ use amane::{ use crate::edge::{self, Edge, HIDE_H}; use crate::glyph::{Glyph, icon}; use crate::island::{self, CHIP, CHIP_HOVER, CHIP_OPEN, CLEAR, COMPACT_H, HOVER_MS, PILL_GONE, SHADOW, SHAPE_DONE, TOP_GAP, TRACK, backdrop, faded, mix, phases, veiled}; -use crate::map::{self, AREA_H, Centre, Link, MAP_W, NODES, Node}; +use crate::map::{self, AREA_H, Centre, Link, NODES, Node}; use crate::parse; use crate::prompt; use crate::sound::Sound; @@ -47,8 +47,16 @@ const OUTPUTS: usize = 8; // the pill and the panel sit this far from the monitor's right edge pub const RIGHT: f32 = 12.0; -// both windows are as wide as a map menu, the widest the panel becomes -const WINDOW_W: f32 = RIGHT + MAP_W + SHADOW; +// between a map menu and the media island's pill: room for the pill's pop (15 px a side) and a little air +const ISLAND_GAP: f32 = 16.0; + +// the narrowest a map gets, on a small monitor (where it then runs under the island) +const MIN_MAP_W: f32 = 600.0; + +// how wide the Wi-Fi and Bluetooth maps are on a monitor: from its right edge to the island's pill, less the gap +pub fn map_width(monitor_w: f32) -> f32 { + (monitor_w / 2.0 - island::COMPACT_W / 2.0 - ISLAND_GAP - RIGHT).max(MIN_MAP_W) +} // the pill is as wide as its icons, PILL_PAD on each side, PILL_GAP apart const PILL_PAD: f32 = 16.0; @@ -371,7 +379,9 @@ pub fn view(monitor: &Monitor) -> LayerWindow { let slide = edge::hidden_by(Edge::read().shown(here), open, HIDE_H); // as wide as the widest thing it can become, a map menu; the shape grows leftwards from the corner - let window_w = WINDOW_W; + // both windows are as wide as a map menu, the widest the panel becomes + let map_w = map_width(monitor.width as f32); + let window_w = RIGHT + map_w + SHADOW; let window = LayerWindow::new() .width(window_w) @@ -431,7 +441,7 @@ pub fn view(monitor: &Monitor) -> LayerWindow { let menu_size = match (control.menu.filter(|_| menu_t > 0.0), &sound) { (Some(menu), Some(sound)) => { - let content = menu_content(menu, &control, &links, &bluetooth, sound); + let content = menu_content(menu, map_w, &control, &links, &bluetooth, sound); Some((content.width, menu_height(&content))) } @@ -1608,7 +1618,7 @@ pub fn menu_view(monitor: &Monitor) -> LayerWindow { let bluetooth = Bluetooth::read(); let sound = Sound::read(); - let content = menu_content(menu, &control, &links, &bluetooth, &sound); + let content = menu_content(menu, map_width(monitor.width as f32), &control, &links, &bluetooth, &sound); let menu_w = content.width; let menu_h = menu_height(&content); @@ -1677,10 +1687,10 @@ fn menu_back() { * a menu's content. both windows call it, the panel for the size it takes; every * Service is passed in, since a view reading one twice can deadlock against a writer */ -fn menu_content(menu: Menu, control: &Control, links: &Links, bluetooth: &Bluetooth, sound: &Sound) -> MenuContent { +fn menu_content(menu: Menu, map_w: f32, control: &Control, links: &Links, bluetooth: &Bluetooth, sound: &Sound) -> MenuContent { match menu { - Menu::Wifi => wifi_menu(control, links), - Menu::Bluetooth => bluetooth_menu(control, bluetooth, links), + Menu::Wifi => wifi_menu(control, links, map_w), + Menu::Bluetooth => bluetooth_menu(control, bluetooth, links, map_w), Menu::Vpn => vpn_menu(control, links), Menu::Sound => sound_menu(control, sound), Menu::Ethernet => ethernet_menu(control), @@ -1843,7 +1853,7 @@ fn footer(control: &Control, text: &str) -> Rectangle { .child(Text::new(text).size(11.5).color(Color::blend(theme::now().dim, theme::now().text, lit))) } -fn wifi_menu(control: &Control, links: &Links) -> MenuContent { +fn wifi_menu(control: &Control, links: &Links, map_w: f32) -> MenuContent { let wifi = &links.wifi; // the tile shows "One moment" until the switch has done its work @@ -1950,16 +1960,16 @@ fn wifi_menu(control: &Control, links: &Links) -> MenuContent { on_click: Box::new(|| search_again(Menu::Wifi)), }; - body.add(map::map(MAP_W - 2.0 * MENU_PAD, AREA_H, centre, nodes), AREA_H); + body.add(map::map(map_w - 2.0 * MENU_PAD, AREA_H, centre, nodes), AREA_H); if wifi.service { body.add(footer(control, "Stop the Wi-Fi service").on_click(|_| later(system::stop_wifi_service, None)), 30.0); } - body.into_content("Wi-Fi", right, MAP_W) + body.into_content("Wi-Fi", right, map_w) } -fn bluetooth_menu(control: &Control, bluetooth: &Bluetooth, links: &Links) -> MenuContent { +fn bluetooth_menu(control: &Control, bluetooth: &Bluetooth, links: &Links, map_w: f32) -> MenuContent { let before = bluetooth_state(bluetooth, links).subtitle; let right = (bluetooth.available() || links.bluetooth_service).then(|| { @@ -2059,9 +2069,9 @@ fn bluetooth_menu(control: &Control, bluetooth: &Bluetooth, links: &Links) -> Me on_click: Box::new(|| search_again(Menu::Bluetooth)), }; - body.add(map::map(MAP_W - 2.0 * MENU_PAD, AREA_H, centre, nodes), AREA_H); + body.add(map::map(map_w - 2.0 * MENU_PAD, AREA_H, centre, nodes), AREA_H); - body.into_content("Bluetooth", right, MAP_W) + body.into_content("Bluetooth", right, map_w) } fn vpn_menu(control: &Control, links: &Links) -> MenuContent { diff --git a/src/island.rs b/src/island.rs index 435e7be..468171a 100644 --- a/src/island.rs +++ b/src/island.rs @@ -35,7 +35,7 @@ const PILL_HOVER_GROW: f32 = 10.0; const BOLD: f32 = 2.0; // sizes, in pixels -const COMPACT_W: f32 = 220.0; +pub const COMPACT_W: f32 = 220.0; pub const COMPACT_H: f32 = 38.0; const WIDE_W: f32 = 560.0; const WIDE_RADIUS: f32 = 28.0; diff --git a/src/map.rs b/src/map.rs index 67722e6..3be4e20 100644 --- a/src/map.rs +++ b/src/map.rs @@ -15,11 +15,8 @@ use crate::glyph::{Glyph, glyph_shapes}; use crate::island::{CHIP_OPEN, faded}; use crate::theme; -// how wide the map menus are, against the panel's 360 -pub const MAP_W: f32 = 920.0; - // the map's own height, under the menu's header -pub const AREA_H: f32 = 460.0; +pub const AREA_H: f32 = 480.0; const BUBBLE: f32 = 40.0; const NODE_R: f32 = 22.0; @@ -27,9 +24,6 @@ const ICON: f32 = 18.0; const LABEL_W: f32 = 140.0; const LABEL_H: f32 = 30.0; -// every other one sits this much further in -const STAGGER: f32 = 0.9; - // how far each one floats from its place, either way const DRIFT_X: f32 = 26.0; const DRIFT_Y: f32 = 16.0; @@ -117,8 +111,8 @@ pub fn map(width: f32, height: f32, centre: Centre, nodes: Vec) -> Rectang // evenly round, a quarter step past the top, so no one sits right on top of or under the bubble let angle = -PI / 2.0 + TAU * (phase + 0.25) / count; - // the stronger the nearer, but never far in; every other one a step further in, so neighbours' labels don't meet - let reach = (1.0 - 0.1 * node.nearness.clamp(0.0, 1.0)) * if index % 2 == 1 { STAGGER } else { 1.0 }; + // the stronger the nearer, but never far in, so neighbours' labels don't meet + let reach = 1.0 - 0.1 * node.nearness.clamp(0.0, 1.0); // pushed out to the inner ellipse if the nearness took it inside let (ex, ey) = (angle.cos() * rx * reach, angle.sin() * ry * reach); diff --git a/src/prompt.rs b/src/prompt.rs index b14a02d..f141c39 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -21,7 +21,6 @@ use amane::{ use crate::control::{self, Control, RIGHT}; use crate::glyph::{Glyph, icon}; use crate::island::{self, CHIP, CHIP_HOVER, CHIP_OPEN, HOVER_MS, SHADOW, TOP_GAP, backdrop, veiled}; -use crate::map::MAP_W; use crate::parse::{self, Pairing}; use crate::system; use crate::theme; @@ -636,7 +635,7 @@ pub fn view(monitor: &Monitor) -> LayerWindow { .child(veiled(content, POPUP_W, card_h, 22.0, appear, &colors)); // over the middle of the map menu, a little below the control centre's top - let (right, top) = (RIGHT + (MAP_W - POPUP_W) / 2.0 - SHADOW, TOP_GAP + 110.0 - SHADOW); + let (right, top) = (RIGHT + (control::map_width(monitor.width as f32) - POPUP_W) / 2.0 - SHADOW, TOP_GAP + 110.0 - SHADOW); /* * the keyboard only while there's something to type: the other popups come on their