Desktop: Add native file dialogs (#2939)

* Add native open file dialog

* Add native save file dialog

* Fix integer underflow in defer message handler

* Update nix flake

* Cleanup

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon
2025-08-04 13:58:33 +00:00
committed by GitHub
co-authored by Dennis Kobert
parent c98477d8ed
commit 7cb42b9523
15 changed files with 665 additions and 64 deletions
+58 -6
View File
@@ -1,5 +1,7 @@
use crate::CustomEvent;
use crate::WindowSize;
use crate::dialogs::dialog_open_graphite_file;
use crate::dialogs::dialog_save_graphite_file;
use crate::render::GraphicsState;
use crate::render::WgpuContext;
use graph_craft::wasm_application_io::WasmApplicationIo;
@@ -7,6 +9,7 @@ use graphite_editor::application::Editor;
use graphite_editor::messages::prelude::*;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use winit::application::ApplicationHandler;
@@ -15,23 +18,25 @@ use winit::event::StartCause;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow;
use winit::event_loop::EventLoopProxy;
use winit::window::Window;
use winit::window::WindowId;
use crate::cef;
pub(crate) struct WinitApp {
pub(crate) cef_context: cef::Context<cef::Initialized>,
pub(crate) window: Option<Arc<Window>>,
cef_context: cef::Context<cef::Initialized>,
window: Option<Arc<Window>>,
cef_schedule: Option<Instant>,
window_size_sender: Sender<WindowSize>,
graphics_state: Option<GraphicsState>,
wgpu_context: WgpuContext,
pub(crate) editor: Editor,
event_loop_proxy: EventLoopProxy<CustomEvent>,
editor: Editor,
}
impl WinitApp {
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext) -> Self {
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
Self {
cef_context,
window: None,
@@ -39,6 +44,7 @@ impl WinitApp {
graphics_state: None,
window_size_sender,
wgpu_context,
event_loop_proxy,
editor: Editor::new(),
}
}
@@ -57,6 +63,49 @@ impl WinitApp {
}
}
for _ in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerOpenDocument)) {
let event_loop_proxy = self.event_loop_proxy.clone();
let _ = thread::spawn(move || {
let path = futures::executor::block_on(dialog_open_graphite_file());
if let Some(path) = path {
let content = std::fs::read_to_string(&path).unwrap_or_else(|_| {
tracing::error!("Failed to read file: {}", path.display());
String::new()
});
let message = PortfolioMessage::OpenDocumentFile {
document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
document_serialized_content: content,
};
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
}
});
}
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveDocument { .. })) {
let FrontendMessage::TriggerSaveDocument { document_id, name, path, document } = message else {
unreachable!()
};
if let Some(path) = path {
let _ = std::fs::write(&path, document);
} else {
let event_loop_proxy = self.event_loop_proxy.clone();
let _ = thread::spawn(move || {
let path = futures::executor::block_on(dialog_save_graphite_file(name));
if let Some(path) = path {
if let Err(e) = std::fs::write(&path, document) {
tracing::error!("Failed to save file: {}: {}", path.display(), e);
} else {
let message = Message::Portfolio(PortfolioMessage::DocumentPassMessage {
document_id,
message: DocumentMessage::SavedDocument { path: Some(path) },
});
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message));
}
}
});
}
}
if responses.is_empty() {
return;
}
@@ -132,7 +181,10 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.cef_schedule = Some(instant);
}
}
CustomEvent::MessageReceived { message } => {
CustomEvent::DispatchMessage(message) => {
self.dispatch_message(message);
}
CustomEvent::MessageReceived(message) => {
if let Message::InputPreprocessor(_) = &message {
if let Some(window) = &self.window {
window.request_redraw();
@@ -155,7 +207,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.dispatch_message(message);
}
CustomEvent::NodeGraphRan { texture } => {
CustomEvent::NodeGraphRan(texture) => {
if let Some(texture) = texture
&& let Some(graphics_state) = &mut self.graphics_state
{