abletk/abletk-direct2d/src/lib.rs

243 lines
7.1 KiB
Rust
Executable File

/*
* 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;
use brush::{Brush, DEFAULT_BRUSH_COLOR};
use raw_window_handle::Win32Handle;
use windows::{
core::Interface, Win32::Foundation::*,
Win32::Graphics::Direct2D::Common::*, Win32::Graphics::Direct2D::*,
Win32::Graphics::DirectWrite::*, Win32::UI::WindowsAndMessaging::*,
};
pub struct Renderer {
handle: Win32Handle,
factory: ID2D1Factory1,
dw_factory: IDWriteFactory,
target: Option<ID2D1HwndRenderTarget>,
brush: Option<ID2D1Brush>,
stroke_style: ID2D1StrokeStyle,
system_fonts: IDWriteFontCollection,
text_format: IDWriteTextFormat,
}
impl Renderer {
pub fn new(handle: Win32Handle) -> Self {
let factory = create_factory();
let dw_factory = create_dw_factory();
let system_fonts = create_system_font_collection(&dw_factory);
let text_format = create_text_format(&dw_factory, &system_fonts);
let stroke_style = unsafe {
factory.CreateStrokeStyle(&Default::default(), &[])
.unwrap()
};
let mut renderer = Self {
handle,
factory,
dw_factory,
target: None,
brush: None,
stroke_style,
system_fonts,
text_format,
};
renderer.setup_target();
renderer
}
pub fn begin_draw(&mut self) {
unsafe { self.target.as_ref().unwrap().BeginDraw() }
}
pub fn clear<C: Into<D2D1_COLOR_F>>(&self, color: C) {
unsafe { self.target.as_ref().unwrap().Clear(&color.into()) }
}
pub fn draw_rect<R: Into<D2D_RECT_F>>(&self, rect: R) {
unsafe {
self.target.as_ref().unwrap()
.DrawRectangle(
&xywh_to_ltrb_rect(rect.into()),
self.brush.as_ref().unwrap(),
1.0, // fixme
&self.stroke_style)
}
}
pub fn draw_text<R: Into<D2D_RECT_F>>(&self, text: &str, layoutrect: R) {
unsafe {
self.target.as_ref().unwrap().DrawText(
// fixme: make this not hacky
&text.as_bytes().iter().map(|b| *b as u16).collect::<Vec<_>>(),
&self.text_format,
&layoutrect.into(),
self.brush.as_ref().unwrap(),
D2D1_DRAW_TEXT_OPTIONS_NO_SNAP,
DWRITE_MEASURING_MODE_NATURAL,
);
}
}
pub fn end_draw(&self) {
unsafe {
self.target.as_ref().unwrap()
.EndDraw(std::ptr::null_mut(), std::ptr::null_mut()).unwrap()
}
}
pub fn fill_rect<R: Into<D2D_RECT_F>>(&self, rect: R) {
unsafe {
self.target.as_ref().unwrap()
.FillRectangle(
&xywh_to_ltrb_rect(rect.into()),
self.brush.as_ref().unwrap())
}
}
pub fn get_text_size(&self, text: &str) -> (u32, u32) {
let metrics = unsafe {
let layout = self.dw_factory.CreateTextLayout(
// fixme: make this not hacky
&text.as_bytes().iter().map(|b| *b as u16).collect::<Vec<_>>(),
&self.text_format,
f32::INFINITY,
f32::INFINITY,
).unwrap();
layout.GetMetrics().unwrap()
};
(metrics.widthIncludingTrailingWhitespace as u32, metrics.height as u32)
}
pub fn resized(&mut self, width: u32, height: u32) {
unsafe {
if let Some(target) = self.target.as_ref() {
target.Resize(&D2D_SIZE_U {
width,
height
}).unwrap()
}
}
}
pub fn set_brush<B: Into<Brush>>(&mut self, brush: B) {
self.brush = Some(brush.into().to_brush(self.target.as_ref().unwrap()))
}
pub fn size(&self) -> (u32, u32) {
let size = unsafe {
self.target.as_ref().unwrap().GetSize()
};
(size.width as u32, size.height as u32)
}
fn setup_target(&mut self) {
if self.target.is_none() {
let mut rect = RECT::default();
unsafe {
GetClientRect(
mem::transmute::<_, HWND>(self.handle.hwnd),
&mut rect
);
}
let d2d_rect = D2D_SIZE_U {
width: (rect.right - rect.left) as u32,
height: (rect.bottom - rect.top) as u32,
};
let render_properties = D2D1_RENDER_TARGET_PROPERTIES::default();
let hwnd_render_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES {
hwnd: unsafe { mem::transmute(self.handle.hwnd) },
pixelSize: d2d_rect,
presentOptions: D2D1_PRESENT_OPTIONS_NONE,
};
let target = unsafe {
self.factory
.CreateHwndRenderTarget(
&render_properties,
&hwnd_render_properties)
.unwrap()
};
self.brush = Some(Brush::Solid(DEFAULT_BRUSH_COLOR).to_brush(&target));
self.target = Some(target);
}
}
}
fn create_factory() -> ID2D1Factory1 {
let mut options = D2D1_FACTORY_OPTIONS::default();
if cfg!(debug_assertions) {
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
}
let mut result = None;
unsafe {
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&ID2D1Factory1::IID,
&options,
mem::transmute(&mut result)
).map(|()| result.unwrap())
}.unwrap()
}
fn create_dw_factory() -> IDWriteFactory {
unsafe {
mem::transmute(DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
&IDWriteFactory::IID
).unwrap())
}
}
fn create_system_font_collection(factory: &IDWriteFactory) -> IDWriteFontCollection {
unsafe {
let mut result = None;
factory.GetSystemFontCollection(&mut result, false)
.map(|_| result.unwrap())
.unwrap()
}
}
fn create_text_format(factory: &IDWriteFactory, fonts: &IDWriteFontCollection)
-> IDWriteTextFormat
{
unsafe {
let format = factory.CreateTextFormat(
"Arial",
fonts,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
10.0 * 96.0 / 72.0,
"en-US",
).unwrap();
format.SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP).unwrap();
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,
}
}