[gimp/gimp-2-10] app: add GimpToolFocus tool widget



commit 36b3745d104e14d1558d23e260b08cc259a8f88d
Author: Ell <ell_se yahoo com>
Date:   Thu May 14 23:55:43 2020 +0300

    app: add GimpToolFocus tool widget
    
    Add a new GimpToolFocus tool widget, which defines a focus region,
    consisting of an inner limit, an outer limit, and a transition
    midpoint between them.  The widget allows controlling the limits
    and the midpoint, and moving, scaling, and rotating the region.
    
    (cherry picked from commit 5e005a762c04b3692db39827089a25b88abc73d3)

 app/display/Makefile.am     |    2 +
 app/display/gimptoolfocus.c | 1141 +++++++++++++++++++++++++++++++++++++++++++
 app/display/gimptoolfocus.h |   58 +++
 po/POTFILES.in              |    1 +
 4 files changed, 1202 insertions(+)
---
diff --git a/app/display/Makefile.am b/app/display/Makefile.am
index 8db8c5de6c..dbee710234 100644
--- a/app/display/Makefile.am
+++ b/app/display/Makefile.am
@@ -182,6 +182,8 @@ libappdisplay_a_sources = \
        gimptoolgui.h                           \
        gimptoolcompass.c                       \
        gimptoolcompass.h                       \
+       gimptoolfocus.h                         \
+       gimptoolfocus.c                         \
        gimptoolgyroscope.c                     \
        gimptoolgyroscope.h                     \
        gimptoolhandlegrid.c                    \
