Просмотр исходного кода

wallet: render da fucking king gnu (display images)

darkfi 2 лет назад
Родитель
Сommit
7ac19cd843

+ 1 - 1
bin/darkwallet/Cargo.toml

@@ -16,7 +16,7 @@ harfbuzz-sys = { git = "https://github.com/narodnik/rust-harfbuzz", features = [
 # Old and crap
 #harfbuzz_rs = { git = "https://github.com/narodnik/hbrs2.git", features = ["freetype"] }
 freetype-rs = { version = "0.37.0", features = ["bundled"] }
-image = "0.25.1"
+image = "0.25.2"
 log = "0.4.22"
 glam = "0.28.0"
 #zmq = "0.10.0"

+ 40 - 1
bin/darkwallet/src/app.rs

@@ -27,7 +27,7 @@ use crate::{
     prop::{Property, PropertySubType, PropertyType},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId, SceneNodeType},
     text2::TextShaperPtr,
-    ui::{chatview, ChatView, EditBox, Mesh, RenderLayer, Stoppable, Text, Window},
+    ui::{chatview, ChatView, EditBox, Image, Mesh, RenderLayer, Stoppable, Text, Window},
 };
 
 //fn print_type_of<T>(_: &T) {
@@ -303,6 +303,28 @@ impl App {
 
         sg.link(node_id, layer_node_id).unwrap();
 
+        // Create KING GNU!
+        let node_id = create_image(&mut sg, "king");
+
+        let node = sg.get_node_mut(node_id).unwrap();
+        let prop = node.get_property("rect").unwrap();
+        prop.set_f32(0, 80.).unwrap();
+        prop.set_f32(1, 10.).unwrap();
+        prop.set_f32(2, 60.).unwrap();
+        prop.set_f32(3, 60.).unwrap();
+
+        node.set_property_str("path", "../../king.png").unwrap();
+
+        // Setup the pimpl
+        drop(sg);
+        let pimpl =
+            Image::new(self.ex.clone(), self.sg.clone(), node_id, self.render_api.clone()).await;
+        let mut sg = self.sg.lock().await;
+        let node = sg.get_node_mut(node_id).unwrap();
+        node.pimpl = pimpl;
+
+        sg.link(node_id, layer_node_id).unwrap();
+
         // Create some text
         let node_id = create_text(&mut sg, "label");
 
@@ -505,6 +527,23 @@ pub fn create_mesh(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
     node.id
 }
 
+pub fn create_image(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
+    let node = sg.add_node(name, SceneNodeType::RenderMesh);
+
+    let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_array_len(4);
+    prop.allow_exprs();
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("z_index", PropertyType::Uint32, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("path", PropertyType::Str, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    node.id
+}
+
 fn create_text(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
     let node = sg.add_node(name, SceneNodeType::RenderText);
 

+ 2 - 0
bin/darkwallet/src/scene.rs

@@ -50,6 +50,7 @@ pub enum SceneNodeType {
     Plugin = 15,
     ChatView = 16,
     EditBox = 17,
+    Image = 18,
 }
 
 pub struct ScenePath(Vec<String>);
@@ -651,6 +652,7 @@ pub enum Pimpl {
     Text(ui::TextPtr),
     EditBox(ui::EditBoxPtr),
     ChatView(ui::ChatViewPtr),
+    Image(ui::ImagePtr),
 }
 
 impl std::fmt::Debug for SceneNode {

+ 219 - 0
bin/darkwallet/src/ui/image.rs

@@ -0,0 +1,219 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+//use async_lock::Mutex;
+use miniquad::{BufferId, TextureId};
+use rand::{rngs::OsRng, Rng};
+use std::sync::{Arc, Mutex as SyncMutex, Weak};
+
+use crate::{
+    gfx2::{DrawCall, DrawInstruction, DrawMesh, Rectangle, RenderApi, RenderApiPtr, Vertex},
+    mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE},
+    prop::{
+        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyStr, PropertyUint32,
+    },
+    scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
+    text2::{self, Glyph, GlyphPositionIter, SpritePtr, TextShaper, TextShaperPtr},
+    util::zip3,
+};
+
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
+
+pub type ImagePtr = Arc<Image>;
+
+pub struct Image {
+    sg: SceneGraphPtr2,
+    render_api: RenderApiPtr,
+    tasks: Vec<smol::Task<()>>,
+
+    mesh: SyncMutex<Option<MeshInfo>>,
+    texture: SyncMutex<Option<TextureId>>,
+    dc_key: u64,
+
+    node_id: SceneNodeId,
+    rect: PropertyPtr,
+    z_index: PropertyUint32,
+    path: PropertyStr,
+}
+
+impl Image {
+    pub async fn new(
+        ex: Arc<smol::Executor<'static>>,
+        sg: SceneGraphPtr2,
+        node_id: SceneNodeId,
+        render_api: RenderApiPtr,
+    ) -> Pimpl {
+        let scene_graph = sg.lock().await;
+        let node = scene_graph.get_node(node_id).unwrap();
+        let node_name = node.name.clone();
+        let rect = node.get_property("rect").expect("Text::rect");
+        let z_index = PropertyUint32::wrap(node, "z_index", 0).unwrap();
+        let path = PropertyStr::wrap(node, "path", 0).unwrap();
+        drop(scene_graph);
+
+        let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
+            let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
+            on_modify.when_change(rect.clone(), Self::redraw);
+            on_modify.when_change(z_index.prop(), Self::redraw);
+            on_modify.when_change(path.prop(), Self::reload);
+
+            Self {
+                sg,
+                render_api,
+                tasks: on_modify.tasks,
+                mesh: SyncMutex::new(None),
+                texture: SyncMutex::new(None),
+                dc_key: OsRng.gen(),
+                node_id,
+                rect,
+                z_index,
+                path,
+            }
+        });
+
+        *self_.texture.lock().unwrap() = Some(self_.load_texture().await);
+
+        Pimpl::Image(self_)
+    }
+
+    async fn reload(self: Arc<Self>) {
+        let texture = self.load_texture().await;
+        let old_texture = std::mem::replace(&mut *self.texture.lock().unwrap(), Some(texture));
+
+        self.clone().redraw().await;
+
+        if let Some(old_texture) = old_texture {
+            self.render_api.delete_texture(old_texture);
+        }
+    }
+
+    async fn load_texture(&self) -> TextureId {
+        let path = self.path.get();
+        // TODO we should NOT use unwrap here
+        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_id = self.render_api.new_texture(width, height, bmp).await.unwrap();
+        texture_id
+    }
+
+    async fn redraw(self: Arc<Self>) {
+        let sg = self.sg.lock().await;
+        let node = sg.get_node(self.node_id).unwrap();
+
+        let Some(parent_rect) = get_parent_rect(&sg, node) else {
+            return;
+        };
+
+        let Some(draw_update) = self.draw(&sg, &parent_rect).await else {
+            error!(target: "ui::text", "Text {:?} failed to draw", node);
+            return;
+        };
+        self.render_api.replace_draw_calls(draw_update.draw_calls).await;
+        debug!(target: "ui::text", "replace draw calls done");
+    }
+
+    /// Called whenever any property changes.
+    async fn regen_mesh(&self, clip: Rectangle) -> MeshInfo {
+        let basic = Rectangle { x: 0., y: 0., w: 1., h: 1. };
+
+        let mut mesh = MeshBuilder::new();
+        mesh.draw_box(&basic, COLOR_WHITE, &basic);
+        mesh.alloc(&self.render_api).await.unwrap()
+    }
+
+    pub async fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
+        debug!(target: "ui::text", "Text::draw()");
+        // Only used for debug messages
+        let node = sg.get_node(self.node_id).unwrap();
+
+        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
+            panic!("Node {:?} bad rect property: {}", node, err);
+        }
+
+        let Ok(mut rect) = read_rect(self.rect.clone()) else {
+            panic!("Node {:?} bad rect property", node);
+        };
+
+        rect.x += parent_rect.x;
+        rect.y += parent_rect.x;
+
+        // draw will recalc this when it's None
+        let mesh = self.regen_mesh(rect.clone()).await;
+        let old_mesh = std::mem::replace(&mut *self.mesh.lock().unwrap(), Some(mesh.clone()));
+
+        let Some(texture_id) = *self.texture.lock().unwrap() else {
+            panic!("Node {:?} missing texture_id!", node);
+        };
+
+        // 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 = DrawMesh {
+            vertex_buffer: mesh.vertex_buffer,
+            index_buffer: mesh.index_buffer,
+            texture: Some(texture_id),
+            num_elements: mesh.num_elements,
+        };
+
+        let off_x = rect.x / parent_rect.w;
+        let off_y = rect.y / parent_rect.h;
+        // We could use pixels here if we want to. No difference really.
+        let scale_x = rect.w / parent_rect.w;
+        let scale_y = rect.h / parent_rect.h;
+        let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
+            glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
+
+        Some(DrawUpdate {
+            key: self.dc_key,
+            draw_calls: vec![(
+                self.dc_key,
+                DrawCall {
+                    instrs: vec![DrawInstruction::ApplyMatrix(model), DrawInstruction::Draw(mesh)],
+                    dcs: vec![],
+                    z_index: self.z_index.get(),
+                },
+            )],
+            freed_textures: vec![],
+            freed_buffers,
+        })
+    }
+}
+
+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);
+        }
+        let texture_id = self.texture.lock().unwrap().unwrap();
+        self.render_api.delete_texture(texture_id);
+    }
+}

+ 1 - 0
bin/darkwallet/src/ui/layer.rs

@@ -143,6 +143,7 @@ impl RenderLayer {
                 Pimpl::Text(txt) => txt.draw(&sg, &rect).await,
                 Pimpl::EditBox(editb) => editb.draw(&sg, &rect).await,
                 Pimpl::ChatView(chat) => chat.draw(&sg, &rect).await,
+                Pimpl::Image(img) => img.draw(&sg, &rect).await,
                 _ => {
                     error!(target: "ui::layer", "unhandled pimpl type");
                     continue

+ 2 - 0
bin/darkwallet/src/ui/mod.rs

@@ -31,6 +31,8 @@ pub mod chatview;
 pub use chatview::{ChatView, ChatViewPtr};
 mod editbox;
 pub use editbox::{EditBox, EditBoxPtr};
+mod image;
+pub use image::{Image, ImagePtr};
 mod mesh;
 pub use mesh::{Mesh, MeshPtr};
 mod layer;

+ 1 - 0
bin/darkwallet/src/ui/text.rs

@@ -160,6 +160,7 @@ impl Text {
     async fn redraw(self: Arc<Self>) {
         let old = self.render_info.lock().unwrap().clone();
 
+        // TODO move this to draw
         let render_info = Self::regen_mesh(
             &self.render_api,
             &self.text_shaper,