/*
 * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include "hdy-swipeable.h"

/**
 * HdySwipeable:
 *
 * An interface for swipeable widgets.
 *
 * The `HdySwipeable` interface is implemented by all swipeable widgets. They
 * can be synced using [class@SwipeGroup].
 *
 * See [class@SwipeTracker] for details about implementing it.
 *
 * Since: 1.0
 */

G_DEFINE_INTERFACE (HdySwipeable, hdy_swipeable, GTK_TYPE_WIDGET)

enum {
  SIGNAL_CHILD_SWITCHED,
  SIGNAL_LAST_SIGNAL,
};

static guint signals[SIGNAL_LAST_SIGNAL];

static void
hdy_swipeable_default_get_swipe_area (HdySwipeable           *self,
                                      HdyNavigationDirection  navigation_direction,
                                      gboolean                is_drag,
                                      GdkRectangle           *rect)
{
  rect->x = 0;
  rect->y = 0;
  rect->width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
  rect->height = gtk_widget_get_allocated_height (GTK_WIDGET (self));
}

static void
hdy_swipeable_default_init (HdySwipeableInterface *iface)
{
  iface->get_swipe_area = hdy_swipeable_default_get_swipe_area;

  /**
   * HdySwipeable::child-switched:
   * @self: a swipeable
   * @index: the index of the child to switch to
   * @duration: animation duration, in milliseconds
   *
   * Emitted when the widget's visible child is changed.
   *
   * @duration can be 0 if the child is switched without animation.
   *
   * This is used by [class@SwipeGroup], applications should not connect to it.
   *
   * Since: 1.0
   */
  signals[SIGNAL_CHILD_SWITCHED] =
    g_signal_new ("child-switched",
                  G_TYPE_FROM_INTERFACE (iface),
                  G_SIGNAL_RUN_FIRST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  2,
                  G_TYPE_UINT, G_TYPE_INT64);
}

/**
 * hdy_swipeable_switch_child:
 * @self: a swipeable
 * @index: the index of the child to switch to
 * @duration: animation duration, in milliseconds
 *
 * Switches to child with index @index.
 *
 * See [signal@Swipeable::child-switched].
 *
 * Since: 1.0
 */
void
hdy_swipeable_switch_child (HdySwipeable *self,
                            guint         index,
                            gint64        duration)
{
  HdySwipeableInterface *iface;

  g_return_if_fail (HDY_IS_SWIPEABLE (self));

  iface = HDY_SWIPEABLE_GET_IFACE (self);
  g_return_if_fail (iface->switch_child != NULL);

  iface->switch_child (self, index, duration);
}

/**
 * hdy_swipeable_emit_child_switched:
 * @self: a swipeable
 * @index: the index of the child to switch to
 * @duration: animation duration, in milliseconds
 *
 * Emits [signal@Swipeable::child-switched] signal.
 *
 * This should be called when the widget switches visible child widget.
 *
 * @duration can be 0 if the child is switched without animation.
 *
 * Since: 1.0
 */
void
hdy_swipeable_emit_child_switched (HdySwipeable *self,
                                   guint         index,
                                   gint64        duration)
{
  g_return_if_fail (HDY_IS_SWIPEABLE (self));

  g_signal_emit (self, signals[SIGNAL_CHILD_SWITCHED], 0, index, duration);
}

/**
 * hdy_swipeable_get_swipe_tracker:
 * @self: a swipeable
 *
 * Gets the [class@SwipeTracker] used by this swipeable widget.
 *
 * Returns: (transfer none): the swipe tracker
 *
 * Since: 1.0
 */
HdySwipeTracker *
hdy_swipeable_get_swipe_tracker (HdySwipeable *self)
{
  HdySwipeableInterface *iface;

  g_return_val_if_fail (HDY_IS_SWIPEABLE (self), NULL);

  iface = HDY_SWIPEABLE_GET_IFACE (self);
  g_return_val_if_fail (iface->get_swipe_tracker != NULL, NULL);

  return iface->get_swipe_tracker (self);
}

