diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bar_state.rs | 243 | ||||
| -rw-r--r-- | src/config.rs | 10 | ||||
| -rw-r--r-- | src/i3bar_protocol.rs | 4 | ||||
| -rw-r--r-- | src/text.rs | 49 |
4 files changed, 241 insertions, 65 deletions
diff --git a/src/bar_state.rs b/src/bar_state.rs index eb3047c..656b88e 100644 --- a/src/bar_state.rs +++ b/src/bar_state.rs @@ -1,5 +1,5 @@ use std::cell::{Cell, RefCell}; -use std::collections::{BinaryHeap, HashMap}; +use std::collections::BinaryHeap; use std::io; use std::rc::Rc; @@ -43,6 +43,7 @@ pub struct BarState { pub status_cmd: Option<StatusCmd>, config: Rc<RefCell<Config>>, blocks: Vec<Block>, + blocks_cache: Vec<ComputedBlock>, blocks_updated: bool, surfaces: Vec<Surface>, layer_shell: Attached<zwlr_layer_shell_v1::ZwlrLayerShellV1>, @@ -71,6 +72,7 @@ impl BarState { status_cmd, config: Rc::new(RefCell::new(config)), blocks: Vec::new(), + blocks_cache: Vec::new(), blocks_updated: false, surfaces: Default::default(), layer_shell: env.require_global::<zwlr_layer_shell_v1::ZwlrLayerShellV1>(), @@ -223,7 +225,11 @@ impl BarState { // https://github.com/rust-lang/rust/issues/43244 let mut i = 0; while i != self.surfaces.len() { - if self.surfaces[i].handle_events(&self.blocks, self.blocks_updated) { + if self.surfaces[i].handle_events( + &self.blocks, + &mut self.blocks_cache, + self.blocks_updated, + ) { self.surfaces.remove(i); } else { i += 1; @@ -255,25 +261,30 @@ pub struct Surface { impl Surface { /// Handles any events that have occurred since the last call, redrawing if needed. /// Returns true if the surface should be dropped. - fn handle_events(&mut self, blocks: &[Block], blocks_updated: bool) -> bool { + fn handle_events( + &mut self, + blocks: &[Block], + blocks_cache: &mut Vec<ComputedBlock>, + blocks_updated: bool, + ) -> bool { match self.next_render_event.take() { Some(RenderEvent::Closed) => return true, Some(RenderEvent::Configure { width, height }) => { if self.dimensions != (width, height) { self.dimensions = (width, height); self.layer_surface.set_exclusive_zone(height as _); - self.draw(blocks); + self.draw(blocks, blocks_cache); return false; } } Some(RenderEvent::TagsUpdated) => { - self.draw(blocks); + self.draw(blocks, blocks_cache); return false; } _ => (), } if blocks_updated { - self.draw(blocks); + self.draw(blocks, blocks_cache); return false; } false @@ -315,7 +326,7 @@ impl Surface { None } - fn draw(&mut self, blocks: &[Block]) { + fn draw(&mut self, blocks: &[Block], blocks_cache: &mut Vec<ComputedBlock>) { let config = self.config.borrow(); let stride = 4 * self.dimensions.0 as i32; @@ -367,7 +378,8 @@ impl Surface { } let tags_info = tags_info.borrow(); for (i, label) in self.tags_computed.iter().enumerate() { - let (bg, fg) = match tags_info.get_state(i) { + let state = tags_info.get_state(i); + let (bg, fg) = match state { TagState::Focused => (config.tag_focused_bg, config.tag_focused_fg), TagState::Inactive => (config.tag_bg, config.tag_fg), TagState::Urgent => (config.tag_urgent_bg, config.tag_urgent_fg), @@ -379,6 +391,17 @@ impl Surface { bar_height: height_f, fg_color: fg, bg_color: Some(bg), + r_left: if i == 0 || tags_info.get_state(i.saturating_sub(1)) != state { + config.tags_r + } else { + 0.0 + }, + r_right: if i == 8 || tags_info.get_state(i + 1) != state { + config.tags_r + } else { + 0.0 + }, + overlap: 0.0, }, ); offset_left += label.width; @@ -390,6 +413,7 @@ impl Surface { &cairo_ctx, &*config, blocks, + blocks_cache, &mut self.blocks_btns, offset_left, width_f, @@ -416,26 +440,28 @@ impl Drop for Surface { } } +#[allow(clippy::too_many_arguments)] fn render_blocks( context: &cairo::Context, config: &Config, blocks: &[Block], + blocks_cache: &mut Vec<ComputedBlock>, buttons: &mut ButtonManager<(Option<String>, Option<String>)>, offset_left: f64, full_width: f64, full_height: f64, ) { - let mut blocks_computed = Vec::with_capacity(blocks.len()); - let mut blocks_width = 0.0; - let mut deltas = HashMap::<&str, f64>::new(); - for (i, block) in blocks.iter().enumerate() { + let comp_min_width = |block: &Block| { let markup = block.markup.as_deref() == Some("pango"); - let min_width = match &block.min_width { + match &block.min_width { Some(MinWidth::Pixels(p)) => Some(*p as f64), Some(MinWidth::Text(t)) => Some(text::width_of(t, context, markup, &config.font.0)), None => None, - }; - let full_text = text::Text { + } + }; + let comp_full = |block: &Block, min_width: Option<f64>| { + let markup = block.markup.as_deref() == Some("pango"); + text::Text { text: block.full_text.clone(), attr: text::Attributes { font: config.font.clone(), @@ -446,10 +472,13 @@ fn render_blocks( markup, }, } - .compute(context); - let short_text = block.short_text.clone().map(|short_text| { + .compute(context) + }; + let comp_short = |block: &Block, min_width: Option<f64>| { + let markup = block.markup.as_deref() == Some("pango"); + block.short_text.as_ref().map(|short_text| { text::Text { - text: short_text, + text: short_text.clone(), attr: text::Attributes { font: config.font.clone(), padding_left: 0.0, @@ -460,34 +489,104 @@ fn render_blocks( }, } .compute(context) - }); - blocks_width += full_text.width; - if i + 1 != blocks.len() { - blocks_width += block.separator_block_width as f64; + }) + }; + + // update cashe + if blocks.len() != blocks_cache.len() { + blocks_cache.clear(); + for block in blocks { + let mw = comp_min_width(block); + blocks_cache.push(ComputedBlock { + block: block.clone(), + full: comp_full(block, mw), + short: comp_short(block, mw), + min_width: mw, + }); + } + } else { + for (block, computed) in blocks.iter().zip(blocks_cache.iter_mut()) { + if block.min_width != computed.block.min_width || block.markup != computed.block.markup + { + let mw = comp_min_width(block); + *computed = ComputedBlock { + block: block.clone(), + full: comp_full(block, mw), + short: comp_short(block, mw), + min_width: mw, + }; + } else { + if block.full_text != computed.block.full_text { + computed.block.full_text = block.full_text.clone(); + computed.full = comp_full(block, computed.min_width); + } + if block.short_text != computed.block.short_text { + computed.block.full_text = block.full_text.clone(); + computed.short = comp_short(block, computed.min_width); + } + computed.block.color = block.color; + computed.block.background = block.background; + computed.block.align = block.align; + computed.block.name = block.name.clone(); + computed.block.instance = block.instance.clone(); + computed.block.separator = block.separator; + computed.block.separator_block_width = block.separator_block_width; + } + } + } + + struct BlockSeries<'a> { + blocks: Vec<(&'a ComputedBlock, bool)>, + delta: f64, + separator: bool, + separator_block_width: u8, + } + + let mut blocks_computed = Vec::new(); + let mut blocks_width = 0.0; + let mut s_start = 0; + while s_start < blocks.len() { + let mut s_end = s_start + 1; + let series_name = &blocks[s_start].name; + while s_end < blocks.len() + && blocks[s_end - 1].separator_block_width == 0 + && &blocks[s_end].name == series_name + { + s_end += 1; } - if let Some(short) = &short_text { - if let Some(name) = &block.name { - *deltas.entry(name).or_insert(0.0) += full_text.width - short.width; + + let mut series = BlockSeries { + blocks: Vec::with_capacity(s_end - s_start), + delta: 0.0, + separator: blocks[s_end - 1].separator, + separator_block_width: blocks[s_end - 1].separator_block_width, + }; + + for comp in &blocks_cache[s_start..s_end] { + blocks_width += comp.full.width; + if let Some(short) = &comp.short { + series.delta += comp.full.width - short.width; } + series.blocks.push((comp, false)); } - blocks_computed.push((block.name.as_deref(), full_text, short_text)); + if s_end != blocks.len() { + blocks_width += series.separator_block_width as f64; + } + blocks_computed.push(series); + s_start = s_end; } // Progressively switch to short mode if offset_left + blocks_width > full_width { let mut heap = BinaryHeap::new(); - for (name, delta) in deltas { - if delta > 0.0 { - heap.push((DefaultLess(delta), name)); + for (i, b) in blocks_computed.iter().enumerate() { + if b.delta > 0.0 { + heap.push((DefaultLess(b.delta), i)); } } while let Some((DefaultLess(delta), to_switch)) = heap.pop() { - for (name, full, short) in &mut blocks_computed { - if *name == Some(to_switch) { - if let Some(short) = short { - std::mem::swap(full, short); - } - } + for comp in &mut blocks_computed[to_switch].blocks { + comp.1 = true; } blocks_width -= delta; if offset_left + blocks_width <= full_width { @@ -496,32 +595,51 @@ fn render_blocks( } } + // Remove all the empy blocks + for s in &mut blocks_computed { + s.blocks.retain(|(text, is_short)| { + (*is_short && text.short.as_ref().map_or(false, |s| s.width > 0.0)) + || (!is_short && text.full.width > 0.0) + }); + } + + // Render blocks buttons.clear(); - for (i, (block, computed)) in blocks - .iter() - .zip(blocks_computed.iter().map(|(_, full, _)| full)) - .enumerate() - { - computed.render( - context, - RenderOptions { - x_offset: full_width - blocks_width, - bar_height: full_height, - fg_color: block.color.unwrap_or(config.color), - bg_color: block.background, - }, - ); - buttons.push( - full_width - blocks_width, - computed.width, - (block.name.clone(), block.instance.clone()), - ); - blocks_width -= computed.width; - if i + 1 != blocks.len() && block.separator_block_width > 0 { - let w = block.separator_block_width as f64; - if block.separator { + let mut j = 0; + for series in blocks_computed { + let s_len = series.blocks.len(); + for (i, (computed, is_short)) in series.blocks.into_iter().enumerate() { + let block = &computed.block; + let to_render = if is_short { + computed.short.as_ref().unwrap_or(&computed.full) + } else { + &computed.full + }; + j += 1; + to_render.render( + context, + RenderOptions { + x_offset: full_width - blocks_width, + bar_height: full_height, + fg_color: block.color.unwrap_or(config.color), + bg_color: block.background, + r_left: if i == 0 { config.blocks_r } else { 0.0 }, + r_right: if i + 1 == s_len { config.blocks_r } else { 0.0 }, + overlap: config.blocks_overlap, + }, + ); + buttons.push( + full_width - blocks_width, + to_render.width, + (block.name.clone(), block.instance.clone()), + ); + blocks_width -= to_render.width; + } + if j + 1 != blocks.len() && series.separator_block_width > 0 { + let w = series.separator_block_width as f64; + if series.separator && config.separator_width > 0.0 { config.separator.apply(context); - context.set_line_width(2.0); + context.set_line_width(config.separator_width); context.move_to(full_width - blocks_width + w * 0.5, full_height * 0.1); context.line_to(full_width - blocks_width + w * 0.5, full_height * 0.9); context.stroke().unwrap(); @@ -530,3 +648,10 @@ fn render_blocks( } } } + +struct ComputedBlock { + block: Block, + full: ComputedText, + short: Option<ComputedText>, + min_width: Option<f64>, +} diff --git a/src/config.rs b/src/config.rs index 21ef9a2..66b8c8a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,14 +23,18 @@ pub struct Config { // font and size pub font: Font, pub height: u32, + pub separator_width: f64, + pub tags_r: f64, + pub blocks_r: f64, + pub blocks_overlap: f64, // command pub command: Option<String>, } impl Default for Config { fn default() -> Self { - // A kind of gruvbox theme Self { + // A kind of gruvbox theme background: Color::from_rgba_hex(0x282828ff), color: Color::from_rgba_hex(0xffffffff), separator: Color::from_rgba_hex(0x9a8a62ff), @@ -42,6 +46,10 @@ impl Default for Config { tag_urgent_bg: Color::from_rgba_hex(0xcc241dff), font: Font::new("monospace 10"), height: 24, + separator_width: 2.0, + tags_r: 0.0, + blocks_r: 0.0, + blocks_overlap: 0.0, command: None, } } diff --git a/src/i3bar_protocol.rs b/src/i3bar_protocol.rs index 4ef68ba..891fba6 100644 --- a/src/i3bar_protocol.rs +++ b/src/i3bar_protocol.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use serde::Serialize; use std::io::{Error, ErrorKind, Result}; -#[derive(Deserialize, Default)] +#[derive(Clone, Deserialize, Default)] pub struct Block { pub full_text: String, #[serde(default)] @@ -39,7 +39,7 @@ fn def_sep_width() -> u8 { 9 } -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, PartialEq, Eq)] #[serde(untagged)] pub enum MinWidth { Text(String), diff --git a/src/text.rs b/src/text.rs index 1af64ec..60ed707 100644 --- a/src/text.rs +++ b/src/text.rs @@ -2,6 +2,8 @@ use crate::color::Color; use pango::{EllipsizeMode, FontDescription}; use pangocairo::{cairo, pango}; use serde::Deserialize; +use std::f64::consts::{FRAC_PI_2, PI, TAU}; +use std::ops::Deref; #[derive(Clone, Debug, PartialEq)] pub struct RenderOptions { @@ -9,6 +11,9 @@ pub struct RenderOptions { pub bar_height: f64, pub fg_color: Color, pub bg_color: Option<Color>, + pub r_left: f64, + pub r_right: f64, + pub overlap: f64, } #[derive(Clone, Debug, PartialEq)] @@ -96,6 +101,14 @@ pub struct ComputedText { pub height: f64, } +impl Deref for ComputedText { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.text.text + } +} + impl ComputedText { pub fn render(&self, context: &cairo::Context, options: RenderOptions) { let text = &self.text; @@ -108,7 +121,7 @@ impl ComputedText { } context.save().unwrap(); - context.translate(options.x_offset - 1.0, 0.0); + context.translate(options.x_offset - options.overlap, 0.0); // Set the width/height on the Pango layout so that it word-wraps/ellipises. let text_width = self.width - text.attr.padding_left - text.attr.padding_right; @@ -117,15 +130,24 @@ impl ComputedText { layout.set_width(text_width as i32 * pango::SCALE); layout.set_height(text_height as i32 * pango::SCALE); + // Draw background if let Some(bg) = options.bg_color { bg.apply(context); - context.rectangle(0.0, 0.0, self.width + 1.0, options.bar_height); + rounded_rectangle( + context, + 0.0, + 0.0, + self.width + options.overlap, + options.bar_height, + options.r_left, + options.r_right, + ); context.fill().unwrap(); } options.fg_color.apply(context); context.translate( - text.attr.padding_left + 1.0, + text.attr.padding_left + options.overlap, (options.bar_height - self.height) * 0.5, ); show_pango_layout(context, &layout); @@ -133,6 +155,27 @@ impl ComputedText { } } +fn rounded_rectangle( + context: &cairo::Context, + x: f64, + y: f64, + w: f64, + h: f64, + r_left: f64, + r_right: f64, +) { + if r_left > 0.0 || r_right > 0.0 { + context.new_sub_path(); + context.arc(x + r_left, y + r_left, r_left, PI, 3.0 * FRAC_PI_2); + context.arc(x + w - r_right, y + r_right, r_right, 3.0 * FRAC_PI_2, TAU); + context.arc(x + w - r_right, y + h - r_right, r_right, 0.0, FRAC_PI_2); + context.arc(x + r_left, y + h - r_left, r_left, FRAC_PI_2, PI); + context.close_path(); + } else { + context.rectangle(x, y, w, h); + } +} + pub fn width_of(text: &str, context: &cairo::Context, markup: bool, font: &FontDescription) -> f64 { let text = Text { text: text.into(), |
