Adds incoming bubble animations
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Gtk;
|
||||
using Gee;
|
||||
using Gdk;
|
||||
|
||||
private class TranscriptDrawingArea : Widget
|
||||
{
|
||||
@@ -22,6 +23,8 @@ private class TranscriptDrawingArea : Widget
|
||||
private const float bubble_margin = 18.0f;
|
||||
|
||||
private const bool debug_viewport = false;
|
||||
private uint? _tick_callback_id = null;
|
||||
private HashMap<string, ChatItemAnimation> _animations = new HashMap<string, ChatItemAnimation>();
|
||||
|
||||
public TranscriptDrawingArea() {
|
||||
add_css_class("transcript-drawing-area");
|
||||
@@ -63,7 +66,7 @@ private class TranscriptDrawingArea : Widget
|
||||
recompute_message_layouts();
|
||||
}
|
||||
|
||||
public override void snapshot(Snapshot snapshot) {
|
||||
public override void snapshot(Gtk.Snapshot snapshot) {
|
||||
const int viewport_y_padding = 50;
|
||||
var viewport_rect = Graphene.Rect() {
|
||||
origin = Graphene.Point() { x = 0, y = ((int)viewport.value - viewport_y_padding) },
|
||||
@@ -104,9 +107,22 @@ private class TranscriptDrawingArea : Widget
|
||||
};
|
||||
|
||||
// Skip drawing if this item is not in the viewport
|
||||
float height_offset = 0.0f;
|
||||
if (viewport_rect.intersection(rect, null)) {
|
||||
snapshot.save();
|
||||
|
||||
var pushed_opacity = false;
|
||||
if (_animations.has_key(chat_item.id)) {
|
||||
var animation = _animations[chat_item.id];
|
||||
|
||||
var item_height_offset = (float) (-item_height * (1.0 - animation.progress));
|
||||
snapshot.translate(Graphene.Point() { x = 0, y = item_height_offset });
|
||||
height_offset = item_height_offset;
|
||||
|
||||
snapshot.push_opacity(animation.progress);
|
||||
pushed_opacity = true;
|
||||
}
|
||||
|
||||
// Translate to the correct position
|
||||
snapshot.translate(origin);
|
||||
|
||||
@@ -117,17 +133,64 @@ private class TranscriptDrawingArea : Widget
|
||||
snapshot.translate(Graphene.Point() { x = 0, y = -item_height });
|
||||
|
||||
chat_item.draw(snapshot);
|
||||
|
||||
if (pushed_opacity) {
|
||||
snapshot.pop();
|
||||
}
|
||||
|
||||
snapshot.restore();
|
||||
}
|
||||
|
||||
y_offset += item_height + chat_item.vertical_padding;
|
||||
y_offset += item_height + chat_item.vertical_padding + height_offset;
|
||||
}
|
||||
|
||||
animation_tick();
|
||||
}
|
||||
|
||||
private bool animation_tick() {
|
||||
HashSet<string> animations_to_remove = new HashSet<string>();
|
||||
_animations.foreach(entry => {
|
||||
var animation = entry.value;
|
||||
animation.tick_animation();
|
||||
|
||||
if ((animation.progress - 1.0).abs() < 0.000001) {
|
||||
animations_to_remove.add(entry.key);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
foreach (var key in animations_to_remove) {
|
||||
_animations.unset(key);
|
||||
}
|
||||
|
||||
if (_animations.size > 0) {
|
||||
queue_draw();
|
||||
}
|
||||
|
||||
return _animations.size > 0;
|
||||
}
|
||||
|
||||
private bool on_frame_clock_tick(Gtk.Widget widget, Gdk.FrameClock frame_clock) {
|
||||
return animation_tick();
|
||||
}
|
||||
|
||||
private void start_animation(string id) {
|
||||
if (_animations.has_key(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var animation = new ChatItemAnimation(get_frame_clock());
|
||||
animation.start_animation(0.18f);
|
||||
_animations.set(id, animation);
|
||||
|
||||
_tick_callback_id = add_tick_callback(on_frame_clock_tick);
|
||||
}
|
||||
|
||||
private void recompute_message_layouts() {
|
||||
var container_width = get_width();
|
||||
float max_width = container_width * 0.90f;
|
||||
|
||||
|
||||
DateTime? last_date = null;
|
||||
string? last_sender = null;
|
||||
ArrayList<ChatItemLayout> items = new ArrayList<ChatItemLayout>();
|
||||
@@ -147,8 +210,14 @@ private class TranscriptDrawingArea : Widget
|
||||
}
|
||||
|
||||
// Text Bubble
|
||||
var animate = message.should_animate;
|
||||
if (message.text.length > 0 && !message.is_attachment_marker) {
|
||||
var text_bubble = new TextBubbleLayout(message, this, max_width);
|
||||
text_bubble.id = @"text-$(message.guid)";
|
||||
if (animate) {
|
||||
start_animation(text_bubble.id);
|
||||
}
|
||||
|
||||
text_bubble.vertical_padding = (last_sender == message.sender) ? 4.0f : 10.0f;
|
||||
items.add(text_bubble);
|
||||
}
|
||||
@@ -164,6 +233,11 @@ private class TranscriptDrawingArea : Widget
|
||||
}
|
||||
|
||||
var image_layout = new ImageBubbleLayout(attachment.preview_path, message.from_me, this, max_width, image_size);
|
||||
image_layout.id = @"image-$(attachment.guid)";
|
||||
if (animate) {
|
||||
start_animation(image_layout.id);
|
||||
}
|
||||
|
||||
image_layout.is_downloaded = attachment.preview_downloaded;
|
||||
items.add(image_layout);
|
||||
|
||||
@@ -191,3 +265,30 @@ private class TranscriptDrawingArea : Widget
|
||||
queue_resize();
|
||||
}
|
||||
}
|
||||
|
||||
private class ChatItemAnimation
|
||||
{
|
||||
public double progress = 0.0;
|
||||
private int64 start_time = 0;
|
||||
private double duration = 0.0;
|
||||
private Gdk.FrameClock frame_clock = null;
|
||||
|
||||
public ChatItemAnimation(Gdk.FrameClock frame_clock) {
|
||||
this.frame_clock = frame_clock;
|
||||
progress = 0.0f;
|
||||
}
|
||||
|
||||
public void start_animation(double duration) {
|
||||
this.duration = duration;
|
||||
this.progress = 0.0;
|
||||
this.start_time = frame_clock.get_frame_time();
|
||||
}
|
||||
|
||||
public void tick_animation() {
|
||||
progress = ease_out_quart((frame_clock.get_frame_time() - start_time) / (duration * 1000000.0));
|
||||
}
|
||||
|
||||
private static double ease_out_quart(double t) {
|
||||
return 1.0 - Math.pow(1.0 - t, 4);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user