1214 lines
38 KiB
C
1214 lines
38 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikatoolfocus.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 "libpikabase/pikabase.h"
|
|
#include "libpikamath/pikamath.h"
|
|
|
|
#include "display-types.h"
|
|
|
|
#include "widgets/pikawidgets-utils.h"
|
|
|
|
#include "pikacanvasgroup.h"
|
|
#include "pikacanvashandle.h"
|
|
#include "pikacanvaslimit.h"
|
|
#include "pikadisplayshell.h"
|
|
#include "pikadisplayshell-transform.h"
|
|
#include "pikadisplayshell-utils.h"
|
|
#include "pikatoolfocus.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
#define HANDLE_SIZE 12.0
|
|
#define SNAP_DISTANCE 12.0
|
|
|
|
#define EPSILON 1e-6
|
|
|
|
|
|
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
|
|
{
|
|
PikaCanvasItem *item;
|
|
|
|
GtkOrientation orientation;
|
|
PikaVector2 dir;
|
|
} PikaToolFocusHandle;
|
|
|
|
typedef struct
|
|
{
|
|
PikaCanvasGroup *group;
|
|
PikaCanvasItem *item;
|
|
|
|
gint n_handles;
|
|
PikaToolFocusHandle handles[4];
|
|
} PikaToolFocusLimit;
|
|
|
|
struct _PikaToolFocusPrivate
|
|
{
|
|
PikaLimitType type;
|
|
|
|
gdouble x;
|
|
gdouble y;
|
|
gdouble radius;
|
|
gdouble aspect_ratio;
|
|
gdouble angle;
|
|
|
|
gdouble inner_limit;
|
|
gdouble midpoint;
|
|
|
|
PikaToolFocusLimit limits[N_LIMITS];
|
|
|
|
Hover hover;
|
|
gint hover_limit;
|
|
gint hover_handle;
|
|
PikaCanvasItem *hover_item;
|
|
|
|
PikaCanvasItem *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 pika_tool_focus_constructed (GObject *object);
|
|
static void pika_tool_focus_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_tool_focus_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void pika_tool_focus_changed (PikaToolWidget *widget);
|
|
static gint pika_tool_focus_button_press (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type);
|
|
static void pika_tool_focus_button_release (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type);
|
|
static void pika_tool_focus_motion (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state);
|
|
static PikaHit pika_tool_focus_hit (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity);
|
|
static void pika_tool_focus_hover (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity);
|
|
static void pika_tool_focus_leave_notify (PikaToolWidget *widget);
|
|
static void pika_tool_focus_motion_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state);
|
|
static void pika_tool_focus_hover_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state);
|
|
static gboolean pika_tool_focus_get_cursor (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaCursorType *cursor,
|
|
PikaToolCursorType *tool_cursor,
|
|
PikaCursorModifier *modifier);
|
|
|
|
static void pika_tool_focus_update_hover (PikaToolFocus *focus,
|
|
const PikaCoords *coords,
|
|
gboolean proximity);
|
|
|
|
static void pika_tool_focus_update_highlight (PikaToolFocus *focus);
|
|
static void pika_tool_focus_update_status (PikaToolFocus *focus,
|
|
GdkModifierType state);
|
|
|
|
static void pika_tool_focus_save (PikaToolFocus *focus);
|
|
static void pika_tool_focus_restore (PikaToolFocus *focus);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaToolFocus, pika_tool_focus,
|
|
PIKA_TYPE_TOOL_WIDGET)
|
|
|
|
#define parent_class pika_tool_focus_parent_class
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
pika_tool_focus_class_init (PikaToolFocusClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);
|
|
|
|
object_class->constructed = pika_tool_focus_constructed;
|
|
object_class->set_property = pika_tool_focus_set_property;
|
|
object_class->get_property = pika_tool_focus_get_property;
|
|
|
|
widget_class->changed = pika_tool_focus_changed;
|
|
widget_class->button_press = pika_tool_focus_button_press;
|
|
widget_class->button_release = pika_tool_focus_button_release;
|
|
widget_class->motion = pika_tool_focus_motion;
|
|
widget_class->hit = pika_tool_focus_hit;
|
|
widget_class->hover = pika_tool_focus_hover;
|
|
widget_class->leave_notify = pika_tool_focus_leave_notify;
|
|
widget_class->motion_modifier = pika_tool_focus_motion_modifier;
|
|
widget_class->hover_modifier = pika_tool_focus_hover_modifier;
|
|
widget_class->get_cursor = pika_tool_focus_get_cursor;
|
|
widget_class->update_on_scale = TRUE;
|
|
|
|
g_object_class_install_property (object_class, PROP_TYPE,
|
|
g_param_spec_enum ("type", NULL, NULL,
|
|
PIKA_TYPE_LIMIT_TYPE,
|
|
PIKA_LIMIT_CIRCLE,
|
|
PIKA_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,
|
|
PIKA_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,
|
|
PIKA_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,
|
|
PIKA_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,
|
|
PIKA_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,
|
|
PIKA_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,
|
|
PIKA_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,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_init (PikaToolFocus *focus)
|
|
{
|
|
PikaToolFocusPrivate *priv;
|
|
|
|
priv = pika_tool_focus_get_instance_private (focus);
|
|
|
|
focus->priv = priv;
|
|
|
|
priv->hover = HOVER_NONE;
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_constructed (GObject *object)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (object);
|
|
PikaToolWidget *widget = PIKA_TOOL_WIDGET (object);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
gint i;
|
|
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
for (i = N_LIMITS - 1; i >= 0; i--)
|
|
{
|
|
priv->limits[i].group = pika_tool_widget_add_group (widget);
|
|
|
|
pika_tool_widget_push_group (widget, priv->limits[i].group);
|
|
|
|
priv->limits[i].item = pika_tool_widget_add_limit (
|
|
widget,
|
|
PIKA_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 = pika_tool_widget_add_handle (
|
|
widget,
|
|
PIKA_HANDLE_FILLED_CIRCLE,
|
|
0.0, 0.0, HANDLE_SIZE, HANDLE_SIZE,
|
|
PIKA_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);
|
|
}
|
|
}
|
|
|
|
pika_tool_widget_pop_group (widget);
|
|
}
|
|
|
|
pika_tool_focus_changed (widget);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (object);
|
|
PikaToolFocusPrivate *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
|
|
pika_tool_focus_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (object);
|
|
PikaToolFocusPrivate *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
|
|
pika_tool_focus_changed (PikaToolWidget *widget)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
gint i;
|
|
|
|
for (i = 0; i < N_LIMITS; i++)
|
|
{
|
|
pika_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;
|
|
|
|
pika_canvas_limit_get_radii (PIKA_CANVAS_LIMIT (priv->limits[i].item),
|
|
&rx, &ry);
|
|
|
|
for (j = 0; j < priv->limits[i].n_handles; j++)
|
|
{
|
|
PikaVector2 p = priv->limits[i].handles[j].dir;
|
|
gdouble r;
|
|
|
|
p.x *= rx;
|
|
p.y *= ry;
|
|
|
|
pika_vector2_rotate (&p, -priv->angle);
|
|
|
|
p.x += priv->x;
|
|
p.y += priv->y;
|
|
|
|
pika_canvas_handle_set_position (priv->limits[i].handles[j].item,
|
|
p.x, p.y);
|
|
|
|
r = pika_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++)
|
|
{
|
|
pika_canvas_item_set_visible (priv->limits[i].handles[j].item,
|
|
priv->type != PIKA_LIMIT_HORIZONTAL &&
|
|
priv->type != PIKA_LIMIT_VERTICAL &&
|
|
max_r >= 1.5 * HANDLE_SIZE);
|
|
}
|
|
|
|
pika_canvas_item_end_change (priv->limits[i].item);
|
|
}
|
|
}
|
|
|
|
static gint
|
|
pika_tool_focus_button_press (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
|
|
if (press_type == PIKA_BUTTON_PRESS_NORMAL)
|
|
{
|
|
pika_tool_focus_save (focus);
|
|
|
|
priv->orig_x = coords->x;
|
|
priv->orig_y = coords->y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_button_release (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
|
|
if (release_type == PIKA_BUTTON_RELEASE_CANCEL)
|
|
pika_tool_focus_restore (focus);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_motion (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
PikaDisplayShell *shell = pika_tool_widget_get_shell (widget);
|
|
gboolean extend;
|
|
gboolean constrain;
|
|
|
|
extend = state & pika_get_extend_selection_mask ();
|
|
constrain = state & pika_get_constrain_behavior_mask ();
|
|
|
|
switch (priv->hover)
|
|
{
|
|
case HOVER_NONE:
|
|
break;
|
|
|
|
case HOVER_LIMIT:
|
|
{
|
|
PikaCanvasItem *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;
|
|
|
|
pika_canvas_limit_center_point (PIKA_CANVAS_LIMIT (limit),
|
|
x, y,
|
|
&cx, &cy);
|
|
|
|
if (pika_canvas_item_transform_distance (limit,
|
|
x, y,
|
|
cx, cy) <= SNAP_DISTANCE)
|
|
{
|
|
x = cx;
|
|
y = cy;
|
|
}
|
|
|
|
if (fabs (fabs (priv->aspect_ratio) - 1.0) <= EPSILON)
|
|
{
|
|
if (priv->radius <= EPSILON)
|
|
{
|
|
g_object_set (focus,
|
|
"aspect-ratio", 0.0,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
radius = pika_canvas_limit_boundary_radius (PIKA_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)
|
|
{
|
|
if (priv->inner_limit > EPSILON)
|
|
outer_radius = inner_radius / priv->inner_limit;
|
|
else
|
|
inner_radius = 0.0;
|
|
}
|
|
else
|
|
{
|
|
inner_radius = MIN (inner_radius, outer_radius);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LIMIT_MIDDLE:
|
|
{
|
|
if (extend)
|
|
{
|
|
if (priv->inner_limit > EPSILON || priv->midpoint > EPSILON)
|
|
{
|
|
outer_radius = radius / (priv->inner_limit +
|
|
(1.0 - priv->inner_limit) *
|
|
priv->midpoint);
|
|
inner_radius = priv->inner_limit * outer_radius;
|
|
}
|
|
else
|
|
{
|
|
radius = 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
radius = CLAMP (radius, inner_radius, outer_radius);
|
|
}
|
|
|
|
if (fabs (outer_radius - inner_radius) > EPSILON)
|
|
{
|
|
g_object_set (focus,
|
|
"midpoint", MAX ((radius - inner_radius) /
|
|
(outer_radius - inner_radius),
|
|
0.0),
|
|
NULL);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
g_object_set (focus,
|
|
"radius", outer_radius,
|
|
NULL);
|
|
|
|
if (outer_radius > EPSILON)
|
|
{
|
|
g_object_set (focus,
|
|
"inner-limit", inner_radius / outer_radius,
|
|
NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HOVER_HANDLE:
|
|
{
|
|
PikaToolFocusHandle *handle;
|
|
PikaVector2 e;
|
|
PikaVector2 s;
|
|
PikaVector2 p;
|
|
gdouble rx, ry;
|
|
gdouble r;
|
|
|
|
handle = &priv->limits[priv->hover_limit].handles[priv->hover_handle];
|
|
|
|
e = handle->dir;
|
|
|
|
pika_vector2_rotate (&e, -priv->angle);
|
|
|
|
s = e;
|
|
|
|
pika_canvas_limit_get_radii (
|
|
PIKA_CANVAS_LIMIT (priv->limits[priv->hover_limit].item),
|
|
&rx, &ry);
|
|
|
|
if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
pika_vector2_mul (&s, ry);
|
|
else
|
|
pika_vector2_mul (&s, rx);
|
|
|
|
p.x = coords->x - priv->x;
|
|
p.y = coords->y - priv->y;
|
|
|
|
r = pika_vector2_inner_product (&p, &e);
|
|
r = MAX (r, 0.0);
|
|
|
|
p = e;
|
|
|
|
pika_vector2_mul (&p, r);
|
|
|
|
if (extend)
|
|
{
|
|
if (handle->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
if (rx <= EPSILON && ry > EPSILON)
|
|
break;
|
|
|
|
ry = r * (1.0 - priv->aspect_ratio);
|
|
}
|
|
else
|
|
{
|
|
if (ry <= EPSILON && rx > EPSILON)
|
|
break;
|
|
|
|
rx = r * (1.0 + priv->aspect_ratio);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pika_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 (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,
|
|
NULL);
|
|
|
|
if (! extend)
|
|
{
|
|
gdouble aspect_ratio;
|
|
|
|
if (fabs (rx - ry) <= EPSILON)
|
|
aspect_ratio = 0.0;
|
|
else if (rx > ry)
|
|
aspect_ratio = 1.0 - ry / rx;
|
|
else
|
|
aspect_ratio = rx / ry - 1.0;
|
|
|
|
g_object_set (focus,
|
|
"aspect-ratio", aspect_ratio,
|
|
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 = pika_display_shell_constrain_angle (shell, angle, 12);
|
|
|
|
g_object_set (focus,
|
|
"angle", angle,
|
|
NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static PikaHit
|
|
pika_tool_focus_hit (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
|
|
pika_tool_focus_update_hover (focus, coords, proximity);
|
|
|
|
switch (priv->hover)
|
|
{
|
|
case HOVER_NONE:
|
|
return PIKA_HIT_NONE;
|
|
|
|
case HOVER_LIMIT:
|
|
case HOVER_HANDLE:
|
|
return PIKA_HIT_DIRECT;
|
|
|
|
case HOVER_MOVE:
|
|
case HOVER_ROTATE:
|
|
return PIKA_HIT_INDIRECT;
|
|
}
|
|
|
|
g_return_val_if_reached (PIKA_HIT_NONE);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_hover (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
|
|
pika_tool_focus_update_hover (focus, coords, proximity);
|
|
|
|
pika_tool_focus_update_highlight (focus);
|
|
pika_tool_focus_update_status (focus, state);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_leave_notify (PikaToolWidget *widget)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
|
|
pika_tool_focus_update_hover (focus, NULL, FALSE);
|
|
|
|
pika_tool_focus_update_highlight (focus);
|
|
|
|
PIKA_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_motion_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
|
|
pika_tool_focus_update_status (focus, state);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_hover_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
|
|
pika_tool_focus_update_status (focus, state);
|
|
}
|
|
|
|
static gboolean
|
|
pika_tool_focus_get_cursor (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaCursorType *cursor,
|
|
PikaToolCursorType *tool_cursor,
|
|
PikaCursorModifier *modifier)
|
|
{
|
|
PikaToolFocus *focus = PIKA_TOOL_FOCUS (widget);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
|
|
switch (priv->hover)
|
|
{
|
|
case HOVER_NONE:
|
|
return FALSE;
|
|
|
|
case HOVER_LIMIT:
|
|
case HOVER_HANDLE:
|
|
*modifier = PIKA_CURSOR_MODIFIER_RESIZE;
|
|
return TRUE;
|
|
|
|
case HOVER_MOVE:
|
|
*modifier = PIKA_CURSOR_MODIFIER_MOVE;
|
|
return TRUE;
|
|
|
|
case HOVER_ROTATE:
|
|
*modifier = PIKA_CURSOR_MODIFIER_ROTATE;
|
|
return TRUE;
|
|
}
|
|
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_update_hover (PikaToolFocus *focus,
|
|
const PikaCoords *coords,
|
|
gboolean proximity)
|
|
{
|
|
PikaToolFocusPrivate *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++)
|
|
{
|
|
PikaCanvasItem *handle = priv->limits[i].handles[j].item;
|
|
|
|
if (pika_canvas_item_get_visible (handle))
|
|
{
|
|
gdouble x, y;
|
|
gdouble dist;
|
|
|
|
g_object_get (handle,
|
|
"x", &x,
|
|
"y", &y,
|
|
NULL);
|
|
|
|
dist = pika_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 ( pika_canvas_limit_is_inside (
|
|
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
|
|
coords->x, coords->y) &&
|
|
! pika_canvas_limit_is_inside (
|
|
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
|
|
coords->x, coords->y))
|
|
{
|
|
if (pika_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 (! pika_canvas_limit_is_inside (
|
|
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_INNER].item),
|
|
coords->x, coords->y))
|
|
{
|
|
if (pika_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 (pika_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 (pika_canvas_limit_is_inside (
|
|
PIKA_CANVAS_LIMIT (priv->limits[LIMIT_OUTER].item),
|
|
coords->x, coords->y))
|
|
{
|
|
priv->hover = HOVER_MOVE;
|
|
}
|
|
else
|
|
{
|
|
priv->hover = HOVER_ROTATE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_update_highlight (PikaToolFocus *focus)
|
|
{
|
|
PikaToolWidget *widget = PIKA_TOOL_WIDGET (focus);
|
|
PikaToolFocusPrivate *priv = focus->priv;
|
|
gint i;
|
|
|
|
if (priv->hover_item == priv->last_hover_item)
|
|
return;
|
|
|
|
if (priv->last_hover_item)
|
|
pika_canvas_item_set_highlight (priv->last_hover_item, FALSE);
|
|
|
|
#define RAISE_ITEM(item) \
|
|
G_STMT_START \
|
|
{ \
|
|
g_object_ref (item); \
|
|
\
|
|
pika_tool_widget_remove_item (widget, item); \
|
|
pika_tool_widget_add_item (widget, item); \
|
|
\
|
|
g_object_unref (item); \
|
|
} \
|
|
G_STMT_END
|
|
|
|
for (i = N_LIMITS - 1; i >= 0; i--)
|
|
RAISE_ITEM (PIKA_CANVAS_ITEM (priv->limits[i].group));
|
|
|
|
if (priv->hover_item)
|
|
{
|
|
pika_canvas_item_set_highlight (priv->hover_item, TRUE);
|
|
|
|
RAISE_ITEM (PIKA_CANVAS_ITEM (priv->limits[priv->hover_limit].group));
|
|
}
|
|
|
|
#undef RAISE_ITEM
|
|
|
|
priv->last_hover_item = priv->hover_item;
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_update_status (PikaToolFocus *focus,
|
|
GdkModifierType state)
|
|
{
|
|
PikaToolFocusPrivate *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 & pika_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 |= pika_get_extend_selection_mask ();
|
|
}
|
|
else
|
|
{
|
|
message = _("Click-Drag to resize the focus");
|
|
}
|
|
break;
|
|
|
|
case HOVER_HANDLE:
|
|
if (! (state & pika_get_extend_selection_mask ()))
|
|
{
|
|
message = _("Click-Drag to change the aspect ratio");
|
|
extend_selection_format = _("%s to resize the focus");
|
|
state_mask |= pika_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 |= pika_get_constrain_behavior_mask ();
|
|
break;
|
|
}
|
|
|
|
status = pika_suggest_modifiers (message,
|
|
~state & state_mask,
|
|
extend_selection_format,
|
|
toggle_behavior_format,
|
|
NULL);
|
|
|
|
pika_tool_widget_set_status (PIKA_TOOL_WIDGET (focus), status);
|
|
|
|
g_free (status);
|
|
}
|
|
|
|
static void
|
|
pika_tool_focus_save (PikaToolFocus *focus)
|
|
{
|
|
PikaToolFocusPrivate *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
|
|
pika_tool_focus_restore (PikaToolFocus *focus)
|
|
{
|
|
PikaToolFocusPrivate *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 */
|
|
|
|
PikaToolWidget *
|
|
pika_tool_focus_new (PikaDisplayShell *shell)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_TOOL_FOCUS,
|
|
"shell", shell,
|
|
NULL);
|
|
}
|