oxwm

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