oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
10,097 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
    ) -> Result<(), X11Error> {
176
        if !self.needs_redraw {
177
            return Ok(());
178
        }
179
180
        connection.change_gc(
181
            self.graphics_context,
182
            &ChangeGCAux::new().foreground(self.scheme_normal.background),
183
        )?;
184
        connection.poly_fill_rectangle(
185
            self.window,
186
            self.graphics_context,
187
            &[Rectangle {
188
                x: 0,
189
                y: 0,
190
                width: self.width,
191
                height: self.height,
192
            }],
193
        )?;
194
195
        let mut x_position: i16 = 0;
196
197
        for (tag_index, tag) in self.tags.iter().enumerate() {
198
            let tag_mask = 1 << tag_index;
199
            let is_selected = (current_tags & tag_mask) != 0;
200
            let is_occupied = (occupied_tags & tag_mask) != 0;
201
202
            let tag_width = self.tag_widths[tag_index];
203
204
            let scheme = if is_selected {
205
                &self.scheme_selected
206
            } else if is_occupied {
207
                &self.scheme_occupied
208
            } else {
209
                &self.scheme_normal
210
            };
211
212
            let text_width = font.text_width(tag);
213
            let text_x = x_position + ((tag_width - text_width) / 2) as i16;
214
215
            let top_padding = 4;
216
            let text_y = top_padding + font.ascent();
217
218
            self.font_draw
219
                .draw_text(font, scheme.foreground, text_x, text_y, tag);
220
221
            if is_selected {
222
                let font_height = font.height();
223
                let underline_height = font_height / 8;
224
                let bottom_gap = 3;
225
                let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
226
227
                let underline_padding = 4;
228
                let underline_width = tag_width - underline_padding;
229
                let underline_x = x_position + (underline_padding / 2) as i16;
230
231
                connection.change_gc(
232
                    self.graphics_context,
233
                    &ChangeGCAux::new().foreground(scheme.underline),
234
                )?;
235
                connection.poly_fill_rectangle(
236
                    self.window,
237
                    self.graphics_context,
238
                    &[Rectangle {
239
                        x: underline_x,
240
                        y: underline_y,
241
                        width: underline_width,
242
                        height: underline_height,
243
                    }],
244
                )?;
245
            }
246
247
            x_position += tag_width as i16;
248
        }
249
250
        x_position += 10;
251
252
        let text_x = x_position;
253
        let top_padding = 4;
254
        let text_y = top_padding + font.ascent();
255
256
        self.font_draw.draw_text(
257
            font,
258
            self.scheme_normal.foreground,
259
            text_x,
260
            text_y,
261
            layout_symbol
262
        );
263
264
        if draw_blocks && !self.status_text.is_empty() {
265
            let padding = 10;
266
            let mut x_position = self.width as i16 - padding;
267
268
            for (i, block) in self.blocks.iter_mut().enumerate().rev() {
269
                if let Ok(text) = block.content() {
270
                    let text_width = font.text_width(&text);
271
                    x_position -= text_width as i16;
272
273
                    let top_padding = 4;
274
                    let text_y = top_padding + font.ascent();
275
276
                    self.font_draw
277
                        .draw_text(font, block.color(), x_position, text_y, &text);
278
279
                    if self.block_underlines[i] {
280
                        let font_height = font.height();
281
                        let underline_height = font_height / 8;
282
                        let bottom_gap = 3;
283
                        let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
284
285
                        let underline_padding = 8;
286
                        let underline_width = text_width + underline_padding;
287
                        let underline_x = x_position - (underline_padding / 2) as i16;
288
289
                        connection.change_gc(
290
                            self.graphics_context,
291
                            &ChangeGCAux::new().foreground(block.color()),
292
                        )?;
293
294
                        connection.poly_fill_rectangle(
295
                            self.window,
296
                            self.graphics_context,
297
                            &[Rectangle {
298
                                x: underline_x,
299
                                y: underline_y,
300
                                width: underline_width,
301
                                height: underline_height,
302
                            }],
303
                        )?;
304
                    }
305
                }
306
            }
307
        }
308
        connection.flush()?;
309
        unsafe {
310
            x11::xlib::XFlush(display);
311
        }
312
        self.needs_redraw = false;
313
314
        Ok(())
315
    }
316
317
    pub fn handle_click(&self, click_x: i16) -> Option<usize> {
318
        let mut current_x_position = 0;
319
320
        for (tag_index, &tag_width) in self.tag_widths.iter().enumerate() {
321
            if click_x >= current_x_position && click_x < current_x_position + tag_width as i16 {
322
                return Some(tag_index);
323
            }
324
            current_x_position += tag_width as i16;
325
        }
326
        None
327
    }
328
329
    pub fn needs_redraw(&self) -> bool {
330
        self.needs_redraw
331
    }
332
}