Browse Source

app: add AbstractAnimation with a SequenceAnimation impl, and a Video node type (mostly stubs/untested for now)

darkfi 11 tháng trước cách đây
mục cha
commit
a123bc0556
5 tập tin đã thay đổi với 304 bổ sung1 xóa
  1. 57 0
      bin/app/src/gfx/anim.rs
  2. 8 1
      bin/app/src/gfx/mod.rs
  3. 1 0
      bin/app/src/scene.rs
  4. 4 0
      bin/app/src/ui/mod.rs
  5. 234 0
      bin/app/src/ui/video.rs

+ 57 - 0
bin/app/src/gfx/anim.rs

@@ -0,0 +1,57 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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 std::cell::RefCell;
+
+use super::DrawCall;
+
+pub(super) trait AbstractAnimation: std::fmt::Debug {
+    fn tick(&self) -> DrawCall;
+}
+
+#[derive(Debug)]
+struct SequenceAnimation {
+    oneshot: bool,
+    frames: Vec<SequenceAnimationFrame>,
+    state: RefCell<SequenceAnimationState>,
+}
+#[derive(Debug)]
+struct SequenceAnimationFrame {
+    dc: DrawCall,
+    duration: std::time::Duration,
+}
+#[derive(Debug)]
+struct SequenceAnimationState {
+    /// Timer between frames
+    timer: Option<std::time::Instant>,
+    current_idx: usize,
+}
+
+impl AbstractAnimation for SequenceAnimation {
+    fn tick(&self) -> DrawCall {
+        let mut state = self.state.borrow_mut();
+
+        let elapsed = state.timer.get_or_insert_with(|| std::time::Instant::now()).elapsed();
+        assert!(state.current_idx < self.frames.len());
+        if elapsed >= self.frames[state.current_idx].duration {
+            state.current_idx = (state.current_idx + 1) % self.frames.len();
+        }
+
+        self.frames[state.current_idx].dc.clone()
+    }
+}

+ 8 - 1
bin/app/src/gfx/mod.rs

@@ -41,6 +41,8 @@ use std::{
     },
 };
 
+mod anim;
+use anim::AbstractAnimation;
 mod favico;
 mod linalg;
 pub use linalg::{Dimension, Point, Rectangle};
