oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
10,589 bytes raw
1
use super::blocks::Block;
2
use super::font::{Font, FontDraw};
3
use crate::Config;
4
use crate::errors::X11Error;
5
use std::time::Instant;
6
use x11rb::COPY_DEPTH_FROM_PARENT;
7
use x11rb::connection::Connection;
8
use x11rb::protocol::xproto::*;
9
use x11rb::rust_connection::RustConnection;
10
11
pub struct Bar {
12
    window: Window,
13
    width: u16,
14
    height: u16,
15
    graphics_context: Gcontext,
16
17
    font_draw: FontDraw,
18
19
    tag_widths: Vec<u16>,
20
    needs_redraw: bool,
21
22
    blocks: Vec<Box<dyn Block>>,
23
    block_last_updates: Vec<Instant>,
24
    block_underlines: Vec<bool>,
25
    status_text: String,
26
27
    tags: Vec<String>,
28
    scheme_normal: crate::ColorScheme,
29
    scheme_occupied: crate::ColorScheme,
30
    scheme_selected: crate::ColorScheme,
31
}
32
33
impl Bar {
34
    pub fn new(
35
        connection: &RustConnection,
36
        screen: &Screen,
37
        screen_num: usize,
38
        config: &Config,
39
        display: *mut x11::xlib::Display,
40
        font: &Font,
41
        x: i16,
42
        y: i16,
43
        width: u16,
44
    ) -> Result<Self, X11Error> {
45
        let window = connection.generate_id()?;
46
        let graphics_context = connection.generate_id()?;
47
48
        let height = (font.height() as f32 * 1.4) as u16;
49
50
        connection.create_window(
51
            COPY_DEPTH_FROM_PARENT,
52
            window,
53
            screen.root,
54
            x,
55
            y,
56
            width,
57
            height,
58
            0,
59
            WindowClass::INPUT_OUTPUT,
60
            screen.root_visual,
61
            &CreateWindowAux::new()
62
                .background_pixel(config.scheme_normal.background)
63
                .event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS)
64
                .override_redirect(1),
65
        )?;
66
67
        connection.create_gc(
68
            graphics_context,
69
            window,
70
            &CreateGCAux::new()
71
                .foreground(config.scheme_normal.foreground)
72
                .background(config.scheme_normal.background),
73
        )?;
74
75
        connection.map_window(window)?;
76
        connection.flush()?;
77
78
        let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) };
79
        let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) };
80
81
        let font_draw = FontDraw::new(display, window as x11::xlib::Drawable, visual, colormap)?;
82
83
        let horizontal_padding = (font.height() as f32 * 0.4) as u16;
84
85
        let tag_widths = config
86
            .tags
87
            .iter()
88
            .map(|tag| {
89
                let text_width = font.text_width(tag);
90
                text_width + (horizontal_padding * 2)
91
            })
92
            .collect();
93
94
        let blocks: Vec<Box<dyn Block>> = config
95
            .status_blocks
96
            .iter()
97
            .map(|block_config| block_config.to_block())
98
            .collect();
99
100
        let block_underlines: Vec<bool> = config
101
            .status_blocks
102
            .iter()
103
            .map(|block_config| block_config.underline)
104
            .collect();
105
106
        let block_last_updates = vec![Instant::now(); blocks.len()];
107
108
        Ok(Bar {
109
            window,
110
            width,
111
            height,
112
            graphics_context,
113
            font_draw,
114
            tag_widths,
115
            needs_redraw: true,
116
            blocks,
117
            block_last_updates,
118
            block_underlines,
119
            status_text: String::new(),
120
            tags: config.tags.clone(),
121
            scheme_normal: config.scheme_normal,
122
            scheme_occupied: config.scheme_occupied,
123
            scheme_selected: config.scheme_selected,
124
        })
125
    }
126
127
    pub fn window(&self) -> Window {
128
        self.window
129
    }
130
131
    pub fn height(&self) -> u16 {
132
        self.height
133
    }
134
135
    pub fn invalidate(&mut self) {
136
        self.needs_redraw = true;
137
    }
138
139
    pub fn update_blocks(&mut self) {
140
        let now = Instant::now();
141
        let mut changed = false;
142
143
        for (i, block) in self.blocks.iter_mut().enumerate() {
144
            let elapsed = now.duration_since(self.block_last_updates[i]);
145
146
            if elapsed >= block.interval() {
147
                if block.content().is_ok() {
148
                    self.block_last_updates[i] = now;
149
                    changed = true;
150
                }
151
            }
152
        }
153
154
        if changed {
155
            let mut parts = Vec::new();
156
            for block in &mut self.blocks {
157
                if let Ok(text) = block.content() {
158
                    parts.push(text);
159
                }
160
            }
161
            self.status_text = parts.join("");
162
            self.needs_redraw = true;
163
        }
164
    }
