Improve source structure in the node api (#6164)

* Update api/node/typescript/models.ts

Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>

* Code review feedback
---------

Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
This commit is contained in:
FloVanGH 2024-09-30 10:49:35 +02:00 committed by GitHub
parent a2ded25914
commit 25ae55b5dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 528 additions and 450 deletions

View file

@ -100,7 +100,6 @@ All notable changes to this project are documented in this file.
- Skia renderer: Improve rendering quality of layers
- GridLayout: Fixed panic when rowspan or colspan is 0 (#6181)
## [1.7.2] - 2024-08-14
### General

1
api/node/.gitignore vendored
View file

@ -3,3 +3,4 @@ rust-module.d.ts
index.js
docs/
npm/
dist/

View file

@ -18,6 +18,7 @@ build = "build.rs"
[lib]
crate-type = ["cdylib"]
path = "rust/lib.rs"
[features]
default = ["backend-winit", "renderer-femtovg", "renderer-software", "backend-qt", "accessibility"]

View file

@ -5,7 +5,7 @@ import test from "ava";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { loadFile, loadSource, CompileError } from "../index.js";
import { loadFile, loadSource, CompileError } from "../dist/index.js";
const dirname = path.dirname(fileURLToPath(import.meta.url));
@ -81,7 +81,6 @@ test("loadFile constructor parameters", (t) => {
});
test("loadFile component instances and modules are sealed", (t) => {
"use strict";
const demo = loadFile(path.join(dirname, "resources/test.slint")) as any;
t.throws(
@ -182,7 +181,6 @@ test("loadSource constructor parameters", (t) => {
});
test("loadSource component instances and modules are sealed", (t) => {
"use strict";
const source = `export component Test {
out property <string> check: "Test";
}`;

View file

@ -3,7 +3,7 @@
import test from "ava";
import { private_api } from "../index.js";
import { private_api } from "../dist/index.js";
test("get/set include paths", (t) => {
const compiler = new private_api.ComponentCompiler();

View file

@ -7,7 +7,7 @@ import test from "ava";
import * as http from "node:http";
import fetch from "node-fetch";
import { runEventLoop, quitEventLoop, private_api } from "../index.js";
import { runEventLoop, quitEventLoop, private_api } from "../dist/index.js";
test.serial("merged event loops with timer", async (t) => {
let invoked = false;

View file

@ -3,7 +3,7 @@
import test from "ava";
import { private_api } from "../index.js";
import { private_api } from "../dist/index.js";
test("get/set global properties", (t) => {
const compiler = new private_api.ComponentCompiler();

View file

@ -11,7 +11,7 @@ import {
type ImageData,
ArrayModel,
type Model,
} from "../index.js";
} from "../dist/index.js";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
@ -598,11 +598,12 @@ test("MapModel", (t) => {
instance!.setProperty("model", mapModel);
nameModel.setRowData(1, { first: "Simon", last: "Hausmann" });
nameModel.setRowData(0, { first: "Simon", last: "Hausmann" });
nameModel.setRowData(1, { first: "Olivier", last: "Goffart" });
const checkModel = instance!.getProperty("model") as Model<string>;
t.is(checkModel.rowData(0), "Emil, Hans");
t.is(checkModel.rowData(1), "Hausmann, Simon");
t.is(checkModel.rowData(0), "Hausmann, Simon");
t.is(checkModel.rowData(1), "Goffart, Olivier");
t.is(checkModel.rowData(2), "Tisch, Roman");
});

View file

@ -0,0 +1,64 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import test from "ava";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import {
loadFile,
loadSource,
CompileError,
ArrayModel,
private_api,
Model,
} from "../dist/index.js";
test("MapModel notify rowChanged", (t) => {
const source = `
export component App {
in-out property <[string]> model;
in-out property <string> changed-items;
for item in root.model : Text {
text: item;
changed text => {
root.changed-items += self.text;
}
}
}`;
const path = "api.spec.ts";
private_api.initTesting();
const demo = loadSource(source, path) as any;
const instance = new demo.App();
interface Name {
first: string;
last: string;
}
const nameModel: ArrayModel<Name> = new ArrayModel([
{ first: "Hans", last: "Emil" },
{ first: "Max", last: "Mustermann" },
{ first: "Roman", last: "Tisch" },
]);
const mapModel = new private_api.MapModel(nameModel, (data) => {
return data.last + ", " + data.first;
});
instance.model = mapModel;
private_api.send_mouse_click(instance, 5, 5);
nameModel.setRowData(0, { first: "Simon", last: "Hausmann" });
nameModel.setRowData(1, { first: "Olivier", last: "Goffart" });
private_api.send_mouse_click(instance, 5, 5);
t.is(instance.changed_items, "Goffart, OlivierHausmann, Simon");
});

View file

@ -3,7 +3,7 @@
import test from "ava";
import { private_api, ArrayModel } from "../index.js";
import { private_api, ArrayModel } from "../dist/index.js";
test("SlintColor from fromRgb", (t) => {
const color = private_api.SlintRgbaColor.fromRgb(100, 110, 120);

View file

@ -3,7 +3,7 @@
import test from "ava";
import { private_api, Window } from "../index.js";
import { private_api, Window } from "../dist/index.js";
test("Window constructor", (t) => {
t.throws(

View file

@ -1,8 +1,8 @@
{
"name": "slint-ui",
"version": "1.9.0",
"main": "index.js",
"types": "index.d.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"homepage": "https://github.com/slint-ui/slint",
"license": "SEE LICENSE IN LICENSE.md",
"repository": {
@ -20,8 +20,8 @@
],
"description": "Slint is a declarative GUI toolkit to build native user interfaces for desktop and embedded applications.",
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@ava/typescript": "^4.1.0",
"@biomejs/biome": "1.8.3",
"@types/node": "^20.8.6",
"@types/node-fetch": "^2.6.7",
"ava": "^5.3.0",
@ -40,7 +40,7 @@
"build:debug": "napi build --platform --js rust-module.cjs --dts rust-module.d.ts -c binaries.json && npm run compile",
"build:testing": "napi build --platform --js rust-module.cjs --dts rust-module.d.ts -c binaries.json --features testing && npm run compile",
"install": "node build-on-demand.mjs",
"docs": "npm run build && typedoc --hideGenerator --treatWarningsAsErrors --readme cover.md index.ts",
"docs": "npm run build && typedoc --hideGenerator --treatWarningsAsErrors --readme cover.md typescript/index.ts",
"check": "biome check",
"format": "biome format",
"format:fix": "biome format --write",

View file

@ -3,8 +3,9 @@
"module": "CommonJS",
"target": "esnext",
"declaration": true,
"outDir": "dist"
},
"include": [
"index.ts"
"typescript/"
],
}

View file

@ -1,15 +1,20 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import * as napi from "./rust-module.cjs";
import * as napi from "../rust-module.cjs";
export {
Diagnostic,
DiagnosticLevel,
RgbaColor,
Brush,
} from "./rust-module";
} from "../rust-module.cjs";
import { Diagnostic } from "./rust-module.cjs";
import { Model } from "./models";
export { Model };
export { ArrayModel } from "./models";
import { Diagnostic } from "../rust-module.cjs";
/**
* Represents a two-dimensional point.
@ -115,434 +120,6 @@ export interface ImageData {
get height(): number;
}
class ModelIterator<T> implements Iterator<T> {
private row: number;
private model: Model<T>;
constructor(model: Model<T>) {
this.model = model;
this.row = 0;
}
public next(): IteratorResult<T> {
if (this.row < this.model.rowCount()) {
const row = this.row;
this.row++;
return {
done: false,
value: this.model.rowData(row),
};
}
return {
done: true,
value: undefined,
};
}
}
/**
* Model<T> is the interface for feeding dynamic data into
* `.slint` views.
*
* A model is organized like a table with rows of data. The
* fields of the data type T behave like columns.
*
* @template T the type of the model's items.
*
* ### Example
* As an example let's see the implementation of {@link ArrayModel}
*
* ```js
* export class ArrayModel<T> extends Model<T> {
* private a: Array<T>
*
* constructor(arr: Array<T>) {
* super();
* this.a = arr;
* }
*
* rowCount() {
* return this.a.length;
* }
*
* rowData(row: number) {
* return this.a[row];
* }
*
* setRowData(row: number, data: T) {
* this.a[row] = data;
* this.notifyRowDataChanged(row);
* }
*
* push(...values: T[]) {
* let size = this.a.length;
* Array.prototype.push.apply(this.a, values);
* this.notifyRowAdded(size, arguments.length);
* }
*
* remove(index: number, size: number) {
* let r = this.a.splice(index, size);
* this.notifyRowRemoved(index, size);
* }
*
* get length(): number {
* return this.a.length;
* }
*
* values(): IterableIterator<T> {
* return this.a.values();
* }
*
* entries(): IterableIterator<[number, T]> {
* return this.a.entries()
* }
*}
* ```
*/
export abstract class Model<T> implements Iterable<T> {
/**
* @hidden
*/
modelNotify: napi.ExternalObject<napi.SharedModelNotify>;
constructor() {
this.modelNotify = napi.jsModelNotifyNew(this);
}
// /**
// * Returns a new Model where all elements are mapped by the function `mapFunction`.
// * @template T the type of the source model's items.
// * @param mapFunction functions that maps
// * @returns a new {@link MapModel} that wraps the current model.
// */
// map<U>(
// mapFunction: (data: T) => U
// ): MapModel<T, U> {
// return new MapModel(this, mapFunction);
// }
/**
* Implementations of this function must return the current number of rows.
*/
abstract rowCount(): number;
/**
* Implementations of this function must return the data at the specified row.
* @param row index in range 0..(rowCount() - 1).
* @returns undefined if row is out of range otherwise the data.
*/
abstract rowData(row: number): T | undefined;
/**
* Implementations of this function must store the provided data parameter
* in the model at the specified row.
* @param _row index in range 0..(rowCount() - 1).
* @param _data new data item to store on the given row index
*/
setRowData(_row: number, _data: T): void {
console.log(
"setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
);
}
[Symbol.iterator](): Iterator<T> {
return new ModelIterator(this);
}
/**
* Notifies the view that the data of the current row is changed.
* @param row index of the changed row.
*/
protected notifyRowDataChanged(row: number): void {
napi.jsModelNotifyRowDataChanged(this.modelNotify, row);
}
/**
* Notifies the view that multiple rows are added to the model.
* @param row index of the first added row.
* @param count the number of added items.
*/
protected notifyRowAdded(row: number, count: number): void {
napi.jsModelNotifyRowAdded(this.modelNotify, row, count);
}
/**
* Notifies the view that multiple rows are removed to the model.
* @param row index of the first removed row.
* @param count the number of removed items.
*/
protected notifyRowRemoved(row: number, count: number): void {
napi.jsModelNotifyRowRemoved(this.modelNotify, row, count);
}
/**
* Notifies the view that the complete data must be reload.
*/
protected notifyReset(): void {
napi.jsModelNotifyReset(this.modelNotify);
}
}
/**
* ArrayModel wraps a JavaScript array for use in `.slint` views. The underlying
* array can be modified with the [[ArrayModel.push]] and [[ArrayModel.remove]] methods.
*/
export class ArrayModel<T> extends Model<T> {
/**
* @hidden
*/
#array: Array<T>;
/**
* Creates a new ArrayModel.
*
* @param arr
*/
constructor(arr: Array<T>) {
super();
this.#array = arr;
}
/**
* Returns the number of entries in the array model.
*/
get length(): number {
return this.#array.length;
}
/**
* Returns the number of entries in the array model.
*/
rowCount() {
return this.#array.length;
}
/**
* Returns the data at the specified row.
* @param row index in range 0..(rowCount() - 1).
* @returns undefined if row is out of range otherwise the data.
*/
rowData(row: number) {
return this.#array[row];
}
/**
* Stores the given data on the given row index and notifies run-time about the changed row.
* @param row index in range 0..(rowCount() - 1).
* @param data new data item to store on the given row index
*/
setRowData(row: number, data: T) {
this.#array[row] = data;
this.notifyRowDataChanged(row);
}
/**
* Pushes new values to the array that's backing the model and notifies
* the run-time about the added rows.
* @param values list of values that will be pushed to the array.
*/
push(...values: T[]) {
const size = this.#array.length;
Array.prototype.push.apply(this.#array, values);
this.notifyRowAdded(size, arguments.length);
}
/**
* Removes the last element from the array and returns it.
*
* @returns The removed element or undefined if the array is empty.
*/
pop(): T | undefined {
const last = this.#array.pop();
if (last !== undefined) {
this.notifyRowRemoved(this.#array.length, 1);
}
return last;
}
// FIXME: should this be named splice and have the splice api?
/**
* Removes the specified number of element from the array that's backing
* the model, starting at the specified index.
* @param index index of first row to remove.
* @param size number of rows to remove.
*/
remove(index: number, size: number) {
const r = this.#array.splice(index, size);
this.notifyRowRemoved(index, size);
}
/**
* Returns an iterable of values in the array.
*/
values(): IterableIterator<T> {
return this.#array.values();
}
/**
* Returns an iterable of key, value pairs for every entry in the array.
*/
entries(): IterableIterator<[number, T]> {
return this.#array.entries();
}
}
export namespace private_api {
/**
* Provides rows that are generated by a map function based on the rows of another Model.
*
* @template T item type of source model that is mapped to U.
* @template U the type of the mapped items
*
* ## Example
*
* Here we have a {@link ArrayModel} holding rows of a custom interface `Name` and a {@link MapModel} that maps the name rows
* to single string rows.
*
* ```ts
* import { Model, ArrayModel, MapModel } from "./index";
*
* interface Name {
* first: string;
* last: string;
* }
*
* const model = new ArrayModel<Name>([
* {
* first: "Hans",
* last: "Emil",
* },
* {
* first: "Max",
* last: "Mustermann",
* },
* {
* first: "Roman",
* last: "Tisch",
* },
* ]);
*
* const mappedModel = new MapModel(
* model,
* (data) => {
* return data.last + ", " + data.first;
* }
* );
*
* // prints "Emil, Hans"
* console.log(mappedModel.rowData(0));
*
* // prints "Mustermann, Max"
* console.log(mappedModel.rowData(1));
*
* // prints "Tisch, Roman"
* console.log(mappedModel.rowData(2));
*
* // Alternatively you can use the shortcut {@link MapModel.map}.
*
* const model = new ArrayModel<Name>([
* {
* first: "Hans",
* last: "Emil",
* },
* {
* first: "Max",
* last: "Mustermann",
* },
* {
* first: "Roman",
* last: "Tisch",
* },
* ]);
*
* const mappedModel = model.map(
* (data) => {
* return data.last + ", " + data.first;
* }
* );
*
*
* // prints "Emil, Hans"
* console.log(mappedModel.rowData(0));
*
* // prints "Mustermann, Max"
* console.log(mappedModel.rowData(1));
*
* // prints "Tisch, Roman"
* console.log(mappedModel.rowData(2));
*
* // You can modifying the underlying {@link ArrayModel}:
*
* const model = new ArrayModel<Name>([
* {
* first: "Hans",
* last: "Emil",
* },
* {
* first: "Max",
* last: "Mustermann",
* },
* {
* first: "Roman",
* last: "Tisch",
* },
* ]);
*
* const mappedModel = model.map(
* (data) => {
* return data.last + ", " + data.first;
* }
* );
*
* model.setRowData(1, { first: "Minnie", last: "Musterfrau" } );
*
* // prints "Emil, Hans"
* console.log(mappedModel.rowData(0));
*
* // prints "Musterfrau, Minnie"
* console.log(mappedModel.rowData(1));
*
* // prints "Tisch, Roman"
* console.log(mappedModel.rowData(2));
* ```
*/
export class MapModel<T, U> extends Model<U> {
readonly sourceModel: Model<T>;
#mapFunction: (data: T) => U;
/**
* Constructs the MapModel with a source model and map functions.
* @template T item type of source model that is mapped to U.
* @template U the type of the mapped items.
* @param sourceModel the wrapped model.
* @param mapFunction maps the data from T to U.
*/
constructor(sourceModel: Model<T>, mapFunction: (data: T) => U) {
super();
this.sourceModel = sourceModel;
this.#mapFunction = mapFunction;
}
/**
* Returns the number of entries in the model.
*/
rowCount(): number {
return this.sourceModel.rowCount();
}
/**
* Returns the data at the specified row.
* @param row index in range 0..(rowCount() - 1).
* @returns undefined if row is out of range otherwise the data.
*/
rowData(row: number): U | undefined {
const data = this.sourceModel.rowData(row);
if (data === undefined) {
return undefined;
}
return this.#mapFunction(data);
}
}
}
/**
* This interface describes the public API of a Slint component that is common to all instances. Use this to
* show() the window on the screen, access the window and subsequent window properties, or start the
@ -1160,6 +737,164 @@ export function quitEventLoop() {
globalEventLoop.quit();
}
export namespace private_api {
/**
* Provides rows that are generated by a map function based on the rows of another Model.
*
* @template T item type of source model that is mapped to U.
* @template U the type of the mapped items
*
* ## Example
*
* Here we have a {@link ArrayModel} holding rows of a custom interface `Name` and a {@link MapModel} that maps the name rows
* to single string rows.
*
* ```ts
* import { Model, ArrayModel, MapModel } from "./index";
*
* interface Name {
* first: string;
* last: string;
* }
*
* const model = new ArrayModel<Name>([
* {
* first: "Hans",
* last: "Emil",
* },
* {
* first: "Max",
* last: "Mustermann",
* },
* {
* first: "Roman",
* last: "Tisch",
* },
* ]);
*
* const mappedModel = new MapModel(
* model,
* (data) => {
* return data.last + ", " + data.first;
* }
* );
*
* // prints "Emil, Hans"
* console.log(mappedModel.rowData(0));
*
* // prints "Mustermann, Max"
* console.log(mappedModel.rowData(1));
*
* // prints "Tisch, Roman"
* console.log(mappedModel.rowData(2));
*
* // Alternatively you can use the shortcut {@link MapModel.map}.
*
* const model = new ArrayModel<Name>([
* {
* first: "Hans",
* last: "Emil",
* },
* {
* first: "Max",
* last: "Mustermann",
* },
* {
* first: "Roman",
* last: "Tisch",
* },
* ]);
*
* const mappedModel = model.map(
* (data) => {
* return data.last + ", " + data.first;
* }
* );
*
*
* // prints "Emil, Hans"
* console.log(mappedModel.rowData(0));
*
* // prints "Mustermann, Max"
* console.log(mappedModel.rowData(1));
*
* // prints "Tisch, Roman"
* console.log(mappedModel.rowData(2));
*
* // You can modifying the underlying {@link ArrayModel}:
*
* const model = new ArrayModel<Name>([
* {
* first: "Hans",
* last: "Emil",
* },
* {
* first: "Max",
* last: "Mustermann",
* },
* {
* first: "Roman",
* last: "Tisch",
* },
* ]);
*
* const mappedModel = model.map(
* (data) => {
* return data.last + ", " + data.first;
* }
* );
*
* model.setRowData(1, { first: "Minnie", last: "Musterfrau" } );
*
* // prints "Emil, Hans"
* console.log(mappedModel.rowData(0));
*
* // prints "Musterfrau, Minnie"
* console.log(mappedModel.rowData(1));
*
* // prints "Tisch, Roman"
* console.log(mappedModel.rowData(2));
* ```
*/
export class MapModel<T, U> extends Model<U> {
readonly sourceModel: Model<T>;
#mapFunction: (data: T) => U;
/**
* Constructs the MapModel with a source model and map functions.
* @template T item type of source model that is mapped to U.
* @template U the type of the mapped items.
* @param sourceModel the wrapped model.
* @param mapFunction maps the data from T to U.
*/
constructor(sourceModel: Model<T>, mapFunction: (data: T) => U) {
super(sourceModel.modelNotify);
this.sourceModel = sourceModel;
this.#mapFunction = mapFunction;
}
/**
* Returns the number of entries in the model.
*/
rowCount(): number {
return this.sourceModel.rowCount();
}
/**
* Returns the data at the specified row.
* @param row index in range 0..(rowCount() - 1).
* @returns undefined if row is out of range otherwise the data.
*/
rowData(row: number): U | undefined {
const data = this.sourceModel.rowData(row);
if (data === undefined) {
return undefined;
}
return this.#mapFunction(data);
}
}
}
/**
* @hidden
*/

View file

@ -0,0 +1,278 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import * as napi from "../rust-module.cjs";
class ModelIterator<T> implements Iterator<T> {
private row: number;
private model: Model<T>;
constructor(model: Model<T>) {
this.model = model;
this.row = 0;
}
public next(): IteratorResult<T> {
if (this.row < this.model.rowCount()) {
const row = this.row;
this.row++;
return {
done: false,
value: this.model.rowData(row),
};
}
return {
done: true,
value: undefined,
};
}
}
/**
* Model<T> is the interface for feeding dynamic data into
* `.slint` views.
*
* A model is organized like a table with rows of data. The
* fields of the data type T behave like columns.
*
* @template T the type of the model's items.
*
* ### Example
* As an example let's see the implementation of {@link ArrayModel}
*
* ```js
* export class ArrayModel<T> extends Model<T> {
* private a: Array<T>
*
* constructor(arr: Array<T>) {
* super();
* this.a = arr;
* }
*
* rowCount() {
* return this.a.length;
* }
*
* rowData(row: number) {
* return this.a[row];
* }
*
* setRowData(row: number, data: T) {
* this.a[row] = data;
* this.notifyRowDataChanged(row);
* }
*
* push(...values: T[]) {
* let size = this.a.length;
* Array.prototype.push.apply(this.a, values);
* this.notifyRowAdded(size, arguments.length);
* }
*
* remove(index: number, size: number) {
* let r = this.a.splice(index, size);
* this.notifyRowRemoved(index, size);
* }
*
* get length(): number {
* return this.a.length;
* }
*
* values(): IterableIterator<T> {
* return this.a.values();
* }
*
* entries(): IterableIterator<[number, T]> {
* return this.a.entries()
* }
*}
* ```
*/
export abstract class Model<T> implements Iterable<T> {
/**
* @hidden
*/
modelNotify: napi.ExternalObject<napi.SharedModelNotify>;
/**
* @hidden
*/
constructor(modelNotify?: napi.ExternalObject<napi.SharedModelNotify>) {
this.modelNotify = modelNotify ?? napi.jsModelNotifyNew(this);
}
// /**
// * Returns a new Model where all elements are mapped by the function `mapFunction`.
// * @template T the type of the source model's items.
// * @param mapFunction functions that maps
// * @returns a new {@link MapModel} that wraps the current model.
// */
// map<U>(
// mapFunction: (data: T) => U
// ): MapModel<T, U> {
// return new MapModel(this, mapFunction);
// }
/**
* Implementations of this function must return the current number of rows.
*/
abstract rowCount(): number;
/**
* Implementations of this function must return the data at the specified row.
* @param row index in range 0..(rowCount() - 1).
* @returns undefined if row is out of range otherwise the data.
*/
abstract rowData(row: number): T | undefined;
/**
* Implementations of this function must store the provided data parameter
* in the model at the specified row.
* @param _row index in range 0..(rowCount() - 1).
* @param _data new data item to store on the given row index
*/
setRowData(_row: number, _data: T): void {
console.log(
"setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
);
}
[Symbol.iterator](): Iterator<T> {
return new ModelIterator(this);
}
/**
* Notifies the view that the data of the current row is changed.
* @param row index of the changed row.
*/
protected notifyRowDataChanged(row: number): void {
napi.jsModelNotifyRowDataChanged(this.modelNotify, row);
}
/**
* Notifies the view that multiple rows are added to the model.
* @param row index of the first added row.
* @param count the number of added items.
*/
protected notifyRowAdded(row: number, count: number): void {
napi.jsModelNotifyRowAdded(this.modelNotify, row, count);
}
/**
* Notifies the view that multiple rows are removed to the model.
* @param row index of the first removed row.
* @param count the number of removed items.
*/
protected notifyRowRemoved(row: number, count: number): void {
napi.jsModelNotifyRowRemoved(this.modelNotify, row, count);
}
/**
* Notifies the view that the complete data must be reload.
*/
protected notifyReset(): void {
napi.jsModelNotifyReset(this.modelNotify);
}
}
/**
* ArrayModel wraps a JavaScript array for use in `.slint` views. The underlying
* array can be modified with the [[ArrayModel.push]] and [[ArrayModel.remove]] methods.
*/
export class ArrayModel<T> extends Model<T> {
/**
* @hidden
*/
#array: Array<T>;
/**
* Creates a new ArrayModel.
*
* @param arr
*/
constructor(arr: Array<T>) {
super();
this.#array = arr;
}
/**
* Returns the number of entries in the array model.
*/
get length(): number {
return this.#array.length;
}
/**
* Returns the number of entries in the array model.
*/
rowCount() {
return this.#array.length;
}
/**
* Returns the data at the specified row.
* @param row index in range 0..(rowCount() - 1).
* @returns undefined if row is out of range otherwise the data.
*/
rowData(row: number) {
return this.#array[row];
}
/**
* Stores the given data on the given row index and notifies run-time about the changed row.
* @param row index in range 0..(rowCount() - 1).
* @param data new data item to store on the given row index
*/
setRowData(row: number, data: T) {
this.#array[row] = data;
this.notifyRowDataChanged(row);
}
/**
* Pushes new values to the array that's backing the model and notifies
* the run-time about the added rows.
* @param values list of values that will be pushed to the array.
*/
push(...values: T[]) {
const size = this.#array.length;
Array.prototype.push.apply(this.#array, values);
this.notifyRowAdded(size, arguments.length);
}
/**
* Removes the last element from the array and returns it.
*
* @returns The removed element or undefined if the array is empty.
*/
pop(): T | undefined {
const last = this.#array.pop();
if (last !== undefined) {
this.notifyRowRemoved(this.#array.length, 1);
}
return last;
}
// FIXME: should this be named splice and have the splice api?
/**
* Removes the specified number of element from the array that's backing
* the model, starting at the specified index.
* @param index index of first row to remove.
* @param size number of rows to remove.
*/
remove(index: number, size: number) {
const r = this.#array.splice(index, size);
this.notifyRowRemoved(index, size);
}
/**
* Returns an iterable of values in the array.
*/
values(): IterableIterator<T> {
return this.#array.values();
}
/**
* Returns an iterable of key, value pairs for every entry in the array.
*/
entries(): IterableIterator<[number, T]> {
return this.#array.entries();
}
}

View file

@ -49,7 +49,7 @@ lazy_static::lazy_static! {
check_output(o);
node_dir.join("index.js")
node_dir.join("dist/index.js")
};
}