1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-17 14:51:04 +02:00
ironbar/src/modules/launcher/item.rs

300 lines
8.1 KiB
Rust
Raw Normal View History

use super::open_state::OpenState;
2022-08-14 14:30:13 +01:00
use crate::collection::Collection;
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;
use std::sync::RwLock;
use swayipc_async::Node;
use tokio::sync::mpsc::Sender;
2022-08-14 14:30:13 +01:00
#[derive(Debug, Clone)]
pub struct Item {
2022-08-14 14:30:13 +01:00
pub app_id: String,
pub favorite: bool,
pub open_state: OpenState,
pub windows: Collection<i64, Window>,
pub name: Option<String>,
2022-08-14 14:30:13 +01:00
pub is_xwayland: bool,
}
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
/// 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
if self.windows.is_empty() {
self.name = node.name.clone();
}
2022-08-14 14:30:13 +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
window
2022-08-14 14:30:13 +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
pub fn get_name(&self) -> &str {
self.name.as_ref().unwrap_or(&self.app_id)
2022-08-14 14:30:13 +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
pub fn set_unfocused(&mut self) {
let focused = self
.windows
.iter_mut()
.find(|window| window.open_state.is_focused());
2022-08-14 14:30:13 +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
self.recalculate_open_state();
2022-08-14 14:30:13 +01:00
}
}
2022-08-14 14:30:13 +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
self.recalculate_open_state();
}
}
2022-08-14 14:30:13 +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
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
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
let is_xwayland = is_node_xwayland(&node);
2022-08-14 14:30:13 +01:00
let mut windows = Collection::new();
windows.insert(node.id, node.into());
2022-08-14 14:30:13 +01:00
Self {
app_id,
favorite: false,
open_state,
windows,
name,
is_xwayland,
}
2022-08-14 14:30:13 +01:00
}
}
2022-08-14 14:30:13 +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-08-14 14:30:13 +01:00
pub struct MenuState {
pub num_windows: usize,
}
2022-08-14 14:30:13 +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
}
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
}
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
}
{
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
}
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");
if menu_state.num_windows > 1 {
tx.try_send(ModuleUpdateEvent::Update(LauncherUpdate::Hover(
app_id.clone(),
)))
.expect("Failed to send item open popup event");
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,
}
}
pub fn set_open(&self, open: bool) {
self.update_class("open", open);
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-14 14:30:13 +01:00
}