makepad_studio/
log_list.rs

1
2use {
3    crate::{
4        build_manager::{
5            build_manager::*,
6            build_protocol::*,
7        },
8        makepad_platform::studio::JumpToFile,
9        app::{AppAction, AppData},
10        makepad_widgets::*,
11        makepad_code_editor::code_view::*,
12    },
13    std::{
14        env,
15    },
16};
17
18live_design!{
19    use link::shaders::*;
20    use link::widgets::*;
21    use link::theme::*;
22    use makepad_code_editor::code_view::CodeView;
23    
24    Icon = <View> {
25        width: 10, height: 10
26        margin:{top:2, right: 10},
27        show_bg: true,
28    }
29    
30    LogItem = <View> {
31        height: Fit, width: Fill
32        padding: <THEME_MSPACE_2> {} // TODO: Fix. Changing this value to i.e. '0.' causes Makepad Studio to freeze when switching to the log tab.
33        spacing: (THEME_SPACE_2)
34        align: { x: 0.0, y: 0.0 }
35        show_bg: true,
36        draw_bg: {
37            instance is_even: 0.0
38            instance selected: 0.0
39            instance hover: 0.0
40            fn pixel(self) -> vec4 {
41                return mix(
42                    mix(
43                        THEME_COLOR_BG_EVEN,
44                        THEME_COLOR_BG_ODD,
45                        self.is_even
46                    ),
47                    THEME_COLOR_OUTSET_ACTIVE,
48                    self.selected
49                );
50            }
51        }
52        animator: {
53            ignore_missing: true,
54            hover = {
55                default: off
56                off = {
57                    from: {all: Forward {duration: 0.1}}
58                    apply: {
59                        draw_bg: {hover: 0.0}
60                    }
61                }
62                on = {
63                    cursor: Hand
64                    from: {all: Snap}
65                    apply: {
66                        draw_bg: {hover: 1.0}
67                    },
68                }
69            }
70            
71            select = {
72                default: off
73                off = {
74                    from: {all: Snap}
75                    apply: {
76                        draw_bg: {selected: 0.0}
77                    }
78                }
79                on = {
80                    from: {all: Snap}
81                    apply: {
82                        draw_bg: {selected: 1.0}
83                    }
84                }  
85            }
86        }
87        flow = <TextFlow>{
88            width: Fill,
89            height: Fit
90            width: Fill,
91            height: Fit
92            
93            code_view = <CodeView>{
94                editor:{
95                    margin:{left:25}
96                }
97            }
98            
99            fold_button = <FoldButton>{
100                animator:{
101                    active={default:off}
102                }
103            }
104            
105            wait_icon = <Icon> {
106                draw_bg: {
107                    fn pixel(self) -> vec4 {
108                        let sdf = Sdf2d::viewport(self.pos * self.rect_size)
109                        sdf.circle(5., 5., 4.)
110                        sdf.fill(THEME_COLOR_LABEL_OUTER)
111                        sdf.move_to(3., 5.)
112                        sdf.line_to(3., 5.)
113                        sdf.move_to(5., 5.)
114                        sdf.line_to(5., 5.)
115                        sdf.move_to(7., 5.)
116                        sdf.line_to(7., 5.)
117                        sdf.stroke(#0, 0.8)
118                        return sdf.result
119                    }
120                }
121            },
122            log_icon = <Icon> {
123                draw_bg: {
124                    fn pixel(self) -> vec4 {
125                        let sdf = Sdf2d::viewport(self.pos * self.rect_size)
126                        sdf.circle(5., 5., 4.);
127                        sdf.fill(THEME_COLOR_LABEL_OUTER);
128                        let sz = 1.;
129                        sdf.move_to(5., 5.);
130                        sdf.line_to(5., 5.);
131                        sdf.stroke(#a, 0.8);
132                        return sdf.result
133                    }
134                }
135            }
136            error_icon = <Icon> {
137                draw_bg: {
138                    fn pixel(self) -> vec4 {
139                        let sdf = Sdf2d::viewport(self.pos * self.rect_size)
140                        sdf.circle(5., 5., 4.5);
141                        sdf.fill(THEME_COLOR_ERROR);
142                        let sz = 1.5;
143                        sdf.move_to(5. - sz, 5. - sz);
144                        sdf.line_to(5. + sz, 5. + sz);
145                        sdf.move_to(5. - sz, 5. + sz);
146                        sdf.line_to(5. + sz, 5. - sz);
147                        sdf.stroke(#0, 0.8)
148                        return sdf.result
149                    }
150                }
151            },
152            warning_icon = <Icon> {
153                draw_bg: {
154                    fn pixel(self) -> vec4 {
155                        let sdf = Sdf2d::viewport(self.pos * self.rect_size)
156                        sdf.move_to(5., 1.);
157                        sdf.line_to(9.25, 9.);
158                        sdf.line_to(0.75, 9.);
159                        sdf.close_path();
160                        sdf.fill(THEME_COLOR_WARNING);
161                        //  sdf.stroke(#be, 0.5);
162                        sdf.move_to(5., 3.5);
163                        sdf.line_to(5., 5.25);
164                        sdf.stroke(#0, 1.0);
165                        sdf.move_to(5., 7.25);
166                        sdf.line_to(5., 7.5);
167                        sdf.stroke(#0, 1.0);
168                        return sdf.result
169                    }
170                }
171            }
172            panic_icon = <Icon> {
173                draw_bg: {
174                    fn pixel(self) -> vec4 {
175                        let sdf = Sdf2d::viewport(self.pos * self.rect_size)
176                        sdf.move_to(5., 1.);
177                        sdf.line_to(9., 9.);
178                        sdf.line_to(1., 9.);
179                        sdf.close_path();
180                        sdf.fill(THEME_COLOR_PANIC);
181                        let sz = 1.;
182                        sdf.move_to(5. - sz, 6.25 - sz);
183                        sdf.line_to(5. + sz, 6.25 + sz);
184                        sdf.move_to(5. - sz, 6.25 + sz);
185                        sdf.line_to(5. + sz, 6.25 - sz);
186                        sdf.stroke(#0, 0.8);
187                        return sdf.result
188                    }
189                }
190            }
191        }
192    }
193    
194    pub LogList = {{LogList}}{
195        height: Fill, width: Fill,
196        list = <PortalList> {
197            max_pull_down: 0,
198            capture_overload: false,
199            grab_key_focus: false
200            auto_tail: true
201            drag_scrolling: false
202            height: Fill, width: Fill,
203            flow: Down
204            LogItem = <LogItem> {
205            }
206            Empty = <LogItem> {
207                cursor: Default
208                width: Fill
209                height: 25,
210                body = <P> {  margin: 0, text: "" }
211            }
212        }
213    }
214}
215
216#[derive(Clone, Debug, DefaultNone)]
217pub enum LogListAction {
218    JumpTo(JumpToFile),
219    None
220}
221
222#[derive(Live, LiveHook, Widget)]
223pub struct LogList{
224    #[deref] view:View
225}
226
227#[derive(Clone, Debug, PartialEq)]
228pub struct JumpToFileLink{item_id:usize}
229
230impl LogList{
231    fn draw_log(&mut self, cx: &mut Cx2d, list:&mut PortalList, build_manager:&mut BuildManager){
232        list.set_item_range(cx, 0, build_manager.log.len());
233        while let Some(item_id) = list.next_visible_item(cx) {
234            let is_even = item_id & 1 == 0;
235            fn map_level_to_icon(level: LogLevel) -> LiveId {
236                match level {
237                    LogLevel::Warning => live_id!(warning_icon),
238                    LogLevel::Error => live_id!(error_icon),
239                    LogLevel::Log => live_id!(log_icon),
240                    LogLevel::Wait => live_id!(wait_icon),
241                    LogLevel::Panic => live_id!(panic_icon),
242                }
243            }
244            let mut location = String::new();
245            if let Some((build_id, log_item)) = build_manager.log.get(item_id as usize) {
246                let _binary = if build_manager.active.builds.len()>1 {
247                    if let Some(build) = build_manager.active.builds.get(&build_id) {
248                        &build.log_index
249                    }
250                    else {""}
251                }else {""};
252                let mut item = list.item(cx, item_id, live_id!(LogItem)).as_view();
253                item.apply_over(cx, live!{
254                    draw_bg: {is_even: (if is_even {1.0} else {0.0})}
255                });
256                while let Some(step) = item.draw(cx, &mut Scope::empty()).step(){
257                    if let Some(mut tf) = step.as_text_flow().borrow_mut(){
258                        match log_item {
259                            LogItem::Bare(msg) => {
260                                tf.draw_item_counted(cx, map_level_to_icon(msg.level));
261                                tf.draw_text(cx,&msg.line);
262                            }
263                            LogItem::Location(msg) => {
264                                tf.draw_item_counted(cx, map_level_to_icon(msg.level));
265                                let fold_button = if msg.explanation.is_some(){
266                                    tf.draw_item_counted_ref(cx, live_id!(fold_button)).as_fold_button()
267                                }
268                                else{
269                                    Default::default()
270                                };
271                                fmt_over!(location, "{}: {}:{}", msg.file_name, msg.start.line_index + 1, msg.start.byte_index + 1);
272                                tf.draw_link(cx, live_id!(link), JumpToFileLink{item_id}, &location);
273                                
274                                tf.draw_text(cx, &msg.message);
275                                if let Some(explanation) = &msg.explanation{
276                                    let open = fold_button.open_float();
277                                    if open > 0.0{
278                                        cx.turtle_new_line();
279                                        let code = tf.item_counted(cx, live_id!(code_view));
280                                        code.set_text(cx, explanation);
281                                        code.as_code_view().borrow_mut().unwrap().editor.height_scale = open;
282                                        code.draw_all_unscoped(cx);
283                                    }
284                                };
285                            }
286                            _ => {}
287                        }
288                    }
289                }
290                continue
291            }
292            let item = list.item(cx, item_id, live_id!(Empty)).as_view();
293            item.apply_over(cx, live!{draw_bg: {is_even: (if is_even {1.0} else {0.0})}});
294            item.draw_all(cx, &mut Scope::empty());
295        }
296    }
297}
298
299impl Widget for LogList {
300    fn draw_walk(&mut self, cx: &mut Cx2d, scope:&mut Scope, walk:Walk)->DrawStep{
301        while let Some(step) = self.view.draw_walk(cx, scope, walk).step(){
302            if let Some(mut list) = step.as_portal_list().borrow_mut(){
303                self.draw_log(cx, &mut *list, &mut scope.data.get_mut::<AppData>().unwrap().build_manager)
304            }
305        }
306        DrawStep::done()
307    }
308    
309    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope){
310        let log_list = self.view.portal_list(id!(list));
311        self.view.handle_event(cx, event, scope);
312        let data = scope.data.get::<AppData>().unwrap();
313        if let Event::Actions(actions) = event{
314            
315            if log_list.any_items_with_actions(&actions) {
316                // alright lets figure out if someone clicked a link
317                // alright so how do we now filter which link was clicked
318                for jtf in actions.filter_actions_data::<JumpToFileLink>(){
319                    // ok we have a JumpToFile link
320                    if let Some((_build_id, log_item)) = data.build_manager.log.get(jtf.item_id) {
321                        match log_item {
322                            LogItem::Location(msg) => {
323                                cx.action(AppAction::JumpTo(JumpToFile{
324                                    file_name: msg.file_name.clone(), 
325                                    line: msg.start.line_index as u32,
326                                    column: msg.start.byte_index as u32
327                                }));
328                            }
329                            _ => ()
330                        }
331                    }
332                }
333            }
334        }
335    }
336}
337
338impl LogListRef{
339    pub fn reset_scroll(&self, cx:&mut Cx){
340        if let Some(inner) = self.borrow_mut() {
341            let log_list = inner.view.portal_list(id!(list));
342            log_list.set_first_id_and_scroll(0,0.0);
343            log_list.redraw(cx);
344        }
345    }
346}