11use crate :: CustomEvent ;
22use crate :: WindowSize ;
33use crate :: consts:: APP_NAME ;
4- use crate :: dialogs:: dialog_open_graphite_file;
5- use crate :: dialogs:: dialog_save_file;
6- use crate :: dialogs:: dialog_save_graphite_file;
4+ use crate :: desktop_wrapper:: DesktopWrapper ;
5+ use crate :: desktop_wrapper:: NodeGraphExecutionResult ;
6+ use crate :: desktop_wrapper:: WgpuContext ;
7+ use crate :: desktop_wrapper:: messages:: DesktopFrontendMessage ;
8+ use crate :: desktop_wrapper:: messages:: DesktopWrapperMessage ;
9+ use crate :: desktop_wrapper:: serialize_frontend_messages;
710use crate :: render:: GraphicsState ;
8- use crate :: render:: WgpuContext ;
9- use graph_craft:: wasm_application_io:: WasmApplicationIo ;
10- use graphene_std:: Color ;
11- use graphene_std:: raster:: Image ;
12- use graphite_editor:: application:: Editor ;
13- use graphite_editor:: messages:: prelude:: * ;
14- use std:: fs;
11+ use rfd:: AsyncFileDialog ;
1512use std:: sync:: Arc ;
1613use std:: sync:: mpsc:: Sender ;
1714use std:: thread;
@@ -37,11 +34,12 @@ pub(crate) struct WinitApp {
3734 graphics_state : Option < GraphicsState > ,
3835 wgpu_context : WgpuContext ,
3936 event_loop_proxy : EventLoopProxy < CustomEvent > ,
40- editor : Editor ,
37+ desktop_wrapper : DesktopWrapper ,
4138}
4239
4340impl WinitApp {
4441 pub ( crate ) fn new ( cef_context : cef:: Context < cef:: Initialized > , window_size_sender : Sender < WindowSize > , wgpu_context : WgpuContext , event_loop_proxy : EventLoopProxy < CustomEvent > ) -> Self {
42+ let desktop_wrapper = DesktopWrapper :: new ( ) ;
4543 Self {
4644 cef_context,
4745 window : None ,
@@ -50,97 +48,106 @@ impl WinitApp {
5048 window_size_sender,
5149 wgpu_context,
5250 event_loop_proxy,
53- editor : Editor :: new ( ) ,
51+ desktop_wrapper ,
5452 }
5553 }
5654
57- fn dispatch_message ( & mut self , message : Message ) {
58- let responses = self . editor . handle_message ( message) ;
59- self . send_messages_to_editor ( responses) ;
60- }
61-
62- fn send_messages_to_editor ( & mut self , mut responses : Vec < FrontendMessage > ) {
63- for message in responses. extract_if ( .., |m| matches ! ( m, FrontendMessage :: RenderOverlays { .. } ) ) {
64- let FrontendMessage :: RenderOverlays { context : overlay_context } = message else { unreachable ! ( ) } ;
65- if let Some ( graphics_state) = & mut self . graphics_state {
66- let scene = overlay_context. take_scene ( ) ;
67- graphics_state. set_overlays_scene ( scene) ;
55+ fn handle_desktop_frontend_message ( & mut self , message : DesktopFrontendMessage ) {
56+ match message {
57+ DesktopFrontendMessage :: ToWeb ( messages) => {
58+ let Some ( bytes) = serialize_frontend_messages ( messages) else {
59+ tracing:: error!( "Failed to serialize frontend messages" ) ;
60+ return ;
61+ } ;
62+ self . cef_context . send_web_message ( bytes. as_slice ( ) ) ;
6863 }
69- }
70-
71- for _ in responses. extract_if ( .., |m| matches ! ( m, FrontendMessage :: TriggerOpenDocument ) ) {
72- let event_loop_proxy = self . event_loop_proxy . clone ( ) ;
73- let _ = thread:: spawn ( move || {
74- let path = futures:: executor:: block_on ( dialog_open_graphite_file ( ) ) ;
75- if let Some ( path) = path {
76- let content = std:: fs:: read_to_string ( & path) . unwrap_or_else ( |_| {
77- tracing:: error!( "Failed to read file: {}" , path. display( ) ) ;
78- String :: new ( )
79- } ) ;
80- let message = PortfolioMessage :: OpenDocumentFile {
81- document_name : None ,
82- document_path : Some ( path) ,
83- document_serialized_content : content,
84- } ;
85- let _ = event_loop_proxy. send_event ( CustomEvent :: DispatchMessage ( message. into ( ) ) ) ;
86- }
87- } ) ;
88- }
89-
90- for message in responses. extract_if ( .., |m| matches ! ( m, FrontendMessage :: TriggerSaveDocument { .. } ) ) {
91- let FrontendMessage :: TriggerSaveDocument { document_id, name, path, content } = message else {
92- unreachable ! ( )
93- } ;
94- if let Some ( path) = path {
95- let _ = std:: fs:: write ( & path, content) ;
96- } else {
64+ DesktopFrontendMessage :: OpenFileDialog { title, filters, context } => {
9765 let event_loop_proxy = self . event_loop_proxy . clone ( ) ;
9866 let _ = thread:: spawn ( move || {
99- let path = futures:: executor:: block_on ( dialog_save_graphite_file ( name) ) ;
100- if let Some ( path) = path {
101- if let Err ( e) = std:: fs:: write ( & path, content) {
102- tracing:: error!( "Failed to save file: {}: {}" , path. display( ) , e) ;
103- } else {
104- let message = Message :: Portfolio ( PortfolioMessage :: DocumentPassMessage {
105- document_id,
106- message : DocumentMessage :: SavedDocument { path : Some ( path) } ,
107- } ) ;
108- let _ = event_loop_proxy. send_event ( CustomEvent :: DispatchMessage ( message) ) ;
109- }
67+ let mut dialog = AsyncFileDialog :: new ( ) . set_title ( title) ;
68+ for filter in filters {
69+ dialog = dialog. add_filter ( filter. name , & filter. extensions ) ;
70+ }
71+
72+ let show_dialog = async move { dialog. pick_file ( ) . await . map ( |f| f. path ( ) . to_path_buf ( ) ) } ;
73+
74+ if let Some ( path) = futures:: executor:: block_on ( show_dialog)
75+ && let Ok ( content) = std:: fs:: read ( & path)
76+ {
77+ let message = DesktopWrapperMessage :: OpenFileDialogResult { path, content, context } ;
78+ let _ = event_loop_proxy. send_event ( CustomEvent :: DesktopWrapperMessage ( message) ) ;
11079 }
11180 } ) ;
11281 }
113- }
82+ DesktopFrontendMessage :: SaveFileDialog {
83+ title,
84+ default_filename,
85+ default_folder,
86+ filters,
87+ context,
88+ } => {
89+ let event_loop_proxy = self . event_loop_proxy . clone ( ) ;
90+ let _ = thread:: spawn ( move || {
91+ let mut dialog = AsyncFileDialog :: new ( ) . set_title ( title) . set_file_name ( default_filename) ;
92+ if let Some ( folder) = default_folder {
93+ dialog = dialog. set_directory ( folder) ;
94+ }
95+ for filter in filters {
96+ dialog = dialog. add_filter ( filter. name , & filter. extensions ) ;
97+ }
98+
99+ let show_dialog = async move { dialog. save_file ( ) . await . map ( |f| f. path ( ) . to_path_buf ( ) ) } ;
114100
115- for message in responses. extract_if ( .., |m| matches ! ( m, FrontendMessage :: TriggerSaveFile { .. } ) ) {
116- let FrontendMessage :: TriggerSaveFile { name, content } = message else { unreachable ! ( ) } ;
117- let _ = thread:: spawn ( move || {
118- let path = futures:: executor:: block_on ( dialog_save_file ( name) ) ;
119- if let Some ( path) = path {
120- if let Err ( e) = std:: fs:: write ( & path, content) {
121- tracing:: error!( "Failed to save file: {}: {}" , path. display( ) , e) ;
101+ if let Some ( path) = futures:: executor:: block_on ( show_dialog) {
102+ let message = DesktopWrapperMessage :: SaveFileDialogResult { path, context } ;
103+ let _ = event_loop_proxy. send_event ( CustomEvent :: DesktopWrapperMessage ( message) ) ;
122104 }
105+ } ) ;
106+ }
107+ DesktopFrontendMessage :: WriteFile { path, content } => {
108+ if let Err ( e) = std:: fs:: write ( & path, content) {
109+ tracing:: error!( "Failed to write file {}: {}" , path. display( ) , e) ;
123110 }
124- } ) ;
125- }
111+ }
112+ DesktopFrontendMessage :: OpenUrl ( url) => {
113+ let _ = thread:: spawn ( move || {
114+ if let Err ( e) = open:: that ( & url) {
115+ tracing:: error!( "Failed to open URL: {}: {}" , url, e) ;
116+ }
117+ } ) ;
118+ }
119+ DesktopFrontendMessage :: UpdateViewportBounds { x, y, width, height } => {
120+ if let Some ( graphics_state) = & mut self . graphics_state
121+ && let Some ( window) = & self . window
122+ {
123+ let window_size = window. inner_size ( ) ;
126124
127- for message in responses. extract_if ( .., |m| matches ! ( m, FrontendMessage :: TriggerVisitLink { .. } ) ) {
128- let _ = thread:: spawn ( move || {
129- let FrontendMessage :: TriggerVisitLink { url } = message else { unreachable ! ( ) } ;
130- if let Err ( e) = open:: that ( & url) {
131- tracing:: error!( "Failed to open URL: {}: {}" , url, e) ;
125+ let viewport_offset_x = x / window_size. width as f32 ;
126+ let viewport_offset_y = y / window_size. height as f32 ;
127+ graphics_state. set_viewport_offset ( [ viewport_offset_x, viewport_offset_y] ) ;
128+
129+ let viewport_scale_x = if width != 0.0 { window_size. width as f32 / width } else { 1.0 } ;
130+ let viewport_scale_y = if height != 0.0 { window_size. height as f32 / height } else { 1.0 } ;
131+ graphics_state. set_viewport_scale ( [ viewport_scale_x, viewport_scale_y] ) ;
132+ }
133+ }
134+ DesktopFrontendMessage :: UpdateOverlays ( scene) => {
135+ if let Some ( graphics_state) = & mut self . graphics_state {
136+ graphics_state. set_overlays_scene ( scene) ;
132137 }
133- } ) ;
138+ }
134139 }
140+ }
135141
136- if responses. is_empty ( ) {
137- return ;
142+ fn handle_desktop_frontend_messages ( & mut self , messages : Vec < DesktopFrontendMessage > ) {
143+ for message in messages {
144+ self . handle_desktop_frontend_message ( message) ;
138145 }
139- let Ok ( message ) = ron :: to_string ( & responses ) else {
140- tracing :: error! ( "Failed to serialize Messages" ) ;
141- return ;
142- } ;
143- self . cef_context . send_web_message ( message . as_bytes ( ) ) ;
146+ }
147+
148+ fn dispatch_desktop_wrapper_message ( & mut self , message : DesktopWrapperMessage ) {
149+ let responses = self . desktop_wrapper . dispatch ( message ) ;
150+ self . handle_desktop_frontend_messages ( responses ) ;
144151 }
145152}
146153
@@ -194,13 +201,25 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
194201
195202 tracing:: info!( "Winit window created and ready" ) ;
196203
197- let application_io = WasmApplicationIo :: new_with_context ( self . wgpu_context . clone ( ) ) ;
198-
199- futures:: executor:: block_on ( graphite_editor:: node_graph_executor:: replace_application_io ( application_io) ) ;
204+ self . desktop_wrapper . init ( self . wgpu_context . clone ( ) ) ;
200205 }
201206
202207 fn user_event ( & mut self , _: & ActiveEventLoop , event : CustomEvent ) {
203208 match event {
209+ CustomEvent :: DesktopWrapperMessage ( message) => self . dispatch_desktop_wrapper_message ( message) ,
210+ CustomEvent :: NodeGraphExecutionResult ( result) => match result {
211+ NodeGraphExecutionResult :: HasRun ( texture) => {
212+ self . dispatch_desktop_wrapper_message ( DesktopWrapperMessage :: PollNodeGraphEvaluation ) ;
213+ if let Some ( texture) = texture
214+ && let Some ( graphics_state) = self . graphics_state . as_mut ( )
215+ && let Some ( window) = self . window . as_ref ( )
216+ {
217+ graphics_state. bind_viewport_texture ( texture) ;
218+ window. request_redraw ( ) ;
219+ }
220+ }
221+ NodeGraphExecutionResult :: NotRun => { }
222+ } ,
204223 CustomEvent :: UiUpdate ( texture) => {
205224 if let Some ( graphics_state) = self . graphics_state . as_mut ( ) {
206225 graphics_state. resize ( texture. width ( ) , texture. height ( ) ) ;
@@ -217,127 +236,13 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
217236 self . cef_schedule = Some ( instant) ;
218237 }
219238 }
220- CustomEvent :: DispatchMessage ( message) => {
221- self . dispatch_message ( message) ;
222- }
223- CustomEvent :: MessageReceived ( message) => {
224- if let Message :: InputPreprocessor ( _) = & message {
225- if let Some ( window) = & self . window {
226- window. request_redraw ( ) ;
227- }
228- }
229- if let Message :: InputPreprocessor ( InputPreprocessorMessage :: BoundsOfViewports { bounds_of_viewports } ) = & message {
230- if let Some ( graphic_state) = & mut self . graphics_state {
231- let window_size = self . window . as_ref ( ) . unwrap ( ) . inner_size ( ) ;
232- let window_size = glam:: Vec2 :: new ( window_size. width as f32 , window_size. height as f32 ) ;
233- let top_left = bounds_of_viewports[ 0 ] . top_left . as_vec2 ( ) / window_size;
234- let bottom_right = bounds_of_viewports[ 0 ] . bottom_right . as_vec2 ( ) / window_size;
235- let offset = top_left. to_array ( ) ;
236- let scale = ( bottom_right - top_left) . recip ( ) ;
237- graphic_state. set_viewport_offset ( offset) ;
238- graphic_state. set_viewport_scale ( scale. to_array ( ) ) ;
239- } else {
240- panic ! ( "graphics state not intialized, viewport offset might be lost" ) ;
241- }
242- }
243-
244- self . dispatch_message ( message) ;
245- }
246- CustomEvent :: NodeGraphRan ( texture) => {
247- if let Some ( texture) = texture
248- && let Some ( graphics_state) = & mut self . graphics_state
249- {
250- graphics_state. bind_viewport_texture ( texture) ;
251- }
252- let mut responses = VecDeque :: new ( ) ;
253- let err = self . editor . poll_node_graph_evaluation ( & mut responses) ;
254- if let Err ( e) = err {
255- if e != "No active document" {
256- tracing:: error!( "Error poling node graph: {}" , e) ;
257- }
258- }
259-
260- for message in responses {
261- self . dispatch_message ( message) ;
262- }
263- }
264239 }
265240 }
266241
267242 fn window_event ( & mut self , event_loop : & ActiveEventLoop , _window_id : WindowId , event : WindowEvent ) {
268243 let Some ( event) = self . cef_context . handle_window_event ( event) else { return } ;
269244
270245 match event {
271- // Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
272- WindowEvent :: DroppedFile ( path) => {
273- let name = path. file_stem ( ) . and_then ( |s| s. to_str ( ) ) . map ( |s| s. to_string ( ) ) ;
274- let Some ( extension) = path. extension ( ) . and_then ( |s| s. to_str ( ) ) else {
275- tracing:: warn!( "Unsupported file dropped: {}" , path. display( ) ) ;
276- // Fine to early return since we don't need to do cef work in this case
277- return ;
278- } ;
279- let load_string = |path : & std:: path:: PathBuf | {
280- let Ok ( content) = fs:: read_to_string ( path) else {
281- tracing:: error!( "Failed to read file: {}" , path. display( ) ) ;
282- return None ;
283- } ;
284-
285- if content. is_empty ( ) {
286- tracing:: warn!( "Dropped file is empty: {}" , path. display( ) ) ;
287- return None ;
288- }
289- Some ( content)
290- } ;
291- // TODO: Consider moving this logic to the editor so we have one message to load data which is then demultiplexed in the portfolio message handler
292- match extension {
293- "graphite" => {
294- let Some ( content) = load_string ( & path) else { return } ;
295-
296- let message = PortfolioMessage :: OpenDocumentFile {
297- document_name : None ,
298- document_path : Some ( path) ,
299- document_serialized_content : content,
300- } ;
301- self . dispatch_message ( message. into ( ) ) ;
302- }
303- "svg" => {
304- let Some ( content) = load_string ( & path) else { return } ;
305-
306- let message = PortfolioMessage :: PasteSvg {
307- name : path. file_stem ( ) . map ( |s| s. to_string_lossy ( ) . to_string ( ) ) ,
308- svg : content,
309- mouse : None ,
310- parent_and_insert_index : None ,
311- } ;
312- self . dispatch_message ( message. into ( ) ) ;
313- }
314- _ => match image:: ImageReader :: open ( & path) {
315- Ok ( reader) => match reader. decode ( ) {
316- Ok ( image) => {
317- let width = image. width ( ) ;
318- let height = image. height ( ) ;
319- // TODO: support loading images with more than 8 bits per channel
320- let image_data = image. to_rgba8 ( ) ;
321- let image = Image :: < Color > :: from_image_data ( image_data. as_raw ( ) , width, height) ;
322-
323- let message = PortfolioMessage :: PasteImage {
324- name,
325- image,
326- mouse : None ,
327- parent_and_insert_index : None ,
328- } ;
329- self . dispatch_message ( message. into ( ) ) ;
330- }
331- Err ( e) => {
332- tracing:: error!( "Failed to decode image: {}: {}" , path. display( ) , e) ;
333- }
334- } ,
335- Err ( e) => {
336- tracing:: error!( "Failed to open image file: {}: {}" , path. display( ) , e) ;
337- }
338- } ,
339- }
340- }
341246 WindowEvent :: CloseRequested => {
342247 tracing:: info!( "The close button was pressed; stopping" ) ;
343248 event_loop. exit ( ) ;
@@ -362,6 +267,19 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
362267 Err ( e) => tracing:: error!( "{:?}" , e) ,
363268 }
364269 }
270+ // Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
271+ WindowEvent :: DroppedFile ( path) => {
272+ match std:: fs:: read ( & path) {
273+ Ok ( content) => {
274+ let message = DesktopWrapperMessage :: OpenFile { path, content } ;
275+ let _ = self . event_loop_proxy . send_event ( CustomEvent :: DesktopWrapperMessage ( message) ) ;
276+ }
277+ Err ( e) => {
278+ tracing:: error!( "Failed to read dropped file {}: {}" , path. display( ) , e) ;
279+ return ;
280+ }
281+ } ;
282+ }
365283 _ => { }
366284 }
367285
0 commit comments