Ver código fonte

app: add SetAlpha to gfx, add layer alpha support and use it to fade in the netstatus overlay when shown

darkfi 12 horas atrás
pai
commit
f3e581ddbe

+ 6 - 0
bin/app/src/app/node.rs

@@ -71,6 +71,12 @@ pub fn create_layer(name: &str) -> SceneNode {
     prop.allow_exprs();
     node.add_property(prop).unwrap();
 
+    let mut prop = Property::new("alpha", PropertyType::Float32, PropertySubType::Null);
+    prop.set_ui_text("Alpha", "Layer transparency for all drawn content (0.0-1.0)");
+    prop.set_range_f32(0., 1.);
+    prop.set_defaults_f32(vec![1.0]).unwrap();
+    node.add_property(prop).unwrap();
+
     let prop = Property::new("z_index", PropertyType::Uint32, PropertySubType::Null);
     node.add_property(prop).unwrap();
 

+ 82 - 55
bin/app/src/app/schema/mod.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{fs::File, io::Write};
+use std::{fs::File, io::Write, sync::atomic::Ordering};
 
 use darkfi::system::msleep;
 
@@ -254,60 +254,62 @@ pub async fn make(
         content.setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
     window.link(content.clone());
 
-    // Splash layer with the scramble message
-    cc.add_const_f32("SPLASH_FONTSIZE", SPLASH_FONTSIZE);
-    cc.add_const_f32("SPLASH_MARGIN", SPLASH_MARGIN);
-    let splash_layer = create_layer("splash_layer");
-    let prop = splash_layer.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
-    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
-    prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
-    prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
-    splash_layer.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
-    splash_layer.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let splash_layer = splash_layer
-        .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
-        .await;
-    content.link(splash_layer.clone());
-
-    let node = create_text_scramble("splash_msg");
-    let prop = node.get_property("rect").unwrap();
-    prop.set_f32(atom, Role::App, 0, SPLASH_MARGIN).unwrap();
-    let code = cc.compile("h * 0.4").unwrap();
-    prop.set_expr(atom, Role::App, 1, code).unwrap();
-    let code = cc.compile("w - 2 * SPLASH_MARGIN").unwrap();
-    prop.set_expr(atom, Role::App, 2, code).unwrap();
-    prop.set_f32(atom, Role::App, 3, SPLASH_FONTSIZE * 1.2).unwrap();
-    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
-    node.set_property_f32(atom, Role::App, "font_size", SPLASH_FONTSIZE).unwrap();
-    node.set_property_enum(atom, Role::App, "text_align", "center").unwrap();
-    let prop = node.get_property("text_color").unwrap();
-    prop.set_f32(atom, Role::App, 0, 1.).unwrap();
-    prop.set_f32(atom, Role::App, 1, 1.).unwrap();
-    prop.set_f32(atom, Role::App, 2, 1.).unwrap();
-    prop.set_f32(atom, Role::App, 3, 1.).unwrap();
-    node.set_property_str(atom, Role::App, "text", "welcome back commander").unwrap();
-    let node = node
-        .setup(|me| {
-            TextScramble::new(
-                me,
-                window_scale.clone(),
-                app.renderer.clone(),
-                i18n_fish.clone(),
-                app.redraw_trigger.clone(),
-            )
-        })
-        .await;
-    splash_layer.link(node);
+    // Splash layer with the scramble message, shown only on first app launch
+    if app.is_first_time.load(Ordering::Relaxed) {
+        cc.add_const_f32("SPLASH_FONTSIZE", SPLASH_FONTSIZE);
+        cc.add_const_f32("SPLASH_MARGIN", SPLASH_MARGIN);
+        let splash_layer = create_layer("splash_layer");
+        let prop = splash_layer.get_property("rect").unwrap();
+        prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+        prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+        prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+        prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+        splash_layer.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+        splash_layer.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
+        let splash_layer = splash_layer
+            .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
+            .await;
+        content.link(splash_layer.clone());
 
-    // Hide the splash layer after 3s
-    let redraw = app.redraw_trigger.clone();
-    let hide_task = app.ex.spawn(async move {
-        msleep(3000).await;
-        let atom = &mut redraw.make_guard(gfxtag!("splash_layer hide"));
-        splash_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
-    });
-    app.tasks.lock().unwrap().push(hide_task);
+        let node = create_text_scramble("splash_msg");
+        let prop = node.get_property("rect").unwrap();
+        prop.set_f32(atom, Role::App, 0, SPLASH_MARGIN).unwrap();
+        let code = cc.compile("h * 0.4").unwrap();
+        prop.set_expr(atom, Role::App, 1, code).unwrap();
+        let code = cc.compile("w - 2 * SPLASH_MARGIN").unwrap();
+        prop.set_expr(atom, Role::App, 2, code).unwrap();
+        prop.set_f32(atom, Role::App, 3, SPLASH_FONTSIZE * 1.2).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
+        node.set_property_f32(atom, Role::App, "font_size", SPLASH_FONTSIZE).unwrap();
+        node.set_property_enum(atom, Role::App, "text_align", "center").unwrap();
+        let prop = node.get_property("text_color").unwrap();
+        prop.set_f32(atom, Role::App, 0, 1.).unwrap();
+        prop.set_f32(atom, Role::App, 1, 1.).unwrap();
+        prop.set_f32(atom, Role::App, 2, 1.).unwrap();
+        prop.set_f32(atom, Role::App, 3, 1.).unwrap();
+        node.set_property_str(atom, Role::App, "text", "welcome back commander").unwrap();
+        let node = node
+            .setup(|me| {
+                TextScramble::new(
+                    me,
+                    window_scale.clone(),
+                    app.renderer.clone(),
+                    i18n_fish.clone(),
+                    app.redraw_trigger.clone(),
+                )
+            })
+            .await;
+        splash_layer.link(node);
+
+        // Hide the splash layer after 3s
+        let redraw = app.redraw_trigger.clone();
+        let hide_task = app.ex.spawn(async move {
+            msleep(3000).await;
+            let atom = &mut redraw.make_guard(gfxtag!("splash_layer hide"));
+            splash_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+        });
+        app.tasks.lock().unwrap().push(hide_task);
+    }
 
     if COLOR_SCHEME == ColorScheme::DarkMode {
         let node = create_video("king");
@@ -554,9 +556,12 @@ pub async fn make(
 
     let sg_root = app.sg_root.clone();
     let redraw = app.redraw_trigger.clone();
+    let ex = app.ex.clone();
+    let ex_fade = app.ex.clone();
     let (slot, recvr) = Slot::new("reconnect_clicked");
     node.register("click", slot).unwrap();
-    let reconnect_task = app.ex.spawn(async move {
+    let reconnect_task = ex.spawn(async move {
+        let mut _fade_task = None;
         while let Ok(_) = recvr.recv().await {
             i!("Reconnect button clicked");
 
@@ -569,6 +574,28 @@ pub async fn make(
             let atom = &mut redraw.make_guard(gfxtag!("netstatus overlay toggle"));
             overlay.set_property_bool(atom, Role::App, "is_visible", !is_visible).unwrap();
 
+            if !is_visible {
+                // Start from fully transparent so the fade begins hidden
+                overlay.set_property_f32(atom, Role::App, "alpha", 0.).unwrap();
+
+                // Fade the overlay alpha from 0 to 1 over 1s
+                let overlay = overlay.clone();
+                let redraw = redraw.clone();
+                _fade_task = Some(ex_fade.spawn(async move {
+                    let steps = 50;
+                    for i in 1..=steps {
+                        msleep(1000 / steps as u64).await;
+                        let atom = &mut redraw.make_guard(gfxtag!("netstatus overlay fade"));
+                        overlay
+                            .set_property_f32(atom, Role::App, "alpha", i as f32 / steps as f32)
+                            .unwrap();
+                    }
+                }));
+            } else {
+                // Hiding cancels any in-flight fade
+                _fade_task = None;
+            }
+
             /*
             // Show netstat-klik icon
             let netstat_klik =

+ 29 - 4
bin/app/src/gfx/mod.rs

@@ -228,6 +228,7 @@ impl AsyncEncodable for DrawMesh {
 #[derive(Debug, Clone)]
 pub enum DrawInstruction {
     SetScale(f32),
+    SetAlpha(f32),
     Move(Point),
     SetPos(Point),
     ApplyView(Rectangle),
@@ -247,6 +248,7 @@ impl DrawInstruction {
     ) -> GfxDrawInstruction {
         match self {
             Self::SetScale(scale) => GfxDrawInstruction::SetScale(scale),
+            Self::SetAlpha(alpha) => GfxDrawInstruction::SetAlpha(alpha),
             Self::Move(off) => GfxDrawInstruction::Move(off),
             Self::SetPos(pos) => GfxDrawInstruction::SetPos(pos),
             Self::ApplyView(view) => GfxDrawInstruction::ApplyView(view),
@@ -313,6 +315,7 @@ struct GfxDrawMesh {
 #[derive(Debug, Clone)]
 enum GfxDrawInstruction {
     SetScale(f32),
+    SetAlpha(f32),
     Move(Point),
     SetPos(Point),
     ApplyView(Rectangle),
@@ -333,13 +336,14 @@ struct GfxDrawCall {
 struct OverlayDefer {
     scale: f32,
     pos: Point,
+    alpha: f32,
     instrs: Vec<GfxDrawInstruction>,
 }
 
 struct RenderContext<'a> {
     ctx: &'a mut Box<dyn RenderingBackend>,
     draw_calls: &'a HashMap<DcId, GfxDrawCall>,
-    uniforms_data: [u8; 128],
+    uniforms_data: [u8; 132],
     white_texture: miniquad::TextureId,
     loaded_pipelines: &'a [Pipeline; 2],
 
@@ -347,6 +351,7 @@ struct RenderContext<'a> {
     view: Rectangle,
     cursor: Point,
     gfx_pipeline: GraphicPipeline,
+    alpha: f32,
 
     anims: &'a mut HashMap<AnimId, GfxSeqAnim>,
     overlays: Vec<OverlayDefer>,
@@ -378,6 +383,7 @@ impl<'a> RenderContext<'a> {
             self.apply_view();
 
             self.cursor = overlay.pos;
+            self.alpha = overlay.alpha;
             self.apply_model();
 
             for instr in overlay.instrs {
@@ -406,6 +412,8 @@ impl<'a> RenderContext<'a> {
                 }
             }
         }
+
+        self.alpha = 1.;
     }
 
     fn apply_view(&mut self) {
@@ -442,7 +450,8 @@ impl<'a> RenderContext<'a> {
             glam::Mat4::from_scale(glam::Vec3::new(scale_w, scale_h, 1.));
 
         let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
-        self.uniforms_data[64..].copy_from_slice(&data);
+        self.uniforms_data[64..128].copy_from_slice(&data);
+        self.uniforms_data[128..132].copy_from_slice(&self.alpha.to_ne_bytes());
         self.ctx.apply_uniforms_from_bytes(self.uniforms_data.as_ptr(), self.uniforms_data.len());
     }
 
@@ -453,6 +462,7 @@ impl<'a> RenderContext<'a> {
         let old_view = self.view;
         let old_cursor = self.cursor;
         let old_pipeline = self.gfx_pipeline;
+        let old_alpha = self.alpha;
 
         for (idx, instr) in draw_call.instrs.iter().enumerate() {
             match instr {
@@ -464,6 +474,17 @@ impl<'a> RenderContext<'a> {
                         d!("{ws}set_scale({scale})");
                     }
                 }
+                GfxDrawInstruction::SetAlpha(alpha) => {
+                    self.alpha *= alpha;
+                    self.uniforms_data[128..132].copy_from_slice(&self.alpha.to_ne_bytes());
+                    self.ctx.apply_uniforms_from_bytes(
+                        self.uniforms_data.as_ptr(),
+                        self.uniforms_data.len(),
+                    );
+                    if is_debug {
+                        d!("{ws}set_alpha({alpha})  alpha={}", self.alpha);
+                    }
+                }
                 GfxDrawInstruction::Move(off) => {
                     self.cursor += *off;
                     if is_debug {
@@ -551,6 +572,7 @@ impl<'a> RenderContext<'a> {
                     self.overlays.push(OverlayDefer {
                         scale: self.scale,
                         pos,
+                        alpha: self.alpha,
                         instrs: instrs.clone(),
                     });
                 }
@@ -579,6 +601,7 @@ impl<'a> RenderContext<'a> {
 
         self.cursor = old_cursor;
         self.gfx_pipeline = old_pipeline;
+        self.alpha = old_alpha;
         let pipeline_idx = self.gfx_pipeline as usize;
         assert!(pipeline_idx < self.loaded_pipelines.len());
         self.ctx.apply_pipeline(&self.loaded_pipelines[pipeline_idx]);
@@ -1186,12 +1209,13 @@ impl EventHandler for Stage {
         let proj = glam::Mat4::from_translation(glam::Vec3::new(-1., 1., 0.)) *
             glam::Mat4::from_scale(glam::Vec3::new(2., -2., 1.));
 
-        let mut uniforms_data = [0u8; 128];
+        let mut uniforms_data = [0u8; 132];
         let data: [u8; 64] = unsafe { std::mem::transmute_copy(&proj) };
         uniforms_data[0..64].copy_from_slice(&data);
+        uniforms_data[128..132].copy_from_slice(&1.0f32.to_ne_bytes());
         //let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
         //uniforms_data[64..].copy_from_slice(&data);
-        assert_eq!(128, 2 * UniformType::Mat4.size());
+        assert_eq!(132, 2 * UniformType::Mat4.size() + UniformType::Float1.size());
 
         let (screen_w, screen_h) = miniquad::window::screen_size();
 
@@ -1210,6 +1234,7 @@ impl EventHandler for Stage {
             view: Rectangle::from([0., 0., screen_w, screen_h]),
             cursor: Point::zero(),
             gfx_pipeline: GraphicPipeline::RGB,
+            alpha: 1.,
             anims: &mut self.anims,
             overlays: vec![],
         };

+ 2 - 0
bin/app/src/gfx/shader/gl_fragment_rgb.frag

@@ -3,7 +3,9 @@ varying lowp vec4 color;
 varying lowp vec2 uv;
 
 uniform sampler2D tex;
+uniform lowp float Alpha;
 
 void main() {
     gl_FragColor = color * texture2D(tex, uv);
+    gl_FragColor.a *= Alpha;
 }

+ 2 - 0
bin/app/src/gfx/shader/gl_fragment_yuv.frag

@@ -5,6 +5,7 @@ varying lowp vec2 uv;
 uniform sampler2D tex_y;
 uniform sampler2D tex_u;
 uniform sampler2D tex_v;
+uniform lowp float Alpha;
 
 void main() {
     lowp float y = texture2D(tex_y, uv).r;
@@ -17,4 +18,5 @@ void main() {
     lowp float b = y + 1.772 * u;
 
     gl_FragColor = color * vec4(r, g, b, 1.0);
+    gl_FragColor.a *= Alpha;
 }

+ 5 - 2
bin/app/src/gfx/shader/metal_rgb.metal

@@ -6,6 +6,7 @@ struct Uniforms
 {
     float4x4 Projection;
     float4x4 Model;
+    float Alpha;
 };
 
 struct Vertex
@@ -33,7 +34,9 @@ vertex RasterizerData vertexShader(Vertex v [[stage_in]])
     return out
 }
 
-fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler texSmplr [[sampler(0)]])
+fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler texSmplr [[sampler(0)]], constant Uniforms& uniforms [[buffer(0)]])
 {
-    return in.color * tex.sample(texSmplr, in.uv)
+    float4 out = in.color * tex.sample(texSmplr, in.uv);
+    out.a *= uniforms.Alpha;
+    return out
 }

+ 6 - 2
bin/app/src/gfx/shader/metal_yuv.metal

@@ -6,6 +6,7 @@ struct Uniforms
 {
     float4x4 Projection;
     float4x4 Model;
+    float Alpha;
 };
 
 struct Vertex
@@ -39,7 +40,8 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]],
                                 texture2d<float> tex_v [[texture(2)]],
                                 sampler tex_y_smplr [[sampler(0)]],
                                 sampler tex_u_smplr [[sampler(1)]],
-                                sampler tex_v_smplr [[sampler(2)]])
+                                sampler tex_v_smplr [[sampler(2)]],
+                                constant Uniforms& uniforms [[buffer(0)]])
 {
     float y = tex_y.sample(tex_y_smplr, in.uv).r;
     float u = tex_u.sample(tex_u_smplr, in.uv).r - 0.5;
@@ -50,5 +52,7 @@ fragment float4 fragmentShader(RasterizerData in [[stage_in]],
     float g = y - 0.344 * u - 0.714 * v;
     float b = y + 1.772 * u;
 
-    return in.color * float4(r, g, b, 1.0);
+    float4 out = in.color * float4(r, g, b, 1.0);
+    out.a *= uniforms.Alpha;
+    return out
 }

+ 1 - 0
bin/app/src/gfx/shader/mod.rs

@@ -124,6 +124,7 @@ fn create_pipeline_with_meta(
 ) -> Pipeline {
     shader_meta.uniforms.uniforms.push(UniformDesc::new("Projection", UniformType::Mat4));
     shader_meta.uniforms.uniforms.push(UniformDesc::new("Model", UniformType::Mat4));
+    shader_meta.uniforms.uniforms.push(UniformDesc::new("Alpha", UniformType::Float1));
 
     let shader = ctx.new_shader(shader_source, shader_meta).unwrap();
 

+ 12 - 2
bin/app/src/ui/layer.rs

@@ -25,7 +25,9 @@ use tracing::instrument;
 
 use crate::{
     gfx::{DrawCall, DrawInstruction, Point, Rectangle, Renderer},
-    prop::{PropertyAtomicGuard, PropertyBool, PropertyRect, PropertyUint32, Role},
+    prop::{
+        PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyRect, PropertyUint32, Role,
+    },
     scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
     util::i18n::I18nBabelFish,
     ExecutorPtr,
@@ -48,6 +50,7 @@ pub struct Layer {
 
     is_visible: PropertyBool,
     rect: PropertyRect,
+    alpha: PropertyFloat32,
     z_index: PropertyUint32,
     priority: PropertyUint32,
 }
@@ -57,6 +60,7 @@ impl Layer {
         let node_ref = &_node.upgrade().unwrap();
         let is_visible = PropertyBool::wrap(node_ref, Role::Internal, "is_visible", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
+        let alpha = PropertyFloat32::wrap(node_ref, Role::Internal, "alpha", 0).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();
 
@@ -70,6 +74,7 @@ impl Layer {
 
             is_visible,
             rect,
+            alpha,
             z_index,
             priority,
         });
@@ -90,6 +95,8 @@ impl Layer {
         self.rect.eval(atom, &parent_rect).ok()?;
         let rect = self.rect.get();
 
+        let alpha = self.alpha.get();
+
         // Apply viewport
 
         let mut draw_calls = vec![];
@@ -111,7 +118,7 @@ impl Layer {
         }
 
         let dc = DrawCall::new(
-            vec![DrawInstruction::ApplyView(rect)],
+            vec![DrawInstruction::ApplyView(rect), DrawInstruction::SetAlpha(alpha)],
             child_calls,
             self.z_index.get(),
             "layer",
@@ -148,6 +155,9 @@ impl UIObject for Layer {
         on_modify.when_change_external(self.rect.prop(), |self_, _| async move {
             self_.redraw.trigger();
         });
+        on_modify.when_change_external(self.alpha.prop(), |self_, _| async move {
+            self_.redraw.trigger();
+        });
         on_modify.when_change_external(self.z_index.prop(), |self_, _| async move {
             self_.redraw.trigger();
         });