Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions src/ndi/+ndi/+gui/+component/ProgressBarWindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
% ndi.gui.component.ProgressBarWindow.isHeadless,
% ndi.gui.component.ProgressBarWindow.silentModeDefault

properties (Constant, Hidden)
BarMargin double = 5 % Pixels between a bar and the edges of its row (as R2024b drew it).
end

properties (Hidden)
ScreenFrac double = 0.025 % Fraction of screen height used per bar row.
IconClose char = fullfile(ndi.common.PathConstants.RootFolder,...
Expand Down Expand Up @@ -358,13 +362,31 @@
app.ProgressBars(barNum).Timer.Layout.Row = rowNum - 1;
app.ProgressBars(barNum).Timer.Layout.Column = 1:2;

% Add bar axes (background)
app.ProgressBars(barNum).Axes = uiaxes(app.ProgressGrid,...
% Add bar axes (background), inside a borderless panel that
% fills the grid cell. In a grid layout MATLAB places a uiaxes
% by its OUTER box and keeps room inside it for tick labels and
% a title, even hidden ones; R2026a keeps about 25 px, which
% left a 28 px row with a 2.7 px bar. Inside a plain panel the
% axes' inner box is placed exactly, BarMargin px in from each
% side -- the margin R2024b drew -- and kept there when the
% panel resizes. The panel is the axes' parent: it is what the
% grid lays out (removeBar moves and deletes it through
% Axes.Parent).
barPanel = uipanel(app.ProgressGrid,'BorderType','none',...
'BackgroundColor',app.ProgressGrid.BackgroundColor,...
'AutoResizeChildren','off'); % else SizeChangedFcn never runs
barPanel.Layout.Row = rowNum;
barPanel.Layout.Column = 1;
app.ProgressBars(barNum).Axes = uiaxes(barPanel,...
'XLim',[0 1],'YLim',[0 1],'XTick',[],'YTick',[],'Box','off',...
'XColor','none','YColor','none','Color','w','Interactions',[]);
app.ProgressBars(barNum).Axes.Toolbar.Visible = 'off';
app.ProgressBars(barNum).Axes.Layout.Row = rowNum;
app.ProgressBars(barNum).Axes.Layout.Column = 1;
app.ProgressBars(barNum).Axes.Units = 'pixels';
app.ProgressBars(barNum).Axes.PositionConstraint = 'innerposition';
barAxes = app.ProgressBars(barNum).Axes;
barPanel.SizeChangedFcn = @(panel,~) ...
ndi.gui.component.ProgressBarWindow.fitBarAxes(barAxes, panel);
ndi.gui.component.ProgressBarWindow.fitBarAxes(barAxes, barPanel);

% Add bar patch (foreground)
app.ProgressBars(barNum).Patch = patch(app.ProgressBars(barNum).Axes, ...
Expand Down Expand Up @@ -525,7 +547,7 @@
rowNum = app.ProgressBars(barNum).Label.Layout.Row + [0 1];

% Remove progress bar
delete([app.ProgressBars(barNum).Axes,...
delete([app.ProgressBars(barNum).Axes.Parent,... % the bar's panel, and its axes
app.ProgressBars(barNum).Percent,...
app.ProgressBars(barNum).Button,...
app.ProgressBars(barNum).Label,...
Expand All @@ -536,7 +558,7 @@
for i = 1:numel(openBars)
app.ProgressBars(openBars(i)).Label.Layout.Row = 2*i - 1;
app.ProgressBars(openBars(i)).Timer.Layout.Row = 2*i - 1;
app.ProgressBars(openBars(i)).Axes.Layout.Row = 2*i;
app.ProgressBars(openBars(i)).Axes.Parent.Layout.Row = 2*i;
app.ProgressBars(openBars(i)).Percent.Layout.Row = 2*i;
app.ProgressBars(openBars(i)).Button.Layout.Row = 2*i;
end
Expand Down Expand Up @@ -1043,6 +1065,21 @@ function bringToFront(app)
end

methods (Static)
function fitBarAxes(ax, panel)
%fitBarAxes Place a bar's axes BarMargin px inside its panel.
%
% ndi.gui.component.ProgressBarWindow.fitBarAxes(AX, PANEL)
% sets AX's inner box (pixels) to PANEL's area less BarMargin
% on each side, at least 1 x 1. addBar calls it once and as
% PANEL's SizeChangedFcn.
if ~isvalid(ax) || ~isvalid(panel)
return
end
m = ndi.gui.component.ProgressBarWindow.BarMargin;
pos = getpixelposition(panel);
ax.InnerPosition = [1 + m, 1 + m, max(pos(3) - 2*m, 1), max(pos(4) - 2*m, 1)];
end

function tf = isHeadless()
%isHeadless True when this MATLAB has no usable display.
%
Expand Down
36 changes: 35 additions & 1 deletion tests/+ndi/+unittest/+gui/+component/TestProgressBarWindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,40 @@ function testAddOneBar(testCase)
testCase.verifyNotEmpty(app.ProgressBars(1).Axes, 'Axes handle should not be empty.');
testCase.verifyNotEmpty(app.ProgressBars(1).Patch, 'Patch handle should not be empty.');
end

function testBarFillsItsRow(testCase)
% The bar's axes sit in a borderless panel that the grid lays
% out, the axes' INNER box BarMargin px inside it. In a grid cell
% a uiaxes keeps room for hidden tick labels; on R2026a that
% left a 2.7 px bar in a 28 px row.
app = ndi.gui.component.ProgressBarWindow('Fill Test', 'Visible', 'off');
testCase.addTeardown(@delete, app.ProgressFigure);
app.addBar('Label', 'Task 1', 'Tag', 'T1');
app.addBar('Label', 'Task 2', 'Tag', 'T2');
ax = app.ProgressBars(2).Axes;
testCase.verifyClass(ax.Parent, 'matlab.ui.container.Panel');
testCase.verifyEqual(ax.Parent.Layout.Row, 4, 'the panel takes the bar row');
testCase.verifyEqual(ax.PositionConstraint, 'innerposition');
testCase.verifyEqual(ax.Units, 'pixels');
testCase.verifyNotEmpty(ax.Parent.SizeChangedFcn, 'the bar follows its panel''s size');
testCase.verifyEqual(char(ax.Parent.AutoResizeChildren), 'off', ...
'SizeChangedFcn does not run while AutoResizeChildren is on');
% BarMargin px inside the panel on every side
m = ndi.gui.component.ProgressBarWindow.BarMargin;
drawnow
ndi.gui.component.ProgressBarWindow.fitBarAxes(ax, ax.Parent);
pos = getpixelposition(ax.Parent); % the grid sets the panel's size
testCase.verifyEqual(ax.InnerPosition, ...
[1+m, 1+m, max(pos(3)-2*m, 1), max(pos(4)-2*m, 1)], 'AbsTol', 1e-6);

% removing the first bar moves the second panel up and deletes
% the first bar's panel along with its axes
firstPanel = app.ProgressBars(1).Axes.Parent;
app.updateBar('T1', 1);
app.removeBar('T1');
testCase.verifyFalse(isvalid(firstPanel), 'the removed bar''s panel is deleted');
testCase.verifyEqual(ax.Parent.Layout.Row, 2, 'the remaining bar''s panel moves up');
end
function testAddMultipleBars(testCase)
app = ndi.gui.component.ProgressBarWindow('Multi Bar Test', 'Visible', 'off');
testCase.addTeardown(@delete, app.ProgressFigure);
Expand Down Expand Up @@ -248,7 +282,7 @@ function testRemoveBar(testCase)
testCase.verifyEqual(app.ProgressBars(2).State, 'Open', 'Remaining bar state incorrect.');
testCase.verifyNumElements(app.ProgressGrid.RowHeight, initialRowCount - 2, 'Grid rows not removed correctly.');
testCase.verifyEqual(app.ProgressBars(2).Label.Layout.Row, 1);
testCase.verifyEqual(app.ProgressBars(2).Axes.Layout.Row, 2);
testCase.verifyEqual(app.ProgressBars(2).Axes.Parent.Layout.Row, 2); % the bar's panel
% Verify properties of removed bar
testCase.verifyEqual(app.ProgressBars(1).Tag, 'TRemove', 'Removed bar is incorrect.');
testCase.verifyEqual(app.ProgressBars(1).State, 'Closed', 'Removed bar state incorrect.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ function testDockedBarRenders(testCase)

testCase.verifyEqual(app.ProgressBars(1).Progress, 0.5);
testCase.verifyEqual(app.ProgressBars(1).Percent.Text, '50%');
testCase.verifySameHandle(app.ProgressBars(1).Axes.Parent, testCase.Pane.BarGrid, ...
'Bar axes should be parented to the pane bar grid.');
% the axes sit in the bar's panel, which the pane's grid lays out
testCase.verifySameHandle(app.ProgressBars(1).Axes.Parent.Parent, testCase.Pane.BarGrid, ...
'Bar panel should be parented to the pane bar grid.');
end

function testCascadeMultipleBars(testCase)
Expand Down
Loading