-
Notifications
You must be signed in to change notification settings - Fork 0
/
draggable.m
453 lines (404 loc) · 17.8 KB
/
draggable.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
function draggable(h,varargin)
% DRAGGABLE - Make it so that a graphics object can be dragged in a figure.
% This function makes an object interactive by allowing it to be dragged
% accross a set of axes, following or not certain constraints. This
% allows for intuitive control elements which are not buttons or other
% standard GUI objects, and which reside inside an axis. Typical use
% involve markers on an axis, whose position alters the output of a
% computation or display
%
% >> draggable(h);
%
% makes the object with handle "h" draggable. Use the "Position" property
% of the object to retrieve its position, by issuing a get(h,'Position')
% command.
%
% >> draggable(h,...,motionfcn)
%
% where "motionfcn" is a function handle, executes the given function
% while the object is dragged. Handle h is passed to motionfcn as an
% argument. Argument "motionfcn" can be put anywhere after handle "h".
%
% >> draggable(h,...,'endfcn',endfcn) %% added by SMB
%
% where "endfcn" is a function handle, executes the given function
% AFTER the object is dragged. The function handle must come after
% the string 'endfcn', to avoid ambiguity with the "motionfcn"
% argument (above). Handle h is passed to endfcn as an argument.
%
% >> draggable(h,...,constraint,p);
%
% enables the object with handle "h" to be dragged, with a constraint.
% Arguments "constraint" (a string) and "p" (a vector) can be put
% anywhere after handle "h".
%
% The argument "constraint" may be one of the following strings:
%
% 'n' or 'none': The object is unconstrained (default).
% 'h' or 'horizontal': The object can only be moved horizontally.
% 'v' or 'vertical': The object can only be moved vertically.
%
% The argument "p" is an optional parameter which depends upon the
% constraint type:
%
% Constraint p Description
% -----------------------------------------------------------------------
%
% 'none' [x1 x2 y1 y2] Drag range (for the object's outer
% limits, from x1 to x2 on the x-axis
% and from y1 to y2 on the y-axis).
% Default is the current axes range.
% Use "inf" if no limit is desired.
%
% 'horizontal' [xmin xmax] Drag range (for the object's outer
% limits). Default is the x-axis
% range. Use "inf" if no limit is
% desired.
%
% 'vertical' [ymin ymax] Drag range (for the object's outer
% limits). Default is the y-axis
% range. Use "inf" if no limit is
% desired.
%
% -----------------------------------------------------------------------
%
% >> draggable(h,'off')
%
% returns object h to its original, non-draggable state.
%
% See the source code (e.g. by issuing "type draggable" at the Matlab
% prompt) for implementation notes and the copyright notice.
% VERSION INFORMATION:
% 2003-11-20: Initially submitted to MatlabCentral.Com
% 2004-01-06: Addition of the renderer option, as proposed by Ohad Gal
% as a feedback on MatlabCentral.Com.
% 2004-02-18: Bugfix: now works with 1-element plots and line objects
% 2004-03-04: Bugfix: sanitized the way the object's new position is
% computed; it now always follow the mouse even after the
% mouse pointer was out of the axes.
% 2004-03-05: Bugfix: movement when mouse is out of the axes is now
% definitely correct ;)
% 2006-05-23: Bugfix: fix a rendering issue using Matlab 7 & +
% Deprecated the rendering options: rendering seems ok with
% every renderer.
% Removed help section concerning the renderer option:
%
% >> draggable(h,...,renderer);
%
% where renderer is one of 'painters', 'zbuffer' or 'opengl', uses the
% corresponding renderer for the figure while the graphical object whose
% handle is h is being dragged. By default, zbuffer is used since it is
% the only renderer that offers both acceptable performance and a
% guaranteed correct behavior with draggable. The 'painters' renderer is
% too slow, while the 'opengl' renderer's behavior with draggable depends
% ont the graphics driver used and may differ from what is expected.
% IMPLEMENTATION NOTES:
%
% This function uses the dragged object's "ButtonDownFcn" function and set it
% so that the objec becomes draggable. Any previous "ButtonDownFcn" is thus
% lost during operation, but is retrieved after issuing the draggable(h,'off')
% command.
%
% Information about the object's behavior is also stored in the object's
% 'UserData' property, using setappdata() and getappdata(). The original
% 'UserData' property is restored after issuing the draggable(h,'off')
% command.
%
% The corresponding figure's "WindowButtonDownFcn", "WindowButtonUpFcn" and
% "WindowButtonMotionFcn" functions. During operation, those functions are
% set by DRAGGABLE; however, the original ones are restored after the user
% stops dragging the object.
%
% By default, DRAGGABLE also switches the figure's renderer to 'zbuffer'
% during operation: 'painters' is not fast enough and 'opengl' sometimes
% produce curious results. However there may be a need to switch to another
% renderer, so the user can now specify a specific figure renderer during
% object drag (thanks to Ohad Gal for the suggestion).
%
% The "motionfcn" function handle is called at each displacement, after the
% object's position is updated, using "feval(motionfcn,h)", where h is the
% object's handle.
%
% TO DO:
%
% 1 - For now, DRAGGABLE allows only one object at a time to be draggable. In
% the future, draggable(h), where h is a vector of handles, will create a
% group of objects that will all be dragged when one of them is selected.
% ==============================================================================
% Copyright (C) 2003, 2004
% Francois Bouffard
% fbouffar@gel.ulaval.ca
% Université Laval, Québec City
% ==============================================================================
% ==============================================================================
% Input arguments management
% ==============================================================================
% Initialization of some default arguments
user_renderer = 'zbuffer';
user_movefcn = [];
constraint = 'none';
p = [];
user_endfcn = []; % added by SMB (see 'for k' loop below)
endinput = 0;
% At least the handle to the object must be given
Narg = nargin;
if Narg == 0
error('Not engough input arguments');
elseif prod(size(h))>1
error('Only one object at a time can be made draggable');
end;
% Fetching informations about the parent axes
axh = get(h,'Parent');
if iscell(axh)
axh = axh{1};
end;
fgh = get(axh,'Parent');
ax_xlim = get(axh,'XLim');
ax_ylim = get(axh,'YLim');
% Assigning optional arguments
Noptarg = Narg - 1;
for k = 1:Noptarg
current_arg = varargin{k};
if isa(current_arg,'function_handle') && endinput
user_endfcn = current_arg; % added by SMB
endinput = 0; % 'movefcn' can still be a later argument
elseif isa(current_arg,'function_handle')
user_movefcn = current_arg;
end;
if ischar(current_arg);
switch lower(current_arg)
case {'off'}
set_initial_state(h);
return;
case {'painters','zbuffer','opengl'}
warning('The renderer option is deprecated and will not be taken into account');
user_renderer = current_arg;
case {'endfcn'} % added by SMB
endinput = 1;
otherwise
constraint = current_arg;
end;
end;
if isnumeric(current_arg);
p = current_arg;
end;
end;
% Assigning defaults for constraint
switch lower(constraint)
case {'n','none'}
if isempty(p); p = [ax_xlim ax_ylim]; end;
case {'h','horizontal'}
if isempty(p); p = ax_xlim; end;
case {'v','vertical'}
if isempty(p); p = ax_ylim; end;
otherwise
error('Unknown constraint type');
end;
% ==============================================================================
% Saving initial state and parameters, setting up the object callback
% ==============================================================================
% Saving object's and parent figure's initial state
setappdata(h,'initial_userdata',get(h,'UserData'));
setappdata(h,'initial_objbdfcn',get(h,'ButtonDownFcn'));
setappdata(h,'initial_renderer',get(fgh,'Renderer'));
setappdata(h,'initial_wbdfcn',get(fgh,'WindowButtonDownFcn'));
setappdata(h,'initial_wbufcn',get(fgh,'WindowButtonUpFcn'));
setappdata(h,'initial_wbmfcn',get(fgh,'WindowButtonMotionFcn'));
% Saving parameters
setappdata(h,'constraint_type',constraint);
setappdata(h,'constraint_parameters',p);
setappdata(h,'user_movefcn',user_movefcn);
setappdata(h,'user_endfcn',user_endfcn); % added by SMB
setappdata(h,'user_renderer',user_renderer);
% Detecting if object's position is specified through the
% 'Position' or 'XData' and 'YData' properties
h_properties = get(h);
if isfield(h_properties,'Position')
setappdata(h,'position_type','rect');
else
setappdata(h,'position_type','xydata');
end;
% Setting the object's ButtonDownFcn
set(h,'ButtonDownFcn',@click_object);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ==============================================================================
% FUNCTION click_object
% Executed when the object is clicked
% ==============================================================================
function click_object(obj,eventdata);
% obj here is the object to be dragged and gcf is the object's parent
% figure since the user clicked on the object
h = obj;
position_type = getappdata(h,'position_type');
if strcmp(position_type,'xydata')
setappdata(h,'initial_xdata',get(h,'XData'));
setappdata(h,'initial_ydata',get(h,'YData'));
else
setappdata(h,'initial_position',get(h,'Position'));
end;
setappdata(h,'initial_point',get(gca,'CurrentPoint'));
set(gcf,'WindowButtonDownFcn',{@activate_movefcn,h});
set(gcf,'WindowButtonUpFcn',{@deactivate_movefcn,h});
activate_movefcn(gcf,eventdata,h);
% ==============================================================================
% FUNCTION activate_movefcn
% Activates the WindowButtonMotionFcn for the figure
% ==============================================================================
function activate_movefcn(obj,eventdata,h);
% obj here is the figure containing the object
user_renderer = getappdata(h,'user_renderer');
%set(obj,'Renderer',user_renderer);
if strcmp(user_renderer,'painters')
set(obj,'Doublebuffer','on');
end;
set(obj,'WindowButtonMotionFcn',{@movefcn,h});
% ==============================================================================
% FUNCTION deactivate_movefcn
% Deactivates the WindowButtonMotionFcn for the figure
% ==============================================================================
function deactivate_movefcn(obj,eventdata,h);
% obj here is the figure containing the object
set(obj,'WindowButtonMotionFcn',getappdata(h,'initial_wbmfcn'));
set(obj,'WindowButtonDownFcn',getappdata(h,'initial_wbdfcn'));
set(obj,'WindowButtonUpFcn',getappdata(h,'initial_wbufcn'));
%set(obj,'Renderer',getappdata(h,'initial_renderer'));
feval(getappdata(h,'user_endfcn'),h); % added by SMB
% ==============================================================================
% FUNCTION set_initial_state
% Returns the object to its initial state
% ==============================================================================
function set_initial_state(h);
initial_objbdfcn = getappdata(h,'initial_objbdfcn');
initial_userdata = getappdata(h,'initial_userdata');
set(h,'ButtonDownFcn',initial_objbdfcn);
set(h,'UserData',initial_userdata);
% ==============================================================================
% FUNCTION movefcn
% Actual code for dragging the object
% ==============================================================================
function movefcn(obj,eventdata,h);
% obj here is the figure containing the object
% Retrieving data saved in the figure
position_type = getappdata(h,'position_type');
if strcmp(position_type,'xydata')
initial_xdata = getappdata(h,'initial_xdata');
initial_ydata = getappdata(h,'initial_ydata');
else
%initial_position = getappdata(h,'initial_position');
end;
initial_point = getappdata(h,'initial_point');
constraint = getappdata(h,'constraint_type');
p = getappdata(h,'constraint_parameters');
user_movefcn = getappdata(h,'user_movefcn');
% Getting current point
current_point = get(gca,'CurrentPoint');
% Retrieving (x,y) couple for current and initial points
cpt = current_point(1,1:2);
ipt = initial_point(1,1:2);
% Computing movement
dpt = cpt - ipt;
% Computing movement range and imposing movement constraints
% (p is always [xmin xmax ymin ymax])
switch lower(constraint)
case {'n','none'};
range = p;
case {'h','horizontal'}
dpt(2) = 0;
range = [p -inf inf];
case {'v','vertical'}
dpt(1) = 0;
range = [-inf inf p];
end;
% Computing new position.
% What we want is actually a bit complex: we want the object to adopt the new
% position, unless it gets out of range. If it gets out of range in a direction,
% we want it to stick to the limit in that direction. Also, if the object is out
% of range at the beginning of the movement, we want to be able to move it back
% into range, so that movement must then be allowed.
switch lower(position_type)
% Objects with rectangle-type position information
case 'rect'
% Retrieveing various quantities:
% initial_position is the position when the object was clicked
% oldpos is the current position, which will be updated
% newpos is the new (proposed) position
% old_obj_extent and new_pos extent are the extent occupied by
% the old and new objects
initial_position = getappdata(h,'initial_position');
oldpos = get(h,'Position');
old_obj_extent = [oldpos(1) oldpos(1)+oldpos(3) oldpos(2) oldpos(2)+oldpos(4)];
newpos = initial_position + [dpt 0 0];
new_obj_extent = [newpos(1) newpos(1)+newpos(3) newpos(2) newpos(2)+newpos(4)];
% Verifying if old and new objects breach the allowed range in any
% direction (see the function is_inside_range below)
old_inrange = is_inside_range(old_obj_extent,range);
new_inrange = is_inside_range(new_obj_extent,range);
% Modifying dpt to stick to range limit if range violation occured,
% but the movement won't get restricted if the object was out of range
% to begin with
if old_inrange(1) & ~new_inrange(1)
dpt(1) = range(1) - initial_position(1);
end
if old_inrange(2) & ~new_inrange(2)
dpt(1) = range(2) - initial_position(1) - initial_position(3);
end;
if old_inrange(3) & ~new_inrange(3)
dpt(2) = range(3) - initial_position(2);
end;
if old_inrange(4) & ~new_inrange(4)
dpt(2) = range(4) - initial_position(2) - initial_position(4);
end
% Computing the final new position and setting it
newpos = initial_position + [dpt 0 0];
set(h,'Position',newpos);
% Objects with line-type position information
case 'xydata'
% Retrieveing various quantities:
% initial_position is the position when the object was clicked
% oldpos is the current position, which will be updated
% newpos is the new (proposed) position
% old_obj_extent and new_pos extent are the extent occupied by
% the old and new objects
initial_xdata = getappdata(h,'initial_xdata');
initial_ydata = getappdata(h,'initial_ydata');
initial_position = [initial_xdata(:)' ; initial_ydata(:)'];
xdata = get(h,'XData');
ydata = get(h,'YData');
oldpos = [xdata(:)' ; ydata(:)'];
old_obj_extent = [min(xdata) max(xdata) min(ydata) max(ydata)];
newpos = initial_position + dpt'*ones(1,size(oldpos,2));
new_obj_extent = [min(newpos(1,:)) max(newpos(1,:)) min(newpos(2,:)) max(newpos(2,:))];
% Verifying if old and new objects breach the allowed range in any
% direction (see the function is_inside_range below)
old_inrange = is_inside_range(old_obj_extent,range);
new_inrange = is_inside_range(new_obj_extent,range);
% Modifying dpt to stick to range limit if range violation occured,
% but the movement won't get restricted if the object was out of range
% to begin with
if old_inrange(1) & ~new_inrange(1)
dpt(1) = range(1) - min(initial_xdata);
end
if old_inrange(2) & ~new_inrange(2)
dpt(1) = range(2) - max(initial_xdata);
end;
if old_inrange(3) & ~new_inrange(3)
dpt(2) = range(3) - min(initial_ydata);
end;
if old_inrange(4) & ~new_inrange(4)
dpt(2) = range(4) - max(initial_ydata);
end
% Computing the final new position and setting it
newpos = initial_position + dpt'*ones(1,size(oldpos,2));
set(h,'XData',newpos(1,:),'YData',newpos(2,:));
end;
% Calling user-provided function handle
if ~isempty(user_movefcn)
feval(user_movefcn,h);
end;
% ==============================================================================
% FUNCTION is_inside_range
% Checks if a rectangular object is entirely inside a rectangular range
% ==============================================================================
function inrange = is_inside_range(extent,range)
inrange = [extent(1)>=range(1) extent(2)<=range(2) ...
extent(3)>=range(3) extent(4)<=range(4)];