mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
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:
@@ -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
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ impl CefEventHandler for CefHandler {
|
||||
let str = std::str::from_utf8(message).unwrap();
|
||||
match ron::from_str(str) {
|
||||
Ok(message) => {
|
||||
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message });
|
||||
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived(message));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to deserialize message {:?}", e)
|
||||
|
||||
22
desktop/src/dialogs.rs
Normal file
22
desktop/src/dialogs.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rfd::AsyncFileDialog;
|
||||
|
||||
pub(crate) async fn dialog_open_graphite_file() -> Option<PathBuf> {
|
||||
AsyncFileDialog::new()
|
||||
.add_filter("Graphite", &["graphite"])
|
||||
.set_title("Open Graphite Document")
|
||||
.pick_file()
|
||||
.await
|
||||
.map(|f| f.path().to_path_buf())
|
||||
}
|
||||
|
||||
pub(crate) async fn dialog_save_graphite_file(name: String) -> Option<PathBuf> {
|
||||
AsyncFileDialog::new()
|
||||
.add_filter("Graphite", &["graphite"])
|
||||
.set_title("Save Graphite Document")
|
||||
.set_file_name(name)
|
||||
.save_file()
|
||||
.await
|
||||
.map(|f| f.path().to_path_buf())
|
||||
}
|
||||
@@ -17,12 +17,15 @@ use app::WinitApp;
|
||||
|
||||
mod dirs;
|
||||
|
||||
mod dialogs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CustomEvent {
|
||||
UiUpdate(wgpu::Texture),
|
||||
ScheduleBrowserWork(Instant),
|
||||
MessageReceived { message: Message },
|
||||
NodeGraphRan { texture: Option<wgpu::Texture> },
|
||||
DispatchMessage(Message),
|
||||
MessageReceived(Message),
|
||||
NodeGraphRan(Option<wgpu::Texture>),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -63,9 +66,7 @@ fn main() {
|
||||
let last_render = Instant::now();
|
||||
let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph());
|
||||
if has_run {
|
||||
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan {
|
||||
texture: texture.map(|t| (*t.texture).clone()),
|
||||
});
|
||||
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan(texture.map(|t| (*t.texture).clone())));
|
||||
}
|
||||
let frame_time = Duration::from_secs_f32((target_fps as f32).recip());
|
||||
let sleep = last_render + frame_time - Instant::now();
|
||||
@@ -73,7 +74,7 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
|
||||
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context, event_loop.create_proxy());
|
||||
|
||||
event_loop.run_app(&mut winit_app).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user