pub mod bar;
pub mod widget;
#[doc(inline)]
pub use bar::*;
#[doc(inline)]
pub use widget::{HookableWidget, KeyboardControlled, Widget};
use crate::core::{
data_types::{Region, WinType},
xconnection::{XClientHandler, XClientProperties, XKeyboardHandler, Xid},
};
#[cfg(feature = "xcb")]
use crate::xcb::XcbError;
use std::{convert::TryFrom, convert::TryInto};
#[derive(thiserror::Error, Debug)]
pub enum DrawError {
#[error("Invalid Hex color code: {0}")]
InvalidHexColor(String),
#[error("Invalid Hex color code")]
ParseInt(#[from] std::num::ParseIntError),
#[error("Unhandled error: {0}")]
Raw(String),
#[error("'{0}' is has not been registered as a font")]
UnknownFont(String),
#[cfg(feature = "xcb")]
#[error(transparent)]
Xcb(#[from] XcbError),
#[error(transparent)]
X(#[from] crate::core::xconnection::XError),
#[cfg(feature = "xcb")]
#[error("Error calling Cairo API: {0}")]
Cairo(#[from] cairo::Error),
}
pub type Result<T> = std::result::Result<T, DrawError>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct TextStyle {
pub font: String,
pub point_size: i32,
pub fg: Color,
pub bg: Option<Color>,
pub padding: (f64, f64),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Color {
r: f64,
g: f64,
b: f64,
a: f64,
}
macro_rules! _f2u { { $f:expr, $s:expr } => { (($f * 255.0) as u32) << $s } }
impl Color {
pub fn new_from_hex(hex: u32) -> Self {
let floats: Vec<f64> = hex
.to_be_bytes()
.iter()
.map(|n| *n as f64 / 255.0)
.collect();
let (r, g, b, a) = (floats[0], floats[1], floats[2], floats[3]);
Self { r, g, b, a }
}
pub fn rgb(&self) -> (f64, f64, f64) {
(self.r, self.g, self.b)
}
pub fn rgba(&self) -> (f64, f64, f64, f64) {
(self.r, self.g, self.b, self.a)
}
pub fn as_rgb_hex_string(&self) -> String {
format!("#{:x}", self.rgb_u32())
}
pub fn rgb_u32(&self) -> u32 {
_f2u!(self.r, 16) + _f2u!(self.g, 8) + _f2u!(self.b, 0)
}
pub fn rgba_u32(&self) -> u32 {
_f2u!(self.r, 24) + _f2u!(self.g, 16) + _f2u!(self.b, 8) + _f2u!(self.a, 0)
}
}
impl From<u32> for Color {
fn from(hex: u32) -> Self {
Self::new_from_hex(hex)
}
}
impl From<(f64, f64, f64)> for Color {
fn from(rgb: (f64, f64, f64)) -> Self {
let (r, g, b) = rgb;
Self { r, g, b, a: 1.0 }
}
}
impl From<(f64, f64, f64, f64)> for Color {
fn from(rgba: (f64, f64, f64, f64)) -> Self {
let (r, g, b, a) = rgba;
Self { r, g, b, a }
}
}
impl TryFrom<String> for Color {
type Error = DrawError;
fn try_from(s: String) -> Result<Self> {
(&s[..]).try_into()
}
}
impl TryFrom<&str> for Color {
type Error = DrawError;
fn try_from(s: &str) -> Result<Self> {
let hex = u32::from_str_radix(s.strip_prefix('#').unwrap_or(&s), 16)?;
if s.len() == 7 {
Ok(Self::new_from_hex((hex << 8) + 0xFF))
} else if s.len() == 9 {
Ok(Self::new_from_hex(hex))
} else {
Err(DrawError::InvalidHexColor(s.into()))
}
}
}
pub trait Draw: XClientHandler + XClientProperties {
type Ctx: DrawContext;
fn new_window(&mut self, ty: WinType, r: Region, managed: bool) -> Result<Xid>;
fn screen_sizes(&self) -> Result<Vec<Region>>;
fn register_font(&mut self, font_name: &str);
fn context_for(&self, id: Xid) -> Result<Self::Ctx>;
fn temp_context(&self, w: u32, h: u32) -> Result<Self::Ctx>;
fn flush(&self, id: Xid) -> Result<()>;
}
pub trait KeyPressDraw: Draw + XKeyboardHandler {}
impl<T> KeyPressDraw for T where T: Draw + XKeyboardHandler {}
pub trait DrawContext {
fn font(&mut self, font_name: &str, point_size: i32) -> Result<()>;
fn color(&mut self, color: &Color);
fn clear(&mut self);
fn translate(&self, dx: f64, dy: f64);
fn set_x_offset(&self, x: f64);
fn set_y_offset(&self, y: f64);
fn rectangle(&self, x: f64, y: f64, w: f64, h: f64);
fn text(&self, s: &str, h_offset: f64, padding: (f64, f64)) -> Result<(f64, f64)>;
fn text_extent(&self, s: &str) -> Result<(f64, f64)>;
fn flush(&self);
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
test_cases! {
color_from_hex_rgba;
args: (hex: u32, floats: (f64, f64, f64, f64));
case: black => (0x00000000, (0.0, 0.0, 0.0, 0.0));
case: black_alpha => (0x000000FF, (0.0, 0.0, 0.0, 1.0));
case: white => (0xFFFFFFFF, (1.0, 1.0, 1.0, 1.0));
case: red => (0xFF0000FF, (1.0, 0.0, 0.0, 1.0));
case: green => (0x00FF00FF, (0.0, 1.0, 0.0, 1.0));
case: blue => (0x0000FFFF, (0.0, 0.0, 1.0, 1.0));
body: {
assert_eq!(Color::new_from_hex(hex), Color::from(floats));
}
}
test_cases! {
color_from_str_or_string;
args: (s: &str, floats: (f64, f64, f64, f64));
case: alpha1 => ("#FFFF00FF", (1.0, 1.0, 0.0, 1.0));
case: alpha0 => ("#FFFF0000", (1.0, 1.0, 0.0, 0.0));
body: {
assert_eq!(Color::try_from(s).unwrap(), Color::from(floats));
assert_eq!(Color::try_from(s.to_string()).unwrap(), Color::from(floats));
}
}
test_cases! {
color_from_str_or_string_no_alpha;
args: (s: &str, floats: (f64, f64, f64, f64));
case: black => ("#000000", (0.0, 0.0, 0.0, 1.0));
case: white => ("#FFFFFF", (1.0, 1.0, 1.0, 1.0));
case: red => ("#FF0000", (1.0, 0.0, 0.0, 1.0));
case: green => ("#00FF00", (0.0, 1.0, 0.0, 1.0));
case: blue => ("#0000FF", (0.0, 0.0, 1.0, 1.0));
body: {
assert_eq!(Color::try_from(s).unwrap(), Color::from(floats));
assert_eq!(Color::try_from(s.to_string()).unwrap(), Color::from(floats));
}
}
test_cases! {
color_rgb_u32;
args: (s: &str, expected: u32);
case: black => ("#000000", 0x000000);
case: white => ("#FFFFFF", 0xFFFFFF);
case: red => ("#FF0000", 0xFF0000);
case: green => ("#00FF00", 0x00FF00);
case: blue => ("#0000FF", 0x0000FF);
body: {
assert_eq!(Color::try_from(s).unwrap().rgb_u32(), expected);
}
}
test_cases! {
color_rgba_u32;
args: (s: &str, expected: u32);
case: black => ("#00000000", 0x00000000);
case: white => ("#FFFFFF00", 0xFFFFFF00);
case: red => ("#FF000000", 0xFF000000);
case: green => ("#00FF0000", 0x00FF0000);
case: blue => ("#0000FF00", 0x0000FF00);
body: {
assert_eq!(Color::try_from(s).unwrap().rgba_u32(), expected);
}
}
}