165
166
    pub fn draw(
167
        &mut self,
168
        connection: &RustConnection,
169
        font: &Font,
170
        display: *mut x11::xlib::Display,
171
        current_tags: u32,
172
        occupied_tags: u32,
173
        draw_blocks: bool,
174
        layout_symbol: &str,
175
        keychord_indicator: Option<&str>,
176
    ) -> Result<(), X11Error> {
177
        if !self.needs_redraw {
178
            return Ok(());
179
        }
180
181
        connection.change_gc(
182
            self.graphics_context,
183
            &ChangeGCAux::new().foreground(self.scheme_normal.background),
184
        )?;
185
        connection.poly_fill_rectangle(
186
            self.window,
187
            self.graphics_context,
188
            &[Rectangle {
189
                x: 0,
190
                y: 0,
191
                width: self.width,
192
                height: self.height,
193
            }],
194
        )?;
195
196
        let mut x_position: i16 = 0;
197
198
        for (tag_index, tag) in self.tags.iter().enumerate() {
199
            let tag_mask = 1 << tag_index;
200
            let is_selected = (current_tags & tag_mask) != 0;
201
            let is_occupied = (occupied_tags & tag_mask) != 0;
202
203
            let tag_width = self.tag_widths[tag_index];
204
205
            let scheme = if is_selected {
206
                &self.scheme_selected
207
            } else if is_occupied {
208
                &self.scheme_occupied
209
            } else {
210
                &self.scheme_normal
211
            };
212
213
            let text_width = font.text_width(tag);
214
            let text_x = x_position + ((tag_width - text_width) / 2) as i16;
215
216
            let top_padding = 4;
217
            let text_y = top_padding + font.ascent();
218
219
            self.font_draw
220
                .draw_text(font, scheme.foreground, text_x, text_y, tag);
221
222
            if is_selected {
223
                let font_height = font.height();
224
                let underline_height = font_height / 8;
225
                let bottom_gap = 3;
226
                let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
227
228
                let underline_padding = 4;
229
                let underline_width = tag_width - underline_padding;
230
                let underline_x = x_position + (underline_padding / 2) as i16;
231
232
                connection.change_gc(
233
                    self.graphics_context,
234
                    &ChangeGCAux::new().foreground(scheme.underline),
235
                )?;
236
                connection.poly_fill_rectangle(
237
                    self.window,
238
                    self.graphics_context,
239
                    &[Rectangle {
240
                        x: underline_x,
241
                        y: underline_y,
242
                        width: underline_width,
243
                        height: underline_height,
244
                    }],
245
                )?;
246
            }
247
248
            x_position += tag_width as i16;
249
        }
250
251
        x_position += 10;
252
253
        let text_x = x_position;
254
        let top_padding = 4;
255
        let text_y = top_padding + font.ascent();
256
257
        self.font_draw.draw_text(
258
            font,
259
            self.scheme_normal.foreground,
260
            text_x,
261
            text_y,
262
            layout_symbol,
263
        );
264
265
        x_position += font.text_width(layout_symbol) as i16;
266
267
        if let Some(indicator) = keychord_indicator {
268
            x_position += 10;
269
270
            let text_x = x_position;
271
            let text_y = top_padding + font.ascent();
272
273
            self.font_draw.draw_text(
274
                font,
275
                self.scheme_selected.foreground,
276
                text_x,
277
                text_y,
278
                indicator,
279
            );
280
        }
281
282
        if draw_blocks && !self.status_text.is_empty() {
283
            let padding = 10;
284
            let mut x_position = self.width as i16 - padding;
285
286
            for (i, block) in self.blocks.iter_mut().enumerate().rev() {
287
                if let Ok(text) = block.content() {
288
                    let text_width = font.text_width(&text);
289
                    x_position -= text_width as i16;
290
291
                    let top_padding = 4;
292
                    let text_y = top_padding + font.ascent();
293
294
                    self.font_draw
295
                        .draw_text(font, block.color(), x_position, text_y, &text);
296
297
                    if self.block_underlines[i] {
298
                        let font_height = font.height();
299
                        let underline_height = font_height / 8;
300
                        let bottom_gap = 3;
301
                        let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
302
303
                        let underline_padding = 8;
304
                        let underline_width = text_width + underline_padding;
305
                        let underline_x = x_position - (underline_padding / 2) as i16;
306
307
                        connection.change_gc(
308
                            self.graphics_context,
309
                            &ChangeGCAux::new().foreground(block.color()),
310
                        )?;
311
312
                        connection.poly_fill_rectangle(
313
                            self.window,
314
                            self.graphics_context,
315
                            &[Rectangle {
316
                                x: underline_x,
317
                                y: underline_y,
318
                                width: underline_width,
319
                                height: underline_height,
320
                            }],
321
                        )?;
322
                    }
323
                }
324
            }
325
        }
326
        connection.flush()?;
327
        unsafe {
328
            x11::xlib::XFlush(display);
329
        }
330
        self.needs_redraw = false;
331
332
        Ok(())
333
    }
334
335
    pub fn handle_click(&self, click_x: i16) -> Option<usize> {
336
        let mut current_x_position = 0;
337
338
        for (tag_index, &tag_width) in self.tag_widths.iter().enumerate() {
339
            if click_x >= current_x_position && click_x < current_x_position + tag_width as i16 {
340
                return Some(tag_index);
341
            }
342
            current_x_position += tag_width as i16;
343
        }
344
        None
345
    }
346
347
    pub fn needs_redraw(&self) -> bool {
348
        self.needs_redraw
349
    }
350
}