diff --git a/uiGridLayout.m b/uiGridLayout.m new file mode 100644 index 0000000000000000000000000000000000000000..17700ced72f80b2564940ee592bb4fe5b5b45f82 --- /dev/null +++ b/uiGridLayout.m @@ -0,0 +1,364 @@ +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