classdef CardPanel < uiextras.Container
%CardPanel Show one element (card) from a list
%
% obj = uiextras.CardPanel() creates a new card panel which allows
% selection between the different child objects contained, making the
% selected child fill the space available and all other children
% invisible. This is commonly used for creating wizards or quick
% switching between different views of a single data-set.
%
% obj = uiextras.CardPanel(param,value,...) also sets one or more
% property values.
%
% See the <a href="matlab:doc uiextras.CardPanel">documentation</a> for more detail and the list of properties.
%
% Examples:
% >> f = figure();
% >> p = uiextras.CardPanel( 'Parent', f, 'Padding', 5 );
% >> uicontrol( 'Style', 'frame', 'Parent', p, 'Background', 'r' );
% >> uicontrol( 'Style', 'frame', 'Parent', p, 'Background', 'b' );
% >> uicontrol( 'Style', 'frame', 'Parent', p, 'Background', 'g' );
% >> p.SelectedChild = 2;
%
% See also: uiextras.Panel
% uiextras.BoxPanel
% uiextras.TabPanel
% Copyright 2009-2010 The MathWorks, Inc.
% $Revision: 380 $
% $Date: 2013-02-27 10:29:08 +0000 (Wed, 27 Feb 2013) $
properties
Callback = []
Padding = 0 % padding around contents (pixels)
end % public properties
properties ( Dependent = true, SetObservable = true )
SelectedChild % Which child is visible [+ve integer or empty]
end % dependent properties
properties ( SetAccess = private, GetAccess = private, Hidden = true )
SelectedChild_ = [] % the index of the child that is currently being shown
end % private properties
methods
function obj = CardPanel(varargin)
% First step is to create the parent class. We pass the
% arguments (if any) just incase the parent needs setting
obj = obj@uiextras.Container( varargin{:} );
% Set some defaults
obj.setPropertyFromDefault( 'Padding' );
% Set user-supplied property values (only if this is the leaf class)
if nargin>0 && isequal( class( obj ), 'uiextras.CardPanel' )
set( obj, varargin{:} );
end
obj.redraw();
end % CardPanel
end % public methods
methods
function value = get.SelectedChild( obj )
value = obj.SelectedChild_;
end % get.SelectedChild
function set.SelectedChild( obj, value )
% Check
if isempty( obj.Children )
obj.SelectedChild_ = [];
else
if ~isscalar( value ) || (round( value ) ~= value) || value < 0
error( 'GUILayout:InvalidPropertyValue', ...
'Property ''SelectedChild'' must be a positive integer.' )
elseif value > numel( obj.Children )
error( 'GUILayout:InvalidPropertyValue', ...
'Cannot select child %d of %d.', value, numel( obj.Children ) )
end
% Set
obj.SelectedChild_ = value;
end
% Redraw
obj.redraw();
end % set.SelectedChild
function set.Padding( obj, value )
% Check input
if ~isnumeric( value ) || ~isscalar( value ) || ...
~isreal( value ) || isnan( value ) || ~isfinite( value ) || ...
value < 0 || rem( value, 1 ) ~= 0
error( 'GUILayout:InvalidPropertyValue', ...
'Property ''Padding'' must be a nonnegative integer.' )
end
% All OK, so set it and redraw using the new value
obj.Padding = value;
obj.redraw();
end % set.Padding
end % accessor methods
methods ( Access = protected )
function redraw(obj)
%REDRAW redraw the contents
%
% Fort a card layout the only thing we really need to do is
% show one of the children filling the view
pos = getpixelposition( obj.UIContainer );
contentPos = [1 1 pos(3) pos(4)] + obj.Padding*[1 1 -2 -2];
obj.showSelectedChild( contentPos );
end % redraw
function showSelectedChild( obj, contentPos )
% Generic function for showing just one child
page_offset = 2500; % The amount by which widgets are moved when making invisible
C = obj.Children;
nC = numel(C);
if ~isempty( obj.SelectedChild )
% Set all to be invisible except current page
% We also have to move them offscreen to avoid problems with invisible
% panels and things blocking out visible ones (an HG bug?)
otherPages = 1:nC;
otherPages( otherPages==obj.SelectedChild ) = [];
for page=otherPages
oldunits = get( C(page), 'Units' );
set( C(page), 'Units', 'pixels' );
p = get(C(page), 'Position');
if p(1)<page_offset || p(2)<page_offset
newPosition = p + [page_offset page_offset 0 0];
obj.repositionChild( C(page), newPosition )
end
set( C(page), 'Units', oldunits );
end
% And put the selected one on view
obj.repositionChild( C(obj.SelectedChild), contentPos );
% Hack: to fix problems with axes, give them a wiggle
iWiggleAxes(C(obj.SelectedChild));
end
end % showSelectedChild
function onChildAdded( obj, source, eventData ) %#ok<INUSD>
%onChildAdded: Callback that fires when a child is added to a container.
% Select the new addition
C = obj.Children;
N = numel( C );
obj.SelectedChild = N;
end % onChildAdded
function onChildRemoved( obj, source, eventData ) %#ok<INUSL>
%onChildAdded: Callback that fires when a container child is destroyed or reparented.
% If the missing child is the selected one, select something else
if obj.SelectedChild >= eventData.ChildIndex
% Changing the selection will force a redraw
if isempty( obj.Children )
obj.SelectedChild = [];
else
obj.SelectedChild = max( 1, obj.SelectedChild - 1 );
end
else
% We don't need to change the selection, so explicitly
% redraw
obj.redraw();
end
end % onChildRemoved
end % protected methods
end % classdef
function iWiggleAxes(parent)
% Helper to give any axes inside the specified parent a "wiggle" (i.e.
% resize then resize back again).
ax = findall(parent, 'type', 'axes');
% Be careful to ignore legends and colorbars
for ii=1:numel(ax)
if ~isLegendOrColorbar(ax(ii))
propname = get(ax(ii),'ActivePositionProperty');
set(ax(ii),propname, get(ax(ii), propname)+[0 0 1 0]);
set(ax(ii),propname, get(ax(ii), propname)-[0 0 1 0]);
end
end
end
function result = isLegendOrColorbar( child )
% Determine whether an object is a legend or colorbar
child = handle(child);
result = (isa( child, 'axes' ) ...
&& ismember(lower(get( child, 'Tag' )), {'legend', 'colorbar'}) );
end % isLegendOrColorbar