the memes will be perfect

master
TheOddGarlic 2022-04-16 20:18:23 +03:00
commit 9323a47044
22 changed files with 1018 additions and 0 deletions

3
.gitignore vendored Executable file
View File

@ -0,0 +1,3 @@
/target
Cargo.lock
.idea

20
Cargo.toml Executable file
View File

@ -0,0 +1,20 @@
[package]
name = "abletk"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
abletk-macros = { path = "./abletk-macros" }
abletk-common = { path = "./abletk-common" }
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "sync", "time"] }
winit = "0.26.1"
raw-window-handle = "0.4.3"
[workspace]
members = [
"abletk-macros",
"abletk-common",
"abletk-direct2d",
]

13
abletk-common/Cargo.toml Executable file
View File

@ -0,0 +1,13 @@
[package]
name = "abletk-common"
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"
[target.'cfg(windows)'.dependencies]
abletk-direct2d = { path = "../abletk-direct2d" }
windows = { version = "0.35.0", features = ["Win32_Graphics_Direct2D_Common"] }

30
abletk-common/src/color.rs Executable file
View File

@ -0,0 +1,30 @@
#[cfg(windows)]
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
#[derive(Copy, Clone, Debug)]
pub enum Color {
RGBA(f32, f32, f32, f32),
RGB(f32, f32, f32),
}
impl Color {
pub fn to_rgba(&self) -> (f32, f32, f32, f32) {
match *self {
Self::RGBA(r, g, b, a) => (r, g, b, a),
Self::RGB(r, g, b) => (r, g, b, 1.0),
}
}
}
#[cfg(windows)]
impl From<Color> for D2D1_COLOR_F {
fn from(color: Color) -> Self {
let rgba = color.to_rgba();
Self {
r: rgba.0,
g: rgba.1,
b: rgba.2,
a: rgba.3,
}
}
}

54
abletk-common/src/lib.rs Executable file
View File

@ -0,0 +1,54 @@
pub mod color;
pub mod rect;
use raw_window_handle::RawWindowHandle;
use color::Color;
use rect::Rect;
#[cfg(windows)]
use abletk_direct2d as backend;
pub struct Renderer {
renderer: backend::Renderer,
}
impl Renderer {
pub fn new(handle: RawWindowHandle) -> Self {
let handle = match handle {
RawWindowHandle::Win32(handle) => handle,
_ => todo!()
};
Self {
renderer: backend::Renderer::new(handle)
}
}
pub fn begin_draw(&mut self) {
self.renderer.begin_draw()
}
pub fn clear(&self, color: Color) {
self.renderer.clear(color)
}
pub fn draw_text<S: AsRef<str>>(&self, text: S) {
self.renderer.draw_text(text.as_ref())
}
pub fn end_draw(&self) {
self.renderer.end_draw()
}
pub fn fill_rect(&self, rect: Rect) {
self.renderer.fill_rect(rect)
}
pub fn resized(&mut self, width: u32, height: u32) {
self.renderer.resized(width, height)
}
pub fn size(&self) -> (f32, f32) {
self.renderer.size()
}
}

50
abletk-common/src/rect.rs Executable file
View File

@ -0,0 +1,50 @@
#[cfg(windows)]
use windows::Win32::{
Foundation::RECT,
Graphics::Direct2D::Common::{D2D_RECT_F, D2D_RECT_U},
};
// fixme: consider splitting this into two types (f32 and u32) or make this generic
#[derive(Copy, Clone, Debug)]
pub struct Rect {
pub left: f32,
pub right: f32,
pub top: f32,
pub bottom: f32,
}
#[cfg(windows)]
impl From<Rect> for RECT {
fn from(rect: Rect) -> Self {
Self {
left: rect.left as i32,
right: rect.right as i32,
top: rect.top as i32,
bottom: rect.bottom as i32,
}
}
}
#[cfg(windows)]
impl From<Rect> for D2D_RECT_U {
fn from(rect: Rect) -> Self {
Self {
left: rect.left as u32,
right: rect.right as u32,
top: rect.top as u32,
bottom: rect.bottom as u32,
}
}
}
#[cfg(windows)]
impl From<Rect> for D2D_RECT_F {
fn from(rect: Rect) -> Self {
Self {
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
}
}
}

