makepad_widgets/
image.rs

1use crate::{
2    makepad_derive_widget::*,
3    image_cache::*,
4    makepad_draw::*,
5    widget::*
6};
7use std::sync::Arc;
8use std::path::{Path, PathBuf};
9
10live_design!{
11    link widgets;
12    use link::shaders::*;
13    
14    DrawImage= {{DrawImage}} {
15        texture image: texture2d
16        opacity: 1.0
17        image_scale: vec2(1.0, 1.0)
18        image_pan: vec2(0.0, 0.0)
19                
20        fn get_color_scale_pan(self, scale: vec2, pan: vec2) -> vec4 {
21            return sample2d(self.image, self.pos * scale + pan).xyzw;
22        }
23                                
24        fn get_color(self) -> vec4 {
25            return self.get_color_scale_pan(self.image_scale, self.image_pan)
26        }
27        
28        fn pixel(self) -> vec4 {
29            let color = mix(self.get_color(), #3, self.async_load);
30            return Pal::premul(vec4(color.xyz, color.w * self.opacity))
31        }
32        
33    }
34    
35    pub ImageBase = {{Image}} {}
36    
37    pub Image = <ImageBase> {
38        animator: {
39            async_load = {
40                default: off,
41                off = {
42                    from: {all: Forward {duration: 0.1}}
43                    apply: {
44                        draw_bg: {async_load: 0.0}
45                    }
46                }
47                on = {
48                    from: {
49                        all: Forward {duration: 0.1}
50                    }
51                    apply: {
52                        draw_bg: {async_load: 1.0}
53                    }
54                }
55            }
56        }
57        
58        width: 100
59        height: 100
60    }
61    
62}
63
64#[derive(Live, LiveHook, LiveRegister)]
65#[repr(C)]
66pub struct DrawImage {
67    #[deref] draw_super: DrawQuad,
68    #[live] pub opacity: f32,
69    #[live] image_scale: Vec2,
70    #[live] image_pan: Vec2,
71    #[live] async_load: f32
72}
73
74
75#[derive(Copy, Clone, Debug, Live, LiveHook)]
76#[live_ignore]
77pub enum ImageAnimation {
78    Stop,
79    Once,
80    #[pick] Loop,
81    Bounce,
82    #[live(0.0)] Frame(f64),
83    #[live(0.0)] Factor(f64),
84    #[live(60.0)] OnceFps(f64),
85    #[live(60.0)] LoopFps(f64),
86    #[live(60.0)] BounceFps(f64),
87}
88
89#[derive(Live, Widget)]
90pub struct Image {
91    #[walk] walk: Walk,
92    #[animator] animator: Animator,
93    #[redraw] #[live] pub draw_bg: DrawImage,
94    #[live] min_width: i64,
95    #[live] min_height: i64,
96    #[live(1.0)] width_scale: f64,
97    #[live(ImageAnimation::BounceFps(25.0))] animation: ImageAnimation,
98    #[rust] last_time: Option<f64>,
99    #[rust] animation_frame: f64,
100    #[visible] #[live(true)] visible: bool,
101    #[rust] next_frame: NextFrame,
102    #[live] fit: ImageFit,
103    #[live] source: LiveDependency,
104    #[rust] async_image_path: Option<PathBuf>,
105    #[rust] async_image_size: Option<(usize, usize)>,
106    #[rust] texture: Option<Texture>,
107}
108
109impl ImageCacheImpl for Image {
110    fn get_texture(&self, _id:usize) -> &Option<Texture> {
111        &self.texture
112    }
113    
114    fn set_texture(&mut self, texture: Option<Texture>, _id:usize) {
115        self.texture = texture;
116    }
117}
118
119impl LiveHook for Image{
120    fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
121        match apply.from{
122            ApplyFrom::NewFromDoc{..}|// newed from DSL,
123            ApplyFrom:: UpdateFromDoc{..}|
124            ApplyFrom::Over{..}=>{
125                self.lazy_create_image_cache(cx);
126                let source = self.source.clone();
127                if source.as_str().len()>0 {
128                    let _ = self.load_image_dep_by_path(cx, source.as_str(), 0);
129                }
130            }
131            _=>()
132        }
133    }
134}
135
136impl Widget for Image {
137    fn handle_event(&mut self, cx:&mut Cx, event:&Event, _scope:&mut Scope){
138        if self.animator_handle_event(cx, event).must_redraw() {
139            self.draw_bg.redraw(cx);
140        }
141        // lets check if we have a post action
142        if let Event::Actions(actions) = &event{
143            for action in actions{
144                if let Some(AsyncImageLoad{image_path, result}) = &action.downcast_ref(){
145                    if let Some(result) = result.borrow_mut().take(){
146                        // we have a result for the image_cache to load up
147                        self.process_async_image_load(cx, image_path, result);
148                    }
149                    if self.async_image_size.is_some() && self.async_image_path.clone() == Some(image_path.to_path_buf()){ // see if we can load from cache
150                        self.load_image_from_cache(cx, image_path, 0);
151                        self.async_image_size = None;
152                        self.animator_play(cx, id!(async_load.off));
153                        self.redraw(cx);
154                    }
155                }
156            }
157        }
158        if let Some(nf) = self.next_frame.is_event(event) {
159            // compute the next frame and patch things up
160            if let Some(image_texture) = &self.texture {
161                let (texture_width, texture_height) = image_texture.get_format(cx).vec_width_height().unwrap_or((self.min_width as usize, self.min_height as usize));
162                if let Some(animation) = image_texture.animation(cx).clone(){
163                    let delta = if let Some(last_time) = &self.last_time{
164                        nf.time - last_time
165                    }
166                    else{
167                        0.0
168                    };
169                    self.last_time = Some(nf.time);
170                    let num_frames = animation.num_frames as f64;
171                    match self.animation{
172                        ImageAnimation::Stop=>{
173                            
174                        }
175                        ImageAnimation::Frame(frame)=>{
176                            self.animation_frame = frame;                                                   
177                        }
178                        ImageAnimation::Factor(pos)=>{
179                            self.animation_frame = pos * (num_frames - 1.0);         
180                        }
181                        ImageAnimation::Once=>{
182                            self.animation_frame += 1.0;
183                            if self.animation_frame >= num_frames{
184                                self.animation_frame = num_frames - 1.0;
185                            }
186                            else{
187                                self.next_frame = cx.new_next_frame();
188                            }
189                        }
190                        ImageAnimation::Loop=>{
191                            self.animation_frame += 1.0;
192                            if self.animation_frame >= num_frames{
193                                self.animation_frame = 0.0;
194                            }
195                            self.next_frame = cx.new_next_frame();
196                        }
197                        ImageAnimation::Bounce=>{
198                            self.animation_frame += 1.0;
199                            if self.animation_frame >= num_frames * 2.0{
200                                self.animation_frame = 0.0;
201                            }
202                            self.next_frame = cx.new_next_frame();
203                        }
204                        ImageAnimation::OnceFps(fps)=>{
205                            self.animation_frame += delta * fps;
206                            if self.animation_frame >= num_frames{
207                                self.animation_frame = num_frames - 1.0;
208                            }
209                            else{
210                                self.next_frame = cx.new_next_frame();
211                            }
212                        }
213                        ImageAnimation::LoopFps(fps)=>{
214                            self.animation_frame += delta * fps;
215                            if self.animation_frame >= num_frames{
216                                self.animation_frame = 0.0;
217                            }
218                            self.next_frame = cx.new_next_frame();
219                        }
220                        ImageAnimation::BounceFps(fps)=>{
221                            self.animation_frame += delta * fps;
222                            if self.animation_frame >= num_frames * 2.0{
223                                self.animation_frame = 0.0;
224                            }
225                            self.next_frame = cx.new_next_frame();
226                        }
227                    }
228                    // alright now lets turn animation_frame into the right image_pan
229                    let last_pan = self.draw_bg.image_pan;
230                    
231                    let frame = if self.animation_frame >= num_frames{
232                        num_frames * 2.0 - 1.0 - self.animation_frame
233                    }
234                    else{
235                        self.animation_frame
236                    } as usize;
237                    
238                    let horizontal_frames = texture_width / animation.width;
239                    let xpos = ((frame % horizontal_frames) * animation.width) as f32 / texture_width as f32;
240                    let ypos = ((frame / horizontal_frames) * animation.height) as f32 / texture_height as f32;
241                    self.draw_bg.image_pan = vec2(xpos, ypos);
242                    if self.draw_bg.image_pan != last_pan{
243                        // patch it into the area
244                        self.draw_bg.update_instance_area_value(cx, id!(image_pan))
245                    }
246                }
247            }
248        }
249    }
250    
251    fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep {
252        self.draw_walk(cx, walk)
253    }
254}
255
256impl Image {
257    /// Returns the original size of the image in pixels (not its displayed size).
258    ///
259    /// Returns `None` if the image has not been loaded into a texture yet.
260    pub fn size_in_pixels(&self, cx: &mut Cx) -> Option<(usize, usize)> {
261        self.texture.as_ref()
262            .and_then(|t| t.get_format(cx).vec_width_height())
263    }
264
265    /// True if a texture has been set on this `Image`.
266    pub fn has_texture(&self) -> bool {
267        self.texture.is_some()
268    }
269
270    pub fn draw_walk(&mut self, cx: &mut Cx2d, mut walk: Walk) -> DrawStep {
271        if !self.visible{
272            return DrawStep::done()
273        }
274        // alright we get a walk. depending on our aspect ratio
275        // we change either nothing, or width or height
276        let rect = cx.peek_walk_turtle(walk);
277        let dpi = cx.current_dpi_factor();
278        
279        let (width, height) = if let Some((w,h)) = &self.async_image_size{
280            // still loading
281            
282            (*w as f64,*h as f64)
283        }else if let Some(image_texture) = &self.texture {
284            self.draw_bg.draw_vars.set_texture(0, image_texture);
285            let (width,height) = image_texture.get_format(cx).vec_width_height().unwrap_or((self.min_width as usize, self.min_height as usize));
286            if let Some(animation) = image_texture.animation(cx){
287                let (w,h) = (animation.width as f64, animation.height as f64);
288                self.next_frame = cx.new_next_frame();
289                // we have an animation. lets compute the scale and zoom for a certain frame
290                let scale_x = w as f32 / width as f32;
291                let scale_y = h as f32 / height as f32;
292                self.draw_bg.image_scale = vec2(scale_x, scale_y);
293                (w,h)
294            }
295            else{
296                self.draw_bg.image_scale = vec2(1.0,1.0);
297                self.draw_bg.image_pan = vec2(0.0,0.0);
298                (width as f64 * self.width_scale, height as f64)
299            }
300        }
301        else {
302            self.draw_bg.draw_vars.empty_texture(0);
303            (self.min_width as f64 / dpi, self.min_height as f64 / dpi)
304        };
305        
306        let aspect = width / height;
307        match self.fit {
308            ImageFit::Size => {
309                walk.width = Size::Fixed(width);
310                walk.height = Size::Fixed(height);
311            }
312            ImageFit::Stretch => {
313            }
314            ImageFit::Horizontal => {
315                walk.height = Size::Fixed(rect.size.x / aspect);
316            }
317            ImageFit::Vertical => {
318                walk.width = Size::Fixed(rect.size.y * aspect);
319            }
320            ImageFit::Smallest => {
321                let walk_height = rect.size.x / aspect;
322                if walk_height > rect.size.y {
323                    walk.width = Size::Fixed(rect.size.y * aspect);
324                }
325                else {
326                    walk.height = Size::Fixed(walk_height);
327                }
328            }
329            ImageFit::Biggest => {
330                let walk_height = rect.size.x / aspect;
331                if walk_height < rect.size.y {
332                    walk.width = Size::Fixed(rect.size.y * aspect);
333                }
334                else {
335                    walk.height = Size::Fixed(walk_height);
336                }
337            }
338        }
339        
340        
341        self.draw_bg.draw_walk(cx, walk);
342        
343        DrawStep::done()
344    }
345    
346    /// Loads the image at the given `image_path` on disk into this `ImageRef`.
347    pub fn load_image_file_by_path_async(&mut self, cx: &mut Cx,  image_path: &Path) -> Result<(), ImageError> {
348        if let Ok(result) = self.load_image_file_by_path_async_impl(cx, image_path, 0){
349            match result{
350                AsyncLoadResult::Loading(w,h)=>{
351                    self.async_image_size = Some((w,h));
352                    self.async_image_path = Some(image_path.into());
353                    self.animator_play(cx, id!(async_load.on));
354                    self.redraw(cx);
355                }
356                AsyncLoadResult::Loaded=>{
357                    self.redraw(cx);
358                }
359            }
360            // lets set the w-h
361        }
362        Ok(())
363    }    
364    pub fn load_image_from_data_async(&mut self, cx: &mut Cx, image_path: &Path, data: Arc<Vec<u8>>) -> Result<(), ImageError> {
365        if let Ok(result) = self.load_image_from_data_async_impl(cx, image_path, data, 0){
366            match result{
367                AsyncLoadResult::Loading(w,h)=>{
368                    self.async_image_size = Some((w,h));
369                    self.async_image_path = Some(image_path.into());
370                    self.animator_play(cx, id!(async_load.on));
371                    self.redraw(cx);
372                }
373                AsyncLoadResult::Loaded=>{
374                    self.redraw(cx);
375                }
376            }
377            // lets set the w-h
378        }
379        Ok(())
380    }
381}
382
383pub enum AsyncLoad{
384    Yes,
385    No
386}
387
388impl ImageRef {
389    /// Loads the image at the given `image_path` resource into this `ImageRef`.
390    pub fn load_image_dep_by_path(&self, cx: &mut Cx, image_path: &str) -> Result<(), ImageError> {
391        if let Some(mut inner) = self.borrow_mut() {
392            inner.load_image_dep_by_path(cx, image_path, 0)
393        } else {
394            Ok(()) // preserving existing behavior of silent failures.
395        }
396    }
397    
398    /// Loads the image at the given `image_path` on disk into this `ImageRef`.
399    pub fn load_image_file_by_path(&self, cx: &mut Cx,  image_path: &Path) -> Result<(), ImageError> {
400        if let Some(mut inner) = self.borrow_mut() {
401            inner.load_image_file_by_path(cx, image_path, 0)
402        } else {
403            Ok(()) // preserving existing behavior of silent failures.
404        }
405    }
406    
407    /// Loads the image at the given `image_path` on disk into this `ImageRef`.
408    pub fn load_image_file_by_path_async(&self, cx: &mut Cx,  image_path: &Path) -> Result<(), ImageError> {
409        if let Some(mut inner) = self.borrow_mut() {
410            return inner.load_image_file_by_path_async(cx, image_path)
411        }
412        Ok(())
413    }    
414            
415    /// Loads the image at the given `image_path` on disk into this `ImageRef`.
416    pub fn load_image_from_data_async(&self, cx: &mut Cx,  image_path: &Path, data:Arc<Vec<u8>>) -> Result<(), ImageError> {
417        if let Some(mut inner) = self.borrow_mut() {
418            return inner.load_image_from_data_async(cx, image_path, data)
419        }
420        Ok(())
421    }    
422    
423    /// Loads a JPEG into this `ImageRef` by decoding the given encoded JPEG `data`.
424    pub fn load_jpg_from_data(&self, cx: &mut Cx, data: &[u8]) -> Result<(), ImageError> {
425        if let Some(mut inner) = self.borrow_mut() {
426            inner.load_jpg_from_data(cx, data, 0)
427        } else {
428            Ok(()) // preserving existing behavior of silent failures.
429        }
430    }
431    
432    /// Loads a PNG into this `ImageRef` by decoding the given encoded PNG `data`.
433    pub fn load_png_from_data(&self, cx: &mut Cx, data: &[u8]) -> Result<(), ImageError> {
434        if let Some(mut inner) = self.borrow_mut() {
435            inner.load_png_from_data(cx, data, 0)
436        } else {
437            Ok(()) // preserving existing behavior of silent failures.
438        }
439    }
440    
441    pub fn set_texture(&self, cx:&mut Cx, texture: Option<Texture>) {
442        if let Some(mut inner) = self.borrow_mut() {
443            inner.texture = texture;
444            if cx.in_draw_event(){
445                inner.redraw(cx);
446            }
447        }
448    }
449    
450    pub fn set_uniform(&self, cx: &Cx, uniform: &[LiveId], value: &[f32]) {
451        if let Some(mut inner) = self.borrow_mut() {
452            inner.draw_bg.set_uniform(cx, uniform, value);
453        }
454    }
455
456    /// See [`Image::size_in_pixels()`].
457    pub fn size_in_pixels(&self, cx: &mut Cx) -> Option<(usize, usize)> {
458        if let Some(inner) = self.borrow() {
459            inner.size_in_pixels(cx)
460        } else {
461            None
462        }
463    }
464
465    /// See [`Image::has_texture()`].
466    pub fn has_texture(&self) -> bool {
467        if let Some(inner) = self.borrow() {
468            inner.has_texture()
469        } else {
470            false
471        }
472    }
473}
474