2022-09-25 22:49:00 +01:00
|
|
|
use super::open_state::OpenState;
|
2022-08-14 14:30:13 +01:00
|
|
|
use crate::collection::Collection;
|
2022-09-25 22:49:00 +01:00
|
|
|
use crate::icon::get_icon;
|
|
|
|
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
|
|
|
use crate::modules::ModuleUpdateEvent;
|
|
|
|
use crate::popup::Popup;
|
|
|
|
use crate::sway::node::{get_node_id, is_node_xwayland};
|
2022-08-14 14:30:13 +01:00
|
|
|
use gtk::prelude::*;
|
|
|
|
use gtk::{Button, IconTheme, Image};
|
|
|
|
use std::rc::Rc;
|
2022-09-25 22:49:00 +01:00
|
|
|
use std::sync::RwLock;
|
|
|
|
use swayipc_async::Node;
|
|
|
|
use tokio::sync::mpsc::Sender;
|
2022-08-14 14:30:13 +01:00
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2022-09-25 22:49:00 +01:00
|
|
|
pub struct Item {
|
2022-08-14 14:30:13 +01:00
|
|
|
pub app_id: String,
|
|
|
|
pub favorite: bool,
|
2022-08-25 23:42:57 +01:00
|
|
|
pub open_state: OpenState,
|
2022-09-25 22:49:00 +01:00
|
|
|
pub windows: Collection<i64, Window>,
|
|
|
|
pub name: Option<String>,
|
2022-08-14 14:30:13 +01:00
|
|
|
pub is_xwayland: bool,
|
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
impl Item {
|
|
|
|
pub const fn new(app_id: String, open_state: OpenState, favorite: bool) -> Self {
|
|
|
|
Self {
|
|
|
|
app_id,
|
|
|
|
favorite,
|
|
|
|
open_state,
|
|
|
|
windows: Collection::new(),
|
|
|
|
name: None,
|
|
|
|
is_xwayland: false,
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
/// Merges the provided node into this launcher item
|
|
|
|
pub fn merge_node(&mut self, node: Node) -> Window {
|
|
|
|
let id = node.id;
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
if self.windows.is_empty() {
|
|
|
|
self.name = node.name.clone();
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
self.is_xwayland = self.is_xwayland || is_node_xwayland(&node);
|
|
|
|
|
|
|
|
let window: Window = node.into();
|
|
|
|
self.windows.insert(id, window.clone());
|
|
|
|
|
|
|
|
self.recalculate_open_state();
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
window
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn unmerge_node(&mut self, node: &Node) {
|
|
|
|
self.windows.remove(&node.id);
|
|
|
|
self.recalculate_open_state();
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn get_name(&self) -> &str {
|
|
|
|
self.name.as_ref().unwrap_or(&self.app_id)
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn set_window_name(&mut self, window_id: i64, name: Option<String>) {
|
|
|
|
if let Some(window) = self.windows.get_mut(&window_id) {
|
|
|
|
if let OpenState::Open { focused: true, .. } = window.open_state {
|
|
|
|
self.name = name.clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
window.name = name;
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn set_unfocused(&mut self) {
|
|
|
|
let focused = self
|
2022-08-25 23:42:57 +01:00
|
|
|
.windows
|
2022-09-25 22:49:00 +01:00
|
|
|
.iter_mut()
|
|
|
|
.find(|window| window.open_state.is_focused());
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
if let Some(focused) = focused {
|
|
|
|
focused.open_state = OpenState::Open {
|
|
|
|
focused: false,
|
|
|
|
urgent: focused.open_state.is_urgent(),
|
|
|
|
};
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
self.recalculate_open_state();
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
2022-09-25 22:49:00 +01:00
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn set_window_focused(&mut self, window_id: i64, focused: bool) {
|
|
|
|
if let Some(window) = self.windows.get_mut(&window_id) {
|
|
|
|
window.open_state =
|
|
|
|
OpenState::merge_states(&[&window.open_state, &OpenState::focused(focused)]);
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
self.recalculate_open_state();
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn set_window_urgent(&mut self, window_id: i64, urgent: bool) {
|
|
|
|
if let Some(window) = self.windows.get_mut(&window_id) {
|
|
|
|
window.open_state =
|
|
|
|
OpenState::merge_states(&[&window.open_state, &OpenState::urgent(urgent)]);
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
self.recalculate_open_state();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets this item's open state
|
|
|
|
/// to the merged result of its windows' open states
|
|
|
|
fn recalculate_open_state(&mut self) {
|
|
|
|
let new_state = OpenState::merge_states(
|
|
|
|
&self
|
|
|
|
.windows
|
|
|
|
.iter()
|
|
|
|
.map(|win| &win.open_state)
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
self.open_state = new_state;
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
impl From<Node> for Item {
|
|
|
|
fn from(node: Node) -> Self {
|
|
|
|
let app_id = get_node_id(&node).to_string();
|
|
|
|
let open_state = OpenState::from_node(&node);
|
|
|
|
let name = node.name.clone();
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
let is_xwayland = is_node_xwayland(&node);
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
let mut windows = Collection::new();
|
|
|
|
windows.insert(node.id, node.into());
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
Self {
|
|
|
|
app_id,
|
|
|
|
favorite: false,
|
|
|
|
open_state,
|
|
|
|
windows,
|
|
|
|
name,
|
|
|
|
is_xwayland,
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
2022-09-25 22:49:00 +01:00
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Window {
|
|
|
|
pub id: i64,
|
|
|
|
pub name: Option<String>,
|
|
|
|
pub open_state: OpenState,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Node> for Window {
|
|
|
|
fn from(node: Node) -> Self {
|
|
|
|
let open_state = OpenState::from_node(&node);
|
|
|
|
|
|
|
|
Self {
|
|
|
|
id: node.id,
|
|
|
|
name: node.name,
|
|
|
|
open_state,
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
2022-09-25 22:49:00 +01:00
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub struct MenuState {
|
|
|
|
pub num_windows: usize,
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub struct ItemButton {
|
|
|
|
pub button: Button,
|
|
|
|
pub persistent: bool,
|
|
|
|
pub show_names: bool,
|
|
|
|
pub menu_state: Rc<RwLock<MenuState>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ItemButton {
|
|
|
|
pub fn new(
|
|
|
|
item: &Item,
|
|
|
|
show_names: bool,
|
|
|
|
show_icons: bool,
|
|
|
|
icon_theme: &IconTheme,
|
|
|
|
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
|
|
|
|
controller_tx: &Sender<ItemEvent>,
|
|
|
|
) -> Self {
|
|
|
|
let mut button = Button::builder();
|
|
|
|
|
|
|
|
if show_names {
|
|
|
|
button = button.label(item.get_name());
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
if show_icons {
|
|
|
|
let icon = get_icon(icon_theme, &item.app_id, 32);
|
|
|
|
if icon.is_some() {
|
|
|
|
let image = Image::from_pixbuf(icon.as_ref());
|
|
|
|
button = button.image(&image).always_show_image(true);
|
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
let button = button.build();
|
|
|
|
|
|
|
|
let style_context = button.style_context();
|
|
|
|
style_context.add_class("item");
|
|
|
|
|
|
|
|
if item.favorite {
|
|
|
|
style_context.add_class("favorite");
|
|
|
|
}
|
|
|
|
if item.open_state.is_open() {
|
|
|
|
style_context.add_class("open");
|
|
|
|
}
|
|
|
|
if item.open_state.is_focused() {
|
|
|
|
style_context.add_class("focused");
|
|
|
|
}
|
|
|
|
if item.open_state.is_urgent() {
|
|
|
|
style_context.add_class("urgent");
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
{
|
|
|
|
let app_id = item.app_id.clone();
|
|
|
|
let tx = controller_tx.clone();
|
|
|
|
button.connect_clicked(move |button| {
|
|
|
|
// lazy check :|
|
|
|
|
let style_context = button.style_context();
|
|
|
|
if style_context.has_class("open") {
|
|
|
|
tx.try_send(ItemEvent::FocusItem(app_id.clone()))
|
|
|
|
.expect("Failed to send item focus event");
|
|
|
|
} else {
|
|
|
|
tx.try_send(ItemEvent::OpenItem(app_id.clone()))
|
|
|
|
.expect("Failed to send item open event");
|
|
|
|
}
|
|
|
|
});
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|
2022-08-25 23:42:57 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
let menu_state = Rc::new(RwLock::new(MenuState {
|
|
|
|
num_windows: item.windows.len(),
|
|
|
|
}));
|
|
|
|
|
|
|
|
{
|
|
|
|
let app_id = item.app_id.clone();
|
|
|
|
let tx = tx.clone();
|
|
|
|
let menu_state = menu_state.clone();
|
|
|
|
|
|
|
|
button.connect_enter_notify_event(move |button, _| {
|
|
|
|
let menu_state = menu_state
|
|
|
|
.read()
|
|
|
|
.expect("Failed to get read lock on item menu state");
|
2022-08-25 23:42:57 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
if menu_state.num_windows > 1 {
|
|
|
|
tx.try_send(ModuleUpdateEvent::Update(LauncherUpdate::Hover(
|
|
|
|
app_id.clone(),
|
|
|
|
)))
|
|
|
|
.expect("Failed to send item open popup event");
|
2022-08-25 23:42:57 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
tx.try_send(ModuleUpdateEvent::OpenPopup(Popup::button_pos(button)))
|
|
|
|
.expect("Failed to send item open popup event");
|
|
|
|
} else {
|
|
|
|
tx.try_send(ModuleUpdateEvent::ClosePopup)
|
|
|
|
.expect("Failed to send item close popup event");
|
|
|
|
}
|
|
|
|
|
|
|
|
Inhibit(false)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
button.show_all();
|
|
|
|
|
|
|
|
Self {
|
|
|
|
button,
|
|
|
|
persistent: item.favorite,
|
|
|
|
show_names,
|
|
|
|
menu_state,
|
2022-08-25 23:42:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
pub fn set_open(&self, open: bool) {
|
|
|
|
self.update_class("open", open);
|
2022-08-25 23:42:57 +01:00
|
|
|
|
2022-09-25 22:49:00 +01:00
|
|
|
if !open {
|
|
|
|
self.set_focused(false);
|
|
|
|
self.set_urgent(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_focused(&self, focused: bool) {
|
|
|
|
self.update_class("focused", focused);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_urgent(&self, urgent: bool) {
|
|
|
|
self.update_class("urgent", urgent);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds or removes a class to the button based on `toggle`.
|
|
|
|
fn update_class(&self, class: &str, toggle: bool) {
|
|
|
|
let style_context = self.button.style_context();
|
|
|
|
|
|
|
|
if toggle {
|
|
|
|
style_context.add_class(class);
|
|
|
|
} else {
|
|
|
|
style_context.remove_class(class);
|
|
|
|
}
|
2022-08-25 23:42:57 +01:00
|
|
|
}
|
2022-08-14 14:30:13 +01:00
|
|
|
}
|