things
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
//! Audio module: the default sink and source as icons, drawn only while
|
||||
//! something is muted — the signal the quickshell Audio widget gives.
|
||||
|
||||
use iced::widget::{container, row, space, text};
|
||||
use iced::{border, Alignment, Element, Length, Task, Theme};
|
||||
|
||||
use common::{AudioNode, BarModule, ModuleEffect, PipewireMsg, PipewireState, Service, Wire};
|
||||
|
||||
/// Glyphs exactly as the Quickshell Audio widget draws them: the speaker is a
|
||||
/// Material `md-volume` icon, the mic a Font Awesome one.
|
||||
const SINK_ON: &str = "\u{f057e}"; // md-volume_high
|
||||
const SINK_MUTED: &str = "\u{f0581}"; // md-volume_off
|
||||
const MIC_ON: &str = "\u{f130}"; // fa-microphone
|
||||
const MIC_MUTED: &str = "\u{f131}"; // fa-microphone_slash
|
||||
|
||||
/// Quickshell's geometry at its 20 px reference bar: a 24 px slot per icon
|
||||
/// and 4 px between them. Heights and the pill fill the bar through layout,
|
||||
/// but font size is a fixed px value iced cannot derive from the space it is
|
||||
/// given — if the bar ever leaves 20 px, scale the two sizes here.
|
||||
const REF_SLOT_W: f32 = 24.0;
|
||||
const REF_SPACING: f32 = 4.0;
|
||||
const REF_RADIUS: f32 = 3.5;
|
||||
/// The speaker is re-drawn in the mono face, whose ink is 1168/1496 the size
|
||||
/// of the proportional face quickshell uses, so it is pre-compensated.
|
||||
const REF_SINK_SIZE: f32 = 20.0 * 1496.0 / 1168.0;
|
||||
const REF_MIC_SIZE: f32 = 30.0;
|
||||
|
||||
pub struct Audio {
|
||||
state: PipewireState,
|
||||
}
|
||||
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: PipewireState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BarModule for Audio {
|
||||
fn id(&self) -> &'static str {
|
||||
"audio"
|
||||
}
|
||||
|
||||
fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
|
||||
let (sink, source) = (self.state.sink, self.state.source);
|
||||
if !sink.muted && !source.muted {
|
||||
// Nothing is muted, so there is nothing to show.
|
||||
return space::horizontal().into();
|
||||
}
|
||||
|
||||
let icons = row![
|
||||
icon(glyph(sink, SINK_ON, SINK_MUTED), REF_SINK_SIZE),
|
||||
icon(glyph(source, MIC_ON, MIC_MUTED), REF_MIC_SIZE),
|
||||
]
|
||||
.height(Length::Fill)
|
||||
.spacing(REF_SPACING);
|
||||
|
||||
container(icons)
|
||||
.height(Length::Fill)
|
||||
.style(pill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||
if let Some(state) = msg.downcast::<PipewireState>() {
|
||||
self.state = *state;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn services(&self) -> Vec<Service> {
|
||||
vec![PipewireMsg::State.key()]
|
||||
}
|
||||
}
|
||||
|
||||
/// One glyph centred in a fixed-width slot that fills the bar's height.
|
||||
fn icon<'a>(glyph: &'static str, size: f32) -> Element<'a, Wire> {
|
||||
container(
|
||||
text(glyph)
|
||||
.size(size)
|
||||
.height(Length::Fill)
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.center_x(Length::Fixed(REF_SLOT_W))
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The muted glyph when the node is muted, its normal glyph otherwise.
|
||||
fn glyph(node: AudioNode, on: &'static str, muted: &'static str) -> &'static str {
|
||||
if node.muted {
|
||||
muted
|
||||
} else {
|
||||
on
|
||||
}
|
||||
}
|
||||
|
||||
/// The quickshell pill: rounded, a shade lighter than the bar behind it.
|
||||
fn pill(theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(theme.extended_palette().background.weak.color.into()),
|
||||
border: border::rounded(REF_RADIUS),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Bar module registry: constructs every enabled module. Adding a module is
|
||||
//! a new file under `src/` plus one entry in `all()` — core never changes.
|
||||
|
||||
mod audio;
|
||||
mod clock;
|
||||
mod weather;
|
||||
mod workspaces;
|
||||
@@ -13,6 +14,7 @@ pub use weather::*;
|
||||
/// One instance of every enabled module, ready to insert by `id()`.
|
||||
pub fn all(module_config: Table) -> Vec<Box<dyn BarModule>> {
|
||||
vec![
|
||||
Box::new(audio::Audio::new()),
|
||||
Box::new(clock::Clock::new(Some(module_config.clone()))),
|
||||
Box::new(weather::WeatherModule::default()),
|
||||
Box::new(workspaces::Workspaces::new(Some(module_config))),
|
||||
|
||||
Reference in New Issue
Block a user