@@ -475,10 +477,11 @@ enum DrawInstruction {
     SetPos(Point),
     ApplyView(Rectangle),
     Draw(DrawMesh),
+    Animation { anim: Arc<dyn AbstractAnimation> },
     EnableDebug,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 struct DrawCall {
     instrs: Vec<DrawInstruction>,
     dcs: Vec<DcId>,
@@ -627,6 +630,10 @@ impl<'a> RenderContext<'a> {
                     self.ctx.apply_bindings(&bindings);
                     self.ctx.draw(0, mesh.num_elements, 1);
                 }
+                DrawInstruction::Animation { anim } => {
+                    let dc = anim.tick();
+                    self.draw_call(&dc, indent + 1, is_debug);
+                }
                 DrawInstruction::EnableDebug => {
                     if !is_debug {
                         indent = 0;

+ 1 - 0
bin/app/src/scene.rs

@@ -544,6 +544,7 @@ pub enum Pimpl {
     ChatEdit(ui::ChatEditPtr),
     ChatView(ui::ChatViewPtr),
     Image(ui::ImagePtr),
+    Video(ui::VideoPtr),
     Button(ui::ButtonPtr),
     Shortcut(ui::ShortcutPtr),
     Gesture(ui::GesturePtr),

+ 4 - 0
bin/app/src/ui/mod.rs

@@ -44,6 +44,8 @@ mod gesture;
 pub use gesture::GesturePtr;
 mod image;
 pub use image::{Image, ImagePtr};
+mod video;
+pub use video::{Video, VideoPtr};
 mod vector_art;
 pub use vector_art::{
     shape::{ShapeVertex, VectorShape},
@@ -203,6 +205,7 @@ pub fn get_ui_object_ptr(node: &SceneNode3) -> Arc<dyn UIObject + Send> {
         Pimpl::ChatEdit(obj) => obj.clone(),
         Pimpl::ChatView(obj) => obj.clone(),
         Pimpl::Image(obj) => obj.clone(),
+        Pimpl::Video(obj) => obj.clone(),
         Pimpl::Button(obj) => obj.clone(),
         Pimpl::EmojiPicker(obj) => obj.clone(),
         Pimpl::Shortcut(obj) => obj.clone(),
@@ -219,6 +222,7 @@ pub fn get_ui_object3<'a>(node: &'a SceneNode3) -> &'a dyn UIObject {
         Pimpl::ChatEdit(obj) => obj.as_ref(),
         Pimpl::ChatView(obj) => obj.as_ref(),
         Pimpl::Image(obj) => obj.as_ref(),
+        Pimpl::Video(obj) => obj.as_ref(),
         Pimpl::Button(obj) => obj.as_ref(),
         Pimpl::EmojiPicker(obj) => obj.as_ref(),
         Pimpl::Shortcut(obj) => obj.as_ref(),

+ 234 - 0
bin/app/src/ui/video.rs

@@ -0,0 +1,234 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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_trait::async_trait;
+use image::ImageReader;
+use parking_lot::Mutex as SyncMutex;
+use rand::{rngs::OsRng, Rng};
+use std::{io::Cursor, sync::Arc};
+
+use crate::{
+    gfx::{
+        gfxtag, GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, ManagedTexturePtr, Rectangle,
+        RenderApi,
+    },
+    mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
+    prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyRect, PropertyStr, PropertyUint32, Role},
+    scene::{Pimpl, SceneNodeWeak},
+    util::unixtime,
+    ExecutorPtr,
+};
+
+use super::{DrawTrace, DrawUpdate, OnModify, UIObject};
+
+macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::video", $($arg)*); } }
+
+pub type VideoPtr = Arc<Video>;
+
+pub struct Video {
+    node: SceneNodeWeak,
+    render_api: RenderApi,
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
+
+    texture: SyncMutex<Option<ManagedTexturePtr>>,
+    dc_key: u64,
+
+    rect: PropertyRect,
+    uv: PropertyRect,
+    z_index: PropertyUint32,
+    priority: PropertyUint32,
+    path: PropertyStr,
+
+    parent_rect: SyncMutex<Option<Rectangle>>,
+}
+
+impl Video {
+    pub async fn new(node: SceneNodeWeak, render_api: RenderApi) -> Pimpl {
+        t!("Video::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 priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
+        let path = PropertyStr::wrap(node_ref, Role::Internal, "path", 0).unwrap();
+
+        let self_ = Arc::new(Self {
+            node,
+            render_api,
+            tasks: SyncMutex::new(vec![]),
+
+            texture: SyncMutex::new(None),
+            dc_key: OsRng.gen(),
+
+            rect,
+            uv,
+            z_index,
+            priority,
+            path,
+
+            parent_rect: SyncMutex::new(None),
+        });
+
+        Pimpl::Video(self_)
+    }
+
+    async fn reload(self: Arc<Self>, batch: BatchGuardPtr) {
+        let texture = self.load_texture();
+        *self.texture.lock() = Some(texture);
+
+        self.clone().redraw(batch).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() = res,
+            Err(e) => {
+                error!(target: "ui::video", "Unable to open video: {path}: {e}");
+                panic!("Resource not found! {e}");
+            }
+        });
+        let data = std::mem::take(&mut *data.lock());
+        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();
+
+        self.render_api.new_texture(width, height, bmp, gfxtag!("img"))
+    }
+
+    async fn redraw(self: Arc<Self>, batch: BatchGuardPtr) {
+        let trace: DrawTrace = rand::random();
+        let timest = unixtime();
+        t!("redraw({:?}) [trace={trace}]", self.node.upgrade().unwrap());
+        let Some(parent_rect) = self.parent_rect.lock().clone() else { return };
+
+        let atom = &mut batch.spawn();
+        let Some(draw_update) = self.get_draw_calls(atom, parent_rect).await else {
+            error!(target: "ui::video", "Video failed to draw");
+            return
+        };
+        self.render_api.replace_draw_calls(batch.id, timest, draw_update.draw_calls);
+        t!("redraw() DONE [trace={trace}]");
+    }
+
+    /// 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(gfxtag!("img"));
+        mesh.draw_box(&mesh_rect, COLOR_WHITE, &uv);
+        mesh.alloc(&self.render_api)
+    }
+
+    async fn get_draw_calls(
+        &self,
+        atom: &mut PropertyAtomicGuard,
+        parent_rect: Rectangle,
+    ) -> Option<DrawUpdate> {
+        self.rect.eval(atom, &parent_rect).ok()?;
+        let rect = self.rect.get();
+        self.uv.eval(atom, &rect).ok()?;
+
+        let mesh = self.regen_mesh();
+        let texture = self.texture.lock().clone().expect("Node missing texture_id!");
+
+        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::new(
+                    vec![GfxDrawInstruction::Move(rect.pos()), GfxDrawInstruction::Draw(mesh)],
+                    vec![],
+                    self.z_index.get(),
+                    "img",
+                ),
+            )],
+        })
+    }
+}
+
+#[async_trait]
+impl UIObject for Video {
+    fn priority(&self) -> u32 {
+        self.priority.get()
+    }
+
+    fn init(&self) {
+        *self.texture.lock() = Some(self.load_texture());
+    }
+
+    async fn start(self: Arc<Self>, ex: ExecutorPtr) {
+        let me = Arc::downgrade(&self);
+
+        let mut on_modify = OnModify::new(ex, self.node.clone(), 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.lock() = on_modify.tasks;
+    }
+
+    fn stop(&self) {
+        self.tasks.lock().clear();
+        *self.parent_rect.lock() = None;
+        *self.texture.lock() = None;
+    }
+
+    async fn draw(
+        &self,
+        parent_rect: Rectangle,
+        trace: DrawTrace,
+        atom: &mut PropertyAtomicGuard,
+    ) -> Option<DrawUpdate> {
+        t!("Video::draw() [trace={trace}]");
+        *self.parent_rect.lock() = Some(parent_rect);
+        self.get_draw_calls(atom, parent_rect).await
+    }
+}
+
+impl Drop for Video {
+    fn drop(&mut self) {
+        let atom = self.render_api.make_guard(gfxtag!("Video::drop"));
+        self.render_api.replace_draw_calls(
+            atom.batch_id,
+            unixtime(),
+            vec![(self.dc_key, Default::default())],
+        );
+    }
+}
+