#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
pub use macos::{choose_file_to_open, choose_file_to_save, choose_folder, MacOSApp, MacOSWindow};
#[cfg(target_os = "windows")]
pub use self::windows::{WindowsApp, WindowsTimer, WindowsWindow};
#[cfg(target_os = "linux")]
pub use self::linux::{LinuxApp, LinuxTimer, LinuxWindow};
use crate::element::ElementPtr;
use crate::support::point::Extent;
use crate::view::View;
#[cfg(target_os = "macos")]
use objc2_foundation::MainThreadMarker;
#[derive(Debug, Clone, Copy)]
pub struct WindowPosition {
pub x: i32,
pub y: i32,
}
impl WindowPosition {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
pub fn center() -> Self {
Self { x: -1, y: -1 } }
}
impl Default for WindowPosition {
fn default() -> Self {
Self::center()
}
}
#[derive(Debug, Clone, Copy)]
pub struct WindowStyle {
pub closable: bool,
pub miniaturizable: bool,
pub resizable: bool,
pub borderless: bool,
}
impl Default for WindowStyle {
fn default() -> Self {
Self {
closable: true,
miniaturizable: true,
resizable: true,
borderless: false,
}
}
}
impl WindowStyle {
pub fn borderless() -> Self {
Self {
closable: false,
miniaturizable: false,
resizable: false,
borderless: true,
}
}
}
pub type WindowHandle = *mut std::ffi::c_void;
pub type ViewHandle = *mut std::ffi::c_void;
pub struct WindowBuilder {
title: String,
size: Extent,
position: WindowPosition,
style: WindowStyle,
min_size: Option<Extent>,
max_size: Option<Extent>,
}
impl WindowBuilder {
pub fn new(title: impl Into<String>, size: Extent) -> Self {
Self {
title: title.into(),
size,
position: WindowPosition::default(),
style: WindowStyle::default(),
min_size: None,
max_size: None,
}
}
pub fn position(mut self, pos: WindowPosition) -> Self {
self.position = pos;
self
}
pub fn style(mut self, style: WindowStyle) -> Self {
self.style = style;
self
}
pub fn min_size(mut self, size: Extent) -> Self {
self.min_size = Some(size);
self
}
pub fn max_size(mut self, size: Extent) -> Self {
self.max_size = Some(size);
self
}
pub fn build(self) -> Window {
Window::new_with_options(self)
}
}
pub struct Window {
title: String,
size: Extent,
position: WindowPosition,
style: WindowStyle,
view: View,
handle: Option<WindowHandle>,
#[cfg(target_os = "macos")]
macos_window: Option<MacOSWindow>,
#[cfg(target_os = "windows")]
windows_window: Option<WindowsWindow>,
#[cfg(target_os = "linux")]
linux_window: Option<LinuxWindow>,
}
impl Window {
pub fn new(title: impl Into<String>, size: Extent) -> Self {
let title_str = title.into();
#[cfg(target_os = "macos")]
let macos_window =
{ MainThreadMarker::new().map(|mtm| MacOSWindow::new(&title_str, size, mtm)) };
#[cfg(target_os = "windows")]
let windows_window = WindowsWindow::new(&title_str, size);
#[cfg(target_os = "linux")]
let linux_window = LinuxWindow::new(&title_str, size);
Self {
title: title_str,
size,
position: WindowPosition::default(),
style: WindowStyle::default(),
view: View::new(size),
handle: None,
#[cfg(target_os = "macos")]
macos_window,
#[cfg(target_os = "windows")]
windows_window,
#[cfg(target_os = "linux")]
linux_window,
}
}
fn new_with_options(builder: WindowBuilder) -> Self {
#[cfg(target_os = "macos")]
let macos_window = {
MainThreadMarker::new().map(|mtm| {
MacOSWindow::new_with_style(&builder.title, builder.size, builder.style, mtm)
})
};
#[cfg(target_os = "windows")]
let windows_window = WindowsWindow::new(&builder.title, builder.size);
#[cfg(target_os = "linux")]
let linux_window = LinuxWindow::new(&builder.title, builder.size);
Self {
title: builder.title,
size: builder.size,
position: builder.position,
style: builder.style,
view: View::new(builder.size),
handle: None,
#[cfg(target_os = "macos")]
macos_window,
#[cfg(target_os = "windows")]
windows_window,
#[cfg(target_os = "linux")]
linux_window,
}
}
pub fn title(&self) -> &str {
&self.title
}
pub fn set_title(&mut self, title: impl Into<String>) {
self.title = title.into();
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.set_title(&self.title);
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.set_title(&self.title);
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.set_title(&self.title);
}
}
pub fn size(&self) -> Extent {
self.size
}
pub fn set_size(&mut self, size: Extent) {
self.size = size;
self.view.set_size(size);
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.set_size(size);
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.set_size(size);
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.set_size(size);
}
}
pub fn position(&self) -> WindowPosition {
self.position
}
pub fn set_position(&mut self, pos: WindowPosition) {
self.position = pos;
}
pub fn view(&self) -> &View {
&self.view
}
pub fn view_mut(&mut self) -> &mut View {
&mut self.view
}
pub fn set_content(&mut self, content: ElementPtr) {
self.view.set_content(content.clone());
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.set_content(content.clone());
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.set_content(content.clone());
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.set_content(content);
}
}
pub fn on_focus(&self, callback: impl Fn() + 'static) {
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.on_focus(callback);
}
#[cfg(not(target_os = "macos"))]
let _ = callback;
}
pub fn show(&mut self) {
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.show();
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.show();
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.show();
}
}
pub fn hide(&mut self) {
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.hide();
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.hide();
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.hide();
}
}
pub fn close(&mut self) {
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.close();
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.close();
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.close();
}
}
pub fn is_visible(&self) -> bool {
true }
pub fn refresh(&self) {
self.view.refresh();
#[cfg(target_os = "macos")]
if let Some(ref win) = self.macos_window {
win.refresh();
}
#[cfg(target_os = "windows")]
if let Some(ref win) = self.windows_window {
win.refresh();
}
#[cfg(target_os = "linux")]
if let Some(ref win) = self.linux_window {
win.refresh();
}
}
pub fn handle(&self) -> Option<WindowHandle> {
#[cfg(target_os = "macos")]
{
self.macos_window
.as_ref()
.map(|window| window.native_window_handle())
}
#[cfg(target_os = "windows")]
{
self.windows_window
.as_ref()
.map(|window| window.native_window_handle())
}
#[cfg(target_os = "linux")]
{
self.linux_window
.as_ref()
.map(|window| window.native_window_handle())
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
self.handle
}
}
}
pub enum CloseBehavior {
QuitApp,
KeepRunning(Box<dyn Fn() -> Window>),
}
pub struct App {
running: bool,
#[cfg(target_os = "macos")]
macos_app: Option<MacOSApp>,
#[cfg(target_os = "windows")]
windows_app: Option<WindowsApp>,
#[cfg(target_os = "linux")]
linux_app: Option<LinuxApp>,
}
impl App {
pub fn new() -> Self {
#[cfg(target_os = "macos")]
{
Self {
running: false,
macos_app: MacOSApp::new(),
}
}
#[cfg(target_os = "windows")]
{
Self {
running: false,
windows_app: WindowsApp::new(),
}
}
#[cfg(target_os = "linux")]
{
Self {
running: false,
linux_app: LinuxApp::new(),
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
Self { running: false }
}
}
pub fn run(&mut self) {
self.running = true;
#[cfg(target_os = "macos")]
{
if let Some(ref app) = self.macos_app {
app.run();
}
}
#[cfg(target_os = "windows")]
{
if let Some(ref app) = self.windows_app {
app.run();
}
}
#[cfg(target_os = "linux")]
{
if let Some(ref mut app) = self.linux_app {
app.run();
}
}
}
pub fn stop(&mut self) {
self.running = false;
#[cfg(target_os = "macos")]
{
if let Some(ref app) = self.macos_app {
app.stop();
}
}
#[cfg(target_os = "windows")]
{
if let Some(ref app) = self.windows_app {
app.stop();
}
}
#[cfg(target_os = "linux")]
{
if let Some(ref mut app) = self.linux_app {
app.stop();
}
}
}
pub fn is_running(&self) -> bool {
self.running
}
#[cfg(target_os = "macos")]
pub fn main_thread_marker(&self) -> Option<MainThreadMarker> {
MainThreadMarker::new()
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
pub fn set_close_behavior(&self, behavior: CloseBehavior) {
#[cfg(target_os = "macos")]
if let Some(ref app) = self.macos_app {
app.set_close_behavior(behavior);
}
#[cfg(target_os = "windows")]
if let Some(ref app) = self.windows_app {
app.set_close_behavior(behavior);
}
#[cfg(target_os = "linux")]
if let Some(ref app) = self.linux_app {
app.set_close_behavior(behavior);
}
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
pub fn schedule_timer(&self, interval_secs: f64, callback: impl FnMut() + 'static) -> Timer {
#[cfg(target_os = "macos")]
let inner = self
.macos_app
.as_ref()
.expect("App::new should have created a MacOSApp")
.schedule_timer(interval_secs, true, callback);
#[cfg(target_os = "windows")]
let inner = self
.windows_app
.as_ref()
.expect("App::new should have created a WindowsApp")
.schedule_timer(interval_secs, true, callback);
#[cfg(target_os = "linux")]
let inner = self
.linux_app
.as_ref()
.expect("App::new should have created a LinuxApp")
.schedule_timer(interval_secs, true, callback);
Timer { inner }
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
pub fn schedule_once(&self, callback: impl FnOnce() + 'static) {
let mut callback = Some(callback);
#[cfg(target_os = "macos")]
let inner = self
.macos_app
.as_ref()
.expect("App::new should have created a MacOSApp")
.schedule_timer(0.0, false, move || {
if let Some(callback) = callback.take() {
callback();
}
});
#[cfg(target_os = "windows")]
let inner = self
.windows_app
.as_ref()
.expect("App::new should have created a WindowsApp")
.schedule_timer(0.0, false, move || {
if let Some(callback) = callback.take() {
callback();
}
});
#[cfg(target_os = "linux")]
let inner = self
.linux_app
.as_ref()
.expect("App::new should have created a LinuxApp")
.schedule_timer(0.0, false, move || {
if let Some(callback) = callback.take() {
callback();
}
});
std::mem::forget(inner);
}
}
#[cfg(target_os = "macos")]
pub struct Timer {
inner: objc2::rc::Retained<objc2_foundation::NSTimer>,
}
#[cfg(target_os = "windows")]
pub struct Timer {
inner: WindowsTimer,
}
#[cfg(target_os = "linux")]
pub struct Timer {
inner: LinuxTimer,
}
#[cfg(target_os = "macos")]
impl Timer {
pub fn cancel(&self) {
unsafe {
self.inner.invalidate();
}
}
}
#[cfg(target_os = "windows")]
impl Timer {
pub fn cancel(&self) {
self.inner.cancel();
}
}
#[cfg(target_os = "linux")]
impl Timer {
pub fn cancel(&self) {
self.inner.cancel();
}
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
impl Drop for Timer {
fn drop(&mut self) {
self.cancel();
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
pub enum PlatformError {
#[error("Failed to create window: {0}")]
WindowCreation(String),
#[error("Failed to initialize application: {0}")]
Initialization(String),
#[error("Platform error: {0}")]
Other(String),
}
pub type PlatformResult<T> = Result<T, PlatformError>;