26
abletk-direct2d/Cargo.toml Executable file
View File

@ -0,0 +1,26 @@
[package]
name = "abletk-direct2d"
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"
[dependencies.windows]
version = "0.35.0"
features = [
"Foundation_Numerics",
"Win32_System_Com",
"Win32_Foundation",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_Direct2D",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_System_LibraryLoader",
"Win32_System_SystemInformation",
"Win32_UI_WindowsAndMessaging",
"alloc",
]

218
abletk-direct2d/src/lib.rs Executable file
View File

@ -0,0 +1,218 @@
use std::mem;
use raw_window_handle::Win32Handle;
use windows::{
core::Interface, Foundation::Numerics::*, Win32::Foundation::*,
Win32::Graphics::Direct2D::Common::*, Win32::Graphics::Direct2D::*,
Win32::Graphics::DirectWrite::*, Win32::UI::WindowsAndMessaging::*,
};
/// The default brush color is black.
const DEFAULT_BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
const DEFAULT_BRUSH_PROPERTIES: D2D1_BRUSH_PROPERTIES = D2D1_BRUSH_PROPERTIES {
opacity: 1.0,
// Matrix3x2::identity() is not a const fn but it could be
//
// I (TheOddGarlic) sent windows-rs a PR and it got merged but now we wait
// for it to be included in the next release
transform: Matrix3x2 {
M11: 1.0, M12: 0.0, M21: 0.0, M22: 1.0, M31: 0.0, M32: 0.0
}
};
pub struct Renderer {
handle: Win32Handle,
factory: ID2D1Factory1,
dw_factory: IDWriteFactory,
target: Option<ID2D1HwndRenderTarget>,
brush: Option<ID2D1SolidColorBrush>,
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()
};
Self {
handle,
factory,
dw_factory,
target: None,
brush: None,
stroke_style,
system_fonts,
text_format,
}
}
pub fn begin_draw(&mut self) {
self.setup_target();
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_text(&self, text: &str) {
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,
// fixme: unhardcode this (needs layout stuff probably)
&D2D_RECT_F {
left: 0.0,
top: 0.0,
right: 100.0,
bottom: 100.0,
},
self.brush.as_ref().unwrap(),
D2D1_DRAW_TEXT_OPTIONS_NONE,
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(
&rect.into(),
self.brush.as_ref().unwrap())
}
}
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 size(&self) -> (f32, f32) {
let size = unsafe {
self.target.as_ref().unwrap().GetSize()
};
(size.width, size.height)
}
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()
};
let brush = unsafe {
target.CreateSolidColorBrush(&DEFAULT_BRUSH_COLOR, &DEFAULT_BRUSH_PROPERTIES)
.unwrap()
};
self.target = Some(target);
self.brush = Some(brush);
}
}
}
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 {
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()
}
}

13
abletk-macros/Cargo.toml Executable file
View File

@ -0,0 +1,13 @@
[package]
name = "abletk-macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
quote = "1.0.18"
syn = { version = "1.0.91", features = ["full"] }

73
abletk-macros/src/lib.rs Executable file
View File

