extern crate x11;
use std::ffi::*;
use std::mem::MaybeUninit;
use x11::xft;
use x11::xlib;
use x11::xrender;
use crate::common::*;
use crate::common::error::*;
use crate::interfaces::draw::*;
use crate::platforms::x11::misc::window::*;
#[derive(Clone, Copy)]
struct CanvasStyle {
line_width: u32,
line_style: i32,
cap_style: i32,
join_style: i32,
fill_style: i32,
xft_color: xft::XftColor,
xfont: Option<*mut xft::XftFont>,
}
pub struct X11Canvas {
display: *mut xlib::Display,
screen: i32,
window: xlib::Window,
pixbuffer: xlib::Drawable,
gc: xlib::GC,
style: CanvasStyle,
}
impl X11Canvas {
pub fn new_for_window(display: *mut xlib::Display, window: xlib::Window) -> Result<X11Canvas> {
let screen = unsafe { xlib::XDefaultScreen(display) };
let style = CanvasStyle::default();
let pixbuffer = Self::create_pixmap(display, screen, window)?;
let gc = Self::create_default_gc(display, window, false)
.inspect_err(|_| unsafe { xlib::XFreePixmap(display, pixbuffer); })?;
Ok( X11Canvas {
display, screen, window, pixbuffer, gc, style,
} )
}
fn create_default_gc(display: *mut xlib::Display, window: xlib::Window, reverse_video: bool) -> Result<xlib::GC> {
let style = CanvasStyle::default();
unsafe {
let screen = xlib::XDefaultScreen(display);
let value_mask = 0;
let mut values: xlib::XGCValues = MaybeUninit::zeroed().assume_init();
let gc = xlib::XCreateGC(display, window, value_mask, &mut values);
if gc.is_null() {
return Err(MarsError::failed_request(stringify!(xlib::XCreateGC)));
}
if reverse_video {
xlib::XSetForeground(display, gc, xlib::XWhitePixel(display, screen));
xlib::XSetBackground(display, gc, xlib::XBlackPixel(display, screen));
} else {
xlib::XSetForeground(display, gc, xlib::XBlackPixel(display, screen));
xlib::XSetBackground(display, gc, xlib::XWhitePixel(display, screen));
}
xlib::XSetLineAttributes(display, gc, style.line_width, style.line_style, style.cap_style, style.join_style);
xlib::XSetFillStyle(display, gc, style.fill_style);
Ok(gc)
}
}
fn create_pixmap(display: *mut xlib::Display, screen: i32, window: xlib::Window) -> Result<xlib::Drawable> {
unsafe {
let window_dims = window.x11_dimensions(display)?;
let depth = xlib::XDefaultDepth(display, screen);
let root = xlib::XDefaultRootWindow(display);
Ok(xlib::XCreatePixmap(display, root, window_dims.w(), window_dims.h(), depth as u32))
}
}
pub fn expose_loop(&mut self, display: *mut xlib::Display, draw: fn(&mut Self)) {
loop {
let mut event: MaybeUninit<xlib::XEvent> = MaybeUninit::uninit();
unsafe {
xlib::XNextEvent(display, event.as_mut_ptr());
let event = event.assume_init();
if event.get_type() == xlib::Expose
&& event.expose.window == self.window() {
draw(self);
self.flush();
}
}
}
}
fn gc_apply_foreground(&self) {
unsafe {
xlib::XSetForeground(self.display, self.gc, self.style.xft_color.pixel);
}
}
fn gc_apply_line_attrib(&self) {
unsafe {
xlib::XSetLineAttributes(self.display, self.gc, self.style.line_width,
self.style.line_style, self.style.cap_style, self.style.join_style);
}
}
fn alloc_color(&self, color: u64) -> Result<xft::XftColor> {
let xr_color = {
xrender::XRenderColor {
red: ((color >> 16) & 0xff) as u16 * 0xff,
green: ((color >> 8) & 0xff) as u16 * 0xff,
blue: (color & 0xff) as u16 * 0xff,
alpha: c_ushort::MAX,
}
};
let mut xft_color: MaybeUninit<xft::XftColor> = MaybeUninit::uninit();
unsafe {
let visual = xlib::XDefaultVisual(self.display, self.screen);
let colormap = xlib::XDefaultColormap(self.display, self.screen);
if xft::XftColorAllocValue(self.display, visual, colormap, &xr_color, xft_color.as_mut_ptr()) == 0 {
Err(MarsError::failed_request(stringify!(xft::XftColorAllocValue)))
} else {
Ok(xft_color.assume_init())
}
}
}
pub fn window(&self) -> xlib::Window {
self.window
}
}
impl Canvas for X11Canvas {
fn dimensions(&self) -> Dimensions {
self.pixbuffer.x11_dimensions(self.display).unwrap()
}
fn draw_line(&mut self, pt1: (i32, i32), pt2: (i32, i32)) {
unsafe {
xlib::XDrawLine(self.display, self.pixbuffer, self.gc,
pt1.0, pt1.1, pt2.0, pt2.1);
}
}
fn draw_line_with(&mut self, pt1: (i32, i32), pt2: (i32, i32), color: u64, line_width: u32) {
if let Ok(xft_color) = self.alloc_color(color) {
let saved_color = self.style.xft_color;
let saved_line_width = self.style.line_width;
self.style.xft_color = xft_color;
self.style.line_width = line_width;
self.gc_apply_foreground();
self.gc_apply_line_attrib();
self.draw_line(pt1, pt2);
self.style.line_width = saved_line_width;
self.style.xft_color = saved_color;
self.gc_apply_foreground();
self.gc_apply_line_attrib();
} else {
self.draw_line(pt1, pt2);
}
}
fn draw_rectangle(&mut self, x: i32, y: i32, width: u32, height: u32) {
unsafe {
xlib::XDrawRectangle(self.display, self.pixbuffer, self.gc,
x, y, width, height);
}
}
fn draw_rectangle_with(&mut self, x: i32, y: i32, width: u32, height: u32, color: u64, line_width: u32) {
if let Ok(xft_color) = self.alloc_color(color) {
let saved_color = self.style.xft_color;
let saved_line_width = self.style.line_width;
self.style.xft_color = xft_color;
self.style.line_width = line_width;
self.gc_apply_foreground();
self.gc_apply_line_attrib();
self.draw_rectangle(x, y, width, height);
self.style.line_width = saved_line_width;
self.style.xft_color = saved_color;
self.gc_apply_foreground();
self.gc_apply_line_attrib();
} else {
self.draw_rectangle(x, y, width, height);
}
}
fn text_size(&self, text: &str) -> Result<(u32, u32)> {
if let Some(xfont) = self.style.xfont {
let strlen = match text.len().try_into() {
Ok(i) => i,
Err(_) => return Err(MarsError::failed_conversion(text.len(), stringify!(usize), stringify!(i32))),
};
unsafe {
let mut extents: MaybeUninit<xrender::XGlyphInfo> = MaybeUninit::uninit();
xft::XftTextExtentsUtf8(self.display, xfont, text.as_ptr(), strlen, extents.as_mut_ptr());
let extents = extents.assume_init();
let height = (*xfont).ascent + (*xfont).descent;
Ok((extents.width.into(), height as u32))
}
} else {
Err(MarsError::invalid_input("unable to get text size - no font specified"))
}
}
fn draw_text(&mut self, x: i32, y: i32, height: u32, text: &str) -> Result<(u32, u32)> {
let font = match self.style.xfont {
Some(font) => font,
None => return Err(MarsError::invalid_input("no font specified")),
};
let strlen = match text.len().try_into() {
Ok(i) => i,
Err(_) => return Err(MarsError::failed_conversion(text.len(), stringify!(usize), stringify!(i32))),
};
let ascent = unsafe { (*font).ascent };
let descent = unsafe { (*font).descent };
let th = ascent + descent;
let y = y + th + (height as i32 - th) / 2 - descent;
unsafe {
let visual = xlib::XDefaultVisual(self.display, self.screen);
let colormap = xlib::XDefaultColormap(self.display, self.screen);
let xft_draw = xft::XftDrawCreate(self.display, self.pixbuffer, visual, colormap);
xft::XftDrawStringUtf8(xft_draw, (&self.style.xft_color) as *const xft::XftColor,
font, x, y, text.as_ptr(), strlen);
xft::XftDrawDestroy(xft_draw);
}
self.text_size(text)
}
fn fill_rectangle(&mut self, x: i32, y: i32, width: u32, height: u32) {
unsafe {
xlib::XFillRectangle(self.display, self.pixbuffer, self.gc,
x, y, width, height);
}
}
fn fill_rectangle_with(&mut self, x: i32, y: i32, width: u32, height: u32, color: u64) {
if let Ok(xft_color) = self.alloc_color(color) {
let saved_color = self.style.xft_color;
self.style.xft_color = xft_color;
self.gc_apply_foreground();
self.fill_rectangle(x, y, width, height);
self.style.xft_color = saved_color;
self.gc_apply_foreground();
} else {
self.fill_rectangle(x, y, width, height)
}
}
fn flush(&self) {
unsafe {
if let Ok(d) = self.window.x11_dimensions(self.display) {
xlib::XCopyArea(self.display, self.pixbuffer, self.window, self.gc, 0, 0, d.w(), d.h(), 0, 0);
}
xlib::XFlush(self.display);
}
}
fn match_resize(&mut self) {
if let Ok(new_pixbuffer) = Self::create_pixmap(self.display, self.screen, self.window) {
unsafe {
xlib::XFreePixmap(self.display, self.pixbuffer);
}
self.pixbuffer = new_pixbuffer;
}
}
fn set_background(&mut self, color: u64) -> Result<()> {
let xft_color = self.alloc_color(color)?;
unsafe {
let mut swa: xlib::XSetWindowAttributes = MaybeUninit::zeroed().assume_init();
swa.background_pixel = color;
xlib::XChangeWindowAttributes(self.display, self.window, xlib::CWBackPixel, &mut swa);
xlib::XSetBackground(self.display, self.gc, xft_color.pixel);
}
Ok(())
}
fn set_font(&mut self, font_name: &str) -> Result<()> {
unsafe {
if let Some(prev_font) = self.style.xfont.take() {
xft::XftFontClose(self.display, prev_font);
}
let c_font_name = CString::new(font_name)
.map_err(|_| MarsError::failed_conversion(font_name, stringify!(&str), stringify!(CString)))?;
let xfont = xft::XftFontOpenName(self.display, self.screen, c_font_name.as_ptr());
if xfont.is_null() {
return Err(MarsError::failed_request(stringify!(xft::XftFontOpenName)));
} else {
self.style.xfont = Some(xfont);
}
}
Ok(())
}
fn set_foreground(&mut self, color: u64) -> Result<()> {
self.style.xft_color = self.alloc_color(color)?;
self.gc_apply_foreground();
Ok(())
}
fn set_line_width(&mut self, line_width: u32) {
self.style.line_width = line_width;
self.gc_apply_line_attrib();
}
}
impl Drop for X11Canvas {
fn drop(&mut self) {
unsafe {
xlib::XFreeGC(self.display, self.gc);
xlib::XFreePixmap(self.display, self.pixbuffer);
}
}
}
impl Default for CanvasStyle {
fn default() -> Self {
let default_xft_color = xft::XftColor {
pixel: 0,
color: xrender::XRenderColor {
red: 0,
green: 0,
blue: 0,
alpha: c_ushort::MAX,
},
};
CanvasStyle {
line_width: 10,
line_style: xlib::LineSolid,
cap_style: xlib::CapRound,
join_style: xlib::JoinMiter,
fill_style: xlib::FillSolid,
xft_color: default_xft_color,
xfont: None,
}
}
}