slint-ui 1.9.0-nightly.2024100903 → 1.9.0-nightly.2024101016

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/Cargo.toml CHANGED
@@ -41,10 +41,10 @@ accessibility = ["slint-interpreter/accessibility"]
41
41
  [dependencies]
42
42
  napi = { version = "2.14.0", default-features = false, features = ["napi8"] }
43
43
  napi-derive = "2.14.0"
44
- i-slint-compiler = { features = ["default"] , git = "https://github.com/slint-ui/slint", rev = "d38d968f9b72f99ae3fb71b5c288bd4dd395aaaf", version = "=1.9.0", default-features = false }
45
- i-slint-core = { features = ["default"] , git = "https://github.com/slint-ui/slint", rev = "d38d968f9b72f99ae3fb71b5c288bd4dd395aaaf", version = "=1.9.0", default-features = false }
46
- i-slint-backend-selector = { git = "https://github.com/slint-ui/slint", rev = "d38d968f9b72f99ae3fb71b5c288bd4dd395aaaf", version = "=1.9.0", default-features = false }
47
- slint-interpreter = { default-features = false , features = ["display-diagnostics", "internal", "compat-1-2"] , git = "https://github.com/slint-ui/slint", rev = "d38d968f9b72f99ae3fb71b5c288bd4dd395aaaf", version = "=1.9.0"}
44
+ i-slint-compiler = { features = ["default"] , git = "https://github.com/slint-ui/slint", rev = "ccf5f04087c5d920c80e7e706230e99a12b9e9ef", version = "=1.9.0", default-features = false }
45
+ i-slint-core = { features = ["default", "gettext-rs"] , git = "https://github.com/slint-ui/slint", rev = "ccf5f04087c5d920c80e7e706230e99a12b9e9ef", version = "=1.9.0", default-features = false }
46
+ i-slint-backend-selector = { git = "https://github.com/slint-ui/slint", rev = "ccf5f04087c5d920c80e7e706230e99a12b9e9ef", version = "=1.9.0", default-features = false }
47
+ slint-interpreter = { default-features = false , features = ["display-diagnostics", "internal", "compat-1-2"] , git = "https://github.com/slint-ui/slint", rev = "ccf5f04087c5d920c80e7e706230e99a12b9e9ef", version = "=1.9.0"}
48
48
  spin_on = { version = "0.1" }
49
49
  css-color-parser2 = { version = "1.0.1" }
50
50
  itertools = { version = "0.13" }
package/README.md CHANGED
@@ -57,7 +57,7 @@ Combining these two steps leads us to the obligatory "Hello World" example:
57
57
 
58
58
  ```js
59
59
  import * as slint from "slint-ui";
60
- let ui = slint.loadFile(".ui/main.slint");
60
+ let ui = slint.loadFile(new URL(".ui/main.slint", import.meta.url));
61
61
  let main = new ui.Main();
62
62
  main.run();
63
63
  ```
@@ -94,7 +94,7 @@ an object which allow to initialize the value of public properties or callbacks.
94
94
  import * as slint from "slint-ui";
95
95
  // In this example, the main.slint file exports a module which
96
96
  // has a counter property and a clicked callback
97
- let ui = slint.loadFile("ui/main.slint");
97
+ let ui = slint.loadFile(new URL("ui/main.slint", import.meta.url));
98
98
  let component = new ui.MainWindow({
99
99
  counter: 42,
100
100
  clicked: function() { console.log("hello"); }
@@ -135,7 +135,7 @@ The callbacks in Slint are exposed as properties in JavaScript and that can be c
135
135
  ```js
136
136
  import * as slint from "slint-ui";
137
137
 
138
- let ui = slint.loadFile("ui/my-component.slint");
138
+ let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url));
139
139
  let component = new ui.MyComponent();
140
140
 
141
141
  // connect to a callback
@@ -168,7 +168,7 @@ If the function is marked `public`, it can also be called from JavaScript.
168
168
  ```js
169
169
  import * as slint from "slint-ui";
170
170
 
171
- let ui = slint.loadFile("ui/my-component.slint");
171
+ let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url));
172
172
  let component = new ui.MyComponent();
173
173
 
174
174
  // call a public function
@@ -273,3 +273,72 @@ model.push(4); // this works
273
273
  // does NOT work, getting the model does not return the right object
274
274
  // component.model.push(5);
