1
1
use crate :: CustomEvent ;
2
2
use crate :: WindowSize ;
3
3
use 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;
7
10
use 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 ;
15
12
use std:: sync:: Arc ;
16
13
use std:: sync:: mpsc:: Sender ;
17
14
use std:: thread;
@@ -37,11 +34,12 @@ pub(crate) struct WinitApp {
37
34
graphics_state : Option < GraphicsState > ,
38
35
wgpu_context : WgpuContext ,
39
36
event_loop_proxy : EventLoopProxy < CustomEvent > ,
40
- editor : Editor ,
37
+ desktop_wrapper : DesktopWrapper ,
41
38
}
42
39
43
40
impl WinitApp {
44
41
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 ( ) ;
45
43
Self {
46
44
cef_context,
47
45
window : None ,
@@ -50,97 +48,106 @@ impl WinitApp {
50
48
window_size_sender,
51
49
wgpu_context,
52
50
event_loop_proxy,
53
- editor : Editor :: new ( ) ,
51
+ desktop_wrapper ,
54
52
}
55
53
}
56
54
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 ( ) ) ;
68
63
}
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 } => {
97
65
let event_loop_proxy = self . event_loop_proxy . clone ( ) ;
98
66
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) ) ;
110
79
}
111
80
} ) ;
112
81
}
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 ( ) ) } ;
114
100
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) ) ;
122
104
}
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) ;
123
110
}
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 ( ) ;
126
124
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) ;
132
137
}
133
- } ) ;
138
+ }
134
139
}
140
+ }
135
141
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) ;
138
145
}
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 ) ;
144
151
}
145
152
}
146
153
@@ -194,13 +201,25 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
194
201
195
202
tracing:: info!( "Winit window created and ready" ) ;
196
203
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 ( ) ) ;
200
205
}
201
206
202
207
fn user_event ( & mut self , _: & ActiveEventLoop , event : CustomEvent ) {
203
208
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
+ } ,
204
223
CustomEvent :: UiUpdate ( texture) => {
205
224
if let Some ( graphics_state) = self . graphics_state . as_mut ( ) {
206
225
graphics_state. resize ( texture. width ( ) , texture. height ( ) ) ;
@@ -217,127 +236,13 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
217
236
self . cef_schedule = Some ( instant) ;
218
237
}
219
238
}
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
- }
264
239
}
265
240
}
266
241
267
242
fn window_event ( & mut self , event_loop : & ActiveEventLoop , _window_id : WindowId , event : WindowEvent ) {
268
243
let Some ( event) = self . cef_context . handle_window_event ( event) else { return } ;
269
244
270
245
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
- }
341
246
WindowEvent :: CloseRequested => {
342
247
tracing:: info!( "The close button was pressed; stopping" ) ;
343
248
event_loop. exit ( ) ;
@@ -362,6 +267,19 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
362
267
Err ( e) => tracing:: error!( "{:?}" , e) ,
363
268
}
364
269
}
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
+ }
365
283
_ => { }
366
284
}
367
285
0 commit comments