new cairo backend! for now only tested on X11 on Linux

also includes a couple small changes everywhere
This commit is contained in:
TheOddGarlic 2022-05-21 15:30:06 +03:00
parent 9b3969c6ad
commit c41c2b9c69
11 changed files with 300 additions and 15 deletions

View file

@ -16,6 +16,7 @@ raw-window-handle = "0.4.3"
[workspace] [workspace]
members = [ members = [
"abletk-macros", "abletk-macros",
"abletk-cairo",
"abletk-common", "abletk-common",
"abletk-direct2d", "abletk-direct2d",
] ]

11
abletk-cairo/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "abletk-cairo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
raw-window-handle = "0.4.3"
cairo-sys-rs = { version = "0.15.1", features = ["xlib"] }
x11 = "2.19.1"

11
abletk-cairo/src/brush.rs Normal file
View file

@ -0,0 +1,11 @@
/*
* Copyright (C) 2022 Umut İnan Erdoğan <umutinanerdogan@pm.me>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
pub enum Brush {
Solid(f64, f64, f64, f64),
}

211
abletk-cairo/src/lib.rs Normal file
View file

@ -0,0 +1,211 @@
/*
* Copyright (C) 2022 Umut İnan Erdoğan <umutinanerdogan@pm.me>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
pub mod brush;
use std::{mem, ffi::CString};
use brush::Brush;
use cairo_sys::*;
use raw_window_handle::XlibHandle;
use x11::xlib::{XDefaultScreen, XDefaultVisual, XGetGeometry};
pub struct Renderer {
handle: XlibHandle,
surface: *mut cairo_surface_t,
ctx: *mut cairo_t,
}
impl Renderer {
pub fn new(handle: XlibHandle) -> Self {
println!("{:#?}", handle);
let (width, height) = unsafe {
let mut root = mem::zeroed();
let mut x = mem::zeroed();
let mut y = mem::zeroed();
let mut width = mem::zeroed();
let mut height = mem::zeroed();
let mut border_width = mem::zeroed();
let mut depth = mem::zeroed();
assert_ne!(XGetGeometry(
handle.display as _,
handle.window.try_into().unwrap(),
&mut root,
&mut x,
&mut y,
&mut width,
&mut height,
&mut border_width,
&mut depth,
), 0);
(width.try_into().unwrap(), height.try_into().unwrap())
};
let surface = unsafe {
cairo_xlib_surface_create(handle.display as _,
handle.window,
XDefaultVisual(handle.display as _, XDefaultScreen(handle.display as _)),
width,
height
)
};
let ctx = unsafe {
cairo_create(surface)
};
Self {
handle,
surface,
ctx,
}
}
pub fn begin_draw(&mut self) {
unsafe {
// fixme
let font = CString::new("Georgia").unwrap();
cairo_select_font_face(
self.ctx,
font.as_ptr(),
FONT_SLANT_NORMAL,
FONT_WEIGHT_NORMAL,
);
cairo_set_font_size(self.ctx, 20.0);
cairo_set_line_width(self.ctx, 1.0)
}
}
pub fn clear<C: Into<(f64, f64, f64, f64)>>(&self, color: C) {
let (r, g, b, a) = color.into();
unsafe {
cairo_save(self.ctx);
cairo_set_source_rgba(self.ctx, r, g, b, a);
cairo_set_operator(self.ctx, OPERATOR_SOURCE);
cairo_paint(self.ctx);
cairo_restore(self.ctx)
}
}
pub fn draw_rect<R: Into<(f64, f64, f64, f64)>>(&self, rect: R) {
let (x, y, width, height) = rect.into();
unsafe {
cairo_rectangle(self.ctx, x, y, width, height);
cairo_stroke(self.ctx)
}
}
pub fn draw_text<R>(&self, text: &str, layout_rect: R)
where
R: Into<(f64, f64, f64, f64)>
{
let (x, y, _, _) = layout_rect.into();
let font_extents = self.font_extents();
let text_extents = self.text_extents(text);
let text = CString::new(text).unwrap();
unsafe {
cairo_move_to(
self.ctx,
x - text_extents.x_bearing,
y
+ font_extents.height
+ font_extents.descent
+ text_extents.y_bearing,
);
cairo_show_text(self.ctx, text.as_ptr())
}
}
pub fn end_draw(&self) {
unsafe {
cairo_surface_flush(self.surface)
}
}
pub fn fill_rect<R: Into<(f64, f64, f64, f64)>>(&self, rect: R) {
let (x, y, width, height) = rect.into();
unsafe {
cairo_rectangle(self.ctx, x, y, width, height);
cairo_fill(self.ctx)
}
}
// fixme: this and abletk-direct2d's get_text_size have small differences
pub fn get_text_size(&self, text: &str) -> (u32, u32) {
let text_extents = self.text_extents(text);
let font_extents = self.font_extents();
let result = (
text_extents.x_advance as u32,
(font_extents.ascent + font_extents.descent) as u32,
);
result
}
pub fn resized(&mut self, width: u32, height: u32) {
unsafe {
cairo_xlib_surface_set_size(
self.surface,
width.try_into().unwrap(),
height.try_into().unwrap(),
)
}
}
pub fn set_brush<B: Into<Brush>>(&mut self, brush: B) {
let brush = brush.into();
match brush {
Brush::Solid(r, g, b, a) => unsafe {
cairo_set_source_rgba(self.ctx, r, g, b, a)
},
}
}
pub fn size(&self) -> (u32, u32) {
unsafe {
let mut root = mem::zeroed();
let mut x = mem::zeroed();
let mut y = mem::zeroed();
let mut width = mem::zeroed();
let mut height = mem::zeroed();
let mut border_width = mem::zeroed();
let mut depth = mem::zeroed();
assert_ne!(XGetGeometry(
self.handle.display as _,
self.handle.window.try_into().unwrap(),
&mut root,
&mut x,
&mut y,
&mut width,
&mut height,
&mut border_width,
&mut depth,
), 0);
(width, height)
}
}
fn text_extents(&self, text: &str) -> TextExtents {
unsafe {
let mut extents = mem::zeroed();
let text = CString::new(text).unwrap();
cairo_text_extents(
self.ctx,
text.as_ptr() as _,
&mut extents,
);
extents
}
}
fn font_extents(&self) -> FontExtents {
unsafe {
let mut extents = mem::zeroed();
cairo_font_extents(self.ctx, &mut extents);
extents
}
}
}

View file

@ -11,3 +11,7 @@ raw-window-handle = "0.4.3"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
abletk-direct2d = { path = "../abletk-direct2d" } abletk-direct2d = { path = "../abletk-direct2d" }
windows = { version = "0.36.1", features = ["Win32_Graphics_Direct2D_Common"] } windows = { version = "0.36.1", features = ["Win32_Graphics_Direct2D_Common"] }
[target.'cfg(target_os = "linux")'.dependencies]
abletk-cairo = { path = "../abletk-cairo" }

View file

@ -9,6 +9,9 @@
#[cfg(windows)] #[cfg(windows)]
use abletk_direct2d::brush::Brush as RawBrush; use abletk_direct2d::brush::Brush as RawBrush;
#[cfg(target_os = "linux")]
use abletk_cairo::brush::Brush as RawBrush;
use crate::color::Color; use crate::color::Color;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -16,6 +19,7 @@ pub enum Brush {
Solid(Color), Solid(Color),
} }
#[cfg(windows)]
impl From<Brush> for RawBrush { impl From<Brush> for RawBrush {
fn from(brush: Brush) -> Self { fn from(brush: Brush) -> Self {
match brush { match brush {
@ -23,3 +27,17 @@ impl From<Brush> for RawBrush {
} }
} }
} }
#[cfg(target_os = "linux")]
impl From<Brush> for RawBrush {
fn from(brush: Brush) -> Self {
match brush {
Brush::Solid(color) => RawBrush::Solid(
color.0.into(),
color.1.into(),
color.2.into(),
color.3.into(),
),
}
}
}

View file

@ -22,6 +22,12 @@ impl Color {
} }
} }
impl From<Color> for (f64, f64, f64, f64) {
fn from(color: Color) -> Self {
(color.0.into(), color.1.into(), color.2.into(), color.3.into())
}
}
#[cfg(windows)] #[cfg(windows)]
impl From<Color> for D2D1_COLOR_F { impl From<Color> for D2D1_COLOR_F {
fn from(color: Color) -> Self { fn from(color: Color) -> Self {

View file

@ -18,6 +18,9 @@ use rect::Rect;
#[cfg(windows)] #[cfg(windows)]
use abletk_direct2d as backend; use abletk_direct2d as backend;
#[cfg(target_os = "linux")]
use abletk_cairo as backend;
pub struct Renderer { pub struct Renderer {
renderer: backend::Renderer, renderer: backend::Renderer,
x: u32, x: u32,
@ -27,10 +30,13 @@ pub struct Renderer {
impl Renderer { impl Renderer {
pub fn new(handle: RawWindowHandle) -> Self { pub fn new(handle: RawWindowHandle) -> Self {
let handle = match handle { let handle = match handle {
#[cfg(windows)]
RawWindowHandle::Win32(handle) => handle, RawWindowHandle::Win32(handle) => handle,
#[cfg(target_os = "linux")]
RawWindowHandle::Xlib(handle) => handle,
_ => todo!(), _ => todo!(),
}; };
Self { Self {
renderer: backend::Renderer::new(handle), renderer: backend::Renderer::new(handle),
x: 0, x: 0,
@ -50,8 +56,8 @@ impl Renderer {
self.renderer.draw_rect(Rect::new( self.renderer.draw_rect(Rect::new(
self.x as f32, self.x as f32,
self.y as f32, self.y as f32,
(self.x + width) as f32, width as f32,
(self.y + height) as f32, height as f32,
)) ))
} }
@ -78,8 +84,8 @@ impl Renderer {
self.renderer.fill_rect(Rect::new( self.renderer.fill_rect(Rect::new(
self.x as f32, self.x as f32,
self.y as f32, self.y as f32,
(self.x + width) as f32, width as f32,
(self.y + height) as f32, height as f32,
)) ))
} }
@ -101,10 +107,10 @@ impl Renderer {
} }
pub fn set_brush(&mut self, brush: Brush) { pub fn set_brush(&mut self, brush: Brush) {
self.renderer.set_brush(brush.into()) self.renderer.set_brush(brush)
} }
pub fn size(&self) -> (f32, f32) { pub fn size(&self) -> (u32, u32) {
self.renderer.size() self.renderer.size()
} }
} }

View file

@ -31,6 +31,12 @@ impl Rect {
} }
} }
impl From<Rect> for (f64, f64, f64, f64) {
fn from(rect: Rect) -> Self {
(rect.x.into(), rect.y.into(), rect.width.into(), rect.height.into())
}
}
#[cfg(windows)] #[cfg(windows)]
impl From<Rect> for RECT { impl From<Rect> for RECT {
fn from(rect: Rect) -> Self { fn from(rect: Rect) -> Self {

View file

@ -65,9 +65,9 @@ impl Renderer {
unsafe { unsafe {
self.target.as_ref().unwrap() self.target.as_ref().unwrap()
.DrawRectangle( .DrawRectangle(
&rect.into(), &xywh_to_ltrb_rect(rect.into()),
self.brush.as_ref().unwrap(), self.brush.as_ref().unwrap(),
1.0, 1.0, // fixme
&self.stroke_style) &self.stroke_style)
} }
} }
@ -97,7 +97,7 @@ impl Renderer {
unsafe { unsafe {
self.target.as_ref().unwrap() self.target.as_ref().unwrap()
.FillRectangle( .FillRectangle(
&rect.into(), &xywh_to_ltrb_rect(rect.into()),
self.brush.as_ref().unwrap()) self.brush.as_ref().unwrap())
} }
} }
@ -128,15 +128,15 @@ impl Renderer {
} }
} }
pub fn set_brush(&mut self, brush: Brush) { pub fn set_brush<B: Into<Brush>>(&mut self, brush: B) {
self.brush = Some(brush.to_brush(self.target.as_ref().unwrap())) self.brush = Some(brush.into().to_brush(self.target.as_ref().unwrap()))
} }
pub fn size(&self) -> (f32, f32) { pub fn size(&self) -> (u32, u32) {
let size = unsafe { let size = unsafe {
self.target.as_ref().unwrap().GetSize() self.target.as_ref().unwrap().GetSize()
}; };
(size.width, size.height) (size.width as u32, size.height as u32)
} }
fn setup_target(&mut self) { fn setup_target(&mut self) {
@ -229,3 +229,14 @@ fn create_text_format(factory: &IDWriteFactory, fonts: &IDWriteFontCollection)
format format
} }
} }
/// D2D sometimes expects a rect to have LTRB (left, top, right, bottom) values
/// but abletk-common uses XYWH (x, y, width, height) values.
fn xywh_to_ltrb_rect(xywh: D2D_RECT_F) -> D2D_RECT_F {
D2D_RECT_F {
left: xywh.left,
top: xywh.top,
right: xywh.left + xywh.right,
bottom: xywh.top + xywh.bottom,
}
}

View file

@ -20,7 +20,7 @@ fn launch() -> _ {
.add(Column::new() .add(Column::new()
.add(Label::new("World!")) .add(Label::new("World!"))
.add(Label::new("AbleTK!"))) .add(Label::new("AbleTK!")))
.add(Label::new("this is a label!") .add(Label::new("this is a label! jjjjyyy")
.bg_color(rgb!(0xFF0000FF))) .bg_color(rgb!(0xFF0000FF)))
.padding_left(10)) .padding_left(10))
.on_event(WindowEvent::Closed, |_, window| { .on_event(WindowEvent::Closed, |_, window| {