From 2cbaaae069a34eb00fda3bbfd8dabdb215ed6acd Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 8 Jun 2022 10:47:16 -0400 Subject: [PATCH] wip: applet workspaces --- applets/cosmic-applet-workspaces/Cargo.toml | 23 +++++ applets/cosmic-applet-workspaces/build.rs | 7 ++ ...om.system76.CosmicAppletWorkspaces.desktop | 12 +++ .../com.system76.CosmicAppletWorkspaces.svg | 60 ++++++++++++ .../data/resources/resources.gresource.xml | 6 ++ .../data/resources/style.css | 0 applets/cosmic-applet-workspaces/i18n.toml | 4 + .../i18n/en/cosmic_applet_workspaces.ftl | 1 + .../cosmic-applet-workspaces/src/localize.rs | 38 ++++++++ applets/cosmic-applet-workspaces/src/main.rs | 76 +++++++++++++++ applets/cosmic-applet-workspaces/src/utils.rs | 49 ++++++++++ .../cosmic-applet-workspaces/src/wayland.rs | 0 .../src/window/imp.rs | 32 +++++++ .../src/window/mod.rs | 46 +++++++++ .../src/workspace_button/imp.rs | 29 ++++++ .../src/workspace_button/mod.rs | 43 +++++++++ .../src/workspace_list/imp.rs | 35 +++++++ .../src/workspace_list/mod.rs | 93 +++++++++++++++++++ .../src/workspace_object/imp.rs | 84 +++++++++++++++++ .../src/workspace_object/mod.rs | 31 +++++++ 20 files changed, 669 insertions(+) create mode 100644 applets/cosmic-applet-workspaces/Cargo.toml create mode 100644 applets/cosmic-applet-workspaces/build.rs create mode 100644 applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop create mode 100644 applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg create mode 100644 applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml create mode 100644 applets/cosmic-applet-workspaces/data/resources/style.css create mode 100644 applets/cosmic-applet-workspaces/i18n.toml create mode 100644 applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl create mode 100644 applets/cosmic-applet-workspaces/src/localize.rs create mode 100644 applets/cosmic-applet-workspaces/src/main.rs create mode 100644 applets/cosmic-applet-workspaces/src/utils.rs create mode 100644 applets/cosmic-applet-workspaces/src/wayland.rs create mode 100644 applets/cosmic-applet-workspaces/src/window/imp.rs create mode 100644 applets/cosmic-applet-workspaces/src/window/mod.rs create mode 100644 applets/cosmic-applet-workspaces/src/workspace_button/imp.rs create mode 100644 applets/cosmic-applet-workspaces/src/workspace_button/mod.rs create mode 100644 applets/cosmic-applet-workspaces/src/workspace_list/imp.rs create mode 100644 applets/cosmic-applet-workspaces/src/workspace_list/mod.rs create mode 100644 applets/cosmic-applet-workspaces/src/workspace_object/imp.rs create mode 100644 applets/cosmic-applet-workspaces/src/workspace_object/mod.rs diff --git a/applets/cosmic-applet-workspaces/Cargo.toml b/applets/cosmic-applet-workspaces/Cargo.toml new file mode 100644 index 00000000..3f9ea555 --- /dev/null +++ b/applets/cosmic-applet-workspaces/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "cosmic-applet-workspaces" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +cosmic-panel-config = { git="https://github.com/pop-os/cosmic-panel/", features = ["gtk4"] } +cascade = "1.0.0" +gtk4 = { version = "0.4.5", features = ["v4_4"] } +once_cell = "1.9.0" +pretty_env_logger = "0.4" +anyhow = "1.0.50" +i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] } +i18n-embed-fl = "0.6.4" +rust-embed = "6.3.0" +tokio = { version = "1.16.1", features = ["sync"] } + + +[build-dependencies] +gio = "0.15.10" diff --git a/applets/cosmic-applet-workspaces/build.rs b/applets/cosmic-applet-workspaces/build.rs new file mode 100644 index 00000000..7c42fb5e --- /dev/null +++ b/applets/cosmic-applet-workspaces/build.rs @@ -0,0 +1,7 @@ +fn main() { + gio::compile_resources( + "data/resources", + "data/resources/resources.gresource.xml", + "compiled.gresource", + ); +} diff --git a/applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop b/applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop new file mode 100644 index 00000000..8119b2b1 --- /dev/null +++ b/applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Cosmic Applet Power +Comment=Write a GTK + Rust application +Type=Application +Exec=cosmic-applet-power +Terminal=false +Categories=GNOME;GTK; +Keywords=Gnome;GTK; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=com.system76.CosmicAppletPower.svg +StartupNotify=true +NoDisplay=true diff --git a/applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg b/applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg new file mode 100644 index 00000000..c2bd5b1b --- /dev/null +++ b/applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml b/applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml new file mode 100644 index 00000000..2cf0970f --- /dev/null +++ b/applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/applets/cosmic-applet-workspaces/data/resources/style.css b/applets/cosmic-applet-workspaces/data/resources/style.css new file mode 100644 index 00000000..e69de29b diff --git a/applets/cosmic-applet-workspaces/i18n.toml b/applets/cosmic-applet-workspaces/i18n.toml new file mode 100644 index 00000000..05c50ba2 --- /dev/null +++ b/applets/cosmic-applet-workspaces/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en" + +[fluent] +assets_dir = "i18n" \ No newline at end of file diff --git a/applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl b/applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl new file mode 100644 index 00000000..2d5ea1e4 --- /dev/null +++ b/applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl @@ -0,0 +1 @@ +cosmic-app-list = Cosmic Dock App List diff --git a/applets/cosmic-applet-workspaces/src/localize.rs b/applets/cosmic-applet-workspaces/src/localize.rs new file mode 100644 index 00000000..efb92aa4 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/localize.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use i18n_embed::{ + fluent::{fluent_language_loader, FluentLanguageLoader}, + DefaultLocalizer, LanguageLoader, Localizer, +}; +use once_cell::sync::Lazy; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: Lazy = Lazy::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + + loader + .load_fallback_language(&Localizations) + .expect("Error while loading fallback language"); + + loader +}); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} diff --git a/applets/cosmic-applet-workspaces/src/main.rs b/applets/cosmic-applet-workspaces/src/main.rs new file mode 100644 index 00000000..df05758b --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/main.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use gtk4::{gdk::Display, gio::{self, ApplicationFlags}, glib, prelude::*, CssProvider, StyleContext}; +use once_cell::sync::OnceCell; +use window::CosmicWorkspacesWindow; +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc; +use utils::{Event, BoxedWorkspaceList}; + +mod window; +mod workspace_button; +mod workspace_list; +mod wayland; +mod localize; +mod utils; +mod workspace_object; + +const ID: &str = "com.system76.CosmicAppletWorkspaces"; +static TX: OnceCell> = OnceCell::new(); + +pub fn localize() { + let localizer = crate::localize::localizer(); + let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); + + if let Err(error) = localizer.select(&requested_languages) { + eprintln!("Error while loading language for App List {}", error); + } +} + +fn load_css() { + let provider = CssProvider::new(); + provider.load_from_resource("/com.system76.CosmicAppletWorkspaces/style.css"); + + StyleContext::add_provider_for_display( + &Display::default().unwrap(), + &provider, + gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); +} + +fn main() { + // Initialize logger + pretty_env_logger::init(); + glib::set_application_name(ID); + + localize(); + gio::resources_register_include!("compiled.gresource").unwrap(); + + let app = gtk4::Application::new(Some(ID), ApplicationFlags::default()); + + app.connect_activate(|app| { + load_css(); + let (tx, mut rx) = mpsc::channel(100); + + let window = CosmicWorkspacesWindow::new(app, tx.clone()); + + let workspace_list = Arc::new(Mutex::new(Vec::::new())); + + TX.set(tx.clone()).unwrap(); + + let _ = glib::MainContext::default().spawn_local(async move { + while let Some(event) = rx.recv().await { + match event { + Event::Activate(_) => { + // TODO activate the selected workspace + } + Event::WorkspaceList => { + // TODO update the model with the new workspace list + } + } + } + }); + window.show(); + }); + app.run(); +} diff --git a/applets/cosmic-applet-workspaces/src/utils.rs b/applets/cosmic-applet-workspaces/src/utils.rs new file mode 100644 index 00000000..4d27e5e9 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/utils.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use std::path::PathBuf; + +use gtk4::glib; +use std::future::Future; + +#[derive(Debug)] +pub enum Event { + WorkspaceList, + Activate(u32), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Workspace { + pub(crate) id: u32, + pub(crate) active: bool, +} + +#[derive(Clone, Debug, Default, glib::Boxed)] +#[boxed_type(name = "BoxedWorkspace")] +pub struct BoxedWorkspace(pub Option); + +#[derive(Clone, Debug, Default, glib::Boxed)] +#[boxed_type(name = "BoxedWorkspaceList")] +pub struct BoxedWorkspaceList(pub Vec); + +pub fn data_path() -> PathBuf { + let mut path = glib::user_data_dir(); + path.push(crate::ID); + std::fs::create_dir_all(&path).expect("Could not create directory."); + path.push("data.json"); + path +} + +pub fn thread_context() -> glib::MainContext { + glib::MainContext::thread_default().unwrap_or_else(|| { + let ctx = glib::MainContext::new(); + ctx + }) +} + +pub fn block_on(future: F) -> F::Output +where + F: Future, +{ + let ctx = thread_context(); + ctx.with_thread_default(|| ctx.block_on(future)).unwrap() +} diff --git a/applets/cosmic-applet-workspaces/src/wayland.rs b/applets/cosmic-applet-workspaces/src/wayland.rs new file mode 100644 index 00000000..e69de29b diff --git a/applets/cosmic-applet-workspaces/src/window/imp.rs b/applets/cosmic-applet-workspaces/src/window/imp.rs new file mode 100644 index 00000000..d614237c --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/window/imp.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use gtk4::{glib, subclass::prelude::*}; +use once_cell::sync::OnceCell; +use crate::{workspace_list::WorkspaceList}; + +// Object holding the state +#[derive(Default)] +pub struct CosmicWorkspacesWindow { + pub(super) inner: OnceCell, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for CosmicWorkspacesWindow { + // `NAME` needs to match `class` attribute of template + const NAME: &'static str = "CosmicWorkspacesWindow"; + type Type = super::CosmicWorkspacesWindow; + type ParentType = gtk4::ApplicationWindow; +} + +// Trait shared by all GObjects +impl ObjectImpl for CosmicWorkspacesWindow {} + +// Trait shared by all widgets +impl WidgetImpl for CosmicWorkspacesWindow {} + +// Trait shared by all windows +impl WindowImpl for CosmicWorkspacesWindow {} + +// Trait shared by all application +impl ApplicationWindowImpl for CosmicWorkspacesWindow {} diff --git a/applets/cosmic-applet-workspaces/src/window/mod.rs b/applets/cosmic-applet-workspaces/src/window/mod.rs new file mode 100644 index 00000000..585a1e87 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/window/mod.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::{fl, utils::Event, workspace_list::WorkspaceList}; +use cascade::cascade; +use cosmic_panel_config::config::CosmicPanelConfig; +use gtk4::{ + gio, + glib::{self, Object}, + prelude::*, + subclass::prelude::*, +}; +use tokio::sync::mpsc; + +mod imp; + +glib::wrapper! { + pub struct CosmicWorkspacesWindow(ObjectSubclass) + @extends gtk4::ApplicationWindow, gtk4::Window, gtk4::Widget, + @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable, + gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager; +} + +impl CosmicWorkspacesWindow { + pub fn new(app: >k4::Application, tx: mpsc::Sender) -> Self { + let self_: Self = Object::new(&[("application", app)]) + .expect("Failed to create `CosmicWorkspacesWindow`."); + let imp = imp::CosmicWorkspacesWindow::from_instance(&self_); + + cascade! { + &self_; + ..set_width_request(1); + ..set_height_request(1); + ..set_decorated(false); + ..set_resizable(false); + ..set_title(Some(&fl!("cosmic-app-list"))); + ..add_css_class("transparent"); + }; + let config = CosmicPanelConfig::load_from_env().unwrap_or_default(); + + let app_list = WorkspaceList::new(tx, config); + self_.set_child(Some(&app_list)); + imp.inner.set(app_list).unwrap(); + + self_ + } +} diff --git a/applets/cosmic-applet-workspaces/src/workspace_button/imp.rs b/applets/cosmic-applet-workspaces/src/workspace_button/imp.rs new file mode 100644 index 00000000..254713df --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/workspace_button/imp.rs @@ -0,0 +1,29 @@ +use std::{rc::Rc, cell::RefCell}; +use gtk4::{Label, ToggleButton, glib, subclass::prelude::*}; +use tokio::sync::mpsc; +use once_cell::sync::OnceCell; +use crate::Event; + +// Object holding the state +#[derive(Default)] +pub struct WorkspaceButton { + pub tx: Rc>>, + pub button: Rc>, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for WorkspaceButton { + const NAME: &'static str = "WorkspaceButton"; + type Type = super::WorkspaceButton; + type ParentType = gtk4::Box; +} + +// Trait shared by all GObjects +impl ObjectImpl for WorkspaceButton {} + +// Trait shared by all widgets +impl WidgetImpl for WorkspaceButton {} + +// Trait shared by all buttons +impl BoxImpl for WorkspaceButton {} \ No newline at end of file diff --git a/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs new file mode 100644 index 00000000..bf5a4ab8 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs @@ -0,0 +1,43 @@ +mod imp; + +use glib::Object; +use gtk4::{glib, prelude::*, subclass::prelude::*, ToggleButton}; +use tokio::sync::mpsc; +use crate::{Event, workspace_object::WorkspaceObject}; + +glib::wrapper! { + pub struct WorkspaceButton(ObjectSubclass) + @extends gtk4::Box, gtk4::Widget, + @implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; +} + +impl WorkspaceButton { + pub fn new(tx: mpsc::Sender) -> Self { + let self_ = Object::new(&[]).expect("Failed to create `WorkspaceButton`."); + let imp = imp::WorkspaceButton::from_instance(&self_); + imp.tx.set(tx).unwrap(); + + let tb = ToggleButton::with_label(""); + self_.append(&tb); + + imp.button.replace(tb); + + self_ + } + + pub fn set_workspace_object(&self, obj: &WorkspaceObject) { + let imp = imp::WorkspaceButton::from_instance(&self); + let old_button = imp.button.take(); + self.remove(&old_button); + + let id = obj.id(); + let new_button = ToggleButton::with_label(&format!("{}", id)); + new_button.set_active(obj.active()); + self.append(&new_button); + new_button.connect_clicked(glib::clone!(@weak imp.tx as tx => move |_| { + let _ = tx.get().unwrap().send(Event::Activate(id)); + })); + + imp.button.replace(new_button); + } +} diff --git a/applets/cosmic-applet-workspaces/src/workspace_list/imp.rs b/applets/cosmic-applet-workspaces/src/workspace_list/imp.rs new file mode 100644 index 00000000..ee855b1d --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/workspace_list/imp.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use cosmic_panel_config::config::{Anchor, CosmicPanelConfig}; +use glib::SignalHandlerId; +use gtk4::subclass::prelude::*; +use gtk4::{gio, glib}; +use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView}; +use once_cell::sync::OnceCell; +use std::cell::{Cell, RefCell}; +use std::rc::Rc; +use tokio::sync::mpsc; + +use crate::utils::Event; + +#[derive(Debug, Default)] +pub struct WorkspaceList { + pub list_view: OnceCell, + pub model: OnceCell, + pub click_controller: OnceCell, + pub tx: OnceCell>, + pub config: OnceCell +} + +#[glib::object_subclass] +impl ObjectSubclass for WorkspaceList { + const NAME: &'static str = "WorkspaceList"; + type Type = super::WorkspaceList; + type ParentType = Box; +} + +impl ObjectImpl for WorkspaceList {} + +impl WidgetImpl for WorkspaceList {} + +impl BoxImpl for WorkspaceList {} diff --git a/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs new file mode 100644 index 00000000..5cd6fe35 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::utils::{Event, BoxedWorkspace}; +use crate::workspace_button::WorkspaceButton; +use crate::workspace_object::WorkspaceObject; +use cascade::cascade; +use cosmic_panel_config::config::{CosmicPanelConfig}; +use gtk4::{glib, gio, prelude::*, subclass::prelude::*}; +use gtk4::ListView; +use gtk4::Orientation; +use gtk4::SignalListItemFactory; +use tokio::sync::mpsc::Sender; + +mod imp; + +glib::wrapper! { + pub struct WorkspaceList(ObjectSubclass) + @extends gtk4::Widget, gtk4::Box, + @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; +} + +impl WorkspaceList { + pub fn new(tx: Sender, config: CosmicPanelConfig) -> Self { + let self_: WorkspaceList = glib::Object::new(&[]).expect("Failed to create WorkspaceList"); + let imp = imp::WorkspaceList::from_instance(&self_); + imp.tx.set(tx).unwrap(); + imp.config.set(config).unwrap(); + self_.layout(); + //dnd behavior is different for each type, as well as the data in the model + self_.setup_model(); + self_.setup_factory(); + self_ + } + + pub fn model(&self) -> &gio::ListStore { + // Get state + let imp = imp::WorkspaceList::from_instance(self); + imp.model.get().expect("Could not get model") + } + + fn layout(&self) { + let imp = imp::WorkspaceList::from_instance(self); + let list_view = cascade! { + ListView::default(); + ..set_orientation(Orientation::Horizontal); + ..add_css_class("transparent"); + }; + self.append(&list_view); + imp.list_view.set(list_view).unwrap(); + } + + fn setup_model(&self) { + let imp = imp::WorkspaceList::from_instance(self); + let model = gio::ListStore::new(BoxedWorkspace::static_type()); + + let selection_model = gtk4::NoSelection::new(Some(&model)); + + // Wrap model with selection and pass it to the list view + let list_view = imp.list_view.get().unwrap(); + list_view.set_model(Some(&selection_model)); + imp.model.set(model).expect("Could not set model"); + } + + fn setup_factory(&self) { + let imp = imp::WorkspaceList::from_instance(self); + let factory = SignalListItemFactory::new(); + let model = imp.model.get().expect("Failed to get saved app model."); + let tx = imp.tx.get().unwrap().clone(); + let icon_size = imp.config.get().unwrap().get_applet_icon_size(); + factory.connect_setup( + glib::clone!(@weak model => move |_, list_item| { + let workspace_button = WorkspaceButton::new(tx.clone()); + list_item.set_child(Some(&workspace_button)); + }), + ); + factory.connect_bind(|_, list_item| { + let workspace_object = list_item + .item() + .expect("The item has to exist.") + .downcast::() + .expect("The item has to be a `DockObject`"); + let workspace_button = list_item + .child() + .expect("The list item child needs to exist.") + .downcast::() + .expect("The list item type needs to be `DockItem`"); + workspace_button.set_workspace_object(&workspace_object); + }, + ); + // Set the factory of the list view + imp.list_view.get().unwrap().set_factory(Some(&factory)); + } +} diff --git a/applets/cosmic-applet-workspaces/src/workspace_object/imp.rs b/applets/cosmic-applet-workspaces/src/workspace_object/imp.rs new file mode 100644 index 00000000..90972991 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/workspace_object/imp.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use std::cell::Cell; +use std::cell::RefCell; + +use glib::{ParamFlags, ParamSpec, Value}; +use gtk4::gdk::glib::ParamSpecBoolean; +use gtk4::glib; +use gtk4::glib::ParamSpecUInt; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use once_cell::sync::Lazy; + +// Object holding the state +#[derive(Default)] +pub struct WorkspaceObject { + pub(crate) id: Cell, + pub(crate) active: Cell, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for WorkspaceObject { + const NAME: &'static str = "WorkspaceObject"; + type Type = super::WorkspaceObject; + type ParentType = glib::Object; +} + +// Trait shared by all GObjects +impl ObjectImpl for WorkspaceObject { + fn properties() -> &'static [ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + ParamSpecUInt::new( + // Name + "id", + // Nickname + "id", + // Short description + "id", + // Minimum value + u32::MIN, + // Maximum value + u32::MAX, + // Default value + 0, + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpecBoolean::new( + "active", + "active", + "Indicates whether workspace is active", + false, + ParamFlags::READWRITE, + ), + + ] + }); + PROPERTIES.as_ref() + } + + fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { + match pspec.name() { + "active" => { + self.active + .replace(value.get().expect("Value needs to be a boolean")); + } + "id" => { + self.id + .replace(value.get().expect("Value needs to be a boolean")); + } + _ => unimplemented!(), + } + } + + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { + match pspec.name() { + "id" => self.id.get().to_value(), + "active" => self.active.get().to_value(), + _ => unimplemented!(), + } + } +} diff --git a/applets/cosmic-applet-workspaces/src/workspace_object/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_object/mod.rs new file mode 100644 index 00000000..5fa2f740 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/workspace_object/mod.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use gtk4::{glib, subclass::prelude::*}; + +mod imp; + +glib::wrapper! { + pub struct WorkspaceObject(ObjectSubclass); +} + +impl WorkspaceObject { + pub fn new() -> Self { + glib::Object::new(&[]).unwrap() + } + + pub fn from_id_active(id: u32, active: bool) -> Self { + glib::Object::new(&[("id", &id), ("active", &active)]).unwrap() + } + + pub fn id(&self) -> u32 { + imp::WorkspaceObject::from_instance(&self).id.get() + } + + pub fn active(&self) -> bool { + imp::WorkspaceObject::from_instance(&self).active.get() + } +} + +#[derive(Clone, Debug, Default, glib::Boxed)] +#[boxed_type(name = "BoxedWorkspaceObject")] +pub struct BoxedWorkspaceObject(pub Option);