diff --git a/resources/project/-NvNmnLN-CTaezFjMPFOnb8gLqY/X-gwN0u9Qo3fWErumQYWrvHW6b4d.xml b/resources/project/-NvNmnLN-CTaezFjMPFOnb8gLqY/X-gwN0u9Qo3fWErumQYWrvHW6b4d.xml
new file mode 100644
index 0000000..99772b4
--- /dev/null
+++ b/resources/project/-NvNmnLN-CTaezFjMPFOnb8gLqY/X-gwN0u9Qo3fWErumQYWrvHW6b4d.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/project/-NvNmnLN-CTaezFjMPFOnb8gLqY/X-gwN0u9Qo3fWErumQYWrvHW6b4p.xml b/resources/project/-NvNmnLN-CTaezFjMPFOnb8gLqY/X-gwN0u9Qo3fWErumQYWrvHW6b4p.xml
new file mode 100644
index 0000000..7b2a11a
--- /dev/null
+++ b/resources/project/-NvNmnLN-CTaezFjMPFOnb8gLqY/X-gwN0u9Qo3fWErumQYWrvHW6b4p.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/widgets/+wt/+abstract/BaseInternalDialog.m b/widgets/+wt/+abstract/BaseInternalDialog.m
index 30c36c2..beb9c17 100644
--- a/widgets/+wt/+abstract/BaseInternalDialog.m
+++ b/widgets/+wt/+abstract/BaseInternalDialog.m
@@ -7,6 +7,9 @@
% window. The dialog's lifecycle is tied to the app that launched it.
%
% This enables compatibility with web apps.
+ %
+ % The dialog may flicker when resizing the figure if
+ % AutoResizeChildren is on. Disabling this is recommended.
% ** This is a prototype component that may change in the future.
@@ -31,35 +34,12 @@
% Modal (block other figure interaction)
Modal (1,1) logical = false
- end %properties
-
-
- properties (AbortSet, Dependent, Access = public)
-
% Dialog Title
- Title
+ Title (1,1) string = ""
end %properties
- % Accessors
- methods
-
- function set.Modal(obj, value)
- obj.Modal = value;
- obj.updateModalImage();
- end
-
- function value = get.Title(obj)
- value = string(obj.OuterPanel.Title);
- end
- function set.Title(obj, value)
- obj.OuterPanel.Title = value;
- end
-
- end %methods
-
-
%% Dialog Button Properties
% The dialog subclass can change these values
properties (Dependent)
@@ -149,6 +129,17 @@
%% Internal Properties
+ properties (Hidden)
+
+ % Minimum allowable size before cropping
+ MinimumSize (1,2) double {mustBePositive} = [30 20];
+
+ % Buffer border space required on each side when sizing in figure
+ % Buffer (1,1) double {mustBeNonnegative} = 0
+
+ end %properties
+
+
properties (Transient, NonCopyable, Hidden, SetAccess = private)
% Outer grid to enable the panel to fill the component
@@ -187,6 +178,47 @@
end %properties
+ %% Constructor
+ methods
+
+ function obj = BaseInternalDialog(fig, varargin)
+
+ arguments
+ % Figure parent - Create a figure if not provided
+ fig (1,1) matlab.ui.Figure = uifigure("AutoResizeChildren","off");
+ end
+
+ arguments (Repeating)
+ % Property-value pairs
+ varargin
+ end
+
+ % Get the figure size
+ posF = getpixelposition(fig);
+ szFig = posF(3:4);
+
+ % Add modal image
+ modalImage = uiimage(fig);
+ modalImage.ImageSource = "overlay_gray.png";
+ modalImage.ScaleMethod = "stretch";
+ modalImage.Visible = "off";
+ modalImage.Position = [1 1 szFig];
+ modalImage.Tag = "ModalImage";
+
+ % Call superclass constructor
+ obj = obj@wt.abstract.BaseWidget(fig, varargin{:});
+
+ % Store the modal background image
+ obj.ModalImage = modalImage;
+
+ % Update the modal image positioning
+ obj.updateModalImage();
+
+ end %function
+
+ end %methods
+
+
%% Destructor
methods
function delete(obj)
@@ -211,58 +243,28 @@ function positionOver(obj, refComp)
% Reference component size and position
refPos = getpixelposition(refComp, true);
- refSize = refPos(3:4);
+ % refSize = refPos(3:4);
% Lower left corner depends if it's a figure
if isa(refComp, "matlab.ui.Figure")
- refCornerA = [1 1];
+ % refCornerA = [1 1];
+ refPos(1:2) = [1 1];
else
- refCornerA = refPos(1:2);
+ % refCornerA = refPos(1:2);
end
- % Dialog size
- dlgPos = getpixelposition(obj);
- dlgSize = dlgPos(3:4);
-
- % Does it fit entirely within the reference component?
- if all(refSize >= dlgSize)
- % Yes - center it over the component
-
- % Calculate lower-left corner
- dlgPos = floor((refSize - dlgSize) / 2) + refCornerA;
-
- else
- % NO - position within the figure
-
- % Get the corners of the figure (bottom left and top right)
- figPos = getpixelposition(obj.Parent);
- figSize = figPos(3:4);
-
- % Start with dialog position in lower-left of widget
- dlgPos = refCornerA;
- dlgCornerB = dlgPos + dlgSize;
-
- % Move left and down as needed to fit in figure
- adj = figSize - dlgCornerB;
- adj(adj>0) = 0;
- dlgPos = max(dlgPos + adj, [1 1]);
- dlgCornerB = dlgPos + dlgSize;
-
- % If it doesn't fit in the figure, shrink it
- adj = figSize - dlgCornerB;
- adj(adj>0) = 0;
- dlgSize = dlgSize + adj;
-
- end %if
-
- % Disable warning
- warnState = warning('off','MATLAB:ui:components:noPositionSetWhenInLayoutContainer');
+ % Dialog position
+ posNew = obj.Position;
- % Set final position
- obj.Position = [dlgPos dlgSize];
+ % Calculate the dialog position
+ % Request to center over refPos
+ posNew = calculatePositionWithinBounds(obj, posNew, refPos);
- % Restore warning
- warning(warnState)
+ % Update dialog position
+ if ~isequal(obj.Position, posNew)
+ fprintf(" Change position: posOld = %f posNew = %f\n", obj.Position, posNew);
+ obj.Position = posNew;
+ end
end %function
@@ -384,14 +386,8 @@ function assignOutput(~)
function setup(obj)
% Configure the dialog
- % Disable warning
- warnState = warning('off','MATLAB:ui:components:noPositionSetWhenInLayoutContainer');
-
- % Defaults
- obj.Position(3:4) = obj.Size;
-
- % Restore warning
- warning(warnState)
+ % Store the figure
+ obj.Figure = ancestor(obj,'figure');
% Outer grid to enable the dialog panel to fill the component
obj.OuterGrid = uigridlayout(obj,[1 1]);
@@ -404,7 +400,6 @@ function setup(obj)
obj.OuterPanel.FontWeight = "bold";
%obj.OuterPanel.BorderWidth = 1;
obj.OuterPanel.AutoResizeChildren = false;
- obj.OuterPanel.ResizeFcn = @(~,~)onOuterPanelResize(obj);
obj.OuterPanel.ButtonDownFcn = @(~,evt)onTitleButtonDown(obj,evt);
% Close Button
@@ -452,17 +447,9 @@ function setup(obj)
obj.applyCloseButtonColor()
% Listen to figure size changes
- obj.Figure = ancestor(obj,'figure');
obj.FigureResizeListener = listener(obj.Figure,"SizeChanged",...
@(~,evt)onFigureResized(obj,evt));
- % Add modal image
- obj.ModalImage = uiimage(obj.Figure);
- obj.ModalImage.ImageSource = "overlay_gray.png";
- obj.ModalImage.ScaleMethod = "stretch";
- obj.ModalImage.Visible = "off";
- obj.ModalImage.Position = [1 1 1 1];
-
% Add lower buttons
obj.DialogButtons = wt.ButtonGrid(obj.InnerGrid,"Text",[],"Icon",[]);
obj.DialogButtons.Layout.Row = 2;
@@ -471,40 +458,38 @@ function setup(obj)
obj.DialogButtons.ButtonPushedFcn = ...
@(src,evt)onDialogButtonPushed(obj,evt);
- % Ensure it fits in the figure
- obj.resizeToFitFigure();
-
- % Reposition the close button
- obj.repositionCloseButton();
-
- % Position over figure by default
- if isscalar(obj.Figure) && isvalid(obj.Figure)
- obj.positionOver(obj.Figure)
- end
-
% Update component lists
obj.ButtonColorableComponents = [obj.DialogButtons];
obj.TitleFontStyledComponents = [obj.OuterPanel];
obj.FontStyledComponents = [obj.DialogButtons];
- end %function
-
+ % Listen to resizing of OuterPanel
+ % This enables the close button to stay in the correct spot
+ obj.OuterPanel.ResizeFcn = @(~,~)onOuterPanelResize(obj);
- function postSetup(obj)
+ % Ensure it fits in the figure
+ obj.resizeToFitFigure();
- % Update modal image now
- obj.updateModalImage();
+ % Reposition the close button
+ repositionCloseButton(obj)
end %function
function update(obj)
- % Ensure it fits in the figure
- obj.resizeToFitFigure();
+ % Update title
+ if strlength(obj.Title)
+ obj.OuterPanel.Title = obj.Title;
+ else
+ obj.OuterPanel.Title = " ";
+ end
- % Reposition the close button
- obj.repositionCloseButton();
+ % Ensure it fits in the figure
+ % This is only needed if AutoResizeChildren is on
+ if obj.Figure.AutoResizeChildren
+ obj.resizeToFitFigure();
+ end
end %function
@@ -569,42 +554,92 @@ function checkDeletionCriteria(obj)
%% Private methods
methods (Access = private)
- function updateModalImage(obj)
- % Triggered when the Modal property is changed
+ function resizeToFitFigure(obj)
+ % Triggered on figure resize
+
+ % Update modal image
+ obj.updateModalImage();
+
+ % Get the current positioning
+ posNew = obj.Position;
+ % posLowerLeft = posOld(1:2);
- % Setup must be complete to run this code
- if ~obj.SetupFinished
- return
+ % Calculate the dialog size
+ szDlg = calculateDialogSize(obj);
+ posNew(3:4) = szDlg;
+
+ % Calculate the dialog position
+ if obj.SetupFinished
+ posNew = calculatePositionWithinBounds(obj, posNew);
+ else
+ % Try to center over figure by default
+ posFig = getpixelposition(obj.Figure);
+ posFig(1:2) = 1;
+ posNew = calculatePositionWithinBounds(obj, posNew, posFig);
end
- % If toggled on, do the following
- if obj.Modal
-
- % Bring the dialog above the modal image
- if isMATLABReleaseOlderThan("R2025a")
- isDlg = obj.Figure.Children == obj;
- isModalImage = obj.Figure.Children == obj.ModalImage;
- otherChild = obj.Figure.Children(~isDlg & ~isModalImage);
- obj.Figure.Children = vertcat(obj, obj.ModalImage, otherChild);
- else
- uistack(obj,"top"); % Works in 25a but not earlier
- end
+ % Update dialog position
+ if ~isequal(obj.Position, posNew)
+ obj.Position = posNew;
+ end
- % Set position to match the figure
- posF = getpixelposition(obj.Figure);
- szF = posF(3:4);
- obj.ModalImage.Position = [1 1 szF];
+ end %function
- end %if
- % Toggle visibility
- obj.ModalImage.Visible = obj.Modal;
+ function szDlg = calculateDialogSize(obj)
+ % Calculate the dialog size to use, given the set Size and
+ % figure constraints
+
+ % Get figure size
+ posFig = getpixelposition(obj.Figure);
+
+ % Calculate allowed dialog size
+ szDlg = max( min(obj.Size, posFig(3:4)), obj.MinimumSize);
+
+ end %function
+
+
+ function posOut = calculatePositionWithinBounds(obj, posIn, posCenter)
+ % Confirm and verify the position is within the figure bounds
+
+ arguments
+ obj (1,1) wt.abstract.BaseInternalDialog
+ posIn (1,4) double {mustBeFinite} %requested [x,y,w,h] location
+ posCenter (1,4) double = nan(1,4) %optional - center over this [x,y,w,h]
+ end
+
+ % Default output
+ posOut = posIn;
+
+ % Get figure size
+ figPos = getpixelposition(obj.Figure);
+ figSize = figPos(3:4);
+
+ % Center over a component? (optional posCenter)
+ if ~any(ismissing(posCenter))
+ centerPoint = floor(posCenter(1:2) + posCenter(3:4)/2);
+ posOut(1:2) = floor(centerPoint - posOut(3:4)/2);
+ end
+
+ % Ensure upper right corner is within the figure
+ dlgUpperRight = posOut(1:2) + posOut(3:4) - [1 1];
+ if any(dlgUpperRight > figSize)
+ dlgAdjust = dlgUpperRight - figSize;
+ dlgAdjust(dlgAdjust < 0) = 0;
+ posOut(1:2) = posOut(1:2) - dlgAdjust;
+ end
+
+ % Ensure lower left corner is within the figure
+ posOut(1:2) = max(posOut(1:2), [1 1]);
end %function
function repositionCloseButton(obj)
- % Triggered on figure resize
+ % Called at end of resize
+
+ % Get current position
+ oldPos = obj.CloseButton.Position;
% Outer panel inner/outer position
outerPos = obj.OuterPanel.OuterPosition;
@@ -624,53 +659,31 @@ function repositionCloseButton(obj)
yB = hO - 2*wBorder - hB - 1;
wB = hB;
xB = wO - 2*wBorder - wB - 1;
+ newPos = floor([xB yB wB hB]);
% Move the close button
- set(obj.CloseButton,"Position",[xB yB wB hB]);
+ if ~isequal(oldPos, newPos)
+ obj.CloseButton.Position = newPos;
+ end
end %function
- function resizeToFitFigure(obj)
- % Triggered on figure resize
+ function updateModalImage(obj)
+ % Update modal image size and visibility
- % Get the current positioning
- posD = obj.Position;
- szRequest = obj.Size;
- posLowerLeft = posD(1:2);
+ % Only run if ModalImage exists
+ if isscalar(obj.ModalImage) && isvalid(obj.ModalImage)
- % Get figure size
- posF = getpixelposition(obj.Figure);
- szF = posF(3:4);
- buffer = [20 20];
- maxSize = szF - buffer;
-
- % Size is the smaller of requested size and figure size with
- % buffer space
- szD = min(szRequest, maxSize);
-
- % Restrict a minimum size also
- minSize = [30 20];
- szD = max(szD, minSize);
-
- % Calculate fit within figure
- posUpperRight = posLowerLeft + szD;
- if any(posUpperRight > szF)
- posAdjust = szF - posUpperRight;
- posLowerLeft = posLowerLeft + posAdjust;
- end
+ % Set modal image position to match the figure
+ posF = getpixelposition(obj.Figure);
+ szFig = posF(3:4);
+ obj.ModalImage.Position = [1 1 szFig];
- % Don't go below 1
- posLowerLeft = max(posLowerLeft, 1);
+ % Toggle visibility
+ obj.ModalImage.Visible = obj.Modal;
- % Update modal image position
- if obj.Modal
- set(obj.ModalImage,"Position",[1 1 szF]);
- end
-
- % Update dialog position
- posNew = [posLowerLeft szD];
- set(obj,"Position",posNew);
+ end %if
end %function
@@ -754,20 +767,14 @@ function onFigureResized(obj,~)
% Ensure it fits in the figure
obj.resizeToFitFigure();
- % Reposition the close button
- obj.repositionCloseButton();
-
end %function
function onOuterPanelResize(obj)
% Triggered when the dialog window is resized
- % Ensure it fits in the figure
- obj.resizeToFitFigure();
-
% Reposition the close button
- obj.repositionCloseButton();
+ repositionCloseButton(obj)
end %function
diff --git a/widgets/+wt/+dialog/ListSelection.m b/widgets/+wt/+dialog/ListSelection.m
index 02da59d..da8277b 100644
--- a/widgets/+wt/+dialog/ListSelection.m
+++ b/widgets/+wt/+dialog/ListSelection.m
@@ -132,7 +132,7 @@ function setup(obj)
obj.Grid.ColumnWidth = {'1x'};
% Set title
- obj.Title = " ";
+ obj.Title = "";
% Add controls
obj.PromptLabel = uilabel(obj.Grid);
@@ -153,6 +153,9 @@ function setup(obj)
function update(obj)
+ % Call superclass method
+ obj.update@wt.abstract.BaseInternalDialog;
+
% Configure list
obj.ListBox.Items = obj.Items;
obj.ListBox.ItemsData = obj.ItemsData;
diff --git a/widgets/+wt/+dialog/Login.m b/widgets/+wt/+dialog/Login.m
index 8d73abb..1055a3d 100644
--- a/widgets/+wt/+dialog/Login.m
+++ b/widgets/+wt/+dialog/Login.m
@@ -23,6 +23,9 @@ function setup(obj)
% Defaults
obj.Size = [300,140];
+ % This is normally a modal dialog
+ obj.Modal = true;
+
% Configure which actions close the dialog
obj.DeleteActions = ["close","login","cancel"];
diff --git a/widgets/doc/DialogsList.mlx b/widgets/doc/DialogsList.mlx
new file mode 100644
index 0000000..cdd8db9
Binary files /dev/null and b/widgets/doc/DialogsList.mlx differ
diff --git a/widgets/doc/GettingStarted.mlx b/widgets/doc/GettingStarted.mlx
index 05deffa..e700b94 100644
Binary files a/widgets/doc/GettingStarted.mlx and b/widgets/doc/GettingStarted.mlx differ
diff --git a/widgets/doc/MainPage.mlx b/widgets/doc/MainPage.mlx
index afccfa2..4b6c19d 100644
Binary files a/widgets/doc/MainPage.mlx and b/widgets/doc/MainPage.mlx differ
diff --git a/widgets/doc/UserGuide.mlx b/widgets/doc/UserGuide.mlx
index 6e3679b..84511f2 100644
Binary files a/widgets/doc/UserGuide.mlx and b/widgets/doc/UserGuide.mlx differ
diff --git a/widgets/examples/ListSelectionDialogExample.mlx b/widgets/examples/ListSelectionDialogExample.mlx
index 6bf51c6..aad93f3 100644
Binary files a/widgets/examples/ListSelectionDialogExample.mlx and b/widgets/examples/ListSelectionDialogExample.mlx differ
diff --git a/widgets/examples/LoginDialogExample.mlx b/widgets/examples/LoginDialogExample.mlx
index c7ad936..7be336f 100644
Binary files a/widgets/examples/LoginDialogExample.mlx and b/widgets/examples/LoginDialogExample.mlx differ
diff --git a/widgets/wtExamplesList.m b/widgets/wtExamplesList.m
index 2bbf92b..9b83bf8 100644
--- a/widgets/wtExamplesList.m
+++ b/widgets/wtExamplesList.m
@@ -3,8 +3,20 @@ function wtExamplesList()
% Copyright 2025 The MathWorks Inc.
+
+%% Widgets
+
% Get the path
listPath = fullfile(wt.utility.widgetsRoot, "doc", "WidgetsList");
+% Open the editor file
+edit(listPath)
+
+
+%% Dialogs
+
+% Get the path
+listPath = fullfile(wt.utility.widgetsRoot, "doc", "DialogsList");
+
% Open the editor file
edit(listPath)
\ No newline at end of file