use crate::{
core::{
data_types::{Region, WinType},
xconnection::{Prop, WindowState, XClientHandler, Xid},
},
draw::{Color, Draw, DrawContext, DrawError, Result},
xcb::{Api, XcbError},
};
use pangocairo::functions::{create_layout, show_layout};
use std::collections::HashMap;
#[cfg(feature = "keysyms")]
use crate::core::xconnection::{KeyPressParseAttempt, XKeyboardHandler};
fn pango_layout(ctx: &cairo::Context) -> Result<pango::Layout> {
Ok(create_layout(ctx).ok_or_else(|| XcbError::Pango("unable to create layout".into()))?)
}
#[derive(Debug)]
pub struct XcbDraw {
api: Api,
fonts: HashMap<String, pango::FontDescription>,
surfaces: HashMap<Xid, cairo::XCBSurface>,
}
crate::__xcb_impl_xclienthandler!(XcbDraw);
crate::__xcb_impl_xclientproperties!(XcbDraw);
#[cfg(feature = "keysyms")]
crate::__xcb_impl_xkeyboardhandler!(XcbDraw);
impl XcbDraw {
pub fn new() -> Result<Self> {
Ok(Self {
api: Api::new()?,
fonts: HashMap::new(),
surfaces: HashMap::new(),
})
}
pub fn xcb_connection(&self) -> &xcb::Connection {
&self.api.conn()
}
pub fn api(&self) -> &Api {
&self.api
}
pub fn api_mut(&mut self) -> &mut Api {
&mut self.api
}
}
impl Draw for XcbDraw {
type Ctx = XcbDrawContext;
fn new_window(&mut self, ty: WinType, r: Region, managed: bool) -> Result<Xid> {
let (_, _, w, h) = r.values();
let id = self.api.create_window(ty, r, managed)?;
let xcb_screen = self.api.screen(0)?;
let depth = self.api.get_depth(&xcb_screen)?;
let mut visualtype = self.api.get_visual_type(&depth)?;
let surface = unsafe {
let conn_ptr = self.api.conn().get_raw_conn() as *mut cairo_sys::xcb_connection_t;
cairo::XCBSurface::create(
&cairo::XCBConnection::from_raw_none(conn_ptr),
&cairo::XCBDrawable(id),
&cairo::XCBVisualType::from_raw_none(
&mut visualtype.base as *mut xcb::ffi::xcb_visualtype_t
as *mut cairo_sys::xcb_visualtype_t,
),
w as i32,
h as i32,
)?
};
surface.set_size(w as i32, h as i32)?;
self.surfaces.insert(id, surface);
Ok(id)
}
fn screen_sizes(&self) -> Result<Vec<Region>> {
Ok(self.api.screen_sizes()?)
}
fn register_font(&mut self, font_name: &str) {
self.fonts.insert(
font_name.into(),
pango::FontDescription::from_string(font_name),
);
}
fn context_for(&self, id: Xid) -> Result<Self::Ctx> {
let ctx = cairo::Context::new(
self.surfaces
.get(&id)
.ok_or(XcbError::UnintialisedSurface(id))?,
);
Ok(Self::Ctx {
ctx,
font: None,
fonts: self.fonts.clone(),
})
}
fn temp_context(&self, w: u32, h: u32) -> Result<Self::Ctx> {
let xcb_screen = self.api.screen(0)?;
let depth = self.api.get_depth(&xcb_screen)?;
let mut visualtype = self.api.get_visual_type(&depth)?;
let surface = unsafe {
let conn_ptr = self.api.conn().get_raw_conn() as *mut cairo_sys::xcb_connection_t;
cairo::XCBSurface::create(
&cairo::XCBConnection::from_raw_none(conn_ptr),
&cairo::XCBDrawable(self.api.root()),
&cairo::XCBVisualType::from_raw_none(
&mut visualtype.base as *mut xcb::ffi::xcb_visualtype_t
as *mut cairo_sys::xcb_visualtype_t,
),
w as i32,
h as i32,
)?
};
let surface = surface.create_similar(cairo::Content::Color, w as i32, h as i32)?;
let ctx = cairo::Context::new(&surface);
Ok(Self::Ctx {
ctx,
font: None,
fonts: self.fonts.clone(),
})
}
fn flush(&self, id: Xid) -> Result<()> {
if let Some(s) = self.surfaces.get(&id) {
s.flush()
};
self.map_client(id)?;
self.api.flush();
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct XcbDrawContext {
ctx: cairo::Context,
font: Option<pango::FontDescription>,
fonts: HashMap<String, pango::FontDescription>,
}
impl DrawContext for XcbDrawContext {
fn font(&mut self, font_name: &str, point_size: i32) -> Result<()> {
let mut font = self
.fonts
.get_mut(font_name)
.ok_or_else(|| DrawError::UnknownFont(font_name.into()))?
.clone();
font.set_size(point_size * pango::SCALE);
self.font = Some(font);
Ok(())
}
fn color(&mut self, color: &Color) {
let (r, g, b, a) = color.rgba();
self.ctx.set_source_rgba(r, g, b, a);
}
fn clear(&mut self) {
self.ctx.save();
self.ctx.set_operator(cairo::Operator::Clear);
self.ctx.paint();
self.ctx.restore();
}
fn translate(&self, dx: f64, dy: f64) {
self.ctx.translate(dx, dy)
}
fn set_x_offset(&self, x: f64) {
let (_, y_offset) = self.ctx.get_matrix().transform_point(0.0, 0.0);
self.ctx.set_matrix(cairo::Matrix::identity());
self.ctx.translate(x, y_offset);
}
fn set_y_offset(&self, y: f64) {
let (x_offset, _) = self.ctx.get_matrix().transform_point(0.0, 0.0);
self.ctx.set_matrix(cairo::Matrix::identity());
self.ctx.translate(x_offset, y);
}
fn rectangle(&self, x: f64, y: f64, w: f64, h: f64) {
self.ctx.rectangle(x, y, w, h);
self.ctx.fill();
}
fn text(&self, txt: &str, h_offset: f64, padding: (f64, f64)) -> Result<(f64, f64)> {
let layout = pango_layout(&self.ctx)?;
if let Some(ref font) = self.font {
layout.set_font_description(Some(font));
}
layout.set_text(txt);
layout.set_ellipsize(pango::EllipsizeMode::End);
let (w, h) = layout.get_pixel_size();
let (l, r) = padding;
self.ctx.translate(l, h_offset);
show_layout(&self.ctx, &layout);
self.ctx.translate(-l, -h_offset);
let width = w as f64 + l + r;
let height = h as f64;
Ok((width, height))
}
fn text_extent(&self, s: &str) -> Result<(f64, f64)> {
let layout = pango_layout(&self.ctx)?;
if let Some(ref font) = self.font {
layout.set_font_description(Some(font));
}
layout.set_text(&s);
let (w, h) = layout.get_pixel_size();
Ok((w as f64, h as f64))
}
fn flush(&self) {
self.ctx.get_target().flush();
}
}