@ -0,0 +1,73 @@
//! # abletk-macros
//!
//! This crate implements procedural macros to be used with AbleTK. You
//! shouldn't depend on this directly.
#[macro_use] extern crate quote;
use proc_macro::TokenStream;
use syn::{ItemFn, ReturnType, Type};
use syn::spanned::Spanned;
#[proc_macro_attribute]
pub fn launch(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut func: ItemFn = syn::parse(item).expect("parsing failed");
assert_ne!(func.sig.ident, "main", "Function cannot be named main");
assert_eq!(func.sig.inputs.len(), 0, "Function cannot take any arguments");
let asyncness = func.sig.asyncness.is_some();
let ident = func.sig.ident.clone();
// could be simplified if we could deref inside nested patterns
match &mut func.sig.output {
ReturnType::Type(_, ty) => {
if let Type::Infer(_) = **ty {
let span = ty.span();
*ty = syn::parse2(
quote_spanned!(span=> ::abletk::application::Application)
).unwrap();
}
}
ReturnType::Default => {
let span = func.sig.span();
let new = if asyncness {
quote_spanned! { span =>
async fn #ident() -> ::abletk::application::Application
}
} else {
quote_spanned! { span =>
fn #ident() -> ::abletk::application::Application
}
};
func.sig = syn::parse2(new).unwrap();
}
}
let out = if asyncness {
quote! {
fn main() {
let rt = ::tokio::runtime::Builder::new_multi_thread()
// todo: make this the application name
.thread_name("abletk-tokio-runtime-worker")
.enable_time()
.build().unwrap();
rt.block_on(#ident()).launch(rt);
}
#func
}
} else {
quote! {
fn main() {
let rt = ::tokio::runtime::Builder::new_multi_thread()
// todo: make this the application name
.thread_name("abletk-tokio-runtime-worker")
.enable_time()
.build().unwrap();
#ident().launch(rt);
}
#func
}
};
out.into()
}

128
src/application.rs Executable file
View File

@ -0,0 +1,128 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use winit::event::{Event, WindowEvent as WinitWindowEvent};
use winit::event_loop::EventLoop;
use winit::window::WindowId;
use crate::context::Context;
use crate::event::{
application::Event as ApplicationEvent,
window::Event as WindowEvent,
};
use crate::plugin::Plugin;
use crate::window::{Window, WindowBuilder};
macro_rules! emit_app_event {
($app:expr, $event:expr) => {
if let Some(handlers) = $app.events.get_mut(&$event) {
handlers.iter_mut().for_each(|handler| handler(&mut $app.ctx.borrow_mut()));
}
};
}
pub struct Application {
winit_event_loop: EventLoop<()>,
windows: HashMap<WindowId, Window>,
events: HashMap<ApplicationEvent, Vec<Box<dyn FnMut(&mut Context)>>>,
ctx: Rc<RefCell<Context>>,
}
impl Application {
pub fn new() -> Self {
Default::default()
}
pub fn apply_plugin<P>(self, plugin: P) -> Self
where
P: Plugin
{
plugin.apply(self)
}
pub fn add_window(mut self, builder: WindowBuilder) -> Self {
let window = builder
.build(self.ctx.clone(), &self.winit_event_loop)
.unwrap();
self.windows.insert(window.id(), window);
self
}
pub fn add_windows<const N: usize>(mut self,
builders: [WindowBuilder; N]
) -> Self {
self.windows.extend(builders.map(|builder| {
let window = builder
.build(self.ctx.clone(), &self.winit_event_loop)
.unwrap();
(window.id(), window)
}));
self
}
pub fn on_event(mut self,
event: ApplicationEvent,
handler: fn(&mut Context),
) -> Self {
if let Some(handlers) = self.events.get_mut(&event) {
handlers.push(Box::new(handler));
} else {
self.events.insert(event, vec![Box::new(handler)]);
}
self
}
/// This method enters the event loop. You probably don't want to call this
/// directly, the `launch` macro will call this in the generated main
/// function.
pub fn launch(mut self, rt: tokio::runtime::Runtime) {
self.ctx.borrow_mut().set_rt(rt);
self.winit_event_loop.run(move |event, target, control_flow| {
*control_flow = self.ctx.borrow().control_flow();
while let Some(builder) = self.ctx.borrow_mut().pop_window_builder() {
let window = builder.build(self.ctx.clone(), target).unwrap();
self.windows.insert(window.id(), window);
}
match event {
Event::WindowEvent {
window_id,
event: WinitWindowEvent::CloseRequested,
} => {
self.windows.get_mut(&window_id).unwrap()
.emit_event(WindowEvent::Closed);
self.windows.remove(&window_id);
if self.windows.is_empty() {
emit_app_event!(self, ApplicationEvent::AllWindowsClosed);
}
}
Event::WindowEvent {
window_id,
event: WinitWindowEvent::Resized(size)
} => self.windows.get_mut(&window_id).unwrap().resized(size),
Event::MainEventsCleared => {
// determine if state changed and request redraw if needed
// rinse and repeat for every window
}
Event::RedrawRequested(window_id) =>
self.windows.get_mut(&window_id).unwrap().render(),
_ => ()
}
})
}
}
impl Default for Application {
fn default() -> Self {
Self {
winit_event_loop: EventLoop::new(),
windows: Default::default(),
events: Default::default(),
ctx: Rc::new(RefCell::new(Context::new())),
}
}
}

56
src/context.rs Executable file
View File

@ -0,0 +1,56 @@
use std::collections::vec_deque::VecDeque;
use std::future::Future;
use tokio::runtime::Runtime;
use winit::event_loop::ControlFlow;
use crate::window::WindowBuilder;
/// Contains functionality that is shared across all of the application.
pub struct Context {
control_flow: ControlFlow,
window_queue: VecDeque<WindowBuilder>,
rt: Option<Runtime>,
}
impl Context {
/// Schedules the creation of a new window for the next iteration of the
/// event loop.
pub fn add_window(&mut self, builder: WindowBuilder) {
self.window_queue.push_back(builder);
}
/// Schedules the creation of new windows for the next iteration of the
/// event loop.
pub fn add_windows<const N: usize>(&mut self, builders: [WindowBuilder; N]) {
self.window_queue.extend(builders);
}
/// Block on a future using the internal tokio runtime instance.
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
self.rt.as_ref().unwrap().block_on(future)
}
/// Schedules shutdown for the next iteration of the event loop.
pub fn quit(&mut self) {
self.control_flow = ControlFlow::Exit;
}
pub(crate) fn new() -> Self {
Self {
control_flow: ControlFlow::Wait,
window_queue: VecDeque::new(),
rt: None,
}
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
}
pub(crate) fn pop_window_builder(&mut self) -> Option<WindowBuilder> {
self.window_queue.pop_back()
}
pub(crate) fn set_rt(&mut self, rt: Runtime) {
self.rt = Some(rt)
}
}

