This commit is contained in:
benstrb 2026-03-21 00:24:18 +01:00
parent 27f8cc3ab8
commit 9bf4c44aa2
4 changed files with 41 additions and 236 deletions

View file

@ -1,9 +1,9 @@
use crate::widget::{Message, PanelWidget}; use crate::widget::{Message, PanelWidget};
use crate::widgets::battery::BatteryWidget; use crate::widgets::battery::BatteryWidget;
use crate::widgets::clock::ClockWidget; use crate::widgets::clock::ClockWidget;
use crate::widgets::focused_window::FocusedWindowWidget;
use crate::widgets::powerbutton::ShutdownWidget; use crate::widgets::powerbutton::ShutdownWidget;
use crate::widgets::spacer::Spacer; use crate::widgets::spacer::Spacer;
use iced::Color; use iced::Color;
use iced::Element; use iced::Element;
use iced::Subscription; use iced::Subscription;
@ -24,6 +24,7 @@ impl App {
Box::new(ShutdownWidget::new()), Box::new(ShutdownWidget::new()),
Box::new(Spacer::new(iced::Length::Fill)), Box::new(Spacer::new(iced::Length::Fill)),
Box::new(BatteryWidget::new()), Box::new(BatteryWidget::new()),
Box::new(FocusedWindowWidget::new()),
], ],
} }
} }

View file

