function [ObjHandle, varargout] = guipanel(varargin)
%% Parse Child -----------------------------------------------------------%
child = inputParser;
child.StructExpand = true;
child.KeepUnmatched = true;
%Interuptible: Defaults to off
child.addParamValue('Interruptible','off')
%BusyAction: Defaults to cancel
child.addParamValue('BusyAction','queue')
%Child's Parent: Defaults to new fig if Param/Value pair is missing
if (nargin >= 1) && ~ischar(varargin{1})
assert(ishandle(varargin{1}),'guipanel:badHandle',...
['When creating a guipanel inside an existing figure/panel, ',...
'the first argument must be a currently existing/valid graphics handle'])
child.addOptional('Parent',varargin{1})
else
%-% NOTICE %-%
% The figure handle is generated by evaluating a figure command at the
% time the addParamValue method is called.
child.addParamValue('Parent',figure);
end
%% Finish Parsing and Instantiate ----------------------------------------%
% We have only slightly differed from the calling syntax of uipanel.
% Specifically, guipanel may be called with a single argument, the handle
% of a pre-existing figure or pre-exisiting panel (this allows easy
% nesting of panels).
child.parse(varargin{:});
if ~any(strcmp('BusyAction',child.UsingDefaults));
warning('guipanel:defaultOverWrite',...
'Setting ''BusyAction'' to ''cancel'' may cause unexpected gui behavior')
end
if ~any(strcmp('Interruptible',child.UsingDefaults));
warning('guipanel:defaultOverWrite',...
'Setting ''Interruptible'' to ''on'' may cause unexpected gui behavior')
end
assert(ishandle(child.Results.Parent),'guipanel:badHandle',...
'%s is not a valid graphics handle',num2str(child.Results.Parent))
%Create a panel in the specified container (Figure or Panel)
if isempty(fieldnames(child.Unmatched))
ObjHandle = uipanel(child.Results,...
'ButtonDownFcn',@PanelButtonDownFcn);
else
ObjHandle = uipanel(child.Results,...
child.Unmatched,...
'ButtonDownFcn',@PanelButtonDownFcn);
end
%inputParser class is poorly behaved, manually free mem
clear child
%Create togglebutton in the panel. Togglebutton will lock panel in
%place when activated, and release the panel when deactivated.
uicontrol(ObjHandle,...
'Style','togglebutton',...
'Units','pixels',...
'BackgroundColor','red',...
'Position',[4 2 9 9],...
'ToolTipString','Lock',...
'Tag','Lock',...
'Callback',@LockPanelCallback);
%% Begin callbacks and guipanel subfcns
function PanelButtonDownFcn(src,event)
uistack(src,'top') %just in case
if ~get(findobj('Parent',src,...
'Type','uicontrol',...
'Style','togglebutton',...
'Tag','Lock'),'Value')
%Get parent container of src
parent = get(src,'Parent');
%Get parent figure of src
fig = ancestor(src,'figure');
%Sync units
old_units = SyncUnits([0,src,parent,fig],'pixels');
%Position of src relative to parent figure
pos_r2fig = getpixelposition(src,true);
%Position of src relative to parent container
pos_r2par = getpixelposition(src);
%Position of pointer relative to parent figure
point_r2fig = Root2Fig(get(0,'PointerLocation'),fig);
[epsX,epsY] = MakeEpsilon(point_r2fig,pos_r2fig);
if any(abs(epsX) <= 10) || any(abs(epsY) <= 10)
%RESIZE
set(fig,'Pointer','fleur')
if fig == parent
position = rbbox(pos_r2par);
setpixelposition(src,position)
else
position = rbbox(pos_r2fig);
setpixelposition(src,position,true)
end
else
%MOVE
set(fig,'Pointer','fleur')
if fig == parent
position = dragrect(pos_r2fig);
setpixelposition(src,position)
else
position = dragrect(pos_r2fig);
setpixelposition(src,position,true)
end
end
set(fig,'Pointer','arrow')
UnSyncUnits(old_units)
refresh(fig)
end
end
function LockPanelCallback(src,event)
if get(src,'Value')
set(src,'BackgroundColor','white')
else
set(src,'BackgroundColor','red')
end
end
varargout{1} = struct(...
'Root2Fig',@Root2Fig,...
'Parent2Child',@Parent2Child,...
'MakeEpsilon',@MakeEpsilon,...
'MakeRect',@MakeRect,...
'SyncUnits',@SyncUnits,...
'UnSyncUnits',@UnSyncUnits,...
'OverlapingAt',@OverlappingAt);
end
%% Utility Functions -----------------------------------------------------%
%
function coords = Root2Fig(point, fig)
% Transforms point from root-centric to child-centric coordinates.
% Point is measured in the parent's reference frame
if fig == 0
coords = point;
return
end
oldUnits = SyncUnits([0,fig],'Pixels');
%Get position of user specified point relative to the Root
PxRoot = point(1); PyRoot = point(2);
%Get position of Figure relative to the Root
figloc = get(fig,'Position');
FigOriginX = figloc(1); FigOriginY = figloc(2);
%Send cursor coordinates from Root to Figure reference frame:
PxRoot2Fig = (PxRoot - FigOriginX); PyRoot2Fig = (PyRoot - FigOriginY);
%Clean up and return result
coords = [PxRoot2Fig,PyRoot2Fig];
UnSyncUnits(oldUnits)
end
function coords = Parent2Child(point, child)
% Transforms point from parent-centric to child-centric coordinates.
% Point is measured in the parent's reference frame
parent = ancestor(child,{'figure','uipanel'});
fig = ancestor(child,'figure');
oldUnits = SyncUnits([0,fig,parent,child],'pixels');
%Root->Figure
r2f = Root2Fig(point, fig);
%Parent's position in Figure frame
if (parent ~= 0) && (parent ~= fig)
parent_position = getpixelposition(parent,true);
x = r2f(1) - parent_position(1);
y = r2f(2) - parent_position(2);
coords = [x,y];
else
coords = r2f;
end
UnSyncUnits(oldUnits)
end
function varargout = MakeEpsilon(point, position_vector)
%Determine a position vector's left/right & top/bottom distances from a point
%
% Calling syntax:
% [epsX,epsY] = MakeEpsilon(point, vector)
% [X_right,X_left,Y_bottom,Y_top] = MakeEpsilon(point,vector)
% Where point is 1x2
% or
%
% [X_right,X_left,Y_bottom,Y_top] = MakeEpsilon(vector1,vector2)
% Where vector1 is 1x4xN, and vector2 is 1x4. In the case that
% N > 1, each of X_right, X_left, Y_bottom, and Y_top will be
% 1x1xN.
%
% If epsX(1) <= 0 && epsX(2) >= 0 then px is inside the
% horizontal boundaries.
%
% If epsY(1) <= 0 && epsY(2) >= 0 then py is inside the vertical
% boundaries.
%
assert(nargin == 2,'Too few/Too many inputs, MakeEpsilon requires two input args')
if nargout
%assert((nargout == 2) || (nargout == 4), 'MakeEpsilon takes either two or four output args')
end
sz = size(point);
switch mat2str(sz)
case '[1 2]'
[epsX,epsY] = epsilonPointSubFcn(point, position_vector);
varargout{1} = epsX; varargout{2} = epsY;
return
case '[1 4]'
[X_right,X_left,Y_bottom,Y_top] = epsilonVectorSubFcn(point,position_vector);
otherwise
try
[X_right,X_left,Y_bottom,Y_top] = epsilonVectorSubFcn(point,position_vector);
catch ME
ME.message
error('guiSubFcn:MakeEpsilon','Cannot reconcile input')
end
end
varargout{1} = X_right; varargout{2} = X_left;
varargout{3} = Y_bottom; varargout{4} = Y_top;
function varargout = epsilonPointSubFcn(pnt, pos)
%Do horz
leftXlim = pos(1)-pnt(:,1);
rightXlim = pos(1)+pos(3)-pnt(:,1);
epsX = [leftXlim, rightXlim];
%Do vert
bottomYlim = pos(2)-pnt(:,2);
topYlim = pos(2)+pos(4)-pnt(:,2);
epsY = [bottomYlim, topYlim];
%Parse outputs
if nargout ==2
varargout{1} = epsX; varargout{2} = epsY;
else
varargout{1} = epsX(:,1); varargout{2} = epsX(:,2);
varargout{3} = epsY(:,1); varargout{4} = epsY(:,2);
end
end
function varargout = epsilonVectorSubFcn(pos1,pos2)
[rectX,rectY] = MakeRect(pos1); %todo, overload MakeRect to accept pos vecs
%rectX, rectY will each be 4x1
pnt_array = [rectX,rectY];
[X_right,X_left,Y_bottom,Y_top] = epsilonPointSubFcn(pnt_array, pos2);
varargout{1} = X_right; varargout{2} = X_left;
varargout{3} = Y_bottom; varargout{4} = Y_top;
end
end
function [rectX,rectY] = MakeRect(position_vector)
%MakeRect takes a position vector and creates X,Y vertices for the
%corresponding rectangle. The position vector can be a (1,4,N)
%multidimensional array.
bottomLeft = [position_vector(:,1,:),position_vector(:,2,:)];
bottomRight = [position_vector(:,1,:)+position_vector(:,3,:),position_vector(:,2,:)];
topRight = [position_vector(:,1,:)+position_vector(:,3,:),position_vector(:,2,:)+position_vector(:,4,:)];
topLeft = [position_vector(:,1,:),position_vector(:,2,:)+position_vector(:,4,:)];
coords = [bottomLeft; bottomRight; topRight; topLeft];
rectX = coords(:,1,:); rectY = coords(:,2,:);
end
function oldHandleUnits = SyncUnits(handles, trgt_units)
% oldUnits is a n x 2 cell array where oldUnits{:,1} == handles
% and oldUnits{:,2} == "Units"
if nargout < 1
warning('utilityFcns:reconcileUnits',...
'old units not recorded, new units still set')
elseif nargout > 1
error('Too many output arguments')
elseif nargin < 2 || ~ischar(trgt_units)
error('Target units not specified')
end
[m,n] = size(handles);
handles = reshape(handles,m*n,1); % Turn handles into column vector
oldUnits = get(handles,'Units');
handles_cell = mat2cell(handles, ones(1,m*n), 1); % Turn handles into cell
oldHandleUnits = [handles_cell, oldUnits];
%Set new units last as a precautionary -- if exception arises at
%least we might recover the old units.
try
set(handles, 'Units', trgt_units)
catch ME
ME.message
error('Unable to set units')
end
end
function UnSyncUnits(old_handle_units)
arrayfun(@(k)set(old_handle_units{k,1},'Units',old_handle_units{k,2}),...
1:1:size(old_handle_units,1))
end
function [adjacencyMatrix,varargout] = OverlappingAt(position_vector)
%Returns the adjacency matrix indicating the overlap of any of N rectangles
%as specified by the Nx4 array of position vectors, position_vector. If
%called with two outputs, the second output is matrix where the (i,j)th
%element is the area of overlap between position_vector(i,:) and position_vector(j,:).
%
area = rectint(position_vector,position_vector);
density = nnz(area) / numel(area);
if density <= 0.4
area = sparse(area);
end
area = area - diag(diag(area)); %removes self-reference
if nargout == 2
varargout{1} = area;
end
adjacencyMatrix = (area ~= 0);
end