Skip to content

Commit f2dc907

Browse files
committed
bugfix to Orderable
1 parent 54f37fa commit f2dc907

1 file changed

Lines changed: 118 additions & 70 deletions

File tree

widgets/+wt/+mixin/Orderable.m

Lines changed: 118 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,144 @@
11
classdef (HandleCompatible) Orderable
22
% Implements functionality for orderable lists
3-
% Copyright 2025 The MathWorks Inc.
3+
4+
% Copyright 2025 The MathWorks Inc.
45

56

67
%% Internal Static methods
78
methods (Static, Access = protected)
89

910
function [idxNew, idxSelAfter] = shiftListIndices(shift, numItems, idxSel)
10-
% Shift the selected indices up/down within a list
11-
12-
% Define arguments
13-
arguments %(Input)
14-
% Shift amount and direction (typically 1 or -1)
15-
shift (1,1) double {mustBeInteger}
16-
17-
% Total number of items in the list
18-
numItems (1,1) double {mustBeInteger, mustBeNonnegative}
19-
20-
% Selected indices to move
21-
idxSel (1,:) double {mustBeInteger, mustBePositive, mustBeLessThanOrEqual(idxSel,numItems)}
22-
end
23-
24-
% arguments (Output)
25-
% % Indices of the complete list after re-ordering
26-
% idxNew (1,:) double {mustBeInteger, mustBePositive}
11+
% shiftListIndices Move selected item indices within 1:numItems and report new indices
2712
%
28-
% % Indices where the selected data end up after the move
29-
% idxSelAfter (1,:) double {mustBeInteger, mustBePositive}
30-
% end
31-
32-
% Make indices to all items as they are now
33-
idxNew = 1:numItems;
34-
35-
% Allocate the final indices
36-
idxSelAfter = idxSel;
37-
38-
% Find the last stable item that doesn't move
39-
[~,idxStable] = setdiff(idxNew, idxSel, 'stable');
40-
if ~isempty(idxStable)
41-
idxFirstStable = idxStable(1);
42-
idxLastStable = idxStable(end);
43-
else
44-
idxFirstStable = inf;
45-
idxLastStable = 0;
13+
% [idxNew, idxSelAfter] = shiftListIndices(shift, numItems, idxSel)
14+
%
15+
% Inputs
16+
% shift - integer shift (positive -> down/increase index, negative -> up/decrease)
17+
% use +Inf to move to bottom, -Inf to move to top
18+
% numItems - total number of items (positive integer)
19+
% idxSel - vector of selected indices (1-based). May be unsorted; duplicates ignored.
20+
%
21+
% Outputs
22+
% idxNew - permutation vector 1:numItems after applying the move
23+
% idxSelAfter - vector of same length/order as unique(idxSel) input, giving the
24+
% positions (indices into idxNew) where each originally selected item now sits
25+
%
26+
% Notes
27+
% - Preserves relative order of selected items and of remaining items.
28+
% - Non-contiguous selections are supported.
29+
% - Selections outside 1:numItems are ignored.
30+
31+
arguments
32+
shift {mustBeNumeric}
33+
numItems (1,1) {mustBeInteger, mustBePositive}
34+
idxSel (:,1) {mustBeNumeric} = []
4635
end
4736

48-
% Which way do we loop?
49-
if shift > 0 %Shift to end
37+
% Normalize selection: keep original order of unique entries
38+
idxSelOrig = idxSel(:).';
39+
if isempty(idxSelOrig)
40+
idxNew = 1:numItems;
41+
idxSelAfter = zeros(size(idxSelOrig));
42+
return
43+
end
44+
% Unique while preserving first-occurrence order:
45+
[~, ia] = unique(idxSelOrig, 'stable');
46+
idxSelOrig = idxSelOrig(sort(ia)); % now unique in original order
47+
48+
% Clamp to valid range
49+
idxSelOrig = idxSelOrig(idxSelOrig >= 1 & idxSelOrig <= numItems);
50+
if isempty(idxSelOrig)
51+
idxNew = 1:numItems;
52+
idxSelAfter = zeros(size(idxSelOrig));
53+
return
54+
end
55+
if numel(idxSelOrig) == numItems
56+
idxNew = 1:numItems;
57+
idxSelAfter = (1:numItems);
58+
return
59+
end
5060

