fresh-start
This commit is contained in:
+1
-1
@@ -4,5 +4,5 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { version = "0.14", default-features = false, features = ["wgpu", "wayland", "crisp", "web-colors", "thread-pool", "advanced"] }
|
iced = { version = "0.14", default-features = false, features = ["wgpu", "wayland", "crisp", "web-colors", "thread-pool"] }
|
||||||
iced_layershell = { version = "0.19", default-features = false }
|
iced_layershell = { version = "0.19", default-features = false }
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
//! Widget-tree operation that captures the laid-out bounds of a clickable
|
|
||||||
//! bar module so a popup can be anchored to it.
|
|
||||||
|
|
||||||
use iced::advanced::widget as advanced_widget;
|
|
||||||
use iced::widget::Id;
|
|
||||||
use iced::{Rectangle, Task};
|
|
||||||
|
|
||||||
/// Captures the laid-out bounds of the module with the given id, reported via
|
|
||||||
/// `BoundsFound`.
|
|
||||||
pub fn capture_bounds(label: String) -> Task<crate::Message> {
|
|
||||||
let target = Id::from(label.clone());
|
|
||||||
|
|
||||||
struct FindBounds {
|
|
||||||
target: Id,
|
|
||||||
found: Option<Rectangle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl advanced_widget::Operation<Rectangle> for FindBounds {
|
|
||||||
fn traverse(
|
|
||||||
&mut self,
|
|
||||||
operate: &mut dyn FnMut(&mut dyn advanced_widget::Operation<Rectangle>),
|
|
||||||
) {
|
|
||||||
operate(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn container(&mut self, id: Option<&Id>, bounds: Rectangle) {
|
|
||||||
if self.found.is_none() && id == Some(&self.target) {
|
|
||||||
self.found = Some(bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(&self) -> advanced_widget::operation::Outcome<Rectangle> {
|
|
||||||
match self.found {
|
|
||||||
Some(bounds) => advanced_widget::operation::Outcome::Some(bounds),
|
|
||||||
None => advanced_widget::operation::Outcome::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
advanced_widget::operate(FindBounds {
|
|
||||||
target,
|
|
||||||
found: None,
|
|
||||||
})
|
|
||||||
.map(move |bounds| crate::Message::BoundsFound(label.clone(), bounds))
|
|
||||||
}
|
|
||||||
+42
-182
@@ -1,72 +1,34 @@
|
|||||||
//! Wayland layer-shell status bar built on `iced` + `iced_layershell`.
|
//! barbar — a Wayland layer-shell status bar built on `iced` + `iced_layershell`.
|
||||||
|
//!
|
||||||
|
//! Renders a full-width bar pinned to the top of the screen via the
|
||||||
|
//! `wlr-layer-shell` protocol. Optionally target a specific output by
|
||||||
|
//! passing its name as the first CLI argument.
|
||||||
|
|
||||||
mod bounds;
|
use iced::widget::{row, text};
|
||||||
mod module;
|
use iced::{Alignment, Element, Length, Task, Theme};
|
||||||
mod modules;
|
use iced_layershell::application;
|
||||||
|
use iced_layershell::reexport::Anchor;
|
||||||
use iced::widget::{container, text, Row};
|
use iced_layershell::settings::{LayerShellSettings, StartMode, Settings};
|
||||||
use iced::{window, Alignment, Element, Length, Rectangle, Task, Theme};
|
|
||||||
use iced_layershell::daemon;
|
|
||||||
use iced_layershell::reexport::{Anchor, PopupAnchor, PopupGravity};
|
|
||||||
use iced_layershell::settings::{LayerShellSettings, Settings, StartMode};
|
|
||||||
use iced_layershell::to_layer_message;
|
use iced_layershell::to_layer_message;
|
||||||
|
|
||||||
use crate::module::{ModuleMsg, ModuleState, MODULES};
|
|
||||||
|
|
||||||
/// Height of the bar in logical pixels.
|
/// Height of the bar in logical pixels.
|
||||||
const BAR_HEIGHT: u32 = 36;
|
const BAR_HEIGHT: u32 = 36;
|
||||||
|
|
||||||
const POPUP_W: u32 = 150;
|
/// Optional width of the bar; `None` means "as wide as the output".
|
||||||
const POPUP_H: u32 = 100;
|
const BAR_WIDTH: Option<u32> = None;
|
||||||
|
|
||||||
/// Vertical gap (logical px) between the bar's bottom edge and the popup.
|
pub fn main() -> Result<(), iced_layershell::Error> {
|
||||||
const POPUP_GAP: i32 = 32;
|
// Target a specific output if one was given, otherwise the active one.
|
||||||
|
|
||||||
struct Bar {
|
|
||||||
/// Which button triggered the open popup? None = closed. Keyed by module label.
|
|
||||||
active_popup: Option<String>,
|
|
||||||
/// Surface ID of the currently open popup (for removal).
|
|
||||||
popup_id: Option<window::Id>,
|
|
||||||
/// Live module states, in registry order.
|
|
||||||
modules: Vec<Box<dyn ModuleState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Bar {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
active_popup: None,
|
|
||||||
popup_id: None,
|
|
||||||
modules: MODULES.iter().map(|m| m.new_state()).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[to_layer_message(multi)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum Message {
|
|
||||||
Noop,
|
|
||||||
ModuleClicked(String),
|
|
||||||
BoundsFound(String, Rectangle),
|
|
||||||
ClosePopup(window::Id),
|
|
||||||
Module(String, Box<dyn ModuleMsg>),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn namespace() -> String {
|
|
||||||
String::from("barbar")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), iced_layershell::Error> {
|
|
||||||
let start_mode = match std::env::args().nth(1) {
|
let start_mode = match std::env::args().nth(1) {
|
||||||
Some(output) => StartMode::TargetScreen(output),
|
Some(output) => StartMode::TargetScreen(output),
|
||||||
None => StartMode::Active,
|
None => StartMode::Active,
|
||||||
};
|
};
|
||||||
|
|
||||||
daemon(Bar::default, namespace, update, view)
|
application(Bar::default, namespace, update, view)
|
||||||
.style(style)
|
.style(style)
|
||||||
.subscription(subscription)
|
|
||||||
.settings(Settings {
|
.settings(Settings {
|
||||||
layer_settings: LayerShellSettings {
|
layer_settings: LayerShellSettings {
|
||||||
size: Some((0, BAR_HEIGHT)),
|
size: Some((BAR_WIDTH.unwrap_or(0), BAR_HEIGHT)),
|
||||||
exclusive_zone: BAR_HEIGHT as i32,
|
exclusive_zone: BAR_HEIGHT as i32,
|
||||||
anchor: Anchor::Top | Anchor::Left | Anchor::Right,
|
anchor: Anchor::Top | Anchor::Left | Anchor::Right,
|
||||||
start_mode,
|
start_mode,
|
||||||
@@ -77,145 +39,43 @@ fn main() -> Result<(), iced_layershell::Error> {
|
|||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Folds module subscriptions into one daemon subscription.
|
/// Application state. Empty for now; placeholders will evolve into
|
||||||
fn subscription(bar: &Bar) -> iced::Subscription<Message> {
|
/// workspace/clock/system modules.
|
||||||
iced::Subscription::batch(bar.modules.iter().flat_map(|m| m.subscriptions()))
|
#[derive(Debug, Default)]
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
/// Messages produced by user input, events and subscriptions.
|
||||||
|
#[to_layer_message]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
/// No-op used as a fallback target for pending subscriptions.
|
||||||
|
Noop,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(bar: &mut Bar, msg: Message) -> Task<Message> {
|
fn namespace() -> String {
|
||||||
match msg {
|
String::from("barbar")
|
||||||
Message::ModuleClicked(label) => {
|
|
||||||
// Toggle: close if already open, else open.
|
|
||||||
if bar.active_popup.as_deref() == Some(label.as_str()) {
|
|
||||||
return maybe_close_popup(bar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the widget tree for the module's actual laid-out bounds.
|
|
||||||
// The resulting `BoundsFound` message opens the popup there.
|
|
||||||
bounds::capture_bounds(label)
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::BoundsFound(label, bounds) => {
|
|
||||||
bar.active_popup = Some(label.clone());
|
|
||||||
|
|
||||||
// Anchor rect = the button's bounds, extended below the bar so the
|
|
||||||
// `Bottom` anchor point (bottom-center) sits POPUP_GAP below the
|
|
||||||
// button's bottom edge. With `Bottom` gravity the popup grows
|
|
||||||
// downward from there, leaving a gap under the bar.
|
|
||||||
let (x, y, w, h) = (
|
|
||||||
bounds.x.round() as i32,
|
|
||||||
bounds.y.round() as i32,
|
|
||||||
bounds.width.round() as i32,
|
|
||||||
bounds.height.round() as i32,
|
|
||||||
);
|
|
||||||
let anchor_rect = (x, y, w, h + POPUP_GAP);
|
|
||||||
let settings = iced_layershell::actions::IcedNewPopupSettings::on_current_surface(
|
|
||||||
(POPUP_W, POPUP_H),
|
|
||||||
anchor_rect,
|
|
||||||
)
|
|
||||||
.anchor(PopupAnchor::Bottom)
|
|
||||||
.gravity(PopupGravity::Bottom);
|
|
||||||
|
|
||||||
let (id, task) = Message::popup_open(settings);
|
|
||||||
bar.popup_id = Some(id);
|
|
||||||
|
|
||||||
task
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::Module(label, msg) => {
|
|
||||||
let state = bar
|
|
||||||
.modules
|
|
||||||
.iter_mut()
|
|
||||||
.find(|m| m.label() == label)
|
|
||||||
.expect("unknown module label");
|
|
||||||
state.handle(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::ClosePopup(id) => {
|
|
||||||
if bar.popup_id == Some(id) {
|
|
||||||
bar.popup_id = None;
|
|
||||||
bar.active_popup = None;
|
|
||||||
}
|
|
||||||
Task::done(Message::RemoveWindow(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::Noop => Task::none(),
|
|
||||||
|
|
||||||
// Forward multi-window mutations to their internal handlers.
|
|
||||||
Message::AnchorChange { .. }
|
|
||||||
| Message::SetInputRegion { .. }
|
|
||||||
| Message::AnchorSizeChange { .. }
|
|
||||||
| Message::LayerChange { .. }
|
|
||||||
| Message::MarginChange { .. }
|
|
||||||
| Message::SizeChange { .. }
|
|
||||||
| Message::ExclusiveZoneChange { .. }
|
|
||||||
| Message::KeyboardInteractivityChange { .. }
|
|
||||||
| Message::NewBaseWindow { .. }
|
|
||||||
| Message::NewInputPanel { .. }
|
|
||||||
| Message::NewLayerShell { .. }
|
|
||||||
| Message::NewMenu { .. }
|
|
||||||
| Message::NewPopUp { .. }
|
|
||||||
| Message::RemoveWindow(_)
|
|
||||||
| Message::ForgetLastOutput
|
|
||||||
| Message::VirtualKeyboardPressed { .. } => Task::none(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_close_popup(bar: &mut Bar) -> Task<Message> {
|
fn update(_bar: &mut Bar, _message: Message) -> Task<Message> {
|
||||||
if let Some(id) = bar.popup_id.take() {
|
|
||||||
bar.active_popup = None;
|
|
||||||
return Task::done(Message::RemoveWindow(id));
|
|
||||||
}
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch view by which surface is being rendered.
|
fn view(_bar: &Bar) -> Element<'_, Message> {
|
||||||
fn view(bar: &Bar, id: window::Id) -> Element<'_, Message> {
|
row![
|
||||||
if bar.popup_id == Some(id) {
|
text("workspaces").size(14),
|
||||||
popup_view(bar)
|
text("|").size(14),
|
||||||
} else {
|
text("clock").size(14),
|
||||||
bar_view(bar)
|
text("|").size(14),
|
||||||
}
|
text("system").size(14),
|
||||||
}
|
]
|
||||||
|
.spacing(12)
|
||||||
/// Main bar widget tree.
|
|
||||||
fn bar_view(bar: &Bar) -> Element<'static, Message> {
|
|
||||||
let mut children: Vec<Element<Message>> = Vec::new();
|
|
||||||
|
|
||||||
for (i, module) in bar.modules.iter().enumerate() {
|
|
||||||
children.push(module.button());
|
|
||||||
if i < bar.modules.len() - 1 {
|
|
||||||
children.push(text("|").size(14).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container(
|
|
||||||
Row::from_vec(children)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_y(Alignment::Center),
|
|
||||||
)
|
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.align_x(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Popup widget tree. Rendered on its own floating LayerShell surface.
|
|
||||||
fn popup_view(bar: &Bar) -> Element<'static, Message> {
|
|
||||||
let label = bar
|
|
||||||
.active_popup
|
|
||||||
.as_ref()
|
|
||||||
.expect("popup open without active module");
|
|
||||||
let popup_id = bar.popup_id.expect("popup open without surface id");
|
|
||||||
|
|
||||||
let module = bar
|
|
||||||
.modules
|
|
||||||
.iter()
|
|
||||||
.find(|m| m.label() == label)
|
|
||||||
.expect("unknown module label");
|
|
||||||
|
|
||||||
module.popup(popup_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style(_bar: &Bar, theme: &Theme) -> iced::theme::Style {
|
fn style(_bar: &Bar, theme: &Theme) -> iced::theme::Style {
|
||||||
use iced::theme::Style;
|
use iced::theme::Style;
|
||||||
Style {
|
Style {
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use iced::widget::{container, mouse_area, text, Id};
|
|
||||||
use iced::{window, Alignment, Element, Length, Subscription, Task};
|
|
||||||
|
|
||||||
use crate::Message;
|
|
||||||
|
|
||||||
/// A module's own message type, boxed for transport through [`Message::Module`].
|
|
||||||
pub trait ModuleMsg: Any + Debug + Send + 'static {
|
|
||||||
fn clone_box(&self) -> Box<dyn ModuleMsg>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Box<dyn ModuleMsg> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.clone_box()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Any + Debug + Clone + Send + 'static> ModuleMsg for T {
|
|
||||||
fn clone_box(&self) -> Box<dyn ModuleMsg> {
|
|
||||||
Box::new((*self).clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcast a boxed module message to a concrete type.
|
|
||||||
pub fn downcast<T: Any>(boxed: Box<dyn ModuleMsg>) -> Result<Box<T>, ()> {
|
|
||||||
let erased: Box<dyn Any> = boxed;
|
|
||||||
match erased.downcast::<T>() {
|
|
||||||
Ok(t) => Ok(t),
|
|
||||||
Err(_) => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Static module definition: how to construct the live state.
|
|
||||||
pub trait ModuleDef: Send + Sync {
|
|
||||||
fn new_state(&self) -> Box<dyn ModuleState>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Live, object-safe module state held by the bar.
|
|
||||||
pub trait ModuleState: Send + 'static {
|
|
||||||
/// Stable label used as widget-tree id and message routing key.
|
|
||||||
fn label(&self) -> &'static str;
|
|
||||||
|
|
||||||
/// Stable widget-tree id used for popup bounds capture.
|
|
||||||
fn id(&self) -> Id {
|
|
||||||
Id::from(self.label().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render the module's clickable bar segment.
|
|
||||||
fn button(&self) -> Element<'static, Message> {
|
|
||||||
container(
|
|
||||||
mouse_area(text(self.label()).size(14))
|
|
||||||
.on_press(Message::ModuleClicked(self.label().to_string())),
|
|
||||||
)
|
|
||||||
.id(self.id())
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle a message routed to this module.
|
|
||||||
fn handle(&mut self, msg: Box<dyn ModuleMsg>) -> Task<Message>;
|
|
||||||
|
|
||||||
/// Render the popup body when this module's popup is open.
|
|
||||||
fn popup(&self, popup_id: window::Id) -> Element<'static, Message> {
|
|
||||||
let _ = popup_id;
|
|
||||||
text(format!("{} menu", self.label()))
|
|
||||||
.size(14)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_x(Alignment::Center)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscriptions this module wants to run.
|
|
||||||
fn subscriptions(&self) -> Vec<Subscription<Message>> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registry of all bar modules, in display order.
|
|
||||||
pub static MODULES: &[&'static dyn ModuleDef] = &[
|
|
||||||
&crate::modules::workspaces::Workspaces,
|
|
||||||
&crate::modules::clock::Clock,
|
|
||||||
&crate::modules::system::System,
|
|
||||||
];
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
use iced::widget::{column, mouse_area, text};
|
|
||||||
use iced::{window, Alignment, Element, Length};
|
|
||||||
|
|
||||||
use crate::module::{ModuleDef, ModuleMsg, ModuleState};
|
|
||||||
use crate::Message;
|
|
||||||
|
|
||||||
pub struct Clock;
|
|
||||||
|
|
||||||
pub struct State;
|
|
||||||
|
|
||||||
impl ModuleDef for Clock {
|
|
||||||
fn new_state(&self) -> Box<dyn ModuleState> {
|
|
||||||
Box::new(State)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleState for State {
|
|
||||||
fn label(&self) -> &'static str {
|
|
||||||
"clock"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ponytail: clock has no internal messages yet; the tick subscription
|
|
||||||
// that gives it live time is the upgrade path (add `Msg` + `update`
|
|
||||||
// here when needed).
|
|
||||||
fn handle(&mut self, _msg: Box<dyn ModuleMsg>) -> iced::Task<Message> {
|
|
||||||
iced::Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn popup(&self, popup_id: window::Id) -> Element<'static, Message> {
|
|
||||||
let items: Vec<Element<'static, Message>> = ["Time format", "Date display"]
|
|
||||||
.into_iter()
|
|
||||||
.map(|label| {
|
|
||||||
mouse_area(text(label).size(13))
|
|
||||||
.on_press(Message::ClosePopup(popup_id))
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
column![
|
|
||||||
text("clock menu")
|
|
||||||
.size(14)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_x(Alignment::Center),
|
|
||||||
column(items).spacing(1),
|
|
||||||
mouse_area(text("close").size(12)).on_press(Message::ClosePopup(popup_id)),
|
|
||||||
]
|
|
||||||
.spacing(1)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod clock;
|
|
||||||
pub mod system;
|
|
||||||
pub mod workspaces;
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
use iced::widget::{column, mouse_area, text};
|
|
||||||
use iced::{window, Alignment, Element, Length};
|
|
||||||
|
|
||||||
use crate::module::{ModuleDef, ModuleMsg, ModuleState};
|
|
||||||
use crate::Message;
|
|
||||||
|
|
||||||
pub struct System;
|
|
||||||
|
|
||||||
pub struct State;
|
|
||||||
|
|
||||||
impl ModuleDef for System {
|
|
||||||
fn new_state(&self) -> Box<dyn ModuleState> {
|
|
||||||
Box::new(State)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleState for State {
|
|
||||||
fn label(&self) -> &'static str {
|
|
||||||
"system"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ponytail: no internal messages yet; real brightness/volume/power
|
|
||||||
// actions (each with its own `Msg` variant) are the upgrade path.
|
|
||||||
fn handle(&mut self, _msg: Box<dyn ModuleMsg>) -> iced::Task<Message> {
|
|
||||||
iced::Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn popup(&self, popup_id: window::Id) -> Element<'static, Message> {
|
|
||||||
let items: Vec<Element<'static, Message>> = ["Brightness", "Volume", "Power"]
|
|
||||||
.into_iter()
|
|
||||||
.map(|label| {
|
|
||||||
mouse_area(text(label).size(13))
|
|
||||||
.on_press(Message::ClosePopup(popup_id))
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
column![
|
|
||||||
text("system menu")
|
|
||||||
.size(14)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_x(Alignment::Center),
|
|
||||||
column(items).spacing(1),
|
|
||||||
mouse_area(text("close").size(12)).on_press(Message::ClosePopup(popup_id)),
|
|
||||||
]
|
|
||||||
.spacing(1)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
use iced::widget::{column, mouse_area, text, Row};
|
|
||||||
use iced::{window, Alignment, Element, Length, Task};
|
|
||||||
|
|
||||||
use crate::module::{downcast, ModuleDef, ModuleMsg, ModuleState};
|
|
||||||
use crate::Message;
|
|
||||||
|
|
||||||
pub struct Workspaces;
|
|
||||||
|
|
||||||
pub struct State {
|
|
||||||
workspaces: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Msg {
|
|
||||||
Select(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleDef for Workspaces {
|
|
||||||
fn new_state(&self) -> Box<dyn ModuleState> {
|
|
||||||
Box::new(State {
|
|
||||||
workspaces: vec!["1".into(), "2".into(), "3".into(), "4".into()],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleState for State {
|
|
||||||
fn label(&self) -> &'static str {
|
|
||||||
"workspaces"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Box<dyn ModuleMsg>) -> Task<Message> {
|
|
||||||
let msg = *downcast::<Msg>(msg).expect("workspaces module received foreign message");
|
|
||||||
match msg {
|
|
||||||
Msg::Select(ws) => {
|
|
||||||
self.workspaces.sort_by_key(|w| w != &ws);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn popup(&self, popup_id: window::Id) -> Element<'static, Message> {
|
|
||||||
let items: Vec<Element<'static, Message>> = self
|
|
||||||
.workspaces
|
|
||||||
.iter()
|
|
||||||
.map(|ws| {
|
|
||||||
mouse_area(text(ws.clone()).size(13))
|
|
||||||
.on_press(Message::Module(
|
|
||||||
"workspaces".into(),
|
|
||||||
Box::new(Msg::Select(ws.clone())),
|
|
||||||
))
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
column![
|
|
||||||
text("workspaces menu")
|
|
||||||
.size(14)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_x(Alignment::Center),
|
|
||||||
Row::from_vec(items).spacing(1),
|
|
||||||
mouse_area(text("close").size(12)).on_press(Message::ClosePopup(popup_id)),
|
|
||||||
]
|
|
||||||
.spacing(1)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user