1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-04-19 19:34:24 +02:00

Merge pull request #283 from malicean/feat/workspaces-visible

feat(workspaces): visible CSS selector
This commit is contained in:
Jake Stanger 2023-08-26 22:20:40 +01:00 committed by GitHub
commit af1f9e39b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 80 deletions

View file

@ -103,6 +103,7 @@ end:
| `.workspaces` | Workspaces widget box |
| `.workspaces .item` | Workspace button |
| `.workspaces .item.focused` | Workspace button (workspace focused) |
| `.workspaces .item.visible` | Workspace button (workspace visible, including focused) |
| `.workspaces .item.inactive` | Workspace button (favourite, not currently open)
| `.workspaces .item .icon` | Workspace button icon (any type) |
| `.workspaces .item .text-icon` | Workspace button icon (textual only) |

View file

@ -1,4 +1,4 @@
use super::{Workspace, WorkspaceClient, WorkspaceUpdate};
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{arc_mut, lock, send};
use color_eyre::Result;
use hyprland::data::{Workspace as HWorkspace, Workspaces};
@ -52,11 +52,8 @@ impl EventClient {
let workspace_name = get_workspace_name(workspace_type);
let prev_workspace = lock!(active);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let Some(workspace) = workspace {
send!(tx, WorkspaceUpdate::Add(workspace));
@ -80,10 +77,7 @@ impl EventClient {
);
let workspace_name = get_workspace_name(workspace_type);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
workspace.map_or_else(
|| {
@ -92,7 +86,7 @@ impl EventClient {
|workspace| {
// there may be another type of update so dispatch that regardless of focus change
send!(tx, WorkspaceUpdate::Update(workspace.clone()));
if !focused {
if !workspace.visibility.is_focused() {
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
}
},
@ -117,12 +111,11 @@ impl EventClient {
);
let workspace_name = get_workspace_name(workspace_type);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let (Some(workspace), false) = (workspace, focused) {
if let Some((false, workspace)) =
workspace.map(|w| (w.visibility.is_focused(), w))
{
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
} else {
error!("Unable to locate workspace");
@ -142,15 +135,12 @@ impl EventClient {
let mut prev_workspace = lock!(active);
let workspace_name = get_workspace_name(workspace_type);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let Some(workspace) = workspace {
send!(tx, WorkspaceUpdate::Move(workspace.clone()));
if !focused {
if !workspace.visibility.is_focused() {
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
}
}
@ -180,32 +170,28 @@ impl EventClient {
workspace: Workspace,
tx: &Sender<WorkspaceUpdate>,
) {
let old = prev_workspace
.as_ref()
.map(|w| w.name.clone())
.unwrap_or_default();
send!(
tx,
WorkspaceUpdate::Focus {
old,
new: workspace.name.clone(),
old: prev_workspace.take(),
new: workspace.clone(),
}
);
prev_workspace.replace(workspace);
}
/// Gets a workspace by name from the server.
///
/// Use `focused` to manually mark the workspace as focused,
/// as this is not automatically checked.
fn get_workspace(name: &str, focused: bool) -> Option<Workspace> {
/// Gets a workspace by name from the server, given the active workspace if known.
fn get_workspace(name: &str, active: Option<&Workspace>) -> Option<Workspace> {
Workspaces::get()
.expect("Failed to get workspaces")
.find_map(|w| {
if w.name == name {
Some(Workspace::from((focused, w)))
let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| {
create_is_visible()(w)
}));
Some(Workspace::from((vis, w)))
} else {
None
}
@ -214,7 +200,7 @@ impl EventClient {
/// Gets the active workspace from the server.
fn get_active_workspace() -> Result<Workspace> {
let w = HWorkspace::get_active().map(|w| Workspace::from((true, w)))?;
let w = HWorkspace::get_active().map(|w| Workspace::from((Visibility::focused(), w)))?;
Ok(w)
}
}
@ -236,13 +222,16 @@ impl WorkspaceClient for EventClient {
{
let tx = self.workspace_tx.clone();
let active_name = HWorkspace::get_active()
.map(|active| active.name)
.unwrap_or_default();
let active_id = HWorkspace::get_active().ok().map(|active| active.name);
let is_visible = create_is_visible();
let workspaces = Workspaces::get()
.expect("Failed to get workspaces")
.map(|w| Workspace::from((w.name == active_name, w)))
.map(|w| {
let vis = Visibility::from((&w, active_id.as_deref(), &is_visible));
Workspace::from((vis, w))
})
.collect();
send!(tx, WorkspaceUpdate::Init(workspaces));
@ -271,13 +260,36 @@ fn get_workspace_name(name: WorkspaceType) -> String {
}
}
impl From<(bool, hyprland::data::Workspace)> for Workspace {
fn from((focused, workspace): (bool, hyprland::data::Workspace)) -> Self {
/// Creates a function which determines if a workspace is visible. This function makes a Hyprland call that allocates so it should be cached when possible, but it is only valid so long as workspaces do not change so it should not be stored long term
fn create_is_visible() -> impl Fn(&HWorkspace) -> bool {
let monitors = hyprland::data::Monitors::get().map_or(Vec::new(), |ms| ms.to_vec());
move |w| monitors.iter().any(|m| m.active_workspace.id == w.id)
}
impl From<(Visibility, HWorkspace)> for Workspace {
fn from((visibility, workspace): (Visibility, HWorkspace)) -> Self {
Self {
id: workspace.id.to_string(),
name: workspace.name,
monitor: workspace.monitor,
focused,
visibility,
}
}
}
impl<'a, 'f, F> From<(&'a HWorkspace, Option<&str>, F)> for Visibility
where
F: FnOnce(&'f HWorkspace) -> bool,
'a: 'f,
{
fn from((workspace, active_name, is_visible): (&'a HWorkspace, Option<&str>, F)) -> Self {
if Some(workspace.name.as_str()) == active_name {
Self::focused()
} else if is_visible(workspace) {
Self::visible()
} else {
Self::Hidden
}
}
}

View file

@ -75,8 +75,38 @@ pub struct Workspace {
pub name: String,
/// Name of the monitor (output) the workspace is located on
pub monitor: String,
/// Whether the workspace is in focus
pub focused: bool,
/// How visible the workspace is
pub visibility: Visibility,
}
/// Indicates workspace visibility. Visible workspaces have a boolean flag to indicate if they are also focused.
/// Yes, this is the same signature as Option<bool>, but it's impl is a lot more suited for our case.
#[derive(Debug, Copy, Clone)]
pub enum Visibility {
Visible(bool),
Hidden,
}
impl Visibility {
pub fn visible() -> Self {
Self::Visible(false)
}
pub fn focused() -> Self {
Self::Visible(true)
}
pub fn is_visible(self) -> bool {
matches!(self, Self::Visible(_))
}
pub fn is_focused(self) -> bool {
if let Self::Visible(focused) = self {
focused
} else {
false
}
}
}
#[derive(Debug, Clone)]
@ -90,8 +120,8 @@ pub enum WorkspaceUpdate {
Move(Workspace),
/// Declares focus moved from the old workspace to the new.
Focus {
old: String,
new: String,
old: Option<Workspace>,
new: Workspace,
},
}

View file

@ -1,4 +1,4 @@
use super::{Workspace, WorkspaceClient, WorkspaceUpdate};
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{await_sync, send};
use async_once::AsyncOnce;
use color_eyre::Report;
@ -105,22 +105,50 @@ pub fn get_sub_client() -> &'static SwayEventClient {
impl From<Node> for Workspace {
fn from(node: Node) -> Self {
let visibility = Visibility::from(&node);
Self {
id: node.id.to_string(),
name: node.name.unwrap_or_default(),
monitor: node.output.unwrap_or_default(),
focused: node.focused,
visibility,
}
}
}
impl From<swayipc_async::Workspace> for Workspace {
fn from(workspace: swayipc_async::Workspace) -> Self {
let visibility = Visibility::from(&workspace);
Self {
id: workspace.id.to_string(),
name: workspace.name,
monitor: workspace.output,
focused: workspace.focused,
visibility,
}
}
}
impl From<&Node> for Visibility {
fn from(node: &Node) -> Self {
if node.focused {
Self::focused()
} else if node.visible.unwrap_or(false) {
Self::visible()
} else {
Self::Hidden
}
}
}
impl From<&swayipc_async::Workspace> for Visibility {
fn from(workspace: &swayipc_async::Workspace) -> Self {
if workspace.focused {
Self::focused()
} else if workspace.visible {
Self::visible()
} else {
Self::Hidden
}
}
}
@ -139,16 +167,8 @@ impl From<WorkspaceEvent> for WorkspaceUpdate {
.unwrap_or_default(),
),
WorkspaceChange::Focus => Self::Focus {
old: event
.old
.expect("Missing old workspace")
.name
.unwrap_or_default(),
new: event
.current
.expect("Missing current workspace")
.name
.unwrap_or_default(),
old: event.old.map(Workspace::from),
new: Workspace::from(event.current.expect("Missing current workspace")),
},
WorkspaceChange::Move => {
Self::Move(event.current.expect("Missing current workspace").into())

View file

@ -1,4 +1,4 @@
use crate::clients::compositor::{Compositor, Workspace, WorkspaceUpdate};
use crate::clients::compositor::{Compositor, Visibility, Workspace, WorkspaceUpdate};
use crate::config::CommonConfig;
use crate::image::new_icon_button;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
@ -76,8 +76,7 @@ const fn default_icon_size() -> i32 {
/// Creates a button from a workspace
fn create_button(
name: &str,
focused: bool,
inactive: bool,
visibility: Visibility,
name_map: &HashMap<String, String>,
icon_theme: &IconTheme,
icon_size: i32,
@ -91,10 +90,12 @@ fn create_button(
let style_context = button.style_context();
style_context.add_class("item");
if focused {
if visibility.is_visible() {
style_context.add_class("visible");
}
if visibility.is_focused() {
style_context.add_class("focused");
} else if inactive {
style_context.add_class("inactive");
}
{
@ -131,7 +132,7 @@ fn reorder_workspaces(container: &gtk::Box) {
impl WorkspacesModule {
fn show_workspace_check(&self, output: &String, work: &Workspace) -> bool {
(work.focused || !self.hidden.contains(&work.name))
(work.visibility.is_focused() || !self.hidden.contains(&work.name))
&& (self.all_monitors || output == &work.monitor)
}
}
@ -212,11 +213,10 @@ impl Module<gtk::Box> for WorkspacesModule {
let mut added = HashSet::new();
let mut add_workspace = |name: &str, focused: bool| {
let mut add_workspace = |name: &str, visibility: Visibility| {
let item = create_button(
name,
focused,
false,
visibility,
&name_map,
&icon_theme,
icon_size,
@ -230,7 +230,7 @@ impl Module<gtk::Box> for WorkspacesModule {
// add workspaces from client
for workspace in &workspaces {
if self.show_workspace_check(&output_name, workspace) {
add_workspace(&workspace.name, workspace.focused);
add_workspace(&workspace.name, workspace.visibility);
added.insert(workspace.name.to_string());
}
}
@ -238,7 +238,7 @@ impl Module<gtk::Box> for WorkspacesModule {
let mut add_favourites = |names: &Vec<String>| {
for name in names {
if !added.contains(name) {
add_workspace(name, false);
add_workspace(name, Visibility::Hidden);
added.insert(name.to_string());
fav_names.push(name.to_string());
}
@ -264,14 +264,20 @@ impl Module<gtk::Box> for WorkspacesModule {
}
}
WorkspaceUpdate::Focus { old, new } => {
let old = button_map.get(&old);
if let Some(old) = old {
old.style_context().remove_class("focused");
if let Some(btn) = old.as_ref().and_then(|w| button_map.get(&w.name)) {
if Some(new.monitor) == old.map(|w| w.monitor) {
btn.style_context().remove_class("visible");
}
btn.style_context().remove_class("focused");
}
let new = button_map.get(&new);
if let Some(new) = new {
new.style_context().add_class("focused");
let new = button_map.get(&new.name);
if let Some(btn) = new {
let style = btn.style_context();
style.add_class("visible");
style.add_class("focused");
}
}
WorkspaceUpdate::Add(workspace) => {
@ -284,8 +290,7 @@ impl Module<gtk::Box> for WorkspacesModule {
let name = workspace.name;
let item = create_button(
&name,
workspace.focused,
false,
workspace.visibility,
&name_map,
&icon_theme,
icon_size,
@ -310,8 +315,7 @@ impl Module<gtk::Box> for WorkspacesModule {
let name = workspace.name;
let item = create_button(
&name,
workspace.focused,
false,
workspace.visibility,
&name_map,
&icon_theme,
icon_size,