Skip to content
Snippets Groups Projects
uiGridLayout.m 11.59 KiB
classdef uiGridLayout < handle
%UIGRIDLAYOUT Calculates UI component position
%
%  obj = UIGRIDLAYOUT('PARENT', handle) class constructor based on
%  parent graphics position.
%
%  obj = UIGRIDLAYOUT(..., 'VGRID', varnum) set vertical grid count
%
%  obj = UIGRIDLAYOUT(..., 'HGRID', varnum) set horizontal grid count
%  
%  obj = UIGRIDLAYOUT(..., 'VGAP', varvector) set vertical gap offset,
%  varvector can be 1x1, 1x2 or 1x3, defining top, middle and bottom offset
%
%  obj = UIGRIDLAYOUT(..., 'HGAP', varvector) set horizontal gap offset,
%  varvector can be 1x1, 1x2 or 1x3, defining left, center and right offset
%
%  obj.getGrid() method to return default 1,1 grid box position
%
%  obj.getGrid('VIndex', varvector) specify indexes of grid boxes to
%  include in vertical position, max(varvector) <= VGRID
%
%  obj.getGrid('HIndex', varvector) specify indexes of grid boxes to
%  include in horizontal position, max(varvector) <= HGRID
%
%  obj.align(handle) aligns given handle that is a child uicontrol in the
%  grid.
%
%  obj.align(...,'VIndex', varvector, 'HIndex', varvector) specify vertical
%  and horizontal grid position to align the handle
%
%  obj.align(...,'Anchor', varchar) specify the method of alignment.
%  VARCHAR can be center(default), north, south, west, east, northwest,
%  northeast, southwest, southeast
%
% Georgi Tushev
% Max-Planck Institute for Brain Research
% sciclist@brain.mpg.de
%


    properties
        parent
        
        verGrid_Count
        verGrid_Pos
        verGrid_Size
        verGrid_GapTop
        verGrid_GapMiddle
        verGrid_GapBottom
        
        horGrid_Count
        horGrid_Pos
        horGrid_Size
        horGrid_GapLeft
        horGrid_GapCenter
        horGrid_GapRight
        
    end
    
    methods
        
        % method :: uiGridLaout
        %  input :: varargin
        % action :: class constructor
        function obj = uiGridLayout(varargin)
            
            % use parser class
            parseObj = inputParser;
            addParameter(parseObj, 'Parent', [],@isgraphics);
            addParameter(parseObj, 'VGrid', 1, @isnumeric);
            addParameter(parseObj, 'HGrid', 1, @isnumeric);
            addParameter(parseObj, 'VGap', [0, 0, 0], @isgap);
            addParameter(parseObj, 'HGap', [0, 0, 0], @isgap);
            parse(parseObj, varargin{:});
            
            % parse main properties
            obj.parent = parseObj.Results.Parent;
            obj.verGrid_Count = parseObj.Results.VGrid;
            obj.horGrid_Count = parseObj.Results.HGrid;
            
            % parse gap properties
            obj.setGridGap(parseObj.Results.VGap, 'vertical');
            obj.setGridGap(parseObj.Results.HGap, 'horizontal');
            
            % calculate grid size
            obj.setGridSize();
            
            % calulate grid position
            obj.setGridPosition();
            
        end
        
        % method :: getGrid
        %  input :: class object, varargin
        % action :: calculate grid coordinates
        function grid = getGrid(obj, varargin)
            
            % use parser
            parseObj = inputParser;
            addParameter(parseObj, 'VIndex', 1, @(x) validateattributes(x,{'double'},{'vector'}));
            addParameter(parseObj, 'HIndex', 1, @(x) validateattributes(x,{'double'},{'vector'}));
            parse(parseObj, varargin{:});
            
            % set each grid index
            gridVIndex = parseObj.Results.VIndex;
            if isempty(gridVIndex) || (max(gridVIndex) > obj.verGrid_Count)
                error('uiGridLayout:gridVIndex','grid index outside gird.');
            end
            
            gridHIndex = parseObj.Results.HIndex;
            if isempty(gridHIndex) || (max(gridHIndex) > obj.horGrid_Count)
                error('uiGridLayout:gridHIndex','grid index outside gird.');
            end
            
            % calculate temp grid
            grid(1) = obj.horGrid_Pos(gridHIndex(1));
            grid(2) = obj.verGrid_Pos(gridVIndex(end));
            grid(3) = length(gridHIndex) * obj.horGrid_Size + (length(gridHIndex) - 1) * obj.horGrid_GapCenter;
            grid(4) = length(gridVIndex) * obj.verGrid_Size + (length(gridVIndex) - 1) * obj.verGrid_GapMiddle;
            
        end
        
        % method :: align
        %  input :: class object, handle, varargin
        % action :: align handle to grid
        function obj = align(obj, handle, varargin)
            
            % use parser
            parseObj = inputParser;
            addParameter(parseObj, 'VIndex', 1, @(x) validateattributes(x,{'double'},{'vector'}));
            addParameter(parseObj, 'HIndex', 1, @(x) validateattributes(x,{'double'},{'vector'}));
            addParameter(parseObj, 'Anchor', 'center', @isanchor);
            parse(parseObj, varargin{:});
            
            % set vars
            VIndex = parseObj.Results.VIndex;
            HIndex = parseObj.Results.HIndex;
            anchor = parseObj.Results.Anchor;
            
            % check if handle's parent is the uiGridLayout parent
            if get(handle, 'Parent') ~= obj.parent
                error('uiGridLayout:HandleMismatch','given handle has different parent.');
            end
            
            % get constraints
            if isgraphics(handle, 'uicontrol') && strcmp(get(handle,'style'),'text')
                position = get(handle, 'Extent');
            else
                position = get(handle, 'Position');
            end
            VMax = position(4);
            HMax = position(3);
            
            % get current grid
            tempgrid = obj.getGrid('VIndex',VIndex, 'HIndex', HIndex);
            
            % update size
            if VMax < tempgrid(4)
                grid(4) = VMax;
            else
                grid(4) = tempgrid(4);
            end
            
            if HMax < tempgrid(3)
                grid(3) = HMax;
            else
                grid(3) = tempgrid(3);
            end
            
            % update position
            % set final grid position
            switch lower(anchor)
    
                case 'center'
        
                    grid(1) = tempgrid(1) + (tempgrid(3) - grid(3))/2;
                    grid(2) = tempgrid(2) + (tempgrid(4) - grid(4))/2;
        
                case 'north'
        
                    grid(1) = tempgrid(1) + (tempgrid(3) - grid(3))/2;
                    grid(2) = tempgrid(2) + (tempgrid(4) - grid(4));
        
                case 'south'
        
                    grid(1) = tempgrid(1) + (tempgrid(3) - grid(3))/2;
                    grid(2) = tempgrid(2);
        
                case 'west'
        
                    grid(1) = tempgrid(1);
                    grid(2) = tempgrid(2) + (tempgrid(4) - grid(4))/2;
        
                case 'east'
        
                    grid(1) = tempgrid(1) + (tempgrid(3) - grid(3));
                    grid(2) = tempgrid(2) + (tempgrid(4) - grid(4))/2;
        
                case 'northwest'
        
                    grid(1) = tempgrid(1);
                    grid(2) = tempgrid(2) + (tempgrid(4) - grid(4));
        
                case 'northeast'
        
                    grid(1) = tempgrid(1) + (tempgrid(3) - grid(3));
                    grid(2) = tempgrid(2) + (tempgrid(4) - grid(4));
        
                case 'southwest'
        
                    grid(1) = tempgrid(1);
                    grid(2) = tempgrid(2);
        
                case 'southeast'
        
                    grid(1) = tempgrid(1) + (tempgrid(3) - grid(3));
                    grid(2) = tempgrid(2);
        
            end
            
            % update handle position
            set(handle, 'Position', grid);
            
        end
        
        % method :: setGridPosition
        %  input :: class object
        % action :: calculate grid position
        function obj = setGridPosition(obj)
            
            % calculate grid position
            obj.verGrid_Pos = flipud(cumsum(...
                              [obj.verGrid_GapBottom;...
                               repmat(obj.verGrid_Size + obj.verGrid_GapMiddle,...
                                      obj.verGrid_Count - 1, 1)]));
            obj.horGrid_Pos = cumsum(...
                              [obj.horGrid_GapLeft;...
                               repmat(obj.horGrid_Size + obj.horGrid_GapCenter,...
                                      obj.horGrid_Count -1, 1)]);
            
        end
        
        % method :: setGridSize
        %  input :: class object
        % action :: calculate grid size
        function obj = setGridSize(obj)
            
            % get parent position
            position = get(obj.parent, 'Position');
    
            % calculate grid size
            obj.verGrid_Size = (position(4) - ...
                               obj.verGrid_GapTop - ...
                               obj.verGrid_GapMiddle * (obj.verGrid_Count - 1) - ...
                               obj.verGrid_GapBottom) / obj.verGrid_Count;
                           
            
            obj.horGrid_Size = (position(3) - ...
                               obj.horGrid_GapLeft - ...
                               obj.horGrid_GapCenter * (obj.horGrid_Count - 1) - ...
                               obj.horGrid_GapRight) / obj.horGrid_Count;               
            
        end
        
        
        % method :: setGridGap
        %  input :: class object, gap, direction
        % action :: assign gap based on provided values and direction
        function obj = setGridGap(obj, gap, direction)
            
            % distribute gaps based on 
            % provided values
            switch length(gap)
                case 1
                    
                    maxGap = gap;
                    medGap = gap;
                    minGap = gap;
                    
                case 2
                    
                    maxGap = gap(1);
                    medGap = gap(2);
                    minGap = gap(1);
                    
                case 3
                    
                    maxGap = gap(1);
                    medGap = gap(2);
                    minGap = gap(3);
                    
            end
            
            % assign based on direction
            if strcmpi('vertical', direction)
                
                obj.verGrid_GapTop = maxGap;
                obj.verGrid_GapMiddle = medGap;
                obj.verGrid_GapBottom = minGap;
                
            elseif strcmpi('horizontal', direction)
                
                obj.horGrid_GapLeft = maxGap;
                obj.horGrid_GapCenter = medGap;
                obj.horGrid_GapRight = minGap;
                
            end
            
        end
    end
    
end

%%% --- parser functions --- %%%
function tf = isanchor(varchar)

    % default output
    tf = true;
    
    % check class
    if ~ischar(varchar)
        tf = false;
    end
    
    % check if in list
    reflist = {'center',...
               'north',...
               'south',...
               'west',...
               'east',...
               'northwest',...
               'northeast',...
               'southwest',...
               'souteast'};
    
    if ~any(strcmpi(varchar, reflist))
        tf = false;
    end
                              

end


function tf = isgap(varvalue)

    % default output
    tf = true;
    
    % check class
    if ~isa(varvalue, 'double')
        tf = false;
    end
    
    % check dimension
    if isempty(varvalue) || (length(varvalue) > 3)
        tf = false;
    end

    % check for full number
    if any(rem(varvalue,1))
        tf = false;
    end
    
end