Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
use futures::future::LocalBoxFuture;
|
||||
|
||||
use crate::platform::app::TerminationResult;
|
||||
use crate::platform::test::FontDB as TestFontDB;
|
||||
use crate::{
|
||||
integration::TestDriver,
|
||||
platform::{self},
|
||||
AppContext, AssetProvider,
|
||||
};
|
||||
|
||||
use super::delegate::{self, AppDelegate};
|
||||
use super::event_loop::{self, AppEvent};
|
||||
use super::windowing::WindowManager;
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub struct App {
|
||||
callbacks: platform::app::AppCallbacks,
|
||||
assets: Box<dyn AssetProvider>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub(in crate::platform) fn new(
|
||||
callbacks: platform::app::AppCallbacks,
|
||||
assets: Box<dyn AssetProvider>,
|
||||
test_driver: Option<&TestDriver>,
|
||||
) -> Self {
|
||||
// Other platforms use the test_driver parameter to enable an alternative platform delegate implementation
|
||||
// in integration tests - that doesn't apply here.
|
||||
let _ = test_driver;
|
||||
Self { callbacks, assets }
|
||||
}
|
||||
|
||||
pub(in crate::platform) fn run(
|
||||
self,
|
||||
init_fn: impl FnOnce(&mut AppContext, LocalBoxFuture<'static, crate::App>) + 'static,
|
||||
) -> TerminationResult {
|
||||
let App { callbacks, assets } = self;
|
||||
|
||||
let (sender, receiver) = mpsc::channel::<AppEvent>();
|
||||
|
||||
// Mark this thread as the main thread for DispatchDelegate checks.
|
||||
delegate::mark_current_thread_as_main();
|
||||
|
||||
let platform_delegate = Box::new(AppDelegate::new(sender.clone()));
|
||||
let window_manager = Box::new(WindowManager::new(sender.clone()));
|
||||
// Reuse the testing FontDB implementation, as no font features are needed in headless mode.
|
||||
let font_db: Box<dyn platform::FontDB> = Box::new(TestFontDB::new());
|
||||
|
||||
let ui_app = crate::App::new(platform_delegate, window_manager, font_db, assets)
|
||||
.expect("should not fail to construct application");
|
||||
|
||||
let mut callbacks =
|
||||
galaxyui_core::platform::app::AppCallbackDispatcher::new(callbacks, ui_app.clone());
|
||||
|
||||
// Run the event loop until the app terminates.
|
||||
event_loop::run(ui_app, &mut callbacks, Box::new(init_fn), receiver, sender)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
clipboard::InMemoryClipboard,
|
||||
notification::{NotificationSendError, RequestPermissionsOutcome},
|
||||
platform::{self, Cursor},
|
||||
};
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::thread;
|
||||
|
||||
use super::event_loop::AppEvent;
|
||||
|
||||
/// Stores the ID of the application's main thread, which we can reference
|
||||
/// to determine if a given thread is the main thread or not.
|
||||
static MAIN_THREAD_ID: OnceLock<thread::ThreadId> = OnceLock::new();
|
||||
|
||||
/// Marks the current thread as the application's main thread.
|
||||
///
|
||||
/// Panics if called more than once.
|
||||
pub(super) fn mark_current_thread_as_main() {
|
||||
MAIN_THREAD_ID
|
||||
.set(thread::current().id())
|
||||
.expect("should only call mark_current_thread_as_main once!");
|
||||
}
|
||||
|
||||
pub struct AppDelegate {
|
||||
clipboard: InMemoryClipboard,
|
||||
cursor_shape: Mutex<Cursor>,
|
||||
event_sender: Sender<AppEvent>,
|
||||
}
|
||||
|
||||
impl AppDelegate {
|
||||
pub(super) fn new(event_sender: Sender<AppEvent>) -> Self {
|
||||
Self {
|
||||
clipboard: InMemoryClipboard::default(),
|
||||
cursor_shape: Mutex::new(Cursor::Arrow),
|
||||
event_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn send_event(&self, event: AppEvent) {
|
||||
if self.event_sender.send(event).is_err() {
|
||||
log::warn!("Tried to send event, but event loop is no longer running");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Delegate for AppDelegate {
|
||||
fn dispatch_delegate(&self) -> Arc<dyn platform::DispatchDelegate> {
|
||||
Arc::new(DispatchDelegate {
|
||||
event_sender: self.event_sender.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, _window_id: crate::WindowId) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn clipboard(&mut self) -> &mut dyn crate::Clipboard {
|
||||
&mut self.clipboard
|
||||
}
|
||||
|
||||
fn system_theme(&self) -> platform::SystemTheme {
|
||||
platform::SystemTheme::Light
|
||||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// Use macOS platform implementation
|
||||
crate::platform::mac::Window::open_url(url);
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// Reuse the winit implementation for non-mac platforms
|
||||
crate::windowing::winit::delegate::open_url_in_system(url);
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file_path(&self, _path: &std::path::Path) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn open_file_path_in_explorer(&self, _path: &std::path::Path) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn open_file_picker(
|
||||
&self,
|
||||
callback: platform::FilePickerCallback,
|
||||
_file_picker_config: platform::FilePickerConfiguration,
|
||||
) {
|
||||
self.send_event(AppEvent::RunCallback(Box::new(move |ctx| {
|
||||
callback(Ok(vec![]), ctx);
|
||||
})));
|
||||
}
|
||||
|
||||
fn open_save_file_picker(
|
||||
&self,
|
||||
callback: platform::SaveFilePickerCallback,
|
||||
_config: platform::SaveFilePickerConfiguration,
|
||||
) {
|
||||
self.send_event(AppEvent::RunCallback(Box::new(move |ctx| {
|
||||
callback(None, ctx);
|
||||
})));
|
||||
}
|
||||
|
||||
fn application_bundle_info(
|
||||
&self,
|
||||
_bundle_identifier: &str,
|
||||
) -> Option<crate::ApplicationBundleInfo<'_>> {
|
||||
// This is unsupported, though we could delegate to the macOS implementation.
|
||||
None
|
||||
}
|
||||
|
||||
fn show_native_platform_modal(
|
||||
&self,
|
||||
_id: crate::modals::ModalId,
|
||||
_modal: crate::modals::AlertDialog,
|
||||
) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn request_desktop_notification_permissions(
|
||||
&self,
|
||||
on_completion: platform::RequestNotificationPermissionsCallback,
|
||||
) {
|
||||
self.send_event(AppEvent::RunCallback(Box::new(move |ctx| {
|
||||
on_completion(RequestPermissionsOutcome::PermissionsDenied, ctx);
|
||||
})));
|
||||
}
|
||||
|
||||
fn send_desktop_notification(
|
||||
&self,
|
||||
_notification_content: crate::notification::UserNotification,
|
||||
_window_id: crate::WindowId,
|
||||
on_error: platform::SendNotificationErrorCallback,
|
||||
) {
|
||||
self.send_event(AppEvent::RunCallback(Box::new(move |ctx| {
|
||||
on_error(NotificationSendError::PermissionsDenied, ctx);
|
||||
})));
|
||||
}
|
||||
|
||||
fn set_cursor_shape(&self, cursor: Cursor) {
|
||||
*self.cursor_shape.lock() = cursor;
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-util")]
|
||||
fn get_cursor_shape(&self) -> Cursor {
|
||||
*self.cursor_shape.lock()
|
||||
}
|
||||
|
||||
fn close_ime_async(&self, _window_id: crate::WindowId) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn is_ime_open(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn open_character_palette(&self) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn set_accessibility_contents(&self, _content: crate::accessibility::AccessibilityContent) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn register_global_shortcut(&self, _shortcut: crate::keymap::Keystroke) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn unregister_global_shortcut(&self, _shortcut: &crate::keymap::Keystroke) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
fn terminate_app(&self, termination_mode: platform::TerminationMode) {
|
||||
self.send_event(AppEvent::Terminate(termination_mode));
|
||||
}
|
||||
|
||||
fn is_screen_reader_enabled(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
fn microphone_access_state(&self) -> platform::MicrophoneAccessState {
|
||||
platform::MicrophoneAccessState::Denied
|
||||
}
|
||||
|
||||
fn is_headless(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct DispatchDelegate {
|
||||
event_sender: Sender<AppEvent>,
|
||||
}
|
||||
|
||||
impl platform::DispatchDelegate for DispatchDelegate {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
thread::current().id()
|
||||
== *MAIN_THREAD_ID
|
||||
.get()
|
||||
.expect("should have marked a thread as the main thread")
|
||||
}
|
||||
|
||||
fn run_on_main_thread(&self, task: async_task::Runnable) {
|
||||
// See crate::windowing::winit::delegate::DispatchDelegate for why we use ManuallyDrop.
|
||||
if self
|
||||
.event_sender
|
||||
.send(AppEvent::RunTask(ManuallyDrop::new(task)))
|
||||
.is_err()
|
||||
{
|
||||
log::warn!("Tried to send event, but event loop is no longer running");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use crate::{
|
||||
platform::{
|
||||
self,
|
||||
app::{AppCallbackDispatcher, ApproveTerminateResult, TerminationResult},
|
||||
TerminationMode,
|
||||
},
|
||||
AppContext, WindowId,
|
||||
};
|
||||
|
||||
/// Application events handled on the headless platform's main thread.
|
||||
pub(super) enum AppEvent {
|
||||
/// Run the wrapped task on the main thread.
|
||||
RunTask(ManuallyDrop<async_task::Runnable>),
|
||||
/// Run a synchronous callback on the main thread.
|
||||
RunCallback(Box<dyn FnOnce(&mut AppContext) + Send + Sync>),
|
||||
/// Close a window.
|
||||
CloseWindow(WindowId),
|
||||
/// Active window changed.
|
||||
ActiveWindowChanged(Option<WindowId>),
|
||||
/// Exit the event loop, terminating the application.
|
||||
Terminate(TerminationMode),
|
||||
}
|
||||
|
||||
/// Run a simple, blocking event loop that processes AppEvent messages until termination.
|
||||
pub(super) fn run(
|
||||
mut ui_app: crate::App,
|
||||
callbacks: &mut AppCallbackDispatcher,
|
||||
init_fn: platform::app::AppInitCallbackFn,
|
||||
receiver: Receiver<AppEvent>,
|
||||
sender: Sender<AppEvent>,
|
||||
) -> TerminationResult {
|
||||
// Set up Ctrl-C handler to gracefully terminate the app
|
||||
setup_signal_handler(sender);
|
||||
|
||||
// First, initialize the app.
|
||||
callbacks.initialize_app(init_fn);
|
||||
|
||||
// Then, process events until termination.
|
||||
for event in receiver.iter() {
|
||||
match event {
|
||||
AppEvent::RunCallback(callback) => ui_app.update(callback),
|
||||
AppEvent::RunTask(task) => {
|
||||
// Poll a task on the main thread.
|
||||
let task = ManuallyDrop::into_inner(task);
|
||||
task.run();
|
||||
}
|
||||
AppEvent::Terminate(termination_mode) => {
|
||||
let should_terminate = match termination_mode {
|
||||
TerminationMode::Cancellable => {
|
||||
matches!(
|
||||
callbacks.should_terminate_app(),
|
||||
ApproveTerminateResult::Terminate
|
||||
)
|
||||
}
|
||||
TerminationMode::ForceTerminate | TerminationMode::ContentTransferred => true,
|
||||
};
|
||||
if should_terminate {
|
||||
break;
|
||||
}
|
||||
}
|
||||
AppEvent::CloseWindow(window_id) => {
|
||||
// Notify the app that a window is closing. The app will then remove the window
|
||||
// from WindowManager.
|
||||
callbacks.window_will_close(window_id);
|
||||
}
|
||||
AppEvent::ActiveWindowChanged(window_id) => {
|
||||
callbacks.active_window_changed(window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the receiver so the Ctrl+C signal handler's channel send will fail,
|
||||
// causing it to fall through to `process::exit(130)`. Without this, the
|
||||
// send succeeds (since the receiver is still in scope) but nobody is reading
|
||||
// from the channel, making Ctrl+C ineffective during shutdown.
|
||||
drop(receiver);
|
||||
|
||||
callbacks.app_will_terminate();
|
||||
|
||||
ui_app.termination_result().unwrap_or(Ok(()))
|
||||
}
|
||||
|
||||
/// Set up a signal handler for Ctrl-C (SIGINT) to gracefully terminate the app.
|
||||
///
|
||||
/// When Ctrl-C is received, this will send a Terminate event to the event loop,
|
||||
/// allowing the app to shut down gracefully via the existing termination logic.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn setup_signal_handler(sender: Sender<AppEvent>) {
|
||||
let result = ctrlc::set_handler(move || {
|
||||
log::info!("Received Ctrl-C signal in headless mode, terminating application");
|
||||
// Send a ForceTerminate event to ensure the app exits cleanly.
|
||||
// We use ForceTerminate rather than Cancellable to ensure the app exits
|
||||
// even if there are unsaved changes or other conditions that might prevent shutdown.
|
||||
if sender
|
||||
.send(AppEvent::Terminate(TerminationMode::ForceTerminate))
|
||||
.is_err()
|
||||
{
|
||||
log::warn!("Failed to send termination event - event loop may have already stopped");
|
||||
// If we can't send the event, force exit
|
||||
std::process::exit(130); // 128 + SIGINT (2) = 130
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = result {
|
||||
log::warn!("Failed to set up Ctrl-C handler: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
fn setup_signal_handler(_sender: Sender<AppEvent>) {
|
||||
// No signal handling on WASM
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//! A headless implementation of the UI framework's platform abstraction.
|
||||
//!
|
||||
//! This provides enough functionality to run an app, but no GUI or visible output.
|
||||
|
||||
mod app;
|
||||
mod delegate;
|
||||
mod event_loop;
|
||||
mod windowing;
|
||||
|
||||
pub use app::App;
|
||||
pub use delegate::AppDelegate;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) use windowing::Window;
|
||||
@@ -0,0 +1,269 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::mpsc};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
geometry::rect::RectF,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
platform::{self, WindowOptions},
|
||||
windowing::WindowCallbacks,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
use super::event_loop::AppEvent;
|
||||
|
||||
pub struct WindowManager {
|
||||
windows: HashMap<WindowId, Rc<Window>>,
|
||||
active_window: RefCell<Option<WindowId>>,
|
||||
event_sender: mpsc::Sender<AppEvent>,
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub(super) fn new(event_sender: mpsc::Sender<AppEvent>) -> Self {
|
||||
Self {
|
||||
windows: HashMap::new(),
|
||||
active_window: RefCell::new(None),
|
||||
event_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_active_window(&self, window_id: Option<WindowId>) {
|
||||
*self.active_window.borrow_mut() = window_id;
|
||||
|
||||
if self
|
||||
.event_sender
|
||||
.send(AppEvent::ActiveWindowChanged(window_id))
|
||||
.is_err()
|
||||
{
|
||||
log::warn!(
|
||||
"Tried to send ActiveWindowChanged event, but event loop is no longer running"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl galaxyui_core::platform::WindowManager for WindowManager {
|
||||
fn open_window(
|
||||
&mut self,
|
||||
window_id: WindowId,
|
||||
window_options: WindowOptions,
|
||||
callbacks: WindowCallbacks,
|
||||
) -> Result<()> {
|
||||
let window = Rc::new(Window::new(window_options, callbacks));
|
||||
self.windows.insert(window_id, window);
|
||||
self.set_active_window(Some(window_id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn platform_window(&self, window_id: WindowId) -> galaxyui_core::OptionalPlatformWindow {
|
||||
self.windows
|
||||
.get(&window_id)
|
||||
.map(Rc::clone)
|
||||
.map(|inner| inner as Rc<dyn crate::platform::Window>)
|
||||
}
|
||||
|
||||
fn remove_window(&mut self, window_id: WindowId) {
|
||||
self.windows.remove(&window_id);
|
||||
if *self.active_window.borrow() == Some(window_id) {
|
||||
self.set_active_window(None);
|
||||
}
|
||||
}
|
||||
|
||||
fn active_window_id(&self) -> Option<WindowId> {
|
||||
*self.active_window.borrow()
|
||||
}
|
||||
|
||||
fn key_window_is_modal_panel(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn app_is_active(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn activate_app(&self, last_active_window: Option<WindowId>) -> Option<WindowId> {
|
||||
self.set_active_window(last_active_window);
|
||||
last_active_window
|
||||
}
|
||||
|
||||
fn show_window_and_focus_app(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
_behavior: platform::WindowFocusBehavior,
|
||||
) {
|
||||
self.set_active_window(Some(window_id));
|
||||
}
|
||||
|
||||
fn hide_app(&self) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
fn hide_window(&self, window_id: WindowId) {
|
||||
// If hiding the active window, clear focus.
|
||||
if *self.active_window.borrow() == Some(window_id) {
|
||||
self.set_active_window(None);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_window_bounds(&self, window_id: WindowId, bound: RectF) {
|
||||
if let Some(window) = self.windows.get(&window_id) {
|
||||
window.set_bounds(bound);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_all_windows_background_blur_radius(&self, _blur_radius_pixels: u8) {
|
||||
// No-op for headless.
|
||||
}
|
||||
|
||||
fn set_all_windows_background_blur_texture(&self, _use_blur_texture: bool) {
|
||||
// No-op for headless.
|
||||
}
|
||||
|
||||
fn set_window_title(&self, _window_id: WindowId, _title: &str) {
|
||||
// No-op for headless.
|
||||
}
|
||||
|
||||
fn close_window_async(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
_termination_mode: platform::TerminationMode,
|
||||
) {
|
||||
// In headless mode, always force-close the window since there's no confirmation dialog.
|
||||
if self
|
||||
.event_sender
|
||||
.send(AppEvent::CloseWindow(window_id))
|
||||
.is_err()
|
||||
{
|
||||
log::warn!("Tried to send event, but event loop is no longer running");
|
||||
}
|
||||
}
|
||||
|
||||
fn active_display_bounds(&self) -> RectF {
|
||||
// A single default display.
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn active_display_id(&self) -> crate::DisplayId {
|
||||
crate::DisplayId::from(0)
|
||||
}
|
||||
|
||||
fn display_count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn bounds_for_display_idx(&self, _idx: crate::DisplayIdx) -> Option<RectF> {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn active_cursor_position_updated(&self) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
fn windowing_system(&self) -> Option<crate::windowing::System> {
|
||||
None
|
||||
}
|
||||
|
||||
fn os_window_manager_name(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_tiling_window_manager(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
callbacks: WindowCallbacks,
|
||||
bounds: RefCell<RectF>,
|
||||
fullscreen_state: RefCell<platform::FullscreenState>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
fn new(options: WindowOptions, callbacks: WindowCallbacks) -> Self {
|
||||
let bounds = match options.bounds {
|
||||
platform::WindowBounds::Default => RectF::new(vec2f(0.0, 0.0), vec2f(1024.0, 768.0)),
|
||||
platform::WindowBounds::ExactSize(size) => RectF::new(vec2f(0.0, 0.0), size),
|
||||
platform::WindowBounds::ExactPosition(rect) => rect,
|
||||
};
|
||||
Self {
|
||||
callbacks,
|
||||
bounds: RefCell::new(bounds),
|
||||
fullscreen_state: RefCell::new(options.fullscreen_state),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_bounds(&self, rect: RectF) {
|
||||
*self.bounds.borrow_mut() = rect;
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Window for Window {
|
||||
fn minimize(&self) {}
|
||||
|
||||
fn toggle_maximized(&self) {}
|
||||
|
||||
fn toggle_fullscreen(&self) {}
|
||||
|
||||
fn fullscreen_state(&self) -> platform::FullscreenState {
|
||||
*self.fullscreen_state.borrow()
|
||||
}
|
||||
|
||||
fn set_titlebar_height(&self, _height: f64) {}
|
||||
|
||||
fn supports_transparency(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn graphics_backend(&self) -> platform::GraphicsBackend {
|
||||
platform::GraphicsBackend::Empty
|
||||
}
|
||||
|
||||
fn supported_backends(&self) -> Vec<platform::GraphicsBackend> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn uses_native_window_decorations(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn as_ctx(&self) -> &dyn platform::WindowContext {
|
||||
self
|
||||
}
|
||||
|
||||
fn callbacks(&self) -> &crate::windowing::WindowCallbacks {
|
||||
&self.callbacks
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::WindowContext for Window {
|
||||
fn size(&self) -> Vector2F {
|
||||
self.bounds.borrow().size()
|
||||
}
|
||||
|
||||
fn origin(&self) -> Vector2F {
|
||||
self.bounds.borrow().origin()
|
||||
}
|
||||
|
||||
fn backing_scale_factor(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn max_texture_dimension_2d(&self) -> Option<u32> {
|
||||
Some(2048)
|
||||
}
|
||||
|
||||
fn render_scene(&self, _scene: Rc<crate::Scene>) {}
|
||||
|
||||
fn request_redraw(&self) {}
|
||||
|
||||
fn request_frame_capture(
|
||||
&self,
|
||||
_callback: Box<dyn FnOnce(platform::CapturedFrame) + Send + 'static>,
|
||||
) {
|
||||
// no-op for headless
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user