Tray, fullscreen-only hiding, peeks, Ethernet details, wider maps
- tray.rs: the shell serves org.kde.StatusNotifierWatcher (only when no other watcher runs) and is its host; icons from the theme or from IconPixmap (png.rs, a stdlib png writer); left/middle/right click and scroll; dbusmenu menus drawn in the island's look; tray IPC verbs for testing - edge.rs: the bar hides only on monitors showing a fullscreen window, driven by Hyprland's event socket; peeks of the workspaces (with the disc sliding) and the island (song change, new player), with a pop on the island - prompt.rs: Ethernet details popup (addresses, gateway, DNS, MAC copyable) - map.rs: 1100x560 maps, every other node a ring further in - tools/fake_tray.py: a fake tray app, by bus name or by path
This commit is contained in:
+85
-31
@@ -5,12 +5,12 @@ use std::time::Duration;
|
||||
|
||||
use amane::{
|
||||
Animation, Blend, Center, Color, Full, Horizontal, InputArea, Layer, LayerWindow, Monitor, Padding, Parent, Pointer,
|
||||
Rectangle, Row, Service, Shadow, Start, Text, Vertical, Weight, Widget, Workspace, Workspaces, Zone,
|
||||
Rectangle, Row, Service, Shadow, Stack, Start, Text, Vertical, Weight, Widget, Workspace, Workspaces, Zone,
|
||||
};
|
||||
|
||||
use crate::edge::{self, Edge, HIDE_H};
|
||||
use crate::island::{CHIP_HOVER, CLEAR, COMPACT_H, HOVER_MS, SHADOW, TOP_GAP, backdrop, faded};
|
||||
use crate::theme;
|
||||
use crate::edge::{self, Edge, HIDE_H, Part};
|
||||
use crate::island::{CHIP_HOVER, CLEAR, COMPACT_H, HOVER_MS, SHADOW, TOP_GAP, backdrop, faded, mix};
|
||||
use crate::{theme, tray};
|
||||
|
||||
// one round slot per workspace, inside the pill's inset; the pill sits LEFT px from the monitor's edge
|
||||
const SLOT: f32 = 28.0;
|
||||
@@ -18,6 +18,9 @@ const SLOT_GAP: f32 = 4.0;
|
||||
const INSET: f32 = 5.0;
|
||||
const LEFT: f32 = 12.0;
|
||||
|
||||
// between the workspaces' pill and the tray's
|
||||
const TRAY_GAP: f32 = 8.0;
|
||||
|
||||
// each hovered workspace's highlight by workspace id, fading between 0 and 1
|
||||
#[derive(Default)]
|
||||
struct Hover(HashMap<i64, Animation>);
|
||||
@@ -43,8 +46,12 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
let workspaces = Workspaces::read();
|
||||
let hover = Hover::read();
|
||||
|
||||
// hidden until the pointer rests on the top edge (edge.rs)
|
||||
let slide = edge::hidden_by(Edge::read().shown(&monitor.name), 0.0, HIDE_H);
|
||||
// hidden on a fullscreen monitor until wanted, or for a moment on a workspace change (edge.rs)
|
||||
let (slide, switched) = {
|
||||
let edge = Edge::read();
|
||||
|
||||
(edge::hidden_by(edge.part(Part::Workspaces, &monitor.name), 0.0, HIDE_H), edge.switched(&monitor.name))
|
||||
};
|
||||
|
||||
// special workspaces (scratchpads) aren't places to switch to
|
||||
let here: Vec<&Workspace> = workspaces
|
||||
@@ -57,24 +64,59 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
let count = here.len() as f32;
|
||||
let width = 2.0 * INSET + count * SLOT + (count - 1.0).max(0.0) * SLOT_GAP;
|
||||
|
||||
let window = LayerWindow::new()
|
||||
.width(LEFT + width + SHADOW)
|
||||
.height(TOP_GAP + COMPACT_H + SHADOW)
|
||||
.anchor_vertical(Vertical::Top)
|
||||
.anchor_horizontal(Horizontal::Left)
|
||||
// Overlay like the island: on a desktop of fullscreen windows, Top is hidden behind every one of them
|
||||
.layer(Layer::Overlay)
|
||||
.space(Zone::Ignore)
|
||||
.namespace("magic-workspaces");
|
||||
let window = |window_w: f32| {
|
||||
LayerWindow::new()
|
||||
.width(window_w)
|
||||
.height(TOP_GAP + COMPACT_H + SHADOW)
|
||||
.anchor_vertical(Vertical::Top)
|
||||
.anchor_horizontal(Horizontal::Left)
|
||||
// Overlay like the island: on a desktop of fullscreen windows, Top is hidden behind every one of them
|
||||
.layer(Layer::Overlay)
|
||||
.space(Zone::Ignore)
|
||||
.namespace("magic-workspaces")
|
||||
};
|
||||
|
||||
// empty and click-through rather than hidden: see the island's view for why
|
||||
if here.is_empty() || slide <= -HIDE_H {
|
||||
return window.click_through().child(Rectangle::new().width(0.0).height(0.0));
|
||||
return window(LEFT + width + SHADOW).click_through().child(Rectangle::new().width(0.0).height(0.0));
|
||||
}
|
||||
|
||||
// the tray's pill right of this one (tray.rs), when an app has an icon there
|
||||
let tray = tray::pill(&monitor.name, LEFT + width + TRAY_GAP);
|
||||
let tray_w = tray.as_ref().map_or(0.0, |(_, tray_w)| TRAY_GAP + tray_w);
|
||||
|
||||
let window = window(LEFT + width + tray_w + SHADOW);
|
||||
|
||||
let mut slots: Vec<Box<dyn Widget>> = Vec::new();
|
||||
|
||||
for workspace in here {
|
||||
/*
|
||||
* the focused workspace's accent is one disc under the row, so a change slides it from
|
||||
* the workspace before to this one (edge.rs times it); the slots themselves stay clear
|
||||
*/
|
||||
let theme = theme::now();
|
||||
let slot_x = |index: usize| index as f32 * (SLOT + SLOT_GAP);
|
||||
|
||||
let focused_at = here.iter().position(|workspace| workspace.focused());
|
||||
let came_from = switched.and_then(|(from, t)| here.iter().position(|workspace| workspace.id() == from).map(|at| (at, t)));
|
||||
|
||||
let highlight_x = match (focused_at, came_from) {
|
||||
(Some(to), Some((from, t))) => {
|
||||
// ease in and out, with a slight stretch in the middle like a drop of ink
|
||||
let eased = t * t * (3.0 - 2.0 * t);
|
||||
|
||||
Some(mix(slot_x(from), slot_x(to), eased))
|
||||
}
|
||||
(Some(to), None) => Some(slot_x(to)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let stretch = came_from.map_or(0.0, |(_, t)| (t * std::f32::consts::PI).sin() * 10.0);
|
||||
|
||||
if switched.is_some() {
|
||||
amane::request_frame();
|
||||
}
|
||||
|
||||
for workspace in &here {
|
||||
let id = workspace.id();
|
||||
let lit = hover.0.get(&id).map_or(0.0, Animation::value);
|
||||
|
||||
@@ -84,10 +126,11 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
_ => workspace.index().to_string(),
|
||||
};
|
||||
|
||||
let theme = theme::now();
|
||||
|
||||
let (fill, color, weight) = if workspace.focused() {
|
||||
(theme.accent, theme.on_accent, Weight::Bold)
|
||||
// the label turns as the disc arrives
|
||||
let arrived = switched.map_or(1.0, |(_, t)| t);
|
||||
|
||||
(CLEAR, Color::blend(theme.text, theme.on_accent, arrived), Weight::Bold)
|
||||
} else if workspace.active() {
|
||||
(faded(theme.accent, 70), theme.accent, Weight::Bold)
|
||||
} else {
|
||||
@@ -111,7 +154,16 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
));
|
||||
}
|
||||
|
||||
let theme = theme::now();
|
||||
let row_w = width - 2.0 * INSET;
|
||||
|
||||
// the disc under the focused slot, a little wider while it's on its way
|
||||
let mut layers: Vec<Box<dyn Widget>> = Vec::new();
|
||||
|
||||
if let Some(x) = highlight_x {
|
||||
layers.push(Box::new(Rectangle::new().width(SLOT + stretch).height(SLOT).radius(Full).fill(theme.accent).translate(x - stretch / 2.0, 0.0)));
|
||||
}
|
||||
|
||||
layers.push(Box::new(Row::new(slots).gap(SLOT_GAP).align(Center)));
|
||||
|
||||
let pill = Rectangle::new()
|
||||
.width(width)
|
||||
@@ -123,24 +175,26 @@ pub fn view(monitor: &Monitor) -> LayerWindow {
|
||||
.padding(INSET)
|
||||
.align_child(Start, Center)
|
||||
.on_hover(edge::over_bar)
|
||||
.child(Row::new(slots).gap(SLOT_GAP).align(Center));
|
||||
.child(Stack::new(layers).width(row_w).height(SLOT));
|
||||
|
||||
// clicks next to the pill reach the windows underneath
|
||||
let region = InputArea {
|
||||
x: LEFT as i32,
|
||||
y: TOP_GAP as i32,
|
||||
width: width.ceil() as i32,
|
||||
height: COMPACT_H as i32,
|
||||
};
|
||||
// clicks next to the pills reach the windows underneath
|
||||
let mut regions = vec![InputArea { x: LEFT as i32, y: TOP_GAP as i32, width: width.ceil() as i32, height: COMPACT_H as i32 }];
|
||||
|
||||
window.input_region(vec![region]).child(
|
||||
let mut pills: Vec<Box<dyn Widget>> = vec![Box::new(pill)];
|
||||
|
||||
if let Some((tray, tray_pill_w)) = tray {
|
||||
regions.push(InputArea { x: (LEFT + width + TRAY_GAP) as i32, y: TOP_GAP as i32, width: tray_pill_w.ceil() as i32, height: COMPACT_H as i32 });
|
||||
pills.push(Box::new(tray));
|
||||
}
|
||||
|
||||
window.input_region(regions).child(
|
||||
Rectangle::new()
|
||||
.width(Parent)
|
||||
.height(Parent)
|
||||
.padding(Padding { top: TOP_GAP, right: 0.0, bottom: 0.0, left: LEFT })
|
||||
.align_child(Start, Start)
|
||||
.translate(0.0, slide)
|
||||
.child(pill),
|
||||
.child(Row::new(pills).gap(TRAY_GAP).align(Start)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user