Separate CosmicAppletWindow and CosmicAppletButton
This commit is contained in:
parent
74f6c2eca6
commit
aac43de65d
16 changed files with 421 additions and 343 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -401,6 +401,7 @@ dependencies = [
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"libcosmic-applet",
|
"libcosmic-applet",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"relm4-macros",
|
||||||
"serde",
|
"serde",
|
||||||
"zbus",
|
"zbus",
|
||||||
"zbus_names",
|
"zbus_names",
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ use crate::dock_list::DockList;
|
||||||
use crate::dock_list::DockListType;
|
use crate::dock_list::DockListType;
|
||||||
use crate::utils::Event;
|
use crate::utils::Event;
|
||||||
use cascade::cascade;
|
use cascade::cascade;
|
||||||
use cosmic_panel_config::config::PanelAnchor;
|
|
||||||
use cosmic_panel_config::config::CosmicPanelConfig;
|
use cosmic_panel_config::config::CosmicPanelConfig;
|
||||||
|
use cosmic_panel_config::config::PanelAnchor;
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::subclass::prelude::*;
|
use gtk4::subclass::prelude::*;
|
||||||
use gtk4::Orientation;
|
use gtk4::Orientation;
|
||||||
|
|
@ -69,7 +69,6 @@ impl AppsContainer {
|
||||||
self_.setup_callbacks();
|
self_.setup_callbacks();
|
||||||
self_.set_position(config.anchor);
|
self_.set_position(config.anchor);
|
||||||
|
|
||||||
|
|
||||||
Self::setup_callbacks(&self_);
|
Self::setup_callbacks(&self_);
|
||||||
|
|
||||||
self_
|
self_
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0-only
|
// 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 glib::SignalHandlerId;
|
||||||
use gtk4::subclass::prelude::*;
|
use gtk4::subclass::prelude::*;
|
||||||
use gtk4::{gio, glib};
|
use gtk4::{gio, glib};
|
||||||
|
|
@ -25,7 +25,7 @@ pub struct DockList {
|
||||||
pub popover_menu_index: Rc<Cell<Option<u32>>>,
|
pub popover_menu_index: Rc<Cell<Option<u32>>>,
|
||||||
pub position: Rc<Cell<PanelAnchor>>,
|
pub position: Rc<Cell<PanelAnchor>>,
|
||||||
pub tx: OnceCell<mpsc::Sender<Event>>,
|
pub tx: OnceCell<mpsc::Sender<Event>>,
|
||||||
pub config: OnceCell<CosmicPanelConfig>
|
pub config: OnceCell<CosmicPanelConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
|
||||||
|
|
@ -76,90 +76,93 @@ fn app(application: &Application) {
|
||||||
});
|
});
|
||||||
pa.connect().unwrap(); // XXX unwrap
|
pa.connect().unwrap(); // XXX unwrap
|
||||||
view! {
|
view! {
|
||||||
window = libcosmic_applet::Applet {
|
window = libcosmic_applet::AppletWindow {
|
||||||
set_application: Some(application),
|
set_application: Some(application),
|
||||||
set_title: Some("COSMIC Network Applet"),
|
set_title: Some("COSMIC Network Applet"),
|
||||||
// TODO: adjust based on volume, mute
|
|
||||||
set_button_icon_name: "multimedia-volume-control-symbolic",
|
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_popover_child: window_box = &GtkBox {
|
set_child = &libcosmic_applet::AppletButton {
|
||||||
set_orientation: Orientation::Vertical,
|
// TODO: adjust based on volume, mute
|
||||||
set_spacing: 24,
|
set_button_icon_name: "multimedia-volume-control-symbolic",
|
||||||
append: output_box = &GtkBox {
|
#[wrap(Some)]
|
||||||
set_orientation: Orientation::Horizontal,
|
set_popover_child: window_box = &GtkBox {
|
||||||
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_orientation: Orientation::Vertical,
|
set_orientation: Orientation::Vertical,
|
||||||
append: current_output_button = &Button {
|
set_spacing: 24,
|
||||||
#[wrap(Some)]
|
append: output_box = &GtkBox {
|
||||||
set_child: current_output = &Label {},
|
set_orientation: Orientation::Horizontal,
|
||||||
connect_clicked[outputs_revealer] => move |_| {
|
set_spacing: 16,
|
||||||
outputs_revealer.set_reveal_child(!outputs_revealer.reveals_child());
|
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 {
|
append: input_box = &GtkBox {
|
||||||
set_transition_type: RevealerTransitionType::SlideDown,
|
set_orientation: Orientation::Horizontal,
|
||||||
#[wrap(Some)]
|
set_spacing: 16,
|
||||||
set_child: outputs = &ListBox {
|
append: input_icon = &Image {
|
||||||
set_selection_mode: SelectionMode::None,
|
set_icon_name: Some("audio-input-microphone-symbolic"),
|
||||||
set_activate_on_single_click: true
|
},
|
||||||
}
|
append: input_volume = &Scale::with_range(Orientation::Horizontal, 0., 100., 1.) {
|
||||||
}
|
set_format_value_func: |_, value| {
|
||||||
},
|
format!("{:.0}%", value)
|
||||||
append: _sep = &Separator {
|
},
|
||||||
set_orientation: Orientation::Horizontal,
|
set_value_pos: PositionType::Right,
|
||||||
},
|
set_hexpand: true
|
||||||
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 {
|
append: _sep = &Separator {
|
||||||
set_transition_type: RevealerTransitionType::SlideDown,
|
set_orientation: Orientation::Horizontal,
|
||||||
#[wrap(Some)]
|
},
|
||||||
set_child: inputs = &ListBox {
|
append: output_list_box = &GtkBox {
|
||||||
set_selection_mode: SelectionMode::None,
|
set_orientation: Orientation::Vertical,
|
||||||
set_activate_on_single_click: true
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,115 +77,118 @@ impl SimpleComponent for AppModel {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
libcosmic_applet::Applet {
|
libcosmic_applet::AppletWindow {
|
||||||
#[watch]
|
|
||||||
set_button_icon_name: &model.icon_name,
|
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_popover_child = >k4::Box {
|
set_child = &libcosmic_applet::AppletButton {
|
||||||
set_orientation: gtk4::Orientation::Vertical,
|
#[watch]
|
||||||
|
set_button_icon_name: &model.icon_name,
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_popover_child = >k4::Box {
|
||||||
|
set_orientation: gtk4::Orientation::Vertical,
|
||||||
|
|
||||||
// Battery
|
// Battery
|
||||||
gtk4::Box {
|
|
||||||
set_orientation: gtk4::Orientation::Horizontal,
|
|
||||||
gtk4::Image {
|
|
||||||
#[watch]
|
|
||||||
set_icon_name: Some(&model.icon_name),
|
|
||||||
},
|
|
||||||
gtk4::Box {
|
gtk4::Box {
|
||||||
set_orientation: gtk4::Orientation::Vertical,
|
set_orientation: gtk4::Orientation::Horizontal,
|
||||||
gtk4::Label {
|
gtk4::Image {
|
||||||
set_halign: gtk4::Align::Start,
|
|
||||||
set_label: "Battery",
|
|
||||||
},
|
|
||||||
gtk4::Label {
|
|
||||||
set_halign: gtk4::Align::Start,
|
|
||||||
// XXX time to full, fully changed, etc.
|
|
||||||
#[watch]
|
#[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
|
// Limit charging
|
||||||
gtk4::Box {
|
|
||||||
set_orientation: gtk4::Orientation::Horizontal,
|
|
||||||
gtk4::Box {
|
gtk4::Box {
|
||||||
set_orientation: gtk4::Orientation::Vertical,
|
set_orientation: gtk4::Orientation::Horizontal,
|
||||||
gtk4::Label {
|
gtk4::Box {
|
||||||
set_halign: gtk4::Align::Start,
|
set_orientation: gtk4::Orientation::Vertical,
|
||||||
set_label: "Limit Battery Charging",
|
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 {
|
gtk4::Label {
|
||||||
set_halign: gtk4::Align::Start,
|
#[watch]
|
||||||
set_label: "Increase the lifespan of your battery by setting a maximum charge value of 80%."
|
set_label: &format!("{:.0}%", model.display_brightness * 100.),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
gtk4::Switch {
|
gtk4::Box {
|
||||||
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]
|
#[watch]
|
||||||
set_value: model.display_brightness,
|
set_visible: model.kbd_backlight.is_some(),
|
||||||
connect_change_value[sender] => move |_, _, value| {
|
set_orientation: gtk4::Orientation::Horizontal,
|
||||||
sender.input(AppMsg::SetDisplayBrightness(value));
|
gtk4::Image {
|
||||||
gtk4::Inhibit(false)
|
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 {
|
gtk4::Button {
|
||||||
set_label: "Power Settings...",
|
set_label: "Power Settings...",
|
||||||
connect_clicked => move |_| {
|
connect_clicked => move |_| {
|
||||||
// XXX open subpanel
|
// XXX open subpanel
|
||||||
let _ = Command::new("cosmic-settings").spawn();
|
let _ = Command::new("cosmic-settings").spawn();
|
||||||
// TODO hide
|
// TODO hide
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ pub mod graphics;
|
||||||
pub mod mode_box;
|
pub mod mode_box;
|
||||||
|
|
||||||
use self::{dbus::PowerDaemonProxy, graphics::Graphics, mode_box::ModeSelection};
|
use self::{dbus::PowerDaemonProxy, graphics::Graphics, mode_box::ModeSelection};
|
||||||
|
use cosmic_panel_config::config::CosmicPanelConfig;
|
||||||
use gtk4::{
|
use gtk4::{
|
||||||
gdk::Display,
|
gdk::Display,
|
||||||
gio::ApplicationFlags,
|
gio::ApplicationFlags,
|
||||||
|
|
@ -20,7 +21,6 @@ use gtk4::{
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use cosmic_panel_config::config::CosmicPanelConfig;
|
|
||||||
|
|
||||||
static RT: Lazy<Runtime> = Lazy::new(|| Runtime::new().expect("failed to build tokio runtime"));
|
static RT: Lazy<Runtime> = 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.add_css_class("panel_icon");
|
||||||
image.set_pixel_size(config.get_applet_icon_size().try_into().unwrap());
|
image.set_pixel_size(config.get_applet_icon_size().try_into().unwrap());
|
||||||
button.set_child(Some(&image));
|
button.set_child(Some(&image));
|
||||||
let current_graphics = RT
|
let current_graphics = RT
|
||||||
.block_on(get_current_graphics())
|
.block_on(get_current_graphics())
|
||||||
.expect("failed to connect to system76-power");
|
.expect("failed to connect to system76-power");
|
||||||
view! {
|
view! {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ futures = "0.3"
|
||||||
gtk4 = { git = "https://github.com/gtk-rs/gtk4-rs" }
|
gtk4 = { git = "https://github.com/gtk-rs/gtk4-rs" }
|
||||||
libcosmic-applet = { path = "../../libcosmic-applet" }
|
libcosmic-applet = { path = "../../libcosmic-applet" }
|
||||||
once_cell = "1.12"
|
once_cell = "1.12"
|
||||||
|
relm4-macros = { git = "https://github.com/Relm4/Relm4.git", branch = "next" }
|
||||||
serde = "1"
|
serde = "1"
|
||||||
zbus = "2.0.1"
|
zbus = "2.0.1"
|
||||||
zbus_names = "2"
|
zbus_names = "2"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use cascade::cascade;
|
|
||||||
use gtk4::{glib, prelude::*};
|
use gtk4::{glib, prelude::*};
|
||||||
|
use relm4_macros::view;
|
||||||
|
|
||||||
mod dbus_service;
|
mod dbus_service;
|
||||||
mod deref_cell;
|
mod deref_cell;
|
||||||
|
|
@ -19,18 +19,20 @@ fn main() {
|
||||||
|
|
||||||
let notification_list = NotificationList::new(¬ifications);
|
let notification_list = NotificationList::new(¬ifications);
|
||||||
|
|
||||||
let window = cascade! {
|
view! {
|
||||||
libcosmic_applet::Applet::new();
|
window = libcosmic_applet::AppletWindow {
|
||||||
..set_button_icon_name("user-invisible-symbolic"); // TODO
|
#[wrap(Some)]
|
||||||
..set_popover_child(Some(¬ification_list));
|
set_child: applet_button = &libcosmic_applet::AppletButton {
|
||||||
..show();
|
set_button_icon_name: "user-invisible-symbolic", // TODO
|
||||||
};
|
set_popover_child: Some(¬ification_list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.show();
|
||||||
|
|
||||||
// XXX show in correct place
|
// XXX show in correct place
|
||||||
cascade! {
|
let notification_popover = NotificationPopover::new(¬ifications);
|
||||||
NotificationPopover::new(¬ifications);
|
notification_popover.set_parent(&applet_button);
|
||||||
..set_parent(&window.child().unwrap()); // XXX better way?
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
|
|
|
||||||
|
|
@ -24,34 +24,37 @@ fn main() {
|
||||||
|
|
||||||
fn build_ui(application: >k4::Application) {
|
fn build_ui(application: >k4::Application) {
|
||||||
view! {
|
view! {
|
||||||
window = libcosmic_applet::Applet {
|
window = libcosmic_applet::AppletWindow {
|
||||||
set_title: Some("COSMIC Power Applet"),
|
set_title: Some("COSMIC Power Applet"),
|
||||||
set_application: Some(application),
|
set_application: Some(application),
|
||||||
// TODO adjust battery icon based on charge
|
// TODO adjust battery icon based on charge
|
||||||
set_button_icon_name: "system-shutdown-symbolic",
|
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_popover_child: main_box = >k4::Box {
|
set_child = &libcosmic_applet::AppletButton {
|
||||||
set_orientation: Orientation::Vertical,
|
set_button_icon_name: "system-shutdown-symbolic",
|
||||||
set_spacing: 10,
|
#[wrap(Some)]
|
||||||
set_margin_top: 20,
|
set_popover_child: main_box = >k4::Box {
|
||||||
set_margin_bottom: 20,
|
set_orientation: Orientation::Vertical,
|
||||||
set_margin_start: 24,
|
set_spacing: 10,
|
||||||
set_margin_end: 24,
|
set_margin_top: 20,
|
||||||
append: settings_button = &Button {
|
set_margin_bottom: 20,
|
||||||
#[wrap(Some)]
|
set_margin_start: 24,
|
||||||
set_child = &Label {
|
set_margin_end: 24,
|
||||||
set_label: "Settings...",
|
append: settings_button = &Button {
|
||||||
set_halign: Align::Start,
|
#[wrap(Some)]
|
||||||
set_hexpand: true
|
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 |_| {
|
append = &Separator {},
|
||||||
let _ = Command::new("cosmic-settings").spawn();
|
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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0-only
|
// SPDX-License-Identifier: MPL-2.0-only
|
||||||
|
|
||||||
|
use calloop::channel::SyncSender;
|
||||||
use gtk4::{
|
use gtk4::{
|
||||||
gdk::Display,
|
gdk::Display,
|
||||||
gio::{self, ApplicationFlags},
|
gio::{self, ApplicationFlags},
|
||||||
|
|
@ -13,7 +14,6 @@ use tokio::sync::mpsc;
|
||||||
use utils::{Activate, WorkspaceEvent};
|
use utils::{Activate, WorkspaceEvent};
|
||||||
use wayland::State;
|
use wayland::State;
|
||||||
use window::CosmicWorkspacesWindow;
|
use window::CosmicWorkspacesWindow;
|
||||||
use calloop::channel::SyncSender;
|
|
||||||
|
|
||||||
mod localize;
|
mod localize;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,8 @@ pub fn spawn_workspaces(tx: glib::Sender<State>) -> SyncSender<WorkspaceEvent> {
|
||||||
running: true,
|
running: true,
|
||||||
};
|
};
|
||||||
let loop_handle = event_loop.handle();
|
let loop_handle = event_loop.handle();
|
||||||
loop_handle.insert_source(workspaces_rx, |e, _, state| {
|
loop_handle
|
||||||
match e {
|
.insert_source(workspaces_rx, |e, _, state| match e {
|
||||||
Event::Msg(WorkspaceEvent::Activate(id)) => {
|
Event::Msg(WorkspaceEvent::Activate(id)) => {
|
||||||
if let Some(w) = state
|
if let Some(w) = state
|
||||||
.workspace_groups
|
.workspace_groups
|
||||||
|
|
@ -136,15 +136,16 @@ pub fn spawn_workspaces(tx: glib::Sender<State>) -> SyncSender<WorkspaceEvent> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Closed => if let Some(workspace_manager) = &mut state.workspace_manager {
|
Event::Closed => {
|
||||||
for g in &mut state.workspace_groups {
|
if let Some(workspace_manager) = &mut state.workspace_manager {
|
||||||
g.workspace_group_handle.destroy();
|
for g in &mut state.workspace_groups {
|
||||||
|
g.workspace_group_handle.destroy();
|
||||||
|
}
|
||||||
|
workspace_manager.stop();
|
||||||
}
|
}
|
||||||
workspace_manager.stop();
|
}
|
||||||
},
|
})
|
||||||
}
|
.unwrap();
|
||||||
|
|
||||||
}).unwrap();
|
|
||||||
while state.running {
|
while state.running {
|
||||||
event_loop
|
event_loop
|
||||||
.dispatch(Duration::from_millis(16), &mut state)
|
.dispatch(Duration::from_millis(16), &mut state)
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ impl WorkspaceButton {
|
||||||
new_button.connect_clicked(move |_| {
|
new_button.connect_clicked(move |_| {
|
||||||
let id_clone = id.clone();
|
let id_clone = id.clone();
|
||||||
if !is_active {
|
if !is_active {
|
||||||
let _ = TX.get()
|
let _ = TX.get().unwrap().send(WorkspaceEvent::Activate(id_clone));
|
||||||
.unwrap()
|
|
||||||
.send(WorkspaceEvent::Activate(id_clone));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,7 @@ impl WorkspaceList {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
scroll_controller.connect_scroll(|_, dx, dy| {
|
scroll_controller.connect_scroll(|_, dx, dy| {
|
||||||
let _ = TX.get()
|
let _ = TX.get().unwrap().send(WorkspaceEvent::Scroll(dx + dy));
|
||||||
.unwrap()
|
|
||||||
.send(WorkspaceEvent::Scroll(dx + dy));
|
|
||||||
Inhibit::default()
|
Inhibit::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
126
libcosmic-applet/src/button.rs
Normal file
126
libcosmic-applet/src/button.rs
Normal file
|
|
@ -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<gtk4::MenuButton>,
|
||||||
|
panel_config: DerefCell<CosmicPanelConfig>,
|
||||||
|
popover: DerefCell<gtk4::Popover>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<AppletButtonInner>)
|
||||||
|
@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<gtk4::Widget>>) {
|
||||||
|
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<gtk4::Widget>>) {
|
||||||
|
self.inner().popover.set_child(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use cosmic_panel_config::config::CosmicPanelConfig;
|
mod button;
|
||||||
use gtk4::{glib, prelude::*, subclass::prelude::*};
|
pub use button::AppletButton;
|
||||||
use relm4_macros::view;
|
|
||||||
|
|
||||||
mod deref_cell;
|
mod deref_cell;
|
||||||
use deref_cell::DerefCell;
|
mod window;
|
||||||
|
pub use window::AppletWindow;
|
||||||
|
|
||||||
// TODO make sure style fits different panel colors?
|
// TODO make sure style fits different panel colors?
|
||||||
// TODO abstraction to start main loop? Work with relm4.
|
// TODO abstraction to start main loop? Work with relm4.
|
||||||
|
|
@ -11,118 +10,4 @@ use deref_cell::DerefCell;
|
||||||
// TODO orientation, etc.
|
// TODO orientation, etc.
|
||||||
// TODO make image size dependent on CosmicPanelConfig?
|
// TODO make image size dependent on CosmicPanelConfig?
|
||||||
// TODO way to have multiple applets with this style, for system tray.
|
// TODO way to have multiple applets with this style, for system tray.
|
||||||
|
// TODO also handle non-popover button? Is GtkMenuButton particularly special, or just use a toggle button?
|
||||||
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<CosmicPanelConfig>,
|
|
||||||
menu_button: DerefCell<gtk4::MenuButton>,
|
|
||||||
popover: DerefCell<gtk4::Popover>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<AppletInner>)
|
|
||||||
@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<gtk4::Widget>>) {
|
|
||||||
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<gtk4::Widget>>) {
|
|
||||||
self.inner().popover.set_child(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
58
libcosmic-applet/src/window.rs
Normal file
58
libcosmic-applet/src/window.rs
Normal file
|
|
@ -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<AppletWindowInner>)
|
||||||
|
@extends gtk4::Widget, gtk4::Window;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppletWindow {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppletWindow {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
glib::Object::new(&[]).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue