makepad_studio/
run_view.rs

1use crate::{
2    app::{AppData},
3    makepad_widgets::*,
4    makepad_platform::os::cx_stdin::*,
5    build_manager::build_manager::BuildManager,
6};
7
8live_design!{
9    use link::shaders::*;
10    
11    pub RunView = {{RunView}} {
12        draw_app: {
13            texture tex: texture2d
14            instance recompiling: 0.0
15            instance started: 0.0
16            instance tex_scale: vec2(0.0, 0.0),
17            instance tex_size: vec2(0.0, 0.0),
18            fn pixel(self) -> vec4 {
19                //return sample2d(self.tex, self.pos * self.tex_scale);
20                let tp1 = sample2d_rt(self.tex, vec2(0.5/self.tex_size.x,0.5/self.tex_size.y))
21                let tp2 = sample2d_rt(self.tex, vec2(1.5/self.tex_size.x,0.5/self.tex_size.y));
22                let tp = vec2(tp1.r*65280.0 + tp1.b*255.0,tp2.r*65280.0 + tp2.b*255.0);
23                // ok so we should be having the same size in self.pos
24                let counter = (self.rect_size * self.dpi_factor) / tp;
25                let tex_scale = tp / self.tex_size;
26                let fb = sample2d_rt(self.tex, self.pos * tex_scale * counter)
27                if fb.r == 1.0 && fb.g == 0.0 && fb.b == 1.0 {
28                    return #2
29                }
30                return mix(fb, #4, self.recompiling * 0.4);
31            }
32        }
33        animator: {
34            started = {
35                default: off,
36                off = {
37                    from: {all: Forward {duration: 0.05}}
38                    apply: {draw_app: {started: 0.0}}
39                }
40                on = {
41                    from: {all: Forward {duration: 0.05}}
42                    apply: {draw_app: {started: 1.0}}
43                }
44            }
45            recompiling = {
46                default: off,
47                off = {
48                    from: {all: Forward {duration: 0.05}}
49                    apply: {draw_app: {recompiling: 0.0}}
50                }
51                on = {
52                    from: {all: Forward {duration: 0.05}}
53                    apply: {draw_app: {recompiling: 1.0}}
54                }
55            }
56        }
57    }
58}
59 
60
61#[derive(Live, Widget)]
62pub struct RunView {
63    #[walk] walk: Walk,
64    #[animator] animator: Animator,
65    #[redraw] #[live] draw_app: DrawQuad,
66    //#[live] frame_delta: f64,
67    #[rust] last_rect: Rect,
68    #[rust(100usize)] redraw_countdown: usize,
69   // #[rust] time: f64,
70   // #[rust] frame: u64,
71    #[rust] started: bool,
72    #[rust] pub build_id: Option<LiveId>,
73    #[rust] pub window_id: usize,
74    #[rust(WindowKindId::Main)] pub kind_id: WindowKindId,
75}
76
77impl LiveHook for RunView {
78    fn after_new_from_doc(&mut self, cx: &mut Cx) {
79        //self.tick = cx.new_next_frame(); //start_interval(self.frame_delta);
80        //self.time = 0.0;
81        self.draw_app.set_texture(0, &cx.null_texture());
82    }
83    
84    fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
85        if let ApplyFrom::UpdateFromDoc{..} = apply.from{
86            self.last_rect = Default::default();
87            self.animator_cut(cx, id!(started.on));
88        }
89    }
90}
91
92impl RunView {
93    /*
94    pub fn run_tick(&mut self, cx: &mut Cx, manager: &mut BuildManager) {
95        self.frame += 1;
96        
97        manager.send_host_to_stdin(self.build_id, HostToStdin::PollSwapChain{window_id: self.window_id});
98        
99        if self.window_id == 0{
100            manager.send_host_to_stdin(self.build_id, HostToStdin::Tick);
101        }
102            
103        if self.redraw_countdown>0 {
104            self.redraw_countdown -= 1;
105            self.redraw(cx);
106            self.tick = cx.new_next_frame();
107        }
108        else {
109            self.timer = cx.start_timeout(0.008);
110        }
111    }*/
112    /*
113    pub fn pump_event_loop(&mut self, cx: &mut Cx, event: &Event, run_view_id: LiveId, manager: &mut BuildManager) {
114        let run_view_id = run_view_id.sub(self.window_id as u64);
115        if let Some(_) = self.timer.is_event(event) {
116            self.run_tick(cx, run_view_id, manager)
117        }
118        if let Some(_) = self.tick.is_event(event) {
119            self.run_tick(cx, run_view_id, manager)
120        }
121    }*/
122    
123    pub fn draw_complete_and_flip(&mut self, cx: &mut Cx, presentable_draw: &PresentableDraw, manager: &mut BuildManager){
124        let window_id = self.window_id;
125        if self.build_id.is_none(){
126            return
127        }
128        if let Some(v) = manager.active.builds.get_mut(self.build_id.as_ref().unwrap()){
129            // Only allow presenting images in the current host swapchain
130            // (or the previous one, before any draws on the current one),
131            // and look them up by their unique IDs, to avoid rendering
132            // different textures than the ones the client just drew to.
133            let mut try_present_through = |swapchain: &Option<Swapchain<Texture>>| {
134                let swapchain = swapchain.as_ref()?;
135                let drawn = swapchain.get_image(presentable_draw.target_id)?;
136                        
137                self.draw_app.set_texture(0, &drawn.image);
138                self.draw_app.draw_vars.set_var_instance(cx, id!(tex_scale), &[
139                    (presentable_draw.width as f32) / (swapchain.alloc_width as f32),
140                    (presentable_draw.height as f32) / (swapchain.alloc_height as f32),
141                ]);
142                self.draw_app.draw_vars.set_var_instance(cx, id!(tex_size), &[
143                    (swapchain.alloc_width as f32),
144                    (swapchain.alloc_height as f32),
145                ]);
146                
147                if !self.started {
148                    self.started = true;
149                    self.animator_play(cx, id!(started.on));
150                }
151                self.redraw_countdown = 20;
152                self.redraw(cx);
153                Some(())
154            };
155                    
156            if try_present_through(&v.swapchain_mut(window_id)).is_some() {
157                // The client is now drawing to the current swapchain,
158                // we can discard any previous one we were stashing.
159                *v.last_swapchain_with_completed_draws_mut(window_id) = None;
160            } else {
161                // New draws to a previous swapchain are fine, just means
162                // the client hasn't yet drawn on the current swapchain,
163                // what lets us accept draws is their target `Texture`s.
164                try_present_through(&v.last_swapchain_with_completed_draws_mut(window_id));
165            }
166        }
167    }
168    
169    pub fn ready_to_start(&mut self, cx: &mut Cx){
170        self.animator_play(cx, id!(recompiling.off));
171        // cause a resize event to fire
172        self.last_rect = Default::default();
173        self.redraw(cx);
174    }
175    
176    pub fn recompile_started(&mut self, cx: &mut Cx) {
177        self.animator_play(cx, id!(recompiling.on));
178    }
179    
180    pub fn redraw(&mut self, cx: &mut Cx) {
181        self.draw_app.redraw(cx);
182    }
183    
184    
185    pub fn resend_framebuffer(&mut self, _cx: &mut Cx) {
186        self.last_rect = Default::default();
187    }
188    
189    pub fn draw_run_view(&mut self, cx: &mut Cx2d, run_view_id: LiveId, manager: &mut BuildManager, walk:Walk) {
190        if self.build_id.is_none(){
191            return
192        }
193        // alright so here we draw em texturezs
194        // pick a texture off the buildstate
195        let dpi_factor = cx.current_dpi_factor();
196        let rect = cx.walk_turtle(walk).dpi_snap(dpi_factor);
197        // lets pixelsnap rect in position and size
198        if self.redraw_countdown > 0{
199            self.redraw_countdown -= 1;
200            self.redraw(cx);
201        }
202        if self.last_rect != rect{
203            manager.send_host_to_stdin(run_view_id, HostToStdin::WindowGeomChange {
204                window_id: self.window_id,
205                dpi_factor,
206                left: rect.pos.x,
207                top: rect.pos.y,
208                width: rect.size.x,
209                height: rect.size.y,
210            });
211        }
212        if self.last_rect.size != rect.size {
213
214            let min_width = ((rect.size.x * dpi_factor).ceil() as u32).max(1);
215            let min_height = ((rect.size.y * dpi_factor).ceil() as u32).max(1);
216            
217            let active_build_needs_new_swapchain = manager.active.builds
218                .get_mut(&run_view_id)
219                .filter(|v| {
220                    v.aux_chan_host_endpoint.is_some()
221                })
222                .filter(|v| {
223                    v.swapchain(self.window_id).map(|swapchain| {
224                        min_width > swapchain.alloc_width || min_height > swapchain.alloc_height
225                    }).unwrap_or(true)
226                });
227                                
228            if let Some(v) = active_build_needs_new_swapchain {
229                
230                // HACK(eddyb) there is no check that there were any draws on
231                // the current swapchain, but the absence of an older swapchain
232                // (i.e. `last_swapchain_with_completed_draws`) implies either
233                // zero draws so far, or a draw to the current one discarded it.
234                if v.last_swapchain_with_completed_draws(self.window_id).is_none() {
235                    let chain = v.swapchain_mut(self.window_id).take();
236                    *v.last_swapchain_with_completed_draws_mut(self.window_id) = chain;
237                }
238
239                // `Texture`s can be reused, but all `PresentableImageId`s must
240                // be regenerated, to tell apart swapchains when e.g. resizing
241                // constantly, so textures keep getting created and replaced.
242                if let Some(swapchain) = v.swapchain_mut(self.window_id) {
243                    for pi in &mut swapchain.presentable_images {
244                        pi.id = cx_stdin::PresentableImageId::alloc();
245                    }
246                }
247
248                // Update the swapchain allocated size, rounding it up to
249                // reduce the need for further swapchain recreation.
250                let alloc_width = min_width.max(64).next_power_of_two();
251                let alloc_height = min_height.max(64).next_power_of_two();
252                
253                let swapchain = v.swapchain_mut(self.window_id).get_or_insert_with(|| {
254                    Swapchain::new(self.window_id, alloc_width, alloc_height).images_map(|pi| {
255                        // Prepare a version of the swapchain for cross-process sharing.
256                        Texture::new_with_format(cx, TextureFormat::SharedBGRAu8 {
257                            id: pi.id,
258                            width: alloc_width as usize,
259                            height: alloc_height as usize,
260                            initial: true,
261                        })
262                    })
263                });
264                
265                let shared_swapchain = swapchain.images_as_ref().images_map(|pi| {
266                    cx.share_texture_for_presentable_image(&pi.image)
267                });
268
269                let shared_swapchain =  {
270                    // FIMXE(eddyb) this could be platform-agnostic if the serializer
271                    // could drive the out-of-band UNIX domain socket messaging itself.
272                    #[cfg(target_os = "linux")] {
273                        shared_swapchain.images_map(|pi| {
274                            pi.send_fds_to_aux_chan(v.aux_chan_host_endpoint.as_ref().unwrap())
275                                .map(|pi| pi.image)
276                        })
277                    }
278                    #[cfg(not(target_os = "linux"))] {
279                        shared_swapchain.images_map(|pi| std::io::Result::Ok(pi.image))
280                    }
281                };
282
283                let mut any_errors = false;
284                for pi in &shared_swapchain.presentable_images {
285                    if let Err(err) = &pi.image {
286                        // FIXME(eddyb) is this recoverable or should the whole
287                        // client be restarted? desyncs can get really bad...
288                        error!("failed to send swapchain image to client: {:?}", err);
289                        any_errors = true;
290                    }
291                }
292
293                if !any_errors {
294                    // Inform the client about the new swapchain it *should* use
295                    // (using older swapchains isn't an error, but the draw calls
296                    // will either be noops, or write to orphaned GPU memory ranges).
297                    
298                    manager.send_host_to_stdin(run_view_id, HostToStdin::Swapchain(
299                        shared_swapchain.images_map(|pi| pi.image.unwrap()),
300                    ));
301                }
302            }
303        }
304        self.last_rect = rect;
305        self.draw_app.draw_abs(cx, rect);
306        // lets store the area 
307        if let Some(ab) = manager.active.builds.get_mut(&run_view_id){
308            ab.app_area.insert(self.window_id, self.draw_app.area());
309        }
310        
311    }
312}
313
314impl Widget for RunView {
315
316    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
317        let run_view_id = scope.path.last().sub(self.window_id as u64);
318        let manager = &mut scope.data.get_mut::<AppData>().unwrap().build_manager;
319        self.draw_run_view(cx, run_view_id, manager, walk);
320        DrawStep::done()
321    }
322    
323    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope){
324        let run_view_id = scope.path.last().sub(self.window_id as u64);
325        let manager = &scope.data.get::<AppData>().unwrap().build_manager;
326        
327        self.animator_handle_event(cx, event);
328        // lets send mouse events
329        match event.hits(cx, self.draw_app.area()) {
330            Hit::FingerDown(_) => {
331                cx.set_key_focus(self.draw_app.area());
332            }
333            Hit::TextInput(e) => {
334                manager.send_host_to_stdin(run_view_id, HostToStdin::TextInput(e));
335            }
336            Hit::KeyDown(e) => {
337                manager.send_host_to_stdin(run_view_id, HostToStdin::KeyDown(e));
338            }
339            Hit::KeyUp(e) => {
340                manager.send_host_to_stdin(run_view_id, HostToStdin::KeyUp(e));
341            }
342            _ => ()
343        }
344
345    }
346    
347}