51-
for idxToMove = numel(idxSel):-1:1
61+
% Quick handle infinities
62+
if isinf(shift)
63+
if shift > 0
64+
idxNew = [setdiff(1:numItems, idxSelOrig, 'stable'), idxSelOrig];
65+
else
66+
idxNew = [idxSelOrig, setdiff(1:numItems, idxSelOrig, 'stable')];
67+
end
68+
% positions of original selected items in idxNew
69+
% For each original selected item, find its index in idxNew
70+
idxSelAfter = arrayfun(@(x) find(idxNew==x,1,'first'), idxSelOrig);
71+
return
72+
end
5273

53-
% Calculate if there's room to move this item
54-
idxThisBefore = idxSel(idxToMove);
55-
thisShift = max( min(idxLastStable-idxThisBefore, shift), 0 );
74+
k = round(shift);
5675

57-
% Where does this item move from/to
58-
idxThisAfter = idxThisBefore + thisShift;
59-
idxSelAfter(idxToMove) = idxThisAfter;
76+
% Work with sorted selection for deterministic placement logic, but track originals
77+
selSorted = unique(idxSelOrig); % ascending order
78+
% numSel = numel(selSorted);
6079

61-
% Where do other items move from/to
62-
idxOthersBefore = idxSel(idxToMove)+1:1:idxThisAfter;
63-
idxOthersAfter = idxOthersBefore - thisShift;
80+
% Compute desired target positions for each selected item (clamped)
81+
targets = min(max(selSorted + k, 1), numItems);
6482

65-
% Move the items
66-
idxNew([idxThisAfter idxOthersAfter]) = idxNew([idxThisBefore idxOthersBefore]);
83+
% Prepare result vector and occupancy map
84+
res = nan(1, numItems);
85+
occupied = false(1, numItems);
6786

87+
% Determine assignment order to resolve collisions consistent with shift direction
88+
if k >= 0
89+
% for nonnegative shift, assign in increasing target order (tie-break by original index)
90+
[~, ord] = sortrows([targets(:), selSorted(:)]);
91+
else
92+
% for negative shift, assign in decreasing target order
93+
[~, ord] = sortrows([-targets(:), -selSorted(:)]);
94+
end
95+
ord = ord.'; % make row vector of indices into selSorted
96+
97+
% Assign selected items to nearest available slot in shift direction
98+
for ii = ord
99+
t = targets(ii);
100+
if k >= 0
101+
% first free position >= t
102+
posRel = find(~occupied(t:end), 1, 'first');
103+
if isempty(posRel)
104+
% place at last free slot
105+
p = find(~occupied, 1, 'last');
106+
assignPos = p;
107+
else
108+
assignPos = t + posRel - 1;
109+
end
110+
else
111+
% last free position <= t
112+
pos = find(~occupied(1:t), 1, 'last');
113+
if isempty(pos)
114+
p = find(~occupied, 1, 'first');
115+
assignPos = p;
116+
else
117+
assignPos = pos;
118+
end
68119
end
120+
res(assignPos) = selSorted(ii);
121+
occupied(assignPos) = true;
122+
end
69123

70-
elseif shift < 0 %Shift to start
71-
72-
for idxToMove = 1:numel(idxSel)
73-
74-
% Calculate if there's room to move this item
75-
idxThisBefore = idxSel(idxToMove);
76-
thisShift = min( max(idxFirstStable-idxThisBefore, shift), 0 );
77-
78-
% Where does this item move from/to
79-
idxThisAfter = idxThisBefore + thisShift;
80-
idxSelAfter(idxToMove) = idxThisAfter;
81-
82-
% Where do other items move from/to
83-
idxOthersBefore = idxThisAfter:1:idxSel(idxToMove)-1;
84-
idxOthersAfter = idxOthersBefore - thisShift;
124+
% Fill remaining slots with non-selected items in original order
125+
remItems = setdiff(1:numItems, selSorted, 'stable');
126+
remPtr = 1;
127+
for p = 1:numItems
128+
if isnan(res(p))
129+
res(p) = remItems(remPtr);
130+
remPtr = remPtr + 1;
131+
end
132+
end
85133

86-
% Move the items
87-
idxNew([idxThisAfter idxOthersAfter]) = idxNew([idxThisBefore idxOthersBefore]);
134+
idxNew = res;
88135

89-
end
136+
% Map original selected items (in the order provided) to their new positions
137+
% Note: if input had duplicates or out-of-range entries removed earlier, idxSelOrig reflects uniques in-range
138+
idxSelAfter = arrayfun(@(x) find(idxNew==x,1,'first'), idxSelOrig);
90139

91-
end %if shift > 0
140+
end
92141

93-
end %function
94142

95143

96144
function [backEnabled, fwdEnabled] = areOrderButtonsEnabled(numItems, idxSel, allowSortItem)

0 commit comments

Comments
 (0)