From 718625d684b9353e67a3bd68eea2ba15eef0b075 Mon Sep 17 00:00:00 2001 From: Evangelos Paterakis Date: Fri, 13 Oct 2023 02:00:29 +0300 Subject: [PATCH 01/13] feat(MediaViewer): ScaleRevealer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marco Melorio Co-authored-by: Kévin Commaille --- data/ui/dialogs/main.ui | 40 +++------ src/Dialogs/MainWindow.vala | 18 ++-- src/Views/MediaViewer.vala | 25 +++++- src/Widgets/Attachment/Image.vala | 1 + src/Widgets/ScaleRevealer.vala | 131 ++++++++++++++++++++++++++++++ src/Widgets/meson.build | 1 + 6 files changed, 182 insertions(+), 34 deletions(-) create mode 100644 src/Widgets/ScaleRevealer.vala diff --git a/data/ui/dialogs/main.ui b/data/ui/dialogs/main.ui index 8b3820f31..7f44b3017 100644 --- a/data/ui/dialogs/main.ui +++ b/data/ui/dialogs/main.ui @@ -10,38 +10,24 @@ - - 1 - 1 - 0 - crossfade - 1 - - - main - - - - - - - - - - - - + + + + 0 - - - media_viewer - - + + + + + + + + - + diff --git a/src/Dialogs/MainWindow.vala b/src/Dialogs/MainWindow.vala index a57f7db07..5ab773340 100644 --- a/src/Dialogs/MainWindow.vala +++ b/src/Dialogs/MainWindow.vala @@ -3,7 +3,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { [GtkChild] unowned Adw.NavigationView navigation_view; [GtkChild] public unowned Adw.OverlaySplitView split_view; [GtkChild] unowned Views.Sidebar sidebar; - [GtkChild] unowned Gtk.Stack main_stack; + // [GtkChild] unowned Gtk.Stack main_stack; [GtkChild] unowned Views.MediaViewer media_viewer; [GtkChild] unowned Adw.Breakpoint breakpoint; @@ -45,7 +45,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { } public bool is_media_viewer_visible () { - return main_stack.visible_child_name == "media_viewer"; + return media_viewer.visible; } public void scroll_media_viewer (int pos) { @@ -54,9 +54,15 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { media_viewer.scroll_to (pos); } + public void temp_reveal_media_viewer (Gtk.Widget widget) { + // if (is_media_viewer_visible ()) + media_viewer.reveal (widget); + } + public void show_media_viewer (string url, string? alt_text, bool video, Gdk.Paintable? preview, int? pos) { if (!is_media_viewer_visible ()) { - main_stack.visible_child_name = "media_viewer"; + // main_stack.visible_child_name = "media_viewer"; + // media_viewer.visible = true; media_viewer.clear.connect (hide_media_viewer); } @@ -71,7 +77,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { if (paintable == null) return; if (!is_media_viewer_visible ()) { - main_stack.visible_child_name = "media_viewer"; + // main_stack.visible_child_name = "media_viewer"; media_viewer.clear.connect (hide_media_viewer); } @@ -80,7 +86,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { public void show_media_viewer_remote_video (string url, Gdk.Paintable? preview, string? user_friendly_url = null) { if (!is_media_viewer_visible ()) { - main_stack.visible_child_name = "media_viewer"; + // main_stack.visible_child_name = "media_viewer"; media_viewer.clear.connect (hide_media_viewer); } @@ -90,7 +96,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { public void hide_media_viewer () { if (!is_media_viewer_visible ()) return; - main_stack.visible_child_name = "main"; + // main_stack.visible_child_name = "main"; } public void show_book (API.BookWyrm book, string? fallback = null) { diff --git a/src/Views/MediaViewer.vala b/src/Views/MediaViewer.vala index ce0976a47..cf2b4876b 100644 --- a/src/Views/MediaViewer.vala +++ b/src/Views/MediaViewer.vala @@ -236,6 +236,7 @@ public class Tuba.Views.MediaViewer : Gtk.Box { private Adw.Carousel carousel; private Adw.CarouselIndicatorDots carousel_dots; protected SimpleAction copy_media_simple_action; + private Tuba.Widgets.ScaleRevealer scale_revealer; protected Gtk.PopoverMenu context_menu { get; set; } construct { @@ -259,9 +260,14 @@ public class Tuba.Views.MediaViewer : Gtk.Box { Gtk.Widget page_btns; generate_media_buttons (out page_btns, out zoom_btns); + scale_revealer = new Tuba.Widgets.ScaleRevealer () { + child = carousel + }; + scale_revealer.transition_done.connect (on_scale_revealer_transition_end); + overlay.add_overlay (page_btns); overlay.add_overlay (zoom_btns); - overlay.child = carousel; + overlay.child = scale_revealer; var drag = new Gtk.GestureDrag (); drag.drag_begin.connect (on_drag_begin); @@ -349,6 +355,10 @@ public class Tuba.Views.MediaViewer : Gtk.Box { context_menu.unparent (); } + private void on_scale_revealer_transition_end () { + if (!scale_revealer.reveal_child) this.visible = false; + } + int? old_height; int? old_width; protected void on_scale_changed (double scale) { @@ -426,6 +436,7 @@ public class Tuba.Views.MediaViewer : Gtk.Box { protected void on_back_clicked () { clear (); + scale_revealer.reveal_child = false; } protected void toggle_fullscreen () { @@ -564,12 +575,24 @@ public class Tuba.Views.MediaViewer : Gtk.Box { }); items.clear (); + revealed = false; } private async string download_video (string url) throws Error { return yield Host.download (url); } + private bool revealed = false; + public void reveal (Gtk.Widget widget) { + if (revealed) return; + + this.visible = true; + scale_revealer.source_widget = widget; + scale_revealer.reveal_child = true; + + revealed = true; + } + public void add_video (string url, Gdk.Paintable? preview, int? pos) { var video = new Gtk.Video (); var item = new Item (video, url, preview, true); diff --git a/src/Widgets/Attachment/Image.vala b/src/Widgets/Attachment/Image.vala index 7027ad621..2c31e524e 100644 --- a/src/Widgets/Attachment/Image.vala +++ b/src/Widgets/Attachment/Image.vala @@ -120,6 +120,7 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { public void load_image_in_media_viewer (int? pos) { app.main_window.show_media_viewer (entity.url, pic.alternative_text, media_kind in VIDEO_TYPES, pic.paintable, pos); + app.main_window.temp_reveal_media_viewer (this); } public signal void on_any_attachment_click (string url) {} diff --git a/src/Widgets/ScaleRevealer.vala b/src/Widgets/ScaleRevealer.vala new file mode 100644 index 000000000..82d53b0ce --- /dev/null +++ b/src/Widgets/ScaleRevealer.vala @@ -0,0 +1,131 @@ +public class Tuba.Widgets.ScaleRevealer : Adw.Bin { + const uint ANIMATION_DURATION = 250; + + public signal void transition_done (); + public Adw.TimedAnimation animation { get; construct set; } + public Gtk.Widget? source_widget { get; set; } + public Gdk.Texture? source_widget_texture { get; set; } + + private bool _reveal_child = false; + public bool reveal_child { + get { + return _reveal_child; + } + + set { + if (_reveal_child == value) return; + animation.value_from = animation.value; + + if (value) { + animation.value_to = 1.0; + this.visible = true; + + if (source_widget == null) { + source_widget_texture = null; + } else { + source_widget_texture = render_widget_to_texture (this.source_widget); + source_widget.opacity = 0.0; + } + } else { + animation.value_to = 0.0; + } + + _reveal_child = value; + animation.play (); + this.notify_property ("reveal-child"); + } + } + + private Gdk.Texture? render_widget_to_texture (Gtk.Widget widget) { + var widget_paintable = new Gtk.WidgetPaintable (widget); + var t_snapshot = new Gtk.Snapshot (); + + widget_paintable.snapshot ( + t_snapshot, + widget_paintable.get_intrinsic_width (), + widget_paintable.get_intrinsic_height () + ); + + var node = t_snapshot.to_node (); + var native = widget.get_native (); + if (native == null || node == null) return null; + + return native.get_renderer ().render_texture (node, null); + } + + construct { + var target = new Adw.CallbackAnimationTarget (animation_target_cb); + animation = new Adw.TimedAnimation (this, 0.0, 1.0, ANIMATION_DURATION, target) { + easing = Adw.Easing.EASE_OUT_QUART + }; + animation.done.connect (on_animation_end); + + this.visible = false; + } + + private void on_animation_end () { + if (!reveal_child) { + source_widget.opacity = 1.0; + this.visible = false; + } + + transition_done (); + } + + private void animation_target_cb (double value) { + this.queue_draw (); + } + + public override void snapshot (Gtk.Snapshot snapshot) { + if (this.child == null) return; + + var progress = this.animation.value; + if (progress == 1.0) { + this.snapshot_child (this.child, snapshot); + return; + } + var rev_progress = (1.0 - progress).abs (); + + Graphene.Rect source_bounds; + if (!this.source_widget.compute_bounds (this, out source_bounds)) source_bounds = Graphene.Rect () { + origin = Graphene.Point () { x = 0.0f, y = 0.0f }, + size = Graphene.Size () { width = 100.0f, height = 100.0f } + }; + + float x_scale = source_bounds.get_width () / this.get_width (); + float y_scale = source_bounds.get_height () / this.get_height (); + + x_scale = 1.0f + (x_scale - 1.0f) * (float) rev_progress; + y_scale = 1.0f + (y_scale - 1.0f) * (float) rev_progress; + + float x = source_bounds.get_x () * (float) rev_progress; + float y = source_bounds.get_y () * (float) rev_progress; + + snapshot.translate (Graphene.Point () { x = x, y = y }); + snapshot.scale (x_scale, y_scale); + + if (source_widget_texture == null) { + warning ("The source widget texture is None, using child snapshot as fallback"); + this.snapshot_child (this.child, snapshot); + } else { + if (progress > 0.0) { + snapshot.push_cross_fade (progress); + source_widget_texture.snapshot ( + snapshot, + this.get_width (), + this.get_height () + ); + snapshot.pop (); + + this.snapshot_child (this.child, snapshot); + snapshot.pop (); + } else if (progress <= 0.0) { + source_widget_texture.snapshot ( + snapshot, + this.get_width (), + this.get_height () + ); + } + } + } +} diff --git a/src/Widgets/meson.build b/src/Widgets/meson.build index 03d998b0e..41d8bace0 100644 --- a/src/Widgets/meson.build +++ b/src/Widgets/meson.build @@ -14,6 +14,7 @@ sources += files( 'ProfileCover.vala', 'RelationshipButton.vala', 'RichLabel.vala', + 'ScaleRevealer.vala', 'Status.vala', 'StatusActionButton.vala', 'VoteBox.vala', From d9862c53eadba33d0f6bcd02bdc6c876859630b2 Mon Sep 17 00:00:00 2001 From: Evangelos Paterakis Date: Fri, 13 Oct 2023 19:08:46 +0300 Subject: [PATCH 02/13] feat: combine API methods & use enum for kind --- src/Dialogs/MainWindow.vala | 46 ++++---- src/Views/MediaViewer.vala | 172 +++++++++++++++++++----------- src/Widgets/Attachment/Image.vala | 15 +-- src/Widgets/Attachment/Item.vala | 5 +- src/Widgets/ProfileCover.vala | 4 +- 5 files changed, 140 insertions(+), 102 deletions(-) diff --git a/src/Dialogs/MainWindow.vala b/src/Dialogs/MainWindow.vala index 5ab773340..a7d6bf7a4 100644 --- a/src/Dialogs/MainWindow.vala +++ b/src/Dialogs/MainWindow.vala @@ -59,46 +59,38 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { media_viewer.reveal (widget); } - public void show_media_viewer (string url, string? alt_text, bool video, Gdk.Paintable? preview, int? pos) { - if (!is_media_viewer_visible ()) { - // main_stack.visible_child_name = "media_viewer"; - // media_viewer.visible = true; - media_viewer.clear.connect (hide_media_viewer); - } - - if (video) { - media_viewer.add_video (url, preview, pos); - } else { - media_viewer.add_image (url, alt_text, preview, pos); - } - } - - public void show_media_viewer_single (string? url, Gdk.Paintable? paintable) { - if (paintable == null) return; + public void show_media_viewer ( + string url, + Tuba.Attachment.MediaType media_type, + Gdk.Paintable? preview, + int? pos = null, + Gtk.Widget? source_widget = null, + bool as_is = false, + string? alt_text = null, + string? user_friendly_url = null + ) { + if (as_is && preview == null) return; + + media_viewer.add_media (url, media_type, preview, pos, as_is, user_friendly_url); if (!is_media_viewer_visible ()) { - // main_stack.visible_child_name = "media_viewer"; - media_viewer.clear.connect (hide_media_viewer); + if (source_widget != null) { + media_viewer.reveal (source_widget); + } else { + media_viewer.visible = true; + } } - - media_viewer.set_single_paintable (url, paintable); } public void show_media_viewer_remote_video (string url, Gdk.Paintable? preview, string? user_friendly_url = null) { if (!is_media_viewer_visible ()) { // main_stack.visible_child_name = "media_viewer"; - media_viewer.clear.connect (hide_media_viewer); + // media_viewer.clear.connect (hide_media_viewer); } media_viewer.set_remote_video (url, preview, user_friendly_url); } - public void hide_media_viewer () { - if (!is_media_viewer_visible ()) return; - - // main_stack.visible_child_name = "main"; - } - public void show_book (API.BookWyrm book, string? fallback = null) { try { var book_widget = book.to_widget (); diff --git a/src/Views/MediaViewer.vala b/src/Views/MediaViewer.vala index cf2b4876b..3db4fb168 100644 --- a/src/Views/MediaViewer.vala +++ b/src/Views/MediaViewer.vala @@ -1,5 +1,66 @@ // Mostly inspired by Loupe https://gitlab.gnome.org/Incubator/loupe +public class Tuba.Attachment { + public enum MediaType { + IMAGE, + VIDEO, + GIFV, + AUDIO, + UNKNOWN; + + public bool can_copy () { + switch (this) { + case IMAGE: + return true; + default: + return false; + } + } + + public bool is_video () { + switch (this) { + case VIDEO: + case GIFV: + case AUDIO: + return true; + default: + return false; + } + } + + public string to_string () { + switch (this) { + case IMAGE: + return "IMAGE"; + case VIDEO: + return "VIDEO"; + case GIFV: + return "GIFV"; + case AUDIO: + return "AUDIO"; + default: + return "UNKNOWN"; + } + } + + public static MediaType from_string (string media_type) { + string media_type_up = media_type.up (); + switch (media_type_up) { + case "IMAGE": + return IMAGE; + case "VIDEO": + return VIDEO; + case "GIFV": + return GIFV; + case "AUDIO": + return AUDIO; + default: + return UNKNOWN; + } + } + } +} + public class Tuba.Views.MediaViewer : Gtk.Box { const double MAX_ZOOM = 20; static double last_used_volume = 1.0; @@ -138,8 +199,8 @@ public class Tuba.Views.MediaViewer : Gtk.Box { Gdk.Paintable? paintable, bool t_is_video = false ) { - child_widget = child; - is_video = t_is_video; + this.child_widget = child; + this.is_video = t_is_video; stack.add_named (setup_scrolledwindow (child), "child"); this.url = t_url; @@ -147,15 +208,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box { if (paintable != null) overlay.child = new Gtk.Picture.for_paintable (paintable); } - public Item.static (Gtk.Widget child, string t_url) { - child_widget = child; - - stack.add_named (setup_scrolledwindow (child), "child"); - this.url = t_url; - - done (); - } - ~Item () { debug ("Destroying MediaViewer.Item"); @@ -593,39 +645,62 @@ public class Tuba.Views.MediaViewer : Gtk.Box { revealed = true; } - public void add_video (string url, Gdk.Paintable? preview, int? pos) { - var video = new Gtk.Video (); - var item = new Item (video, url, preview, true); - if (pos == null) { - carousel.append (item); - items.add (item); + public void add_media ( + string url, + Tuba.Attachment.MediaType media_type, + Gdk.Paintable? preview, + int? pos = null, + bool as_is = false, + string? alt_text = null, + string? user_friendly_url = null + ) { + Item item; + string final_friendly_url = user_friendly_url == null ? url : user_friendly_url; + Gdk.Paintable? final_preview = as_is ? null : preview; + + if (media_type.is_video ()) { + var video = new Gtk.Video (); + item = new Item (video, final_friendly_url, final_preview, true); + + if (!as_is) { + download_video.begin (url, (obj, res) => { + try { + var path = download_video.end (res); + video.set_filename (path); + item.done (); + } + catch (Error e) { + var dlg = app.inform (_("Error"), e.message); + dlg.present (); + } + }); + } } else { - carousel.insert (item, pos); - items.insert (pos, item); - } + var picture = new Gtk.Picture (); - download_video.begin (url, (obj, res) => { - try { - var path = download_video.end (res); - video.set_filename (path); - item.done (); + if (!settings.media_viewer_expand_pictures) { + picture.valign = picture.halign = Gtk.Align.CENTER; } - catch (Error e) { - var dlg = app.inform (_("Error"), e.message); - dlg.present (); - } - }); - } - public void add_image (string url, string? alt_text, Gdk.Paintable? preview, int? pos) { - var picture = new Gtk.Picture (); + item = new Item (picture, final_friendly_url, final_preview); + item.zoom_changed.connect (on_zoom_change); - if (!settings.media_viewer_expand_pictures) { - picture.valign = picture.halign = Gtk.Align.CENTER; + if (alt_text != null) picture.alternative_text = alt_text; + + if (!as_is) { + image_cache.request_paintable (url, (is_loaded, data) => { + if (is_loaded) { + picture.paintable = data; + item.done (); + } + }); + } else { + picture.paintable = preview; + } } - var item = new Item (picture, url, preview); - item.zoom_changed.connect (on_zoom_change); + if (as_is) item.done (); + if (pos == null) { carousel.append (item); items.add (item); @@ -633,15 +708,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box { carousel.insert (item, pos); items.insert (pos, item); } - - if (alt_text != null) picture.alternative_text = alt_text; - - image_cache.request_paintable (url, (is_loaded, data) => { - if (is_loaded) { - picture.paintable = data; - item.done (); - } - }); } public void set_remote_video (string url, Gdk.Paintable? preview, string? user_friendly_url = null) { @@ -658,20 +724,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box { carousel.page_changed (0); } - public void set_single_paintable (string url, Gdk.Paintable paintable) { - var picture = new Gtk.Picture (); - picture.paintable = paintable; - - if (!settings.media_viewer_expand_pictures) { - picture.valign = picture.halign = Gtk.Align.CENTER; - } - - var item = new Item.static (picture, url); - item.zoom_changed.connect (on_zoom_change); - carousel.append (item); - items.add (item); - } - public void scroll_to (int pos, bool should_timeout = true) { if (pos >= items.size || pos < 0) return; diff --git a/src/Widgets/Attachment/Image.vala b/src/Widgets/Attachment/Image.vala index 2c31e524e..375eb3381 100644 --- a/src/Widgets/Attachment/Image.vala +++ b/src/Widgets/Attachment/Image.vala @@ -1,7 +1,4 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { - const string[] ALLOWED_TYPES = {"IMAGE", "VIDEO", "GIFV", "AUDIO"}; - const string[] VIDEO_TYPES = {"GIFV", "VIDEO", "AUDIO"}; - protected Gtk.Picture pic; protected Gtk.Overlay media_overlay; @@ -45,7 +42,6 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { button.child = media_overlay; } - const string[] CAN_COPY_KINDS = { "IMAGE" }; protected Gtk.Image? media_icon = null; protected override void on_rebind () { base.on_rebind (); @@ -53,13 +49,13 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { image_cache.request_paintable (entity.preview_url, on_cache_response); - if (media_kind in VIDEO_TYPES) { + if (media_kind.is_video ()) { media_icon = new Gtk.Image () { valign = Gtk.Align.CENTER, halign = Gtk.Align.CENTER }; - if (media_kind != "AUDIO") { + if (media_kind != Tuba.Attachment.MediaType.AUDIO) { media_icon.css_classes = { "osd", "circular", "attachment-overlay-icon" }; media_icon.icon_name = "media-playback-start-symbolic"; } else { @@ -72,7 +68,7 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { media_icon.icon_size = Gtk.IconSize.LARGE; } - copy_media_simple_action.set_enabled (media_kind in CAN_COPY_KINDS); + copy_media_simple_action.set_enabled (media_kind.can_copy ()); } protected override void copy_media () { @@ -110,7 +106,7 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { return; } - if (media_kind in ALLOWED_TYPES) { + if (media_kind != Tuba.Attachment.MediaType.UNKNOWN) { load_image_in_media_viewer (null); on_any_attachment_click (entity.url); } else { // Fallback @@ -119,8 +115,7 @@ public class Tuba.Widgets.Attachment.Image : Widgets.Attachment.Item { } public void load_image_in_media_viewer (int? pos) { - app.main_window.show_media_viewer (entity.url, pic.alternative_text, media_kind in VIDEO_TYPES, pic.paintable, pos); - app.main_window.temp_reveal_media_viewer (this); + app.main_window.show_media_viewer (entity.url, media_kind, pic.paintable, pos, this, false, pic.alternative_text); } public signal void on_any_attachment_click (string url) {} diff --git a/src/Widgets/Attachment/Item.vala b/src/Widgets/Attachment/Item.vala index d5d7f40e8..d36f31267 100644 --- a/src/Widgets/Attachment/Item.vala +++ b/src/Widgets/Attachment/Item.vala @@ -1,5 +1,4 @@ public class Tuba.Widgets.Attachment.Item : Adw.Bin { - public API.Attachment entity { get; set; default = null; } protected Gtk.GestureClick gesture_click_controller { get; set; } protected Gtk.GestureLongPress gesture_lp_controller { get; set; } @@ -16,7 +15,7 @@ public class Tuba.Widgets.Attachment.Item : Adw.Bin { protected Gtk.Button alt_btn; protected Gtk.Box badge_box; protected ulong alt_btn_clicked_id; - protected string media_kind; + protected Tuba.Attachment.MediaType media_kind; private void copy_url () { Host.copy (entity.url); @@ -199,7 +198,7 @@ public class Tuba.Widgets.Attachment.Item : Adw.Bin { protected virtual void on_rebind () { alt_btn.visible = entity != null && entity.description != null && entity.description != ""; - media_kind = entity.kind.up (); + media_kind = Tuba.Attachment.MediaType.from_string (entity.kind); } protected virtual void on_click () { diff --git a/src/Widgets/ProfileCover.vala b/src/Widgets/ProfileCover.vala index bf8ac9268..64e1a4616 100644 --- a/src/Widgets/ProfileCover.vala +++ b/src/Widgets/ProfileCover.vala @@ -64,11 +64,11 @@ protected class Tuba.Widgets.Cover : Gtk.Box { private string avi_url { get; set; default=""; } private string header_url { get; set; default=""; } void open_header_in_media_viewer () { - app.main_window.show_media_viewer_single (header_url, background.paintable); + app.main_window.show_media_viewer (header_url, Tuba.Attachment.MediaType.IMAGE, background.paintable, null, background, true); } void open_pfp_in_media_viewer () { - app.main_window.show_media_viewer_single (avi_url, avatar.custom_image); + app.main_window.show_media_viewer (avi_url, Tuba.Attachment.MediaType.IMAGE, avatar.custom_image, null, avatar, true); } public Cover (Views.Profile.ProfileAccount profile) { From e7b493bdf65e8ac61c383f7e6eb7fd106926a0d1 Mon Sep 17 00:00:00 2001 From: Evangelos Paterakis Date: Fri, 13 Oct 2023 22:45:31 +0300 Subject: [PATCH 03/13] feat: move to ui files, style, revealed view --- data/gresource.xml | 1 + data/style.css | 4 +- data/ui/views/media_viewer.ui | 182 +++++++++++++++++++++ src/Dialogs/MainWindow.vala | 24 ++- src/Views/MediaViewer.vala | 262 +++++++++---------------------- src/Widgets/Attachment/Item.vala | 2 +- 6 files changed, 269 insertions(+), 206 deletions(-) create mode 100644 data/ui/views/media_viewer.ui diff --git a/data/gresource.xml b/data/gresource.xml index 68859ad8e..214c4aad6 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -69,6 +69,7 @@ gtk/dropdown/expiration.ui ui/views/base.ui + ui/views/media_viewer.ui ui/views/profile_header.ui ui/views/sidebar/view.ui ui/views/sidebar/account.ui diff --git a/data/style.css b/data/style.css index 35326c2fe..32fe8a624 100644 --- a/data/style.css +++ b/data/style.css @@ -231,8 +231,8 @@ flowboxchild { background: alpha(@success_bg_color, 0.1); } -.media-viewer-headerbar { - background: rgba(0, 0, 0, .7); +.media-viewer { + background: black; color: white; } diff --git a/data/ui/views/media_viewer.ui b/data/ui/views/media_viewer.ui new file mode 100644 index 000000000..2fdd69205 --- /dev/null +++ b/data/ui/views/media_viewer.ui @@ -0,0 +1,182 @@ + + + + + + Open in Browser + mediaviewer.open-in-browser + + + Copy URL + mediaviewer.copy-url + + + Save Media + mediaviewer.save-as + + + Copy Media + mediaviewer.copy-media + action-disabled + + + + \ No newline at end of file diff --git a/src/Dialogs/MainWindow.vala b/src/Dialogs/MainWindow.vala index a7d6bf7a4..c5439bd54 100644 --- a/src/Dialogs/MainWindow.vala +++ b/src/Dialogs/MainWindow.vala @@ -44,21 +44,16 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { #endif } - public bool is_media_viewer_visible () { - return media_viewer.visible; + public bool is_media_viewer_visible { + get { return media_viewer.visible; } } public void scroll_media_viewer (int pos) { - if (!is_media_viewer_visible ()) return; + if (!is_media_viewer_visible) return; media_viewer.scroll_to (pos); } - public void temp_reveal_media_viewer (Gtk.Widget widget) { - // if (is_media_viewer_visible ()) - media_viewer.reveal (widget); - } - public void show_media_viewer ( string url, Tuba.Attachment.MediaType media_type, @@ -73,7 +68,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { media_viewer.add_media (url, media_type, preview, pos, as_is, user_friendly_url); - if (!is_media_viewer_visible ()) { + if (!is_media_viewer_visible) { if (source_widget != null) { media_viewer.reveal (source_widget); } else { @@ -83,12 +78,11 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { } public void show_media_viewer_remote_video (string url, Gdk.Paintable? preview, string? user_friendly_url = null) { - if (!is_media_viewer_visible ()) { - // main_stack.visible_child_name = "media_viewer"; - // media_viewer.clear.connect (hide_media_viewer); - } - media_viewer.set_remote_video (url, preview, user_friendly_url); + + if (!is_media_viewer_visible) { + media_viewer.visible = true; + } } public void show_book (API.BookWyrm book, string? fallback = null) { @@ -149,7 +143,7 @@ public class Tuba.Dialogs.MainWindow: Adw.ApplicationWindow, Saveable { } public bool back () { - if (is_media_viewer_visible ()) { + if (is_media_viewer_visible) { media_viewer.clear (); return true; }; diff --git a/src/Views/MediaViewer.vala b/src/Views/MediaViewer.vala index 3db4fb168..be6ca597f 100644 --- a/src/Views/MediaViewer.vala +++ b/src/Views/MediaViewer.vala @@ -61,7 +61,8 @@ public class Tuba.Attachment { } } -public class Tuba.Views.MediaViewer : Gtk.Box { +[GtkTemplate (ui = "/dev/geopjr/Tuba/ui/views/media_viewer.ui")] +public class Tuba.Views.MediaViewer : Adw.Bin { const double MAX_ZOOM = 20; static double last_used_volume = 1.0; @@ -283,44 +284,29 @@ public class Tuba.Views.MediaViewer : Gtk.Box { }; private Gee.ArrayList items = new Gee.ArrayList (); - protected Gtk.Button fullscreen_btn; - protected Adw.HeaderBar headerbar; - private Adw.Carousel carousel; - private Adw.CarouselIndicatorDots carousel_dots; protected SimpleAction copy_media_simple_action; - private Tuba.Widgets.ScaleRevealer scale_revealer; - protected Gtk.PopoverMenu context_menu { get; set; } - construct { - carousel = new Adw.Carousel () { - vexpand = true, - hexpand = true, - css_classes = {"osd"} - }; + [GtkChild] unowned Gtk.PopoverMenu context_menu; + [GtkChild] unowned Gtk.Button fullscreen_btn; + + [GtkChild] unowned Gtk.Revealer page_buttons_revealer; + [GtkChild] unowned Gtk.Button prev_btn; + [GtkChild] unowned Gtk.Button next_btn; + + [GtkChild] unowned Gtk.Revealer zoom_buttons_revealer; + [GtkChild] unowned Gtk.Button zoom_out_btn; + [GtkChild] unowned Gtk.Button zoom_in_btn; + + [GtkChild] unowned Tuba.Widgets.ScaleRevealer scale_revealer; + [GtkChild] unowned Adw.Carousel carousel; + [GtkChild] unowned Adw.CarouselIndicatorDots carousel_dots; + construct { // Move between media using the arrow keys var keypresscontroller = new Gtk.EventControllerKey (); keypresscontroller.key_pressed.connect (on_keypress); add_controller (keypresscontroller); - var overlay = new Gtk.Overlay () { - vexpand = true, - hexpand = true - }; - - Gtk.Widget zoom_btns; - Gtk.Widget page_btns; - generate_media_buttons (out page_btns, out zoom_btns); - - scale_revealer = new Tuba.Widgets.ScaleRevealer () { - child = carousel - }; - scale_revealer.transition_done.connect (on_scale_revealer_transition_end); - - overlay.add_overlay (page_btns); - overlay.add_overlay (zoom_btns); - overlay.child = scale_revealer; - var drag = new Gtk.GestureDrag (); drag.drag_begin.connect (on_drag_begin); drag.drag_update.connect (on_drag_update); @@ -337,9 +323,6 @@ public class Tuba.Views.MediaViewer : Gtk.Box { motion.motion.connect (on_motion); add_controller (motion); - orientation = Gtk.Orientation.VERTICAL; - spacing = 0; - var actions = new GLib.SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); @@ -349,55 +332,11 @@ public class Tuba.Views.MediaViewer : Gtk.Box { this.insert_action_group ("mediaviewer", actions); - headerbar = new Adw.HeaderBar () { - title_widget = new Gtk.Label (_("Media Viewer")) { - css_classes = {"title"} - }, - css_classes = {"flat", "media-viewer-headerbar"} - }; - var back_btn = new Gtk.Button.from_icon_name ("tuba-left-large-symbolic") { - tooltip_text = _("Go Back") - }; - back_btn.clicked.connect (on_back_clicked); - headerbar.pack_start (back_btn); - - var end_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); - fullscreen_btn = new Gtk.Button.from_icon_name ("view-fullscreen-symbolic") { - tooltip_text = _("Toggle Fullscreen") - }; - fullscreen_btn.clicked.connect (toggle_fullscreen); - - var menu_model = create_actions_menu (); - var actions_btn = new Gtk.MenuButton () { - icon_name = "view-more-symbolic", - menu_model = menu_model - }; - - context_menu = new Gtk.PopoverMenu.from_model (menu_model) { - has_arrow = false, - halign = Gtk.Align.START - }; + carousel.notify["n-pages"].connect (on_carousel_n_pages_changed); + carousel.page_changed.connect (on_carousel_page_changed); + scale_revealer.transition_done.connect (on_scale_revealer_transition_end); context_menu.set_parent (this); - end_box.append (fullscreen_btn); - end_box.append (actions_btn); - headerbar.pack_end (end_box); - - carousel_dots = new Adw.CarouselIndicatorDots () { - carousel = carousel, - css_classes = {"osd"}, - visible = false - }; - - carousel.bind_property ("n_pages", carousel_dots, "visible", BindingFlags.SYNC_CREATE, (b, src, ref target) => { - target.set_boolean (src.get_uint () > 1); - return true; - }); - - append (headerbar); - append (overlay); - append (carousel_dots); - setup_mouse_previous_click (); setup_double_click (); setup_mouse_secondary_click (); @@ -408,7 +347,11 @@ public class Tuba.Views.MediaViewer : Gtk.Box { } private void on_scale_revealer_transition_end () { - if (!scale_revealer.reveal_child) this.visible = false; + if (!scale_revealer.reveal_child) { + this.visible = false; + scale_revealer.source_widget = null; + reset_media_viewer (); + } } int? old_height; @@ -486,28 +429,17 @@ public class Tuba.Views.MediaViewer : Gtk.Box { return true; } - protected void on_back_clicked () { - clear (); + [GtkCallback] + public void clear () { + if (!revealed) reset_media_viewer (); scale_revealer.reveal_child = false; } - protected void toggle_fullscreen () { + [GtkCallback] + private void toggle_fullscreen () { this.fullscreen = !this._fullscreen; } - protected GLib.Menu create_actions_menu () { - var menu_model = new GLib.Menu (); - menu_model.append (_("Open in Browser"), "mediaviewer.open-in-browser"); - menu_model.append (_("Copy URL"), "mediaviewer.copy-url"); - menu_model.append (_("Save Media"), "mediaviewer.save-as"); - - var copy_media_menu_item = new MenuItem (_("Copy Media"), "mediaviewer.copy-media"); - copy_media_menu_item.set_attribute_value ("hidden-when", "action-disabled"); - menu_model.append_item (copy_media_menu_item); - - return menu_model; - } - private void copy_url () { Item? page = safe_get ((int) carousel.position); if (page == null) return; @@ -605,7 +537,7 @@ public class Tuba.Views.MediaViewer : Gtk.Box { } private void handle_mouse_previous_click (int n_press, double x, double y) { - on_back_clicked (); + clear (); } private void on_double_click (int n_press, double x, double y) { @@ -617,7 +549,7 @@ public class Tuba.Views.MediaViewer : Gtk.Box { page.on_double_click (); } - public virtual signal void clear () { + private void reset_media_viewer () { this.fullscreen = false; items.foreach ((item) => { @@ -744,106 +676,60 @@ public class Tuba.Views.MediaViewer : Gtk.Box { }, Priority.LOW); } - private Gtk.Button zoom_out_btn; - private Gtk.Button zoom_in_btn; - private Gtk.Revealer page_buttons_revealer; - private Gtk.Revealer zoom_buttons_revealer; - private void generate_media_buttons (out Gtk.Revealer page_btns, out Gtk.Revealer zoom_btns) { - var t_page_btns = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); - var t_zoom_btns = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); - - var prev_btn = new Gtk.Button.from_icon_name ("go-previous-symbolic") { - css_classes = {"circular", "osd", "media-viewer-fab"}, - tooltip_text = _("Previous Attachment") - }; - - var next_btn = new Gtk.Button.from_icon_name ("go-next-symbolic") { - css_classes = {"circular", "osd", "media-viewer-fab"}, - tooltip_text = _("Next Attachment") - }; - - prev_btn.clicked.connect (() => scroll_to (((int) carousel.position) - 1, false)); - next_btn.clicked.connect (() => scroll_to (((int) carousel.position) + 1, false)); - - carousel.notify["n-pages"].connect (() => { - var has_more_than_1_item = carousel.n_pages > 1; - - prev_btn.visible = has_more_than_1_item; - next_btn.visible = has_more_than_1_item; - }); - - t_page_btns.append (prev_btn); - t_page_btns.append (next_btn); - - zoom_out_btn = new Gtk.Button.from_icon_name ("zoom-out-symbolic") { - css_classes = {"circular", "osd", "media-viewer-fab"}, - tooltip_text = _("Zoom Out") - }; + [GtkCallback] + private void on_previous_clicked () { + scroll_to (((int) carousel.position) - 1, false); + } - zoom_in_btn = new Gtk.Button.from_icon_name ("zoom-in-symbolic") { - css_classes = {"circular", "osd", "media-viewer-fab"}, - tooltip_text = _("Zoom In") - }; + [GtkCallback] + private void on_next_clicked () { + scroll_to (((int) carousel.position) + 1, false); + } - zoom_out_btn.clicked.connect (() => { - Item? page = safe_get ((int) carousel.position); + [GtkCallback] + private void on_zoom_out_clicked () { + Item? page = safe_get ((int) carousel.position); if (page == null) return; page.zoom_out (); - }); - zoom_in_btn.clicked.connect (() => { - Item? page = safe_get ((int) carousel.position); + } + + [GtkCallback] + private void on_zoom_in_clicked () { + Item? page = safe_get ((int) carousel.position); if (page == null) return; page.zoom_in (); - }); - - carousel.page_changed.connect ((pos) => { - prev_btn.sensitive = pos > 0; - next_btn.sensitive = pos < items.size - 1; - - Item? page = safe_get ((int) pos); - // Media buttons overlap the video - // controller, so position them higher - if (page != null && page.is_video) { - page_buttons_revealer.margin_bottom = zoom_buttons_revealer.margin_bottom = 68; - zoom_buttons_revealer.visible = false; - play_video ((int) pos); - copy_media_simple_action.set_enabled (false); - } else { - page_buttons_revealer.margin_bottom = zoom_buttons_revealer.margin_bottom = 18; - zoom_buttons_revealer.visible = true; - pause_all_videos (); - copy_media_simple_action.set_enabled (true); - } + } - on_zoom_change (); - }); + private void on_carousel_page_changed (uint pos) { + prev_btn.sensitive = pos > 0; + next_btn.sensitive = pos < items.size - 1; + + Item? page = safe_get ((int) pos); + // Media buttons overlap the video + // controller, so position them higher + if (page != null && page.is_video) { + page_buttons_revealer.margin_bottom = zoom_buttons_revealer.margin_bottom = 68; + zoom_buttons_revealer.visible = false; + play_video ((int) pos); + copy_media_simple_action.set_enabled (false); + } else { + page_buttons_revealer.margin_bottom = zoom_buttons_revealer.margin_bottom = 18; + zoom_buttons_revealer.visible = true; + pause_all_videos (); + copy_media_simple_action.set_enabled (true); + } - t_zoom_btns.append (zoom_out_btn); - t_zoom_btns.append (zoom_in_btn); - - zoom_buttons_revealer = new Gtk.Revealer () { - child = t_zoom_btns, - transition_type = Gtk.RevealerTransitionType.CROSSFADE, - valign = Gtk.Align.END, - halign = Gtk.Align.END, - margin_end = 18, - margin_bottom = 18, - visible = false - }; + on_zoom_change (); + } - page_buttons_revealer = new Gtk.Revealer () { - child = t_page_btns, - transition_type = Gtk.RevealerTransitionType.CROSSFADE, - valign = Gtk.Align.END, - halign = Gtk.Align.START, - margin_start = 18, - margin_bottom = 18 - }; + private void on_carousel_n_pages_changed () { + bool has_more_than_1_item = carousel.n_pages > 1; - page_btns = page_buttons_revealer; - zoom_btns = zoom_buttons_revealer; + carousel_dots.visible = has_more_than_1_item; + prev_btn.visible = has_more_than_1_item; + next_btn.visible = has_more_than_1_item; } public void on_zoom_change () { diff --git a/src/Widgets/Attachment/Item.vala b/src/Widgets/Attachment/Item.vala index d36f31267..1beb92c2b 100644 --- a/src/Widgets/Attachment/Item.vala +++ b/src/Widgets/Attachment/Item.vala @@ -221,7 +221,7 @@ public class Tuba.Widgets.Attachment.Item : Adw.Bin { gesture_click_controller.set_state (Gtk.EventSequenceState.CLAIMED); gesture_lp_controller.set_state (Gtk.EventSequenceState.CLAIMED); - if (app.main_window.is_media_viewer_visible ()) return; + if (app.main_window.is_media_viewer_visible) return; Gdk.Rectangle rectangle = { (int) x, (int) y, From ab8b16c082a0fb918298e06ef3bdbabf2923a58b Mon Sep 17 00:00:00 2001 From: Evangelos Paterakis Date: Sat, 14 Oct 2023 01:35:24 +0300 Subject: [PATCH 04/13] feat: swipe to dismiss --- data/style.css | 1 - data/ui/views/media_viewer.ui | 4 +- src/Views/MediaViewer.vala | 110 +++++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/data/style.css b/data/style.css index 32fe8a624..1a293ab8d 100644 --- a/data/style.css +++ b/data/style.css @@ -232,7 +232,6 @@ flowboxchild { } .media-viewer { - background: black; color: white; } diff --git a/data/ui/views/media_viewer.ui b/data/ui/views/media_viewer.ui index 2fdd69205..5bb4df858 100644 --- a/data/ui/views/media_viewer.ui +++ b/data/ui/views/media_viewer.ui @@ -20,7 +20,7 @@ action-disabled -