diff --git a/app/display/gimptoolfocus.c b/app/display/gimptoolfocus.c
new file mode 100644
index 0000000000..e912654341
--- /dev/null
+++ b/app/display/gimptoolfocus.c
@@ -0,0 +1,1141 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolfocus.c
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvasgroup.h"
+#include "gimpcanvashandle.h"
+#include "gimpcanvaslimit.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolfocus.h"
+
+#include "gimp-intl.h"
+
+
+#define HANDLE_SIZE   12.0
+#define SNAP_DISTANCE 12.0
+
+
+enum
+{
+  PROP_0,
+  PROP_TYPE,
+  PROP_X,
+  PROP_Y,
+  PROP_RADIUS,
+  PROP_ASPECT_RATIO,
+  PROP_ANGLE,
+  PROP_INNER_LIMIT,
+  PROP_MIDPOINT
+};
+
+enum
+{
+  LIMIT_OUTER,
+  LIMIT_INNER,
+  LIMIT_MIDDLE,
+
+  N_LIMITS
+};
+
+
+typedef enum
+{
+  HOVER_NONE,
+  HOVER_LIMIT,
+  HOVER_HANDLE,
+  HOVER_MOVE,
+  HOVER_ROTATE
+} Hover;
+
+
+typedef struct
+{
+  GimpCanvasItem *item;
+
+  GtkOrientation  orientation;
+  GimpVector2     dir;
+} GimpToolFocusHandle;
+
+typedef struct
+{
+  GimpCanvasGroup     *group;
+  GimpCanvasItem      *item;
+
+  gint                 n_handles;
+  GimpToolFocusHandle  handles[4];
+} GimpToolFocusLimit;
+
+struct _GimpToolFocusPrivate
+{
+  GimpLimitType       type;
+
+  gdouble             x;
+  gdouble             y;
+  gdouble             radius;
+  gdouble             aspect_ratio;
+  gdouble             angle;
+
+  gdouble             inner_limit;
+  gdouble             midpoint;
+
+  GimpToolFocusLimit  limits[N_LIMITS];
+
+  Hover               hover;
+  gint                hover_limit;
+  gint                hover_handle;
+  GimpCanvasItem     *hover_item;
+
+  GimpCanvasItem     *last_hover_item;
+
+  gdouble             saved_x;
+  gdouble             saved_y;
+  gdouble             saved_radius;
+  gdouble             saved_aspect_ratio;
+  gdouble             saved_angle;
+
+  gdouble             saved_inner_limit;
+  gdouble             saved_midpoint;
+
+  gdouble             orig_x;
+  gdouble             orig_y;
+};
+
+
+/*  local function prototypes  */
+
+static void       gimp_tool_focus_constructed      (GObject               *object);
+static void       gimp_tool_focus_set_property     (GObject               *object,
+                                                    guint                  property_id,
+                                                    const GValue          *value,
+                                                    GParamSpec            *pspec);
+static void       gimp_tool_focus_get_property     (GObject               *object,
+                                                    guint                  property_id,
+                                                    GValue                *value,
+                                                    GParamSpec            *pspec);
+
+static void       gimp_tool_focus_changed          (GimpToolWidget        *widget);
+static gint       gimp_tool_focus_button_press     (GimpToolWidget        *widget,
+                                                    const GimpCoords      *coords,
+                                                    guint32                time,
+                                                    GdkModifierType        state,
+                                                    GimpButtonPressType    press_type);
+static void       gimp_tool_focus_button_release   (GimpToolWidget        *widget,
+                                                    const GimpCoords      *coords,
+                                                    guint32                time,
+                                                    GdkModifierType        state,
+                                                    GimpButtonReleaseType  release_type);
+static void       gimp_tool_focus_motion           (GimpToolWidget        *widget,
+                                                    const GimpCoords      *coords,
+                                                    guint32                time,
+                                                    GdkModifierType        state);
+static GimpHit    gimp_tool_focus_hit              (GimpToolWidget        *widget,
+                                                    const GimpCoords      *coords,
+                                                    GdkModifierType        state,
+                                                    gboolean               proximity);
+static void       gimp_tool_focus_hover            (GimpToolWidget        *widget,
+                                                    const GimpCoords      *coords,
+                                                    GdkModifierType        state,
+                                                    gboolean               proximity);
+static void       gimp_tool_focus_leave_notify     (GimpToolWidget        *widget);
+static void       gimp_tool_focus_motion_modifier  (GimpToolWidget        *widget,
+                                                    GdkModifierType        key,
+                                                    gboolean               press,
+                                                    GdkModifierType        state);
+static void       gimp_tool_focus_hover_modifier   (GimpToolWidget        *widget,
+                                                    GdkModifierType        key,
+                                                    gboolean               press,
+                                                    GdkModifierType        state);
+static gboolean   gimp_tool_focus_get_cursor       (GimpToolWidget        *widget,
+                                                    const GimpCoords      *coords,
+                                                    GdkModifierType        state,
+                                                    GimpCursorType        *cursor,
+                                                    GimpToolCursorType    *tool_cursor,
+                                                    GimpCursorModifier    *modifier);
+
+static void       gimp_tool_focus_update_hover     (GimpToolFocus         *focus,
+                                                    const GimpCoords      *coords,
+                                                    gboolean               proximity);
+
+static void       gimp_tool_focus_update_highlight (GimpToolFocus         *focus);
+static void       gimp_tool_focus_update_status    (GimpToolFocus         *focus,
+                                                    GdkModifierType        state);
+
+static void       gimp_tool_focus_save             (GimpToolFocus         *focus);
+static void       gimp_tool_focus_restore          (GimpToolFocus         *focus);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolFocus, gimp_tool_focus,
+                            GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_focus_parent_class
+
+
+/*  private functions  */
+
+static void
+gimp_tool_focus_class_init (GimpToolFocusClass *klass)
+{
+  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+  GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+  object_class->constructed     = gimp_tool_focus_constructed;
+  object_class->set_property    = gimp_tool_focus_set_property;
+  object_class->get_property    = gimp_tool_focus_get_property;
+
+  widget_class->changed         = gimp_tool_focus_changed;
+  widget_class->button_press    = gimp_tool_focus_button_press;
+  widget_class->button_release  = gimp_tool_focus_button_release;
+  widget_class->motion          = gimp_tool_focus_motion;
+  widget_class->hit             = gimp_tool_focus_hit;
+  widget_class->hover           = gimp_tool_focus_hover;
+  widget_class->leave_notify    = gimp_tool_focus_leave_notify;
+  widget_class->motion_modifier = gimp_tool_focus_motion_modifier;
+  widget_class->hover_modifier  = gimp_tool_focus_hover_modifier;
+  widget_class->get_cursor      = gimp_tool_focus_get_cursor;
+
+  g_object_class_install_property (object_class, PROP_TYPE,
+                                   g_param_spec_enum ("type", NULL, NULL,
+                                                      GIMP_TYPE_LIMIT_TYPE,
+                                                      GIMP_LIMIT_CIRCLE,
+                                                      GIMP_PARAM_READWRITE |
+                                                      G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_X,
+                                   g_param_spec_double ("x", NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        +G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_Y,
+                                   g_param_spec_double ("y", NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        +G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_RADIUS,
+                                   g_param_spec_double ("radius", NULL, NULL,
+                                                        0.0,
+                                                        +G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_ASPECT_RATIO,
+                                   g_param_spec_double ("aspect-ratio", NULL, NULL,
+                                                        -1.0,
+                                                        +1.0,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_ANGLE,
+                                   g_param_spec_double ("angle", NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        +G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_INNER_LIMIT,
+                                   g_param_spec_double ("inner-limit", NULL, NULL,
+                                                        0.0,
+                                                        1.0,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_MIDPOINT,
+                                   g_param_spec_double ("midpoint", NULL, NULL,
+                                                        0.0,
+                                                        1.0,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_focus_init (GimpToolFocus *focus)
+{
+  GimpToolFocusPrivate *priv;
+
+  priv = gimp_tool_focus_get_instance_private (focus);
+
+  focus->priv = priv;
+
+  priv->hover = HOVER_NONE;
+}
+
+static void
+gimp_tool_focus_constructed (GObject *object)
+{
+  GimpToolFocus        *focus  = GIMP_TOOL_FOCUS (object);
+  GimpToolWidget       *widget = GIMP_TOOL_WIDGET (object);
+  GimpToolFocusPrivate *priv   = focus->priv;
+  gint                  i;
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+
+  for (i = N_LIMITS - 1; i >= 0; i--)
+    {
+      priv->limits[i].group = gimp_tool_widget_add_group (widget);
+
+      gimp_tool_widget_push_group (widget, priv->limits[i].group);
+
+      priv->limits[i].item = gimp_tool_widget_add_limit (
+        widget,
+        GIMP_LIMIT_CIRCLE,
+        0.0, 0.0,
+        0.0,
+        1.0,
+        0.0,
+        /* dashed = */ i == LIMIT_MIDDLE);
+
+      if (i == LIMIT_OUTER || i == LIMIT_INNER)
+        {
+          gint j;
+
+          priv->limits[i].n_handles = 4;
+
+          for (j = priv->limits[i].n_handles - 1; j >= 0; j--)
+            {
+              priv->limits[i].handles[j].item = gimp_tool_widget_add_handle (
+                widget,
+                GIMP_HANDLE_FILLED_CIRCLE,
+                0.0, 0.0, HANDLE_SIZE, HANDLE_SIZE,
+                GIMP_HANDLE_ANCHOR_CENTER);
+
+              priv->limits[i].handles[j].orientation =
+                j % 2 == 0 ? GTK_ORIENTATION_HORIZONTAL :
+                             GTK_ORIENTATION_VERTICAL;
+
+              priv->limits[i].handles[j].dir.x = cos (j * G_PI / 2.0);
+              priv->limits[i].handles[j].dir.y = sin (j * G_PI / 2.0);
+            }
+        }
+
+      gimp_tool_widget_pop_group (widget);
+    }
+
+  gimp_tool_focus_changed (widget);
+}
+
+static void
+gimp_tool_focus_set_property (GObject      *object,
+                              guint         property_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (object);
+  GimpToolFocusPrivate *priv  = focus->priv;
+
+  switch (property_id)
+    {
+    case PROP_TYPE:
+      priv->type = g_value_get_enum (value);
+      break;
+
+    case PROP_X:
+      priv->x = g_value_get_double (value);
+      break;
+
+    case PROP_Y:
+      priv->y = g_value_get_double (value);
+      break;
+
+    case PROP_RADIUS:
+      priv->radius = g_value_get_double (value);
+      break;
+
+    case PROP_ASPECT_RATIO:
+      priv->aspect_ratio = g_value_get_double (value);
+      break;
+
+    case PROP_ANGLE:
+      priv->angle = g_value_get_double (value);
+      break;
+
+    case PROP_INNER_LIMIT:
+      priv->inner_limit = g_value_get_double (value);
+      break;
+
+    case PROP_MIDPOINT:
+      priv->midpoint = g_value_get_double (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_tool_focus_get_property (GObject    *object,
+                              guint       property_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (object);
+  GimpToolFocusPrivate *priv  = focus->priv;
+
+  switch (property_id)
+    {
+    case PROP_TYPE:
+      g_value_set_enum (value, priv->type);
+      break;
+
+    case PROP_X:
+      g_value_set_double (value, priv->x);
+      break;
+
+    case PROP_Y:
+      g_value_set_double (value, priv->y);
+      break;
+
+    case PROP_RADIUS:
+      g_value_set_double (value, priv->radius);
+      break;
+
+    case PROP_ASPECT_RATIO:
+      g_value_set_double (value, priv->aspect_ratio);
+      break;
+
+    case PROP_ANGLE:
+      g_value_set_double (value, priv->angle);
+      break;
+
+    case PROP_INNER_LIMIT:
+      g_value_set_double (value, priv->inner_limit);
+      break;
+
+    case PROP_MIDPOINT:
+      g_value_set_double (value, priv->midpoint);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_tool_focus_changed (GimpToolWidget *widget)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (widget);
+  GimpToolFocusPrivate *priv  = focus->priv;
+  gint                  i;
+
+  for (i = 0; i < N_LIMITS; i++)
+    {
+      gimp_canvas_item_begin_change (priv->limits[i].item);
+
+      g_object_set (priv->limits[i].item,
+                    "type",         priv->type,
+                    "x",            priv->x,
+                    "y",            priv->y,
+                    "aspect-ratio", priv->aspect_ratio,
+                    "angle",        priv->angle,
+                    NULL);
+    }
+
+  g_object_set (priv->limits[LIMIT_OUTER].item,
+                "radius", priv->radius,
+                NULL);
+
+  g_object_set (priv->limits[LIMIT_INNER].item,
+                "radius", priv->radius * priv->inner_limit,
+                NULL);
+
+  g_object_set (priv->limits[LIMIT_MIDDLE].item,
+                "radius", priv->radius * (priv->inner_limit         +
+                                          (1.0 - priv->inner_limit) *
+                                          priv->midpoint),
+                NULL);
+
+  for (i = 0; i < N_LIMITS; i++)
+    {
+      gdouble rx, ry;
+      gdouble max_r = 0.0;
+      gint    j;
+
+      gimp_canvas_limit_get_radii (GIMP_CANVAS_LIMIT (priv->limits[i].item),
+                                   &rx, &ry);
+
+      for (j = 0; j < priv->limits[i].n_handles; j++)
+        {
+          GimpVector2 p = priv->limits[i].handles[j].dir;
+          gdouble     r;
+
+          p.x *= rx;
+          p.y *= ry;
+
+          gimp_vector2_rotate (&p, -priv->angle);
+
+          p.x += priv->x;
+          p.y += priv->y;
+
+          gimp_canvas_handle_set_position (priv->limits[i].handles[j].item,
+                                           p.x, p.y);
+
+          r = gimp_canvas_item_transform_distance (
+            priv->limits[i].handles[j].item,
+            priv->x, priv->y,
+            p.x,     p.y);
+
+          max_r = MAX (max_r, r);
+        }
+
+      for (j = 0; j < priv->limits[i].n_handles; j++)
+        {
+          gimp_canvas_item_set_visible (priv->limits[i].handles[j].item,
+                                        priv->type != GIMP_LIMIT_HORIZONTAL &&
+                                        priv->type != GIMP_LIMIT_VERTICAL   &&
+                                        max_r >= 1.5 * HANDLE_SIZE);
+        }
+
+      gimp_canvas_item_end_change (priv->limits[i].item);
+    }
+}
+
+static gint
+gimp_tool_focus_button_press (GimpToolWidget      *widget,
+                              const GimpCoords    *coords,
+                              guint32              time,
+                              GdkModifierType      state,
+                              GimpButtonPressType  press_type)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (widget);
+  GimpToolFocusPrivate *priv  = focus->priv;
+
+  if (press_type == GIMP_BUTTON_PRESS_NORMAL)
+    {
+      gimp_tool_focus_save (focus);
+
+      priv->orig_x = coords->x;
+      priv->orig_y = coords->y;
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+gimp_tool_focus_button_release (GimpToolWidget        *widget,
+                                const GimpCoords      *coords,
+                                guint32                time,
+                                GdkModifierType        state,
+                                GimpButtonReleaseType  release_type)
+{
+  GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+  if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+    gimp_tool_focus_restore (focus);
+}
+
+static void
+gimp_tool_focus_motion (GimpToolWidget   *widget,
+                        const GimpCoords *coords,
+                        guint32           time,
+                        GdkModifierType   state)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (widget);
+  GimpToolFocusPrivate *priv  = focus->priv;
+  GimpDisplayShell     *shell = gimp_tool_widget_get_shell (widget);
+  gboolean              extend;
+  gboolean              constrain;
+
+  extend    = state & gimp_get_extend_selection_mask ();
+  constrain = state & gimp_get_constrain_behavior_mask ();
+
+  switch (priv->hover)
+    {
+    case HOVER_NONE:
+      break;
+
+    case HOVER_LIMIT:
+      {
+        GimpCanvasItem *limit = priv->limits[priv->hover_limit].item;
+        gdouble         radius;
+        gdouble         outer_radius;
+        gdouble         inner_radius;
+        gdouble         x,  y;
+        gdouble         cx, cy;
+
+        x = coords->x;
+        y = coords->y;
+
+        gimp_canvas_limit_center_point (GIMP_CANVAS_LIMIT (limit),
+                                        x,   y,
+                                        &cx, &cy);
+
+        if (gimp_canvas_item_transform_distance (limit,
+                                                 x,  y,
+                                                 cx, cy) <= SNAP_DISTANCE)
+          {
+            x = cx;
+            y = cy;
+          }
+
+        radius = gimp_canvas_limit_boundary_radius (GIMP_CANVAS_LIMIT (limit),
+                                                    x, y);
+
+        outer_radius = priv->radius;
+        inner_radius = priv->radius * priv->inner_limit;
+
+        switch (priv->hover_limit)
+          {
+          case LIMIT_OUTER:
+            {
+              outer_radius = radius;
+
+              if (extend)
+                inner_radius = priv->inner_limit * radius;
+              else
+                outer_radius = MAX (outer_radius, inner_radius);
+            }
+            break;
+
+          case LIMIT_INNER:
+            {
+              inner_radius = radius;
+
+              if (extend)
+                outer_radius = inner_radius / priv->inner_limit;
+              else
+                inner_radius = MIN (inner_radius, outer_radius);
+            }
+            break;
+
+          case LIMIT_MIDDLE:
+            {
+              if (extend)
+                {
+                  outer_radius = radius / (priv->inner_limit         +
+                                           (1.0 - priv->inner_limit) *
+                                           priv->midpoint);
+                  inner_radius = priv->inner_limit * outer_radius;
+                }
+              else
+                {
+                  radius = CLAMP (radius, inner_radius, outer_radius);
+                }
+
+              g_object_set (focus,
+                            "midpoint",    (radius       - inner_radius) /
+                                           (outer_radius - inner_radius),
+                            NULL);
+            }
+            break;
+          }
+
+        g_object_set (focus,
+                      "radius",      outer_radius,
+                      "inner-limit", inner_radius / outer_radius,
+                      NULL);
+      }
+      break;
+
+    case HOVER_HANDLE:
+      {
+        GimpToolFocusHandle *handle;
+        GimpVector2          e;
+        GimpVector2          s;
+        GimpVector2          p;
+        gdouble              rx, ry;
+        gdouble              r;
+
+        handle = &priv->limits[priv->hover_limit].handles[priv->hover_handle];
+
+        e = handle->dir;
+
+        gimp_vector2_rotate (&e, -priv->angle);
+
+        s = e;
+
+        gimp_canvas_limit_get_radii (
+          GIMP_CANVAS_LIMIT (priv->limits[priv->hover_limit].item),
+          &rx, &ry);
+
+        if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+          gimp_vector2_mul (&s, ry);
+        else
+          gimp_vector2_mul (&s, rx);
+
+        p.x = coords->x - priv->x;
+        p.y = coords->y - priv->y;
+
+        r = gimp_vector2_inner_product (&p, &e);
+        r = MAX (r, 0.0);
+
+        p = e;
+
+        gimp_vector2_mul (&p, r);
+
+        if (gimp_canvas_item_transform_distance (
+              priv->limits[priv->hover_limit].item,
+              s.x, s.y,
+              p.x, p.y) <= SNAP_DISTANCE * 0.75)
+          {
+            if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+              r = ry;
+            else
+              r = rx;
+          }
+
+        if (extend)
+          {
+            if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+              ry *= r / rx;
+            else
+              rx *= r / ry;
+          }
+
+        if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
+          rx = r;
+        else
+          ry = r;
+
+        r = MAX (rx, ry);
+
+        if (priv->hover_limit == LIMIT_INNER)
+          r /= priv->inner_limit;
+
+        g_object_set (focus,
+                      "radius",       r,
+                      "aspect-ratio", rx >= ry ? 1.0 - ry / rx : rx / ry - 1.0,
+                      NULL);
+      }
+      break;
+
+    case HOVER_MOVE:
+      g_object_set (focus,
+                    "x", priv->saved_x + (coords->x - priv->orig_x),
+                    "y", priv->saved_y + (coords->y - priv->orig_y),
+                    NULL);
+      break;
+
+    case HOVER_ROTATE:
+      {
+        gdouble angle;
+        gdouble orig_angle;
+
+        angle      = atan2 (coords->y    - priv->y, coords->x    - priv->x);
+        orig_angle = atan2 (priv->orig_y - priv->y, priv->orig_x - priv->x);
+
+        angle = priv->saved_angle + (angle - orig_angle);
+
+        if (constrain)
+          angle = gimp_display_shell_constrain_angle (shell, angle, 12);
+
+        g_object_set (focus,
+                      "angle", angle,
+                      NULL);
+      }
+      break;
+    }
+}
+
+static GimpHit
+gimp_tool_focus_hit (GimpToolWidget   *widget,
+                     const GimpCoords *coords,
+                     GdkModifierType   state,
+                     gboolean          proximity)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (widget);
+  GimpToolFocusPrivate *priv  = focus->priv;
+
+  gimp_tool_focus_update_hover (focus, coords, proximity);
+
+  switch (priv->hover)
+    {
+    case HOVER_NONE:
+      return GIMP_HIT_NONE;
+
+    case HOVER_LIMIT:
+    case HOVER_HANDLE:
+      return GIMP_HIT_DIRECT;
+
+    case HOVER_MOVE:
+    case HOVER_ROTATE:
+      return GIMP_HIT_INDIRECT;
+    }
+
+  g_return_val_if_reached (GIMP_HIT_NONE);
+}
+
+static void
+gimp_tool_focus_hover (GimpToolWidget   *widget,
+                       const GimpCoords *coords,
+                       GdkModifierType   state,
+                       gboolean          proximity)
+{
+  GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+  gimp_tool_focus_update_hover (focus, coords, proximity);
+
+  gimp_tool_focus_update_highlight (focus);
+  gimp_tool_focus_update_status    (focus, state);
+}
+
+static void
+gimp_tool_focus_leave_notify (GimpToolWidget *widget)
+{
+  GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+  gimp_tool_focus_update_hover (focus, NULL, FALSE);
+
+  gimp_tool_focus_update_highlight (focus);
+
+  GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static void
+gimp_tool_focus_motion_modifier (GimpToolWidget  *widget,
+                                 GdkModifierType  key,
+                                 gboolean         press,
+                                 GdkModifierType  state)
+{
+  GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+  gimp_tool_focus_update_status (focus, state);
+}
+
+static void
+gimp_tool_focus_hover_modifier (GimpToolWidget  *widget,
+                                GdkModifierType  key,
+                                gboolean         press,
+                                GdkModifierType  state)
+{
+  GimpToolFocus *focus = GIMP_TOOL_FOCUS (widget);
+
+  gimp_tool_focus_update_status (focus, state);
+}
+
+static gboolean
+gimp_tool_focus_get_cursor (GimpToolWidget     *widget,
+                            const GimpCoords   *coords,
+                            GdkModifierType     state,
+                            GimpCursorType     *cursor,
+                            GimpToolCursorType *tool_cursor,
+                            GimpCursorModifier *modifier)
+{
+  GimpToolFocus        *focus = GIMP_TOOL_FOCUS (widget);
+  GimpToolFocusPrivate *priv  = focus->priv;
+
+  switch (priv->hover)
+    {
+    case HOVER_NONE:
+      return FALSE;
+
+    case HOVER_LIMIT:
+    case HOVER_HANDLE:
+      *modifier = GIMP_CURSOR_MODIFIER_RESIZE;
+      return TRUE;
+
+    case HOVER_MOVE:
+      *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+      return TRUE;
+
+    case HOVER_ROTATE:
+      *modifier = GIMP_CURSOR_MODIFIER_ROTATE;
+      return TRUE;
+    }
+
+  g_return_val_if_reached (FALSE);
+}
+
+static void
+gimp_tool_focus_update_hover (GimpToolFocus    *focus,
+                              const GimpCoords *coords,
+                              gboolean          proximity)
+{
+  GimpToolFocusPrivate *priv            = focus->priv;
+  gdouble               min_handle_dist = HANDLE_SIZE;
+  gint                  i;
+
+  priv->hover      = HOVER_NONE;
+  priv->hover_item = NULL;
+
+  if (! proximity)
+    return;
+
+  for (i = 0; i < N_LIMITS; i++)
+    {
+      gint j;
+
+      for (j = 0; j < priv->limits[i].n_handles; j++)
+        {
+          GimpCanvasItem *handle = priv->limits[i].handles[j].item;
+
+          if (gimp_canvas_item_get_visible (handle))
+            {
+              gdouble x, y;
+              gdouble dist;
+
+              g_object_get (handle,
+                            "x", &x,
+                            "y", &y,
+                            NULL);
+
+              dist = gimp_canvas_item_transform_distance (handle,
+                                                          x,         y,
+                                                          coords->x, coords->y);
+
+              if (dist < min_handle_dist)
+                {
+                  min_handle_dist = dist;
+
+                  priv->hover        = HOVER_HANDLE;
+                  priv->hover_limit  = i;
+                  priv->hover_handle = j;
+                  priv->hover_item   = handle;
+                }
+            }
+        }
+    }
+
+  if (priv->hover != HOVER_NONE)
+    return;
+
+  if (  gimp_canvas_limit_is_inside (
+          GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
+          coords->x, coords->y) &&
+      ! gimp_canvas_limit_is_inside (
+          GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
+          coords->x, coords->y))
+    {
+      if (gimp_canvas_item_hit (priv->limits[LIMIT_MIDDLE].item,
+                                coords->x, coords->y))
+        {
+          priv->hover       = HOVER_LIMIT;
+          priv->hover_limit = LIMIT_MIDDLE;
+          priv->hover_item  = priv->limits[LIMIT_MIDDLE].item;
+
+          return;
+        }
+    }
+
+  if (! gimp_canvas_limit_is_inside (
+          GIMP_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
+          coords->x, coords->y))
+    {
+      if (gimp_canvas_item_hit (priv->limits[LIMIT_OUTER].item,
+                                coords->x, coords->y))
+        {
+          priv->hover       = HOVER_LIMIT;
+          priv->hover_limit = LIMIT_OUTER;
+          priv->hover_item  = priv->limits[LIMIT_OUTER].item;
+
+          return;
+        }
+    }
+
+  if (gimp_canvas_item_hit (priv->limits[LIMIT_INNER].item,
+                            coords->x, coords->y))
+    {
+      priv->hover       = HOVER_LIMIT;
+      priv->hover_limit = LIMIT_INNER;
+      priv->hover_item  = priv->limits[LIMIT_INNER].item;
+
+      return;
+    }
+
+  if (gimp_canvas_limit_is_inside (
+        GIMP_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
+        coords->x, coords->y))
+    {
+      priv->hover = HOVER_MOVE;
+    }
+  else
+    {
+      priv->hover = HOVER_ROTATE;
+    }
+}
+
+static void
+gimp_tool_focus_update_highlight (GimpToolFocus *focus)
+{
+  GimpToolWidget       *widget = GIMP_TOOL_WIDGET (focus);
+  GimpToolFocusPrivate *priv   = focus->priv;
+  gint                  i;
+
+  if (priv->hover_item == priv->last_hover_item)
+    return;
+
+  if (priv->last_hover_item)
+    gimp_canvas_item_set_highlight (priv->last_hover_item, FALSE);
+
+  #define RAISE_ITEM(item)                           \
+    G_STMT_START                                     \
+      {                                              \
+        g_object_ref (item);                         \
+                                                     \
+        gimp_tool_widget_remove_item (widget, item); \
+        gimp_tool_widget_add_item    (widget, item); \
+                                                     \
+        g_object_unref (item);                       \
+      }                                              \
+    G_STMT_END
+
+  for (i = N_LIMITS - 1; i >= 0; i--)
+    RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[i].group));
+
+  if (priv->hover_item)
+    {
+      gimp_canvas_item_set_highlight (priv->hover_item, TRUE);
+
+      RAISE_ITEM (GIMP_CANVAS_ITEM (priv->limits[priv->hover_limit].group));
+    }
+
+  #undef RAISE_ITEM
+
+  priv->last_hover_item = priv->hover_item;
+}
+
+static void
+gimp_tool_focus_update_status (GimpToolFocus   *focus,
+                               GdkModifierType  state)
+{
+  GimpToolFocusPrivate *priv                    = focus->priv;
+  GdkModifierType       state_mask              = 0;
+  const gchar          *message                 = NULL;
+  const gchar          *extend_selection_format = NULL;
+  const gchar          *toggle_behavior_format  = NULL;
+  gchar                *status;
+
+  switch (priv->hover)
+    {
+    case HOVER_NONE:
+      break;
+
+    case HOVER_LIMIT:
+      if (! (state & gimp_get_extend_selection_mask ()))
+        {
+          if (priv->hover_limit == LIMIT_MIDDLE)
+            message                = _("Click-Drag to change the midpoint");
+          else
+            message                = _("Click-Drag to resize the limit");
+
+          extend_selection_format  = _("%s to resize the focus");
+          state_mask              |= gimp_get_extend_selection_mask ();
+        }
+      else
+        {
+          message                  = _("Click-Drag to resize the focus");
+        }
+      break;
+
+    case HOVER_HANDLE:
+      if (! (state & gimp_get_extend_selection_mask ()))
+        {
+          message                  = _("Click-Drag to change the aspect ratio");
+          extend_selection_format  = _("%s to resize the focus");
+          state_mask              |= gimp_get_extend_selection_mask ();
+        }
+      else
+        {
+          message                  = _("Click-Drag to resize the focus");
+        }
+      break;
+
+    case HOVER_MOVE:
+      message                      = _("Click-Drag to move the focus");
+      break;
+
+    case HOVER_ROTATE:
+      message                      = _("Click-Drag to rotate the focus");
+      toggle_behavior_format       = _("%s for constrained angles");
+      state_mask                  |= gimp_get_constrain_behavior_mask ();
+      break;
+    }
+
+  status = gimp_suggest_modifiers (message,
+                                   ~state & state_mask,
+                                   extend_selection_format,
+                                   toggle_behavior_format,
+                                   NULL);
+
+  gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (focus), status);
+
+  g_free (status);
+}
+
+static void
+gimp_tool_focus_save (GimpToolFocus *focus)
+{
+  GimpToolFocusPrivate *priv = focus->priv;
+
+  priv->saved_x            = priv->x;
+  priv->saved_y            = priv->y;
+  priv->saved_radius       = priv->radius;
+  priv->saved_aspect_ratio = priv->aspect_ratio;
+  priv->saved_angle        = priv->angle;
+
+  priv->saved_inner_limit  = priv->inner_limit;
+  priv->saved_midpoint     = priv->midpoint;
+}
+
+static void
+gimp_tool_focus_restore (GimpToolFocus *focus)
+{
+  GimpToolFocusPrivate *priv = focus->priv;
+
+  g_object_set (focus,
+                "x",            priv->saved_x,
+                "y",            priv->saved_y,
+                "radius",       priv->saved_radius,
+                "aspect-ratio", priv->saved_aspect_ratio,
+                "angle",        priv->saved_angle,
+
+                "inner_limit",  priv->saved_inner_limit,
+                "midpoint",     priv->saved_midpoint,
+
+                NULL);
+}
+
+
+/*  public functions  */
+
+GimpToolWidget *
+gimp_tool_focus_new (GimpDisplayShell *shell)
+{
+  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+  return g_object_new (GIMP_TYPE_TOOL_FOCUS,
+                       "shell", shell,
+                       NULL);
+}
diff --git a/app/display/gimptoolfocus.h b/app/display/gimptoolfocus.h
new file mode 100644
index 0000000000..e2fc5bdbfe
--- /dev/null
+++ b/app/display/gimptoolfocus.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolfocus.h
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_FOCUS_H__
+#define __GIMP_TOOL_FOCUS_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_FOCUS            (gimp_tool_focus_get_type ())
+#define GIMP_TOOL_FOCUS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_FOCUS, 
GimpToolFocus))
+#define GIMP_TOOL_FOCUS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_FOCUS, 
GimpToolFocusClass))
+#define GIMP_IS_TOOL_FOCUS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_FOCUS))
+#define GIMP_IS_TOOL_FOCUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_FOCUS))
+#define GIMP_TOOL_FOCUS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_FOCUS, 
GimpToolFocusClass))
+
+
+typedef struct _GimpToolFocus        GimpToolFocus;
+typedef struct _GimpToolFocusPrivate GimpToolFocusPrivate;
+typedef struct _GimpToolFocusClass   GimpToolFocusClass;
+
+struct _GimpToolFocus
+{
+  GimpToolWidget        parent_instance;
+
+  GimpToolFocusPrivate *priv;
+};
+
+struct _GimpToolFocusClass
+{
+  GimpToolWidgetClass  parent_class;
+};
+
+
+GType            gimp_tool_focus_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_focus_new      (GimpDisplayShell *shell);
+
+
+#endif /* __GIMP_TOOL_FOCUS_H__ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e2c4b73205..c910ac7b7a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -262,6 +262,7 @@ app/display/gimpdisplayshell-utils.c
 app/display/gimpnavigationeditor.c
 app/display/gimpstatusbar.c
 app/display/gimptoolcompass.c
+app/display/gimptoolfocus.c
 app/display/gimptoolgyroscope.c
 app/display/gimptoolhandlegrid.c
 app/display/gimptoolline.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]