275
275
  ```
276
+
277
+ ### structs
278
+
279
+ An exported struct can be created either by defing of an object literal or by using the new keyword.
280
+
281
+ **`my-component.slint`**
282
+
283
+ ```slint
284
+ export struct Person {
285
+ name: string,
286
+ age: int
287
+ }
288
+
289
+ export component MyComponent inherits Window {
290
+ in-out property <Person> person;
291
+ }
292
+ ```
293
+
294
+ **`main.js`**
295
+
296
+ ```js
297
+
298
+ import * as slint from "slint-ui";
299
+
300
+ let ui = slint.loadFile(new URL("my-component.slint", import.meta.url));
301
+ let component = new ui.MyComponent();
302
+
303
+ // object literal
304
+ component.person = { name: "Peter", age: 22 };
305
+
306
+ // new keyword (sets property values to default e.g. '' for string)
307
+ component.person = new ui.Person();
308
+
309
+ // new keyword with parameters
310
+ component.person = new ui.Person({ name: "Tim", age: 30 });
311
+ ```
312
+
313
+ ### enums
314
+
315
+ A value of an exported enum can be set as string or by usign the value from the exported enum.
316
+
317
+ **`my-component.slint`**
318
+
319
+ ```slint
320
+ export enum Position {
321
+ top,
322
+ bottom
323
+ }
324
+
325
+ export component MyComponent inherits Window {
326
+ in-out property <Position> position;
327
+ }
328
+ ```
329
+
330
+ **`main.js`**
331
+
332
+ ```js
333
+
334
+ import * as slint from "slint-ui";
335
+
336
+ let ui = slint.loadFile(new URL("my-component.slint", import.meta.url));
337
+ let component = new ui.MyComponent();
338
+
339
+ // set enum value as string
340
+ component.position = "top";
341
+
342
+ // use the value of the enum
343
+ component.position = ui.Position.bottom;
344
+ ```
package/cover.md CHANGED
@@ -271,6 +271,75 @@ component.model = component.model.concat(4);
271
271
 
272
272
  Another option is to set an object that implements the {@link Model} interface.
273
273
 
274
+ ### structs
275
+
276
+ An exported struct can be created either by defing of an object literal or by using the new keyword.
277
+
278
+ **`my-component.slint`**
279
+
280
+ ```
281
+ export struct Person {
282
+ name: string,
283
+ age: int
284
+ }
285
+
286
+ export component MyComponent inherits Window {
287
+ in-out property <Person> person;
288
+ }
289
+ ```
290
+
291
+ **`main.js`**
292
+
293
+ ```js
294
+
295
+ import * as slint from "slint-ui";
296
+
297
+ let ui = slint.loadFile("my-component.slint");
298
+ let component = new ui.MyComponent();
299
+
300
+ // object literal
301
+ component.person = { name: "Peter", age: 22 };
302
+
303
+ // new keyword (sets property values to default e.g. '' for string)
304
+ component.person = new ui.Person();
305
+
306
+ // new keyword with parameters
307
+ component.person = new ui.Person({ name: "Tim", age: 30 });
308
+ ```
309
+
310
+ ### enums
311
+
312
+ A value of an exported enum can be set as string or by usign the value from the exported enum.
313
+
314
+ **`my-component.slint`**
315
+
316
+ ```
317
+ export enum Position {
318
+ top,
319
+ bottom
320
+ }
321
+
322
+ export component MyComponent inherits Window {
323
+ in-out property <Position> position;
324
+ }
325
+ ```
326
+
327
+ **`main.js`**
328
+
329
+ ```js
330
+
331
+ import * as slint from "slint-ui";
332
+
333
+ let ui = slint.loadFile("my-component.slint");
334
+ let component = new ui.MyComponent();
335
+
336
+ // set enum value as string
337
+ component.position = "top";
338
+
339
+ // use the value of the enum
340
+ component.position = ui.Position.bottom;
341
+ ```
342
+
274
343
  ### Globals
275
344
 
276
345
  You can declare [globally available singletons](../slint/src/language/syntax/globals) in your
@@ -302,3 +371,4 @@ component.Logic.to_upper_case = (str) => {
302
371
 
303
372
  **Note**: Global singletons are instantiated once per component. When declaring multiple components for `export` to JavaScript,
304
373
  each instance will have their own instance of associated globals singletons.
374
+
package/dist/index.d.ts CHANGED
@@ -194,7 +194,7 @@ export interface LoadFileOptions {
194
194
  * main.greeting = "Hello friends";
195
195
  * ```
196
196
  *
197
- * @param filePath The path to the file to load. Relative paths are resolved against the process' current working directory.
197
+ * @param filePath The path to the file to load as `string` or `URL`. Relative paths are resolved against the process' current working directory.
198
198
  * @param options An optional {@link LoadFileOptions} to configure additional Slint compilation settings,
199
199
  * such as include search paths, library imports, or the widget style.
200
200
  * @returns Returns an object that is immutable and provides a constructor function for each exported Window component found in the `.slint` file.
