From 69010b62bae6ba22c200776e3922f5b41f3cdf3f Mon Sep 17 00:00:00 2001 From: YuraIz <7516890@gmail.com> Date: Tue, 23 May 2023 17:08:11 +0300 Subject: [PATCH] shimmer-effect: Add shimmer effect widget --- demo/src/window/mod.rs | 4 +- demo/src/window/shimmer_effect/mod.rs | 34 +++++ .../window/shimmer_effect/shimmer_effect.blp | 65 +++++++++ demo/src/window/window.blp | 7 + origami/src/lib.rs | 5 +- origami/src/shimmer_effect/mod.rs | 132 ++++++++++++++++++ 6 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 demo/src/window/shimmer_effect/mod.rs create mode 100644 demo/src/window/shimmer_effect/shimmer_effect.blp create mode 100644 origami/src/shimmer_effect/mod.rs diff --git a/demo/src/window/mod.rs b/demo/src/window/mod.rs index 4a099db..11cc943 100644 --- a/demo/src/window/mod.rs +++ b/demo/src/window/mod.rs @@ -1,4 +1,5 @@ mod loading_indicator; +mod shimmer_effect; mod spoiler; use adw::prelude::*; @@ -23,8 +24,9 @@ mod imp { type ParentType = adw::ApplicationWindow; fn class_init(klass: &mut Self::Class) { - spoiler::SpoilerPage::static_type(); loading_indicator::LoadingIndicatorPage::static_type(); + shimmer_effect::ShimmerEffectPage::static_type(); + spoiler::SpoilerPage::static_type(); klass.bind_template(); } diff --git a/demo/src/window/shimmer_effect/mod.rs b/demo/src/window/shimmer_effect/mod.rs new file mode 100644 index 0000000..cc8ba03 --- /dev/null +++ b/demo/src/window/shimmer_effect/mod.rs @@ -0,0 +1,34 @@ +use adw::subclass::prelude::*; +use gtk::glib; + +mod imp { + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate)] + #[template(file = "src/window/shimmer_effect/shimmer_effect.blp")] + pub struct ShimmerEffectPage; + + #[glib::object_subclass] + impl ObjectSubclass for ShimmerEffectPage { + const NAME: &'static str = "OriDemoShimmerEffectPage"; + type Type = super::ShimmerEffectPage; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for ShimmerEffectPage {} + impl WidgetImpl for ShimmerEffectPage {} + impl BinImpl for ShimmerEffectPage {} +} + +glib::wrapper! { + pub struct ShimmerEffectPage(ObjectSubclass) + @extends adw::Bin, gtk::Widget; +} diff --git a/demo/src/window/shimmer_effect/shimmer_effect.blp b/demo/src/window/shimmer_effect/shimmer_effect.blp new file mode 100644 index 0000000..33ac614 --- /dev/null +++ b/demo/src/window/shimmer_effect/shimmer_effect.blp @@ -0,0 +1,65 @@ +using Gtk 4.0; +using Adw 1; + +template $OriDemoShimmerEffectPage : Adw.Bin { + child: Adw.StatusPage { + title: "Shimmer Effect"; + + Box { + orientation: vertical; + halign: center; + + + $OriShimmerEffect { + playing: true; + + child: Box { + Image { + icon-name: "folder-documents-symbolic"; + pixel-size: 128; + } + + Image { + icon-name: "folder-documents-symbolic"; + pixel-size: 128; + } + + Image { + icon-name: "folder-documents-symbolic"; + pixel-size: 128; + } + }; + } + + + Box { + $OriShimmerEffect { + playing: true; + + Image { + icon-name: "folder-documents-symbolic"; + pixel-size: 128; + } + } + + $OriShimmerEffect { + playing: true; + + Image { + icon-name: "folder-documents-symbolic"; + pixel-size: 128; + } + } + + $OriShimmerEffect { + playing: true; + + Image { + icon-name: "folder-documents-symbolic"; + pixel-size: 128; + } + } + } + } + }; +} \ No newline at end of file diff --git a/demo/src/window/window.blp b/demo/src/window/window.blp index 518ca7c..837d069 100644 --- a/demo/src/window/window.blp +++ b/demo/src/window/window.blp @@ -74,6 +74,13 @@ template $SpoilerWindow : Adw.ApplicationWindow { child: $OriDemoLoadingIndicatorPage {}; } + + StackPage { + name: "shimmer_effect"; + title: "Shimmer Effect"; + + child: $OriDemoShimmerEffectPage {}; + } } } } diff --git a/origami/src/lib.rs b/origami/src/lib.rs index b5bce72..a148120 100644 --- a/origami/src/lib.rs +++ b/origami/src/lib.rs @@ -1,16 +1,19 @@ //! [Paper Plane](https://github.com/paper-plane-developers/paper-plane) related set of gtk widgets that can be usable outside of it. mod loading_indicator; +mod shimmer_effect; mod spoiler_overlay; use gtk::prelude::StaticType; pub use loading_indicator::LoadingIndicator; +pub use shimmer_effect::ShimmerEffect; pub use spoiler_overlay::SpoilerOverlay; /// Registers all library types. /// /// Expected to be called in the main function pub fn init() { - SpoilerOverlay::static_type(); LoadingIndicator::static_type(); + ShimmerEffect::static_type(); + SpoilerOverlay::static_type(); } diff --git a/origami/src/shimmer_effect/mod.rs b/origami/src/shimmer_effect/mod.rs new file mode 100644 index 0000000..e711f92 --- /dev/null +++ b/origami/src/shimmer_effect/mod.rs @@ -0,0 +1,132 @@ +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib::clone; +use gtk::{glib, graphene, gsk}; + +const GRADIENT_WIDTH: f32 = 256.0; +const GRADIENT_PIXELS_PER_SEC: f32 = 100.0; + +mod imp { + use super::*; + use std::cell::Cell; + + #[derive(Default, glib::Properties)] + #[properties(wrapper_type = super::ShimmerEffect)] + pub struct ShimmerEffect { + #[property(get, set)] + pub(super) playing: Cell, + } + + #[glib::object_subclass] + impl ObjectSubclass for ShimmerEffect { + const NAME: &'static str = "OriShimmerEffect"; + type Type = super::ShimmerEffect; + type ParentType = adw::Bin; + } + + impl ObjectImpl for ShimmerEffect { + fn properties() -> &'static [glib::ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + self.derived_set_property(id, value, pspec) + } + + fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value { + self.derived_property(id, pspec) + } + + fn constructed(&self) { + self.parent_constructed(); + self.obj().connect_child_notify(|obj| { + if let Some(child) = obj.child() { + child.connect_visible_notify(clone!(@weak obj => move |child| { + if child.is_visible() { + obj.queue_draw(); + } + })); + } + }); + + self.obj().connect_playing_notify(|widget| { + if widget.playing() { + widget.add_tick_callback(|widget, _clock| { + widget.queue_draw(); + Continue(widget.playing()) + }); + } + }); + } + } + + impl WidgetImpl for ShimmerEffect { + fn snapshot(&self, snapshot: >k::Snapshot) { + if !self.playing.get() { + self.parent_snapshot(snapshot); + } + + let widget = self.obj(); + + snapshot.push_mask(gsk::MaskMode::Alpha); + self.parent_snapshot(snapshot); + snapshot.pop(); + + let window = self + .obj() + .ancestor(gtk::Window::static_type()) + .and_downcast::() + .unwrap(); + + let win_bounds = window.compute_bounds(self.obj().as_ref()).unwrap(); + + let time_secs = std::time::Duration::from_micros(widget.time() as u64).as_secs_f32(); + + let shift = time_secs * GRADIENT_PIXELS_PER_SEC % GRADIENT_WIDTH; + + let gradient_bounds = + graphene::Rect::new(win_bounds.x() + shift, win_bounds.y(), GRADIENT_WIDTH, 1.0); + + let mut color1 = widget.color(); + let mut color2 = color1.clone(); + color1.set_alpha(0.6); + color2.set_alpha(0.3); + + snapshot.append_repeating_linear_gradient( + &win_bounds, + &gradient_bounds.top_left(), + &gradient_bounds.top_right(), + &[ + gsk::ColorStop::new(0.0, color2), + gsk::ColorStop::new(0.4, color1), + gsk::ColorStop::new(0.6, color1), + gsk::ColorStop::new(1.0, color2), + ], + ); + + snapshot.pop(); + } + } + impl BinImpl for ShimmerEffect {} +} + +glib::wrapper! { + /// Pulsating shimmer effect + /// + /// Useful for skeleton loaders + /// + /// # Properties + /// * playing: [bool]. + /// Controls whether to display the effect + pub struct ShimmerEffect(ObjectSubclass) + @extends adw::Bin, gtk::Widget; +} + +impl ShimmerEffect { + fn time(&self) -> i64 { + self.frame_clock() + .and_then(|clk| clk.current_timings()) + .map(|t| t.frame_time()) + .unwrap_or_default() + } +}