Add audio output switching and fix the workspace trail
All checks were successful
dev release / build (push) Successful in 1m33s
All checks were successful
dev release / build (push) Successful in 1m33s
The control panel lists PipeWire sinks and sets the default, moving playing streams so the change is immediate. The workspace pill starts on switch without a layout wait, measures against the Fixed host so it stays centered on the digit, and ignores row-wide bounds that used to stretch it across several chips.
This commit is contained in:
parent
241dfd17a7
commit
e4c12e9b62
4 changed files with 172 additions and 128 deletions
|
|
@ -121,12 +121,29 @@ pub fn spawn_set_brightness(v: f64) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn spawn_set_sink(name: String, sender: ComponentSender<App>) {
|
||||||
pub fn spawn_set_sink(name: String) {
|
|
||||||
relm4::spawn(async move {
|
relm4::spawn(async move {
|
||||||
let _ = tokio::process::Command::new("pactl")
|
let _ = tokio::process::Command::new("pactl")
|
||||||
.args(["set-default-sink", &name])
|
.args(["set-default-sink", &name])
|
||||||
.output()
|
.output()
|
||||||
.await;
|
.await;
|
||||||
|
// Default sink alone leaves already-playing streams on the old
|
||||||
|
// device — move them too so the switch is audible immediately.
|
||||||
|
if let Ok(o) = tokio::process::Command::new("pactl")
|
||||||
|
.args(["list", "short", "sink-inputs"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
for line in String::from_utf8_lossy(&o.stdout).lines() {
|
||||||
|
let Some(id) = line.split_whitespace().next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let _ = tokio::process::Command::new("pactl")
|
||||||
|
.args(["move-sink-input", id, &name])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spawn_load(sender);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,14 @@ pub fn make_button(
|
||||||
btn.add_css_class("active");
|
btn.add_css_class("active");
|
||||||
}
|
}
|
||||||
btn.set_valign(gtk4::Align::Center);
|
btn.set_valign(gtk4::Align::Center);
|
||||||
|
btn.set_halign(gtk4::Align::Center);
|
||||||
btn.set_vexpand(false);
|
btn.set_vexpand(false);
|
||||||
|
btn.set_hexpand(false);
|
||||||
btn.set_size_request(-1, crate::CHIP_HEIGHT);
|
btn.set_size_request(-1, crate::CHIP_HEIGHT);
|
||||||
|
if let Some(child) = btn.child() {
|
||||||
|
child.set_halign(gtk4::Align::Center);
|
||||||
|
child.set_valign(gtk4::Align::Center);
|
||||||
|
}
|
||||||
btn.connect_clicked(move |_| {
|
btn.connect_clicked(move |_| {
|
||||||
relm4::spawn(async move {
|
relm4::spawn(async move {
|
||||||
switch_workspace(id).await;
|
switch_workspace(id).await;
|
||||||
|
|
@ -189,7 +195,7 @@ impl WorkspaceTrail {
|
||||||
pill.set_visible(false);
|
pill.set_visible(false);
|
||||||
host.put(&pill, 0.0, 0.0);
|
host.put(&pill, 0.0, 0.0);
|
||||||
|
|
||||||
let buttons = gtk4::Box::new(gtk4::Orientation::Horizontal, 2);
|
let buttons = gtk4::Box::new(gtk4::Orientation::Horizontal, 1);
|
||||||
buttons.set_halign(gtk4::Align::Fill);
|
buttons.set_halign(gtk4::Align::Fill);
|
||||||
buttons.set_valign(gtk4::Align::Center);
|
buttons.set_valign(gtk4::Align::Center);
|
||||||
buttons.set_vexpand(false);
|
buttons.set_vexpand(false);
|
||||||
|
|
@ -230,120 +236,49 @@ impl WorkspaceTrail {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn place(&self, btn: >k4::Button) {
|
pub fn place(&self, btn: >k4::Button) {
|
||||||
let pill = self.pill.clone();
|
|
||||||
let host = self.host.clone();
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
self.when_stable(btn, move |g| {
|
|
||||||
apply_geom(&host, &pill, &inner, &g);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stretch(&self, from: Option<>k4::Button>, to: >k4::Button) {
|
|
||||||
let from_g = self.from_geom(from);
|
|
||||||
let overlay = self.overlay.clone();
|
|
||||||
let pill = self.pill.clone();
|
|
||||||
let host = self.host.clone();
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let dest = to.clone();
|
|
||||||
self.when_stable(to, move |to_g| {
|
|
||||||
stretch_geom_on(&overlay, &host, &pill, &inner, from_g, to_g, Some(dest));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// New empty-workspace buttons first allocate at CSS `min-width` (32px)
|
|
||||||
/// and only then grow to the padded label. Two stable frames of that
|
|
||||||
/// placeholder is not enough — empty→empty used to shrink the pill to it.
|
|
||||||
fn when_stable(&self, btn: >k4::Button, then: impl FnOnce(Geom) + 'static) {
|
|
||||||
self.cancel();
|
self.cancel();
|
||||||
let btn = btn.clone();
|
if let Some(g) = button_geom(btn, &self.host) {
|
||||||
|
apply_geom(&self.host, &self.pill, &self.inner, &inset_pill(g));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pill = self.pill.clone();
|
||||||
|
let host = self.host.clone();
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
let last = std::cell::Cell::new(None::<Geom>);
|
let btn = btn.clone();
|
||||||
let same = std::cell::Cell::new(0u8);
|
let id = self.overlay.add_tick_callback(move |_, _| {
|
||||||
let frames = std::cell::Cell::new(0u8);
|
let Some(g) = button_geom(&btn, &host) else {
|
||||||
let then = std::cell::Cell::new(Some(then));
|
return ControlFlow::Continue;
|
||||||
let id = self.overlay.add_tick_callback(move |ov, _| {
|
};
|
||||||
frames.set(frames.get().saturating_add(1));
|
apply_geom(&host, &pill, &inner, &inset_pill(g));
|
||||||
let n = frames.get();
|
|
||||||
let Some(g) = button_geom(&btn, ov) else {
|
|
||||||
last.set(None);
|
|
||||||
same.set(0);
|
|
||||||
return if n > 24 {
|
|
||||||
inner.borrow_mut().tick = None;
|
inner.borrow_mut().tick = None;
|
||||||
ControlFlow::Break
|
ControlFlow::Break
|
||||||
} else {
|
|
||||||
ControlFlow::Continue
|
|
||||||
};
|
|
||||||
};
|
|
||||||
// Still sitting on the 32px min-width slot, or smaller than the
|
|
||||||
// button's natural request — keep waiting for the real layout.
|
|
||||||
if still_placeholder(&btn, &g) && n < 20 {
|
|
||||||
last.set(None);
|
|
||||||
same.set(0);
|
|
||||||
return ControlFlow::Continue;
|
|
||||||
}
|
|
||||||
let stable = last.get().is_some_and(|p| geom_close(&p, &g));
|
|
||||||
last.set(Some(g));
|
|
||||||
same.set(if stable { same.get().saturating_add(1) } else { 0 });
|
|
||||||
if same.get() >= 2 || n > 22 {
|
|
||||||
inner.borrow_mut().tick = None;
|
|
||||||
if let Some(f) = then.take() {
|
|
||||||
f(g);
|
|
||||||
}
|
|
||||||
return ControlFlow::Break;
|
|
||||||
}
|
|
||||||
ControlFlow::Continue
|
|
||||||
});
|
});
|
||||||
self.inner.borrow_mut().tick = Some(id);
|
self.inner.borrow_mut().tick = Some(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_geom(&self, from: Option<>k4::Button>) -> Option<Geom> {
|
pub fn stretch(&self, from: Option<>k4::Button>, to: >k4::Button) {
|
||||||
let st = self.inner.borrow();
|
self.cancel();
|
||||||
if self.pill.is_visible() && st.geom.w > 0.5 {
|
let Some(from_g) = self.from_geom(from) else {
|
||||||
return Some(st.geom);
|
self.place(to);
|
||||||
}
|
|
||||||
drop(st);
|
|
||||||
from.and_then(|b| button_geom(b, &self.overlay))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stretch_geom_on(
|
|
||||||
overlay: >k4::Overlay,
|
|
||||||
host: >k4::Fixed,
|
|
||||||
pill: >k4::Box,
|
|
||||||
inner: &Rc<RefCell<TrailInner>>,
|
|
||||||
from_g: Option<Geom>,
|
|
||||||
to_g: Geom,
|
|
||||||
dest: Option<gtk4::Button>,
|
|
||||||
) {
|
|
||||||
let Some(from_g) = from_g else {
|
|
||||||
apply_geom(host, pill, inner, &to_g);
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (from_g.x - to_g.x).abs() < 0.5 && (from_g.w - to_g.w).abs() < 0.5 {
|
let dest = to.clone();
|
||||||
apply_geom(host, pill, inner, &to_g);
|
let pill = self.pill.clone();
|
||||||
return;
|
let host = self.host.clone();
|
||||||
}
|
let inner = self.inner.clone();
|
||||||
|
let started = Instant::now();
|
||||||
|
let id = self.overlay.add_tick_callback(move |_, _| {
|
||||||
|
let to_g = resolved_dest(&dest, &host, &from_g);
|
||||||
|
let mid = {
|
||||||
let span_x = from_g.x.min(to_g.x);
|
let span_x = from_g.x.min(to_g.x);
|
||||||
let span_w = (from_g.x + from_g.w).max(to_g.x + to_g.w) - span_x;
|
let span_w = (from_g.x + from_g.w).max(to_g.x + to_g.w) - span_x;
|
||||||
let mid = Geom {
|
Geom {
|
||||||
x: span_x,
|
x: span_x,
|
||||||
y: to_g.y,
|
y: to_g.y,
|
||||||
w: span_w,
|
w: span_w,
|
||||||
h: to_g.h,
|
h: to_g.h,
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(id) = inner.borrow_mut().tick.take() {
|
|
||||||
id.remove();
|
|
||||||
}
|
}
|
||||||
let started = Instant::now();
|
};
|
||||||
let pill = pill.clone();
|
|
||||||
let host = host.clone();
|
|
||||||
let inner_tick = inner.clone();
|
|
||||||
let dest = dest.clone();
|
|
||||||
let ov = overlay.clone();
|
|
||||||
let id = overlay.add_tick_callback(move |_, _| {
|
|
||||||
let elapsed = started.elapsed().as_secs_f64() * 1000.0;
|
let elapsed = started.elapsed().as_secs_f64() * 1000.0;
|
||||||
let (g, done) = if elapsed < STRETCH_MS {
|
let (g, done) = if elapsed < STRETCH_MS {
|
||||||
let t = ease(elapsed / STRETCH_MS);
|
let t = ease(elapsed / STRETCH_MS);
|
||||||
|
|
@ -352,28 +287,74 @@ fn stretch_geom_on(
|
||||||
let t = ease_overshoot((elapsed - STRETCH_MS) / SNAP_MS);
|
let t = ease_overshoot((elapsed - STRETCH_MS) / SNAP_MS);
|
||||||
(lerp_geom(&mid, &to_g, t), false)
|
(lerp_geom(&mid, &to_g, t), false)
|
||||||
} else {
|
} else {
|
||||||
let end = dest
|
(to_g, true)
|
||||||
.as_ref()
|
|
||||||
.and_then(|b| button_geom(b, &ov))
|
|
||||||
.unwrap_or(to_g);
|
|
||||||
(end, true)
|
|
||||||
};
|
};
|
||||||
apply_geom(&host, &pill, &inner_tick, &g);
|
apply_geom(&host, &pill, &inner, &g);
|
||||||
if done {
|
if done {
|
||||||
inner_tick.borrow_mut().tick = None;
|
inner.borrow_mut().tick = None;
|
||||||
ControlFlow::Break
|
ControlFlow::Break
|
||||||
} else {
|
} else {
|
||||||
ControlFlow::Continue
|
ControlFlow::Continue
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
inner.borrow_mut().tick = Some(id);
|
self.inner.borrow_mut().tick = Some(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_geom(&self, from: Option<>k4::Button>) -> Option<Geom> {
|
||||||
|
let st = self.inner.borrow();
|
||||||
|
// A leftover mid-stretch can be as wide as the whole row — never
|
||||||
|
// treat that as the start of the next animation.
|
||||||
|
if self.pill.is_visible() && st.geom.w > 0.5 && st.geom.w <= MAX_CHIP_W {
|
||||||
|
return Some(st.geom);
|
||||||
|
}
|
||||||
|
drop(st);
|
||||||
|
from.and_then(|b| button_geom(b, &self.host).map(inset_pill))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button_geom(btn: >k4::Button, overlay: >k4::Overlay) -> Option<Geom> {
|
/// Keep the trail slimmer than the hit target so the fill doesn't look
|
||||||
let r = btn.compute_bounds(overlay)?;
|
/// like a second, fatter button.
|
||||||
|
const PILL_INSET_X: f64 = 5.0;
|
||||||
|
const PILL_INSET_Y: f64 = 3.0;
|
||||||
|
/// One workspace chip is a digit + padding. Wider than this is the overlay
|
||||||
|
/// or the whole button row leaking through `compute_bounds`.
|
||||||
|
const MAX_CHIP_W: f64 = 72.0;
|
||||||
|
|
||||||
|
fn inset_pill(g: Geom) -> Geom {
|
||||||
|
let w = (g.w - PILL_INSET_X * 2.0).max(10.0);
|
||||||
|
let h = (g.h - PILL_INSET_Y * 2.0).max(18.0);
|
||||||
|
Geom {
|
||||||
|
x: g.x + (g.w - w) * 0.5,
|
||||||
|
y: g.y + (g.h - h) * 0.5,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolved_dest(btn: >k4::Button, host: >k4::Fixed, from: &Geom) -> Geom {
|
||||||
|
match button_geom(btn, host) {
|
||||||
|
Some(g) if !still_placeholder(btn, &g) => inset_pill(g),
|
||||||
|
Some(g) => {
|
||||||
|
let centered = inset_pill(g);
|
||||||
|
Geom {
|
||||||
|
x: centered.x + (centered.w - from.w) * 0.5,
|
||||||
|
y: centered.y + (centered.h - from.h) * 0.5,
|
||||||
|
w: from.w,
|
||||||
|
h: from.h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => *from,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Position in the Fixed host's space — that's what `host.move_` uses.
|
||||||
|
/// Measuring against the Overlay instead left the pill a few px left of
|
||||||
|
/// the digit whenever the host and overlay origins disagreed.
|
||||||
|
fn button_geom(btn: >k4::Button, host: >k4::Fixed) -> Option<Geom> {
|
||||||
|
let r = btn.compute_bounds(host)?;
|
||||||
let w = f64::from(r.width());
|
let w = f64::from(r.width());
|
||||||
let h = f64::from(r.height());
|
let h = f64::from(r.height());
|
||||||
if w < 1.0 || h < 1.0 {
|
if w < 8.0 || h < 8.0 || w > MAX_CHIP_W {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(Geom {
|
Some(Geom {
|
||||||
|
|
@ -405,13 +386,6 @@ fn still_placeholder(btn: >k4::Button, g: &Geom) -> bool {
|
||||||
g.w <= f64::from(min_w) + 1.0 || g.w + 0.5 < f64::from(nat_w)
|
g.w <= f64::from(min_w) + 1.0 || g.w + 0.5 < f64::from(nat_w)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn geom_close(a: &Geom, b: &Geom) -> bool {
|
|
||||||
(a.x - b.x).abs() < 0.5
|
|
||||||
&& (a.y - b.y).abs() < 0.5
|
|
||||||
&& (a.w - b.w).abs() < 0.5
|
|
||||||
&& (a.h - b.h).abs() < 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lerp(a: f64, b: f64, t: f64) -> f64 {
|
fn lerp(a: f64, b: f64, t: f64) -> f64 {
|
||||||
a + (b - a) * t
|
a + (b - a) * t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
src/main.rs
51
src/main.rs
|
|
@ -99,6 +99,8 @@ pub struct App {
|
||||||
panel_vol_slider: gtk4::Scale,
|
panel_vol_slider: gtk4::Scale,
|
||||||
panel_bright_slider: gtk4::Scale,
|
panel_bright_slider: gtk4::Scale,
|
||||||
panel_loading: Rc<Cell<bool>>,
|
panel_loading: Rc<Cell<bool>>,
|
||||||
|
sink_box: gtk4::Box,
|
||||||
|
sink_section: gtk4::Box,
|
||||||
|
|
||||||
// ── Tray ──────────────────────────────────────────────────────────────
|
// ── Tray ──────────────────────────────────────────────────────────────
|
||||||
tray_section: gtk4::Box,
|
tray_section: gtk4::Box,
|
||||||
|
|
@ -517,6 +519,18 @@ impl SimpleComponent for App {
|
||||||
let panel_vol_slider = vol_row.1.clone();
|
let panel_vol_slider = vol_row.1.clone();
|
||||||
panel_inner.append(&vol_row.0);
|
panel_inner.append(&vol_row.0);
|
||||||
|
|
||||||
|
let sink_section = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
|
sink_section.add_css_class("control-panel-section");
|
||||||
|
let sink_header = gtk4::Label::new(Some("OUTPUT"));
|
||||||
|
sink_header.add_css_class("control-panel-header");
|
||||||
|
sink_header.set_xalign(0.0);
|
||||||
|
sink_header.set_margin_top(6);
|
||||||
|
let sink_box = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
|
sink_section.append(&sink_header);
|
||||||
|
sink_section.append(&sink_box);
|
||||||
|
sink_section.set_visible(false);
|
||||||
|
panel_inner.append(&sink_section);
|
||||||
|
|
||||||
let bright_row = build_slider_row("bl", 0.0, 1.0, 0.02);
|
let bright_row = build_slider_row("bl", 0.0, 1.0, 0.02);
|
||||||
let panel_bright_slider = bright_row.1.clone();
|
let panel_bright_slider = bright_row.1.clone();
|
||||||
panel_inner.append(&bright_row.0);
|
panel_inner.append(&bright_row.0);
|
||||||
|
|
@ -747,6 +761,8 @@ impl SimpleComponent for App {
|
||||||
panel_vol_slider,
|
panel_vol_slider,
|
||||||
panel_bright_slider,
|
panel_bright_slider,
|
||||||
panel_loading,
|
panel_loading,
|
||||||
|
sink_box,
|
||||||
|
sink_section,
|
||||||
tray_section,
|
tray_section,
|
||||||
tray_sep,
|
tray_sep,
|
||||||
tray_box,
|
tray_box,
|
||||||
|
|
@ -1033,6 +1049,7 @@ impl SimpleComponent for App {
|
||||||
self.panel_vol_slider.set_value(data.volume);
|
self.panel_vol_slider.set_value(data.volume);
|
||||||
self.panel_bright_slider.set_value(data.brightness);
|
self.panel_bright_slider.set_value(data.brightness);
|
||||||
self.panel_loading.set(false);
|
self.panel_loading.set(false);
|
||||||
|
self.rebuild_sinks(&data.sinks, &sender);
|
||||||
}
|
}
|
||||||
AppInput::WidgetsUpdate(specs) => {
|
AppInput::WidgetsUpdate(specs) => {
|
||||||
self.reconcile_widgets(specs);
|
self.reconcile_widgets(specs);
|
||||||
|
|
@ -1129,6 +1146,40 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rebuild_sinks(
|
||||||
|
&mut self,
|
||||||
|
sinks: &[bar::control::AudioSink],
|
||||||
|
sender: &ComponentSender<Self>,
|
||||||
|
) {
|
||||||
|
while let Some(child) = self.sink_box.first_child() {
|
||||||
|
self.sink_box.remove(&child);
|
||||||
|
}
|
||||||
|
self.sink_section.set_visible(!sinks.is_empty());
|
||||||
|
for (i, sink) in sinks.iter().enumerate() {
|
||||||
|
let row = gtk4::Button::new();
|
||||||
|
row.add_css_class("flat");
|
||||||
|
row.add_css_class("wifi-popover-row");
|
||||||
|
row.add_css_class("sink-row");
|
||||||
|
if sink.is_default {
|
||||||
|
row.add_css_class("wifi-popover-row-active");
|
||||||
|
}
|
||||||
|
stagger_row(&row, i);
|
||||||
|
let lbl = gtk4::Label::new(Some(&sink.description));
|
||||||
|
lbl.set_xalign(0.0);
|
||||||
|
lbl.set_hexpand(true);
|
||||||
|
lbl.set_ellipsize(gtk4::pango::EllipsizeMode::End);
|
||||||
|
lbl.set_max_width_chars(22);
|
||||||
|
lbl.set_valign(gtk4::Align::Center);
|
||||||
|
row.set_child(Some(&lbl));
|
||||||
|
let name = sink.name.clone();
|
||||||
|
let sender = sender.clone();
|
||||||
|
row.connect_clicked(move |_| {
|
||||||
|
bar::control::spawn_set_sink(name.clone(), sender.clone());
|
||||||
|
});
|
||||||
|
self.sink_box.append(&row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_wifi_label(&self) {
|
fn apply_wifi_label(&self) {
|
||||||
let label = match &self.wifi_profile {
|
let label = match &self.wifi_profile {
|
||||||
Some(p) => format!("{p} · {}", self.current_ssid),
|
Some(p) => format!("{p} · {}", self.current_ssid),
|
||||||
|
|
|
||||||
12
src/theme.rs
12
src/theme.rs
|
|
@ -39,10 +39,10 @@ fn load_css() -> String {
|
||||||
window.breadbar > centerbox {{ padding: 0 8px 0 6px; }}\
|
window.breadbar > centerbox {{ padding: 0 8px 0 6px; }}\
|
||||||
window.breadbar button {{ min-height: 0; min-width: 0; }}\
|
window.breadbar button {{ min-height: 0; min-width: 0; }}\
|
||||||
.workspace-trail {{ background-image: linear-gradient(90deg, @accent, @teal);\
|
.workspace-trail {{ background-image: linear-gradient(90deg, @accent, @teal);\
|
||||||
background-color: @accent; border-radius: 999px; }}\
|
background-color: @accent; border-radius: 12px; }}\
|
||||||
.workspace-btn {{ background: transparent; opacity: 0.36; color: @on-bg;\
|
.workspace-btn {{ background: transparent; opacity: 0.36; color: @on-bg;\
|
||||||
border-radius: 999px; border: none; outline: none; box-shadow: none;\
|
border-radius: 12px; border: none; outline: none; box-shadow: none;\
|
||||||
min-width: 32px; min-height: 32px; margin: 0 2px; padding: 0 12px;\
|
min-width: 28px; min-height: 28px; margin: 0; padding: 0 7px;\
|
||||||
font-size: 22px; font-weight: bold;\
|
font-size: 22px; font-weight: bold;\
|
||||||
transition: opacity 0.22s cubic-bezier(0.22, 1.2, 0.36, 1),\
|
transition: opacity 0.22s cubic-bezier(0.22, 1.2, 0.36, 1),\
|
||||||
background-color 0.22s cubic-bezier(0.22, 1.2, 0.36, 1); }}\
|
background-color 0.22s cubic-bezier(0.22, 1.2, 0.36, 1); }}\
|
||||||
|
|
@ -67,9 +67,10 @@ fn load_css() -> String {
|
||||||
opacity 0.18s ease; }}\
|
opacity 0.18s ease; }}\
|
||||||
.stat-pair:hover {{ background: alpha(@on-bg, 0.12); }}\
|
.stat-pair:hover {{ background: alpha(@on-bg, 0.12); }}\
|
||||||
.stat-pair:active {{ background: alpha(@on-bg, 0.18); }}\
|
.stat-pair:active {{ background: alpha(@on-bg, 0.18); }}\
|
||||||
.stat-pair.icon-only {{ padding: 6px; border-radius: 999px; }}\
|
.stat-pair.icon-only {{ padding: 4px; border-radius: 999px;\
|
||||||
|
min-width: 32px; min-height: 32px; }}\
|
||||||
.stat-icon {{ margin-right: 6px; }}\
|
.stat-icon {{ margin-right: 6px; }}\
|
||||||
.stat-pair.icon-only .stat-icon {{ margin-right: 0; }}\
|
.stat-pair.icon-only .stat-icon {{ margin: 0; }}\
|
||||||
.bt-icon {{ margin-right: 8px; }}
|
.bt-icon {{ margin-right: 8px; }}
|
||||||
separator.bar-sep {{ min-height: 12px; min-width: 1px; margin: 0 10px 0 2px;\
|
separator.bar-sep {{ min-height: 12px; min-width: 1px; margin: 0 10px 0 2px;\
|
||||||
background: alpha(@on-bg, 0.10); }}\
|
background: alpha(@on-bg, 0.10); }}\
|
||||||
|
|
@ -208,6 +209,7 @@ fn load_css() -> String {
|
||||||
padding: 0; opacity: 0; background: transparent; border: none;\
|
padding: 0; opacity: 0; background: transparent; border: none;\
|
||||||
outline: none; box-shadow: none; }}\
|
outline: none; box-shadow: none; }}\
|
||||||
.control-panel-section {{ margin: 8px 0 0; }}\
|
.control-panel-section {{ margin: 8px 0 0; }}\
|
||||||
|
.sink-row label {{ font-size: 15px; }}\
|
||||||
.power-row {{ margin-top: 8px; }}\
|
.power-row {{ margin-top: 8px; }}\
|
||||||
.power-btn {{ min-width: 0; min-height: 0; padding: 8px 10px; border-radius: 8px;\
|
.power-btn {{ min-width: 0; min-height: 0; padding: 8px 10px; border-radius: 8px;\
|
||||||
background: alpha(@on-bg, 0.08); font-size: 13px; border: none;\
|
background: alpha(@on-bg, 0.08); font-size: 13px; border: none;\
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue