diff --git a/Cargo.toml b/Cargo.toml index 84b6fc8..8f0f734 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ raw-window-handle = "0.4.3" [workspace] members = [ "abletk-macros", + "abletk-cairo", "abletk-common", "abletk-direct2d", ] diff --git a/abletk-cairo/Cargo.toml b/abletk-cairo/Cargo.toml new file mode 100644 index 0000000..8f8c615 --- /dev/null +++ b/abletk-cairo/Cargo.toml @@ -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" diff --git a/abletk-cairo/src/brush.rs b/abletk-cairo/src/brush.rs new file mode 100644 index 0000000..1fada6d --- /dev/null +++ b/abletk-cairo/src/brush.rs @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2022 Umut İnan Erdoğan + * + * 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), +} diff --git a/abletk-cairo/src/lib.rs b/abletk-cairo/src/lib.rs new file mode 100644 index 0000000..0637f46 --- /dev/null +++ b/abletk-cairo/src/lib.rs @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2022 Umut İnan Erdoğan + * + * 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>(&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>(&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(&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>(&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>(&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 + } + } +} diff --git a/abletk-common/Cargo.toml b/abletk-common/Cargo.toml index c77985a..d82108b 100755 --- a/abletk-common/Cargo.toml +++ b/abletk-common/Cargo.toml @@ -11,3 +11,7 @@ raw-window-handle = "0.4.3" [target.'cfg(windows)'.dependencies] abletk-direct2d = { path = "../abletk-direct2d" } windows = { version = "0.36.1", features = ["Win32_Graphics_Direct2D_Common"] } + +[target.'cfg(target_os = "linux")'.dependencies] +abletk-cairo = { path = "../abletk-cairo" } + diff --git a/abletk-common/src/brush.rs b/abletk-common/src/brush.rs index c6810fb..f97ecd7 100755 --- a/abletk-common/src/brush.rs +++ b/abletk-common/src/brush.rs @@ -9,6 +9,9 @@ #[cfg(windows)] use abletk_direct2d::brush::Brush as RawBrush; +#[cfg(target_os = "linux")] +use abletk_cairo::brush::Brush as RawBrush; + use crate::color::Color; #[derive(Copy, Clone, Debug)] @@ -16,6 +19,7 @@ pub enum Brush { Solid(Color), } +#[cfg(windows)] impl From for RawBrush { fn from(brush: Brush) -> Self { match brush { @@ -23,3 +27,17 @@ impl From for RawBrush { } } } + +#[cfg(target_os = "linux")] +impl From 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(), + ), + } + } +} diff --git a/abletk-common/src/color.rs b/abletk-common/src/color.rs index 495e663..672053a 100755 --- a/abletk-common/src/color.rs +++ b/abletk-common/src/color.rs @@ -22,6 +22,12 @@ impl Color { } } +impl From for (f64, f64, f64, f64) { + fn from(color: Color) -> Self { + (color.0.into(), color.1.into(), color.2.into(), color.3.into()) + } +} + #[cfg(windows)] impl From for D2D1_COLOR_F { fn from(color: Color) -> Self { diff --git a/abletk-common/src/lib.rs b/abletk-common/src/lib.rs index e7c7780..9405415 100755 --- a/abletk-common/src/lib.rs +++ b/abletk-common/src/lib.rs @@ -18,6 +18,9 @@ use rect::Rect; #[cfg(windows)] use abletk_direct2d as backend; +#[cfg(target_os = "linux")] +use abletk_cairo as backend; + pub struct Renderer { renderer: backend::Renderer, x: u32, @@ -27,10 +30,13 @@ pub struct Renderer { impl Renderer { pub fn new(handle: RawWindowHandle) -> Self { let handle = match handle { + #[cfg(windows)] RawWindowHandle::Win32(handle) => handle, + #[cfg(target_os = "linux")] + RawWindowHandle::Xlib(handle) => handle, _ => todo!(), }; - + Self { renderer: backend::Renderer::new(handle), x: 0, @@ -50,8 +56,8 @@ impl Renderer { self.renderer.draw_rect(Rect::new( self.x as f32, self.y as f32, - (self.x + width) as f32, - (self.y + height) as f32, + width as f32, + height as f32, )) } @@ -78,8 +84,8 @@ impl Renderer { self.renderer.fill_rect(Rect::new( self.x as f32, self.y as f32, - (self.x + width) as f32, - (self.y + height) as f32, + width as f32, + height as f32, )) } @@ -101,10 +107,10 @@ impl Renderer { } 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() } } diff --git a/abletk-common/src/rect.rs b/abletk-common/src/rect.rs index 1af8c21..3cc3381 100755 --- a/abletk-common/src/rect.rs +++ b/abletk-common/src/rect.rs @@ -31,6 +31,12 @@ impl Rect { } } +impl From for (f64, f64, f64, f64) { + fn from(rect: Rect) -> Self { + (rect.x.into(), rect.y.into(), rect.width.into(), rect.height.into()) + } +} + #[cfg(windows)] impl From for RECT { fn from(rect: Rect) -> Self { diff --git a/abletk-direct2d/src/lib.rs b/abletk-direct2d/src/lib.rs index 138c059..9876a7a 100755 --- a/abletk-direct2d/src/lib.rs +++ b/abletk-direct2d/src/lib.rs @@ -65,9 +65,9 @@ impl Renderer { unsafe { self.target.as_ref().unwrap() .DrawRectangle( - &rect.into(), + &xywh_to_ltrb_rect(rect.into()), self.brush.as_ref().unwrap(), - 1.0, + 1.0, // fixme &self.stroke_style) } } @@ -97,7 +97,7 @@ impl Renderer { unsafe { self.target.as_ref().unwrap() .FillRectangle( - &rect.into(), + &xywh_to_ltrb_rect(rect.into()), self.brush.as_ref().unwrap()) } } @@ -128,15 +128,15 @@ impl Renderer { } } - pub fn set_brush(&mut self, brush: Brush) { - self.brush = Some(brush.to_brush(self.target.as_ref().unwrap())) + pub fn set_brush>(&mut self, brush: B) { + 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 { self.target.as_ref().unwrap().GetSize() }; - (size.width, size.height) + (size.width as u32, size.height as u32) } fn setup_target(&mut self) { @@ -229,3 +229,14 @@ fn create_text_format(factory: &IDWriteFactory, fonts: &IDWriteFontCollection) 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, + } +} diff --git a/src/main.rs b/src/main.rs index 6d854da..a4a660a 100755 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ fn launch() -> _ { .add(Column::new() .add(Label::new("World!")) .add(Label::new("AbleTK!"))) - .add(Label::new("this is a label!") + .add(Label::new("this is a label! jjjjyyy") .bg_color(rgb!(0xFF0000FF))) .padding_left(10)) .on_event(WindowEvent::Closed, |_, window| {