makepad_studio/
app.rs

1use crate::{
2    makepad_code_editor::code_editor::*,
3    makepad_platform::*,
4    makepad_draw::*,
5    makepad_widgets::*,
6    makepad_widgets::file_tree::*,
7    file_system::file_system::*,
8    build_manager::{
9        run_view::*,
10        log_list::{
11            LogListAction
12        },
13        run_list::{
14            RunListAction
15        },
16        build_manager::{
17            BuildManager,
18            BuildManagerAction
19        },
20    }
21};
22
23live_design!{
24    import makepad_draw::shader::std::*;
25    import makepad_widgets::base::*;
26    import makepad_widgets::theme_desktop_dark::*;
27    import makepad_code_editor::code_editor::CodeEditor;
28    
29    import makepad_studio::build_manager::run_view::RunView;
30    import makepad_studio::build_manager::log_list::LogList;
31    import makepad_studio::build_manager::run_list::RunList;
32    
33    Logo = <Button> {
34        draw_icon: {
35            svg_file: dep("crate://self/resources/logo_makepad.svg"),
36            fn get_color(self) -> vec4 {
37                return #xffffff
38            }
39        }
40        icon_walk: {width: 300.0, height: Fit}
41        draw_bg: {
42            fn pixel(self) -> vec4 {
43                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
44                return sdf.result
45            }
46        }
47        margin: {top: 20.0, right: 0.0, bottom: 30.0, left: 0.0}
48        padding: 0.0
49        text: ""
50    }
51    
52    App = {{App}} {
53        ui: <Window> {
54            caption_bar = {visible: true, caption_label = {label = {text: "Makepad Studio"}}},
55            window: {inner_size: vec2(1600, 900)},
56            window_menu = {
57                main = Main {items: [app, file, edit, selection, view, run, window, help]}
58                
59                app = Sub {name: "Makepad Studio", items: [about, line, settings, line, quit]}
60                about = Item {name: "About Makepad Studio", enabled: false}
61                settings = Item {name: "Settings", enabled: false}
62                quit = Item {name: "Quit Makepad Studio", key: KeyQ}
63                
64                file = Sub {name: "File", items: [new_file, new_window, line, save_as, line, rename, line, close_editor, close_window]}
65                new_file = Item {name: "New File", enabled: false, shift: true, key: KeyN}
66                new_window = Item {name: "New Window", enabled: false, shift: true, key: KeyN}
67                save_as = Item {name: "Save As", enabled: false}
68                rename = Item {name: "Rename", enabled: false}
69                close_editor = Item {name: "Close Editor", enabled: false}
70                close_window = Item {name: "Close Window", enabled: false}
71                
72                edit = Sub {name: "Edit", items: [undo, redo, line, cut, copy, paste, line, find, replace, line, find_in_files, replace_in_files]}
73                undo = Item {name: "Undo", enabled: false}
74                redo = Item {name: "Redo", enabled: false}
75                cut = Item {name: "Cut", enabled: false}
76                copy = Item {name: "Copy", enabled: false}
77                paste = Item {name: "Paste", enabled: false}
78                find = Item {name: "Find", enabled: false}
79                replace = Item {name: "Replace", enabled: false}
80                find_in_files = Item {name: "Find in Files", enabled: false}
81                replace_in_files = Item {name: "Replace in Files", enabled: false}
82                
83                selection = Sub {name: "Selection", items: [select_all]}
84                select_all = Item {name: "Select All", enabled: false}
85                
86                view = Sub {name: "View", items: [select_all]}
87                zoom_in = Item {name: "Zoom In", enabled: false}
88                zoom_out = Item {name: "Zoom Out", enabled: false}
89                select_all = Item {name: "Enter Full Screen", enabled: false}
90                
91                run = Sub {name: "Run", items: [run_program]}
92                run_program = Item {name: "Run Program", enabled: false}
93                
94                window = Sub {name: "Window", items: [minimize, zoom, line, all_to_front]}
95                minimize = Item {name: "Minimize", enabled: false}
96                zoom = Item {name: "Zoom", enabled: false}
97                all_to_front = Item {name: "Bring All to Front", enabled: false}
98                
99                help = Sub {name: "Help", items: [about]}
100                
101                line = Line,
102            }
103            body = {dock = <Dock> {
104                height: Fill,
105                width: Fill
106                
107                root = Splitter {
108                    axis: Horizontal,
109                    align: FromA(230.0),
110                    a: file_tree_tabs,
111                    b: split1
112                }
113                
114                split1 = Splitter {
115                    axis: Vertical,
116                    align: FromB(200.0),
117                    a: split2,
118                    b: log_tabs
119                }
120                
121                split2 = Splitter {
122                    axis: Horizontal,
123                    align: Weighted(0.5),
124                    a: edit_tabs,
125                    b: run_tabs
126                }
127                
128                
129                
130                file_tree_tabs = Tabs {
131                    tabs: [file_tree, search, run_list],
132                    selected: 2
133                }
134                
135                edit_tabs = Tabs {
136                    tabs: [edit_first],
137                    selected: 0
138                }
139                
140                log_tabs = Tabs {
141                    tabs: [log_list],
142                    selected: 0
143                }
144                
145                run_tabs = Tabs {
146                    tabs: [run_first],
147                    selected: 0
148                }
149                
150                file_tree = Tab {
151                    name: "Explore",
152                    closable: false,
153                    kind: FileTree
154                }
155                
156                search = Tab {
157                    name: "Search"
158                    closable: false,
159                    kind: Search
160                }
161                
162                run_first = Tab {
163                    name: "View"
164                    closable: false,
165                    kind: RunFirst
166                }
167                edit_first = Tab {
168                    name: "Edit"
169                    closable: false,
170                    kind: EditFirst
171                }
172                
173                run_list = Tab {
174                    name: "Run"
175                    closable: false,
176                    kind: RunList
177                }
178                
179                file1 = Tab {
180                    name: "app.rs",
181                    closable: true,
182                    kind: CodeEditor
183                }
184                
185                log_list = Tab {
186                    name: "Log",
187                    closable: false,
188                    kind: LogList
189                }
190                
191                CodeEditor = <CodeEditor> {}
192                EditFirst = <RectView> {
193                    draw_bg: {color: #052329}
194                    <View> {
195                        width: Fill,
196                        height: Fill
197                        align: {
198                            x: 0.5,
199                            y: 0.5
200                        }
201                        flow: Down
202                        
203                            <Logo> {}
204                        
205                        <Label> {
206                            text: "Welcome to\nMakepad \n\n欢迎来到\nMakepad"
207                            width: Fit,
208                            margin: {left: 200}
209                            draw_text: {
210                                text_style: {
211                                    font_size: 20.0,
212                                    height_factor: 1.0,
213                                    font: {path: dep("crate://makepad-widgets/resources/GoNotoKurrent-Regular.ttf")}
214                                },
215                            }
216                        }
217                    }
218                    
219                }
220                RunFirst = <RectView> {
221                    draw_bg: {color: #4}
222                    <View> {
223                        width: Fill,
224                        height: Fill
225                        align: {
226                            x: 0.5,
227                            y: 0.5
228                        }
229                        flow: Down
230                            <Logo> {
231                            draw_icon: {
232                                fn get_color(self) -> vec4 {
233                                    return #7
234                                }
235                            }
236                        }
237                    }
238                    
239                }
240                RunList = <RunList> {
241                }
242                Search = <RectView> {
243                    draw_bg: {color: #2}
244                }
245                RunView = <RunView> {}
246                FileTree = <FileTree> {}
247                LogList = <LogList> {}
248            }}
249        }
250    }
251}
252
253#[derive(Live)]
254pub struct App {
255    #[live] ui: WidgetRef,
256    #[live] build_manager: BuildManager,
257    #[rust] file_system: FileSystem,
258}
259
260impl LiveHook for App {
261    fn before_live_design(cx: &mut Cx) {
262        crate::makepad_widgets::live_design(cx);
263        crate::makepad_code_editor::live_design(cx);
264        crate::build_manager::build_manager::live_design(cx);
265        crate::build_manager::run_list::live_design(cx);
266        crate::build_manager::log_list::live_design(cx);
267        crate::build_manager::run_view::live_design(cx);
268        // for macos
269        cx.start_stdin_service();
270    }
271    
272    fn after_new_from_doc(&mut self, cx: &mut Cx) {
273        self.file_system.init(cx);
274        self.build_manager.init(cx);
275        
276        //self.file_system.request_open_file(live_id!(file1), "examples/news_feed/src/app.rs".into());
277    }
278}
279
280app_main!(App);
281
282impl App {
283    fn open_code_file_by_path(&mut self, cx: &mut Cx, path: &str) {
284        let tab_id = LiveId::unique();
285        if let Some(file_id) = self.file_system.path_to_file_node_id(&path) {
286            self.file_system.request_open_file(tab_id, file_id);
287            let dock = self.ui.dock(id!(dock));
288            dock.create_and_select_tab(cx, live_id!(edit_tabs), tab_id, live_id!(CodeEditor), "".to_string(), TabClosable::Yes);
289            self.file_system.ensure_unique_tab_names(cx, &dock)
290        }
291    }
292}
293
294impl AppMain for App {
295    
296    
297    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
298        let dock = self.ui.dock(id!(dock));
299        let file_tree = self.ui.file_tree(id!(file_tree));
300        let log_list = self.ui.portal_list(id!(log_list));
301        let run_list = self.ui.flat_list(id!(run_list));
302        if let Event::Draw(event) = event {
303            //let dt = profile_start();
304            let cx = &mut Cx2d::new(cx, event);
305            while let Some(next) = self.ui.draw_widget(cx).hook_widget() {
306                
307                if let Some(mut file_tree) = file_tree.has_widget(&next).borrow_mut() {
308                    file_tree.set_folder_is_open(cx, live_id!(root).into(), true, Animate::No);
309                    self.file_system.draw_file_node(
310                        cx,
311                        live_id!(root).into(),
312                        &mut *file_tree
313                    );
314                }
315                else if let Some(mut run_view) = next.as_run_view().borrow_mut() {
316                    let current_id = dock.drawing_item_id().unwrap();
317                    run_view.draw(cx, current_id, &mut self.build_manager);
318                }
319                else if let Some(mut log_list) = log_list.has_widget(&next).borrow_mut() {
320                    self.build_manager.draw_log(cx, &mut *log_list);
321                }
322                else if let Some(mut run_list) = run_list.has_widget(&next).borrow_mut() {
323                    self.build_manager.draw_run_list(cx, &mut *run_list);
324                }
325                else if let Some(mut code_editor) = next.as_code_editor().borrow_mut() {
326                    // lets fetch a session
327                    let current_id = dock.drawing_item_id().unwrap();
328                    if let Some(session) = self.file_system.get_session_mut(current_id) {
329                        code_editor.draw(cx, session);
330                    }
331                }
332            }
333            //profile_end!(dt);
334            return
335        }
336        
337        if let Event::Destruct = event {
338            self.build_manager.clear_active_builds();
339        }
340        
341        if let Event::KeyDown(KeyEvent {
342            key_code,
343            modifiers: KeyModifiers {logo, control, ..},
344            ..
345        }) = event {
346            if *control || *logo {
347                if let KeyCode::Backtick = key_code {
348                    self.build_manager.start_recompile(cx);
349                }
350                else if let KeyCode::KeyK = key_code {
351                    self.build_manager.clear_log(cx, &dock, &mut self.file_system);
352                    log_list.redraw(cx);
353                }
354            }
355        }
356        
357        for action in self.file_system.handle_event(cx, event, &self.ui) {
358            match action {
359                FileSystemAction::TreeLoaded => {
360                    self.open_code_file_by_path(cx, "examples/news_feed/src/app.rs");
361                }
362                FileSystemAction::RecompileNeeded => {
363                    self.build_manager.start_recompile_timer(cx, &self.ui);
364                }
365                FileSystemAction::LiveReloadNeeded(live_file_change) => {
366                    self.build_manager.live_reload_needed(live_file_change);
367                    self.build_manager.clear_log(cx, &dock, &mut self.file_system);
368                    log_list.redraw(cx);
369                }
370            }
371        }
372        
373        // lets iterate over the editors and handle events
374        for (item_id, item) in dock.borrow_mut().unwrap().visible_items() {
375            if let Some(mut run_view) = item.as_run_view().borrow_mut() {
376                run_view.handle_event(cx, event, item_id, &mut self.build_manager);
377            }
378            else if let Some(mut code_editor) = item.as_code_editor().borrow_mut() {
379                if let Some(session) = self.file_system.get_session_mut(item_id) {
380                    for action in code_editor.handle_event(cx, event, session) {
381                        match action {
382                            CodeEditorAction::TextDidChange => {
383                                // lets write the file
384                                self.file_system.request_save_file(item_id)
385                            }
386                        }
387                    }
388                }
389            }
390        }
391        
392        for action in self.build_manager.handle_event(cx, event, &mut self.file_system, &dock) {
393            match action {
394                BuildManagerAction::RedrawLog => {
395                    // if the log_list is tailing, set the new len
396                    log_list.redraw(cx);
397                }
398                BuildManagerAction::StdinToHost {run_view_id, msg} => {
399                    if let Some(mut run_view) = dock.item(run_view_id).as_run_view().borrow_mut() {
400                        run_view.handle_stdin_to_host(cx, &msg, run_view_id, &mut self.build_manager);
401                    }
402                }
403                _ => ()
404            }
405        }
406        
407        let actions = self.ui.handle_widget_event(cx, event);
408        
409        for (item_id, item) in run_list.items_with_actions(&actions) {
410            for action in self.build_manager.handle_run_list(cx, &run_list, item_id, item, &actions) {
411                match action {
412                    RunListAction::Create(run_view_id, name) => {
413                        let tab_bar_id = dock.find_tab_bar_of_tab(live_id!(run_first)).unwrap();
414                        dock.create_and_select_tab(cx, tab_bar_id, run_view_id, live_id!(RunView), name, TabClosable::Yes);
415                        dock.redraw(cx);
416                    }
417                    RunListAction::Destroy(run_view_id) => {
418                        dock.close_tab(cx, run_view_id);
419                        dock.redraw(cx);
420                    }
421                    _ => ()
422                }
423                log_list.redraw(cx);
424            }
425        }
426        
427        for (item_id, item) in log_list.items_with_actions(&actions) {
428            for action in self.build_manager.handle_log_list(cx, &log_list, item_id, item, &actions) {
429                match action {
430                    LogListAction::JumpToError{file_name, start, length} => {
431                        // lets find a tab if we have it otherwise open it
432                        if let Some(file_id) = self.file_system.path_to_file_node_id(&file_name) {
433                            if let Some(tab_id) = self.file_system.file_node_id_to_tab_id(file_id){
434                                dock.select_tab(cx, tab_id);
435                                // ok lets scroll into view
436                                if let Some(mut editor) = dock.item(tab_id).as_code_editor().borrow_mut() {
437                                    if let Some(session) = self.file_system.get_session_mut(tab_id) {
438                                        editor.set_cursor_and_scroll(cx, start, length, session);
439                                        editor.set_key_focus(cx);
440                                    }
441                                }
442                            }
443                            else{
444                                // lets open the editor
445                                let tab_id = LiveId::unique();
446                                self.file_system.request_open_file(tab_id, file_id);
447                                // lets add a file tab 'somewhere'
448                                dock.create_and_select_tab(cx, live_id!(edit_tabs), tab_id, live_id!(CodeEditor), "".to_string(), TabClosable::Yes);
449                                // lets scan the entire doc for duplicates
450                                self.file_system.ensure_unique_tab_names(cx, &dock)
451                            }
452                        }
453                    }
454                    _ => ()
455                }
456                log_list.redraw(cx);
457            }
458        }
459        
460        if let Some(tab_id) = dock.clicked_tab_close(&actions) {
461            dock.close_tab(cx, tab_id);
462            if self.build_manager.handle_tab_close(tab_id) {
463                log_list.redraw(cx);
464                run_list.redraw(cx);
465            }
466            self.file_system.remove_tab(tab_id);
467            self.file_system.ensure_unique_tab_names(cx, &dock);
468        }
469        
470        if let Some(tab_id) = dock.should_tab_start_drag(&actions) {
471            
472            dock.tab_start_drag(cx, tab_id, DragItem::FilePath {
473                path: "".to_string(), //String::from("file://") + &*path.into_unix_string().to_string_lossy(),
474                internal_id: Some(tab_id)
475            });
476        }
477        
478        if let Some(drag) = dock.should_accept_drag(&actions) {
479            if drag.items.len() == 1 {
480                if drag.modifiers.logo {
481                    dock.accept_drag(cx, drag, DragResponse::Copy);
482                }
483                else {
484                    dock.accept_drag(cx, drag, DragResponse::Move);
485                }
486            }
487        }
488        
489        if let Some(drop) = dock.has_drop(&actions) {
490            
491            if let DragItem::FilePath {path, internal_id} = &drop.items[0] {
492                if let Some(internal_id) = internal_id { // from inside the dock
493                    if drop.modifiers.logo {
494                        dock.drop_clone(cx, drop.abs, *internal_id, LiveId::unique());
495                    }
496                    else {
497                        dock.drop_move(cx, drop.abs, *internal_id);
498                    }
499                    self.file_system.ensure_unique_tab_names(cx, &dock);
500                }
501                else { // external file, we have to create a new tab
502                    let tab_id = LiveId::unique();
503                    if let Some(file_id) = self.file_system.path_to_file_node_id(&path) {
504                        self.file_system.request_open_file(tab_id, file_id);
505                        dock.drop_create(cx, drop.abs, tab_id, live_id!(CodeEditor), "".to_string(), TabClosable::Yes);
506                        self.file_system.ensure_unique_tab_names(cx, &dock)
507                    }
508                }
509            }
510        }
511        
512        if let Some(file_id) = file_tree.should_file_start_drag(&actions) {
513            
514            let path = self.file_system.file_node_path(file_id);
515            file_tree.file_start_drag(cx, file_id, DragItem::FilePath {
516                path,
517                internal_id: None
518            });
519        }
520        
521        if let Some(file_id) = file_tree.file_clicked(&actions) {
522            // ok lets open the file
523            let tab_id = LiveId::unique();
524            self.file_system.request_open_file(tab_id, file_id);
525            // lets add a file tab 'somewhere'
526            dock.create_and_select_tab(cx, live_id!(edit_tabs), tab_id, live_id!(CodeEditor), "".to_string(), TabClosable::Yes);
527            // lets scan the entire doc for duplicates
528            self.file_system.ensure_unique_tab_names(cx, &dock)
529        }
530    }
531}