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