use anyhow::{anyhow, Result};
use cairo::{Context, Surface};
use pango::{EllipsizeMode, FontDescription};
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub struct Color {
red: f64,
green: f64,
blue: f64,
}
macro_rules! color {
($name:ident,($r:expr, $g:expr, $b:expr)) => {
#[allow(dead_code)]
pub fn $name() -> Color {
Color {
red: $r,
green: $g,
blue: $b,
}
}
};
}
impl Color {
color!(red, (1.0, 0.0, 0.0));
color!(green, (0.0, 1.0, 0.0));
color!(blue, (0.0, 0.0, 1.0));
color!(white, (1.0, 1.0, 1.0));
color!(black, (0.0, 0.0, 0.0));
color!(yellow, (1.0, 1.0, 0.0));
pub fn apply_to_context(&self, cr: &Context) {
cr.set_source_rgb(self.red, self.green, self.blue);
}
pub fn to_hex(&self) -> String {
let r = if self.red >= 1.0 {
255
} else {
(self.red * 255.0) as i32
};
let g = if self.green >= 1.0 {
255
} else {
(self.green * 255.0) as i32
};
let b = if self.blue >= 1.0 {
255
} else {
(self.blue * 255.0) as i32
};
format!("#{:0width$X}{:0width$X}{:0width$X}", r, g, b, width = 2)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Padding {
left: f64,
right: f64,
top: f64,
bottom: f64,
}
impl Padding {
pub fn new(left: f64, right: f64, top: f64, bottom: f64) -> Padding {
Padding {
left,
right,
top,
bottom,
}
}
}
#[derive(Clone, PartialEq)]
pub struct Font(FontDescription);
impl Font {
pub fn new(name: &str) -> Font {
Font(FontDescription::from_string(name))
}
}
impl fmt::Debug for Font {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Attributes {
pub font: Font,
pub fg_color: Color,
pub bg_color: Option<Color>,
pub padding: Padding,
}
fn create_pango_layout(cairo_context: &cairo::Context) -> Result<pango::Layout> {
let layout = pangocairo::functions::create_layout(cairo_context)
.ok_or_else(|| anyhow!("Failed to create Pango layout"))?;
Ok(layout)
}
fn show_pango_layout(cairo_context: &cairo::Context, layout: &pango::Layout) {
pangocairo::functions::show_layout(cairo_context, layout);
}
#[derive(Clone, Debug, PartialEq)]
pub struct Text {
pub attr: Attributes,
pub text: String,
pub stretch: bool,
pub markup: bool,
}
impl Text {
pub(crate) fn compute(self, surface: &Surface) -> Result<ComputedText> {
let (width, height) = {
let context = Context::new(&surface);
let layout = create_pango_layout(&context)?;
if self.markup {
layout.set_markup(&self.text);
} else {
layout.set_text(&self.text);
}
layout.set_font_description(Some(&self.attr.font.0));
let padding = &self.attr.padding;
let (text_width, text_height) = layout.get_pixel_size();
let width = f64::from(text_width) + padding.left + padding.right;
let height = f64::from(text_height) + padding.top + padding.bottom;
(width, height)
};
Ok(ComputedText {
attr: self.attr,
text: self.text,
stretch: self.stretch,
x: 0.0,
y: 0.0,
width,
height,
markup: self.markup,
})
}
}
impl PartialEq<ComputedText> for Text {
fn eq(&self, other: &ComputedText) -> bool {
self.attr == other.attr && self.text == other.text && self.stretch == other.stretch
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ComputedText {
pub attr: Attributes,
pub text: String,
pub stretch: bool,
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub markup: bool,
}
impl ComputedText {
pub fn render(&self, surface: &Surface) -> Result<()> {
let context = Context::new(&surface);
let layout = create_pango_layout(&context)?;
if self.markup {
layout.set_markup(&self.text);
} else {
layout.set_text(&self.text);
}
layout.set_font_description(Some(&self.attr.font.0));
context.translate(self.x, self.y);
let padding = &self.attr.padding;
let text_width = self.width - padding.left - padding.right;
let text_height = self.height - padding.top - padding.bottom;
layout.set_ellipsize(EllipsizeMode::End);
layout.set_width(text_width as i32 * pango::SCALE);
layout.set_height(text_height as i32 * pango::SCALE);
let bg_color = &self.attr.bg_color.clone().unwrap_or_else(Color::black);
bg_color.apply_to_context(&context);
context.rectangle(0.0, 0.0, self.width, self.height);
context.fill();
self.attr.fg_color.apply_to_context(&context);
context.translate(padding.left, padding.top);
show_pango_layout(&context, &layout);
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ThresholdValue {
pub threshold: u8,
pub color: Color,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Threshold {
pub low: ThresholdValue,
pub normal: ThresholdValue,
pub high: ThresholdValue,
}
impl Default for Threshold {
fn default() -> Self {
Threshold {
low: ThresholdValue {
threshold: 40,
color: Color::red(),
},
normal: ThresholdValue {
threshold: 60,
color: Color::yellow(),
},
high: ThresholdValue {
threshold: 100,
color: Color::green(),
},
}
}
}