diff --git a/Cargo.lock b/Cargo.lock index 54481ae1..42d7bd7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -684,6 +684,7 @@ dependencies = [ name = "cosmic-applet-status-line" version = "0.1.0" dependencies = [ + "delegate", "libcosmic", "serde", "serde_json", @@ -1113,6 +1114,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "delegate" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d358e0ec5c59a5e1603b933def447096886121660fc680dc1e64a0753981fe3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derivative" version = "2.2.0" diff --git a/cosmic-applet-status-line/Cargo.toml b/cosmic-applet-status-line/Cargo.toml index eeb2a3eb..6cf43c74 100644 --- a/cosmic-applet-status-line/Cargo.toml +++ b/cosmic-applet-status-line/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +delegate = "0.9" libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["tokio", "wayland", "applet"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/cosmic-applet-status-line/src/bar_widget.rs b/cosmic-applet-status-line/src/bar_widget.rs new file mode 100644 index 00000000..a5799950 --- /dev/null +++ b/cosmic-applet-status-line/src/bar_widget.rs @@ -0,0 +1,142 @@ +use cosmic::{ + iced::{Length, Point, Rectangle}, + iced_native::{ + clipboard::Clipboard, + event::{self, Event}, + layout::{Layout, Limits, Node}, + mouse, + renderer::Style, + touch, + widget::{self, operation::Operation, Tree, Widget}, + Shell, + }, +}; + +use crate::protocol::ClickEvent; + +const BTN_LEFT: u32 = 0x110; +const BTN_RIGHT: u32 = 0x111; +const BTN_MIDDLE: u32 = 0x112; + +/// Wraps a `Row` widget, handling mouse input +pub struct BarWidget<'a, Msg> { + pub row: widget::Row<'a, Msg, cosmic::Renderer>, + pub name_instance: Vec<(Option<&'a str>, Option<&'a str>)>, + pub on_press: fn(ClickEvent) -> Msg, +} + +impl<'a, Msg> Widget for BarWidget<'a, Msg> { + delegate::delegate! { + to self.row { + fn children(&self) -> Vec; + fn diff(&self, tree: &mut Tree); + fn layout(&self, renderer: &cosmic::Renderer, limits: &Limits) -> Node; + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn Operation, + ); + fn draw( + &self, + state: &Tree, + renderer: &mut cosmic::Renderer, + theme: &cosmic::Theme, + style: &Style, + layout: Layout, + cursor_position: Point, + viewport: &Rectangle, + ); + } + } + + fn width(&self) -> Length { + Widget::width(&self.row) + } + + fn height(&self) -> Length { + Widget::height(&self.row) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &cosmic::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Msg>, + ) -> event::Status { + if self.update(&event, layout, cursor_position, shell) == event::Status::Captured { + return event::Status::Captured; + } + self.row.on_event( + tree, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } +} + +impl<'a, Msg> From> for cosmic::Element<'a, Msg> +where + Msg: 'a, +{ + fn from(widget: BarWidget<'a, Msg>) -> cosmic::Element<'a, Msg> { + cosmic::Element::new(widget) + } +} + +impl<'a, Msg> BarWidget<'a, Msg> { + fn update( + &mut self, + event: &Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Msg>, + ) -> event::Status { + let (button, event_code) = match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => (1, BTN_LEFT), + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => (2, BTN_MIDDLE), + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => (3, BTN_RIGHT), + Event::Touch(touch::Event::FingerPressed { .. }) => (1, BTN_LEFT), + _ => { + return event::Status::Ignored; + } + }; + + let Some((n, bounds)) = layout.children().map(|x| x.bounds()).enumerate().find(|(_, bounds)| bounds.contains(cursor_position)) else { + return event::Status::Ignored; + }; + + let (name, instance) = self.name_instance.get(n).cloned().unwrap_or((None, None)); + + // TODO coordinate space? int conversion? + let x = cursor_position.x as u32; + let y = cursor_position.y as u32; + let relative_x = (cursor_position.x - bounds.x) as u32; + let relative_y = (cursor_position.y - bounds.y) as u32; + let width = bounds.width as u32; + let height = bounds.height as u32; + + shell.publish((self.on_press)(ClickEvent { + name: name.map(str::to_owned), + instance: instance.map(str::to_owned), + x, + y, + button, + event: event_code, + relative_x, + relative_y, + width, + height, + })); + + event::Status::Captured + } +} diff --git a/cosmic-applet-status-line/src/main.rs b/cosmic-applet-status-line/src/main.rs index c595ddfe..6ca92f3a 100644 --- a/cosmic-applet-status-line/src/main.rs +++ b/cosmic-applet-status-line/src/main.rs @@ -8,11 +8,14 @@ use cosmic::{ iced_style::application, }; +mod bar_widget; +use bar_widget::BarWidget; mod protocol; #[derive(Debug)] enum Msg { Protocol(protocol::StatusLine), + ClickEvent(protocol::ClickEvent), CloseRequest, } @@ -56,13 +59,35 @@ impl iced::Application for App { println!("{:?}", status_line); self.status_line = status_line; } + Msg::ClickEvent(click_event) => { + println!("{:?}", click_event); + if self.status_line.click_events { + // TODO: pass click event to backend + } + } Msg::CloseRequest => {} } iced::Command::none() } fn view(&self, _id: window::Id) -> cosmic::Element { - iced::widget::row(self.status_line.blocks.iter().map(block_view).collect()).into() + let (block_views, name_instance): (Vec<_>, Vec<_>) = self + .status_line + .blocks + .iter() + .map(|block| { + ( + block_view(block), + (block.name.as_deref(), block.instance.as_deref()), + ) + }) + .unzip(); + BarWidget { + row: iced::widget::row(block_views), + name_instance, + on_press: Msg::ClickEvent, + } + .into() } } diff --git a/cosmic-applet-status-line/src/protocol/mod.rs b/cosmic-applet-status-line/src/protocol/mod.rs index e829ea0c..8f3249fc 100644 --- a/cosmic-applet-status-line/src/protocol/mod.rs +++ b/cosmic-applet-status-line/src/protocol/mod.rs @@ -10,8 +10,8 @@ use std::{ use tokio::sync::mpsc; mod serialization; -pub use serialization::Block; use serialization::Header; +pub use serialization::{Block, ClickEvent}; #[derive(Debug, Default)] pub struct StatusLine { diff --git a/cosmic-applet-status-line/src/protocol/serialization.rs b/cosmic-applet-status-line/src/protocol/serialization.rs index 892c239d..4fe18cfc 100644 --- a/cosmic-applet-status-line/src/protocol/serialization.rs +++ b/cosmic-applet-status-line/src/protocol/serialization.rs @@ -128,15 +128,15 @@ pub struct Block { } #[derive(Clone, Debug, serde::Serialize)] -struct ClickEvent { - name: Option, - instance: Option, - x: u32, - y: u32, - button: u32, - event: u32, - relative_x: u32, - relative_y: u32, - width: u32, - height: u32, +pub struct ClickEvent { + pub name: Option, + pub instance: Option, + pub x: u32, + pub y: u32, + pub button: u32, + pub event: u32, + pub relative_x: u32, + pub relative_y: u32, + pub width: u32, + pub height: u32, }