@gtkx/components 1.0.0-rc.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/column-view.d.ts +2 -2
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +14 -12
- package/dist/column-view.js.map +1 -1
- package/dist/grid-view.js +1 -1
- package/dist/grid-view.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +31 -23
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +168 -77
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +8 -11
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +53 -73
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +24 -4
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +247 -96
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +8 -10
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +38 -48
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +11 -0
- package/dist/internal/controlled-sync.d.ts.map +1 -0
- package/dist/internal/controlled-sync.js +19 -0
- package/dist/internal/controlled-sync.js.map +1 -0
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +27 -15
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.d.ts +3 -2
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +73 -48
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/keys.d.ts +5 -0
- package/dist/internal/keys.d.ts.map +1 -0
- package/dist/internal/keys.js +13 -0
- package/dist/internal/keys.js.map +1 -0
- package/dist/internal/selection.d.ts +3 -2
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +68 -35
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/slots.d.ts +10 -0
- package/dist/internal/slots.d.ts.map +1 -0
- package/dist/internal/slots.js +43 -0
- package/dist/internal/slots.js.map +1 -0
- package/dist/internal/tree-expansion.d.ts +19 -0
- package/dist/internal/tree-expansion.d.ts.map +1 -0
- package/dist/internal/tree-expansion.js +72 -0
- package/dist/internal/tree-expansion.js.map +1 -0
- package/dist/internal/tree-order.d.ts +26 -0
- package/dist/internal/tree-order.d.ts.map +1 -0
- package/dist/internal/tree-order.js +109 -0
- package/dist/internal/tree-order.js.map +1 -0
- package/dist/internal/use-collection.d.ts +1 -1
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +4 -4
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +6 -6
- package/dist/list-view.js.map +1 -1
- package/dist/types.d.ts +72 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +17 -9
- package/src/column-view.tsx +28 -25
- package/src/grid-view.tsx +1 -1
- package/src/index.ts +3 -0
- package/src/internal/cells.tsx +339 -117
- package/src/internal/collection-index.ts +68 -92
- package/src/internal/collection-model.ts +327 -112
- package/src/internal/collection.ts +45 -65
- package/src/internal/controlled-sync.ts +34 -0
- package/src/internal/drop-down.tsx +41 -20
- package/src/internal/expansion.ts +95 -70
- package/src/internal/keys.ts +18 -0
- package/src/internal/selection.tsx +100 -51
- package/src/internal/slots.ts +67 -0
- package/src/internal/tree-expansion.ts +115 -0
- package/src/internal/tree-order.ts +208 -0
- package/src/internal/use-collection.tsx +5 -5
- package/src/list-view.tsx +14 -10
- package/src/types.ts +78 -20
|
@@ -1,178 +1,348 @@
|
|
|
1
1
|
import * as Gio from "@gtkx/gi/gio";
|
|
2
2
|
import * as GObject from "@gtkx/gi/gobject";
|
|
3
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
+
import { registerClass } from "@gtkx/runtime";
|
|
4
5
|
import type { CollectionIndex, Level } from "./collection-index.js";
|
|
5
|
-
import {
|
|
6
|
+
import type { TreeExpansion } from "./tree-expansion.js";
|
|
7
|
+
import { createCollectionIndex, emptyLevel } from "./collection-index.js";
|
|
8
|
+
import { encodePart } from "./keys.js";
|
|
9
|
+
import { adoptIndex, createTreeExpansion, pruneSlots } from "./tree-expansion.js";
|
|
6
10
|
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
type LevelStore = Gio.ListModel & LazyLevelStore;
|
|
12
|
+
|
|
13
|
+
type SlotRef = {
|
|
14
|
+
store: LevelStore;
|
|
15
|
+
slot: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type SyncContext = {
|
|
19
|
+
index: CollectionIndex;
|
|
20
|
+
expansion: TreeExpansion;
|
|
11
21
|
};
|
|
12
22
|
|
|
13
|
-
type
|
|
23
|
+
type SlotRun = {
|
|
14
24
|
start: number;
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
length: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type LevelSync = {
|
|
29
|
+
store: LevelStore;
|
|
30
|
+
level: Level;
|
|
17
31
|
};
|
|
18
32
|
|
|
19
33
|
type ModelState = {
|
|
20
34
|
root: Gio.ListStore;
|
|
21
35
|
rootModels: GObject.Object[];
|
|
22
36
|
model: Gtk.FlattenListModel;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
groupStores: LevelStore[];
|
|
38
|
+
trees: Map<LevelStore, Gtk.TreeListModel>;
|
|
39
|
+
treeModels: Gtk.TreeListModel[];
|
|
40
|
+
expansion: TreeExpansion;
|
|
41
|
+
index: CollectionIndex;
|
|
26
42
|
};
|
|
27
43
|
|
|
28
44
|
type CollectionModel = {
|
|
29
45
|
model: Gtk.FlattenListModel;
|
|
30
|
-
|
|
46
|
+
expansion: TreeExpansion;
|
|
47
|
+
rowAt: (position: number) => Gtk.TreeListRow | null;
|
|
31
48
|
sync: (index: CollectionIndex) => void;
|
|
32
49
|
};
|
|
33
50
|
|
|
34
|
-
const
|
|
51
|
+
const STORE_CLASS_KEY = Symbol.for("gtkx.components.lazy-level-store");
|
|
52
|
+
const SLOTS_KEY = Symbol.for("gtkx.components.lazy-level-store.slots");
|
|
53
|
+
const EMPTY_INDEX = createCollectionIndex(undefined, undefined, true);
|
|
54
|
+
const SLOTS = sharedSlots();
|
|
35
55
|
|
|
36
|
-
const
|
|
56
|
+
const newRootStore = (): Gio.ListStore => new Gio.ListStore({ itemType: GObject.TYPE_OBJECT });
|
|
37
57
|
|
|
38
|
-
function
|
|
39
|
-
const
|
|
58
|
+
function sharedSlots(): WeakMap<GObject.Object, SlotRef> {
|
|
59
|
+
const cached: unknown = Reflect.get(globalThis, SLOTS_KEY);
|
|
60
|
+
|
|
61
|
+
if (cached instanceof WeakMap) {
|
|
62
|
+
return cached as WeakMap<GObject.Object, SlotRef>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const created: WeakMap<GObject.Object, SlotRef> = new WeakMap();
|
|
66
|
+
Reflect.set(globalThis, SLOTS_KEY, created);
|
|
40
67
|
|
|
41
|
-
return
|
|
68
|
+
return created;
|
|
42
69
|
}
|
|
43
70
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
71
|
+
function registeredStoreClass(): typeof LazyLevelStore {
|
|
72
|
+
const cached: unknown = Reflect.get(globalThis, STORE_CLASS_KEY);
|
|
46
73
|
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
74
|
+
if (typeof cached === "function") {
|
|
75
|
+
return cached as typeof LazyLevelStore;
|
|
49
76
|
}
|
|
50
77
|
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
registerClass(LazyLevelStore, { typeName: "GtkxLazyLevelStore", implements: [Gio.ListModel] });
|
|
79
|
+
Reflect.set(globalThis, STORE_CLASS_KEY, LazyLevelStore);
|
|
53
80
|
|
|
54
|
-
return
|
|
81
|
+
return LazyLevelStore;
|
|
55
82
|
}
|
|
56
83
|
|
|
57
|
-
function
|
|
58
|
-
|
|
84
|
+
function slotRefFor(value: GObject.Object | null): SlotRef | null {
|
|
85
|
+
const item = value instanceof Gtk.TreeListRow ? value.getItem() : value;
|
|
59
86
|
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
if (item === null) {
|
|
88
|
+
return null;
|
|
62
89
|
}
|
|
63
90
|
|
|
64
|
-
|
|
91
|
+
const ref = SLOTS.get(item);
|
|
92
|
+
|
|
93
|
+
return ref === undefined || ref.slot === -1 ? null : ref;
|
|
65
94
|
}
|
|
66
95
|
|
|
67
|
-
function
|
|
68
|
-
|
|
96
|
+
function slotPathAt(store: LevelStore, slot: number): string {
|
|
97
|
+
return store.level.path + encodePart(String(slot));
|
|
98
|
+
}
|
|
69
99
|
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
function growStore(store: LevelStore): void {
|
|
101
|
+
for (let slot = store.refs.length; slot < store.level.items.length; slot++) {
|
|
102
|
+
store.refs.push({ store, slot });
|
|
103
|
+
store.objects.push(null);
|
|
104
|
+
store.childStores.push(null);
|
|
72
105
|
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function newLevelStore(level: Level): LevelStore {
|
|
109
|
+
const store = new (registeredStoreClass())() as LevelStore;
|
|
110
|
+
store.level = level;
|
|
111
|
+
growStore(store);
|
|
73
112
|
|
|
74
|
-
return
|
|
113
|
+
return store;
|
|
75
114
|
}
|
|
76
115
|
|
|
77
|
-
function
|
|
78
|
-
const
|
|
79
|
-
const start = commonPrefix(previous, next, shared);
|
|
116
|
+
function collectFlips(previous: Level, next: Level, overlap: number): Set<number> {
|
|
117
|
+
const flipped: Set<number> = new Set();
|
|
80
118
|
|
|
81
|
-
|
|
82
|
-
|
|
119
|
+
for (let slot = 0; slot < overlap; slot++) {
|
|
120
|
+
if ((previous.expandableFlags[slot] ?? false) !== (next.expandableFlags[slot] ?? false)) {
|
|
121
|
+
flipped.add(slot);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return flipped;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function pruneFlips(context: SyncContext, store: LevelStore, flipped: Set<number>): void {
|
|
129
|
+
if (flipped.size === 0) {
|
|
130
|
+
return;
|
|
83
131
|
}
|
|
84
132
|
|
|
85
|
-
|
|
133
|
+
pruneSlots(context.expansion, store.level.path, (slot) => flipped.has(slot));
|
|
86
134
|
|
|
87
|
-
|
|
135
|
+
for (const slot of flipped) {
|
|
136
|
+
store.childStores[slot] = null;
|
|
137
|
+
}
|
|
88
138
|
}
|
|
89
139
|
|
|
90
|
-
function
|
|
91
|
-
|
|
140
|
+
function detachRefs(store: LevelStore, nextLength: number): void {
|
|
141
|
+
for (let slot = nextLength; slot < store.refs.length; slot++) {
|
|
142
|
+
const ref = store.refs[slot];
|
|
92
143
|
|
|
93
|
-
|
|
94
|
-
|
|
144
|
+
if (ref !== undefined) {
|
|
145
|
+
ref.slot = -1;
|
|
146
|
+
}
|
|
95
147
|
}
|
|
148
|
+
}
|
|
96
149
|
|
|
97
|
-
|
|
98
|
-
|
|
150
|
+
function shrinkStore(context: SyncContext, store: LevelStore, nextLength: number): void {
|
|
151
|
+
if (store.refs.length <= nextLength) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
99
154
|
|
|
100
|
-
|
|
155
|
+
detachRefs(store, nextLength);
|
|
156
|
+
pruneSlots(context.expansion, store.level.path, (slot) => slot >= nextLength);
|
|
157
|
+
store.refs.length = nextLength;
|
|
158
|
+
store.objects.length = nextLength;
|
|
159
|
+
store.childStores.length = nextLength;
|
|
101
160
|
}
|
|
102
161
|
|
|
103
|
-
function
|
|
104
|
-
|
|
162
|
+
function emitTailSplice(store: LevelStore, previousLength: number, nextLength: number): void {
|
|
163
|
+
if (nextLength < previousLength) {
|
|
164
|
+
store.itemsChanged(nextLength, previousLength - nextLength, 0);
|
|
105
165
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const previous = current.canExpand.get(id);
|
|
109
|
-
next.set(id, isWanted);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
110
168
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
169
|
+
if (nextLength > previousLength) {
|
|
170
|
+
store.itemsChanged(previousLength, 0, nextLength - previousLength);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function extendRuns(runs: SlotRun[], slot: number): void {
|
|
175
|
+
const last = runs.at(-1);
|
|
176
|
+
|
|
177
|
+
if (last !== undefined && last.start + last.length === slot) {
|
|
178
|
+
last.length += 1;
|
|
179
|
+
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
runs.push({ start: slot, length: 1 });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function collectFlipRuns(flipped: Set<number>): SlotRun[] {
|
|
187
|
+
const runs: SlotRun[] = [];
|
|
188
|
+
|
|
189
|
+
for (const slot of flipped) {
|
|
190
|
+
extendRuns(runs, slot);
|
|
114
191
|
}
|
|
115
192
|
|
|
116
|
-
|
|
193
|
+
return runs;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function emitFlips(store: LevelStore, flipped: Set<number>): void {
|
|
197
|
+
for (const run of collectFlipRuns(flipped)) {
|
|
198
|
+
store.itemsChanged(run.start, run.length, run.length);
|
|
199
|
+
}
|
|
117
200
|
}
|
|
118
201
|
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
202
|
+
function childSync(
|
|
203
|
+
context: SyncContext,
|
|
204
|
+
store: LevelStore,
|
|
205
|
+
slot: number,
|
|
206
|
+
flipped: Set<number>,
|
|
207
|
+
): LevelSync | undefined {
|
|
208
|
+
const child = store.childStores[slot] ?? null;
|
|
122
209
|
|
|
123
|
-
if (
|
|
124
|
-
return
|
|
210
|
+
if (child === null || flipped.has(slot)) {
|
|
211
|
+
return undefined;
|
|
125
212
|
}
|
|
126
213
|
|
|
127
|
-
|
|
128
|
-
current.ids = [...level.ids];
|
|
214
|
+
const level = context.index.childLevel(store.level, slot);
|
|
129
215
|
|
|
130
|
-
return
|
|
216
|
+
return level === undefined ? undefined : { store: child, level };
|
|
131
217
|
}
|
|
132
218
|
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
219
|
+
function childSyncs(context: SyncContext, store: LevelStore, overlap: number, flipped: Set<number>): LevelSync[] {
|
|
220
|
+
const syncs: LevelSync[] = [];
|
|
221
|
+
|
|
222
|
+
for (let slot = 0; slot < overlap; slot++) {
|
|
223
|
+
const sync = childSync(context, store, slot, flipped);
|
|
224
|
+
|
|
225
|
+
if (sync !== undefined) {
|
|
226
|
+
syncs.push(sync);
|
|
137
227
|
}
|
|
138
228
|
}
|
|
229
|
+
|
|
230
|
+
return syncs;
|
|
139
231
|
}
|
|
140
232
|
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
233
|
+
function syncEntry(context: SyncContext, entry: LevelSync): LevelSync[] {
|
|
234
|
+
const { store, level } = entry;
|
|
235
|
+
const previousLength = store.refs.length;
|
|
236
|
+
const overlap = Math.min(previousLength, level.items.length);
|
|
237
|
+
const flipped = collectFlips(store.level, level, overlap);
|
|
238
|
+
store.level = level;
|
|
239
|
+
pruneFlips(context, store, flipped);
|
|
240
|
+
shrinkStore(context, store, level.items.length);
|
|
241
|
+
growStore(store);
|
|
242
|
+
emitTailSplice(store, previousLength, level.items.length);
|
|
243
|
+
emitFlips(store, flipped);
|
|
244
|
+
|
|
245
|
+
return childSyncs(context, store, overlap, flipped);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function pushSyncs(pending: LevelSync[], syncs: LevelSync[]): void {
|
|
249
|
+
for (const sync of syncs) {
|
|
250
|
+
pending.push(sync);
|
|
144
251
|
}
|
|
252
|
+
}
|
|
145
253
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
254
|
+
function syncLevel(context: SyncContext, store: LevelStore, level: Level): void {
|
|
255
|
+
const pending: LevelSync[] = [{ store, level }];
|
|
256
|
+
|
|
257
|
+
while (pending.length > 0) {
|
|
258
|
+
const entry = pending.pop();
|
|
259
|
+
|
|
260
|
+
if (entry !== undefined) {
|
|
261
|
+
pushSyncs(pending, syncEntry(context, entry).toReversed());
|
|
149
262
|
}
|
|
150
263
|
}
|
|
151
264
|
}
|
|
152
265
|
|
|
153
|
-
function
|
|
154
|
-
const
|
|
266
|
+
function ensureChildStore(index: CollectionIndex, store: LevelStore, slot: number): LevelStore | null {
|
|
267
|
+
const existing = store.childStores[slot] ?? null;
|
|
268
|
+
|
|
269
|
+
if (existing !== null || !(store.level.expandableFlags[slot] ?? false)) {
|
|
270
|
+
return existing;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const level = index.childLevel(store.level, slot);
|
|
274
|
+
|
|
275
|
+
if (level === undefined) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const created = newLevelStore(level);
|
|
280
|
+
store.childStores[slot] = created;
|
|
281
|
+
|
|
282
|
+
return created;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function childStoreFor(state: ModelState, object: GObject.Object): Gio.ListModel | null {
|
|
286
|
+
const ref = slotRefFor(object);
|
|
155
287
|
|
|
156
|
-
if (
|
|
288
|
+
if (ref === null) {
|
|
157
289
|
return null;
|
|
158
290
|
}
|
|
159
291
|
|
|
160
|
-
return state.
|
|
292
|
+
return ensureChildStore(state.index, ref.store, ref.slot);
|
|
161
293
|
}
|
|
162
294
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
childStoreFor(state, object))
|
|
295
|
+
function treeFor(state: ModelState, store: LevelStore): Gtk.TreeListModel {
|
|
296
|
+
return (
|
|
297
|
+
state.trees.get(store) ?? Gtk.TreeListModel.new(store, false, false, (object) => childStoreFor(state, object))
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function nextTrees(state: ModelState, index: CollectionIndex): Map<LevelStore, Gtk.TreeListModel> {
|
|
302
|
+
if (!index.isTree) {
|
|
303
|
+
return new Map();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return new Map(state.groupStores.map((store) => [store, treeFor(state, store)]));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function pruneRebuiltLevels(state: ModelState, next: Map<LevelStore, Gtk.TreeListModel>): void {
|
|
310
|
+
const stores: Set<LevelStore> = new Set([...state.trees.keys(), ...next.keys()]);
|
|
166
311
|
|
|
167
|
-
|
|
312
|
+
for (const store of stores) {
|
|
313
|
+
if (state.trees.get(store) !== next.get(store)) {
|
|
314
|
+
pruneSlots(state.expansion, store.level.path, () => true);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function adoptTrees(state: ModelState, index: CollectionIndex): void {
|
|
320
|
+
const next = nextTrees(state, index);
|
|
321
|
+
pruneRebuiltLevels(state, next);
|
|
322
|
+
state.trees = next;
|
|
323
|
+
state.treeModels = next.values().toArray();
|
|
168
324
|
}
|
|
169
325
|
|
|
170
326
|
function desiredRootModels(state: ModelState, index: CollectionIndex): GObject.Object[] {
|
|
171
|
-
|
|
172
|
-
|
|
327
|
+
adoptTrees(state, index);
|
|
328
|
+
|
|
329
|
+
return index.isTree ? [...state.treeModels] : [...state.groupStores];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function rowAt(state: ModelState, position: number): Gtk.TreeListRow | null {
|
|
333
|
+
let offset = position;
|
|
334
|
+
|
|
335
|
+
for (const tree of state.treeModels) {
|
|
336
|
+
const count = tree.getNItems();
|
|
337
|
+
|
|
338
|
+
if (offset < count) {
|
|
339
|
+
return tree.getRow(offset);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
offset -= count;
|
|
173
343
|
}
|
|
174
344
|
|
|
175
|
-
return
|
|
345
|
+
return null;
|
|
176
346
|
}
|
|
177
347
|
|
|
178
348
|
function hasSameModels(previous: GObject.Object[], next: GObject.Object[]): boolean {
|
|
@@ -190,54 +360,99 @@ function syncRoot(state: ModelState, index: CollectionIndex): void {
|
|
|
190
360
|
state.rootModels = next;
|
|
191
361
|
}
|
|
192
362
|
|
|
193
|
-
function
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
let didChange = false;
|
|
363
|
+
function adjustGroupStores(state: ModelState, context: SyncContext): void {
|
|
364
|
+
const { groups } = context.index;
|
|
365
|
+
state.groupStores.length = Math.min(state.groupStores.length, groups.length);
|
|
197
366
|
|
|
198
|
-
for (const level of
|
|
199
|
-
|
|
200
|
-
visited.add(level.key);
|
|
367
|
+
for (const level of groups.slice(state.groupStores.length)) {
|
|
368
|
+
state.groupStores.push(newLevelStore(level));
|
|
201
369
|
}
|
|
370
|
+
}
|
|
202
371
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
372
|
+
function stepGroupStores(state: ModelState, context: SyncContext, surviving: number): void {
|
|
373
|
+
for (const [group, level] of context.index.groups.entries()) {
|
|
374
|
+
const store = state.groupStores[group];
|
|
207
375
|
|
|
208
|
-
|
|
209
|
-
|
|
376
|
+
if (store !== undefined && group < surviving) {
|
|
377
|
+
syncLevel(context, store, level);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
210
380
|
}
|
|
211
381
|
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
382
|
+
function syncModel(state: ModelState, index: CollectionIndex): void {
|
|
383
|
+
const context: SyncContext = { index, expansion: state.expansion };
|
|
384
|
+
const surviving = Math.min(state.groupStores.length, index.groups.length);
|
|
385
|
+
state.index = index;
|
|
386
|
+
state.expansion.isSyncing = true;
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
adjustGroupStores(state, context);
|
|
390
|
+
syncRoot(state, index);
|
|
391
|
+
stepGroupStores(state, context, surviving);
|
|
392
|
+
} finally {
|
|
393
|
+
state.expansion.isSyncing = false;
|
|
215
394
|
}
|
|
216
395
|
|
|
217
|
-
|
|
218
|
-
refreshExpandable(levelFor(state, level.key), level);
|
|
219
|
-
}
|
|
396
|
+
adoptIndex(state.expansion, index);
|
|
220
397
|
}
|
|
221
398
|
|
|
222
399
|
function createCollectionModel(): CollectionModel {
|
|
223
|
-
|
|
400
|
+
registeredStoreClass();
|
|
401
|
+
const root = newRootStore();
|
|
224
402
|
|
|
225
403
|
const state: ModelState = {
|
|
226
404
|
root,
|
|
227
405
|
rootModels: [],
|
|
228
406
|
model: Gtk.FlattenListModel.new(root),
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
407
|
+
groupStores: [],
|
|
408
|
+
trees: new Map(),
|
|
409
|
+
treeModels: [],
|
|
410
|
+
expansion: createTreeExpansion(EMPTY_INDEX),
|
|
411
|
+
index: EMPTY_INDEX,
|
|
232
412
|
};
|
|
233
413
|
|
|
234
414
|
return {
|
|
235
415
|
model: state.model,
|
|
236
|
-
|
|
416
|
+
expansion: state.expansion,
|
|
417
|
+
rowAt: (position) => rowAt(state, position),
|
|
237
418
|
sync: (index) => {
|
|
238
419
|
syncModel(state, index);
|
|
239
420
|
},
|
|
240
421
|
};
|
|
241
422
|
}
|
|
242
423
|
|
|
243
|
-
|
|
424
|
+
class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
|
|
425
|
+
declare itemsChanged: Gio.ListModel["itemsChanged"];
|
|
426
|
+
level: Level = emptyLevel();
|
|
427
|
+
refs: SlotRef[] = [];
|
|
428
|
+
objects: (Gtk.StringObject | null)[] = [];
|
|
429
|
+
childStores: (LevelStore | null)[] = [];
|
|
430
|
+
|
|
431
|
+
private createItem(position: number, ref: SlotRef): Gtk.StringObject {
|
|
432
|
+
const created = Gtk.StringObject.new("");
|
|
433
|
+
this.objects[position] = created;
|
|
434
|
+
SLOTS.set(created, ref);
|
|
435
|
+
|
|
436
|
+
return created;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
vfuncGetItemType(): bigint {
|
|
440
|
+
return GObject.TYPE_OBJECT;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
vfuncGetNItems(): number {
|
|
444
|
+
return this.refs.length;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
vfuncGetItem(position: number): GObject.Object | null {
|
|
448
|
+
const ref = this.refs[position];
|
|
449
|
+
|
|
450
|
+
if (ref === undefined) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return this.objects[position] ?? this.createItem(position, ref);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export { createCollectionModel, slotPathAt, slotRefFor, type CollectionModel, type SlotRef };
|