use std::any::Any;
use std::cell::{Cell, RefCell};
use std::ffi::c_void;
use std::ffi::OsString;
use std::os::raw::{c_int, c_uint};
use std::ptr;
use std::slice;
use std::sync::{Arc, Mutex, Weak};
use gdk::{EventKey, EventMask, ModifierType, ScrollDirection, WindowExt};
use gio::ApplicationExt;
use gtk::prelude::*;
use gtk::{AccelGroup, ApplicationWindow};
use piet_common::{Piet, RenderContext};
use util::assert_main_thread;
use win_main::with_application;
use crate::clipboard::ClipboardItem;
use crate::dialog::FileDialogOptions;
use crate::keyboard;
use crate::kurbo::{Point, Size, Vec2};
use crate::platform::dialog::FileDialogType;
use crate::platform::menu::Menu;
use crate::window::{self, Cursor, FileInfo, MouseButton, Text, TimerToken, WinCtx, WinHandler};
use crate::Error;
pub mod application;
pub mod dialog;
pub mod menu;
pub mod util;
pub mod win_main;
macro_rules! clone {
(@param _) => ( _ );
(@param $x:ident) => ( $x );
($($n:ident),+ => move || $body:expr) => (
{
$( let $n = $n.clone(); )+
move || $body
}
);
($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
{
$( let $n = $n.clone(); )+
move |$(clone!(@param $p),)+| $body
}
);
}
#[derive(Clone, Default)]
pub struct WindowHandle {
state: Weak<WindowState>,
}
pub struct WindowBuilder {
handler: Option<Box<dyn WinHandler>>,
title: String,
menu: Option<menu::Menu>,
size: Size,
}
#[derive(Clone)]
pub struct IdleHandle {
idle_queue: Arc<Mutex<Vec<Box<dyn IdleCallback>>>>,
state: Weak<WindowState>,
}
trait IdleCallback: Send {
fn call(self: Box<Self>, a: &dyn Any);
}
impl<F: FnOnce(&dyn Any) + Send> IdleCallback for F {
fn call(self: Box<F>, a: &dyn Any) {
(*self)(a)
}
}
struct WindowState {
window: ApplicationWindow,
handler: RefCell<Box<dyn WinHandler>>,
idle_queue: Arc<Mutex<Vec<Box<dyn IdleCallback>>>>,
current_keyval: RefCell<Option<u32>>,
}
struct WinCtxImpl<'a> {
handle: &'a WindowHandle,
text: Text<'static>,
}
impl WindowBuilder {
pub fn new() -> WindowBuilder {
WindowBuilder {
handler: None,
title: String::new(),
menu: None,
size: Size::new(500.0, 400.0),
}
}
pub fn set_handler(&mut self, handler: Box<dyn WinHandler>) {
self.handler = Some(handler);
}
pub fn set_size(&mut self, size: Size) {
self.size = size;
}
pub fn set_title(&mut self, title: impl Into<String>) {
self.title = title.into();
}
pub fn set_menu(&mut self, menu: Menu) {
self.menu = Some(menu);
}
pub fn build(self) -> Result<WindowHandle, Error> {
assert_main_thread();
let handler = self
.handler
.expect("Tried to build a window without setting the handler");
let window = with_application(|app| ApplicationWindow::new(&app));
let dpi_scale = window
.get_display()
.map(|c| c.get_default_screen().get_resolution() as f64)
.unwrap_or(96.0)
/ 96.0;
window.set_default_size(
(self.size.width * dpi_scale) as i32,
(self.size.height * dpi_scale) as i32,
);
let accel_group = AccelGroup::new();
window.add_accel_group(&accel_group);
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
window.add(&vbox);
let win_state = Arc::new(WindowState {
window,
handler: RefCell::new(handler),
idle_queue: Arc::new(Mutex::new(vec![])),
current_keyval: RefCell::new(None),
});
with_application(|app| {
app.connect_shutdown(clone!(win_state => move |_| {
let _ = &win_state;
}))
});
let handle = WindowHandle {
state: Arc::downgrade(&win_state),
};
if let Some(menu) = self.menu {
let menu = menu.into_gtk_menubar(&handle, &accel_group);
vbox.pack_start(&menu, false, false, 0);
}
let drawing_area = gtk::DrawingArea::new();
drawing_area.set_events(
EventMask::EXPOSURE_MASK
| EventMask::POINTER_MOTION_MASK
| EventMask::BUTTON_PRESS_MASK
| EventMask::BUTTON_RELEASE_MASK
| EventMask::KEY_PRESS_MASK
| EventMask::ENTER_NOTIFY_MASK
| EventMask::KEY_RELEASE_MASK
| EventMask::SCROLL_MASK,
);
drawing_area.set_can_focus(true);
drawing_area.grab_focus();
drawing_area.connect_enter_notify_event(|widget, _| {
widget.grab_focus();
Inhibit(true)
});
let last_size = Cell::new((0, 0));
drawing_area.connect_draw(clone!(handle => move |widget, context| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
let extents = context.clip_extents();
let size = (
(extents.2 - extents.0) as u32,
(extents.3 - extents.1) as u32,
);
if last_size.get() != size {
last_size.set(size);
state.handler.borrow_mut().size(size.0, size.1, &mut ctx);
}
let mut context = context.clone();
let mut piet_context = Piet::new(&mut context);
if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() {
let anim = handler_borrow
.paint(&mut piet_context, &mut ctx);
if let Err(e) = piet_context.finish() {
eprintln!("piet error on render: {:?}", e);
}
if anim {
widget.queue_draw();
}
}
}
Inhibit(false)
}));
drawing_area.connect_button_press_event(clone!(handle => move |_widget, button| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
state.handler.borrow_mut().mouse_down(
&window::MouseEvent {
pos: Point::from(button.get_position()),
count: get_mouse_click_count(button.get_event_type()),
mods: get_modifiers(button.get_state()),
button: get_mouse_button(button.get_button()),
},
&mut ctx,
);
}
Inhibit(true)
}));
drawing_area.connect_button_release_event(clone!(handle => move |_widget, button| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
state.handler.borrow_mut().mouse_up(
&window::MouseEvent {
pos: Point::from(button.get_position()),
mods: get_modifiers(button.get_state()),
count: 0,
button: get_mouse_button(button.get_button()),
},
&mut ctx,
);
}
Inhibit(true)
}));
drawing_area.connect_motion_notify_event(clone!(handle=>move |_widget, motion| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
let pos = Point::from(motion.get_position());
let mouse_event = window::MouseEvent {
pos,
mods: get_modifiers(motion.get_state()),
count: 0,
button: get_mouse_button_from_modifiers(motion.get_state()),
};
state
.handler
.borrow_mut()
.mouse_move(&mouse_event, &mut ctx);
}
Inhibit(true)
}));
drawing_area.connect_scroll_event(clone!(handle => move |_widget, scroll| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
let modifiers = get_modifiers(scroll.get_state());
let mut handler = state.handler.borrow_mut();
match scroll.get_direction() {
ScrollDirection::Up => {
handler.wheel(Vec2::from((0.0, -120.0)), modifiers, &mut ctx);
}
ScrollDirection::Down => {
handler.wheel(Vec2::from((0.0, 120.0)), modifiers, &mut ctx);
}
ScrollDirection::Left => {
handler.wheel(Vec2::from((-120.0, 0.0)), modifiers, &mut ctx);
}
ScrollDirection::Right => {
handler.wheel(Vec2::from((120.0, 0.0)), modifiers, &mut ctx);
}
ScrollDirection::Smooth => {
eprintln!(
"Warning: somehow the Druid widget got a smooth scroll event"
);
}
e => {
eprintln!(
"Warning: the Druid widget got some whacky scroll direction {:?}",
e
);
}
}
}
Inhibit(true)
}));
drawing_area.connect_key_press_event(clone!(handle => move |_widget, key| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
let mut current_keyval = state.current_keyval.borrow_mut();
let repeat = *current_keyval == Some(key.get_keyval());
*current_keyval = Some(key.get_keyval());
let key_event = make_key_event(key, repeat);
state.handler.borrow_mut().key_down(key_event, &mut ctx);
}
Inhibit(true)
}));
drawing_area.connect_key_release_event(clone!(handle => move |_widget, key| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
*(state.current_keyval.borrow_mut()) = None;
let key_event = make_key_event(key, false);
state.handler.borrow_mut().key_up(key_event, &mut ctx);
}
Inhibit(true)
}));
drawing_area.connect_destroy(clone!(handle => move |_widget| {
if let Some(state) = handle.state.upgrade() {
let mut ctx = WinCtxImpl::from(&handle);
state.handler.borrow_mut().destroy(&mut ctx);
}
}));
vbox.pack_end(&drawing_area, true, true, 0);
win_state
.handler
.borrow_mut()
.connect(&window::WindowHandle {
inner: handle.clone(),
});
Ok(handle)
}
}
impl WindowHandle {
pub fn show(&self) {
if let Some(state) = self.state.upgrade() {
state.window.show_all();
}
}
pub fn close(&self) {
if let Some(state) = self.state.upgrade() {
with_application(|app| {
app.remove_window(&state.window);
});
}
}
pub fn invalidate(&self) {
if let Some(state) = self.state.upgrade() {
state.window.queue_draw();
}
}
pub fn get_idle_handle(&self) -> Option<IdleHandle> {
self.state.upgrade().map(|s| IdleHandle {
idle_queue: s.idle_queue.clone(),
state: Arc::downgrade(&s),
})
}
pub fn get_dpi(&self) -> f32 {
self.state
.upgrade()
.and_then(|s| s.window.get_window())
.map(|w| w.get_display().get_default_screen().get_resolution() as f32)
.unwrap_or(96.0)
}
pub fn px_to_pixels(&self, x: f32) -> i32 {
(x * self.get_dpi() * (1.0 / 96.0)).round() as i32
}
pub fn px_to_pixels_xy(&self, x: f32, y: f32) -> (i32, i32) {
let scale = self.get_dpi() * (1.0 / 96.0);
((x * scale).round() as i32, (y * scale).round() as i32)
}
pub fn pixels_to_px<T: Into<f64>>(&self, x: T) -> f32 {
(x.into() as f32) * 96.0 / self.get_dpi()
}
pub fn pixels_to_px_xy<T: Into<f64>>(&self, x: T, y: T) -> (f32, f32) {
let scale = 96.0 / self.get_dpi();
((x.into() as f32) * scale, (y.into() as f32) * scale)
}
pub fn set_menu(&self, menu: Menu) {
if let Some(state) = self.state.upgrade() {
let window = &state.window;
let accel_group = AccelGroup::new();
window.add_accel_group(&accel_group);
let vbox = window.get_children()[0]
.clone()
.downcast::<gtkrs::Box>()
.unwrap();
let first_child = &vbox.get_children()[0];
if first_child.is::<gtkrs::MenuBar>() {
vbox.remove(first_child);
}
let menubar = menu.into_gtk_menubar(&self, &accel_group);
vbox.pack_start(&menubar, false, false, 0);
menubar.show_all();
}
}
pub fn show_context_menu(&self, menu: Menu, _x: f64, _y: f64) {
if let Some(state) = self.state.upgrade() {
let window = &state.window;
let accel_group = AccelGroup::new();
window.add_accel_group(&accel_group);
let menu = menu.into_gtk_menu(&self, &accel_group);
menu.show_all();
menu.popup_easy(3, gtkrs::get_current_event_time());
}
}
pub fn set_title(&self, title: impl Into<String>) {
if let Some(state) = self.state.upgrade() {
state.window.set_title(&(title.into()));
}
}
pub fn file_dialog(
&self,
ty: FileDialogType,
options: FileDialogOptions,
) -> Result<OsString, Error> {
if let Some(state) = self.state.upgrade() {
dialog::open_file_sync(state.window.upcast_ref(), ty, options)
} else {
Err(Error::Other(
"Cannot upgrade state from weak pointer to arc",
))
}
}
}
unsafe impl Send for IdleHandle {}
unsafe impl Send for WindowState {}
unsafe impl Sync for WindowState {}
impl IdleHandle {
pub fn add_idle<F>(&self, callback: F)
where
F: FnOnce(&dyn Any) + Send + 'static,
{
let mut queue = self.idle_queue.lock().unwrap();
if let Some(state) = self.state.upgrade() {
if queue.is_empty() {
queue.push(Box::new(callback));
gdk::threads_add_idle(move || run_idle(&state));
} else {
queue.push(Box::new(callback));
}
}
}
}
fn run_idle(state: &Arc<WindowState>) -> bool {
assert_main_thread();
let mut handler = state.handler.borrow_mut();
let handler_as_any = handler.as_any();
let queue: Vec<_> = std::mem::replace(&mut state.idle_queue.lock().unwrap(), Vec::new());
for callback in queue {
callback.call(handler_as_any);
}
false
}
impl<'a> WinCtx<'a> for WinCtxImpl<'a> {
fn invalidate(&mut self) {
self.handle.invalidate();
}
fn text_factory(&mut self) -> &mut Text<'a> {
&mut self.text
}
fn set_cursor(&mut self, cursor: &Cursor) {
if let Some(gdk_window) = self
.handle
.state
.upgrade()
.and_then(|s| s.window.get_window())
{
let cursor = make_gdk_cursor(cursor, &gdk_window);
gdk_window.set_cursor(cursor.as_ref());
}
}
fn open_file_sync(&mut self, options: FileDialogOptions) -> Option<FileInfo> {
if let Some(state) = self.handle.state.upgrade() {
dialog::open_file_sync(state.window.upcast_ref(), FileDialogType::Open, options)
.ok()
.map(|s| FileInfo { path: s.into() })
} else {
None
}
}
fn set_clipboard_contents(&mut self, item: ClipboardItem) {
let display = gdk::Display::get_default().unwrap();
let clipboard = gtk::Clipboard::get_default(&display).unwrap();
match item {
ClipboardItem::Text(text) => {
clipboard.set_text(&text);
}
other => log::warn!("unhandled clipboard data {:?}", other),
}
}
fn request_timer(&mut self, deadline: std::time::Instant) -> TimerToken {
let interval = time_interval_from_deadline(deadline);
let token = next_timer_id();
let handle = self.handle.clone();
gdk::threads_add_timeout(interval, move || {
if let Some(state) = handle.state.upgrade() {
if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() {
let mut ctx = WinCtxImpl::from(&handle);
handler_borrow.timer(TimerToken::new(token), &mut ctx);
return false;
}
}
true
});
TimerToken::new(token)
}
}
impl<'a> From<&'a WindowHandle> for WinCtxImpl<'a> {
fn from(handle: &'a WindowHandle) -> Self {
WinCtxImpl {
handle,
text: Text::new(),
}
}
}
fn time_interval_from_deadline(deadline: std::time::Instant) -> u32 {
let now = std::time::Instant::now();
if now >= deadline {
0
} else {
(deadline - now).as_millis() as u32
}
}
fn next_timer_id() -> usize {
use std::sync::atomic::{AtomicUsize, Ordering};
static TIMER_ID: AtomicUsize = AtomicUsize::new(1);
TIMER_ID.fetch_add(1, Ordering::Relaxed)
}
fn make_gdk_cursor(cursor: &Cursor, gdk_window: &gdk::Window) -> Option<gdk::Cursor> {
gdk::Cursor::new_from_name(
&gdk_window.get_display(),
match cursor {
Cursor::Arrow => "default",
Cursor::IBeam => "text",
Cursor::Crosshair => "crosshair",
Cursor::OpenHand => "grab",
Cursor::NotAllowed => "not-allowed",
Cursor::ResizeLeftRight => "ew-resize",
Cursor::ResizeUpDown => "ns-resize",
},
)
}
fn get_mouse_button(button: u32) -> window::MouseButton {
match button {
1 => MouseButton::Left,
2 => MouseButton::Middle,
3 => MouseButton::Right,
4 => MouseButton::X1,
5 => MouseButton::X2,
_ => MouseButton::Left,
}
}
fn get_mouse_button_from_modifiers(modifiers: gdk::ModifierType) -> window::MouseButton {
match modifiers {
modifiers if modifiers.contains(ModifierType::BUTTON1_MASK) => MouseButton::Left,
modifiers if modifiers.contains(ModifierType::BUTTON2_MASK) => MouseButton::Middle,
modifiers if modifiers.contains(ModifierType::BUTTON3_MASK) => MouseButton::Right,
modifiers if modifiers.contains(ModifierType::BUTTON4_MASK) => MouseButton::X1,
modifiers if modifiers.contains(ModifierType::BUTTON5_MASK) => MouseButton::X2,
_ => {
MouseButton::Left
}
}
}
fn get_mouse_click_count(event_type: gdk::EventType) -> u32 {
match event_type {
gdk::EventType::ButtonPress => 1,
gdk::EventType::DoubleButtonPress => 2,
gdk::EventType::TripleButtonPress => 3,
_ => 0,
}
}
fn get_modifiers(modifiers: gdk::ModifierType) -> keyboard::KeyModifiers {
keyboard::KeyModifiers {
shift: modifiers.contains(ModifierType::SHIFT_MASK),
alt: modifiers.contains(ModifierType::MOD1_MASK),
ctrl: modifiers.contains(ModifierType::CONTROL_MASK),
meta: modifiers.contains(ModifierType::META_MASK),
}
}
fn make_key_event(key: &EventKey, repeat: bool) -> keyboard::KeyEvent {
let keyval = key.get_keyval();
let hardware_keycode = key.get_hardware_keycode();
let keycode = hardware_keycode_to_keyval(hardware_keycode).unwrap_or(keyval);
let text = gdk::keyval_to_unicode(keyval);
keyboard::KeyEvent::new(keycode, repeat, get_modifiers(key.get_state()), text, text)
}
fn hardware_keycode_to_keyval(keycode: u16) -> Option<u32> {
unsafe {
let keymap = gdk_sys::gdk_keymap_get_default();
let mut nkeys = 0;
let mut keys: *mut gdk_sys::GdkKeymapKey = ptr::null_mut();
let mut keyvals: *mut c_uint = ptr::null_mut();
gdk_sys::gdk_keymap_get_entries_for_keycode(
keymap,
c_uint::from(keycode),
&mut keys as *mut *mut gdk_sys::GdkKeymapKey,
&mut keyvals as *mut *mut c_uint,
&mut nkeys as *mut c_int,
);
if nkeys > 0 {
let keyvals_slice = slice::from_raw_parts(keyvals, nkeys as usize);
let keys_slice = slice::from_raw_parts(keys, nkeys as usize);
let resolved_keyval = keys_slice.iter().enumerate().find_map(|(i, key)| {
if key.group == 0 && key.level == 0 {
Some(keyvals_slice[i])
} else {
None
}
});
glib_sys::g_free(keyvals as *mut c_void);
glib_sys::g_free(keys as *mut c_void);
resolved_keyval
} else {
None
}
}
}
impl Default for WindowBuilder {
fn default() -> Self {
WindowBuilder::new()
}
}