5
src/event/application.rs Executable file
View File

@ -0,0 +1,5 @@
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Event {
AllWindowsClosed,
}

2
src/event/mod.rs Executable file
View File

@ -0,0 +1,2 @@
pub mod application;
pub mod window;

5
src/event/window.rs Executable file
View File

@ -0,0 +1,5 @@
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Event {
Closed,
}

19
src/lib.rs Executable file
View File

@ -0,0 +1,19 @@
//! Able GUI Toolkit (AbleTK)
#[doc(inline)] pub use abletk_macros::*;
pub mod application;
pub mod context;
pub mod event;
pub mod platform;
pub mod plugin;
pub mod widget;
pub mod window;
pub mod prelude {
pub use crate::application::*;
pub use crate::event::application::Event as ApplicationEvent;
pub use crate::event::window::Event as WindowEvent;
pub use crate::platform::Platform;
pub use crate::widget::Label;
pub use crate::widget::Widget;
pub use crate::window::*;
pub use crate::launch;
}

9
src/main.rs Executable file
View File

@ -0,0 +1,9 @@
use abletk::plugin::QuitPlugin;
use abletk::prelude::*;
#[launch]
fn launch() -> _ {
Application::new()
.apply_plugin(QuitPlugin)
.add_window(Window::builder(Label::new("Hello, AbleTK!")))
}

