From d1493a5a1f454ba04c7695efb85fad011ddbc7b5 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Sep 2021 10:45:34 -0700 Subject: [PATCH] Use custom `PopoverContainer` instead of `gtk4::MenuButton` --- src/main.rs | 1 + src/popover_container.rs | 96 ++++++++++++++++++++++++++++++++++++++++ src/status_menu.rs | 29 +++++++----- src/time_button.rs | 35 ++++++++------- 4 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 src/popover_container.rs diff --git a/src/main.rs b/src/main.rs index dbde99d2..127007ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod deref_cell; mod mpris; mod mpris_player; mod notifications; +mod popover_container; mod status_area; mod status_menu; mod status_notifier_watcher; diff --git a/src/popover_container.rs b/src/popover_container.rs new file mode 100644 index 00000000..6d9acba0 --- /dev/null +++ b/src/popover_container.rs @@ -0,0 +1,96 @@ +use cascade::cascade; +use gtk4::{glib, prelude::*, subclass::prelude::*}; + +use crate::deref_cell::DerefCell; + +/// Unlike gtk4's `MenuButton`, this supports a custom child. +#[derive(Default)] +pub struct PopoverContainerInner { + child: DerefCell, + popover: DerefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for PopoverContainerInner { + const NAME: &'static str = "S76PopoverContainer"; + type ParentType = gtk4::Widget; + type Type = PopoverContainer; +} + +impl ObjectImpl for PopoverContainerInner { + fn constructed(&self, obj: &PopoverContainer) { + let popover = cascade! { + gtk4::Popover::new(); + ..set_parent(obj); + }; + + self.popover.set(popover); + } + + fn dispose(&self, _obj: &PopoverContainer) { + self.child.unparent(); + self.popover.unparent(); + } +} + +impl WidgetImpl for PopoverContainerInner { + fn measure( + &self, + _obj: &PopoverContainer, + orientation: gtk4::Orientation, + for_size: i32, + ) -> (i32, i32, i32, i32) { + self.child.measure(orientation, for_size) + } + + fn size_allocate(&self, _obj: &PopoverContainer, width: i32, height: i32, baseline: i32) { + self.child.size_allocate( + >k4::Allocation { + x: 0, + y: 0, + width, + height, + }, + baseline, + ); + self.popover.present(); + } + + fn focus(&self, _obj: &PopoverContainer, direction: gtk4::DirectionType) -> bool { + if self.popover.is_visible() { + self.popover.child_focus(direction) + } else { + self.child.child_focus(direction) + } + } +} + +glib::wrapper! { + pub struct PopoverContainer(ObjectSubclass) + @extends gtk4::Widget; +} + +impl PopoverContainer { + pub fn new>(child: &T) -> Self { + let obj = glib::Object::new::(&[]).unwrap(); + child.set_parent(&obj); + obj.inner().child.set(child.clone().upcast()); + obj + } + + fn inner(&self) -> &PopoverContainerInner { + PopoverContainerInner::from_instance(self) + } + + pub fn popover(&self) -> >k4::Popover { + &self.inner().popover + } + + pub fn popup(&self) { + self.popover().popup(); + } + + pub fn popdown(&self) { + self.popover().popdown(); + } +} diff --git a/src/status_menu.rs b/src/status_menu.rs index 117cc8ff..61b1d987 100644 --- a/src/status_menu.rs +++ b/src/status_menu.rs @@ -9,6 +9,7 @@ use gtk4::{ use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt, io}; use crate::deref_cell::DerefCell; +use crate::popover_container::PopoverContainer; struct Menu { box_: gtk4::Box, @@ -17,7 +18,8 @@ struct Menu { #[derive(Default)] pub struct StatusMenuInner { - menu_button: DerefCell, + button: DerefCell, + popover_container: DerefCell, vbox: DerefCell, item: DerefCell, dbus_menu: DerefCell, @@ -41,22 +43,25 @@ impl ObjectImpl for StatusMenuInner { gtk4::Box::new(gtk4::Orientation::Vertical, 0); }; - let menu_button = cascade! { - gtk4::MenuButton::new(); - ..set_parent(obj); + let button = cascade! { + gtk4::ToggleButton::new(); ..set_has_frame(false); - ..set_popover(Some(&cascade! { - gtk4::Popover::new(); - ..set_child(Some(&vbox)); - })); }; - self.menu_button.set(menu_button); + let popover_container = cascade! { + PopoverContainer::new(&button); + ..set_parent(obj); + ..popover().set_child(Some(&vbox)); + ..popover().bind_property("visible", &button, "active").flags(glib::BindingFlags::BIDIRECTIONAL).build(); + }; + + self.button.set(button); + self.popover_container.set(popover_container); self.vbox.set(vbox); } fn dispose(&self, _obj: &StatusMenu) { - self.menu_button.unparent(); + self.button.unparent(); } } @@ -72,7 +77,7 @@ impl StatusMenu { let item = StatusNotifierItem::new(name).await?; let obj = glib::Object::new::(&[]).unwrap(); if let Some(icon_name) = item.icon_name().as_deref() { - obj.inner().menu_button.set_icon_name(&icon_name); + obj.inner().button.set_icon_name(&icon_name); } let menu = item.menu().unwrap(); // XXX unwrap? @@ -175,7 +180,7 @@ impl StatusMenu { ..connect_clicked(clone!(@weak self as self_ => move |_| { // XXX data, timestamp if close_on_click { - self_.inner().menu_button.popdown(); + self_.inner().popover_container.popdown(); } self_.inner().dbus_menu.event(id, "clicked", &0.to_variant(), 0); })); diff --git a/src/time_button.rs b/src/time_button.rs index 0bb26cbd..8907b5ec 100644 --- a/src/time_button.rs +++ b/src/time_button.rs @@ -7,11 +7,12 @@ use gtk4::{ use crate::deref_cell::DerefCell; use crate::mpris::MprisControls; +use crate::popover_container::PopoverContainer; #[derive(Default)] pub struct TimeButtonInner { calendar: DerefCell, - menu_button: DerefCell, + button: DerefCell, } #[glib::object_subclass] @@ -31,25 +32,25 @@ impl ObjectImpl for TimeButtonInner { gtk4::Calendar::new(); }; - let menu_button = cascade! { - gtk4::MenuButton::new(); - ..set_parent(obj); + let button = cascade! { + gtk4::ToggleButton::new(); ..set_has_frame(false); - ..set_direction(gtk4::ArrowType::None); - ..style_context().remove_class("toggle"); - ..set_popover(Some(&cascade! { - gtk4::Popover::new(); - ..set_child(Some(&cascade! { - gtk4::Box::new(gtk4::Orientation::Horizontal, 0); - ..append(&MprisControls::new()); - ..append(&calendar); - })); - ..connect_show(clone!(@strong obj => move |_| obj.opening())); + }; + + cascade! { + PopoverContainer::new(&button); + ..set_parent(obj); + ..popover().set_child(Some(&cascade! { + gtk4::Box::new(gtk4::Orientation::Horizontal, 0); + ..append(&MprisControls::new()); + ..append(&calendar); })); + ..popover().connect_show(clone!(@strong obj => move |_| obj.opening())); + ..popover().bind_property("visible", &button, "active").flags(glib::BindingFlags::BIDIRECTIONAL).build(); }; self.calendar.set(calendar); - self.menu_button.set(menu_button); + self.button.set(button); // TODO: better way to do this? glib::timeout_add_seconds_local( @@ -63,7 +64,7 @@ impl ObjectImpl for TimeButtonInner { } fn dispose(&self, _obj: &TimeButton) { - self.menu_button.unparent(); + self.button.unparent(); } } @@ -93,7 +94,7 @@ impl TimeButton { // TODO: Locale-based formatting? let time = chrono::Local::now(); self.inner() - .menu_button + .button .set_label(&time.format("%b %-d %-I:%M %p").to_string()); // time.format("%B %-d %Y") }