From aac43de65ddc1765a7d8aaf93dfa1e610e6ef4f0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 5 Jul 2022 14:41:09 -0700 Subject: [PATCH] Separate `CosmicAppletWindow` and `CosmicAppletButton` --- Cargo.lock | 1 + .../cosmic-app-list/src/apps_container/mod.rs | 5 +- applets/cosmic-app-list/src/dock_list/imp.rs | 4 +- applets/cosmic-applet-audio/src/main.rs | 151 +++++++------- applets/cosmic-applet-battery/src/main.rs | 185 +++++++++--------- applets/cosmic-applet-graphics/src/main.rs | 4 +- .../cosmic-applet-notifications/Cargo.toml | 1 + .../cosmic-applet-notifications/src/main.rs | 24 +-- applets/cosmic-applet-power/src/main.rs | 49 ++--- applets/cosmic-applet-workspaces/src/main.rs | 2 +- .../cosmic-applet-workspaces/src/wayland.rs | 21 +- .../src/workspace_button/mod.rs | 4 +- .../src/workspace_list/mod.rs | 4 +- libcosmic-applet/src/button.rs | 126 ++++++++++++ libcosmic-applet/src/lib.rs | 125 +----------- libcosmic-applet/src/window.rs | 58 ++++++ 16 files changed, 421 insertions(+), 343 deletions(-) create mode 100644 libcosmic-applet/src/button.rs create mode 100644 libcosmic-applet/src/window.rs diff --git a/Cargo.lock b/Cargo.lock index a6a8f2cb..3a81ae20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,6 +401,7 @@ dependencies = [ "gtk4", "libcosmic-applet", "once_cell", + "relm4-macros", "serde", "zbus", "zbus_names", diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs index 3fc4a7c4..95ef3c33 100644 --- a/applets/cosmic-app-list/src/apps_container/mod.rs +++ b/applets/cosmic-app-list/src/apps_container/mod.rs @@ -5,8 +5,8 @@ use crate::dock_list::DockList; use crate::dock_list::DockListType; use crate::utils::Event; use cascade::cascade; -use cosmic_panel_config::config::PanelAnchor; use cosmic_panel_config::config::CosmicPanelConfig; +use cosmic_panel_config::config::PanelAnchor; use gtk4::prelude::*; use gtk4::subclass::prelude::*; use gtk4::Orientation; @@ -68,13 +68,12 @@ impl AppsContainer { // Setup self_.setup_callbacks(); self_.set_position(config.anchor); - Self::setup_callbacks(&self_); self_ } - + pub fn model(&self, type_: DockListType) -> &gio::ListStore { // Get state let imp = imp::AppsContainer::from_instance(self); diff --git a/applets/cosmic-app-list/src/dock_list/imp.rs b/applets/cosmic-app-list/src/dock_list/imp.rs index 6665e694..1104ad04 100644 --- a/applets/cosmic-app-list/src/dock_list/imp.rs +++ b/applets/cosmic-app-list/src/dock_list/imp.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0-only -use cosmic_panel_config::config::{PanelAnchor, CosmicPanelConfig}; +use cosmic_panel_config::config::{CosmicPanelConfig, PanelAnchor}; use glib::SignalHandlerId; use gtk4::subclass::prelude::*; use gtk4::{gio, glib}; @@ -25,7 +25,7 @@ pub struct DockList { pub popover_menu_index: Rc>>, pub position: Rc>, pub tx: OnceCell>, - pub config: OnceCell + pub config: OnceCell, } #[glib::object_subclass] diff --git a/applets/cosmic-applet-audio/src/main.rs b/applets/cosmic-applet-audio/src/main.rs index 938e1956..b1881f0a 100644 --- a/applets/cosmic-applet-audio/src/main.rs +++ b/applets/cosmic-applet-audio/src/main.rs @@ -76,90 +76,93 @@ fn app(application: &Application) { }); pa.connect().unwrap(); // XXX unwrap view! { - window = libcosmic_applet::Applet { + window = libcosmic_applet::AppletWindow { set_application: Some(application), set_title: Some("COSMIC Network Applet"), - // TODO: adjust based on volume, mute - set_button_icon_name: "multimedia-volume-control-symbolic", #[wrap(Some)] - set_popover_child: window_box = &GtkBox { - set_orientation: Orientation::Vertical, - set_spacing: 24, - append: output_box = &GtkBox { - set_orientation: Orientation::Horizontal, - set_spacing: 16, - append: output_icon = &Image { - set_icon_name: Some("audio-speakers-symbolic"), - }, - append: output_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) { - set_format_value_func: |_, value| { - format!("{:.0}%", value) - }, - set_value_pos: PositionType::Right, - set_hexpand: true - } - }, - append: input_box = &GtkBox { - set_orientation: Orientation::Horizontal, - set_spacing: 16, - append: input_icon = &Image { - set_icon_name: Some("audio-input-microphone-symbolic"), - }, - append: input_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) { - set_format_value_func: |_, value| { - format!("{:.0}%", value) - }, - set_value_pos: PositionType::Right, - set_hexpand: true - } - }, - append: _sep = &Separator { - set_orientation: Orientation::Horizontal, - }, - append: output_list_box = &GtkBox { + set_child = &libcosmic_applet::AppletButton { + // TODO: adjust based on volume, mute + set_button_icon_name: "multimedia-volume-control-symbolic", + #[wrap(Some)] + set_popover_child: window_box = &GtkBox { set_orientation: Orientation::Vertical, - append: current_output_button = &Button { - #[wrap(Some)] - set_child: current_output = &Label {}, - connect_clicked[outputs_revealer] => move |_| { - outputs_revealer.set_reveal_child(!outputs_revealer.reveals_child()); + set_spacing: 24, + append: output_box = &GtkBox { + set_orientation: Orientation::Horizontal, + set_spacing: 16, + append: output_icon = &Image { + set_icon_name: Some("audio-speakers-symbolic"), + }, + append: output_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) { + set_format_value_func: |_, value| { + format!("{:.0}%", value) + }, + set_value_pos: PositionType::Right, + set_hexpand: true } }, - append: outputs_revealer = &Revealer { - set_transition_type: RevealerTransitionType::SlideDown, - #[wrap(Some)] - set_child: outputs = &ListBox { - set_selection_mode: SelectionMode::None, - set_activate_on_single_click: true - } - } - }, - append: _sep = &Separator { - set_orientation: Orientation::Horizontal, - }, - append: input_list_box = &GtkBox { - set_orientation: Orientation::Vertical, - append: current_input_button = &Button { - #[wrap(Some)] - set_child: current_input = &Label {}, - connect_clicked[inputs_revealer] => move |_| { - inputs_revealer.set_reveal_child(!inputs_revealer.reveals_child()); + append: input_box = &GtkBox { + set_orientation: Orientation::Horizontal, + set_spacing: 16, + append: input_icon = &Image { + set_icon_name: Some("audio-input-microphone-symbolic"), + }, + append: input_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) { + set_format_value_func: |_, value| { + format!("{:.0}%", value) + }, + set_value_pos: PositionType::Right, + set_hexpand: true } }, - append: inputs_revealer = &Revealer { - set_transition_type: RevealerTransitionType::SlideDown, - #[wrap(Some)] - set_child: inputs = &ListBox { - set_selection_mode: SelectionMode::None, - set_activate_on_single_click: true + append: _sep = &Separator { + set_orientation: Orientation::Horizontal, + }, + append: output_list_box = &GtkBox { + set_orientation: Orientation::Vertical, + append: current_output_button = &Button { + #[wrap(Some)] + set_child: current_output = &Label {}, + connect_clicked[outputs_revealer] => move |_| { + outputs_revealer.set_reveal_child(!outputs_revealer.reveals_child()); + } + }, + append: outputs_revealer = &Revealer { + set_transition_type: RevealerTransitionType::SlideDown, + #[wrap(Some)] + set_child: outputs = &ListBox { + set_selection_mode: SelectionMode::None, + set_activate_on_single_click: true + } } + }, + append: _sep = &Separator { + set_orientation: Orientation::Horizontal, + }, + append: input_list_box = &GtkBox { + set_orientation: Orientation::Vertical, + append: current_input_button = &Button { + #[wrap(Some)] + set_child: current_input = &Label {}, + connect_clicked[inputs_revealer] => move |_| { + inputs_revealer.set_reveal_child(!inputs_revealer.reveals_child()); + } + }, + append: inputs_revealer = &Revealer { + set_transition_type: RevealerTransitionType::SlideDown, + #[wrap(Some)] + set_child: inputs = &ListBox { + set_selection_mode: SelectionMode::None, + set_activate_on_single_click: true + } + } + }, + append: _sep = &Separator { + set_orientation: Orientation::Horizontal, + }, + append: playing_apps = &ListBox { + set_selection_mode: SelectionMode::None, } - }, - append: _sep = &Separator { - set_orientation: Orientation::Horizontal, - }, - append: playing_apps = &ListBox { - set_selection_mode: SelectionMode::None, } } } diff --git a/applets/cosmic-applet-battery/src/main.rs b/applets/cosmic-applet-battery/src/main.rs index 616712ed..8f17ea66 100644 --- a/applets/cosmic-applet-battery/src/main.rs +++ b/applets/cosmic-applet-battery/src/main.rs @@ -77,115 +77,118 @@ impl SimpleComponent for AppModel { type Output = (); view! { - libcosmic_applet::Applet { - #[watch] - set_button_icon_name: &model.icon_name, + libcosmic_applet::AppletWindow { #[wrap(Some)] - set_popover_child = >k4::Box { - set_orientation: gtk4::Orientation::Vertical, + set_child = &libcosmic_applet::AppletButton { + #[watch] + set_button_icon_name: &model.icon_name, + #[wrap(Some)] + set_popover_child = >k4::Box { + set_orientation: gtk4::Orientation::Vertical, - // Battery - gtk4::Box { - set_orientation: gtk4::Orientation::Horizontal, - gtk4::Image { - #[watch] - set_icon_name: Some(&model.icon_name), - }, + // Battery gtk4::Box { - set_orientation: gtk4::Orientation::Vertical, - gtk4::Label { - set_halign: gtk4::Align::Start, - set_label: "Battery", - }, - gtk4::Label { - set_halign: gtk4::Align::Start, - // XXX time to full, fully changed, etc. + set_orientation: gtk4::Orientation::Horizontal, + gtk4::Image { #[watch] - set_label: &format!("{} until empty ({:.0}%)", format_duration(model.time_remaining), model.battery_percent), + set_icon_name: Some(&model.icon_name), + }, + gtk4::Box { + set_orientation: gtk4::Orientation::Vertical, + gtk4::Label { + set_halign: gtk4::Align::Start, + set_label: "Battery", + }, + gtk4::Label { + set_halign: gtk4::Align::Start, + // XXX time to full, fully changed, etc. + #[watch] + set_label: &format!("{} until empty ({:.0}%)", format_duration(model.time_remaining), model.battery_percent), + }, }, }, - }, - gtk4::Separator { - }, + gtk4::Separator { + }, - // Limit charging - gtk4::Box { - set_orientation: gtk4::Orientation::Horizontal, + // Limit charging gtk4::Box { - set_orientation: gtk4::Orientation::Vertical, - gtk4::Label { - set_halign: gtk4::Align::Start, - set_label: "Limit Battery Charging", + set_orientation: gtk4::Orientation::Horizontal, + gtk4::Box { + set_orientation: gtk4::Orientation::Vertical, + gtk4::Label { + set_halign: gtk4::Align::Start, + set_label: "Limit Battery Charging", + }, + gtk4::Label { + set_halign: gtk4::Align::Start, + set_label: "Increase the lifespan of your battery by setting a maximum charge value of 80%." + }, + }, + gtk4::Switch { + set_valign: gtk4::Align::Center, + }, + }, + + gtk4::Separator { + }, + + // Brightness + gtk4::Box { + #[watch] + set_visible: model.backlight.is_some(), + set_orientation: gtk4::Orientation::Horizontal, + gtk4::Image { + set_icon_name: Some("display-brightness-symbolic"), + }, + gtk4::Scale { + set_hexpand: true, + set_adjustment: >k4::Adjustment::new(0., 0., 1., 1., 1., 0.), + #[watch] + set_value: model.display_brightness, + connect_change_value[sender] => move |_, _, value| { + sender.input(AppMsg::SetDisplayBrightness(value)); + gtk4::Inhibit(false) + }, }, gtk4::Label { - set_halign: gtk4::Align::Start, - set_label: "Increase the lifespan of your battery by setting a maximum charge value of 80%." + #[watch] + set_label: &format!("{:.0}%", model.display_brightness * 100.), }, }, - gtk4::Switch { - set_valign: gtk4::Align::Center, - }, - }, - - gtk4::Separator { - }, - - // Brightness - gtk4::Box { - #[watch] - set_visible: model.backlight.is_some(), - set_orientation: gtk4::Orientation::Horizontal, - gtk4::Image { - set_icon_name: Some("display-brightness-symbolic"), - }, - gtk4::Scale { - set_hexpand: true, - set_adjustment: >k4::Adjustment::new(0., 0., 1., 1., 1., 0.), + gtk4::Box { #[watch] - set_value: model.display_brightness, - connect_change_value[sender] => move |_, _, value| { - sender.input(AppMsg::SetDisplayBrightness(value)); - gtk4::Inhibit(false) + set_visible: model.kbd_backlight.is_some(), + set_orientation: gtk4::Orientation::Horizontal, + gtk4::Image { + set_icon_name: Some("keyboard-brightness-symbolic"), + }, + gtk4::Scale { + set_hexpand: true, + set_adjustment: >k4::Adjustment::new(0., 0., 1., 1., 1., 0.), + #[watch] + set_value: model.keyboard_brightness, + connect_change_value[sender] => move |_, _, value| { + sender.input(AppMsg::SetKeyboardBrightness(value)); + gtk4::Inhibit(false) + }, + }, + gtk4::Label { + #[watch] + set_label: &format!("{:.0}%", model.keyboard_brightness * 100.), }, }, - gtk4::Label { - #[watch] - set_label: &format!("{:.0}%", model.display_brightness * 100.), - }, - }, - gtk4::Box { - #[watch] - set_visible: model.kbd_backlight.is_some(), - set_orientation: gtk4::Orientation::Horizontal, - gtk4::Image { - set_icon_name: Some("keyboard-brightness-symbolic"), - }, - gtk4::Scale { - set_hexpand: true, - set_adjustment: >k4::Adjustment::new(0., 0., 1., 1., 1., 0.), - #[watch] - set_value: model.keyboard_brightness, - connect_change_value[sender] => move |_, _, value| { - sender.input(AppMsg::SetKeyboardBrightness(value)); - gtk4::Inhibit(false) - }, - }, - gtk4::Label { - #[watch] - set_label: &format!("{:.0}%", model.keyboard_brightness * 100.), - }, - }, - gtk4::Separator { - }, + gtk4::Separator { + }, - gtk4::Button { - set_label: "Power Settings...", - connect_clicked => move |_| { - // XXX open subpanel - let _ = Command::new("cosmic-settings").spawn(); - // TODO hide + gtk4::Button { + set_label: "Power Settings...", + connect_clicked => move |_| { + // XXX open subpanel + let _ = Command::new("cosmic-settings").spawn(); + // TODO hide + } } } } diff --git a/applets/cosmic-applet-graphics/src/main.rs b/applets/cosmic-applet-graphics/src/main.rs index dc70515b..b95ed7d1 100644 --- a/applets/cosmic-applet-graphics/src/main.rs +++ b/applets/cosmic-applet-graphics/src/main.rs @@ -10,6 +10,7 @@ pub mod graphics; pub mod mode_box; use self::{dbus::PowerDaemonProxy, graphics::Graphics, mode_box::ModeSelection}; +use cosmic_panel_config::config::CosmicPanelConfig; use gtk4::{ gdk::Display, gio::ApplicationFlags, @@ -20,7 +21,6 @@ use gtk4::{ }; use once_cell::sync::Lazy; use tokio::runtime::Runtime; -use cosmic_panel_config::config::CosmicPanelConfig; static RT: Lazy = Lazy::new(|| Runtime::new().expect("failed to build tokio runtime")); @@ -88,7 +88,7 @@ fn build_ui(application: >k4::Application) { image.add_css_class("panel_icon"); image.set_pixel_size(config.get_applet_icon_size().try_into().unwrap()); button.set_child(Some(&image)); - let current_graphics = RT + let current_graphics = RT .block_on(get_current_graphics()) .expect("failed to connect to system76-power"); view! { diff --git a/applets/cosmic-applet-notifications/Cargo.toml b/applets/cosmic-applet-notifications/Cargo.toml index bf00089c..bc22b597 100644 --- a/applets/cosmic-applet-notifications/Cargo.toml +++ b/applets/cosmic-applet-notifications/Cargo.toml @@ -10,6 +10,7 @@ futures = "0.3" gtk4 = { git = "https://github.com/gtk-rs/gtk4-rs" } libcosmic-applet = { path = "../../libcosmic-applet" } once_cell = "1.12" +relm4-macros = { git = "https://github.com/Relm4/Relm4.git", branch = "next" } serde = "1" zbus = "2.0.1" zbus_names = "2" diff --git a/applets/cosmic-applet-notifications/src/main.rs b/applets/cosmic-applet-notifications/src/main.rs index e9dc63d7..e31923ed 100644 --- a/applets/cosmic-applet-notifications/src/main.rs +++ b/applets/cosmic-applet-notifications/src/main.rs @@ -1,5 +1,5 @@ -use cascade::cascade; use gtk4::{glib, prelude::*}; +use relm4_macros::view; mod dbus_service; mod deref_cell; @@ -19,18 +19,20 @@ fn main() { let notification_list = NotificationList::new(¬ifications); - let window = cascade! { - libcosmic_applet::Applet::new(); - ..set_button_icon_name("user-invisible-symbolic"); // TODO - ..set_popover_child(Some(¬ification_list)); - ..show(); - }; + view! { + window = libcosmic_applet::AppletWindow { + #[wrap(Some)] + set_child: applet_button = &libcosmic_applet::AppletButton { + set_button_icon_name: "user-invisible-symbolic", // TODO + set_popover_child: Some(¬ification_list) + } + } + } + window.show(); // XXX show in correct place - cascade! { - NotificationPopover::new(¬ifications); - ..set_parent(&window.child().unwrap()); // XXX better way? - }; + let notification_popover = NotificationPopover::new(¬ifications); + notification_popover.set_parent(&applet_button); let main_loop = glib::MainLoop::new(None, false); main_loop.run(); diff --git a/applets/cosmic-applet-power/src/main.rs b/applets/cosmic-applet-power/src/main.rs index 0f131f37..d4bab867 100644 --- a/applets/cosmic-applet-power/src/main.rs +++ b/applets/cosmic-applet-power/src/main.rs @@ -24,34 +24,37 @@ fn main() { fn build_ui(application: >k4::Application) { view! { - window = libcosmic_applet::Applet { + window = libcosmic_applet::AppletWindow { set_title: Some("COSMIC Power Applet"), set_application: Some(application), // TODO adjust battery icon based on charge - set_button_icon_name: "system-shutdown-symbolic", #[wrap(Some)] - set_popover_child: main_box = >k4::Box { - set_orientation: Orientation::Vertical, - set_spacing: 10, - set_margin_top: 20, - set_margin_bottom: 20, - set_margin_start: 24, - set_margin_end: 24, - append: settings_button = &Button { - #[wrap(Some)] - set_child = &Label { - set_label: "Settings...", - set_halign: Align::Start, - set_hexpand: true + set_child = &libcosmic_applet::AppletButton { + set_button_icon_name: "system-shutdown-symbolic", + #[wrap(Some)] + set_popover_child: main_box = >k4::Box { + set_orientation: Orientation::Vertical, + set_spacing: 10, + set_margin_top: 20, + set_margin_bottom: 20, + set_margin_start: 24, + set_margin_end: 24, + append: settings_button = &Button { + #[wrap(Some)] + set_child = &Label { + set_label: "Settings...", + set_halign: Align::Start, + set_hexpand: true + }, + connect_clicked => move |_| { + let _ = Command::new("cosmic-settings").spawn(); + } }, - connect_clicked => move |_| { - let _ = Command::new("cosmic-settings").spawn(); - } - }, - append = &Separator {}, - append: &ui::session::build(), - append: second_separator = &Separator {}, - append: &ui::system::build(), + append = &Separator {}, + append: &ui::session::build(), + append: second_separator = &Separator {}, + append: &ui::system::build(), + } } } } diff --git a/applets/cosmic-applet-workspaces/src/main.rs b/applets/cosmic-applet-workspaces/src/main.rs index 681a452b..5a715a5a 100644 --- a/applets/cosmic-applet-workspaces/src/main.rs +++ b/applets/cosmic-applet-workspaces/src/main.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0-only +use calloop::channel::SyncSender; use gtk4::{ gdk::Display, gio::{self, ApplicationFlags}, @@ -13,7 +14,6 @@ use tokio::sync::mpsc; use utils::{Activate, WorkspaceEvent}; use wayland::State; use window::CosmicWorkspacesWindow; -use calloop::channel::SyncSender; mod localize; mod utils; diff --git a/applets/cosmic-applet-workspaces/src/wayland.rs b/applets/cosmic-applet-workspaces/src/wayland.rs index 06485485..59561afd 100644 --- a/applets/cosmic-applet-workspaces/src/wayland.rs +++ b/applets/cosmic-applet-workspaces/src/wayland.rs @@ -92,8 +92,8 @@ pub fn spawn_workspaces(tx: glib::Sender) -> SyncSender { running: true, }; let loop_handle = event_loop.handle(); - loop_handle.insert_source(workspaces_rx, |e, _, state| { - match e { + loop_handle + .insert_source(workspaces_rx, |e, _, state| match e { Event::Msg(WorkspaceEvent::Activate(id)) => { if let Some(w) = state .workspace_groups @@ -136,15 +136,16 @@ pub fn spawn_workspaces(tx: glib::Sender) -> SyncSender { } } } - Event::Closed => if let Some(workspace_manager) = &mut state.workspace_manager { - for g in &mut state.workspace_groups { - g.workspace_group_handle.destroy(); + Event::Closed => { + if let Some(workspace_manager) = &mut state.workspace_manager { + for g in &mut state.workspace_groups { + g.workspace_group_handle.destroy(); + } + workspace_manager.stop(); } - workspace_manager.stop(); - }, - } - - }).unwrap(); + } + }) + .unwrap(); while state.running { event_loop .dispatch(Duration::from_millis(16), &mut state) diff --git a/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs index 4b8d1c03..4c447474 100644 --- a/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs +++ b/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs @@ -43,9 +43,7 @@ impl WorkspaceButton { new_button.connect_clicked(move |_| { let id_clone = id.clone(); if !is_active { - let _ = TX.get() - .unwrap() - .send(WorkspaceEvent::Activate(id_clone)); + let _ = TX.get().unwrap().send(WorkspaceEvent::Activate(id_clone)); } }); diff --git a/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs index 0fe7b019..318bb56f 100644 --- a/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs +++ b/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs @@ -61,9 +61,7 @@ impl WorkspaceList { .build(); scroll_controller.connect_scroll(|_, dx, dy| { - let _ = TX.get() - .unwrap() - .send(WorkspaceEvent::Scroll(dx + dy)); + let _ = TX.get().unwrap().send(WorkspaceEvent::Scroll(dx + dy)); Inhibit::default() }); diff --git a/libcosmic-applet/src/button.rs b/libcosmic-applet/src/button.rs new file mode 100644 index 00000000..73626fdd --- /dev/null +++ b/libcosmic-applet/src/button.rs @@ -0,0 +1,126 @@ +use cosmic_panel_config::config::CosmicPanelConfig; +use gtk4::{glib, prelude::*, subclass::prelude::*}; +use relm4_macros::view; + +use crate::deref_cell::DerefCell; + +static STYLE: &str = " +button.cosmic_applet_button { + border-radius: 12px; + transition: 100ms; + padding: 4px; + border-color: transparent; + background: transparent; + outline-color: transparent; +} +"; + +#[derive(Default)] +pub struct AppletButtonInner { + menu_button: DerefCell, + panel_config: DerefCell, + popover: DerefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for AppletButtonInner { + const NAME: &'static str = "CosmicAppletButton"; + type Type = AppletButton; + type ParentType = gtk4::Widget; +} + +impl ObjectImpl for AppletButtonInner { + fn constructed(&self, obj: &AppletButton) { + view! { + menu_button = gtk4::MenuButton { + set_parent: obj, + add_css_class: "cosmic_applet_button", + set_has_frame: false, + #[wrap(Some)] + set_popover: popover = >k4::Popover { + // TODO: change if it can be positioned correctly? + set_has_arrow: false, + } + }, + provider = gtk4::CssProvider { + load_from_data: STYLE.as_bytes(), + } + } + obj.set_layout_manager(Some(>k4::BinLayout::new())); + obj.style_context() + .add_provider(&provider, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION); + + self.menu_button.set(menu_button); + self.popover.set(popover); + self.panel_config + .set(CosmicPanelConfig::load_from_env().unwrap_or_default()); + } + + fn dispose(&self, _obj: &AppletButton) { + self.menu_button.unparent(); + } +} + +impl WidgetImpl for AppletButtonInner { + fn compute_expand(&self, _obj: &AppletButton, hexpand: &mut bool, vexpand: &mut bool) { + *hexpand = self + .menu_button + .compute_expand(gtk4::Orientation::Horizontal); + *vexpand = self.menu_button.compute_expand(gtk4::Orientation::Vertical); + } + + fn request_mode(&self, _obj: &AppletButton) -> gtk4::SizeRequestMode { + self.menu_button.request_mode() + } +} + +impl WindowImpl for AppletButtonInner {} + +glib::wrapper! { + pub struct AppletButton(ObjectSubclass) + @extends gtk4::Widget; +} + +impl Default for AppletButton { + fn default() -> Self { + Self::new() + } +} + +impl AppletButton { + pub fn new() -> Self { + glib::Object::new(&[]).unwrap() + } + + fn inner(&self) -> &AppletButtonInner { + AppletButtonInner::from_instance(self) + } + + // TODO: avoid multiple instances? + pub fn panel_config(&self) -> &CosmicPanelConfig { + &*self.inner().panel_config + } + + pub fn set_button_child(&self, child: Option<&impl IsA>) { + self.inner().menu_button.set_child(child); + } + + pub fn set_button_icon_name(&self, name: &str) { + let image = gtk4::Image::from_icon_name(name); + image.set_pixel_size( + self.panel_config() + .get_applet_icon_size() + .try_into() + .unwrap(), + ); // XXX unwrap + self.set_button_child(Some(&image)); + } + + pub fn set_button_label(&self, label: &str) { + self.inner().menu_button.set_label(label); + } + + pub fn set_popover_child(&self, child: Option<&impl IsA>) { + self.inner().popover.set_child(child); + } +} diff --git a/libcosmic-applet/src/lib.rs b/libcosmic-applet/src/lib.rs index 10d01904..6e2b20b6 100644 --- a/libcosmic-applet/src/lib.rs +++ b/libcosmic-applet/src/lib.rs @@ -1,9 +1,8 @@ -use cosmic_panel_config::config::CosmicPanelConfig; -use gtk4::{glib, prelude::*, subclass::prelude::*}; -use relm4_macros::view; - +mod button; +pub use button::AppletButton; mod deref_cell; -use deref_cell::DerefCell; +mod window; +pub use window::AppletWindow; // TODO make sure style fits different panel colors? // TODO abstraction to start main loop? Work with relm4. @@ -11,118 +10,4 @@ use deref_cell::DerefCell; // TODO orientation, etc. // TODO make image size dependent on CosmicPanelConfig? // TODO way to have multiple applets with this style, for system tray. - -static STYLE: &str = " -window.cosmic_applet_window { - background: transparent; -} - -button.cosmic_applet_button { - border-radius: 12px; - transition: 100ms; - padding: 4px; - border-color: transparent; - background: transparent; - outline-color: transparent; -} -"; - -#[derive(Default)] -pub struct AppletInner { - panel_config: DerefCell, - menu_button: DerefCell, - popover: DerefCell, -} - -#[glib::object_subclass] -impl ObjectSubclass for AppletInner { - const NAME: &'static str = "CosmicApplet"; - type Type = Applet; - type ParentType = gtk4::Window; -} - -impl ObjectImpl for AppletInner { - fn constructed(&self, obj: &Applet) { - let window = || obj; - view! { - window() { - add_css_class: "cosmic_applet_window", - set_decorated: false, - set_resizable: false, - set_width_request: 1, - set_height_request: 1, - #[wrap(Some)] - set_child: menu_button = >k4::MenuButton { - add_css_class: "cosmic_applet_button", - set_has_frame: false, - #[wrap(Some)] - set_popover: popover = >k4::Popover { - // TODO: change if it can be positioned correctly? - set_has_arrow: false, - } - } - } - } - - let provider = gtk4::CssProvider::new(); - provider.load_from_data(STYLE.as_bytes()); - obj.style_context() - .add_provider(&provider, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION); - - self.menu_button.set(menu_button); - self.popover.set(popover); - self.panel_config - .set(CosmicPanelConfig::load_from_env().unwrap_or_default()); - } -} - -impl WidgetImpl for AppletInner {} -impl WindowImpl for AppletInner {} - -glib::wrapper! { - pub struct Applet(ObjectSubclass) - @extends gtk4::Widget, gtk4::Window; -} - -impl Default for Applet { - fn default() -> Self { - Self::new() - } -} - -impl Applet { - pub fn new() -> Self { - glib::Object::new(&[]).unwrap() - } - - fn inner(&self) -> &AppletInner { - AppletInner::from_instance(self) - } - - pub fn panel_config(&self) -> &CosmicPanelConfig { - &*self.inner().panel_config - } - - pub fn set_button_child(&self, child: Option<&impl IsA>) { - self.inner().menu_button.set_child(child); - } - - pub fn set_button_icon_name(&self, name: &str) { - let image = gtk4::Image::from_icon_name(name); - image.set_pixel_size( - self.panel_config() - .get_applet_icon_size() - .try_into() - .unwrap(), - ); // XXX unwrap - self.set_button_child(Some(&image)); - } - - pub fn set_button_label(&self, label: &str) { - self.inner().menu_button.set_label(label); - } - - pub fn set_popover_child(&self, child: Option<&impl IsA>) { - self.inner().popover.set_child(child); - } -} +// TODO also handle non-popover button? Is GtkMenuButton particularly special, or just use a toggle button? diff --git a/libcosmic-applet/src/window.rs b/libcosmic-applet/src/window.rs new file mode 100644 index 00000000..257658b3 --- /dev/null +++ b/libcosmic-applet/src/window.rs @@ -0,0 +1,58 @@ +use gtk4::{glib, prelude::*, subclass::prelude::*}; +use relm4_macros::view; + +static STYLE: &str = " +window.cosmic_applet_window { + background: transparent; +} +"; + +#[derive(Default)] +pub struct AppletWindowInner; + +#[glib::object_subclass] +impl ObjectSubclass for AppletWindowInner { + const NAME: &'static str = "CosmicAppletWindow"; + type Type = AppletWindow; + type ParentType = gtk4::Window; +} + +impl ObjectImpl for AppletWindowInner { + fn constructed(&self, obj: &AppletWindow) { + let window = || obj; + view! { + window() { + add_css_class: "cosmic_applet_window", + set_decorated: false, + set_resizable: false, + set_width_request: 1, + set_height_request: 1, + }, + provider = gtk4::CssProvider { + load_from_data: STYLE.as_bytes(), + } + } + obj.style_context() + .add_provider(&provider, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION); + } +} + +impl WidgetImpl for AppletWindowInner {} +impl WindowImpl for AppletWindowInner {} + +glib::wrapper! { + pub struct AppletWindow(ObjectSubclass) + @extends gtk4::Widget, gtk4::Window; +} + +impl Default for AppletWindow { + fn default() -> Self { + Self::new() + } +} + +impl AppletWindow { + pub fn new() -> Self { + glib::Object::new(&[]).unwrap() + } +}