@ -1,28 +1,22 @@
use iced::Color;
use iced::Element;
use iced::Task;
use iced::widget::button;
use iced::widget::container;
use iced::widget::row;
use iced::widget::text;
use iced_layershell::daemon; use iced_layershell::daemon;
use iced_layershell::reexport::Anchor; use iced_layershell::reexport::Anchor;
use iced_layershell::reexport::NewLayerShellSettings;
use iced_layershell::settings::LayerShellSettings; use iced_layershell::settings::LayerShellSettings;
use iced_layershell::settings::Settings; use iced_layershell::settings::Settings;
use iced_layershell::to_layer_message;
use std::collections::HashMap; mod app;
use std::time::Duration; mod widget;
mod widgets;
use app::App;
pub fn main() -> Result<(), iced_layershell::Error> { pub fn main() -> Result<(), iced_layershell::Error> {
daemon(App::default, App::name, App::update, App::view) daemon(App::new, App::name, App::update, App::view)
.style(App::style) .style(App::style)
.theme(App::theme) .theme(App::theme)
.subscription(App::subscription) .subscription(App::subscription)
.settings(Settings { .settings(Settings {
layer_settings: LayerShellSettings { layer_settings: LayerShellSettings {
size: Some((0, 32)), size: Some((0, App::WINDOW_HEIGHT)),
exclusive_zone: 32, exclusive_zone: App::WINDOW_HEIGHT as i32,
anchor: Anchor::Top | Anchor::Left | Anchor::Right, anchor: Anchor::Top | Anchor::Left | Anchor::Right,
start_mode: iced_layershell::settings::StartMode::AllScreens, start_mode: iced_layershell::settings::StartMode::AllScreens,
..Default::default() ..Default::default()
@ -32,224 +26,3 @@ pub fn main() -> Result<(), iced_layershell::Error> {
.run() .run()
} }
struct App {
popups: HashMap<iced::window::Id, PopupKind>,
power_off_widget: PowerOffWidget,
time_widget: TimeWidget,
battery_widget: BatteryWidget,
}
enum PopupKind {
PowerOff,
}
impl Default for App {
fn default() -> Self {
let mut output = Self {
popups: HashMap::new(),
power_off_widget: PowerOffWidget(None, false),
time_widget: Default::default(),
battery_widget: Default::default(),
};
let _ = output.update(Message::Clock);
output
}
}
impl App {
fn name() -> String {
"wayland_panel".into()
}
fn close_popup(&mut self, id: iced::window::Id) -> Task<Message> {
self.popups.remove(&id);
Task::done(Message::RemoveWindow(id))
}
fn view(&self, id: iced::window::Id) -> Element<'_, Message> {
if let Some(kind) = self.popups.get(&id) {
return match kind {
PopupKind::PowerOff => container(
row![
text("Shut down?"),
button("").on_press(Message::PowerOffConfirm),
button("").on_press(Message::PowerOffCancel),
]
.spacing(8)
.align_y(iced::Alignment::Center),
)
.center(iced::Length::Fill)
.into(),
};
}
// default: the bar
let content = row![
self.time_widget.view(),
iced::widget::space().width(iced::Length::Fill),
self.power_off_widget.view(),
iced::widget::space().width(iced::Length::Fill),
self.battery_widget.view(),
]
.padding(iced::Padding::from([0, 5]))
.height(iced::Length::Fill)
.width(iced::Length::Fill)
.align_y(iced::Alignment::Center);
container(content).into()
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Clock => {
self.time_widget.update();
self.battery_widget.update();
Task::none()
}
Message::PowerOff => {
let id = iced::window::Id::unique();
self.popups.insert(id, PopupKind::PowerOff);
self.power_off_widget.0 = Some(id);
Task::done(Message::NewLayerShell {
settings: NewLayerShellSettings {
size: Some((220, 40)),
anchor: Anchor::Top,
layer: iced_layershell::reexport::Layer::Overlay,
keyboard_interactivity:
iced_layershell::reexport::KeyboardInteractivity::OnDemand,
exclusive_zone: None,
..Default::default()
},
id,
})
}
Message::PowerOffConfirm => {
std::process::Command::new("poweroff").spawn().ok();
Task::none()
}
Message::PowerOffCancel => self.close_popup(self.power_off_widget.0.unwrap()),
_ => Task::none(),
}
}
fn style(&self, theme: &iced::Theme) -> iced::theme::Style {
iced::theme::Style {
background_color: Color::TRANSPARENT,
text_color: theme.palette().text,
}
}
fn theme(&self, _id: iced::window::Id) -> iced::Theme {
iced::Theme::GruvboxDark
}
fn subscription(&self) -> iced::Subscription<Message> {
iced::time::every(Duration::from_secs(1)).map(|_| Message::Clock)
}
}
trait PanelWidget {
fn update(&mut self);
fn view(&self) -> Element<'_, Message>;
}
#[derive(Default)]
struct BatteryWidget {
battery: Option<usize>,
}
impl PanelWidget for BatteryWidget {
fn update(&mut self) {
let file_content = std::fs::read_to_string(std::path::Path::new(
"/sys/class/power_supply/BAT0/capacity",
));
let Ok(file_string) = file_content else {
self.battery = None;
return;
};
let file_string = file_string.trim();
let Ok(value) = file_string.parse::<usize>() else {
self.battery = None;
return;
};
self.battery = Some(value);
}
fn view(&self) -> Element<'_, Message> {
match self.battery {
Some(battery) => text!("{} {}", battery.to_string(), Self::get_icon(battery)),
None => text("󰂃"),
}
.into()
}
}
impl BatteryWidget {
fn get_icon(battery: usize) -> &'static str {
match battery {
0..6 => "󰂎",
6..11 => "󰁺",
11..21 => "󰁻",
21..31 => "󰁼",
31..41 => "󰁽",
41..51 => "󰁾",
51..61 => "󰁿",
61..71 => "󰂀",
71..81 => "󰂁",
81..91 => "󰂂",
91..101 => "󰁹",
_ => "󰂃",
}
}
}
#[derive(Default)]
struct TimeWidget {
current_time: chrono::DateTime<chrono::Local>,
}
impl PanelWidget for TimeWidget {
fn update(&mut self) {
self.current_time = chrono::Local::now();
}
fn view(&self) -> Element<'_, Message> {
text!("{}", self.current_time.format("%H:%m")).into()
}
}
#[derive(Default)]
struct PowerOffWidget(Option<iced::window::Id>, bool);
impl PanelWidget for PowerOffWidget {
fn update(&mut self) {
self.1 = !self.1;
}
fn view(&self) -> Element<'_, Message> {
button("")
.on_press(if self.1 {
Message::PowerOffCancel
} else {
Message::PowerOff
})
.into()
}
}
#[to_layer_message(multi)]
#[derive(Debug, Clone)]
pub enum Message {
Clock,
PowerOff,
PowerOffConfirm,
PowerOffCancel,
}

View file

@ -0,0 +1,30 @@
use iced::{Subscription, Task};
use wayland_protocols_wlr::foreign_toplevel;
use crate::widget::PanelWidget;
pub struct FocusedWindowWidget {
focused_window: String,
}
impl FocusedWindowWidget {
pub fn new() -> Self {
Self {
focused_window: foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::Event,
}
}
}
impl PanelWidget for FocusedWindowWidget {
fn update(&mut self, _message: &crate::widget::Message) -> iced::Task<crate::widget::Message> {
Task::none()
}
fn subscribe(&self) -> iced::Subscription<crate::widget::Message> {
Subscription::none()
}
fn view(&self, _id: iced::window::Id) -> iced::Element<'_, crate::widget::Message> {
iced::widget::text!("{}", self.focused_window).into()
}
}

View file

@ -1,4 +1,5 @@
pub mod battery; pub mod battery;
pub mod clock; pub mod clock;
pub mod focused_window;
pub mod powerbutton; pub mod powerbutton;
pub mod spacer; pub mod spacer;