summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMax Verevkin <maxxverrr@gmail.com>2022-04-10 11:16:25 +0300
committerMax Verevkin <maxxverrr@gmail.com>2022-04-10 11:23:51 +0300
commitdcc20f7aca51a4ca1f47c15730a1c9e9d8dfdfc7 (patch)
treeeb3e94e385fec28145916ba8f98fe543a32864d0 /src
parentf127f935fc47826d2d3e687a4ad2b3449a370b5f (diff)
downloadi3bar-mango-dcc20f7aca51a4ca1f47c15730a1c9e9d8dfdfc7.tar.gz
rounded corners
Diffstat (limited to 'src')
-rw-r--r--src/bar_state.rs243
-rw-r--r--src/config.rs10
-rw-r--r--src/i3bar_protocol.rs4
-rw-r--r--src/text.rs49
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(),