www.gusucode.com > GUI的布局工具箱 > GUI的布局工具箱/GUI的布局工具箱/GUILayout-v1p10/+uiextras/GridFlex.m
classdef GridFlex < uiextras.Grid %GridFlex Container with contents arranged in a resizable grid % % obj = uiextras.GridFlex() creates a new new grid layout with % draggable dividers between elements. The number of rows and columns % to use is determined from the number of elements in the RowSizes % and ColumnSizes properties respectively. Child elements are % arranged down column one first, then column two etc. If there are % insufficient columns then a new one is added. The output is a new % layout object that can be used as the parent for other % user-interface components. The output is a new layout object that % can be used as the parent for other user-interface components. % % obj = uiextras.GridFlex(param,value,...) also sets one or more % parameter values. % % See the <a href="matlab:doc uiextras.GridFlex">documentation</a> for more detail and the list of properties. % % Examples: % >> f = figure(); % >> g = uiextras.GridFlex( 'Parent', f, 'Spacing', 5 ); % >> uicontrol( 'Parent', g, 'Background', 'r' ) % >> uicontrol( 'Parent', g, 'Background', 'b' ) % >> uicontrol( 'Parent', g, 'Background', 'g' ) % >> uiextras.Empty( 'Parent', g ) % >> uicontrol( 'Parent', g, 'Background', 'c' ) % >> uicontrol( 'Parent', g, 'Background', 'y' ) % >> set( g, 'ColumnSizes', [-1 100 -2], 'RowSizes', [-1 -2] ); % % See also: uiextras.Grid % uiextras.HBoxFlex % uiextras.VBoxFlex % uiextras.Empty % Copyright 2009-2010 The MathWorks, Inc. % $Revision: 366 $ % $Date: 2011-02-10 15:48:11 +0000 (Thu, 10 Feb 2011) $ properties ShowMarkings = 'on' % Show markings on the draggable dividers [ on | off ] end % public methods properties( Access = private ) RowDividers = [] SelectedRowDivider = -1 ColumnDividers = [] SelectedColumnDivider = -1 BlockRedraw = false end % private properties methods function obj = GridFlex( varargin ) %GridFlex Container with contents in a grid and movable dividers % First step is to create the parent class. We pass the % arguments (if any) just incase the parent needs setting obj@uiextras.Grid( varargin{:} ); % Set some defaults obj.setPropertyFromDefault( 'ShowMarkings' ); % Set user-supplied property values if nargin > 0 set( obj, varargin{:} ); end end % constructor end % public methods methods function set.ShowMarkings( obj, value ) % Check if ~ischar( value ) || ~ismember( lower( value ), {'on','off'} ) error( 'GUILayout:InvalidPropertyValue', ... 'Property ''ShowMarkings'' may only be set to ''on'' or ''off''.' ) end % Apply obj.ShowMarkings = lower( value ); obj.redraw(); end % set.ShowMarkings end % accessor methods methods( Access = protected ) function redraw( obj ) %redraw Redraw container contents % Prevent recursive redraws if we need to add/remove/reorder % dividers if obj.BlockRedraw return; end obj.BlockRedraw = true; % First simply call the grid redraw [widths,heights] = redraw@uiextras.Grid(obj); rowSizes = obj.RowSizes; columnSizes = obj.ColumnSizes; padding = obj.Padding; spacing = obj.Spacing; pos0 = ceil( getpixelposition( obj.UIContainer ) ); % Now add the column dividers mph = uiextras.MousePointerHandler( obj.Parent ); numDynamic = 0; for ii = 1:numel(columnSizes)-1 if any(columnSizes(1:ii)<0) && any(columnSizes(ii+1:end)<0) numDynamic = numDynamic + 1; % Both dynamic, so add a divider position = [sum( widths(1:ii) ) + padding + spacing * (ii-1) + 1, ... padding + 1, ... max(1,spacing), ... max(1,pos0(4)-2*padding)]; % Create the divider widget if numDynamic > numel( obj.ColumnDividers ) obj.ColumnDividers(numDynamic) = uiextras.makeFlexDivider( ... obj.UIContainer, ... position, ... get( obj.UIContainer, 'BackgroundColor' ), ... 'Vertical', ... obj.ShowMarkings ); set( obj.ColumnDividers(numDynamic), ... 'ButtonDownFcn', @obj.onColumnButtonDown, ... 'Tag', 'UIExtras:GridFlex:ColumnDivider' ); % Add it to the mouse-over handler mph.register( obj.ColumnDividers(numDynamic), 'left' ); else % Just update an existing divider uiextras.makeFlexDivider( ... obj.ColumnDividers(numDynamic), ... position, ... get( obj.UIContainer, 'BackgroundColor' ), ... 'Vertical', ... obj.ShowMarkings ); end setappdata( obj.ColumnDividers(numDynamic), 'WhichDivider', ii ); end end % Remove any excess dividers if numel( obj.ColumnDividers ) > numDynamic delete( obj.ColumnDividers(numDynamic+1:end) ); obj.ColumnDividers(numDynamic+1:end) = []; end % Now add the row dividers numDynamic = 0; for ii = 1:numel(rowSizes)-1 if any(rowSizes(1:ii)<0) && any(rowSizes(ii+1:end)<0) numDynamic = numDynamic + 1; % Both dynamic, so add a divider position = [padding + 1, ... pos0(4) - sum( heights(1:ii) ) - padding - spacing*ii + 1, ... max(1,pos0(3)-2*padding), ... max(1,spacing)]; % Create the divider widget if numDynamic > numel( obj.RowDividers ) obj.RowDividers(numDynamic) = uiextras.makeFlexDivider( ... obj.UIContainer, ... position, ... get( obj.UIContainer, 'BackgroundColor' ), ... 'Horizontal', ... obj.ShowMarkings ); set( obj.RowDividers(numDynamic), 'ButtonDownFcn', @obj.onRowButtonDown, ... 'Tag', 'UIExtras:GridFlex:RowDivider' ); % Add it to the mouse-over handler mph.register( obj.RowDividers(numDynamic), 'top' ); else % Just update an existing divider uiextras.makeFlexDivider( ... obj.RowDividers(numDynamic), ... position, ... get( obj.UIContainer, 'BackgroundColor' ), ... 'Horizontal', ... obj.ShowMarkings ); end setappdata( obj.RowDividers(numDynamic), 'WhichDivider', ii ); end end % Remove any excess dividers if numel( obj.RowDividers ) > numDynamic delete( obj.RowDividers(numDynamic+1:end) ); obj.RowDividers(numDynamic+1:end) = []; end % We need to ensure dividers are above all other children so % that they receive mouse clicks c = allchild( obj.UIContainer ); tags = get(c,'Tag'); isDivider = strcmp( tags, 'UIExtras:GridFlex:RowDivider' ) ... | strcmp( tags, 'UIExtras:GridFlex:ColumnDivider' ); firstChild = find( ~isDivider, 1, 'first' ); lastDivider = find( isDivider, 1, 'last' ); if firstChild < lastDivider % We need to put the divider first set( obj.UIContainer, 'Children', [c(isDivider);c(~isDivider)] ); end obj.BlockRedraw = false; end % redraw function onRowButtonDown( obj, source, eventData ) %#ok<INUSD> figh = ancestor( source, 'figure' ); % Remove all column dividers ch = allchild( obj.UIContainer ); dividers = strcmpi( get( ch, 'Tag' ), 'GridFlex:ColumnDivider' ); delete( ch(dividers) ); % We need to store any existing motion callbacks so that we can % restore them later. oldProps = struct(); oldProps.WindowButtonMotionFcn = get( figh, 'WindowButtonMotionFcn' ); oldProps.WindowButtonUpFcn = get( figh, 'WindowButtonUpFcn' ); oldProps.Pointer = get( figh, 'Pointer' ); oldProps.Units = get( figh, 'Units' ); % Make sure all interaction modes are off to prevent our % callbacks being clobbered zoomh = zoom( figh ); r3dh = rotate3d( figh ); panh = pan( figh ); oldState = ''; if isequal( zoomh.Enable, 'on' ) zoomh.Enable = 'off'; oldState = 'zoom'; end if isequal( r3dh.Enable, 'on' ) r3dh.Enable = 'off'; oldState = 'rotate3d'; end if isequal( panh.Enable, 'on' ) panh.Enable = 'off'; oldState = 'pan'; end % Now hook up new callbacks set( figh, ... 'WindowButtonMotionFcn', @obj.onRowButtonMotion, ... 'WindowButtonUpFcn', {@obj.onRowButtonUp, oldProps, oldState}, ... 'Pointer', 'top', ... 'Units', 'Pixels' ); % Make the divider visible cdata = get( source, 'CData' ); if mean( cdata(:) ) < 0.5 % Make it brighter cdata = 1-0.5*(1-cdata); newCol = 1-0.5*(1-get( obj.UIContainer, 'BackgroundColor' )); else % Make it darker cdata = 0.5*cdata; newCol = 0.5*get( obj.UIContainer, 'BackgroundColor' ); end set( source, ... 'BackgroundColor', newCol, ... 'ForegroundColor', newCol, ... 'CData', cdata ); obj.SelectedRowDivider = source; end % onRowButtonDown function onRowButtonMotion( obj, source, eventData ) %#ok<INUSD> figh = ancestor( source, 'figure' ); cursorpos = get( figh, 'CurrentPoint' ); dividerpos = get( obj.SelectedRowDivider, 'Position' ); % We need to gaurd against the focus having been lost. In this % case we should have received a button-up event, but sometimes % don't (at least on Windows). if ishandle( obj.SelectedRowDivider ) pos0 = getpixelposition( obj.UIContainer, true ); dividerpos(2) = cursorpos(2) - pos0(2) - round(obj.Spacing/2) + 1; % Make sure that the position doesn't cause an element to % shrink too much minSizes = obj.MinimumRowSizes(:); pixSizes = uiextras.calculatePixelSizes( pos0(4), ... obj.RowSizes, minSizes, obj.Padding, obj.Spacing ); N = numel( minSizes ); % Sometimes the actual width is smaller than the minimum! minSizes = min( minSizes, pixSizes ); whichDivider = getappdata( obj.SelectedRowDivider, 'WhichDivider' ); minPos = pos0(4) - ceil( obj.Padding ... + sum( pixSizes(1:whichDivider-1) ) ... + minSizes(whichDivider) ... + obj.Spacing*(whichDivider-0.5) ); dividerpos(2) = min( dividerpos(2), minPos ); if whichDivider<(N-1) maxPos = floor( obj.Padding ... + sum( pixSizes(whichDivider+2:end) ) ... + minSizes(whichDivider+1) ... + obj.Spacing*(N-whichDivider-0.5) ); else % Final divider maxPos = floor( obj.Padding ... + minSizes(whichDivider+1) ... + obj.Spacing*0.5 ); end dividerpos(2) = max( dividerpos(2), maxPos ); set( obj.SelectedRowDivider, 'Position', dividerpos ); else % Divider has been lost, so we are in a bad state. The % best we can do is kill the callbacks and attempt to put % the figure back in a decent state. set( figh, 'Pointer', 'arrow', ... 'WindowButtonMotionFcn', [], ... 'WindowButtonUpFcn', [] ); end end % onRowButtonMotion function onRowButtonUp( obj, source, eventData, oldFigProps, oldState ) % Deliberately call the motion function to ensure any last % movement is captured obj.onRowButtonMotion( source, eventData ); % Restore figure properties figh = ancestor( source, 'figure' ); flds = fieldnames( oldFigProps ); for ii=1:numel(flds) set( figh, flds{ii}, oldFigProps.(flds{ii}) ); end % If the figure has an interaction mode set, re-set it now if ~isempty( oldState ) switch upper( oldState ) case 'ZOOM' zoom( figh, 'on' ); case 'PAN' pan( figh, 'on' ); case 'ROTATE3D' rotate3d( figh, 'on' ); otherwise error( 'GUILayout:InvalidState', 'Invalid interaction mode ''%s''.', oldState ); end end % Work out which divider was moved and which are the resizable % elements either side of it whichDivider = getappdata( obj.SelectedRowDivider, 'WhichDivider' ); origPos = getappdata( obj.SelectedRowDivider, 'OriginalPosition' ); newPos = get( obj.SelectedRowDivider, 'Position' ); obj.SelectedRowDivider = -1; delta = newPos(2) - origPos(2) - round(obj.Spacing/2) + 1; sizes = obj.RowSizes; % Convert all flexible sizes into pixel units totalPosition = ceil( getpixelposition( obj.UIContainer ) ); totalHeight = totalPosition(4); heights = uiextras.calculatePixelSizes( totalHeight, ... sizes, obj.MinimumRowSizes, obj.Padding, obj.Spacing ); topelement = find( sizes(1:whichDivider)<0, 1, 'last' ); bottomelement = find( sizes(whichDivider+1:end)<0, 1, 'first' )+whichDivider; % Now work out the new sizes. Note that we must ensure the size % stays negative otherwise it'll stop being resizable change = sum(sizes(sizes<0)) * delta / sum( heights(sizes<0) ); sizes(topelement) = min( -0.000001, sizes(topelement) - change ); sizes(bottomelement) = min( -0.000001, sizes(bottomelement) + change ); % Setting the sizes will cause a redraw obj.RowSizes = sizes; end % onRowButtonUp function onColumnButtonDown( obj, source, eventData ) %#ok<INUSD> % Remove all row dividers figh = ancestor( source, 'figure' ); ch = allchild( obj.UIContainer ); dividers = strcmpi( get( ch, 'Tag' ), 'GridFlex:RowDivider' ); delete( ch(dividers) ); % We need to store any existing motion callbacks so that we can % restore them later. oldProps = struct(); oldProps.WindowButtonMotionFcn = get( figh, 'WindowButtonMotionFcn' ); oldProps.WindowButtonUpFcn = get( figh, 'WindowButtonUpFcn' ); oldProps.Pointer = get( figh, 'Pointer' ); oldProps.Units = get( figh, 'Units' ); % Make sure all interaction modes are off to prevent our % callbacks being clobbered zoomh = zoom( figh ); r3dh = rotate3d( figh ); panh = pan( figh ); oldState = ''; if isequal( zoomh.Enable, 'on' ) zoomh.Enable = 'off'; oldState = 'zoom'; end if isequal( r3dh.Enable, 'on' ) r3dh.Enable = 'off'; oldState = 'rotate3d'; end if isequal( panh.Enable, 'on' ) panh.Enable = 'off'; oldState = 'pan'; end % Now hook up new callbacks set( figh, ... 'WindowButtonMotionFcn', @obj.onColumnButtonMotion, ... 'WindowButtonUpFcn', {@obj.onColumnButtonUp, oldProps, oldState}, ... 'Pointer', 'left', ... 'Units', 'Pixels' ); % Make the divider visible cdata = get( source, 'CData' ); if mean( cdata(:) ) < 0.5 % Make it brighter cdata = 1-0.5*(1-cdata); newCol = 1-0.5*(1-get( obj.UIContainer, 'BackgroundColor' )); else % Make it darker cdata = 0.5*cdata; newCol = 0.5*get( obj.UIContainer, 'BackgroundColor' ); end set( source, ... 'BackgroundColor', newCol, ... 'ForegroundColor', newCol, ... 'CData', cdata ); obj.SelectedColumnDivider = source; end % onColumnButtonDown function onColumnButtonMotion( obj, source, eventData ) %#ok<INUSD> figh = ancestor( source, 'figure' ); cursorpos = get( figh, 'CurrentPoint' ); % We need to gaurd against the focus having been lost. In this % case we should have received a button-up event, but sometimes % don't (at least on Windows). if ishandle( obj.SelectedColumnDivider ) dividerpos = get( obj.SelectedColumnDivider, 'Position' ); pos0 = getpixelposition( obj.UIContainer, true ); dividerpos(1) = cursorpos(1) - pos0(1) - round(obj.Spacing/2) + 1; % Make sure that the position doesn't cause an element to % shrink too much minSizes = obj.MinimumColumnSizes(:); pixSizes = uiextras.calculatePixelSizes( pos0(3), ... obj.ColumnSizes, minSizes, obj.Padding, obj.Spacing ); N = numel( minSizes ); % Sometimes the actual width is smaller than the minimum! minSizes = min( minSizes, pixSizes ); whichDivider = getappdata( obj.SelectedColumnDivider, 'WhichDivider' ); minPos = ceil( obj.Padding ... + sum( pixSizes(1:whichDivider-1) ) ... + minSizes(whichDivider) ... + obj.Spacing*(whichDivider-0.5) ); dividerpos(1) = max( dividerpos(1), minPos ); if whichDivider<(N-1) maxPos = pos0(3) - floor( obj.Padding ... + sum( pixSizes(whichDivider+2:end) ) ... + minSizes(whichDivider+1) ... + obj.Spacing*(N-whichDivider-0.5) ); else % Final divider maxPos = pos0(3) - floor( obj.Padding ... + minSizes(whichDivider+1) ... + obj.Spacing*0.5 ); end dividerpos(1) = min( dividerpos(1), maxPos ); set( obj.SelectedColumnDivider, 'Position', dividerpos ); else % Divider has been lost, so we are in a bad state. The % best we can do is kill the callbacks and attempt to put % the figure back in a decent state. set( figh, 'Pointer', 'arrow', ... 'WindowButtonMotionFcn', [], ... 'WindowButtonUpFcn', [] ); end end % onColumnButtonMotion function onColumnButtonUp( obj, source, eventData, oldFigProps, oldState ) figh = ancestor( source, 'figure' ); % Deliberately call the motion function to ensure any last % movement is captured obj.onColumnButtonMotion( source, eventData ); % Restore figure properties flds = fieldnames( oldFigProps ); for ii=1:numel(flds) set( figh, flds{ii}, oldFigProps.(flds{ii}) ); end % If the figure has an interaction mode set, re-set it now if ~isempty( oldState ) switch upper( oldState ) case 'ZOOM' zoom( figh, 'on' ); case 'PAN' zoom( figh, 'on' ); case 'ROTATE3D' rotate3d( figh, 'on' ); otherwise error( 'GUILayout:InvalidState', 'Invalid interaction mode ''%s''.', oldState ); end end % Work out which divider was moved and which are the resizable % elements either side of it whichDivider = getappdata( obj.SelectedColumnDivider, 'WhichDivider' ); origPos = getappdata( obj.SelectedColumnDivider, 'OriginalPosition' ); newPos = get( obj.SelectedColumnDivider, 'Position' ); obj.SelectedColumnDivider = -1; delta = newPos(1) - origPos(1) - round(obj.Spacing/2) + 1; sizes = obj.ColumnSizes; % Convert all flexible sizes into pixel units totalPosition = ceil( getpixelposition( obj.UIContainer ) ); totalWidth = totalPosition(3); widths = uiextras.calculatePixelSizes( totalWidth, ... sizes, obj.MinimumColumnSizes, obj.Padding, obj.Spacing ); leftelement = find( sizes(1:whichDivider)<0, 1, 'last' ); rightelement = find( sizes(whichDivider+1:end)<0, 1, 'first' )+whichDivider; % Now work out the new sizes. Note that we must ensure the size % stays negative otherwise it'll stop being resizable change = sum(sizes(sizes<0)) * delta / sum( widths(sizes<0) ); sizes(leftelement) = min( -0.000001, sizes(leftelement) + change ); sizes(rightelement) = min( -0.000001, sizes(rightelement) - change ); % Setting the sizes will cause a redraw obj.ColumnSizes = sizes; end % onColumnButtonUp function onBackgroundColorChanged( obj, source, eventData ) %#ok<INUSD> %onBackgroundColorChanged Callback that fires when the container background color is changed % % We need to make the dividers match the background, so redarw % them obj.redraw(); end % onChildRemoved end % protected methods end % classdef