flatland3-gfx 0.2.8

Flatland3 Macroquad + egui graphical play client
//! Persist gfx window size/position in `~/.config/flatland/client.json`.

use flatland_client_lib::{ClientConfig, GfxWindowPrefs};
use macroquad::prelude::Conf;

const DEFAULT_WIDTH: i32 = 1400;
const DEFAULT_HEIGHT: i32 = 900;
const MIN_WIDTH: u32 = 640;
const MIN_HEIGHT: u32 = 480;

pub fn window_conf() -> Conf {
    let prefs = ClientConfig::load().gfx_window;
    let (width, height) = prefs
        .width
        .zip(prefs.height)
        .map(|(w, h)| (w as i32, h as i32))
        .unwrap_or((DEFAULT_WIDTH, DEFAULT_HEIGHT));
    Conf {
        window_title: format!("Flatland3 gfx v{}", env!("CARGO_PKG_VERSION")),
        window_width: width.max(MIN_WIDTH as i32),
        window_height: height.max(MIN_HEIGHT as i32),
        window_resizable: true,
        high_dpi: false,
        ..Default::default()
    }
}

/// Apply saved position after the window exists (size comes from [`window_conf`]).
pub fn apply_saved_position() {
    let prefs = ClientConfig::load().gfx_window;
    let (Some(x), Some(y)) = (prefs.x, prefs.y) else {
        return;
    };
    set_window_position(x, y);
}

pub fn capture_window_geometry() -> GfxWindowPrefs {
    let width = macroquad::window::screen_width().round().max(MIN_WIDTH as f32) as u32;
    let height = macroquad::window::screen_height().round().max(MIN_HEIGHT as f32) as u32;
    let (x, y) = window_position();
    GfxWindowPrefs {
        width: Some(width),
        height: Some(height),
        x,
        y,
    }
}

pub fn save_window_geometry(prefs: GfxWindowPrefs) {
    if prefs.is_empty() {
        return;
    }
    let mut cfg = ClientConfig::load();
    if cfg.save_gfx_window(prefs).is_err() {
        tracing::warn!("failed to save gfx window geometry");
    }
}

fn set_window_position(x: u32, y: u32) {
    #[cfg(target_os = "macos")]
    macos::set_frame_origin(x, y);

    #[cfg(any(target_os = "windows", target_os = "linux"))]
    miniquad::window::set_window_position(x, y);

    #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
    let _ = (x, y);
}

fn window_position() -> (Option<u32>, Option<u32>) {
    #[cfg(target_os = "macos")]
    {
        return macos::frame_origin();
    }
    #[cfg(any(target_os = "windows", target_os = "linux"))]
    {
        let (x, y) = miniquad::window::get_window_position();
        return (Some(x), Some(y));
    }
    #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
    {
        (None, None)
    }
}

#[cfg(target_os = "macos")]
#[allow(deprecated)]
mod macos {
    use cocoa::appkit::NSApp;
    use cocoa::base::{id, nil};
    use cocoa::foundation::NSRect;
    use objc::{msg_send, sel};

    pub fn frame_origin() -> (Option<u32>, Option<u32>) {
        unsafe {
            let Some(window) = main_window() else {
                return (None, None);
            };
            let frame: NSRect = msg_send![window, frame];
            (
                Some(frame.origin.x.max(0.0) as u32),
                Some(frame.origin.y.max(0.0) as u32),
            )
        }
    }

    pub fn set_frame_origin(x: u32, y: u32) {
        unsafe {
            let Some(window) = main_window() else {
                return;
            };
            let mut frame: NSRect = msg_send![window, frame];
            frame.origin.x = x as f64;
            frame.origin.y = y as f64;
            let _: () = msg_send![window, setFrameOrigin: frame.origin];
        }
    }

    unsafe fn main_window() -> Option<id> {
        let app: id = NSApp();
        if app == nil {
            return None;
        }
        let window: id = msg_send![app, mainWindow];
        if window == nil {
            return None;
        }
        Some(window)
    }
}