/ bin / app / src / ui / image.rs
image.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  use async_trait::async_trait;
 20  use image::ImageReader;
 21  use parking_lot::Mutex as SyncMutex;
 22  use rand::{rngs::OsRng, Rng};
 23  use std::{io::Cursor, sync::Arc};
 24  
 25  use crate::{
 26      gfx::{gfxtag, DrawCall, DrawInstruction, DrawMesh, ManagedTexturePtr, Rectangle, RenderApi},
 27      mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
 28      prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyRect, PropertyStr, PropertyUint32, Role},
 29      scene::{Pimpl, SceneNodeWeak},
 30      util::unixtime,
 31      ExecutorPtr,
 32  };
 33  
 34  use super::{DrawTrace, DrawUpdate, OnModify, UIObject};
 35  
 36  macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::image", $($arg)*); } }
 37  
 38  pub type ImagePtr = Arc<Image>;
 39  
 40  pub struct Image {
 41      node: SceneNodeWeak,
 42      render_api: RenderApi,
 43      tasks: SyncMutex<Vec<smol::Task<()>>>,
 44  
 45      texture: SyncMutex<Option<ManagedTexturePtr>>,
 46      dc_key: u64,
 47  
 48      rect: PropertyRect,
 49      uv: PropertyRect,
 50      z_index: PropertyUint32,
 51      priority: PropertyUint32,
 52      path: PropertyStr,
 53  
 54      parent_rect: SyncMutex<Option<Rectangle>>,
 55  }
 56  
 57  impl Image {
 58      pub async fn new(node: SceneNodeWeak, render_api: RenderApi) -> Pimpl {
 59          t!("Image::new()");
 60  
 61          let node_ref = &node.upgrade().unwrap();
 62          let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
 63          let uv = PropertyRect::wrap(node_ref, Role::Internal, "uv").unwrap();
 64          let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
 65          let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
 66          let path = PropertyStr::wrap(node_ref, Role::Internal, "path", 0).unwrap();
 67  
 68          let self_ = Arc::new(Self {
 69              node,
 70              render_api,
 71              tasks: SyncMutex::new(vec![]),
 72  
 73              texture: SyncMutex::new(None),
 74              dc_key: OsRng.gen(),
 75  
 76              rect,
 77              uv,
 78              z_index,
 79              priority,
 80              path,
 81  
 82              parent_rect: SyncMutex::new(None),
 83          });
 84  
 85          Pimpl::Image(self_)
 86      }
 87  
 88      async fn reload(self: Arc<Self>, batch: BatchGuardPtr) {
 89          let texture = self.load_texture();
 90          *self.texture.lock() = Some(texture);
 91  
 92          self.clone().redraw(batch).await;
 93      }
 94  
 95      fn load_texture(&self) -> ManagedTexturePtr {
 96          let path = self.path.get();
 97  
 98          // TODO we should NOT use panic here
 99          let data = Arc::new(SyncMutex::new(vec![]));
100          let data2 = data.clone();
101          miniquad::fs::load_file(&path.clone(), move |res| match res {
102              Ok(res) => *data2.lock() = res,
103              Err(e) => {
104                  error!(target: "ui::image", "Unable to open image: {path}: {e}");
105                  panic!("Resource not found! {e}");
106              }
107          });
108          let data = std::mem::take(&mut *data.lock());
109          let img =
110              ImageReader::new(Cursor::new(data)).with_guessed_format().unwrap().decode().unwrap();
111          let img = img.to_rgba8();
112  
113          //let img = image::ImageReader::open(path).unwrap().decode().unwrap().to_rgba8();
114  
115          let width = img.width() as u16;
116          let height = img.height() as u16;
117          let bmp = img.into_raw();
118  
119          self.render_api.new_texture(width, height, bmp, gfxtag!("img"))
120      }
121  
122      async fn redraw(self: Arc<Self>, batch: BatchGuardPtr) {
123          let trace: DrawTrace = rand::random();
124          let timest = unixtime();
125          t!("redraw({:?}) [trace={trace}]", self.node.upgrade().unwrap());
126          let Some(parent_rect) = self.parent_rect.lock().clone() else { return };
127  
128          let atom = &mut batch.spawn();
129          let Some(draw_update) = self.get_draw_calls(atom, parent_rect).await else {
130              error!(target: "ui::image", "Image failed to draw");
131              return
132          };
133          self.render_api.replace_draw_calls(batch.id, timest, draw_update.draw_calls);
134          t!("redraw() DONE [trace={trace}]");
135      }
136  
137      /// Called whenever any property changes.
138      fn regen_mesh(&self) -> MeshInfo {
139          let rect = self.rect.get();
140          let uv = self.uv.get();
141          let mesh_rect = Rectangle::from([0., 0., rect.w, rect.h]);
142          let mut mesh = MeshBuilder::new(gfxtag!("img"));
143          mesh.draw_box(&mesh_rect, COLOR_WHITE, &uv);
144          mesh.alloc(&self.render_api)
145      }
146  
147      async fn get_draw_calls(
148          &self,
149          atom: &mut PropertyAtomicGuard,
150          parent_rect: Rectangle,
151      ) -> Option<DrawUpdate> {
152          self.rect.eval(atom, &parent_rect).ok()?;
153          let rect = self.rect.get();
154          self.uv.eval(atom, &rect).ok()?;
155  
156          let mesh = self.regen_mesh();
157          let texture = self.texture.lock().clone().expect("Node missing texture_id!");
158  
159          let mesh = DrawMesh {
160              vertex_buffer: mesh.vertex_buffer,
161              index_buffer: mesh.index_buffer,
162              texture: Some(texture),
163              num_elements: mesh.num_elements,
164          };
165  
166          Some(DrawUpdate {
167              key: self.dc_key,
168              draw_calls: vec![(
169                  self.dc_key,
170                  DrawCall::new(
171                      vec![DrawInstruction::Move(rect.pos()), DrawInstruction::Draw(mesh)],
172                      vec![],
173                      self.z_index.get(),
174                      "img",
175                  ),
176              )],
177          })
178      }
179  }
180  
181  #[async_trait]
182  impl UIObject for Image {
183      fn priority(&self) -> u32 {
184          self.priority.get()
185      }
186  
187      fn init(&self) {
188          *self.texture.lock() = Some(self.load_texture());
189      }
190  
191      async fn start(self: Arc<Self>, ex: ExecutorPtr) {
192          let me = Arc::downgrade(&self);
193  
194          let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
195          on_modify.when_change(self.rect.prop(), Self::redraw);
196          on_modify.when_change(self.uv.prop(), Self::redraw);
197          on_modify.when_change(self.z_index.prop(), Self::redraw);
198          on_modify.when_change(self.path.prop(), Self::reload);
199  
200          *self.tasks.lock() = on_modify.tasks;
201      }
202  
203      fn stop(&self) {
204          self.tasks.lock().clear();
205          *self.parent_rect.lock() = None;
206          *self.texture.lock() = None;
207      }
208  
209      async fn draw(
210          &self,
211          parent_rect: Rectangle,
212          trace: DrawTrace,
213          atom: &mut PropertyAtomicGuard,
214      ) -> Option<DrawUpdate> {
215          t!("Image::draw() [trace={trace}]");
216          *self.parent_rect.lock() = Some(parent_rect);
217          self.get_draw_calls(atom, parent_rect).await
218      }
219  }
220  
221  impl Drop for Image {
222      fn drop(&mut self) {
223          let atom = self.render_api.make_guard(gfxtag!("Image::drop"));
224          self.render_api.replace_draw_calls(
225              atom.batch_id,
226              unixtime(),
227              vec![(self.dc_key, Default::default())],
228          );
229      }
230  }