use super::blocks::Block; use super::font::{Font, FontDraw}; use crate::Config; use crate::errors::X11Error; use std::time::Instant; use x11rb::COPY_DEPTH_FROM_PARENT; use x11rb::connection::Connection; use x11rb::protocol::xproto::*; use x11rb::rust_connection::RustConnection; pub struct Bar { window: Window, width: u16, height: u16, graphics_context: Gcontext, font_draw: FontDraw, tag_widths: Vec, needs_redraw: bool, blocks: Vec>, block_last_updates: Vec, block_underlines: Vec, status_text: String, tags: Vec, scheme_normal: crate::ColorScheme, scheme_occupied: crate::ColorScheme, scheme_selected: crate::ColorScheme, } impl Bar { pub fn new( connection: &RustConnection, screen: &Screen, screen_num: usize, config: &Config, display: *mut x11::xlib::Display, font: &Font, x: i16, y: i16, width: u16, ) -> Result { let window = connection.generate_id()?; let graphics_context = connection.generate_id()?; let height = (font.height() as f32 * 1.4) as u16; connection.create_window( COPY_DEPTH_FROM_PARENT, window, screen.root, x, y, width, height, 0, WindowClass::INPUT_OUTPUT, screen.root_visual, &CreateWindowAux::new() .background_pixel(config.scheme_normal.background) .event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS) .override_redirect(1), )?; connection.create_gc( graphics_context, window, &CreateGCAux::new() .foreground(config.scheme_normal.foreground) .background(config.scheme_normal.background), )?; connection.map_window(window)?; connection.flush()?; let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) }; let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) }; let font_draw = FontDraw::new(display, window as x11::xlib::Drawable, visual, colormap)?; let horizontal_padding = (font.height() as f32 * 0.4) as u16; let tag_widths = config .tags .iter() .map(|tag| { let text_width = font.text_width(tag); text_width + (horizontal_padding * 2) }) .collect(); let blocks: Vec> = config .status_blocks .iter() .map(|block_config| block_config.to_block()) .collect(); let block_underlines: Vec = config .status_blocks .iter() .map(|block_config| block_config.underline) .collect(); let block_last_updates = vec![Instant::now(); blocks.len()]; Ok(Bar { window, width, height, graphics_context, font_draw, tag_widths, needs_redraw: true, blocks, block_last_updates, block_underlines, status_text: String::new(), tags: config.tags.clone(), scheme_normal: config.scheme_normal, scheme_occupied: config.scheme_occupied, scheme_selected: config.scheme_selected, }) } pub fn window(&self) -> Window { self.window } pub fn height(&self) -> u16 { self.height } pub fn invalidate(&mut self) { self.needs_redraw = true; } pub fn update_blocks(&mut self) { let now = Instant::now(); let mut changed = false; for (i, block) in self.blocks.iter_mut().enumerate() { let elapsed = now.duration_since(self.block_last_updates[i]); if elapsed >= block.interval() { if block.content().is_ok() { self.block_last_updates[i] = now; changed = true; } } } if changed { let mut parts = Vec::new(); for block in &mut self.blocks { if let Ok(text) = block.content() { parts.push(text); } } self.status_text = parts.join(""); self.needs_redraw = true; } } pub fn draw( &mut self, connection: &RustConnection, font: &Font, display: *mut x11::xlib::Display, current_tags: u32, occupied_tags: u32, draw_blocks: bool, layout_symbol: &str, ) -> Result<(), X11Error> { if !self.needs_redraw { return Ok(()); } connection.change_gc( self.graphics_context, &ChangeGCAux::new().foreground(self.scheme_normal.background), )?; connection.poly_fill_rectangle( self.window, self.graphics_context, &[Rectangle { x: 0, y: 0, width: self.width, height: self.height, }], )?; let mut x_position: i16 = 0; for (tag_index, tag) in self.tags.iter().enumerate() { let tag_mask = 1 << tag_index; let is_selected = (current_tags & tag_mask) != 0; let is_occupied = (occupied_tags & tag_mask) != 0; let tag_width = self.tag_widths[tag_index]; let scheme = if is_selected { &self.scheme_selected } else if is_occupied { &self.scheme_occupied } else { &self.scheme_normal }; let text_width = font.text_width(tag); let text_x = x_position + ((tag_width - text_width) / 2) as i16; let top_padding = 4; let text_y = top_padding + font.ascent(); self.font_draw .draw_text(font, scheme.foreground, text_x, text_y, tag); if is_selected { let font_height = font.height(); let underline_height = font_height / 8; let bottom_gap = 3; let underline_y = self.height as i16 - underline_height as i16 - bottom_gap; let underline_padding = 4; let underline_width = tag_width - underline_padding; let underline_x = x_position + (underline_padding / 2) as i16; connection.change_gc( self.graphics_context, &ChangeGCAux::new().foreground(scheme.underline), )?; connection.poly_fill_rectangle( self.window, self.graphics_context, &[Rectangle { x: underline_x, y: underline_y, width: underline_width, height: underline_height, }], )?; } x_position += tag_width as i16; } x_position += 10; let text_x = x_position; let top_padding = 4; let text_y = top_padding + font.ascent(); self.font_draw.draw_text( font, self.scheme_normal.foreground, text_x, text_y, layout_symbol ); if draw_blocks && !self.status_text.is_empty() { let padding = 10; let mut x_position = self.width as i16 - padding; for (i, block) in self.blocks.iter_mut().enumerate().rev() { if let Ok(text) = block.content() { let text_width = font.text_width(&text); x_position -= text_width as i16; let top_padding = 4; let text_y = top_padding + font.ascent(); self.font_draw .draw_text(font, block.color(), x_position, text_y, &text); if self.block_underlines[i] { let font_height = font.height(); let underline_height = font_height / 8; let bottom_gap = 3; let underline_y = self.height as i16 - underline_height as i16 - bottom_gap; let underline_padding = 8; let underline_width = text_width + underline_padding; let underline_x = x_position - (underline_padding / 2) as i16; connection.change_gc( self.graphics_context, &ChangeGCAux::new().foreground(block.color()), )?; connection.poly_fill_rectangle( self.window, self.graphics_context, &[Rectangle { x: underline_x, y: underline_y, width: underline_width, height: underline_height, }], )?; } } } } connection.flush()?; unsafe { x11::xlib::XFlush(display); } self.needs_redraw = false; Ok(()) } pub fn handle_click(&self, click_x: i16) -> Option { let mut current_x_position = 0; for (tag_index, &tag_width) in self.tag_widths.iter().enumerate() { if click_x >= current_x_position && click_x < current_x_position + tag_width as i16 { return Some(tag_index); } current_x_position += tag_width as i16; } None } pub fn needs_redraw(&self) -> bool { self.needs_redraw } }