1use crate::{Context, Result, TextStyle, bar::schedule::UpdateSchedule};
3use penrose::{
4 Color, WinId,
5 core::State,
6 pure::geometry::Rect,
7 x::{XConn, XEvent},
8};
9use std::{
10 fmt,
11 sync::{Arc, Mutex, MutexGuard},
12 time::Duration,
13};
14
15pub mod debug;
16pub mod sys;
17
18mod simple;
19mod workspaces;
20
21pub use simple::{ActiveWindowName, CurrentLayout, RootWindowName};
22pub use workspaces::{DefaultUi, FocusState, Workspaces, WorkspacesUi, WorkspacesWidget, WsMeta};
23
24pub trait Widget<X>
26where
27 X: XConn,
28{
29 fn draw(
31 &mut self,
32 ctx: &mut Context<'_>,
33 screen: usize,
34 screen_has_focus: bool,
35 w: u32,
36 h: u32,
37 ) -> Result<()>;
38
39 fn current_extent(&mut self, ctx: &mut Context<'_>, h: u32) -> Result<(u32, u32)>;
41
42 fn require_draw(&self) -> bool;
44
45 fn is_greedy(&self) -> bool;
49
50 fn update_schedule(&mut self) -> Option<UpdateSchedule> {
53 None
54 }
55
56 #[allow(unused_variables)]
57 fn on_startup(&mut self, state: &mut State<X>, x: &mut X) -> Result<()> {
59 Ok(())
60 }
61
62 #[allow(unused_variables)]
63 fn on_event(&mut self, event: &XEvent, state: &mut State<X>, x: &mut X) -> Result<()> {
65 Ok(())
66 }
67
68 #[allow(unused_variables)]
69 fn on_refresh(&mut self, state: &mut State<X>, x: &mut X) -> Result<()> {
71 Ok(())
72 }
73
74 #[allow(unused_variables)]
75 fn on_new_client(&mut self, id: WinId, state: &mut State<X>, x: &mut X) -> Result<()> {
77 Ok(())
78 }
79}
80
81#[derive(Clone, Debug, PartialEq)]
86pub struct Text {
87 txt: String,
88 fg: Color,
89 bg: Option<Color>,
90 padding: (u32, u32),
91 is_greedy: bool,
92 right_justified: bool,
93 extent: Option<(u32, u32)>,
94 require_draw: bool,
95}
96
97impl Text {
98 pub fn new(
100 txt: impl Into<String>,
101 style: TextStyle,
102 is_greedy: bool,
103 right_justified: bool,
104 ) -> Self {
105 Self {
106 txt: txt.into(),
107 fg: style.fg,
108 bg: style.bg,
109 padding: style.padding,
110 is_greedy,
111 right_justified,
112 extent: None,
113 require_draw: true,
114 }
115 }
116
117 pub fn get_text(&self) -> &String {
119 &self.txt
120 }
121
122 pub fn get_text_mut(&mut self) -> &mut String {
124 &mut self.txt
125 }
126
127 pub fn set_text(&mut self, txt: impl Into<String>) {
129 let new_text = txt.into();
130 if self.txt != new_text {
131 self.txt = new_text;
132 self.extent = None;
133 self.require_draw = true;
134 }
135 }
136}
137
138impl<X: XConn> Widget<X> for Text {
139 fn draw(&mut self, ctx: &mut Context<'_>, _: usize, _: bool, w: u32, h: u32) -> Result<()> {
140 if let Some(color) = self.bg {
141 ctx.fill_rect(Rect::new(0, 0, w, h), color)?;
142 }
143
144 let (ew, eh) = <Self as Widget<X>>::current_extent(self, ctx, h)?;
145 let offset = w as i32 - ew as i32;
146 let right_justify = self.right_justified && self.is_greedy && offset > 0;
147 if right_justify {
148 ctx.translate(offset, 0);
149 ctx.draw_text(&self.txt, h - eh, self.padding, self.fg)?;
150 ctx.translate(-offset, 0);
151 } else {
152 ctx.draw_text(&self.txt, h - eh, self.padding, self.fg)?;
153 }
154
155 self.require_draw = false;
156
157 Ok(())
158 }
159
160 fn current_extent(&mut self, ctx: &mut Context<'_>, _h: u32) -> Result<(u32, u32)> {
161 match self.extent {
162 Some(extent) => Ok(extent),
163 None => {
164 let (l, r) = self.padding;
165 let (w, h) = ctx.text_extent(&self.txt)?;
166 let extent = (w + l + r, h);
167 self.extent = Some(extent);
168
169 Ok(extent)
170 }
171 }
172 }
173
174 fn require_draw(&self) -> bool {
175 self.require_draw
176 }
177
178 fn is_greedy(&self) -> bool {
179 self.is_greedy
180 }
181}
182
183pub struct RefreshText {
224 inner: Text,
225 get_text: Box<dyn Fn() -> String>,
226}
227
228impl fmt::Debug for RefreshText {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.debug_struct("RefreshText")
231 .field("inner", &self.inner)
232 .finish()
233 }
234}
235
236impl RefreshText {
237 pub fn new<F>(style: TextStyle, get_text: F) -> Self
240 where
241 F: Fn() -> String + 'static,
242 {
243 Self {
244 inner: Text::new("", style, false, false),
245 get_text: Box::new(get_text),
246 }
247 }
248}
249
250impl<X: XConn> Widget<X> for RefreshText {
251 fn draw(&mut self, ctx: &mut Context<'_>, s: usize, f: bool, w: u32, h: u32) -> Result<()> {
252 Widget::<X>::draw(&mut self.inner, ctx, s, f, w, h)
253 }
254
255 fn current_extent(&mut self, ctx: &mut Context<'_>, h: u32) -> Result<(u32, u32)> {
256 Widget::<X>::current_extent(&mut self.inner, ctx, h)
257 }
258
259 fn is_greedy(&self) -> bool {
260 Widget::<X>::is_greedy(&self.inner)
261 }
262
263 fn require_draw(&self) -> bool {
264 Widget::<X>::require_draw(&self.inner)
265 }
266
267 fn on_refresh(&mut self, _: &mut State<X>, _: &mut X) -> Result<()> {
268 let txt = (self.get_text)();
269
270 self.inner.set_text(txt);
271
272 Ok(())
273 }
274}
275
276pub struct IntervalText {
320 inner: Arc<Mutex<Text>>,
321 interval: Duration,
322 get_text: Option<Box<dyn Fn() -> Option<String> + Send + 'static>>,
323}
324
325impl fmt::Debug for IntervalText {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.debug_struct("RefreshText")
328 .field("inner", &self.inner)
329 .field("interval", &self.interval)
330 .finish()
331 }
332}
333
334impl IntervalText {
335 pub fn new<F>(
339 style: TextStyle,
340 get_text: F,
341 interval: Duration,
342 is_greedy: bool,
343 right_justified: bool,
344 ) -> Self
345 where
346 F: Fn() -> Option<String> + Send + 'static,
347 {
348 let inner = Arc::new(Mutex::new(Text::new("", style, is_greedy, right_justified)));
349
350 Self {
351 inner,
352 interval,
353 get_text: Some(Box::new(get_text)),
354 }
355 }
356
357 fn inner_guard(&self) -> MutexGuard<'_, Text> {
358 match self.inner.lock() {
359 Ok(inner) => inner,
360 Err(poisoned) => poisoned.into_inner(),
361 }
362 }
363}
364
365impl<X: XConn> Widget<X> for IntervalText {
366 fn draw(&mut self, ctx: &mut Context<'_>, s: usize, f: bool, w: u32, h: u32) -> Result<()> {
367 Widget::<X>::draw(&mut *self.inner_guard(), ctx, s, f, w, h)
368 }
369
370 fn current_extent(&mut self, ctx: &mut Context<'_>, h: u32) -> Result<(u32, u32)> {
371 Widget::<X>::current_extent(&mut *self.inner_guard(), ctx, h)
372 }
373
374 fn is_greedy(&self) -> bool {
375 Widget::<X>::is_greedy(&*self.inner_guard())
376 }
377
378 fn require_draw(&self) -> bool {
379 Widget::<X>::require_draw(&*self.inner_guard())
380 }
381
382 fn update_schedule(&mut self) -> Option<UpdateSchedule> {
383 Some(UpdateSchedule::new(
384 self.interval,
385 self.get_text.take().unwrap(),
386 self.inner.clone(),
387 ))
388 }
389}