/**
 * hdy_swipeable_get_distance:
 * @self: a swipeable
 *
 * Gets the swipe distance of @self.
 *
 * This corresponds to how many pixels 1 unit represents.
 *
 * Returns: the swipe distance in pixels
 *
 * Since: 1.0
 */
gdouble
hdy_swipeable_get_distance (HdySwipeable *self)
{
  HdySwipeableInterface *iface;

  g_return_val_if_fail (HDY_IS_SWIPEABLE (self), 0);

  iface = HDY_SWIPEABLE_GET_IFACE (self);
  g_return_val_if_fail (iface->get_distance != NULL, 0);

  return iface->get_distance (self);
}

/**
 * hdy_swipeable_get_snap_points: (virtual get_snap_points)
 * @self: a swipeable
 * @n_snap_points: (out): location to return the number of the snap points
 *
 * Gets the snap points of @self.
 *
 * Each snap point represents a progress value that is considered acceptable to
 * end the swipe on.
 *
 * Returns: (array length=n_snap_points) (transfer full): the snap points
 *
 * Since: 1.0
 */
gdouble *
hdy_swipeable_get_snap_points (HdySwipeable *self,
                               gint         *n_snap_points)
{
  HdySwipeableInterface *iface;

  g_return_val_if_fail (HDY_IS_SWIPEABLE (self), NULL);

  iface = HDY_SWIPEABLE_GET_IFACE (self);
  g_return_val_if_fail (iface->get_snap_points != NULL, NULL);

  return iface->get_snap_points (self, n_snap_points);
}

/**
 * hdy_swipeable_get_progress:
 * @self: a swipeable
 *
 * Gets the current progress of @self.
 *
 * Returns: the current progress, unitless
 *
 * Since: 1.0
 */
gdouble
hdy_swipeable_get_progress (HdySwipeable *self)
{
  HdySwipeableInterface *iface;

  g_return_val_if_fail (HDY_IS_SWIPEABLE (self), 0);

  iface = HDY_SWIPEABLE_GET_IFACE (self);
  g_return_val_if_fail (iface->get_progress != NULL, 0);

  return iface->get_progress (self);
}

/**
 * hdy_swipeable_get_cancel_progress:
 * @self: a swipeable
 *
 * Gets the progress @self will snap back to after the gesture is canceled.
 *
 * Returns: the cancel progress, unitless
 *
 * Since: 1.0
 */
gdouble
hdy_swipeable_get_cancel_progress (HdySwipeable *self)
{
  HdySwipeableInterface *iface;

  g_return_val_if_fail (HDY_IS_SWIPEABLE (self), 0);

  iface = HDY_SWIPEABLE_GET_IFACE (self);
  g_return_val_if_fail (iface->get_cancel_progress != NULL, 0);

  return iface->get_cancel_progress (self);
}

/**
 * hdy_swipeable_get_swipe_area:
 * @self: a swipeable
 * @navigation_direction: the direction of the swipe
 * @is_drag: whether the swipe is caused by a dragging gesture
 * @rect: (out): a pointer to a rectangle to store the swipe area
 *
 * Gets the area @self can start a swipe from for the given direction and
 * gesture type.
 *
 * This can be used to restrict swipes to only be possible from a certain area,
 * for example, to only allow edge swipes, or to have a draggable element and
 * ignore swipes elsewhere.
 *
 * Swipe area is only considered for direct swipes (as in, not initiated by
 * [class@SwipeGroup]).
 *
 * If not implemented, the default implementation returns the allocation of
 * @self, allowing swipes from anywhere.
 *
 * Since: 1.0
 */
void
hdy_swipeable_get_swipe_area (HdySwipeable           *self,
                              HdyNavigationDirection  navigation_direction,
                              gboolean                is_drag,
                              GdkRectangle           *rect)
{
  HdySwipeableInterface *iface;

  g_return_if_fail (HDY_IS_SWIPEABLE (self));
  g_return_if_fail (rect != NULL);

  iface = HDY_SWIPEABLE_GET_IFACE (self);

  iface->get_swipe_area (self, navigation_direction, is_drag, rect);
}