@@ -203,7 +203,7 @@ export interface LoadFileOptions {
203
203
  * For further information on the available properties, refer to [Instantiating A Component](../index.html#md:instantiating-a-component).
204
204
  * @throws {@link CompileError} if errors occur during compilation.
205
205
  */
206
- export declare function loadFile(filePath: string, options?: LoadFileOptions): Object;
206
+ export declare function loadFile(filePath: string | URL, options?: LoadFileOptions): Object;
207
207
  /**
208
208
  * Loads the given Slint source code and returns an object that contains a functions to construct the exported
209
209
  * components of the Slint source code.
@@ -404,6 +404,24 @@ export declare namespace private_api {
404
404
  rowData(row: number): U | undefined;
405
405
  }
406
406
  }
407
+ /**
408
+ * Initialize translations.
409
+ *
410
+ * Call this with the path where translations are located. This function internally calls the [bindtextdomain](https://man7.org/linux/man-pages/man3/bindtextdomain.3.html) function from gettext.
411
+ *
412
+ * Translations are expected to be found at <path>/<locale>/LC_MESSAGES/<domain>.mo, where path is the directory passed as an argument to this function, locale is a locale name (e.g., en, en_GB, fr), and domain is the package name.
413
+ *
414
+ * @param domain defines the domain name e.g. name of the package.
415
+ * @param path specifies the directory as `string` or as `URL` in which gettext should search for translations.
416
+ *
417
+ * For example, assuming this is in a package called example and the default locale is configured to be French, it will load translations at runtime from ``/path/to/example/translations/fr/LC_MESSAGES/example.mo`.
418
+ *
419
+ * ```js
420
+ * import * as slint from "slint-ui";
421
+ * slint.initTranslations("example", new URL("translations/", import.meta.url));
422
+ * ````
423
+ */
424
+ export declare function initTranslations(domain: string, path: string | URL): void;
407
425
  /**
408
426
  * @hidden
409
427
  */
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright © SixtyFPS GmbH <info@slint.dev>
3
3
  // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.private_api = exports.quitEventLoop = exports.runEventLoop = exports.loadSource = exports.loadFile = exports.CompileError = exports.ArrayModel = exports.Model = exports.Brush = exports.RgbaColor = exports.DiagnosticLevel = exports.Diagnostic = void 0;
5
+ exports.initTranslations = exports.private_api = exports.quitEventLoop = exports.runEventLoop = exports.loadSource = exports.loadFile = exports.CompileError = exports.ArrayModel = exports.Model = exports.Brush = exports.RgbaColor = exports.DiagnosticLevel = exports.Diagnostic = void 0;
6
6
  const napi = require("../rust-module.cjs");
7
7
  var rust_module_cjs_1 = require("../rust-module.cjs");
8
8
  Object.defineProperty(exports, "Diagnostic", { enumerable: true, get: function () { return rust_module_cjs_1.Diagnostic; } });
@@ -72,6 +72,9 @@ class CompileError extends Error {
72
72
  }
73
73
  }
74
74
  exports.CompileError = CompileError;
75
+ function translateName(key) {
76
+ return key.replace(/-/g, "_");
77
+ }
75
78
  function loadSlint(loadData) {
76
79
  const { filePath, options } = loadData.fileData;
77
80
  const compiler = new napi.ComponentCompiler();
@@ -101,14 +104,44 @@ function loadSlint(loadData) {
101
104
  }
102
105
  }
103
106
  const slint_module = Object.create({});
107
+ // generate structs
108
+ const structs = compiler.structs;
109
+ for (const key in compiler.structs) {
110
+ Object.defineProperty(slint_module, translateName(key), {
111
+ value: function (properties) {
112
+ const defaultObject = structs[key];
113
+ const newObject = Object.create({});
114
+ for (const propertyKey in defaultObject) {
115
+ const propertyName = translateName(propertyKey);
116
+ const propertyValue = properties !== undefined &&
117
+ Object.hasOwn(properties, propertyName)
118
+ ? properties[propertyName]
119
+ : defaultObject[propertyKey];
120
+ Object.defineProperty(newObject, propertyName, {
121
+ value: propertyValue,
122
+ writable: true,
123
+ enumerable: true,
124
+ });
125
+ }
126
+ return Object.seal(newObject);
127
+ },
128
+ });
129
+ }
130
+ // generate enums
131
+ const enums = compiler.enums;
132
+ for (const key in enums) {
133
+ Object.defineProperty(slint_module, translateName(key), {
134
+ value: Object.seal(enums[key]),
135
+ enumerable: true,
136
+ });
137
+ }
104
138
  Object.keys(definitions).forEach((key) => {
105
139
  const definition = definitions[key];
106
- Object.defineProperty(slint_module, definition.name.replace(/-/g, "_"), {
140
+ Object.defineProperty(slint_module, translateName(definition.name), {
107
141
  value: function (properties) {
108
142
  const instance = definition.create();
109
143
  if (instance == null) {
110
- throw Error("Could not create a component handle for" +
111
- filePath);
144
+ throw Error("Could not create a component handle for" + filePath);
112
145
  }
113
146
  for (var key in properties) {
114
147
  const value = properties[key];
@@ -121,7 +154,7 @@ function loadSlint(loadData) {
121
154
  }
122
155
  const componentHandle = new Component(instance);
123
156
  instance.definition().properties.forEach((prop) => {
124
- const propName = prop.name.replace(/-/g, "_");
157
+ const propName = translateName(prop.name);
125
158
  if (componentHandle[propName] !== undefined) {
126
159
  console.warn("Duplicated property name " + propName);
127
160
  }
@@ -138,12 +171,12 @@ function loadSlint(loadData) {
138
171
  }
139
172
  });
140
173
  instance.definition().callbacks.forEach((cb) => {
141
- const callbackName = cb.replace(/-/g, "_");
174
+ const callbackName = translateName(cb);
142
175
  if (componentHandle[callbackName] !== undefined) {
143
176
  console.warn("Duplicated callback name " + callbackName);
144
177
  }
145
178
  else {
146
- Object.defineProperty(componentHandle, cb.replace(/-/g, "_"), {
179
+ Object.defineProperty(componentHandle, translateName(cb), {
147
180
  get() {
148
181
  return function () {
149
182
  return instance.invoke(cb, Array.from(arguments));
@@ -157,12 +190,12 @@ function loadSlint(loadData) {
157
190
  }
158
191
  });
159
192
  instance.definition().functions.forEach((cb) => {
160
- const functionName = cb.replace(/-/g, "_");
193
+ const functionName = translateName(cb);
161
194
  if (componentHandle[functionName] !== undefined) {
162
195
  console.warn("Duplicated function name " + functionName);
163
196
  }
164
197
  else {
165
- Object.defineProperty(componentHandle, cb.replace(/-/g, "_"), {
198
+ Object.defineProperty(componentHandle, translateName(cb), {
166
199
  get() {
167
200
  return function () {
168
201
  return instance.invoke(cb, Array.from(arguments));
@@ -183,7 +216,7 @@ function loadSlint(loadData) {
183
216
  .definition()
184
217
  .globalProperties(globalName)
185
218
  .forEach((prop) => {
186
- const propName = prop.name.replace(/-/g, "_");
219
+ const propName = translateName(prop.name);
187
220
  if (globalObject[propName] !== undefined) {
188
221
  console.warn("Duplicated property name " +
189
222
  propName +
@@ -206,7 +239,7 @@ function loadSlint(loadData) {
206
239
  .definition()
207
240
  .globalCallbacks(globalName)
208
241
  .forEach((cb) => {
209
- const callbackName = cb.replace(/-/g, "_");
242
+ const callbackName = translateName(cb);
210
243
  if (globalObject[callbackName] !== undefined) {
211
244
  console.warn("Duplicated property name " +
212
245
  cb +
@@ -214,7 +247,7 @@ function loadSlint(loadData) {
214
247
  global);
215
248
  }
216
249
  else {
217
- Object.defineProperty(globalObject, cb.replace(/-/g, "_"), {
250
+ Object.defineProperty(globalObject, translateName(cb), {
218
251
  get() {
219
252
  return function () {
220
253
  return instance.invokeGlobal(globalName, cb, Array.from(arguments));
@@ -231,7 +264,7 @@ function loadSlint(loadData) {
231
264
  .definition()
232
265
  .globalFunctions(globalName)
233
266
  .forEach((cb) => {
234
- const functionName = cb.replace(/-/g, "_");
267
+ const functionName = translateName(cb);
235
268
  if (globalObject[functionName] !== undefined) {
236
269
  console.warn("Duplicated function name " +
237
270
  cb +
@@ -239,7 +272,7 @@ function loadSlint(loadData) {
239
272
  global);
240
273
  }
241
274
  else {
242
- Object.defineProperty(globalObject, cb.replace(/-/g, "_"), {
275
+ Object.defineProperty(globalObject, translateName(cb), {
243
276
  get() {
244
277
  return function () {
245
278
  return instance.invokeGlobal(globalName, cb, Array.from(arguments));
@@ -286,7 +319,7 @@ function loadSlint(loadData) {
286
319
  * main.greeting = "Hello friends";
287
320
  * ```
288
321
  *
289
- * @param filePath The path to the file to load. Relative paths are resolved against the process' current working directory.
322
+ * @param filePath The path to the file to load as `string` or `URL`. Relative paths are resolved against the process' current working directory.
290
323
  * @param options An optional {@link LoadFileOptions} to configure additional Slint compilation settings,
291
324
  * such as include search paths, library imports, or the widget style.
292
325
  * @returns Returns an object that is immutable and provides a constructor function for each exported Window component found in the `.slint` file.
@@ -296,8 +329,9 @@ function loadSlint(loadData) {
296
329
  * @throws {@link CompileError} if errors occur during compilation.
297
330
  */
298
331
  function loadFile(filePath, options) {
332
+ const pathname = filePath instanceof URL ? filePath.pathname : filePath;
299
333
  return loadSlint({
300
- fileData: { filePath, options },
334
+ fileData: { filePath: pathname, options },
301
335
  from: "file",
302
336
  });
303
337
  }
@@ -570,6 +604,28 @@ var private_api;
570
604
  }
571
605
  private_api.MapModel = MapModel;
572
606
  })(private_api || (exports.private_api = private_api = {}));
607
+ /**
608
+ * Initialize translations.
609
+ *
610
+ * Call this with the path where translations are located. This function internally calls the [bindtextdomain](https://man7.org/linux/man-pages/man3/bindtextdomain.3.html) function from gettext.
611
+ *
612
+ * Translations are expected to be found at <path>/<locale>/LC_MESSAGES/<domain>.mo, where path is the directory passed as an argument to this function, locale is a locale name (e.g., en, en_GB, fr), and domain is the package name.
613
+ *
614
+ * @param domain defines the domain name e.g. name of the package.
615
+ * @param path specifies the directory as `string` or as `URL` in which gettext should search for translations.
616
+ *
617
+ * For example, assuming this is in a package called example and the default locale is configured to be French, it will load translations at runtime from ``/path/to/example/translations/fr/LC_MESSAGES/example.mo`.
618
+ *
619
+ * ```js
620
+ * import * as slint from "slint-ui";
621
+ * slint.initTranslations("example", new URL("translations/", import.meta.url));
622
+ * ````
623
+ */
624
+ function initTranslations(domain, path) {
625
+ const pathname = path instanceof URL ? path.pathname : path;
626
+ napi.initTranslations(domain, pathname);
627
+ }
628
+ exports.initTranslations = initTranslations;
573
629
  /**
574
630
  * @hidden
575
631
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slint-ui",
3
- "version": "1.9.0-nightly.2024100903",
3
+ "version": "1.9.0-nightly.2024101016",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "homepage": "https://github.com/slint-ui/slint",
@@ -67,10 +67,10 @@
67
67
  "@napi-rs/cli": "2.16.5"
68
68
  },
69
69
  "optionalDependencies": {
70
- "@slint-ui/slint-ui-binary-linux-x64-gnu": "1.9.0-nightly.2024100903",
71
- "@slint-ui/slint-ui-binary-darwin-x64": "1.9.0-nightly.2024100903",
72
- "@slint-ui/slint-ui-binary-darwin-arm64": "1.9.0-nightly.2024100903",
73
- "@slint-ui/slint-ui-binary-win32-x64-msvc": "1.9.0-nightly.2024100903",
74
- "@slint-ui/slint-ui-binary-win32-ia32-msvc": "1.9.0-nightly.2024100903"
70
+ "@slint-ui/slint-ui-binary-linux-x64-gnu": "1.9.0-nightly.2024101016",
71
+ "@slint-ui/slint-ui-binary-darwin-x64": "1.9.0-nightly.2024101016",
72
+ "@slint-ui/slint-ui-binary-darwin-arm64": "1.9.0-nightly.2024101016",
73
+ "@slint-ui/slint-ui-binary-win32-x64-msvc": "1.9.0-nightly.2024101016",
74
+ "@slint-ui/slint-ui-binary-win32-ia32-msvc": "1.9.0-nightly.2024101016"
75
75
  }
76
76
  }
@@ -1,10 +1,16 @@
1
1
  // Copyright © SixtyFPS GmbH <info@slint.dev>
2
2
  // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
3
 
4
+ use crate::to_js_unknown;
5
+
4
6
  use super::JsComponentDefinition;
5
7
  use super::JsDiagnostic;
8
+ use i_slint_compiler::langtype::Type;
6
9
  use itertools::Itertools;
10
+ use napi::Env;
11
+ use napi::JsUnknown;
7
12
  use slint_interpreter::Compiler;
13
+ use slint_interpreter::Value;
8
14
  use std::collections::HashMap;
9
15
  use std::path::PathBuf;
10
16
 
@@ -13,6 +19,7 @@ use std::path::PathBuf;
13
19
  #[napi(js_name = "ComponentCompiler")]
14
20
  pub struct JsComponentCompiler {
15
21
  internal: Compiler,
22
+ structs_and_enums: Vec<Type>,
16
23
  diagnostics: Vec<slint_interpreter::Diagnostic>,
17
24
  }
18
25
 
@@ -44,7 +51,7 @@ impl JsComponentCompiler {
44
51
 
45
52
  compiler.set_include_paths(include_paths);
46
53
  compiler.set_library_paths(library_paths);
47
- Self { internal: compiler, diagnostics: vec![] }
54
+ Self { internal: compiler, diagnostics: vec![], structs_and_enums: vec![] }
48
55
  }
49
56
 
50
57
  #[napi(setter)]
@@ -99,12 +106,72 @@ impl JsComponentCompiler {
99
106
  self.diagnostics.iter().map(|d| JsDiagnostic::from(d.clone())).collect()
100
107
  }
101
108
 
109
+ #[napi(getter)]
110
+ pub fn structs(&self, env: Env) -> HashMap<String, JsUnknown> {
111
+ fn convert_type(env: &Env, ty: &Type) -> Option<(String, JsUnknown)> {
112
+ match ty {
113
+ Type::Struct { fields, name: Some(name), node: Some(_), .. } => {
114
+ let struct_instance = to_js_unknown(
115
+ env,
116
+ &Value::Struct(slint_interpreter::Struct::from_iter(fields.iter().map(
117
+ |(name, field_type)| {
118
+ (
119
+ name.to_string(),
120
+ slint_interpreter::default_value_for_type(field_type),
121
+ )
122
+ },
123
+ ))),
124
+ );
125
+
126
+ return Some((name.to_string(), struct_instance.ok()?));
127
+ }
128
+ _ => return None,
129
+ }
130
+ }
131
+
132
+ self.structs_and_enums
133
+ .iter()
134
+ .filter_map(|ty| convert_type(&env, ty))
135
+ .into_iter()
136
+ .collect::<HashMap<String, JsUnknown>>()
137
+ }
138
+
139
+ #[napi(getter)]
140
+ pub fn enums(&self, env: Env) -> HashMap<String, JsUnknown> {
141
+ fn convert_type(env: &Env, ty: &Type) -> Option<(String, JsUnknown)> {
142
+ match ty {
143
+ Type::Enumeration(en) => {
144
+ let mut o = env.create_object().ok()?;
145
+
146
+ for value in en.values.iter() {
147
+ let value = value.replace('-', "_");
148
+ o.set_property(
149
+ env.create_string(&value).ok()?,
150
+ env.create_string(&value).ok()?.into_unknown(),
151
+ )
152
+ .ok()?;
153
+ }
154
+ return Some((en.name.clone(), o.into_unknown()));
155
+ }
156
+ _ => return None,
157
+ }
158
+ }
159
+
160
+ self.structs_and_enums
161
+ .iter()
162
+ .filter_map(|ty| convert_type(&env, ty))
163
+ .into_iter()
164
+ .collect::<HashMap<String, JsUnknown>>()
165
+ }
166
+
102
167
  /// Compile a .slint file into a ComponentDefinition
103
168
  ///
104
169
  /// Returns the compiled `ComponentDefinition` if there were no errors.
105
170
  #[napi]
106
171
  pub fn build_from_path(&mut self, path: String) -> HashMap<String, JsComponentDefinition> {
107
172
  let r = spin_on::spin_on(self.internal.build_from_path(PathBuf::from(path)));
173
+ self.structs_and_enums =
174
+ r.structs_and_enums(i_slint_core::InternalToken {}).cloned().collect::<Vec<_>>();
108
175
  self.diagnostics = r.diagnostics().collect();
109
176
  r.components().map(|c| (c.name().to_owned(), c.into())).collect()
110
177
  }
@@ -118,6 +185,8 @@ impl JsComponentCompiler {
118
185
  ) -> HashMap<String, JsComponentDefinition> {
119
186
  let r = spin_on::spin_on(self.internal.build_from_source(source_code, PathBuf::from(path)));
120
187
  self.diagnostics = r.diagnostics().collect();
188
+ self.structs_and_enums =
189
+ r.structs_and_enums(i_slint_core::InternalToken {}).cloned().collect::<Vec<_>>();
121
190
  r.components().map(|c| (c.name().to_owned(), c.into())).collect()
122
191
  }
123
192
  }
package/rust/lib.rs CHANGED
@@ -2,6 +2,8 @@
2
2
  // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
3
 
4
4
  mod interpreter;
5
+ use std::path::PathBuf;
6
+
5
7
  pub use interpreter::*;
6
8
 
7
9
  mod types;
@@ -86,6 +88,12 @@ pub fn init_testing() {
86
88
  i_slint_backend_testing::init_integration_test_with_mock_time();
87
89
  }
88
90
 
91
+ #[napi]
92
+ pub fn init_translations(domain: String, dir_name: String) -> napi::Result<()> {
93
+ i_slint_core::translations::gettext_bindtextdomain(domain.as_str(), PathBuf::from(dir_name))
94
+ .map_err(|e| napi::Error::from_reason(e.to_string()))
95
+ }
96
+
89
97
  pub fn print_to_console(env: Env, function: &str, arguments: core::fmt::Arguments) {
90
98
  let Ok(global) = env.get_global() else {
91
99
  eprintln!("Unable to obtain global object");
package/rust-module.cjs CHANGED
@@ -252,7 +252,7 @@ if (!nativeBinding) {
252
252
  throw new Error(`Failed to load native binding`)
253
253
  }
254
254
 
255
- const { DiagnosticLevel, ComponentCompiler, ComponentDefinition, ComponentInstance, ValueType, Property, Window, SlintRgbaColor, SlintBrush, SlintImageData, SharedModelNotify, jsModelNotifyNew, jsModelNotifyRowDataChanged, jsModelNotifyRowAdded, jsModelNotifyRowRemoved, jsModelNotifyReset, ReadOnlyRustModel, ModelIterator, SlintPoint, SlintSize, mockElapsedTime, getMockedTime, ProcessEventsResult, processEvents, invokeFromEventLoop, setQuitOnLastWindowClosed, initTesting } = nativeBinding
255
+ const { DiagnosticLevel, ComponentCompiler, ComponentDefinition, ComponentInstance, ValueType, Property, Window, SlintRgbaColor, SlintBrush, SlintImageData, SharedModelNotify, jsModelNotifyNew, jsModelNotifyRowDataChanged, jsModelNotifyRowAdded, jsModelNotifyRowRemoved, jsModelNotifyReset, ReadOnlyRustModel, ModelIterator, SlintPoint, SlintSize, mockElapsedTime, getMockedTime, ProcessEventsResult, processEvents, invokeFromEventLoop, setQuitOnLastWindowClosed, initTesting, initTranslations } = nativeBinding
256
256
 
257
257
  module.exports.DiagnosticLevel = DiagnosticLevel
258
258
  module.exports.ComponentCompiler = ComponentCompiler
@@ -281,3 +281,4 @@ module.exports.processEvents = processEvents
281
281
  module.exports.invokeFromEventLoop = invokeFromEventLoop
282
282
  module.exports.setQuitOnLastWindowClosed = setQuitOnLastWindowClosed
283
283
  module.exports.initTesting = initTesting
284
+ module.exports.initTranslations = initTranslations
package/rust-module.d.ts CHANGED
@@ -83,6 +83,7 @@ export declare function processEvents(): ProcessEventsResult
83
83
  export declare function invokeFromEventLoop(callback: (...args: any[]) => any): void
84
84
  export declare function setQuitOnLastWindowClosed(quitOnLastWindowClosed: boolean): void
85
85
  export declare function initTesting(): void
86
+ export declare function initTranslations(domain: string, dirName: string): void
86
87
  export type JsComponentCompiler = ComponentCompiler
87
88
  /**
88
89
  * ComponentCompiler is the entry point to the Slint interpreter that can be used
@@ -98,6 +99,8 @@ export class ComponentCompiler {
98
99
  set style(style: string)
99
100
  get style(): string | null
100
101
  get diagnostics(): Array<Diagnostic>
102
+ get structs(): Record<string, unknown>
103
+ get enums(): Record<string, unknown>
101
104
  /**
102
105
  * Compile a .slint file into a ComponentDefinition
103
106
  *
@@ -266,6 +266,10 @@ type LoadData =
266
266
  from: "source";
267
267
  };
268
268
 
269
+ function translateName(key: string): string {
270
+ return key.replace(/-/g, "_");
271
+ }
272
+
269
273
  function loadSlint(loadData: LoadData): Object {
270
274
  const { filePath, options } = loadData.fileData;
271
275
 
@@ -309,252 +313,271 @@ function loadSlint(loadData: LoadData): Object {
309
313
 
310
314
  const slint_module = Object.create({});
311
315
 
316
+ // generate structs
317
+ const structs = compiler.structs;
318
+
319
+ for (const key in compiler.structs) {
320
+ Object.defineProperty(slint_module, translateName(key), {
321
+ value: function (properties: any) {
322
+ const defaultObject = structs[key];
323
+ const newObject = Object.create({});
324
+
325
+ for (const propertyKey in defaultObject) {
326
+ const propertyName = translateName(propertyKey);
327
+ const propertyValue =
328
+ properties !== undefined &&
329
+ Object.hasOwn(properties, propertyName)
330
+ ? properties[propertyName]
331
+ : defaultObject[propertyKey];
332
+
333
+ Object.defineProperty(newObject, propertyName, {
334
+ value: propertyValue,
335
+ writable: true,
336
+ enumerable: true,
337
+ });
338
+ }
339
+
340
+ return Object.seal(newObject);
341
+ },
342
+ });
343
+ }
344
+
345
+ // generate enums
346
+ const enums = compiler.enums;
347
+
348
+ for (const key in enums) {
349
+ Object.defineProperty(slint_module, translateName(key), {
350
+ value: Object.seal(enums[key]),
351
+ enumerable: true,
352
+ });
353
+ }
354
+
312
355
  Object.keys(definitions).forEach((key) => {
313
356
  const definition = definitions[key];
314
357
 
315
- Object.defineProperty(
316
- slint_module,
317
- definition.name.replace(/-/g, "_"),
318
- {
319
- value: function (properties: any) {
320
- const instance = definition.create();
321
-
322
- if (instance == null) {
323
- throw Error(
324
- "Could not create a component handle for" +
325
- filePath,
326
- );
327
- }
358
+ Object.defineProperty(slint_module, translateName(definition.name), {
359
+ value: function (properties: any) {
360
+ const instance = definition.create();
361
+
362
+ if (instance == null) {
363
+ throw Error(
364
+ "Could not create a component handle for" + filePath,
365
+ );
366
+ }
328
367
 
329
- for (var key in properties) {
330
- const value = properties[key];
368
+ for (var key in properties) {
369
+ const value = properties[key];
331
370
 
332
- if (value instanceof Function) {
333
- instance.setCallback(key, value);
334
- } else {
335
- instance.setProperty(key, properties[key]);
336
- }
371
+ if (value instanceof Function) {
372
+ instance.setCallback(key, value);
373
+ } else {
374
+ instance.setProperty(key, properties[key]);
337
375
  }
376
+ }
377
+
378
+ const componentHandle = new Component(instance!);
379
+ instance!.definition().properties.forEach((prop) => {
380
+ const propName = translateName(prop.name);
381
+
382
+ if (componentHandle[propName] !== undefined) {
383
+ console.warn("Duplicated property name " + propName);
384
+ } else {
385
+ Object.defineProperty(componentHandle, propName, {
386
+ get() {
387
+ return instance!.getProperty(prop.name);
388
+ },
389
+ set(value) {
390
+ instance!.setProperty(prop.name, value);
391
+ },
392
+ enumerable: true,
393
+ });
394
+ }
395
+ });
338
396
 
339
- const componentHandle = new Component(instance!);
340
- instance!.definition().properties.forEach((prop) => {
341
- const propName = prop.name.replace(/-/g, "_");
397
+ instance!.definition().callbacks.forEach((cb) => {
398
+ const callbackName = translateName(cb);
342
399
 
343
- if (componentHandle[propName] !== undefined) {
344
- console.warn(
345
- "Duplicated property name " + propName,
346
- );
347
- } else {
348
- Object.defineProperty(componentHandle, propName, {
400
+ if (componentHandle[callbackName] !== undefined) {
401
+ console.warn(
402
+ "Duplicated callback name " + callbackName,
403
+ );
404
+ } else {
405
+ Object.defineProperty(
406
+ componentHandle,
407
+ translateName(cb),
408
+ {
349
409
  get() {
350
- return instance!.getProperty(prop.name);
410
+ return function () {
411
+ return instance!.invoke(
412
+ cb,
413
+ Array.from(arguments),
414
+ );
415
+ };
351
416
  },
352
- set(value) {
353
- instance!.setProperty(prop.name, value);
417
+ set(callback) {
418
+ instance!.setCallback(cb, callback);
354
419
  },
355
420
  enumerable: true,
356
- });
357
- }
358
- });
421
+ },
422
+ );
423
+ }
424
+ });
359
425
 
360
- instance!.definition().callbacks.forEach((cb) => {
361
- const callbackName = cb.replace(/-/g, "_");
362
-
363
- if (componentHandle[callbackName] !== undefined) {
364
- console.warn(
365
- "Duplicated callback name " + callbackName,
366
- );
367
- } else {
368
- Object.defineProperty(
369
- componentHandle,
370
- cb.replace(/-/g, "_"),
371
- {
372
- get() {
373
- return function () {
374
- return instance!.invoke(
375
- cb,
376
- Array.from(arguments),
377
- );
378
- };
379
- },
380
- set(callback) {
381
- instance!.setCallback(cb, callback);
382
- },
383
- enumerable: true,
384
- },
385
- );
386
- }
387
- });
426
+ instance!.definition().functions.forEach((cb) => {
427
+ const functionName = translateName(cb);
388
428
 
389
- instance!.definition().functions.forEach((cb) => {
390
- const functionName = cb.replace(/-/g, "_");
391
-
392
- if (componentHandle[functionName] !== undefined) {
393
- console.warn(
394
- "Duplicated function name " + functionName,
395
- );
396
- } else {
397
- Object.defineProperty(
398
- componentHandle,
399
- cb.replace(/-/g, "_"),
400
- {
401
- get() {
402
- return function () {
403
- return instance!.invoke(
404
- cb,
405
- Array.from(arguments),
406
- );
407
- };
408
- },
409
- enumerable: true,
429
+ if (componentHandle[functionName] !== undefined) {
430
+ console.warn(
431
+ "Duplicated function name " + functionName,
432
+ );
433
+ } else {
434
+ Object.defineProperty(
435
+ componentHandle,
436
+ translateName(cb),
437
+ {
438
+ get() {
439
+ return function () {
440
+ return instance!.invoke(
441
+ cb,
442
+ Array.from(arguments),
443
+ );
444
+ };
410
445
  },
411
- );
412
- }
413
- });
414
-
415
- // globals
416
- instance!.definition().globals.forEach((globalName) => {
417
- if (componentHandle[globalName] !== undefined) {
418
- console.warn(
419
- "Duplicated property name " + globalName,
420
- );
421
- } else {
422
- const globalObject = Object.create({});
423
-
424
- instance!
425
- .definition()
426
- .globalProperties(globalName)
427
- .forEach((prop) => {
428
- const propName = prop.name.replace(
429
- /-/g,
430
- "_",
446
+ enumerable: true,
447
+ },
448
+ );
449
+ }
450
+ });
451
+
452
+ // globals
453
+ instance!.definition().globals.forEach((globalName) => {
454
+ if (componentHandle[globalName] !== undefined) {
455
+ console.warn("Duplicated property name " + globalName);
456
+ } else {
457
+ const globalObject = Object.create({});
458
+
459
+ instance!
460
+ .definition()
461
+ .globalProperties(globalName)
462
+ .forEach((prop) => {
463
+ const propName = translateName(prop.name);
464
+
465
+ if (globalObject[propName] !== undefined) {
466
+ console.warn(
467
+ "Duplicated property name " +
468
+ propName +
469
+ " on global " +
470
+ global,
431
471
  );
472
+ } else {
473
+ Object.defineProperty(
474
+ globalObject,
475
+ propName,
476
+ {
477
+ get() {
478
+ return instance!.getGlobalProperty(
479
+ globalName,
480
+ prop.name,
481
+ );
482
+ },
483
+ set(value) {
484
+ instance!.setGlobalProperty(
485
+ globalName,
486
+ prop.name,
487
+ value,
488
+ );
489
+ },
490
+ enumerable: true,
491
+ },
492
+ );
493
+ }
494
+ });
432
495
 
433
- if (globalObject[propName] !== undefined) {
434
- console.warn(
435
- "Duplicated property name " +
436
- propName +
437
- " on global " +
438
- global,
439
- );
440
- } else {
441
- Object.defineProperty(
442
- globalObject,
443
- propName,
444
- {
445
- get() {
446
- return instance!.getGlobalProperty(
447
- globalName,
448
- prop.name,
449
- );
450
- },
451
- set(value) {
452
- instance!.setGlobalProperty(
496
+ instance!
497
+ .definition()
498
+ .globalCallbacks(globalName)
499
+ .forEach((cb) => {
500
+ const callbackName = translateName(cb);
501
+
502
+ if (globalObject[callbackName] !== undefined) {
503
+ console.warn(
504
+ "Duplicated property name " +
505
+ cb +
506
+ " on global " +
507
+ global,
508
+ );
509
+ } else {
510
+ Object.defineProperty(
511
+ globalObject,
512
+ translateName(cb),
513
+ {
514
+ get() {
515
+ return function () {
516
+ return instance!.invokeGlobal(
453
517
  globalName,
454
- prop.name,
455
- value,
518
+ cb,
519
+ Array.from(arguments),
456
520
  );
457
- },
458
- enumerable: true,
521
+ };
459
522
  },
460
- );
461
- }
462
- });
463
-
464
- instance!
465
- .definition()
466
- .globalCallbacks(globalName)
467
- .forEach((cb) => {
468
- const callbackName = cb.replace(/-/g, "_");
469
-
470
- if (
471
- globalObject[callbackName] !== undefined
472
- ) {
473
- console.warn(
474
- "Duplicated property name " +
475
- cb +
476
- " on global " +
477
- global,
478
- );
479
- } else {
480
- Object.defineProperty(
481
- globalObject,
482
- cb.replace(/-/g, "_"),
483
- {
484
- get() {
485
- return function () {
486
- return instance!.invokeGlobal(
487
- globalName,
488
- cb,
489
- Array.from(
490
- arguments,
491
- ),
492
- );
493
- };
494
- },
495
- set(callback) {
496
- instance!.setGlobalCallback(
523
+ set(callback) {
524
+ instance!.setGlobalCallback(
525
+ globalName,
526
+ cb,
527
+ callback,
528
+ );
529
+ },
530
+ enumerable: true,
531
+ },
532
+ );
533
+ }
534
+ });
535
+
536
+ instance!
537
+ .definition()
538
+ .globalFunctions(globalName)
539
+ .forEach((cb) => {
540
+ const functionName = translateName(cb);
541
+
542
+ if (globalObject[functionName] !== undefined) {
543
+ console.warn(
544
+ "Duplicated function name " +
545
+ cb +
546
+ " on global " +
547
+ global,
548
+ );
549
+ } else {
550
+ Object.defineProperty(
551
+ globalObject,
552
+ translateName(cb),
553
+ {
554
+ get() {
555
+ return function () {
556
+ return instance!.invokeGlobal(
497
557
  globalName,
498
558
  cb,
499
- callback,
559
+ Array.from(arguments),
500
560
  );
501
- },
502
- enumerable: true,
561
+ };
503
562
  },
504
- );
505
- }
506
- });
507
-
508
- instance!
509
- .definition()
510
- .globalFunctions(globalName)
511
- .forEach((cb) => {
512
- const functionName = cb.replace(/-/g, "_");
513
-
514
- if (
515
- globalObject[functionName] !== undefined
516
- ) {
517
- console.warn(
518
- "Duplicated function name " +
519
- cb +
520
- " on global " +
521
- global,
522
- );
523
- } else {
524
- Object.defineProperty(
525
- globalObject,
526
- cb.replace(/-/g, "_"),
527
- {
528
- get() {
529
- return function () {
530
- return instance!.invokeGlobal(
531
- globalName,
532
- cb,
533
- Array.from(
534
- arguments,
535
- ),
536
- );
537
- };
538
- },
539
- enumerable: true,
540
- },
541
- );
542
- }
543
- });
544
-
545
- Object.defineProperty(componentHandle, globalName, {
546
- get() {
547
- return globalObject;
548
- },
549
- enumerable: true,
563
+ enumerable: true,
564
+ },
565
+ );
566
+ }
550
567
  });
551
- }
552
- });
553
568
 
554
- return Object.seal(componentHandle);
555
- },
569
+ Object.defineProperty(componentHandle, globalName, {
570
+ get() {
571
+ return globalObject;
572
+ },
573
+ enumerable: true,
574
+ });
575
+ }
576
+ });
577
+
578
+ return Object.seal(componentHandle);
556
579
  },
557
- );
580
+ });
558
581
  });
559
582
  return Object.seal(slint_module);
560
583
  }
@@ -582,7 +605,7 @@ function loadSlint(loadData: LoadData): Object {
582
605
  * main.greeting = "Hello friends";
583
606
  * ```
584
607
  *
585
- * @param filePath The path to the file to load. Relative paths are resolved against the process' current working directory.
608
+ * @param filePath The path to the file to load as `string` or `URL`. Relative paths are resolved against the process' current working directory.
586
609
  * @param options An optional {@link LoadFileOptions} to configure additional Slint compilation settings,
587
610
  * such as include search paths, library imports, or the widget style.
588
611
  * @returns Returns an object that is immutable and provides a constructor function for each exported Window component found in the `.slint` file.
@@ -591,9 +614,13 @@ function loadSlint(loadData: LoadData): Object {
591
614
  * For further information on the available properties, refer to [Instantiating A Component](../index.html#md:instantiating-a-component).
592
615
  * @throws {@link CompileError} if errors occur during compilation.
593
616
  */
594
- export function loadFile(filePath: string, options?: LoadFileOptions): Object {
617
+ export function loadFile(
618
+ filePath: string | URL,
619
+ options?: LoadFileOptions,
620
+ ): Object {
621
+ const pathname = filePath instanceof URL ? filePath.pathname : filePath;
595
622
  return loadSlint({
596
- fileData: { filePath, options },
623
+ fileData: { filePath: pathname, options },
597
624
  from: "file",
598
625
  });
599
626
  }
@@ -895,6 +922,28 @@ export namespace private_api {
895
922
  }
896
923
  }
897
924
 
925
+ /**
926
+ * Initialize translations.
927
+ *
928
+ * Call this with the path where translations are located. This function internally calls the [bindtextdomain](https://man7.org/linux/man-pages/man3/bindtextdomain.3.html) function from gettext.
929
+ *
930
+ * Translations are expected to be found at <path>/<locale>/LC_MESSAGES/<domain>.mo, where path is the directory passed as an argument to this function, locale is a locale name (e.g., en, en_GB, fr), and domain is the package name.
931
+ *
932
+ * @param domain defines the domain name e.g. name of the package.
933
+ * @param path specifies the directory as `string` or as `URL` in which gettext should search for translations.
934
+ *
935
+ * For example, assuming this is in a package called example and the default locale is configured to be French, it will load translations at runtime from ``/path/to/example/translations/fr/LC_MESSAGES/example.mo`.
936
+ *
937
+ * ```js
938
+ * import * as slint from "slint-ui";
939
+ * slint.initTranslations("example", new URL("translations/", import.meta.url));
940
+ * ````
941
+ */
942
+ export function initTranslations(domain: string, path: string | URL) {
943
+ const pathname = path instanceof URL ? path.pathname : path;
944
+ napi.initTranslations(domain, pathname);
945
+ }
946
+
898
947
  /**
899
948
  * @hidden
900
949
  */