/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use async_trait::async_trait; use image::ImageReader; use rand::{rngs::OsRng, Rng}; use std::{ io::Cursor, sync::{Arc, Mutex as SyncMutex, OnceLock, Weak}, }; use crate::{ gfx::{ GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, ManagedTexturePtr, Rectangle, RenderApi, }, mesh::{MeshBuilder, MeshInfo, COLOR_WHITE}, prop::{PropertyPtr, PropertyRect, PropertyStr, PropertyUint32, Role}, scene::{Pimpl, SceneNodePtr, SceneNodeWeak}, ExecutorPtr, }; use super::{DrawUpdate, OnModify, UIObject}; pub type ImagePtr = Arc; pub struct Image { node: SceneNodeWeak, render_api: RenderApi, tasks: OnceLock>>, mesh: SyncMutex>, texture: SyncMutex>, dc_key: u64, rect: PropertyRect, uv: PropertyRect, z_index: PropertyUint32, path: PropertyStr, parent_rect: SyncMutex>, } impl Image { pub async fn new(node: SceneNodeWeak, render_api: RenderApi, ex: ExecutorPtr) -> Pimpl { debug!(target: "ui::image", "Image::new()"); let node_ref = &node.upgrade().unwrap(); let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap(); let uv = PropertyRect::wrap(node_ref, Role::Internal, "uv").unwrap(); let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap(); let path = PropertyStr::wrap(node_ref, Role::Internal, "path", 0).unwrap(); let node_name = node_ref.name.clone(); let node_id = node_ref.id; let self_ = Arc::new(Self { node, render_api, tasks: OnceLock::new(), mesh: SyncMutex::new(None), texture: SyncMutex::new(None), dc_key: OsRng.gen(), rect, uv, z_index, path, parent_rect: SyncMutex::new(None), }); *self_.texture.lock().unwrap() = Some(self_.load_texture()); Pimpl::Image(self_) } async fn reload(self: Arc) { let texture = self.load_texture(); let old_texture = std::mem::replace(&mut *self.texture.lock().unwrap(), Some(texture)); self.clone().redraw().await; } fn load_texture(&self) -> ManagedTexturePtr { let path = self.path.get(); // TODO we should NOT use panic here let data = Arc::new(SyncMutex::new(vec![])); let data2 = data.clone(); miniquad::fs::load_file(&path.clone(), move |res| match res { Ok(res) => *data2.lock().unwrap() = res, Err(e) => { error!(target: "ui::image", "Unable to open image: {path}"); panic!("Resource not found!"); } }); let data = std::mem::take(&mut *data.lock().unwrap()); let img = ImageReader::new(Cursor::new(data)).with_guessed_format().unwrap().decode().unwrap(); let img = img.to_rgba8(); //let img = image::ImageReader::open(path).unwrap().decode().unwrap().to_rgba8(); let width = img.width() as u16; let height = img.height() as u16; let bmp = img.into_raw(); let texture = self.render_api.new_texture(width, height, bmp); texture } async fn redraw(self: Arc) { let Some(parent_rect) = self.parent_rect.lock().unwrap().clone() else { return }; let Some(draw_update) = self.get_draw_calls(parent_rect).await else { error!(target: "ui::image", "Image failed to draw"); return; }; self.render_api.replace_draw_calls(draw_update.draw_calls); debug!(target: "ui::image", "replace draw calls done"); for buff in draw_update.freed_buffers { self.render_api.delete_buffer(buff); } } /// Called whenever any property changes. fn regen_mesh(&self) -> MeshInfo { let rect = self.rect.get(); let uv = self.uv.get(); let mesh_rect = Rectangle::from([0., 0., rect.w, rect.h]); let mut mesh = MeshBuilder::new(); mesh.draw_box(&mesh_rect, COLOR_WHITE, &uv); mesh.alloc(&self.render_api) } async fn get_draw_calls(&self, parent_rect: Rectangle) -> Option { self.rect.eval(&parent_rect).ok()?; let rect = self.rect.get(); self.uv.eval(&rect).ok()?; let mesh = self.regen_mesh(); let old_mesh = std::mem::replace(&mut *self.mesh.lock().unwrap(), Some(mesh.clone())); let texture = self.texture.lock().unwrap().clone().expect("Node missing texture_id!"); // We're finished with these so clean up. let mut freed_buffers = vec![]; if let Some(old) = old_mesh { freed_buffers.push(old.vertex_buffer); freed_buffers.push(old.index_buffer); } let mesh = GfxDrawMesh { vertex_buffer: mesh.vertex_buffer, index_buffer: mesh.index_buffer, texture: Some(texture), num_elements: mesh.num_elements, }; Some(DrawUpdate { key: self.dc_key, draw_calls: vec![( self.dc_key, GfxDrawCall { instrs: vec![ GfxDrawInstruction::Move(rect.pos()), GfxDrawInstruction::Draw(mesh), ], dcs: vec![], z_index: self.z_index.get(), }, )], freed_buffers, }) } } #[async_trait] impl UIObject for Image { fn z_index(&self) -> u32 { self.z_index.get() } async fn start(self: Arc, ex: ExecutorPtr) { let me = Arc::downgrade(&self); let node_ref = &self.node.upgrade().unwrap(); let node_name = node_ref.name.clone(); let node_id = node_ref.id; let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone()); on_modify.when_change(self.rect.prop(), Self::redraw); on_modify.when_change(self.uv.prop(), Self::redraw); on_modify.when_change(self.z_index.prop(), Self::redraw); on_modify.when_change(self.path.prop(), Self::reload); self.tasks.set(on_modify.tasks); } async fn draw(&self, parent_rect: Rectangle) -> Option { debug!(target: "ui::image", "Image::draw()"); *self.parent_rect.lock().unwrap() = Some(parent_rect); self.get_draw_calls(parent_rect).await } } impl Drop for Image { fn drop(&mut self) { // TODO: Delete own draw call // Free buffers // Should this be in drop? if let Some(mesh) = &*self.mesh.lock().unwrap() { let vertex_buffer = mesh.vertex_buffer; let index_buffer = mesh.index_buffer; self.render_api.delete_buffer(vertex_buffer); self.render_api.delete_buffer(index_buffer); } } }