classdef Grid < uix.Box
    %uix.Grid  Grid
    %
    %  b = uix.Grid(p1,v1,p2,v2,...) constructs a grid and sets parameter
    %  p1 to value v1, etc.
    %
    %  A grid lays out contents from top to bottom and left to right.
    %
    %  See also: uix.HBox, uix.VBox, uix.GridFlex
    
    %  Copyright 2009-2020 The MathWorks, Inc.
    
    properties( Access = public, Dependent, AbortSet )
        Widths % widths of contents, in pixels and/or weights
        MinimumWidths % minimum widths of contents, in pixels
        Heights % heights of contents, in pixels and/or weights
        MinimumHeights % minimum heights of contents, in pixels
    end
    
    properties( Access = protected )
        Widths_ = zeros( [0 1] ) % backing for Widths
        MinimumWidths_ = zeros( [0 1] ) % backing for MinimumWidths
        Heights_ = zeros( [0 1] ) % backing for Heights
        MinimumHeights_ = zeros( [0 1] ) % backing for MinimumHeights
    end
    
    methods
        
        function obj = Grid( varargin )
            %uix.Grid  Grid constructor
            %
            %  b = uix.Grid() constructs a grid.
            %
            %  b = uix.Grid(p1,v1,p2,v2,...) sets parameter p1 to value v1,
            %  etc.
            
            % Set properties
            try
                uix.set( obj, varargin{:} )
            catch e
                delete( obj )
                e.throwAsCaller()
            end
            
        end % constructor
        
    end % structors
    
    methods
        
        function value = get.Widths( obj )
            
            value = obj.Widths_;
            
        end % get.Widths
        
        function set.Widths( obj, value )
            
            % For those who can't tell a column from a row...
            if isrow( value )
                value = transpose( value );
            end
            
            % Check
            assert( isa( value, 'double' ), 'uix:InvalidPropertyValue', ...
                'Property ''Widths'' must be of type double.' )
            assert( all( isreal( value ) ) && ~any( isinf( value ) ) && ...
                ~any( isnan( value ) ), 'uix:InvalidPropertyValue', ...
                'Elements of property ''Widths'' must be real and finite.' )
            n = numel( obj.Contents_ );
            b = numel( obj.Widths_ );
            q = numel( obj.Heights_ );
            c = numel( value );
            r = ceil( n / c );
            if c < min( [1 n] )
                error( 'uix:InvalidPropertyValue' , ...
                    'Property ''Widths'' must be non-empty for non-empty contents.' )
            elseif ceil( n / r ) < c
                error( 'uix:InvalidPropertyValue' , ...
                    'Size of property ''Widths'' must not lead to empty columns.' )
            elseif c > n
                error( 'uix:InvalidPropertyValue' , ...
                    'Size of property ''Widths'' must be no larger than size of contents.' )
            end
            
            % Set
            obj.Widths_ = value;
            if c < b % number of columns decreasing
                obj.MinimumWidths_(c+1:end,:) = [];
                if r > q % number of rows increasing
                    obj.Heights_(end+1:r,:) = -1;
                    obj.MinimumHeights_(end+1:r,:) = 1;
                end
            elseif c > b % number of columns increasing
                obj.MinimumWidths_(end+1:c,:) = 1;
                if r < q % number of rows decreasing
                    obj.Heights_(r+1:end,:) = [];
                    obj.MinimumHeights_(r+1:end,:) = [];
                end
            end
            
            % Mark as dirty
            obj.Dirty = true;
            
        end % set.Widths
        
        function value = get.MinimumWidths( obj )
            
            value = obj.MinimumWidths_;
            
        end % get.MinimumWidths
        
        function set.MinimumWidths( obj, value )
            
            % For those who can't tell a column from a row...
            if isrow( value )
                value = transpose( value );
            end
            
            % Check
            assert( isa( value, 'double' ), 'uix:InvalidPropertyValue', ...
                'Property ''MinimumWidths'' must be of type double.' )
            assert( all( isreal( value ) ) && ~any( isinf( value ) ) && ...
                all( value >= 0 ), 'uix:InvalidPropertyValue', ...
                'Elements of property ''MinimumWidths'' must be non-negative.' )
            assert( isequal( size( value ), size( obj.Widths_ ) ), ...
                'uix:InvalidPropertyValue', ...
                'Size of property ''MinimumWidths'' must match size of contents.' )
            
            % Set
            obj.MinimumWidths_ = value;
            
            % Mark as dirty
            obj.Dirty = true;
            
        end % set.MinimumWidths
        
        function value = get.Heights( obj )
            
            value = obj.Heights_;
            
        end % get.Heights
        
        function set.Heights( obj, value )
            
            % For those who can't tell a column from a row...
            if isrow( value )
                value = transpose( value );
            end
            
            % Check
            assert( isa( value, 'double' ), 'uix:InvalidPropertyValue', ...
                'Property ''Heights'' must be of type double.' )
            assert( all( isreal( value ) ) && ~any( isinf( value ) ) && ...
                ~any( isnan( value ) ), 'uix:InvalidPropertyValue', ...
                'Elements of property ''Heights'' must be real and finite.' )
            n = numel( obj.Contents_ );
            b = numel( obj.Widths_ );
            q = numel( obj.Heights_ );
            r = numel( value );
            c = ceil( n / r );
            if r < min( [1 n] )
                error( 'uix:InvalidPropertyValue' , ...
                    'Property ''Heights'' must be non-empty for non-empty contents.' )
            elseif r > n
                error( 'uix:InvalidPropertyValue' , ...
                    'Size of property ''Heights'' must be no larger than size of contents.' )
            end
            
            % Set
            obj.Heights_ = value;
            if r < q % number of rows decreasing
                obj.MinimumHeights_(r+1:end,:) = [];
                if c > b % number of columns increasing
                    obj.Widths_(end+1:c,:) = -1;
                    obj.MinimumWidths_(end+1:c,:) = 1;
                end
            elseif r > q % number of rows increasing
                obj.MinimumHeights_(end+1:r,:) = 1;
                if c < b % number of columns decreasing
                    obj.Widths_(c+1:end,:) = [];
                    obj.MinimumWidths_(c+1:end,:) = [];
                end
            end
            
            % Mark as dirty
            obj.Dirty = true;
            
        end % set.Heights
        
        function value = get.MinimumHeights( obj )
            
            value = obj.MinimumHeights_;
            
        end % get.MinimumHeights
        
        function set.MinimumHeights( obj, value )
            
            % For those who can't tell a column from a row...
            if isrow( value )
                value = transpose( value );
            end
            
            % Check
            assert( isa( value, 'double' ), 'uix:InvalidPropertyValue', ...
                'Property ''MinimumHeights'' must be of type double.' )
            assert( all( isreal( value ) ) && ~any( isinf( value ) ) && ...
                all( value >= 0 ), 'uix:InvalidPropertyValue', ...
                'Elements of property ''MinimumHeights'' must be non-negative.' )
            assert( isequal( size( value ), size( obj.Heights_ ) ), ...
                'uix:InvalidPropertyValue', ...
                'Size of property ''MinimumHeights'' must match size of contents.' )
            
            % Set
            obj.MinimumHeights_ = value;
            
            % Mark as dirty
            obj.Dirty = true;
            
        end % set.MinimumHeights
        
    end % accessors
    
    methods( Access = protected )
        
        function redraw( obj )
            %redraw  Redraw
            %
            %  c.redraw() redraws the container c.
            
            % Compute positions
            bounds = hgconvertunits( ancestor( obj, 'figure' ), ...
                [0 0 1 1], 'normalized', 'pixels', obj );
            widths = obj.Widths_;
            minimumWidths = obj.MinimumWidths_;
            heights = obj.Heights_;
            minimumHeights = obj.MinimumHeights_;
            padding = obj.Padding_;
            spacing = obj.Spacing_;
            c = numel( widths );
            r = numel( heights );
            n = numel( obj.Contents_ );
            xSizes = uix.calcPixelSizes( bounds(3), widths, ...
                minimumWidths, padding, spacing );
            xPositions = [cumsum( [0; xSizes(1:end-1,:)] ) + padding + ...
                spacing * transpose( 0:c-1 ) + 1, xSizes];
            ySizes = uix.calcPixelSizes( bounds(4), heights, ...
                minimumHeights, padding, spacing );
            yPositions = [bounds(4) - cumsum( ySizes ) - padding - ...
                spacing * transpose( 0:r-1 ) + 1, ySizes];
            [iy, ix] = ind2sub( [r c], transpose( 1:n ) );
            positions = [xPositions(ix,1), yPositions(iy,1), ...
                xPositions(ix,2), yPositions(iy,2)];
            
            % Set positions
            children = obj.Contents_;
            for ii = 1:numel( children )
                uix.setPosition( children(ii), positions(ii,:), 'pixels' )
            end
            
        end % redraw
        
        function addChild( obj, child )
            %addChild  Add child
            %
            %  c.addChild(d) adds the child d to the container c.
            
            % Add column and even a row if necessary
            n = numel( obj.Contents_ );
            c = numel( obj.Widths_ );
            r = numel( obj.Heights_ );
            if n == 0
                obj.Widths_(end+1,:) = -1;
                obj.MinimumWidths_(end+1,:) = 1;
                obj.Heights_(end+1,:) = -1;
                obj.MinimumHeights_(end+1,:) = 1;
            elseif ceil( (n+1)/r ) > c
                obj.Widths_(end+1,:) = -1;
                obj.MinimumWidths_(end+1,:) = 1;
            end
            
            % Call superclass method
            addChild@uix.Box( obj, child )
            
        end % addChild
        
        function removeChild( obj, child )
            %removeChild  Remove child
            %
            %  c.removeChild(d) removes the child d from the container c.
            
            % Remove column and even row if necessary
            n = numel( obj.Contents_ );
            c = numel( obj.Widths_ );
            r = numel( obj.Heights_ );
            if n == 1
                obj.Widths_(end,:) = [];
                obj.MinimumWidths_(end,:) = [];
                obj.Heights_(end,:) = [];
                obj.MinimumHeights_(end,:) = [];
            elseif c == 1
                obj.Heights_(end,:) = [];
                obj.MinimumHeights_(end,:) = [];
            elseif ceil( (n-1)/r ) < c
                obj.Widths_(end,:) = [];
                obj.MinimumWidths_(end,:) = [];
            end
            
            % Call superclass method
            removeChild@uix.Box( obj, child )
            
        end % removeChild
        
    end % template methods
    
end % classdef