diff --git a/src/bz-article.c b/src/bz-article.c index 71777299..f92aed4b 100644 --- a/src/bz-article.c +++ b/src/bz-article.c @@ -37,6 +37,7 @@ #include "bz-screenshot-page.h" #include "bz-state-info.h" #include "bz-util.h" +#include "bz-window.h" struct _BzArticle { @@ -347,23 +348,24 @@ static void screenshot_clicked (GtkButton *button, gpointer user_data) { - BzArticle *self = user_data; - guint index = 0; - AdwNavigationPage *page = NULL; - GtkWidget *nav_view = NULL; + BzArticle *self = user_data; + guint index = 0; + GtkWidget *root = NULL; + BzScreenshotPage *page = NULL; index = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (button), "bz-screenshot-index")); - nav_view = gtk_widget_get_ancestor (GTK_WIDGET (button), ADW_TYPE_NAVIGATION_VIEW); - if (nav_view == NULL) + root = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (button))); + if (!BZ_IS_WINDOW (root)) return; - page = bz_screenshot_page_new ( + page = BZ_SCREENSHOT_PAGE (bz_screenshot_page_new ( G_LIST_MODEL (self->screenshot_textures), G_LIST_MODEL (self->screenshot_captions), - index); + index, + GTK_WIDGET (button))); - adw_navigation_view_push (ADW_NAVIGATION_VIEW (nav_view), page); + bz_window_open_screenshot_page (BZ_WINDOW (root), page); } static GtkWidget * diff --git a/src/bz-full-view.blp b/src/bz-full-view.blp index 85a5ee14..0717cc3b 100644 --- a/src/bz-full-view.blp +++ b/src/bz-full-view.blp @@ -473,7 +473,6 @@ template $BzFullView: Adw.Bin { $BzScreenshotsCarousel screenshots { vexpand: true; hexpand: true; - clicked => $screenshot_clicked_cb() swapped; light-accent-color: bind template.ui-entry as <$BzResult>.object as <$BzEntry>.light-accent-color; dark-accent-color: bind template.ui-entry as <$BzResult>.object as <$BzEntry>.dark-accent-color; @@ -733,4 +732,4 @@ template $BzFullView: Adw.Bin { } }; }; -} +} \ No newline at end of file diff --git a/src/bz-full-view.c b/src/bz-full-view.c index 5cf1e229..1fa0b5e8 100644 --- a/src/bz-full-view.c +++ b/src/bz-full-view.c @@ -435,37 +435,6 @@ dl_stats_cb (BzFullView *self, bz_stats_dialog_animate_open (BZ_STATS_DIALOG (bin)); } -static void -screenshot_clicked_cb (BzFullView *self, - guint index, - BzScreenshotsCarousel *carousel) -{ - GListModel *screenshots = NULL; - GListModel *captions = NULL; - AdwNavigationPage *page = NULL; - GtkWidget *nav_view = NULL; - BzEntry *entry = NULL; - - screenshots = bz_screenshots_carousel_get_model (carousel); - if (screenshots == NULL) - return; - - if (self->ui_entry != NULL) - { - entry = bz_result_get_object (self->ui_entry); - if (entry != NULL) - g_object_get (entry, "screenshot-captions", &captions, NULL); - } - - page = bz_screenshot_page_new (screenshots, captions, index); - - g_clear_object (&captions); - - nav_view = gtk_widget_get_ancestor (GTK_WIDGET (self), ADW_TYPE_NAVIGATION_VIEW); - if (nav_view != NULL) - adw_navigation_view_push (ADW_NAVIGATION_VIEW (nav_view), page); -} - static void size_cb (BzFullView *self, GtkButton *button) @@ -801,7 +770,6 @@ bz_full_view_class_init (BzFullViewClass *klass) gtk_widget_class_bind_template_callback (widget_class, open_url_cb); gtk_widget_class_bind_template_callback (widget_class, license_cb); gtk_widget_class_bind_template_callback (widget_class, dl_stats_cb); - gtk_widget_class_bind_template_callback (widget_class, screenshot_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, size_cb); gtk_widget_class_bind_template_callback (widget_class, formfactor_cb); gtk_widget_class_bind_template_callback (widget_class, safety_cb); diff --git a/src/bz-screenshot-page.blp b/src/bz-screenshot-page.blp index c59a3404..67b06750 100644 --- a/src/bz-screenshot-page.blp +++ b/src/bz-screenshot-page.blp @@ -1,9 +1,7 @@ using Gtk 4.0; using Adw 1; -template $BzScreenshotPage: Adw.NavigationPage { - title: _("Screenshots"); - +template $BzScreenshotPage: Adw.Bin { child: Adw.BreakpointBin { width-request: 360; height-request: 100; @@ -34,7 +32,19 @@ template $BzScreenshotPage: Adw.NavigationPage { show-end-title-buttons: false; show-start-title-buttons: false; + show-back-button: false; show-title: false; + + [start] + Button back_button { + icon-name: "go-previous-symbolic"; + tooltip-text: _("Back"); + clicked => $back_clicked() swapped; + + styles [ + "flat", + ] + } } content: Overlay { diff --git a/src/bz-screenshot-page.c b/src/bz-screenshot-page.c index 77b156fb..f1e22335 100644 --- a/src/bz-screenshot-page.c +++ b/src/bz-screenshot-page.c @@ -25,7 +25,7 @@ struct _BzScreenshotPage { - AdwNavigationPage parent_instance; + AdwBin parent_instance; AdwCarousel *carousel; AdwToastOverlay *toast_overlay; @@ -36,9 +36,16 @@ struct _BzScreenshotPage guint initial_index; gboolean is_zoomed; + + GtkWidget *source_widget; + GdkTexture *source_texture; + graphene_rect_t source_bounds_at_map; + AdwTimedAnimation *animation; + double animation_progress; + gboolean closing; }; -G_DEFINE_FINAL_TYPE (BzScreenshotPage, bz_screenshot_page, ADW_TYPE_NAVIGATION_PAGE) +G_DEFINE_FINAL_TYPE (BzScreenshotPage, bz_screenshot_page, ADW_TYPE_BIN) static void on_zoom_level_changed (BzZoom *zoom, GParamSpec *pspec, @@ -57,6 +64,193 @@ enum }; static GParamSpec *props[LAST_PROP] = { 0 }; +static GdkTexture * +render_widget_to_texture (GtkWidget *widget) +{ + g_autoptr (GtkWidgetPaintable) paintable = NULL; + g_autoptr (GtkSnapshot) snapshot = NULL; + g_autoptr (GskRenderNode) node = NULL; + GtkNative *native = NULL; + GdkTexture *texture = NULL; + + paintable = GTK_WIDGET_PAINTABLE (gtk_widget_paintable_new (widget)); + snapshot = gtk_snapshot_new (); + + gdk_paintable_snapshot (GDK_PAINTABLE (paintable), snapshot, + gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (paintable)), + gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (paintable))); + + node = gtk_snapshot_to_node (snapshot); + native = gtk_widget_get_native (widget); + + if (node != NULL && native != NULL) + texture = gsk_renderer_render_texture ( + gtk_native_get_renderer (native), node, NULL); + + return texture; +} + +static void +on_animation_value (AdwTimedAnimation *animation, + GParamSpec *pspec, + BzScreenshotPage *self) +{ + self->animation_progress = adw_animation_get_value (ADW_ANIMATION (animation)); + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +on_close_animation_done (AdwAnimation *animation, + BzScreenshotPage *self) +{ + GtkWidget *parent = NULL; + + if (self->source_widget != NULL) + gtk_widget_set_opacity (self->source_widget, 1.0); + + parent = gtk_widget_get_parent (GTK_WIDGET (self)); + if (parent != NULL) + gtk_overlay_remove_overlay (GTK_OVERLAY (parent), GTK_WIDGET (self)); +} + +static void +back_clicked (BzScreenshotPage *self) +{ + AdwAnimationTarget *target = NULL; + GtkWidget *parent = NULL; + + if (self->closing) + return; + + if (self->source_widget != NULL) + { + self->closing = TRUE; + + if (self->animation != NULL) + { + g_signal_handlers_disconnect_by_func (self->animation, on_animation_value, self); + g_clear_object (&self->animation); + } + + target = adw_callback_animation_target_new ( + (AdwAnimationTargetFunc) gtk_widget_queue_draw, self, NULL); + + self->animation = ADW_TIMED_ANIMATION ( + adw_timed_animation_new (GTK_WIDGET (self), 1.0, 0.0, 250, target)); + + adw_timed_animation_set_easing (self->animation, ADW_EASE_IN_QUART); + + g_signal_connect (self->animation, "notify::value", G_CALLBACK (on_animation_value), self); + g_signal_connect (self->animation, "done", G_CALLBACK (on_close_animation_done), self); + + adw_animation_play (ADW_ANIMATION (self->animation)); + } + else + { + if (self->source_widget != NULL) + gtk_widget_set_opacity (self->source_widget, 1.0); + + parent = gtk_widget_get_parent (GTK_WIDGET (self)); + if (parent != NULL) + gtk_overlay_remove_overlay (GTK_OVERLAY (parent), GTK_WIDGET (self)); + } +} + +static void +bz_screenshot_page_map (GtkWidget *widget) +{ + BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget); + AdwAnimationTarget *target = NULL; + + GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->map (widget); + + if (self->source_widget != NULL) + { + self->source_texture = render_widget_to_texture (self->source_widget); + + if (!gtk_widget_compute_bounds (self->source_widget, widget, &self->source_bounds_at_map)) + graphene_rect_init (&self->source_bounds_at_map, 0, 0, + gtk_widget_get_width (widget), gtk_widget_get_height (widget)); + + gtk_widget_set_opacity (self->source_widget, 0.0); + } + + self->animation_progress = 0.0; + self->closing = FALSE; + + target = adw_callback_animation_target_new ((AdwAnimationTargetFunc) gtk_widget_queue_draw, self, NULL); + self->animation = ADW_TIMED_ANIMATION (adw_timed_animation_new (widget, 0.0, 1.0, 300, target)); + adw_timed_animation_set_easing (self->animation, ADW_EASE_OUT_QUART); + + g_signal_connect (self->animation, "notify::value", G_CALLBACK (on_animation_value), self); + + adw_animation_play (ADW_ANIMATION (self->animation)); +} + +static void +bz_screenshot_page_unmap (GtkWidget *widget) +{ + BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget); + + if (self->source_widget != NULL && !self->closing) + gtk_widget_set_opacity (self->source_widget, 1.0); + + GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->unmap (widget); +} + +static void +bz_screenshot_page_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) +{ + BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget); + double progress = self->animation_progress; + int width = gtk_widget_get_width (widget); + int height = gtk_widget_get_height (widget); + float rev; + float x, y, w, h; + graphene_rect_t lerped; + + if (self->source_texture == NULL || progress >= 1.0) + { + GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->snapshot (widget, snapshot); + return; + } + + rev = (float) (1.0 - progress); + + x = self->source_bounds_at_map.origin.x * rev + 0.0f * (float) progress; + y = self->source_bounds_at_map.origin.y * rev + 0.0f * (float) progress; + w = self->source_bounds_at_map.size.width * rev + (float) width * (float) progress; + h = self->source_bounds_at_map.size.height * rev + (float) height * (float) progress; + + graphene_rect_init (&lerped, x, y, w, h); + + gtk_snapshot_push_clip (snapshot, &lerped); + gtk_snapshot_push_cross_fade (snapshot, progress); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); + gtk_snapshot_scale (snapshot, + w / self->source_bounds_at_map.size.width, + h / self->source_bounds_at_map.size.height); + gdk_paintable_snapshot (GDK_PAINTABLE (self->source_texture), snapshot, + self->source_bounds_at_map.size.width, + self->source_bounds_at_map.size.height); + gtk_snapshot_restore (snapshot); + + gtk_snapshot_pop (snapshot); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); + gtk_snapshot_scale (snapshot, + w / (float) width, + h / (float) height); + GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->snapshot (widget, snapshot); + gtk_snapshot_restore (snapshot); + + gtk_snapshot_pop (snapshot); + gtk_snapshot_pop (snapshot); +} + static void bz_screenshot_page_dispose (GObject *object) { @@ -64,6 +258,8 @@ bz_screenshot_page_dispose (GObject *object) g_clear_object (&self->screenshots); g_clear_object (&self->captions); + g_clear_object (&self->source_texture); + g_clear_object (&self->animation); G_OBJECT_CLASS (bz_screenshot_page_parent_class)->dispose (object); } @@ -344,9 +540,11 @@ on_carousel_position_changed (AdwCarousel *carousel, GtkWidget *old_page = NULL; GtkWidget *new_page = NULL; BzZoom *old_zoom = NULL; + guint new_index; + guint n_pages; - guint new_index = (guint) round (adw_carousel_get_position (carousel)); - guint n_pages = adw_carousel_get_n_pages (carousel); + new_index = (guint) round (adw_carousel_get_position (carousel)); + n_pages = adw_carousel_get_n_pages (carousel); if (new_index == self->current_index || new_index >= n_pages) return; @@ -431,6 +629,42 @@ on_key_pressed (GtkEventControllerKey *controller, return FALSE; } +static void +on_button_pressed (GtkGestureClick *gesture, + int n_press, + double x, + double y, + BzScreenshotPage *self) +{ + if (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)) == 8) + back_clicked (self); +} + +static void +on_swipe (BzScreenshotPage *self, + gdouble vel_x, + gdouble vel_y) +{ + if (vel_y < -500.0 || vel_y > 500.0) + back_clicked (self); +} + +static gboolean +on_scroll (BzScreenshotPage *self, + gdouble dx, + gdouble dy, + GtkEventControllerScroll *controller) +{ + GdkEvent *event = gtk_event_controller_get_current_event (GTK_EVENT_CONTROLLER (controller)); + GdkDevice *device = gdk_event_get_device (event); + GdkInputSource source = gdk_device_get_source (device); + + if (source == GDK_SOURCE_TOUCHPAD && (dy < -2.0 || dy > 2.0)) + back_clicked (self); + + return GDK_EVENT_PROPAGATE; +} + static gboolean has_multiple_screenshots (GObject *object, GListModel *screenshots, @@ -466,6 +700,10 @@ bz_screenshot_page_class_init (BzScreenshotPageClass *klass) object_class->get_property = bz_screenshot_page_get_property; object_class->set_property = bz_screenshot_page_set_property; + widget_class->map = bz_screenshot_page_map; + widget_class->unmap = bz_screenshot_page_unmap; + widget_class->snapshot = bz_screenshot_page_snapshot; + props[PROP_SCREENSHOTS] = g_param_spec_object ( "screenshots", @@ -501,6 +739,7 @@ bz_screenshot_page_class_init (BzScreenshotPageClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-screenshot-page.ui"); gtk_widget_class_bind_template_child (widget_class, BzScreenshotPage, carousel); gtk_widget_class_bind_template_child (widget_class, BzScreenshotPage, toast_overlay); + gtk_widget_class_bind_template_callback (widget_class, back_clicked); gtk_widget_class_bind_template_callback (widget_class, zoom_in_clicked); gtk_widget_class_bind_template_callback (widget_class, zoom_out_clicked); gtk_widget_class_bind_template_callback (widget_class, on_carousel_position_changed); @@ -517,13 +756,34 @@ static void bz_screenshot_page_init (BzScreenshotPage *self) { GtkEventController *key_controller = NULL; + GtkEventController *scroll = NULL; + GtkGesture *swipe = NULL; + GtkGesture *click = NULL; gtk_widget_init_template (GTK_WIDGET (self)); key_controller = gtk_event_controller_key_new (); + gtk_event_controller_set_propagation_phase (key_controller, GTK_PHASE_CAPTURE); g_signal_connect (key_controller, "key-pressed", G_CALLBACK (on_key_pressed), self); gtk_widget_add_controller (GTK_WIDGET (self), key_controller); + + swipe = gtk_gesture_swipe_new (); + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (swipe), GTK_PHASE_CAPTURE); + g_signal_connect_swapped (swipe, "swipe", G_CALLBACK (on_swipe), self); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (swipe), TRUE); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (swipe)); + + click = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), 0); + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (click), GTK_PHASE_CAPTURE); + g_signal_connect (click, "pressed", G_CALLBACK (on_button_pressed), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click)); + + scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); + gtk_event_controller_set_propagation_phase (scroll, GTK_PHASE_BUBBLE); + g_signal_connect_swapped (scroll, "scroll", G_CALLBACK (on_scroll), self); + gtk_widget_add_controller (GTK_WIDGET (self), scroll); } const char * @@ -561,10 +821,24 @@ bz_screenshot_page_set_captions (BzScreenshotPage *self, g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_CAPTION]); } -AdwNavigationPage * +void +bz_screenshot_page_close (BzScreenshotPage *self) +{ + g_return_if_fail (BZ_IS_SCREENSHOT_PAGE (self)); + back_clicked (self); +} + +gboolean +bz_screenshot_page_is_closing (BzScreenshotPage *self) +{ + return self->closing; +} + +AdwBin * bz_screenshot_page_new (GListModel *screenshots, GListModel *captions, - guint initial_index) + guint initial_index, + GtkWidget *source_widget) { BzScreenshotPage *page = g_object_new ( BZ_TYPE_SCREENSHOT_PAGE, @@ -572,8 +846,10 @@ bz_screenshot_page_new (GListModel *screenshots, "current-index", initial_index, NULL); + page->source_widget = source_widget; + if (captions != NULL) bz_screenshot_page_set_captions (page, captions); - return ADW_NAVIGATION_PAGE (page); + return ADW_BIN (page); } diff --git a/src/bz-screenshot-page.h b/src/bz-screenshot-page.h index ce288224..abed7258 100644 --- a/src/bz-screenshot-page.h +++ b/src/bz-screenshot-page.h @@ -27,11 +27,13 @@ G_BEGIN_DECLS #define BZ_TYPE_SCREENSHOT_PAGE (bz_screenshot_page_get_type ()) -G_DECLARE_FINAL_TYPE (BzScreenshotPage, bz_screenshot_page, BZ, SCREENSHOT_PAGE, AdwNavigationPage) +G_DECLARE_FINAL_TYPE (BzScreenshotPage, bz_screenshot_page, BZ, SCREENSHOT_PAGE, AdwBin) -AdwNavigationPage *bz_screenshot_page_new (GListModel *screenshots, - GListModel *captions, - guint initial_index); +AdwBin * +bz_screenshot_page_new (GListModel *screenshots, + GListModel *captions, + guint initial_index, + GtkWidget *source_widget); void bz_screenshot_page_set_captions (BzScreenshotPage *self, @@ -40,4 +42,10 @@ bz_screenshot_page_set_captions (BzScreenshotPage *self, const char * bz_screenshot_page_get_current_caption (BzScreenshotPage *self); +void +bz_screenshot_page_close (BzScreenshotPage *self); + +gboolean +bz_screenshot_page_is_closing (BzScreenshotPage *self); + G_END_DECLS diff --git a/src/bz-screenshots-carousel.c b/src/bz-screenshots-carousel.c index e5bc1101..4f108df0 100644 --- a/src/bz-screenshots-carousel.c +++ b/src/bz-screenshots-carousel.c @@ -32,7 +32,9 @@ #include "bz-decorated-screenshot.h" #include "bz-screenshots-carousel.h" +#include "bz-screenshot-page.h" #include "bz-template-callbacks.h" +#include "bz-window.h" #define LIGHT_CLASS "screenshot-carousel-light" #define DARK_CLASS "screenshot-carousel-dark" @@ -76,14 +78,6 @@ enum static GParamSpec *properties[N_PROPS]; -enum -{ - SIGNAL_CLICKED, - N_SIGNALS -}; - -static guint signals[N_SIGNALS]; - static void refresh_css (BzScreenshotsCarousel *self); static void clear_css (BzScreenshotsCarousel *self); @@ -165,7 +159,11 @@ on_notify_model (BzScreenshotsCarousel *self) static void open_screenshot_at_index (BzScreenshotsCarousel *self, guint index) { - guint n_items; + BzScreenshotPage *page = NULL; + GtkWidget *picture = NULL; + GtkWidget *child = NULL; + GtkWidget *root = NULL; + guint n_items; if (!self->model) return; @@ -174,7 +172,16 @@ open_screenshot_at_index (BzScreenshotsCarousel *self, guint index) if (index >= n_items) return; - g_signal_emit (self, signals[SIGNAL_CLICKED], 0, index); + child = bge_carousel_get_nth_page (self->carousel, index); + if (child != NULL) + picture = gtk_widget_get_first_child (child); + + root = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self))); + if (!BZ_IS_WINDOW (root)) + return; + + page = BZ_SCREENSHOT_PAGE (bz_screenshot_page_new (self->model, self->captions, index, picture)); + bz_window_open_screenshot_page (BZ_WINDOW (root), page); } static void @@ -476,17 +483,6 @@ bz_screenshots_carousel_class_init (BzScreenshotsCarouselClass *klass) g_object_class_install_properties (object_class, N_PROPS, properties); - signals[SIGNAL_CLICKED] = - g_signal_new ("clicked", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_UINT); - gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-screenshots-carousel.ui"); bz_widget_class_bind_all_util_callbacks (widget_class); diff --git a/src/bz-window.blp b/src/bz-window.blp index 93fad38e..c9df4e0e 100644 --- a/src/bz-window.blp +++ b/src/bz-window.blp @@ -32,243 +32,245 @@ template $BzWindow: Adw.ApplicationWindow { } } - content: Adw.ToolbarView { - content: Adw.BreakpointBin split_breakpoint_bin { - width-request: 360; - height-request: 450; - - child: Adw.ToastOverlay toasts { - child: Adw.NavigationView navigation_view { - pop-on-escape: false; - - Adw.NavigationPage { - tag: "main"; - title: _("Bazaar"); - - child: Adw.ToolbarView toolbar_view { - top-bar-style: flat; - bottom-bar-style: flat; - reveal-bottom-bars: false; - - content: Stack main_stack { - StackPage { - name: "loading"; - - child: Box { - halign: fill; - valign: center; - orientation: vertical; - spacing: 6; - tooltip-text: bind template.state as <$BzStateInfo>.background-task-label; - - Adw.Spinner { - width-request: 64; - height-request: 64; - margin-bottom: 18; - } + content: Gtk.Overlay window_overlay { + child: Adw.ToolbarView { + content: Adw.BreakpointBin split_breakpoint_bin { + width-request: 360; + height-request: 450; + + child: Adw.ToastOverlay toasts { + child: Adw.NavigationView navigation_view { + pop-on-escape: false; + + Adw.NavigationPage { + tag: "main"; + title: _("Bazaar"); + + child: Adw.ToolbarView toolbar_view { + top-bar-style: flat; + bottom-bar-style: flat; + reveal-bottom-bars: false; + + content: Stack main_stack { + StackPage { + name: "loading"; + + child: Box { + halign: fill; + valign: center; + orientation: vertical; + spacing: 6; + tooltip-text: bind template.state as <$BzStateInfo>.background-task-label; - Label { - label: _("Refreshing"); - wrap: true; - justify: center; - margin-bottom: 24; + Adw.Spinner { + width-request: 64; + height-request: 64; + margin-bottom: 18; + } + + Label { + label: _("Refreshing"); + wrap: true; + justify: center; + margin-bottom: 24; + + styles [ + "title-3", + ] + } + }; + } + + StackPage { + name: "main"; + + child: Adw.ViewStack main_view_stack { + Adw.ViewStackPage { + name: "browse"; + title: _("Curated"); + icon-name: "star-outline-rounded-symbolic"; + visible: bind template.state as <$BzStateInfo>.curated-provider as <$BzContentProvider>.has-inputs; + + child: $BzCuratedView browse { + state: bind template.state as <$BzStateInfo>; + browse-flathub => $browse_flathub_cb(template); + }; + } + + Adw.ViewStackPage { + name: "flathub"; + title: _("Explore"); + icon-name: "explore-symbolic"; + + child: $BzFlathubPage { + state: bind template.state as <$BzStateInfo>; + open-search => $open_search_cb(template); + }; + } + + Adw.ViewStackPage { + name: "installed"; + // Translators: . + title: _("Library"); + icon-name: "library-symbolic"; + badge-number: bind template.state as <$BzStateInfo>.available-updates as .n-items; + needs-attention: bind template.state as <$BzStateInfo>.available-updates as .n-items; + + child: $BzLibraryPage library_page { + state: bind template.state; + transactions: bind template.state as <$BzStateInfo>.transaction-manager; + model: bind template.state as <$BzStateInfo>.all-installed-entry-groups; + update => $update_cb(template); + }; + } + + Adw.ViewStackPage { + name: "search"; + title: _("Search"); + icon-name: "system-search-symbolic"; + + child: $BzSearchPage search_page { + state: bind template.state; + }; + } + }; + } + }; + [top] + Box { + orientation: vertical; + + Adw.HeaderBar top_header_bar { + [start] + Box { styles [ - "title-3", + "bz-debug", ] - } - }; - } - StackPage { - name: "main"; + orientation: horizontal; + spacing: 6; + visible: bind template.state as <$BzStateInfo>.debug-mode; - child: Adw.ViewStack main_view_stack { - Adw.ViewStackPage { - name: "browse"; - title: _("Curated"); - icon-name: "star-outline-rounded-symbolic"; - visible: bind template.state as <$BzStateInfo>.curated-provider as <$BzContentProvider>.has-inputs; - - child: $BzCuratedView browse { - state: bind template.state as <$BzStateInfo>; - browse-flathub => $browse_flathub_cb(template); - }; + Button { + has-tooltip: true; + tooltip-text: "Open Bazaar Inspector"; + icon-name: "bug-symbolic"; + action-name: "app.bazaar-inspector"; + } } - Adw.ViewStackPage { - name: "flathub"; - title: _("Explore"); - icon-name: "explore-symbolic"; + [start] + MenuButton { + visible: bind template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.authenticated; + menu-model: account_menu; - child: $BzFlathubPage { - state: bind template.state as <$BzStateInfo>; - open-search => $open_search_cb(template); - }; - } + styles [ + "circular", + "flat", + ] - Adw.ViewStackPage { - name: "installed"; - // Translators: . - title: _("Library"); - icon-name: "library-symbolic"; - badge-number: bind template.state as <$BzStateInfo>.available-updates as .n-items; - needs-attention: bind template.state as <$BzStateInfo>.available-updates as .n-items; - - child: $BzLibraryPage library_page { - state: bind template.state; - transactions: bind template.state as <$BzStateInfo>.transaction-manager; - model: bind template.state as <$BzStateInfo>.all-installed-entry-groups; - update => $update_cb(template); + child: Adw.Avatar { + size: 24; + show-initials: true; + text: bind template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.name; + custom-image: bind try { $choose(template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.paintable as <$BzAsyncTexture>.loaded, template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.paintable , null as ), null as }; }; } - Adw.ViewStackPage { - name: "search"; - title: _("Search"); - icon-name: "system-search-symbolic"; - - child: $BzSearchPage search_page { - state: bind template.state; + [start] + Revealer { + transition-type: crossfade; + transition-duration: 750; + margin-start: 8; + reveal-child: bind $logical_and($invert_boolean(template.state as <$BzStateInfo>.busy) as , $invert_boolean($is_null(template.state as <$BzStateInfo>.background-task-label) as ) as ) as ; + child: Adw.Spinner { + width-request: 16; + height-request: 16; + has-tooltip: true; + tooltip-text: bind template.state as <$BzStateInfo>.background-task-label; }; } - }; - } - }; - [top] - Box { - orientation: vertical; - - Adw.HeaderBar top_header_bar { - [start] - Box { - styles [ - "bz-debug", - ] - - orientation: horizontal; - spacing: 6; - visible: bind template.state as <$BzStateInfo>.debug-mode; + [title] + Box { + Adw.ViewSwitcher { + stack: main_view_stack; + policy: wide; + visible: bind $invert_boolean(template.state as <$BzStateInfo>.busy) as ; + } + Label { + label: _("Bazaar"); + visible: bind template.state as <$BzStateInfo>.busy as ; + styles [ + "title", + ] + } + } - Button { + [end] + MenuButton { + primary: true; + icon-name: "open-menu-symbolic"; has-tooltip: true; - tooltip-text: "Open Bazaar Inspector"; - icon-name: "bug-symbolic"; - action-name: "app.bazaar-inspector"; + tooltip-text: _("Main Menu"); + menu-model: primary_menu; } } - [start] - MenuButton { - visible: bind template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.authenticated; - menu-model: account_menu; - + Adw.Banner { styles [ - "circular", - "flat", + "bz-donations-banner", ] - child: Adw.Avatar { - size: 24; - show-initials: true; - text: bind template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.name; - custom-image: bind try { $choose(template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.paintable as <$BzAsyncTexture>.loaded, template.state as <$BzStateInfo>.auth-state as <$BzAuthState>.paintable , null as ), null as }; - }; + revealed: bind $invert_boolean(template.state as <$BzStateInfo>.donation-prompt-dismissed as ) as ; + title: _("You are running a new version of Bazaar!"); + button-label: _("See What's New"); + action-name: "app.donate"; } - [start] - Revealer { - transition-type: crossfade; - transition-duration: 750; - margin-start: 8; - reveal-child: bind $logical_and($invert_boolean(template.state as <$BzStateInfo>.busy) as , $invert_boolean($is_null(template.state as <$BzStateInfo>.background-task-label) as ) as ) as ; - child: Adw.Spinner { - width-request: 16; - height-request: 16; - has-tooltip: true; - tooltip-text: bind template.state as <$BzStateInfo>.background-task-label; - }; - } - - [title] - Box { - Adw.ViewSwitcher { - stack: main_view_stack; - policy: wide; - visible: bind $invert_boolean(template.state as <$BzStateInfo>.busy) as ; - } - Label { - label: _("Bazaar"); - visible: bind template.state as <$BzStateInfo>.busy as ; - styles [ - "title", - ] - } - } - - [end] - MenuButton { - primary: true; - icon-name: "open-menu-symbolic"; - has-tooltip: true; - tooltip-text: _("Main Menu"); - menu-model: primary_menu; + Adw.Banner { + sensitive: bind $invert_boolean(template.state as <$BzStateInfo>.syncing as ) as ; + revealed: bind $logical_and($invert_boolean(template.state as <$BzStateInfo>.metered_connection as ) as , $logical_and(template.state as <$BzStateInfo>.have_connection as , $invert_boolean(template.state as <$BzStateInfo>.online as ) as ) as ) as ; + title: _("You have a network connection but are viewing a cached version of Flathub"); + button-label: _("Refresh Manually"); + button-clicked => $sync_cb(template); } } - Adw.Banner { - styles [ - "bz-donations-banner", - ] - - revealed: bind $invert_boolean(template.state as <$BzStateInfo>.donation-prompt-dismissed as ) as ; - title: _("You are running a new version of Bazaar!"); - button-label: _("See What's New"); - action-name: "app.donate"; + [bottom] + Adw.ViewSwitcherBar { + stack: main_view_stack; + reveal: true; + visible: bind $invert_boolean(template.state as <$BzStateInfo>.busy) as ; } + }; + } - Adw.Banner { - sensitive: bind $invert_boolean(template.state as <$BzStateInfo>.syncing as ) as ; - revealed: bind $logical_and($invert_boolean(template.state as <$BzStateInfo>.metered_connection as ) as , $logical_and(template.state as <$BzStateInfo>.have_connection as , $invert_boolean(template.state as <$BzStateInfo>.online as ) as ) as ) as ; - title: _("You have a network connection but are viewing a cached version of Flathub"); - button-label: _("Refresh Manually"); - button-clicked => $sync_cb(template); - } - } - - [bottom] - Adw.ViewSwitcherBar { - stack: main_view_stack; - reveal: true; - visible: bind $invert_boolean(template.state as <$BzStateInfo>.busy) as ; - } - }; - } - - Adw.NavigationPage { - tag: "view"; - title: bind full_view.entry-group as <$BzEntryGroup>.title as ; + Adw.NavigationPage { + tag: "view"; + title: bind full_view.entry-group as <$BzEntryGroup>.title as ; - child: $BzFullView full_view { - state: bind template.state; - update => $update_cb(template); - }; - } + child: $BzFullView full_view { + state: bind template.state; + update => $update_cb(template); + }; + } + }; }; - }; - Adw.Breakpoint { - condition ("max-width: 700px") + Adw.Breakpoint { + condition ("max-width: 700px") - setters { - top_header_bar.title-widget: null; - toolbar_view.reveal-bottom-bars: true; - } + setters { + top_header_bar.title-widget: null; + toolbar_view.reveal-bottom-bars: true; + } - apply => $breakpoint_apply_cb(template); - unapply => $breakpoint_unapply_cb(template); - } + apply => $breakpoint_apply_cb(template); + unapply => $breakpoint_unapply_cb(template); + } + }; }; }; } @@ -343,4 +345,4 @@ menu account_menu { action: "app.flathub-logout"; } } -} +} \ No newline at end of file diff --git a/src/bz-window.c b/src/bz-window.c index a08a5fe6..8aecf971 100644 --- a/src/bz-window.c +++ b/src/bz-window.c @@ -37,6 +37,7 @@ #include "bz-io.h" #include "bz-library-page.h" #include "bz-progress-bar.h" +#include "bz-screenshot-page.h" #include "bz-search-page.h" #include "bz-template-callbacks.h" #include "bz-transaction-dialog.h" @@ -53,6 +54,8 @@ struct _BzWindow GtkEventController *key_controller; + BzScreenshotPage *screenshot_page; + gboolean breakpoint_applied; /* Template widgets */ @@ -63,6 +66,7 @@ struct _BzWindow AdwToastOverlay *toasts; AdwViewStack *main_view_stack; GtkStack *main_stack; + GtkOverlay *window_overlay; }; G_DEFINE_FINAL_TYPE (BzWindow, bz_window, ADW_TYPE_APPLICATION_WINDOW) @@ -351,6 +355,15 @@ action_escape (GtkWidget *widget, GListModel *stack = NULL; guint n_pages = 0; + if (self->screenshot_page != NULL) + { + if (!bz_screenshot_page_is_closing (self->screenshot_page)) + { + bz_screenshot_page_close (self->screenshot_page); + return; + } + } + stack = adw_navigation_view_get_navigation_stack (self->navigation_view); n_pages = g_list_model_get_n_items (stack); @@ -705,6 +718,7 @@ bz_window_class_init (BzWindowClass *klass) gtk_widget_class_bind_template_child (widget_class, BzWindow, library_page); gtk_widget_class_bind_template_child (widget_class, BzWindow, main_view_stack); gtk_widget_class_bind_template_child (widget_class, BzWindow, main_stack); + gtk_widget_class_bind_template_child (widget_class, BzWindow, window_overlay); gtk_widget_class_bind_template_callback (widget_class, list_length); gtk_widget_class_bind_template_callback (widget_class, update_cb); gtk_widget_class_bind_template_callback (widget_class, page_toggled_cb); @@ -751,6 +765,9 @@ key_pressed (BzWindow *self, if (state & ~(GDK_NO_MODIFIER_MASK | GDK_SHIFT_MASK)) return FALSE; + if (self->screenshot_page != NULL) + return FALSE; + unichar = gdk_keyval_to_unicode (keyval); if (unichar == 0 || !g_unichar_isgraph (unichar)) return FALSE; @@ -1058,6 +1075,23 @@ bz_window_push_page (BzWindow *self, AdwNavigationPage *page) adw_navigation_view_push (self->navigation_view, page); } +void +bz_window_open_screenshot_page (BzWindow *self, + BzScreenshotPage *page) +{ + g_return_if_fail (BZ_IS_WINDOW (self)); + g_return_if_fail (BZ_IS_SCREENSHOT_PAGE (page)); + + if (self->screenshot_page != NULL) + return; + + self->screenshot_page = page; + g_signal_connect_swapped (page, "destroy", + G_CALLBACK (g_nullify_pointer), + &self->screenshot_page); + gtk_overlay_add_overlay (self->window_overlay, GTK_WIDGET (page)); +} + void bz_window_bulk_install (BzWindow *self, GListModel *groups) diff --git a/src/bz-window.h b/src/bz-window.h index 7f58d6b4..1164d03a 100644 --- a/src/bz-window.h +++ b/src/bz-window.h @@ -23,6 +23,7 @@ #include #include "bz-state-info.h" +#include "bz-screenshot-page.h" G_BEGIN_DECLS @@ -59,6 +60,10 @@ void bz_window_add_toast (BzWindow *self, AdwToast *toast); +void +bz_window_open_screenshot_page (BzWindow *self, + BzScreenshotPage *page); + void bz_window_bulk_install (BzWindow *self, GListModel *groups);