use gtk::prelude::*;
use std::sync::Arc;
use tao::{
event_loop::EventLoopWindowTarget,
platform::unix::{WindowBuilderExtUnix, WindowExtUnix},
window::{Window, WindowBuilder},
};
use crate::WindowConfig;
mod canvas;
pub use canvas::GtkCanvas;
pub struct GtkWindow {
window: Arc<Window>,
container: gtk::Fixed,
canvas: Arc<GtkCanvas>,
css: gtk::CssProvider,
}
impl GtkWindow {
pub fn new<T: 'static>(
target: &EventLoopWindowTarget<T>,
config: &WindowConfig,
) -> Result<Self, String> {
let window = Arc::new(
WindowBuilder::new()
.with_title(&config.title)
.with_inner_size(tao::dpi::LogicalSize::new(config.width, config.height))
.with_decorations(config.decorations)
.with_resizable(config.resizable)
.with_app_paintable(true)
.with_transparent(true)
.build(target)
.map_err(|error| error.to_string())?,
);
let css = gtk::CssProvider::new();
css.load_from_data(b"window.argui-host, window.argui-host > box, fixed.argui-canvas { background-color: transparent; }")
.map_err(|error| error.to_string())?;
let native = window.gtk_window();
native.style_context().add_class("argui-host");
let screen = GtkWindowExt::screen(native).ok_or("GTK screen unavailable")?;
gtk::StyleContext::add_provider_for_screen(
&screen,
&css,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
let container = gtk::Fixed::new();
container.set_can_focus(true);
container.style_context().add_class("argui-canvas");
window
.default_vbox()
.ok_or("GTK client container unavailable")?
.pack_start(&container, true, true, 0);
container.show_all();
let canvas = Arc::new(GtkCanvas::new(window.clone())?);
let weak_canvas = Arc::downgrade(&canvas);
native.connect_unmap(move |_| {
if let Some(canvas) = weak_canvas.upgrade() {
canvas.detach();
}
});
Ok(Self {
window,
container,
canvas,
css,
})
}
pub fn native(&self) -> &Window {
&self.window
}
pub fn container(&self) -> >k::Fixed {
&self.container
}
pub fn canvas(&self) -> Arc<GtkCanvas> {
self.canvas.clone()
}
pub fn pointer_position(&self) -> Option<argui_core::Point> {
let window = self.window.gtk_window().window()?;
let pointer = window.display().default_seat()?.pointer()?;
let (_, x, y, _) = window.device_position_double(&pointer);
let allocation = self.container.allocation();
Some(argui_core::Point::new(
x as f32 - allocation.x() as f32,
y as f32 - allocation.y() as f32,
))
}
pub fn native_content_focused(&self) -> bool {
self.window
.gtk_window()
.focused_widget()
.is_some_and(|widget| {
widget != *self.container.upcast_ref::<gtk::Widget>()
&& widget.is_ancestor(&self.container)
})
}
pub fn focus_canvas(&self) {
self.container.grab_focus();
}
pub fn corner_radius(&self) -> f32 {
if self.window.is_decorated() && !self.window.is_maximized() {
8.0
} else {
0.0
}
}
pub fn client_size(&self) -> (u32, u32, f32) {
let allocation = self.container.allocation();
let scale = self.container.scale_factor().max(1);
(
(allocation.width().max(0) * scale) as u32,
(allocation.height().max(0) * scale) as u32,
scale as f32,
)
}
pub fn sync_canvas(&self) -> Result<(), String> {
if !self.window.gtk_window().is_mapped() {
return Ok(());
}
let allocation = self.container.allocation();
if self.canvas.place(allocation.x(), allocation.y())? {
self.window.gtk_window().queue_draw();
}
self.canvas.dispatch_pending()
}
}
impl Drop for GtkWindow {
fn drop(&mut self) {
if let Some(screen) = GtkWindowExt::screen(self.window.gtk_window()) {
gtk::StyleContext::remove_provider_for_screen(&screen, &self.css);
}
}
}