penrose_ui/bar/widgets/
simple.rs

1//! Simple text based widgets built on top of Text
2use crate::{
3    Result,
4    bar::widgets::{Text, TextStyle, Widget},
5    core::Context,
6};
7use penrose::{
8    core::{State, conn::Conn},
9    pure::geometry::Rect,
10    x::{Atom, XConn, XEvent, event::PropertyEvent},
11};
12
13/// A text widget that is set via updating the root window name a la dwm
14#[derive(Clone, Debug, PartialEq)]
15pub struct RootWindowName {
16    inner: Text,
17}
18
19impl RootWindowName {
20    /// Create a new RootWindowName widget
21    pub fn new(style: TextStyle, is_greedy: bool, right_justified: bool) -> Self {
22        Self {
23            inner: Text::new("penrose", style, is_greedy, right_justified),
24        }
25    }
26}
27
28impl<X: XConn> Widget<X> for RootWindowName {
29    fn draw(&mut self, ctx: &mut Context<'_>, s: usize, f: bool, w: u32, h: u32) -> Result<()> {
30        Widget::<X>::draw(&mut self.inner, ctx, s, f, w, h)
31    }
32
33    fn current_extent(&mut self, ctx: &mut Context<'_>, h: u32) -> Result<(u32, u32)> {
34        Widget::<X>::current_extent(&mut self.inner, ctx, h)
35    }
36
37    fn is_greedy(&self) -> bool {
38        Widget::<X>::is_greedy(&self.inner)
39    }
40
41    fn require_draw(&self) -> bool {
42        Widget::<X>::require_draw(&self.inner)
43    }
44
45    fn on_event(&mut self, event: &XEvent, _: &mut State<X>, x: &mut X) -> Result<()> {
46        let name_props = [Atom::NetWmName.as_ref(), Atom::WmName.as_ref()];
47
48        match event {
49            XEvent::PropertyNotify(PropertyEvent {
50                id, atom, is_root, ..
51            }) if *is_root && name_props.contains(&atom.as_ref()) => {
52                self.inner.set_text(x.client_title(*id)?)
53            }
54
55            _ => (),
56        }
57
58        Ok(())
59    }
60}
61
62/// A text widget that shows the name of the currently focused window
63#[derive(Clone, Debug, PartialEq)]
64pub struct ActiveWindowName {
65    inner: Text,
66    max_chars: usize,
67}
68
69impl ActiveWindowName {
70    /// Create a new ActiveWindowName widget with a maximum character count.
71    ///
72    /// max_chars can not be lower than 3.
73    pub fn new(max_chars: usize, style: TextStyle, is_greedy: bool, right_justified: bool) -> Self {
74        Self {
75            inner: Text::new("", style, is_greedy, right_justified),
76            max_chars: max_chars.max(3),
77        }
78    }
79
80    fn set_text(&mut self, txt: &str) {
81        if txt.chars().count() <= self.max_chars {
82            self.inner.set_text(txt);
83        } else {
84            let s: String = txt.chars().take(self.max_chars - 3).collect();
85            self.inner.set_text(format!("{}...", s));
86        }
87    }
88}
89
90impl<X: XConn> Widget<X> for ActiveWindowName {
91    fn draw(&mut self, ctx: &mut Context<'_>, s: usize, f: bool, w: u32, h: u32) -> Result<()> {
92        if f {
93            Widget::<X>::draw(&mut self.inner, ctx, s, f, w, h)
94        } else {
95            ctx.fill_bg(Rect::new(0, 0, w, h))
96        }
97    }
98
99    fn current_extent(&mut self, ctx: &mut Context<'_>, h: u32) -> Result<(u32, u32)> {
100        Widget::<X>::current_extent(&mut self.inner, ctx, h)
101    }
102
103    fn is_greedy(&self) -> bool {
104        Widget::<X>::is_greedy(&self.inner)
105    }
106
107    fn require_draw(&self) -> bool {
108        Widget::<X>::require_draw(&self.inner)
109    }
110
111    fn on_refresh(&mut self, state: &mut State<X>, x: &mut X) -> Result<()> {
112        if let Some(id) = state.client_set.current_client() {
113            self.set_text(&x.client_title(*id)?)
114        } else {
115            self.set_text("")
116        }
117
118        Ok(())
119    }
120
121    fn on_event(&mut self, event: &XEvent, state: &mut State<X>, x: &mut X) -> Result<()> {
122        let name_props = [Atom::NetWmName.as_ref(), Atom::WmName.as_ref()];
123
124        if let Some(focused) = state.client_set.current_client() {
125            match event {
126                XEvent::PropertyNotify(PropertyEvent { id, atom, .. })
127                    if id == focused && name_props.contains(&atom.as_ref()) =>
128                {
129                    self.set_text(&x.client_title(*id)?)
130                }
131
132                _ => (),
133            }
134        }
135
136        Ok(())
137    }
138}
139
140/// A text widget that shows the current layout name
141#[derive(Clone, Debug, PartialEq)]
142pub struct CurrentLayout {
143    inner: Text,
144}
145
146impl CurrentLayout {
147    /// Create a new CurrentLayout widget
148    pub fn new(style: TextStyle) -> Self {
149        Self {
150            inner: Text::new("", style, false, false),
151        }
152    }
153}
154
155impl<X: XConn> Widget<X> for CurrentLayout {
156    fn draw(&mut self, ctx: &mut Context<'_>, s: usize, f: bool, w: u32, h: u32) -> Result<()> {
157        Widget::<X>::draw(&mut self.inner, ctx, s, f, w, h)
158    }
159
160    fn current_extent(&mut self, ctx: &mut Context<'_>, h: u32) -> Result<(u32, u32)> {
161        Widget::<X>::current_extent(&mut self.inner, ctx, h)
162    }
163
164    fn is_greedy(&self) -> bool {
165        Widget::<X>::is_greedy(&self.inner)
166    }
167
168    fn require_draw(&self) -> bool {
169        Widget::<X>::require_draw(&self.inner)
170    }
171
172    fn on_refresh(&mut self, state: &mut State<X>, _: &mut X) -> Result<()> {
173        let layout_name = state.client_set.current_workspace().layout_name();
174        self.inner.set_text(format!("[{layout_name}]"));
175
176        Ok(())
177    }
178}