classdef GuiLayout < handle
    %GUILAYOUT  Automatic positioning of GUI elements
    %
    % See also: maximize
    
    % Copyright (c) 2010-2014 Peter Fiala
    
    properties (Constant)
        Frame = 10;
        Separator = 5;
        MinFlex = 50;
        MaxFlex = Inf;
    end
    
    methods (Static)
        function ApplyLayout(h, l)
            %APPLYLAYOUT   Set Layout of GUI
            %   APPLYLAYOUT(H, L)  Sets the Layout L to the GUI under
            %   handle H.
            %
            % Examples
            %   f = figure;
            %   b1 = uicontrol('string', 'b1');
            %   b2 = uicontrol('string', 'b2');
            %   GuiLayout.ApplyLayout(f, {{b1 [100 20]}, {b2 [100 20]}});
            %   GuiLayout.ApplyLayout(f, {{b1 [100 20]}; {b2 [100 20]}});
            %   GuiLayout.ApplyLayout(f, {{b1 [-1 0]}, {b2 [-2 0]}});
            set(h, 'ResizeFcn', @(src,event)GuiLayout.ArrangeLayout(h,l));
            GuiLayout.ArrangeLayout(h, l);
        end
        
        function ArrangeLayout(h,l)
            fun = get(h, 'ResizeFcn');  % temporally remove Resize Function
            set(h, 'ResizeFcn', '');
            
            topOffset = 0;
            % if object is panel with title, topOffset needs to be increased
            if strcmp(get(h, 'Type'), 'uipanel')
                if ~isempty(get(h, 'Title'))
                    topOffset = 6;
                end
            end
            
            % minimal and maximal GUI size in x and y direction
            s = [GuiLayout.flexsiz(l, 1); GuiLayout.flexsiz(l, 2)]';
            
            % set GUI size between min and max
            p = GuiLayout.getposinpix(h);
            p(3:4) = p(3:4) - 2*GuiLayout.Frame - [0 topOffset];
            p(3:4) = max(p(3:4), min(s, [], 1));
            p(3:4) = min(p(3:4), max(s, [], 1));
            p(3:4) = p(3:4) + 2*GuiLayout.Frame + [0 topOffset];
            
            GuiLayout.setposinpix(h, p);
            
            GuiLayout.arr(l,...
                [GuiLayout.Frame, p(4)-GuiLayout.Frame-topOffset,...
                p(3)-2*GuiLayout.Frame, p(4)-2*GuiLayout.Frame-topOffset]);
            if any(diff(s) > 0)
                set(h, 'Resize', 'on');
            else
                set(h, 'Resize', 'off');
            end
            set(h, 'ResizeFcn', fun);   % Reset Resize function
        end
        
        function arr(l, p)
            %ARR Arrange GUI elements
            
            x0 = p(1:2);
            w = p(3:4);
            
            if iscell(l)
                n = numel(l);               % number of elements
                a = zeros(n,2);             % absolute size
                r = zeros(n,2);             % relative size
                for i = 1 : n
                    item = l{i};
                    
                    for dim = 1 : 2
                        q = item{2}(dim);         % size specifier
                        if q == 0       % keep original size
                            a(i,dim) = GuiLayout.siz(item{1},dim);
                            r(i,dim) = 0;
                        elseif q < 0    % use specifier as relative size
                            r(i,dim) = -q;
                            a(i,dim) = 0;
                        else            % use specifier as absolute size
                            r(i,dim) = 0;
                            a(i,dim) = q;
                        end
                    end
                end
                d1 = find(size(l) == 1, 1, 'first');
                d2 = 3 - d1;
                b = (-1)^d2;
                a(r(:,d1)~=0,d1) = (w(d1)-(n-1)*GuiLayout.Separator...
                    - sum(a(:,d1))) / sum(r(:,d1)) * r(r(:,d1)~=0, d1);
                a(r(:,d2)~= 0,d2) = w(d2);
                for i = 1 : n
                    GuiLayout.arr(l{i}{1}, [x0, a(i,:)]);
                    x0(d1) = x0(d1) + b * (a(i,d1) + GuiLayout.Separator);
                end
            else
                if ishandle(l)
                    GuiLayout.setposinpix(l, p - [0 w(2) 0 0]);
                end
            end
        end
        
        function s = siz(l, q)
            if iscell(l)
                s = zeros(size(l));
                for i = 1 : numel(l)
                    item = l{i};
                    if item{2}(q) <= 0
                        s(i) = GuiLayout.siz(item{1}, q);
                    else
                        s(i) = item{2}(q);
                    end
                end
                if size(l,q) == 1
                    s = sum(s) + (length(s)-1) * GuiLayout.Separator;
                else
                    s = max(s);
                end
            else
                if ishandle(l)
                    p = GuiLayout.getposinpix(l);
                    s = p(2+q);
                else
                    s = 0;
                end
            end
        end
        
        function s = flexsiz(l, q)
            if iscell(l)
                s = zeros(numel(l),2);
                r = zeros(numel(l),1);
                for i = 1 : numel(l)
                    item = l{i};
                    if item{2}(q) < 0
                        s(i,:) = GuiLayout.flexsiz(item{1}, q);
                        s(i,1) = max(s(i,1), GuiLayout.MinFlex);
                        s(i,2) = min(s(i,2), GuiLayout.MaxFlex);
                        r(i) = abs(item{2}(q));
                    elseif item{2}(q) > 0
                        s(i,:) = item{2}(q);
                    else
                        s(i,:) = GuiLayout.siz(item{1}, q);
                    end
                end
                r(r > 0) = r(r > 0) / min(r(r>0));
                if any(r > 0)
                    s(r > 0,:) = s(r > 0,:) .* repmat(r(r > 0), 1, 2);
                end
                if size(l,q) == 1
                    s = sum(s,1) + (length(s)-1) * GuiLayout.Separator;
                else
                    s = max(s,[],1);
                end
            else
                s = [GuiLayout.MinFlex, GuiLayout.MaxFlex];
            end
        end
        
        function p = getposinpix(h)
            %GETPOSINPIX(H, P) get position of a gui element in pixels
            
            % save current units
            u = get(h, 'units');
            % set units to pixels
            set(h, 'units', 'pixels');
            % get position
            if strcmpi(get(h, 'type'), 'axes')
                p = get(h, get(h, 'activepositionproperty'));
            else
                p = get(h, 'position');
            end
            % reset units
            set(h, 'units', u);
        end
        
        function setposinpix(h, p)
            %SETPOSINPIX(H, P) set position of a gui element to P where all
            %elements of P are measured in pixels
            
            type = get(h, 'Type');
            if strcmp(type, 'uitab')
                return;
            end
            
            % save current units
            u = get(h, 'units');
            % set units to pixels
            set(h, 'units', 'pixels');
            % set position
            if strcmpi(get(h, 'type'), 'axes')
                set(h, get(h, 'activepositionproperty'), p);
            else
                set(h, 'position', p);
            end
            % reset units
            set(h, 'units', u);
        end
        
        function MoveGuiRel(hGui, ref)
            if ishandle(ref)
                P0 = GuiLayout.getposinpix(ref);
                C = P0(1:2)+P0(3:4)/2;
            else
                switch lower(ref)
                    case 'cursor'
                        C = get(0, 'PointerLocation');
                    otherwise
                end
            end
            p = GuiLayout.getposinpix(hGui);
            p(1:2) = C-p(3:4)/2;
            GuiLayout.setposinpix(hGui, p);
        end
        
        function CloseToEscapeCallback(event)
            switch event.Key
                case 'escape'
                    close(gcbf);
            end
        end
    end
end
