Skip to content

Commit

Permalink
shimmer-effect: Add shimmer effect widget
Browse files Browse the repository at this point in the history
  • Loading branch information
yuraiz committed May 23, 2023
1 parent 95796fb commit 69010b6
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 2 deletions.
4 changes: 3 additions & 1 deletion demo/src/window/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod loading_indicator;
mod shimmer_effect;
mod spoiler;

use adw::prelude::*;
Expand All @@ -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();
}
Expand Down
34 changes: 34 additions & 0 deletions demo/src/window/shimmer_effect/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Self>) {
obj.init_template();
}
}

impl ObjectImpl for ShimmerEffectPage {}
impl WidgetImpl for ShimmerEffectPage {}
impl BinImpl for ShimmerEffectPage {}
}

glib::wrapper! {
pub struct ShimmerEffectPage(ObjectSubclass<imp::ShimmerEffectPage>)
@extends adw::Bin, gtk::Widget;
}
65 changes: 65 additions & 0 deletions demo/src/window/shimmer_effect/shimmer_effect.blp
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
};
}
7 changes: 7 additions & 0 deletions demo/src/window/window.blp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ template $SpoilerWindow : Adw.ApplicationWindow {

child: $OriDemoLoadingIndicatorPage {};
}

StackPage {
name: "shimmer_effect";
title: "Shimmer Effect";

child: $OriDemoShimmerEffectPage {};
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion origami/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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();
}
132 changes: 132 additions & 0 deletions origami/src/shimmer_effect/mod.rs
Original file line number Diff line number Diff line change
@@ -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<bool>,
}

#[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: &gtk::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::<gtk::Window>()
.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<imp::ShimmerEffect>)
@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()
}
}

0 comments on commit 69010b6

Please sign in to comment.