30
src/platform.rs Executable file
View File

@ -0,0 +1,30 @@
#[cfg(target_os = "windows")] const TARGET_PLATFORM: Platform = Platform::Windows;
#[cfg(target_os = "linux")] const TARGET_PLATFORM: Platform = Platform::Linux;
#[cfg(target_os = "macos")] const TARGET_PLATFORM: Platform = Platform::MacOS;
#[cfg(not(any(
target_os = "windows",
target_os = "linux",
target_os = "macos",
)))]
const TARGET_PLATFORM: Platform = Platform::Unknown;
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
/// An enum of operating systems that are supported by AbleTK. This does not
/// correspond to backends used by AbleTK, and is to be used by applications
/// that have platform-dependent behaviour.
// Contributors: name these according to their marketing names, including
// capitalisation, only changing the first letter to an upper
// case letter. E.g. Windows not Win32, MacOS not MacOs or macOS.
pub enum Platform {
Windows,
Linux,
MacOS,
Unknown
}
impl Platform {
pub const fn target() -> Self {
TARGET_PLATFORM
}
}

26
src/plugin.rs Executable file
View File

@ -0,0 +1,26 @@
use crate::application::Application;
use crate::event::application::Event;
use crate::prelude::Platform;
/// Plugins are modular configurators for AbleTK applications.
pub trait Plugin {
fn apply(&self, app: Application) -> Application;
}
/// A plugin that automatically quits the app if all windows are destroyed, on
/// non-macOS platforms.
///
/// ## Platform-specific
/// - **macOS:** It is common for macOS apps to not quit if all windows are
/// destroyed, so this plugin does exactly that.
pub struct QuitPlugin;
impl Plugin for QuitPlugin {
fn apply(&self, app: Application) -> Application {
app.on_event(Event::AllWindowsClosed, |ctx| {
if Platform::target() != Platform::MacOS {
ctx.quit()
}
})
}
}

20
src/widget/label.rs Executable file
View File

@ -0,0 +1,20 @@
use abletk_common::Renderer;
use crate::widget::Widget;
pub struct Label {
text: String
}
impl Widget for Label {
fn draw(&self, renderer: &Renderer) {
renderer.draw_text(&self.text);
}
}
impl Label {
pub fn new<S: Into<String>>(text: S) -> Self {
Self {
text: text.into()
}
}
}

8
src/widget/mod.rs Executable file
View File

@ -0,0 +1,8 @@
mod label;
use abletk_common::Renderer;
pub use label::*;
pub trait Widget {
fn draw(&self, renderer: &Renderer);
}

210
src/window.rs Executable file
View File

