oxwm

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