use serde_json::{Map, Value};
pub const CALCULATED: i64 = -1;
pub struct QTilePowerline;
impl Default for QTilePowerline {
fn default() -> Self {
Self::new()
}
}
impl QTilePowerline {
pub fn new() -> Self {
Self
}
pub fn do_setup(&self, obj: &mut Map<String, Value>) {
obj.insert(
"powerline".to_string(),
Value::String("<QTilePowerline>".into()),
);
}
}
pub struct PowerlineTextBox {
pub text: String,
pub width: i64,
pub side: String,
pub update_interval: f64,
pub did_run_timer_setup: bool,
pub configured: bool,
pub bar_draw_calls: u32,
pub layout_markup: bool,
}
impl PowerlineTextBox {
pub fn new(
timeout: f64,
text: impl Into<String>,
width: i64,
side: impl Into<String>,
update_interval: Option<f64>,
) -> Self {
let update_interval = update_interval.unwrap_or(timeout);
Self {
text: text.into(),
width,
side: side.into(),
update_interval,
did_run_timer_setup: false,
configured: false,
bar_draw_calls: 0,
layout_markup: false,
}
}
pub fn with_defaults() -> Self {
Self::new(2.0, " ", CALCULATED, "right", None)
}
pub fn update<R>(&mut self, mut render: R) -> bool
where
R: FnMut(&str) -> String,
{
if !self.configured {
return true;
}
self.text = render(&self.side);
self.bar_draw_calls += 1;
true
}
pub fn cmd_update<R>(&mut self, _text: &str, render: R) -> bool
where
R: FnMut(&str) -> String,
{
self.update(render)
}
pub fn cmd_get(&self) -> &str {
&self.text
}
pub fn timer_setup(&mut self) {
if !self.did_run_timer_setup {
self.did_run_timer_setup = true;
}
}
pub fn _configure(&mut self) {
self.configured = true;
if self.layout_markup {
return;
}
self.layout_markup = true;
self.timer_setup();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calculated_constant_matches_libqtile_sentinel() {
assert_eq!(CALCULATED, -1);
}
#[test]
fn qtile_powerline_do_setup_assigns_powerline_attribute() {
let p = QTilePowerline::new();
let mut obj = Map::new();
p.do_setup(&mut obj);
assert!(obj.contains_key("powerline"));
}
#[test]
fn powerline_text_box_defaults_match_upstream_signature() {
let t = PowerlineTextBox::with_defaults();
assert_eq!(t.text, " ");
assert_eq!(t.width, CALCULATED);
assert_eq!(t.side, "right");
assert!((t.update_interval - 2.0).abs() < 1e-9);
assert!(!t.did_run_timer_setup);
assert!(!t.configured);
}
#[test]
fn update_interval_overrides_timeout_when_supplied() {
let t = PowerlineTextBox::new(2.0, " ", CALCULATED, "right", Some(5.0));
assert!((t.update_interval - 5.0).abs() < 1e-9);
}
#[test]
fn update_interval_falls_back_to_timeout_when_none() {
let t = PowerlineTextBox::new(3.5, " ", CALCULATED, "right", None);
assert!((t.update_interval - 3.5).abs() < 1e-9);
}
#[test]
fn update_skips_when_not_configured() {
let mut t = PowerlineTextBox::with_defaults();
let mut render_calls = 0;
let r = t.update(|_side| {
render_calls += 1;
"rendered".to_string()
});
assert!(r);
assert_eq!(render_calls, 0);
assert_eq!(t.text, " ");
assert_eq!(t.bar_draw_calls, 0);
}
#[test]
fn update_renders_and_draws_when_configured() {
let mut t = PowerlineTextBox::with_defaults();
t.configured = true;
let mut last_side = String::new();
let r = t.update(|side| {
last_side = side.to_string();
"POWERLINE".to_string()
});
assert!(r);
assert_eq!(t.text, "POWERLINE");
assert_eq!(last_side, "right");
assert_eq!(t.bar_draw_calls, 1);
}
#[test]
fn update_passes_side_left_for_left_widget() {
let mut t = PowerlineTextBox::new(2.0, " ", CALCULATED, "left", None);
t.configured = true;
let mut last_side = String::new();
let _ = t.update(|side| {
last_side = side.to_string();
String::new()
});
assert_eq!(last_side, "left");
}
#[test]
fn cmd_get_returns_current_text() {
let mut t = PowerlineTextBox::with_defaults();
t.text = "HELLO".to_string();
assert_eq!(t.cmd_get(), "HELLO");
}
#[test]
fn cmd_update_delegates_to_update() {
let mut t = PowerlineTextBox::with_defaults();
t.configured = true;
let mut calls = 0;
let r = t.cmd_update("ignored", |_side| {
calls += 1;
"RENDERED".to_string()
});
assert!(r);
assert_eq!(calls, 1);
assert_eq!(t.text, "RENDERED");
}
#[test]
fn timer_setup_first_call_flips_flag() {
let mut t = PowerlineTextBox::with_defaults();
assert!(!t.did_run_timer_setup);
t.timer_setup();
assert!(t.did_run_timer_setup);
}
#[test]
fn timer_setup_subsequent_calls_no_op() {
let mut t = PowerlineTextBox::with_defaults();
t.timer_setup();
t.timer_setup();
t.timer_setup();
assert!(t.did_run_timer_setup);
}
#[test]
fn configure_marks_widget_configured_and_runs_timer_setup() {
let mut t = PowerlineTextBox::with_defaults();
assert!(!t.configured);
assert!(!t.did_run_timer_setup);
t._configure();
assert!(t.configured);
assert!(t.did_run_timer_setup);
assert!(t.layout_markup);
}
#[test]
fn configure_skips_relayout_when_layout_markup_already_true() {
let mut t = PowerlineTextBox::with_defaults();
t.layout_markup = true;
t._configure();
assert!(t.configured);
assert!(!t.did_run_timer_setup);
}
#[test]
fn update_returns_true_unconditionally() {
let mut t = PowerlineTextBox::with_defaults();
assert!(t.update(|_| String::new()));
t.configured = true;
assert!(t.update(|_| String::new()));
}
#[test]
fn bar_draw_calls_accumulate_across_updates() {
let mut t = PowerlineTextBox::with_defaults();
t.configured = true;
for _ in 0..5 {
t.update(|_side| "x".to_string());
}
assert_eq!(t.bar_draw_calls, 5);
}
}