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 }