@ -0,0 +1,210 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use raw_window_handle::HasRawWindowHandle;
use winit::{
event_loop::EventLoopWindowTarget,
window::{Window as WinitWindow, WindowBuilder as WinitWindowBuilder},
};
use winit::dpi::PhysicalSize;
use winit::error::OsError;
use winit::window::WindowId;
use abletk_common::color::Color;
use abletk_common::Renderer;
use crate::context::Context;
use crate::event::window::Event;
use crate::widget::Widget;
pub struct Window {
window: WinitWindow,
events: HashMap<Event, Vec<fn(&mut Context, &mut Window)>>,
root: Box<dyn Widget>,
ctx: Rc<RefCell<Context>>,
renderer: Renderer,
background: Color,
}
impl Window {
pub fn new<S: Into<String>>(
ctx: Rc<RefCell<Context>>,
root: Box<dyn Widget>,
event_loop: &EventLoopWindowTarget<()>,
events: HashMap<Event, Vec<fn(&mut Context, &mut Window)>>,
always_on_top: bool,
background: Color,
decorations: bool,
maximized: bool,
resizable: bool,
title: S,
transparent: bool,
visible: bool,
) -> Result<Self, OsError> {
let window = WinitWindowBuilder::new()
.with_always_on_top(always_on_top)
.with_decorations(decorations)
.with_maximized(maximized)
.with_resizable(resizable)
.with_title(title)
.with_transparent(transparent)
.with_visible(visible)
.build(event_loop)?;
Ok(Self {
renderer: Renderer::new(window.raw_window_handle()),
window,
background,
events,
root,
ctx,
})
}
pub fn builder<W: Widget + 'static>(root: W) -> WindowBuilder {
WindowBuilder::new(root)
}
pub fn render(&mut self) {
eprintln!("Rendering window with id {:?}", self.id());
self.renderer.begin_draw();
self.renderer.clear(self.background);
self.root.draw(&self.renderer);
self.renderer.end_draw()
}
pub fn set_always_on_top(&self, value: bool) {
self.window.set_always_on_top(value)
}
pub fn focus(&self) {
self.window.focus_window()
}
pub fn redraw(&self) {
self.window.request_redraw()
}
pub fn set_title<S: AsRef<str> + Into<String>>(&mut self, title: S) {
self.window.set_title(title.as_ref())
}
pub(crate) fn emit_event(&mut self, event: Event) {
if let Some(handlers) = self.events.get(&event) {
let ctx = self.ctx.clone();
let ctx = &mut ctx.borrow_mut();
handlers.clone().iter().for_each(|handler| handler(ctx, self))
}
}
pub(crate) fn id(&self) -> WindowId {
self.window.id()
}
pub(crate) fn resized(&mut self, size: PhysicalSize<u32>) {
self.renderer.resized(size.width, size.height)
}
}
pub struct WindowBuilder {
events: HashMap<Event, Vec<fn(&mut Context, &mut Window)>>,
always_on_top: bool,
background: Option<Color>,
decorations: bool,
maximized: bool,
resizable: bool,
title: Option<String>,
transparent: bool,
visible: bool,
root: Box<dyn Widget>,
}
impl WindowBuilder {
pub fn new<W: Widget + 'static>(root: W) -> Self {
Self {
events: Default::default(),
always_on_top: false,
background: None,
decorations: true,
maximized: false,
resizable: true,
title: None,
transparent: false,
visible: true,
root: Box::new(root),
}
}
pub fn on_event(mut self,
event: Event,
handler: fn(&mut Context, &mut Window),
) -> Self {
if let Some(handlers) = self.events.get_mut(&event) {
handlers.push(handler);
} else {
self.events.insert(event, vec![handler]);
}
self
}
pub fn always_on_top(mut self, value: bool) -> Self {
self.always_on_top = value;
self
}
pub fn background(mut self, value: Color) -> Self {
self.background = Some(value);
self
}
pub fn decorations(mut self, value: bool) -> Self {
self.decorations = value;
self
}
pub fn maximized(mut self, value: bool) -> Self {
self.maximized = value;
self
}
pub fn resizable(mut self, value: bool) -> Self {
self.resizable = value;
self
}
pub fn title<S: Into<String>>(mut self, value: S) -> Self {
self.title = Some(value.into());
self
}
pub fn transparent(mut self, value: bool) -> Self {
self.transparent = value;
self
}
pub fn visible(mut self, value: bool) -> Self {
self.visible = value;
self
}
pub fn build(self,
ctx: Rc<RefCell<Context>>,
event_loop: &EventLoopWindowTarget<()>
) -> Result<Window, OsError> {
Window::new(
ctx,
self.root,
event_loop,
self.events,
// todo: make this the application name
self.always_on_top,
self.background.unwrap_or(Color::RGB(255.0, 255.0, 255.0)),
self.decorations,
self.maximized,
self.resizable,
self.title.unwrap_or("AbleTK Window".into()),
self.transparent,
self.visible,
)
}
}