Commit 40afffbe authored by Ell's avatar Ell

app: add option to stroke the warp tool during cursor motion ...

... and to disable/control the rate of the periodic stroke.

The warp tool is now fast enough to enable stroking directly in
the motion handler, which gives better-quality response to motion
than stroking periodically.  It's not quite fast enough to enable
exact motion, though :/

Allow individually enabling/disabling stroking during motion and
periodically, and allow controlling the rate of the periodical
stroke.
parent 13b619c4
......@@ -44,18 +44,21 @@ enum
PROP_EFFECT_SIZE,
PROP_EFFECT_HARDNESS,
PROP_STROKE_SPACING,
PROP_STROKE_DURING_MOTION,
PROP_STROKE_PERIODICALLY,
PROP_STROKE_PERIODICALLY_RATE,
PROP_N_ANIMATION_FRAMES
};
static void gimp_warp_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_warp_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_warp_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_warp_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (GimpWarpOptions, gimp_warp_options,
......@@ -108,6 +111,27 @@ gimp_warp_options_class_init (GimpWarpOptionsClass *klass)
1.0, 100.0, 20.0,
GIMP_PARAM_STATIC_STRINGS);
GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_STROKE_DURING_MOTION,
"stroke-during-motion",
_("During motion"),
_("Apply effect during motion"),
TRUE,
GIMP_PARAM_STATIC_STRINGS);
GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_STROKE_PERIODICALLY,
"stroke-periodically",
_("Periodically"),
_("Apply effect periodically"),
FALSE,
GIMP_PARAM_STATIC_STRINGS);
GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_STROKE_PERIODICALLY_RATE,
"stroke-periodically-rate",
_("Rate"),
_("Periodical stroke rate"),
0.0, 100.0, 50.0,
GIMP_PARAM_STATIC_STRINGS);
GIMP_CONFIG_PROP_INT (object_class, PROP_N_ANIMATION_FRAMES,
"n-animation-frames",
_("Frames"),
......@@ -146,6 +170,15 @@ gimp_warp_options_set_property (GObject *object,
case PROP_STROKE_SPACING:
options->stroke_spacing = g_value_get_double (value);
break;
case PROP_STROKE_DURING_MOTION:
options->stroke_during_motion = g_value_get_boolean (value);
break;
case PROP_STROKE_PERIODICALLY:
options->stroke_periodically = g_value_get_boolean (value);
break;
case PROP_STROKE_PERIODICALLY_RATE:
options->stroke_periodically_rate = g_value_get_double (value);
break;
case PROP_N_ANIMATION_FRAMES:
options->n_animation_frames = g_value_get_int (value);
break;
......@@ -181,6 +214,15 @@ gimp_warp_options_get_property (GObject *object,
case PROP_STROKE_SPACING:
g_value_set_double (value, options->stroke_spacing);
break;
case PROP_STROKE_DURING_MOTION:
g_value_set_boolean (value, options->stroke_during_motion);
break;
case PROP_STROKE_PERIODICALLY:
g_value_set_boolean (value, options->stroke_periodically);
break;
case PROP_STROKE_PERIODICALLY_RATE:
g_value_set_double (value, options->stroke_periodically_rate);
break;
case PROP_N_ANIMATION_FRAMES:
g_value_set_int (value, options->n_animation_frames);
break;
......@@ -198,7 +240,8 @@ gimp_warp_options_gui (GimpToolOptions *tool_options)
GObject *config = G_OBJECT (tool_options);
GtkWidget *vbox = gimp_tool_options_gui (tool_options);
GtkWidget *frame;
GtkWidget *anim_vbox;
GtkWidget *vbox2;
GtkWidget *button;
GtkWidget *combo;
GtkWidget *scale;
......@@ -231,24 +274,46 @@ gimp_warp_options_gui (GimpToolOptions *tool_options)
gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
gtk_widget_show (scale);
/* the stroke frame */
frame = gimp_frame_new (_("Stroke"));
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
gtk_container_add (GTK_CONTAINER (frame), vbox2);
gtk_widget_show (vbox2);
button = gimp_prop_check_button_new (config, "stroke-during-motion", NULL);
gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0);
gtk_widget_show (button);
scale = gimp_prop_spin_scale_new (config, "stroke-periodically-rate", NULL,
1, 10, 1);
gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 0.0, 100.0);
frame = gimp_prop_expanding_frame_new (config, "stroke-periodically", NULL,
scale, NULL);
gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
/* the animation frame */
frame = gimp_frame_new (_("Animate"));
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
anim_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
gtk_container_add (GTK_CONTAINER (frame), anim_vbox);
gtk_widget_show (anim_vbox);
vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
gtk_container_add (GTK_CONTAINER (frame), vbox2);
gtk_widget_show (vbox2);
scale = gimp_prop_spin_scale_new (config, "n-animation-frames", NULL,
1.0, 10.0, 0);
gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 3.0, 100.0);
gtk_box_pack_start (GTK_BOX (anim_vbox), scale, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
gtk_widget_show (scale);
options->animate_button = gtk_button_new_with_label (_("Create Animation"));
gtk_widget_set_sensitive (options->animate_button, FALSE);
gtk_box_pack_start (GTK_BOX (anim_vbox), options->animate_button,
gtk_box_pack_start (GTK_BOX (vbox2), options->animate_button,
FALSE, FALSE, 0);
gtk_widget_show (options->animate_button);
......
......@@ -45,6 +45,10 @@ struct _GimpWarpOptions
gdouble effect_hardness;
gdouble stroke_spacing;
gboolean stroke_during_motion;
gboolean stroke_periodically;
gdouble stroke_periodically_rate;
gint n_animation_frames;
/* options gui */
......
......@@ -52,9 +52,9 @@
#include "gimp-intl.h"
#define STROKE_PERIOD 100
#define PREVIEW_SAMPLER GEGL_SAMPLER_NEAREST
#define COMMIT_SAMPLER GEGL_SAMPLER_CUBIC
#define STROKE_TIMER_MAX_FPS 20
#define PREVIEW_SAMPLER GEGL_SAMPLER_NEAREST
#define COMMIT_SAMPLER GEGL_SAMPLER_CUBIC
static void gimp_warp_tool_control (GimpTool *tool,
......@@ -103,11 +103,17 @@ static void gimp_warp_tool_options_notify (GimpTool *tool
static void gimp_warp_tool_draw (GimpDrawTool *draw_tool);
static gboolean gimp_warp_tool_can_stroke (GimpWarpTool *wt,
GimpDisplay *display,
gboolean show_message);
static gboolean gimp_warp_tool_start (GimpWarpTool *wt,
GimpDisplay *display);
static void gimp_warp_tool_halt (GimpWarpTool *wt);
static void gimp_warp_tool_commit (GimpWarpTool *wt);
static void gimp_warp_tool_start_stroke_timer (GimpWarpTool *wt);
static void gimp_warp_tool_stop_stroke_timer (GimpWarpTool *wt);
static gboolean gimp_warp_tool_stroke_timer (GimpWarpTool *wt);
static void gimp_warp_tool_create_graph (GimpWarpTool *wt);
......@@ -243,6 +249,9 @@ gimp_warp_tool_button_press (GimpTool *tool,
return;
}
if (! gimp_warp_tool_can_stroke (wt, display, TRUE))
return;
wt->current_stroke = gegl_path_new ();
new_op = gegl_node_new_child (NULL,
......@@ -267,11 +276,7 @@ gimp_warp_tool_button_press (GimpTool *tool,
gegl_path_append (wt->current_stroke,
'M', coords->x - off_x, coords->y - off_y);
wt->cursor_moved = FALSE;
wt->stroke_timer = g_timeout_add (STROKE_PERIOD,
(GSourceFunc) gimp_warp_tool_stroke_timer,
wt);
gimp_warp_tool_start_stroke_timer (wt);
gimp_tool_control_activate (tool->control);
}
......@@ -290,8 +295,7 @@ gimp_warp_tool_button_release (GimpTool *tool,
gimp_tool_control_halt (tool->control);
g_source_remove (wt->stroke_timer);
wt->stroke_timer = 0;
gimp_warp_tool_stop_stroke_timer (wt);
g_signal_handlers_disconnect_by_func (wt->current_stroke,
gimp_warp_tool_stroke_changed,
......@@ -339,13 +343,25 @@ gimp_warp_tool_motion (GimpTool *tool,
GdkModifierType state,
GimpDisplay *display)
{
GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
wt->cursor_x = coords->x;
wt->cursor_y = coords->y;
wt->cursor_moved = TRUE;
wt->cursor_x = coords->x;
wt->cursor_y = coords->y;
if (options->stroke_during_motion)
{
gint off_x, off_y;
gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
gegl_path_append (wt->current_stroke,
'L', wt->cursor_x - off_x, wt->cursor_y - off_y);
gimp_warp_tool_start_stroke_timer (wt);
}
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
}
......@@ -414,10 +430,15 @@ gimp_warp_tool_cursor_update (GimpTool *tool,
GdkModifierType state,
GimpDisplay *display)
{
GimpWarpTool *wt = GIMP_WARP_TOOL (tool);
GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (tool);
GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_PLUS;
if (display == tool->display)
if (! gimp_warp_tool_can_stroke (wt, display, FALSE))
{
modifier = GIMP_CURSOR_MODIFIER_BAD;
}
else if (display == tool->display)
{
/* FIXME have better cursors */
......@@ -434,18 +455,6 @@ gimp_warp_tool_cursor_update (GimpTool *tool,
break;
}
}
else
{
GimpImage *image = gimp_display_get_image (display);
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
gimp_item_is_content_locked (GIMP_ITEM (drawable)) ||
! gimp_item_is_visible (GIMP_ITEM (drawable)))
{
modifier = GIMP_CURSOR_MODIFIER_BAD;
}
}
gimp_tool_control_set_cursor_modifier (tool->control, modifier);
......@@ -571,37 +580,77 @@ gimp_warp_tool_draw (GimpDrawTool *draw_tool)
}
static gboolean
gimp_warp_tool_start (GimpWarpTool *wt,
GimpDisplay *display)
gimp_warp_tool_can_stroke (GimpWarpTool *wt,
GimpDisplay *display,
gboolean show_message)
{
GimpTool *tool = GIMP_TOOL (wt);
GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
GimpImage *image = gimp_display_get_image (display);
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
const Babl *format;
GeglRectangle bbox;
if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
{
gimp_tool_message_literal (tool, display,
_("Cannot warp layer groups."));
if (show_message)
{
gimp_tool_message_literal (tool, display,
_("Cannot warp layer groups."));
}
return FALSE;
}
if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
{
gimp_tool_message_literal (tool, display,
_("The active layer's pixels are locked."));
if (show_message)
{
gimp_tool_message_literal (tool, display,
_("The active layer's pixels are locked."));
}
return FALSE;
}
if (! gimp_item_is_visible (GIMP_ITEM (drawable)))
{
gimp_tool_message_literal (tool, display,
_("The active layer is not visible."));
if (show_message)
{
gimp_tool_message_literal (tool, display,
_("The active layer is not visible."));
}
return FALSE;
}
if (! options->stroke_during_motion &&
! options->stroke_periodically)
{
if (show_message)
{
gimp_tool_message_literal (tool, display,
_("No stroke events selected."));
}
return FALSE;
}
return TRUE;
}
static gboolean
gimp_warp_tool_start (GimpWarpTool *wt,
GimpDisplay *display)
{
GimpTool *tool = GIMP_TOOL (wt);
GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
GimpImage *image = gimp_display_get_image (display);
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
const Babl *format;
GeglRectangle bbox;
if (! gimp_warp_tool_can_stroke (wt, display, TRUE))
return FALSE;
tool->display = display;
tool->drawable = drawable;
......@@ -711,26 +760,47 @@ gimp_warp_tool_commit (GimpWarpTool *wt)
}
}
static gboolean
gimp_warp_tool_stroke_timer (GimpWarpTool *wt)
static void
gimp_warp_tool_start_stroke_timer (GimpWarpTool *wt)
{
GimpTool *tool = GIMP_TOOL (wt);
GimpWarpOptions *options = GIMP_WARP_TOOL_GET_OPTIONS (wt);
gint off_x, off_y;
/* don't append the current point to the path if we're using the MOVE
* behavior, and the cursor didn't move since last time; it's a nop, and
* results in an unnecessary update.
*/
if (options->behavior != GIMP_WARP_BEHAVIOR_MOVE || wt->cursor_moved)
gimp_warp_tool_stop_stroke_timer (wt);
if (options->stroke_periodically &&
options->stroke_periodically_rate > 0.0 &&
! (options->behavior == GIMP_WARP_BEHAVIOR_MOVE &&
options->stroke_during_motion))
{
gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
gdouble fps;
gegl_path_append (wt->current_stroke,
'L', wt->cursor_x - off_x, wt->cursor_y - off_y);
fps = STROKE_TIMER_MAX_FPS * options->stroke_periodically_rate / 100.0;
wt->cursor_moved = FALSE;
wt->stroke_timer = g_timeout_add (1000.0 / fps,
(GSourceFunc) gimp_warp_tool_stroke_timer,
wt);
}
}
static void
gimp_warp_tool_stop_stroke_timer (GimpWarpTool *wt)
{
if (wt->stroke_timer)
g_source_remove (wt->stroke_timer);
wt->stroke_timer = 0;
}
static gboolean
gimp_warp_tool_stroke_timer (GimpWarpTool *wt)
{
GimpTool *tool = GIMP_TOOL (wt);
gint off_x, off_y;
gimp_item_get_offset (GIMP_ITEM (tool->drawable), &off_x, &off_y);
gegl_path_append (wt->current_stroke,
'L', wt->cursor_x - off_x, wt->cursor_y - off_y);
return TRUE;
}
......@@ -826,10 +896,10 @@ gimp_warp_tool_update_stroke (GimpWarpTool *wt,
gegl_path_get_bounds (stroke, &min_x, &max_x, &min_y, &max_y);
g_object_unref (stroke);
bbox.x = min_x - size * 0.5;
bbox.y = min_y - size * 0.5;
bbox.width = max_x - min_x + size;
bbox.height = max_y - min_y + size;
bbox.x = floor (min_x - size * 0.5);
bbox.y = floor (min_y - size * 0.5);
bbox.width = ceil (max_x + size * 0.5) - bbox.x;
bbox.height = ceil (max_y + size * 0.5) - bbox.y;
#ifdef WARP_DEBUG
g_printerr ("update stroke: (%d,%d), %dx%d\n",
......
......@@ -43,7 +43,6 @@ struct _GimpWarpTool
gdouble cursor_x; /* Hold the cursor x position */
gdouble cursor_y; /* Hold the cursor y position */
gboolean cursor_moved; /* Did the cursor move since the last stroke? */
GeglBuffer *coords_buffer; /* Buffer where coordinates are stored */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment