mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
Consolidate language tutorials (#5037)
All the language tutorials are merged into the Slint reference as "quick starts".
This commit is contained in:
parent
5389367895
commit
ab9d7f342b
78 changed files with 632 additions and 1135 deletions
1
docs/reference/src/quickstart/.gitignore
vendored
Normal file
1
docs/reference/src/quickstart/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
20
docs/reference/src/quickstart/CMakeLists.txt
Normal file
20
docs/reference/src/quickstart/CMakeLists.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
if (NOT TARGET Slint::Slint)
|
||||
find_package(Slint REQUIRED)
|
||||
endif()
|
||||
|
||||
add_executable(memory_tutorial_initial main_initial.cpp)
|
||||
target_link_libraries(memory_tutorial_initial PRIVATE Slint::Slint)
|
||||
slint_target_sources(memory_tutorial_initial appwindow.slint)
|
||||
|
||||
add_executable(memory_tutorial_tiles_from_cpp main_tiles_from_cpp.cpp)
|
||||
target_link_libraries(memory_tutorial_tiles_from_cpp PRIVATE Slint::Slint)
|
||||
slint_target_sources(memory_tutorial_tiles_from_cpp memory_tiles_from_cpp.slint)
|
||||
|
||||
add_executable(memory_tutorial_game_logic main_game_logic.cpp)
|
||||
target_link_libraries(memory_tutorial_game_logic PRIVATE Slint::Slint)
|
||||
slint_target_sources(memory_tutorial_game_logic memory_game_logic.slint)
|
41
docs/reference/src/quickstart/Cargo.toml
Normal file
41
docs/reference/src/quickstart/Cargo.toml
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
[package]
|
||||
name = "src"
|
||||
version = "1.6.0"
|
||||
authors = ["Slint Developers <info@slint.dev>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Binaries for Rust version of Memory Tutorial of Slint"
|
||||
repository = "https://github.com/slint-ui/slint"
|
||||
homepage = "https://slint.dev"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "memory_tutorial_initial"
|
||||
path = "main_initial.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "memory_tutorial_tile"
|
||||
path = "main_memory_tile.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "memory_tutorial_polishing_the_tile"
|
||||
path = "main_polishing_the_tile.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "memory_tutorial_multiple_tiles"
|
||||
path = "main_multiple_tiles.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "memory_tutorial_tiles_from_rust"
|
||||
path = "main_tiles_from_rust.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "memory_tutorial_game_logic_in_rust"
|
||||
path = "main_game_logic_in_rust.rs"
|
||||
|
||||
[dependencies]
|
||||
slint = { path = "../../../../api/rs/slint" }
|
||||
rand = "0.8"
|
12
docs/reference/src/quickstart/appwindow.slint
Normal file
12
docs/reference/src/quickstart/appwindow.slint
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// ANCHOR: main_window
|
||||
// ui/appwindow.slint
|
||||
export component MainWindow inherits Window {
|
||||
Text {
|
||||
text: "hello world";
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: main_window
|
14
docs/reference/src/quickstart/conclusion.md
Normal file
14
docs/reference/src/quickstart/conclusion.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Conclusion
|
||||
|
||||
This tutorial showed you how to combine built-in Slint elements with C++, Rust, or NodeJS code to build a
|
||||
game. There is much more to Slint, such as layouts, widgets, or styling.
|
||||
|
||||
We recommend the following links to continue:
|
||||
|
||||
- [Examples](https://github.com/slint-ui/slint/tree/master/examples): The Slint repository has several demos and examples. These are a great starting point to learn how to use many Slint features.
|
||||
- [Todo Example](https://github.com/slint-ui/slint/tree/master/examples/todo): This is one of the examples that implements a classic use-case.
|
||||
- [Memory Puzzle](https://github.com/slint-ui/slint/tree/master/examples/memory): This is a slightly more polished version of the code in this example and you can <a href="https://slint.dev/demos/memory/" target="_blank">play the wasm version</a> in your browser.
|
||||
- [Slint API Docs](https://slint.dev/docs/rust/slint/): The reference documentation for the main Rust crate.
|
||||
- [Slint Interpreter API Docs](https://slint.dev/docs/rust/slint_interpreter/): The reference documentation for the Rust crate that allows you to dynamically load Slint files.
|
79
docs/reference/src/quickstart/creating_the_tiles.md
Normal file
79
docs/reference/src/quickstart/creating_the_tiles.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Creating The Tiles From Code
|
||||
|
||||
This step places the game tiles randomly.
|
||||
|
||||
:::::{tab-set}
|
||||
::::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Change the `main` function and includes in `src/main.cpp` to the following:
|
||||
|
||||
:::{literalinclude} main_tiles_from_cpp.cpp
|
||||
:lines: 10-28
|
||||
:::
|
||||
|
||||
The code takes the list of tiles, duplicates it, and shuffles it, accessing the `memory_tiles` property through the C++ code.
|
||||
|
||||
For each top-level property, Slint generates a getter and a setter function. In this case `get_memory_tiles` and `set_memory_tiles`.
|
||||
Since `memory_tiles` is a Slint array, it's represented as a [`std::shared_ptr<slint::Model>`](https://slint.dev/docs/cpp/api/classslint_1_1model).
|
||||
|
||||
You can't change the model generated by Slint, but you can extract the tiles from it and put them
|
||||
in a [`slint::VectorModel`](https://slint.dev/docs/cpp/api/classslint_1_1vectormodel) which inherits from `Model`.
|
||||
`VectorModel` lets you make changes and you can use it to replace the static generated model.
|
||||
|
||||
::::
|
||||
|
||||
::::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Change `main.js` to the following:
|
||||
|
||||
:::{literalinclude} main_tiles_from_js.js
|
||||
:lines: 6-21
|
||||
:::
|
||||
|
||||
The code takes the list of tiles, duplicates it, and shuffles it, accessing the `memory_tiles` property through the JavaScript code.
|
||||
|
||||
As `memory_tiles` is an array, it's represented as a JavaScript [`Array`](https://slint.dev/docs/node/).
|
||||
You can't change the model generated by Slint, but you can extract the tiles from it and put them
|
||||
in a [`slint.ArrayModel`](https://slint.dev/docs/node/classes/arraymodel.html) which implements the [`Model`](https://slint.dev/docs/node/interfaces/model.html) interface.
|
||||
`ArrayModel` allows you to make changes and you can use it to replace the static generated model.
|
||||
::::
|
||||
|
||||
::::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
The code uses the `rand` dependency for the randomization. Add it to the `Cargo.toml` file using the `cargo` command.
|
||||
|
||||
```sh
|
||||
cargo add rand@0.8
|
||||
```
|
||||
|
||||
Change the main function to the following:
|
||||
|
||||
:::{literalinclude} main_tiles_from_rust.rs
|
||||
:lines: 6-26
|
||||
:::
|
||||
|
||||
The code takes the list of tiles, duplicates it, and shuffles it, accessing the `memory_tiles` property through the Rust code.
|
||||
|
||||
For each top-level property,
|
||||
Slint generates a getter and a setter function. In this case `get_memory_tiles` and `set_memory_tiles`.
|
||||
Since `memory_tiles` is a Slint array represented as a [`Rc<dyn slint::Model>`](https://slint.dev/docs/rust/slint/trait.Model).
|
||||
|
||||
You can't change the model generated by Slint, but you can extract the tiles from it and put them
|
||||
in a [`VecModel`](https://slint.dev/docs/rust/slint/struct.VecModel) which implements the `Model` trait.
|
||||
`VecModel` lets you make changes and you can use it to replace the static generated model.
|
||||
|
||||
::::
|
||||
|
||||
:::::
|
||||
|
||||
Running this code opens a window that now shows a 4 by 4 grid of rectangles, which show or hide
|
||||
the icons when a player clicks on them.
|
||||
|
||||
There's one last aspect missing now, the rules for the game.
|
||||
|
||||
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/creating-the-tiles-from-rust.mp4"></video>
|
86
docs/reference/src/quickstart/from_one_to_multiple_tiles.md
Normal file
86
docs/reference/src/quickstart/from_one_to_multiple_tiles.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# From One To Multiple Tiles
|
||||
|
||||
After modeling a single tile, this step creates a grid of them. For the grid to be a game board, you need two features:
|
||||
|
||||
1. **A data model**: An array created as a model in code, where each element describes the tile data structure, such as:
|
||||
|
||||
- URL of the image
|
||||
- Whether the image is visible
|
||||
- If the player has solved this tile.
|
||||
|
||||
2. A way of creating multiple instances of the tiles.
|
||||
|
||||
With Slint you declare an array of structures based on a model using square brackets. Use a <span class="hljs-keyword">for</span> loop
|
||||
to create multiple instances of the same element.
|
||||
|
||||
The <span class="hljs-keyword">for</span> loop is declarative and automatically updates when
|
||||
the model changes. The loop instantiates all the <span class="hljs-title">MemoryTile</span> elements and places them on a grid based on their
|
||||
index with spacing between the tiles.
|
||||
|
||||
::::{tab-set}
|
||||
:::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
First, add the tile data structure definition at the top of the `ui/appwindow.slint` file:
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
First, add the tile data structure definition at the top of the `ui/appwindow.slint` file:
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
First, add the tile data structure definition at the top of the `slint!` macro:
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
:::{literalinclude} main_multiple_tiles.rs
|
||||
:language: slint,no-preview
|
||||
:lines: 11-15
|
||||
:::
|
||||
|
||||
::::{tab-set}
|
||||
:::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Next, replace the _export component <span class="hljs-title">MainWindow</span> inherits Window { ... }_ section at the bottom of the `ui/appwindow.slint` file with the following:
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Next, replace the _export component <span class="hljs-title">MainWindow</span> inherits Window { ... }_ section at the bottom of the `ui/appwindow.slint` file with the following:
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
Next, replace the _export component <span class="hljs-title">MainWindow</span> inherits Window { ... }_ section at the bottom of the `slint!` macro with the following:
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
:::{literalinclude} main_multiple_tiles.rs
|
||||
:language: slint,no-preview
|
||||
:lines: 63-90
|
||||
:::
|
||||
|
||||
The <code><span class="hljs-keyword">for</span> tile\[i\] <span class="hljs-keyword">in</span> memory_tiles:</code> syntax declares a variable `tile` which contains the data of one element from the `memory_tiles` array,
|
||||
and a variable `i` which is the index of the tile. The code uses the `i` index to calculate the position of a tile, based on its row and column,
|
||||
using modulo and integer division to create a 4 by 4 grid.
|
||||
|
||||
Running the code opens a window that shows 8 tiles, which a player can open individually.
|
||||
|
||||
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/from-one-to-multiple-tiles.mp4"></video>
|
132
docs/reference/src/quickstart/game_logic.md
Normal file
132
docs/reference/src/quickstart/game_logic.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Game Logic
|
||||
|
||||
This step implements the rules of the game in your coding language of choice.
|
||||
|
||||
Slint's general philosophy is that you implement the user interface in Slint and the business logic in your favorite programming
|
||||
language.
|
||||
|
||||
The game rules enforce that at most two tiles have their curtain open. If the tiles match, then the game
|
||||
considers them solved and they remain open. Otherwise, the game waits briefly so the player can memorize
|
||||
the location of the icons, and then closes the curtains again.
|
||||
|
||||
:::::{tab-set}
|
||||
::::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Add the following code inside the <span class="hljs-title">MainWindow</span> component to signal to the C++ code when the user clicks on a tile.
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:language: slint,no-preview
|
||||
:lines: 107-115
|
||||
:::
|
||||
|
||||
This change adds a way for the <span class="hljs-title">MainWindow</span> to call to the C++ code that it should
|
||||
check if a player has solved a pair of tiles. The Rust code needs an additional property to toggle to disable further
|
||||
tile interaction, to prevent the player from opening more tiles than allowed. No cheating allowed!
|
||||
|
||||
The last change to the code is to act when the <span class="hljs-title">MemoryTile</span> signals that a player clicked it.
|
||||
|
||||
Add the following handler in the <span class="hljs-title">MainWindow</span> `for` loop `clicked` handler:
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:language: slint,no-preview
|
||||
:lines: 126-143
|
||||
:::
|
||||
|
||||
|
||||
On the C++ side, you can now add a handler to the `check_if_pair_solved` callback, that checks if a player opened two tiles.
|
||||
If they match, the code sets the `solved` property to true in the model. If they don't
|
||||
match, start a timer that closes the tiles after one second. While the timer is running, disable every tile so
|
||||
a player can't click anything during this time.
|
||||
|
||||
Insert this code before the `main_window->run()` call:
|
||||
|
||||
:::{literalinclude} main_game_logic.cpp
|
||||
:lines: 29-65
|
||||
:::
|
||||
|
||||
The code uses a [ComponentWeakHandle](https://slint.dev/docs/cpp/api/classslint_1_1ComponentWeakHandle) pointer of the `main_window`. This is
|
||||
important because capturing a copy of the `main_window` itself within the callback handler would result in circular ownership.
|
||||
The `MainWindow` owns the callback handler, which itself owns a reference to the `MainWindow`, which must be weak
|
||||
instead of strong to avoid a memory leak.
|
||||
|
||||
::::
|
||||
|
||||
::::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Change the contents of `memory.slint` to signal to the JavaScript code when the user clicks on a tile.
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:language: slint,no-preview
|
||||
:lines: 107-115
|
||||
:::
|
||||
|
||||
This change adds a way for the <span class="hljs-title">MainWindow</span> to call to the JavaScript code that it should
|
||||
check if a player has solved a pair of tiles. The Rust code needs an additional property to toggle to disable further
|
||||
tile interaction, to prevent the player from opening more tiles than allowed. No cheating allowed!
|
||||
|
||||
The last change to the code is to act when the <span class="hljs-title">MemoryTile</span> signals that a player clicked it.
|
||||
|
||||
Add the following handler in the <span class="hljs-title">MainWindow</span> `for` loop `clicked` handler:
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:lines: 126-143
|
||||
:::
|
||||
|
||||
On the JavaScript side, now add a handler to the `check_if_pair_solved` callback, that checks if a player opened two tiles. If they match, the code sets the `solved` property to true in the model. If they don't
|
||||
match, start a timer that closes the tiles after one second. While the timer is running, disable every tile so
|
||||
a player can't click anything during this time.
|
||||
|
||||
Insert this code before the `mainWindow.run()` call:
|
||||
|
||||
:::{literalinclude} main_game_logic.js
|
||||
:lines: 23-63
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
::::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
Add the following code inside the <span class="hljs-title">MainWindow</span> component to signal to the Rust code when the user clicks on a tile.
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:lines: 107-115
|
||||
:::
|
||||
|
||||
This change adds a way for the <span class="hljs-title">MainWindow</span> to call to the Rust code that it should
|
||||
check if a player has solved a pair of tiles. The Rust code needs an additional property to toggle to disable further
|
||||
tile interaction, to prevent the player from opening more tiles than allowed. No cheating allowed!
|
||||
|
||||
The last change to the code is to act when the <span class="hljs-title">MemoryTile</span> signals that a player clicked it.
|
||||
|
||||
Add the following handler in the <span class="hljs-title">MainWindow</span> `for` loop `clicked` handler:
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:lines: 126-143
|
||||
:::
|
||||
|
||||
On the Rust side, you can now add a handler to the `check_if_pair_solved` callback, that checks if a player opened two tiles.
|
||||
If they match, the code sets the `solved` property to true in the model. If they don't
|
||||
match, start a timer that closes the tiles after one second. While the timer is running, disable every tile so
|
||||
a player can't click anything during this time.
|
||||
|
||||
Add this code before the `main_window.run().unwrap();` call:
|
||||
|
||||
:::{literalinclude} main_game_logic_in_rust.rs
|
||||
:lines: 21-52
|
||||
:::
|
||||
|
||||
The code uses a [Weak](https://slint.dev/docs/rust/slint/struct.Weak) pointer of the `main_window`. This is
|
||||
important because capturing a copy of the `main_window` itself within the callback handler would result in circular ownership.
|
||||
The `MainWindow` owns the callback handler, which itself owns a reference to the `MainWindow`, which must be weak
|
||||
instead of strong to avoid a memory leak.
|
||||
|
||||
::::
|
||||
|
||||
:::::
|
||||
|
||||
These were the last changes and running the code opens a window that allows a player to play the game by the rules.
|
167
docs/reference/src/quickstart/getting_started.md
Normal file
167
docs/reference/src/quickstart/getting_started.md
Normal file
|
@ -0,0 +1,167 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Getting started
|
||||
|
||||
This tutorial shows you how to use the languages Slint supports as the host programming language.
|
||||
|
||||
We recommend using [our editor integrations for Slint](https://github.com/slint-ui/slint/tree/master/editors) for following this tutorial.
|
||||
|
||||
Slint has application templates you can use to create a project with dependencies already set up that follows recommended best practices.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
:::::{tab-set}
|
||||
|
||||
::::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Before using the template, you need a C++ compiler that supports C++ 20 and to install [CMake](https://cmake.org/download/) 3.21 or newer.
|
||||
|
||||
Clone or download the template repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/slint-ui/slint-cpp-template memory
|
||||
cd memory
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
The `CMakeLists.txt` uses the line `add_executable(my_application src/main.cpp)` to set `src/main.cpp` as the main C++ code file.
|
||||
|
||||
Change the content of `src/main.cpp` to the following:
|
||||
|
||||
:::{literalinclude} main_initial.cpp
|
||||
:lines: 9-13
|
||||
:::
|
||||
|
||||
Also in `CMakeLists.txt` the line
|
||||
`slint_target_sources(my_application ui/appwindow.slint)` is a Slint function used to
|
||||
add the `appwindow.slint` file to the target.
|
||||
|
||||
Change the contents of `ui/appwindow.slint` to the following:
|
||||
|
||||
:::{literalinclude} appwindow.slint
|
||||
:language: slint,no-preview
|
||||
:lines: 6-11
|
||||
:::
|
||||
|
||||
Configure with CMake:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
```
|
||||
|
||||
:::{tip}
|
||||
When configuring with CMake, the FetchContent module fetches the source code of Slint via git.
|
||||
This may take some time when building for the first time, as the process needs to build
|
||||
the Slint runtime and compiler.
|
||||
:::
|
||||
|
||||
Build with CMake:
|
||||
|
||||
```sh
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
### Run the application
|
||||
|
||||
Run the application binary on Linux or macOS:
|
||||
|
||||
```sh
|
||||
./build/my_application
|
||||
```
|
||||
|
||||
Or on Windows:
|
||||
|
||||
```sh
|
||||
build\my_application.exe
|
||||
```
|
||||
|
||||
This opens a window with a green "Hello World" greeting.
|
||||
|
||||
If you are stepping through this tutorial on a Windows machine, you can run the application at each step with:
|
||||
|
||||
```sh
|
||||
my_application
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
::::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Clone the template with the following command:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/slint-ui/slint-nodejs-template memory
|
||||
cd memory
|
||||
```
|
||||
|
||||
Install dependencies with npm:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
The `package.json` file references `src/main.js` as the entry point for the application and `src/main.js` references `memory.slint` as the UI file.
|
||||
|
||||
Replace the contents of `src/main.js` with the following:
|
||||
|
||||
:::{literalinclude} main_initial.js
|
||||
:lines: 6-10
|
||||
:::
|
||||
|
||||
The `slint.loadFile` method resolves files from the process's current working directory, so from the `package.json` file's location.
|
||||
|
||||
Replace the contents of `ui/appwindow.slint` with the following:
|
||||
|
||||
:::{literalinclude} memory.slint
|
||||
:language: slint,no-preview
|
||||
:lines: 6-11
|
||||
:::
|
||||
|
||||
### Run the application
|
||||
|
||||
Run the example with `npm start` and a window appears with the green "Hello World" greeting.
|
||||
|
||||
::::
|
||||
|
||||
::::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
We recommend using [rust-analyzer](https://rust-analyzer.github.io) and [our editor integrations for Slint](https://github.com/slint-ui/slint/tree/master/editors) for following this tutorial.
|
||||
|
||||
Slint has an application template you can use to create a project with dependencies already set up that follows recommended best practices.
|
||||
|
||||
Before using the template, install [`cargo-generate`](https://github.com/cargo-generate/cargo-generate):
|
||||
|
||||
```sh
|
||||
cargo install cargo-generate
|
||||
```
|
||||
|
||||
Use the template to create a new project with the following command:
|
||||
|
||||
```sh
|
||||
cargo generate --git https://github.com/slint-ui/slint-rust-template --name memory
|
||||
cd memory
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
Replace the contents of `src/main.rs` with the following:
|
||||
|
||||
:::{literalinclude} main_initial.rs
|
||||
:lines: 6-17
|
||||
:::
|
||||
|
||||
### Run the application
|
||||
|
||||
Run the example with `cargo run` and a window appears with the green "Hello World" greeting.
|
||||
|
||||
::::
|
||||
|
||||
:::::
|
||||
|
||||

|
1
docs/reference/src/quickstart/icons
Symbolic link
1
docs/reference/src/quickstart/icons
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../../examples/memory/icons
|
16
docs/reference/src/quickstart/ideas_for_the_reader.md
Normal file
16
docs/reference/src/quickstart/ideas_for_the_reader.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Ideas For The Reader
|
||||
|
||||
The game is visually bare. Here are some ideas on how you could make further changes to enhance it:
|
||||
|
||||
- The tiles could have rounded corners, to look less sharp. Use the [border-radius](https://slint.dev/docs/slint/src/language/builtins/elements#rectangle)
|
||||
property of _[Rectangle](https://slint.dev/docs/slint/src/language/builtins/elements#rectangle)_ to achieve that.
|
||||
|
||||
- In real-world memory games, the back of the tiles often have some common graphic. You could add an image with
|
||||
the help of another _[Image](https://slint.dev/docs/slint/src/language/builtins/elements#image)_
|
||||
element. Note that you may have to use _Rectangle_'s _clip property_
|
||||
element around it to ensure that the image is clipped away when the curtain effect opens.
|
||||
|
||||
Let us know in the comments on [Github Discussions](https://github.com/slint-ui/slint/discussions)
|
||||
how you polished your code, or feel free to ask questions about how to implement something.
|
34
docs/reference/src/quickstart/index.rst
Normal file
34
docs/reference/src/quickstart/index.rst
Normal file
|
@ -0,0 +1,34 @@
|
|||
.. Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
.. SPDX-License-Identifier: MIT
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
This tutorial introduces you to the Slint UI framework in a playful way by implementing a memory game. It combines the Slint language for the graphics with the game rules implemented in C++, Rust, or NodeJS.
|
||||
|
||||
The game consists of a grid of 16 rectangular tiles. Clicking on a tile uncovers an icon underneath.
|
||||
There are 8 different icons in total, so each tile has a sibling somewhere in the grid with the
|
||||
same icon. The objective is to locate all icon pairs. The player can uncover two tiles at the same time. If they
|
||||
aren't the same, the game obscures the icons again.
|
||||
If the player uncovers two tiles with the same icon, then they remain visible - they're solved.
|
||||
|
||||
This is how the game looks in action:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/memory_clip.mp4" class="img-fluid img-thumbnail rounded"></video>
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 2
|
||||
:caption: Quickstart
|
||||
|
||||
getting_started.md
|
||||
memory_tile.md
|
||||
polishing_the_tile.md
|
||||
from_one_to_multiple_tiles.md
|
||||
creating_the_tiles.md
|
||||
game_logic.md
|
||||
running_in_a_browser.md
|
||||
ideas_for_the_reader.md
|
||||
conclusion.md
|
69
docs/reference/src/quickstart/main_game_logic.cpp
Normal file
69
docs/reference/src/quickstart/main_game_logic.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// clang-format off
|
||||
// main.cpp
|
||||
|
||||
#include "memory_game_logic.h" // generated header from memory_game_logic.slint
|
||||
|
||||
#include <random> // Added
|
||||
|
||||
int main()
|
||||
{
|
||||
auto main_window = MainWindow::create();
|
||||
auto old_tiles = main_window->get_memory_tiles();
|
||||
std::vector<TileData> new_tiles;
|
||||
new_tiles.reserve(old_tiles->row_count() * 2);
|
||||
for (int i = 0; i < old_tiles->row_count(); ++i) {
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
}
|
||||
std::default_random_engine rng {};
|
||||
std::shuffle(new_tiles.begin(), new_tiles.end(), rng);
|
||||
|
||||
auto tiles_model = std::make_shared<slint::VectorModel<TileData>>(new_tiles);
|
||||
main_window->set_memory_tiles(tiles_model);
|
||||
|
||||
// ANCHOR: game_logic
|
||||
|
||||
main_window->on_check_if_pair_solved(
|
||||
[main_window_weak = slint::ComponentWeakHandle(main_window)] {
|
||||
auto main_window = *main_window_weak.lock();
|
||||
auto tiles_model = main_window->get_memory_tiles();
|
||||
int first_visible_index = -1;
|
||||
TileData first_visible_tile;
|
||||
for (int i = 0; i < tiles_model->row_count(); ++i) {
|
||||
auto tile = *tiles_model->row_data(i);
|
||||
if (!tile.image_visible || tile.solved)
|
||||
continue;
|
||||
if (first_visible_index == -1) {
|
||||
first_visible_index = i;
|
||||
first_visible_tile = tile;
|
||||
continue;
|
||||
}
|
||||
bool is_pair_solved = tile == first_visible_tile;
|
||||
if (is_pair_solved) {
|
||||
first_visible_tile.solved = true;
|
||||
tiles_model->set_row_data(first_visible_index,
|
||||
first_visible_tile);
|
||||
tile.solved = true;
|
||||
tiles_model->set_row_data(i, tile);
|
||||
return;
|
||||
}
|
||||
main_window->set_disable_tiles(true);
|
||||
|
||||
slint::Timer::single_shot(std::chrono::seconds(1),
|
||||
[=]() mutable {
|
||||
main_window->set_disable_tiles(false);
|
||||
first_visible_tile.image_visible = false;
|
||||
tiles_model->set_row_data(first_visible_index,
|
||||
first_visible_tile);
|
||||
tile.image_visible = false;
|
||||
tiles_model->set_row_data(i, tile);
|
||||
});
|
||||
}
|
||||
});
|
||||
// ANCHOR_END: game_logic
|
||||
main_window->run();
|
||||
}
|
||||
// clang-format on
|
65
docs/reference/src/quickstart/main_game_logic.js
Normal file
65
docs/reference/src/quickstart/main_game_logic.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
// main.js
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("./memory.slint");
|
||||
let mainWindow = new ui.MainWindow();
|
||||
|
||||
let initial_tiles = mainWindow.memory_tiles;
|
||||
let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile)));
|
||||
|
||||
for (let i = tiles.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * i);
|
||||
[tiles[i], tiles[j]] = [tiles[j], tiles[i]];
|
||||
}
|
||||
|
||||
let model = new slint.ArrayModel(tiles);
|
||||
mainWindow.memory_tiles = model;
|
||||
|
||||
// ANCHOR: game_logic
|
||||
mainWindow.check_if_pair_solved = function () {
|
||||
let flipped_tiles = [];
|
||||
tiles.forEach((tile, index) => {
|
||||
if (tile.image_visible && !tile.solved) {
|
||||
flipped_tiles.push({
|
||||
index,
|
||||
tile
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (flipped_tiles.length == 2) {
|
||||
let {
|
||||
tile: tile1,
|
||||
index: tile1_index
|
||||
} = flipped_tiles[0];
|
||||
|
||||
let {
|
||||
tile: tile2,
|
||||
index: tile2_index
|
||||
} = flipped_tiles[1];
|
||||
|
||||
let is_pair_solved = tile1.image.path === tile2.image.path;
|
||||
if (is_pair_solved) {
|
||||
tile1.solved = true;
|
||||
model.setRowData(tile1_index, tile1);
|
||||
tile2.solved = true;
|
||||
model.setRowData(tile2_index, tile2);
|
||||
} else {
|
||||
mainWindow.disable_tiles = true;
|
||||
setTimeout(() => {
|
||||
mainWindow.disable_tiles = false;
|
||||
tile1.image_visible = false;
|
||||
model.setRowData(tile1_index, tile1);
|
||||
tile2.image_visible = false;
|
||||
model.setRowData(tile2_index, tile2);
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
// ANCHOR_END: game_logic
|
||||
await mainWindow.run();
|
146
docs/reference/src/quickstart/main_game_logic_in_rust.rs
Normal file
146
docs/reference/src/quickstart/main_game_logic_in_rust.rs
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn main() {
|
||||
use slint::Model;
|
||||
|
||||
let main_window = MainWindow::new().unwrap();
|
||||
|
||||
// Fetch the tiles from the model
|
||||
let mut tiles: Vec<TileData> = main_window.get_memory_tiles().iter().collect();
|
||||
// Duplicate them to ensure that we have pairs
|
||||
tiles.extend(tiles.clone());
|
||||
|
||||
// Randomly mix the tiles
|
||||
use rand::seq::SliceRandom;
|
||||
let mut rng = rand::thread_rng();
|
||||
tiles.shuffle(&mut rng);
|
||||
|
||||
// ANCHOR: game_logic
|
||||
// Assign the shuffled Vec to the model property
|
||||
let tiles_model = std::rc::Rc::new(slint::VecModel::from(tiles));
|
||||
main_window.set_memory_tiles(tiles_model.clone().into());
|
||||
|
||||
let main_window_weak = main_window.as_weak();
|
||||
main_window.on_check_if_pair_solved(move || {
|
||||
let mut flipped_tiles =
|
||||
tiles_model.iter().enumerate().filter(|(_, tile)| tile.image_visible && !tile.solved);
|
||||
|
||||
if let (Some((t1_idx, mut t1)), Some((t2_idx, mut t2))) =
|
||||
(flipped_tiles.next(), flipped_tiles.next())
|
||||
{
|
||||
let is_pair_solved = t1 == t2;
|
||||
if is_pair_solved {
|
||||
t1.solved = true;
|
||||
tiles_model.set_row_data(t1_idx, t1);
|
||||
t2.solved = true;
|
||||
tiles_model.set_row_data(t2_idx, t2);
|
||||
} else {
|
||||
let main_window = main_window_weak.unwrap();
|
||||
main_window.set_disable_tiles(true);
|
||||
let tiles_model = tiles_model.clone();
|
||||
slint::Timer::single_shot(std::time::Duration::from_secs(1), move || {
|
||||
main_window.set_disable_tiles(false);
|
||||
t1.image_visible = false;
|
||||
tiles_model.set_row_data(t1_idx, t1);
|
||||
t2.image_visible = false;
|
||||
tiles_model.set_row_data(t2_idx, t2);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// ANCHOR_END: game_logic
|
||||
main_window.run().unwrap();
|
||||
}
|
||||
|
||||
slint::slint! {
|
||||
struct TileData {
|
||||
image: image,
|
||||
image_visible: bool,
|
||||
solved: bool,
|
||||
}
|
||||
|
||||
component MemoryTile inherits Rectangle {
|
||||
callback clicked;
|
||||
in property <bool> open_curtain;
|
||||
in property <bool> solved;
|
||||
in property <image> icon;
|
||||
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
background: solved ? #34CE57 : #3960D5;
|
||||
animate background { duration: 800ms; }
|
||||
|
||||
Image {
|
||||
source: icon;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
|
||||
// Left curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
// Right curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: open_curtain ? parent.width : (parent.width / 2);
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
animate x { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
// Delegate to the user of this element
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR: mainwindow_interface
|
||||
export component MainWindow inherits Window {
|
||||
width: 326px;
|
||||
height: 326px;
|
||||
|
||||
callback check_if_pair_solved(); // Added
|
||||
in property <bool> disable_tiles; // Added
|
||||
|
||||
in-out property <[TileData]> memory_tiles: [
|
||||
{ image: @image-url("icons/at.png") },
|
||||
// ANCHOR_END: mainwindow_interface
|
||||
{ image: @image-url("icons/balance-scale.png") },
|
||||
{ image: @image-url("icons/bicycle.png") },
|
||||
{ image: @image-url("icons/bus.png") },
|
||||
{ image: @image-url("icons/cloud.png") },
|
||||
{ image: @image-url("icons/cogs.png") },
|
||||
{ image: @image-url("icons/motorcycle.png") },
|
||||
{ image: @image-url("icons/video.png") },
|
||||
];
|
||||
// ANCHOR: tile_click_logic
|
||||
for tile[i] in memory_tiles : MemoryTile {
|
||||
x: mod(i, 4) * 74px;
|
||||
y: floor(i / 4) * 74px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
icon: tile.image;
|
||||
open_curtain: tile.image_visible || tile.solved;
|
||||
// propagate the solved status from the model to the tile
|
||||
solved: tile.solved;
|
||||
clicked => {
|
||||
// old: tile.image_visible = !tile.image_visible;
|
||||
// new:
|
||||
if (!root.disable_tiles) {
|
||||
tile.image_visible = !tile.image_visible;
|
||||
root.check_if_pair_solved();
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: tile_click_logic
|
||||
}
|
||||
}
|
14
docs/reference/src/quickstart/main_initial.cpp
Normal file
14
docs/reference/src/quickstart/main_initial.cpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// ANCHOR: main
|
||||
// src/main.cpp
|
||||
|
||||
#include "appwindow.h" // generated header from memory.slint
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
auto main_window = MainWindow::create();
|
||||
main_window->run();
|
||||
}
|
||||
// ANCHOR_END: main
|
12
docs/reference/src/quickstart/main_initial.js
Normal file
12
docs/reference/src/quickstart/main_initial.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// ANCHOR: main
|
||||
// main.js
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("./ui/appwindow.slint");
|
||||
let mainWindow = new ui.MainWindow();
|
||||
await mainWindow.run();
|
||||
|
||||
// ANCHOR_END: main
|
18
docs/reference/src/quickstart/main_initial.rs
Normal file
18
docs/reference/src/quickstart/main_initial.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[allow(dead_code)]
|
||||
// ANCHOR: main
|
||||
fn main() {
|
||||
MainWindow::new().unwrap().run().unwrap();
|
||||
}
|
||||
|
||||
slint::slint! {
|
||||
export component MainWindow inherits Window {
|
||||
Text {
|
||||
text: "hello world";
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: main
|
26
docs/reference/src/quickstart/main_memory_tile.rs
Normal file
26
docs/reference/src/quickstart/main_memory_tile.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn main() {
|
||||
MainWindow::new().unwrap().run().unwrap();
|
||||
}
|
||||
slint::slint! {
|
||||
// ANCHOR: tile
|
||||
component MemoryTile inherits Rectangle {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: #3960D5;
|
||||
|
||||
Image {
|
||||
source: @image-url("icons/bus.png");
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
MemoryTile {}
|
||||
}
|
||||
// ANCHOR_END: tile
|
||||
}
|
92
docs/reference/src/quickstart/main_multiple_tiles.rs
Normal file
92
docs/reference/src/quickstart/main_multiple_tiles.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn main() {
|
||||
MainWindow::new().unwrap().run().unwrap();
|
||||
}
|
||||
slint::slint! {
|
||||
// ANCHOR: tile_data
|
||||
|
||||
struct TileData {
|
||||
image: image,
|
||||
image_visible: bool,
|
||||
solved: bool,
|
||||
}
|
||||
|
||||
// ANCHOR_END: tile_data
|
||||
|
||||
component MemoryTile inherits Rectangle {
|
||||
|
||||
callback clicked;
|
||||
in property <bool> open_curtain;
|
||||
in property <bool> solved;
|
||||
in property <image> icon;
|
||||
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
background: solved ? #34CE57 : #3960D5;
|
||||
animate background { duration: 800ms; }
|
||||
|
||||
Image {
|
||||
source: icon;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
|
||||
// Left curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
// Right curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: open_curtain ? parent.width : (parent.width / 2);
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
animate x { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
// Delegate to the user of this element
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR: main_window
|
||||
export component MainWindow inherits Window {
|
||||
width: 326px;
|
||||
height: 326px;
|
||||
|
||||
in property <[TileData]> memory_tiles: [
|
||||
{ image: @image-url("icons/at.png") },
|
||||
{ image: @image-url("icons/balance-scale.png") },
|
||||
{ image: @image-url("icons/bicycle.png") },
|
||||
{ image: @image-url("icons/bus.png") },
|
||||
{ image: @image-url("icons/cloud.png") },
|
||||
{ image: @image-url("icons/cogs.png") },
|
||||
{ image: @image-url("icons/motorcycle.png") },
|
||||
{ image: @image-url("icons/video.png") },
|
||||
];
|
||||
for tile[i] in memory_tiles : MemoryTile {
|
||||
x: mod(i, 4) * 74px;
|
||||
y: floor(i / 4) * 74px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
icon: tile.image;
|
||||
open_curtain: tile.image_visible || tile.solved;
|
||||
// propagate the solved status from the model to the tile
|
||||
solved: tile.solved;
|
||||
clicked => {
|
||||
tile.image_visible = !tile.image_visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: main_window
|
||||
}
|
63
docs/reference/src/quickstart/main_polishing_the_tile.rs
Normal file
63
docs/reference/src/quickstart/main_polishing_the_tile.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn main() {
|
||||
MainWindow::new().unwrap().run().unwrap();
|
||||
}
|
||||
slint::slint! {
|
||||
// ANCHOR: tile
|
||||
component MemoryTile inherits Rectangle {
|
||||
callback clicked;
|
||||
in property <bool> open_curtain;
|
||||
in property <bool> solved;
|
||||
in property <image> icon;
|
||||
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
background: solved ? #34CE57 : #3960D5;
|
||||
animate background { duration: 800ms; }
|
||||
|
||||
Image {
|
||||
source: icon;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
|
||||
// Left curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: 0px;
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
// Right curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: open_curtain ? parent.width : (parent.width / 2);
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
animate x { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
// Delegate to the user of this element
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
MemoryTile {
|
||||
icon: @image-url("icons/bus.png");
|
||||
clicked => {
|
||||
self.open_curtain = !self.open_curtain;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: tile
|
||||
}
|
29
docs/reference/src/quickstart/main_tiles_from_cpp.cpp
Normal file
29
docs/reference/src/quickstart/main_tiles_from_cpp.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// main.cpp
|
||||
|
||||
#include "memory_tiles_from_cpp.h" // generated header from memory_tiles_from_cpp.slint
|
||||
// ANCHOR: main
|
||||
// ...
|
||||
|
||||
#include <random> // Added
|
||||
|
||||
int main()
|
||||
{
|
||||
auto main_window = MainWindow::create();
|
||||
auto old_tiles = main_window->get_memory_tiles();
|
||||
std::vector<TileData> new_tiles;
|
||||
new_tiles.reserve(old_tiles->row_count() * 2);
|
||||
for (int i = 0; i < old_tiles->row_count(); ++i) {
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
}
|
||||
std::default_random_engine rng {};
|
||||
std::shuffle(new_tiles.begin(), new_tiles.end(), rng);
|
||||
auto tiles_model = std::make_shared<slint::VectorModel<TileData>>(new_tiles);
|
||||
main_window->set_memory_tiles(tiles_model);
|
||||
|
||||
main_window->run();
|
||||
}
|
||||
// ANCHOR_END: main
|
23
docs/reference/src/quickstart/main_tiles_from_js.js
Normal file
23
docs/reference/src/quickstart/main_tiles_from_js.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// ANCHOR: main
|
||||
// main.js
|
||||
import * as slint from "slint-ui";
|
||||
let ui = slint.loadFile("./ui/appwindow.slint");
|
||||
let mainWindow = new ui.MainWindow();
|
||||
|
||||
let initial_tiles = mainWindow.memory_tiles;
|
||||
let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile)));
|
||||
|
||||
for (let i = tiles.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * i);
|
||||
[tiles[i], tiles[j]] = [tiles[j], tiles[i]];
|
||||
}
|
||||
|
||||
let model = new slint.ArrayModel(tiles);
|
||||
mainWindow.memory_tiles = model;
|
||||
|
||||
await mainWindow.run();
|
||||
|
||||
// ANCHOR_END: main
|
107
docs/reference/src/quickstart/main_tiles_from_rust.rs
Normal file
107
docs/reference/src/quickstart/main_tiles_from_rust.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[allow(dead_code)]
|
||||
// ANCHOR: tiles
|
||||
fn main() {
|
||||
use slint::Model;
|
||||
|
||||
let main_window = MainWindow::new().unwrap();
|
||||
|
||||
// Fetch the tiles from the model
|
||||
let mut tiles: Vec<TileData> = main_window.get_memory_tiles().iter().collect();
|
||||
// Duplicate them to ensure that we have pairs
|
||||
tiles.extend(tiles.clone());
|
||||
|
||||
// Randomly mix the tiles
|
||||
use rand::seq::SliceRandom;
|
||||
let mut rng = rand::thread_rng();
|
||||
tiles.shuffle(&mut rng);
|
||||
|
||||
// Assign the shuffled Vec to the model property
|
||||
let tiles_model = std::rc::Rc::new(slint::VecModel::from(tiles));
|
||||
main_window.set_memory_tiles(tiles_model.into());
|
||||
|
||||
main_window.run().unwrap();
|
||||
}
|
||||
|
||||
// ANCHOR_END: tiles
|
||||
slint::slint! {
|
||||
struct TileData {
|
||||
image: image,
|
||||
image_visible: bool,
|
||||
solved: bool,
|
||||
}
|
||||
|
||||
component MemoryTile inherits Rectangle {
|
||||
callback clicked;
|
||||
in property <bool> open_curtain;
|
||||
in property <bool> solved;
|
||||
in property <image> icon;
|
||||
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
background: solved ? #34CE57 : #3960D5;
|
||||
animate background { duration: 800ms; }
|
||||
|
||||
Image {
|
||||
source: icon;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
|
||||
// Left curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
// Right curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: open_curtain ? parent.width : (parent.width / 2);
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
animate x { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
// Delegate to the user of this element
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
width: 326px;
|
||||
height: 326px;
|
||||
|
||||
in-out property <[TileData]> memory_tiles: [
|
||||
{ image: @image-url("icons/at.png") },
|
||||
{ image: @image-url("icons/balance-scale.png") },
|
||||
{ image: @image-url("icons/bicycle.png") },
|
||||
{ image: @image-url("icons/bus.png") },
|
||||
{ image: @image-url("icons/cloud.png") },
|
||||
{ image: @image-url("icons/cogs.png") },
|
||||
{ image: @image-url("icons/motorcycle.png") },
|
||||
{ image: @image-url("icons/video.png") },
|
||||
];
|
||||
for tile[i] in memory_tiles : MemoryTile {
|
||||
x: mod(i, 4) * 74px;
|
||||
y: floor(i / 4) * 74px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
icon: tile.image;
|
||||
open_curtain: tile.image_visible || tile.solved;
|
||||
// propagate the solved status from the model to the tile
|
||||
solved: tile.solved;
|
||||
clicked => {
|
||||
tile.image_visible = !tile.image_visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
docs/reference/src/quickstart/memory.slint
Normal file
12
docs/reference/src/quickstart/memory.slint
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// ANCHOR: main_window
|
||||
// appwindow.slint
|
||||
export component MainWindow inherits Window {
|
||||
Text {
|
||||
text: "hello world";
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: main_window
|
93
docs/reference/src/quickstart/memory_game_logic.slint
Normal file
93
docs/reference/src/quickstart/memory_game_logic.slint
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
struct TileData {
|
||||
image: image,
|
||||
image_visible: bool,
|
||||
solved: bool,
|
||||
}
|
||||
|
||||
component MemoryTile inherits Rectangle {
|
||||
callback clicked;
|
||||
in property <bool> open_curtain;
|
||||
in property <bool> solved;
|
||||
in property <image> icon;
|
||||
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
background: solved ? #34CE57 : #3960D5;
|
||||
animate background { duration: 800ms; }
|
||||
|
||||
Image {
|
||||
source: icon;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
|
||||
// Left curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
// Right curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: open_curtain ? parent.width : (parent.width / 2);
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
animate x { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
// Delegate to the user of this element
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: mainwindow_interface
|
||||
export component MainWindow inherits Window {
|
||||
width: 326px;
|
||||
height: 326px;
|
||||
|
||||
callback check_if_pair_solved(); // Added
|
||||
in property <bool> disable_tiles; // Added
|
||||
|
||||
in-out property <[TileData]> memory_tiles: [
|
||||
{ image: @image-url("icons/at.png") },
|
||||
// ANCHOR_END: mainwindow_interface
|
||||
{ image: @image-url("icons/balance-scale.png") },
|
||||
{ image: @image-url("icons/bicycle.png") },
|
||||
{ image: @image-url("icons/bus.png") },
|
||||
{ image: @image-url("icons/cloud.png") },
|
||||
{ image: @image-url("icons/cogs.png") },
|
||||
{ image: @image-url("icons/motorcycle.png") },
|
||||
{ image: @image-url("icons/video.png") },
|
||||
];
|
||||
|
||||
// ANCHOR: tile_click_logic
|
||||
for tile[i] in memory_tiles : MemoryTile {
|
||||
x: mod(i, 4) * 74px;
|
||||
y: floor(i / 4) * 74px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
icon: tile.image;
|
||||
open_curtain: tile.image_visible || tile.solved;
|
||||
// propagate the solved status from the model to the tile
|
||||
solved: tile.solved;
|
||||
clicked => {
|
||||
// old: tile.image_visible = !tile.image_visible;
|
||||
// new:
|
||||
if (!root.disable_tiles) {
|
||||
tile.image_visible = !tile.image_visible;
|
||||
root.check_if_pair_solved();
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: tile_click_logic
|
||||
}
|
123
docs/reference/src/quickstart/memory_tile.md
Normal file
123
docs/reference/src/quickstart/memory_tile.md
Normal file
|
@ -0,0 +1,123 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Memory Tile
|
||||
|
||||
With the skeleton code in place, this step looks at the first element of the game, the memory tile. It's the
|
||||
visual building block that consists of an underlying filled rectangle background, the icon image. Later steps add a covering rectangle that acts as a curtain.
|
||||
|
||||
You declare the background rectangle as 64 logical pixels wide and tall
|
||||
filled with a soothing tone of blue.
|
||||
|
||||
Lengths in Slint have a unit, here, the `px` suffix.
|
||||
This makes the code easier to read and the compiler can detect when you accidentally
|
||||
mix values with different units attached to them.
|
||||
|
||||
::::{tab-set}
|
||||
|
||||
:::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Copy the following code into `ui/appwindow.slint` file, replacing the current content:
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Copy the following code into `ui/appwindow.slint` file, replacing the current content:
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
Copy the following code inside of the `slint!` macro, replacing the current content:
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
:::{literalinclude} memory_tile.slint
|
||||
:language: slint,no-preview
|
||||
:lines: 5-19
|
||||
:::
|
||||
|
||||
This exports the <span class="hljs-title">MainWindow</span> component so that the game logic code can access it later.
|
||||
|
||||
::::{tab-set}
|
||||
:::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Inside the <span class="hljs-built_in">Rectangle</span> place an <span class="hljs-built_in">Image</span> element that
|
||||
loads an icon with the <span class="hljs-built_in">@image-url()</span> macro. The path is relative to the location of `ui/appwindow.slint`.
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Inside the <span class="hljs-built_in">Rectangle</span> place an <span class="hljs-built_in">Image</span> element that
|
||||
loads an icon with the <span class="hljs-built_in">@image-url()</span> macro. The path is relative to the location of `ui/appwindow.slint`.
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
Inside the <span class="hljs-built_in">Rectangle</span> place an <span class="hljs-built_in">Image</span> element that
|
||||
loads an icon with the <span class="hljs-built_in">@image-url()</span> macro.
|
||||
When using the `slint!` macro, the path is relative to the folder that contains the `Cargo.toml` file.
|
||||
When using Slint files, it's relative to the folder of the Slint file containing it.
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
You need to install this icon and others you use later first. You can download a pre-prepared
|
||||
[Zip archive](https://slint.dev/blog/memory-game-tutorial/icons.zip) to the `ui` folder,
|
||||
|
||||
If you are on Linux or macOS, download and extract it with the following commands:
|
||||
|
||||
```sh
|
||||
cd ui
|
||||
curl -O https://slint.dev/blog/memory-game-tutorial/icons.zip
|
||||
unzip icons.zip
|
||||
cd ..
|
||||
```
|
||||
|
||||
If you are on Windows, use the following commands:
|
||||
|
||||
```sh
|
||||
cd ui
|
||||
powershell curl -Uri https://slint.dev/blog/memory-game-tutorial/icons.zip -Outfile icons.zip
|
||||
powershell Expand-Archive -Path icons.zip -DestinationPath .
|
||||
cd ..
|
||||
```
|
||||
|
||||
This unpacks an `icons` directory containing several icons.
|
||||
|
||||
::::{tab-set}
|
||||
:::{tab-item} C++
|
||||
:sync: cpp
|
||||
|
||||
Compiling the program with `cmake --build build` and running with the `./build/my_application` opens a window that shows the icon of a bus on a blue background.
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} NodeJS
|
||||
:sync: nodejs
|
||||
|
||||
Running the program with `npm start` opens a window that shows the icon of a bus on a blue background.
|
||||
|
||||
:::
|
||||
|
||||
:::{tab-item} Rust
|
||||
:sync: rust
|
||||
|
||||
Running the program with `cargo run` opens a window that shows the icon of a bus on a blue background.
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||

|
20
docs/reference/src/quickstart/memory_tile.slint
Normal file
20
docs/reference/src/quickstart/memory_tile.slint
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// ANCHOR: main_window
|
||||
component MemoryTile inherits Rectangle {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: #3960D5;
|
||||
|
||||
Image {
|
||||
source: @image-url("icons/bus.png");
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
MemoryTile {}
|
||||
}
|
||||
// ANCHOR_END: main_window
|
80
docs/reference/src/quickstart/memory_tiles_from_cpp.slint
Normal file
80
docs/reference/src/quickstart/memory_tiles_from_cpp.slint
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
struct TileData {
|
||||
image: image,
|
||||
image_visible: bool,
|
||||
solved: bool,
|
||||
}
|
||||
|
||||
component MemoryTile inherits Rectangle {
|
||||
callback clicked;
|
||||
in property <bool> open_curtain;
|
||||
in property <bool> solved;
|
||||
in property <image> icon;
|
||||
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
background: solved ? #34CE57 : #3960D5;
|
||||
animate background { duration: 800ms; }
|
||||
|
||||
Image {
|
||||
source: icon;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
}
|
||||
|
||||
// Left curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
// Right curtain
|
||||
Rectangle {
|
||||
background: #193076;
|
||||
x: open_curtain ? parent.width : (parent.width / 2);
|
||||
width: open_curtain ? 0px : (parent.width / 2);
|
||||
height: parent.height;
|
||||
animate width { duration: 250ms; easing: ease-in; }
|
||||
animate x { duration: 250ms; easing: ease-in; }
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
// Delegate to the user of this element
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
width: 326px;
|
||||
height: 326px;
|
||||
|
||||
in-out property <[TileData]> memory_tiles: [
|
||||
{ image: @image-url("icons/at.png") },
|
||||
{ image: @image-url("icons/balance-scale.png") },
|
||||
{ image: @image-url("icons/bicycle.png") },
|
||||
{ image: @image-url("icons/bus.png") },
|
||||
{ image: @image-url("icons/cloud.png") },
|
||||
{ image: @image-url("icons/cogs.png") },
|
||||
{ image: @image-url("icons/motorcycle.png") },
|
||||
{ image: @image-url("icons/video.png") },
|
||||
];
|
||||
for tile[i] in memory_tiles : MemoryTile {
|
||||
x: mod(i, 4) * 74px;
|
||||
y: floor(i / 4) * 74px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
icon: tile.image;
|
||||
open_curtain: tile.image_visible || tile.solved;
|
||||
// propagate the solved status from the model to the tile
|
||||
solved: tile.solved;
|
||||
clicked => {
|
||||
tile.image_visible = !tile.image_visible;
|
||||
}
|
||||
}
|
||||
}
|
12
docs/reference/src/quickstart/package.json
Normal file
12
docs/reference/src/quickstart/package.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "memory",
|
||||
"version": "1.5.1",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "^1.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ."
|
||||
}
|
||||
}
|
43
docs/reference/src/quickstart/polishing_the_tile.md
Normal file
43
docs/reference/src/quickstart/polishing_the_tile.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Polishing the Tile
|
||||
|
||||
In this step, you add a curtain-like cover that opens when clicked. You do this by declaring two rectangles
|
||||
below the <span class="hljs-built_in">Image</span>, so that Slint draws them after the Image and thus on top of the image.
|
||||
|
||||
The <span class="hljs-built_in">TouchArea</span> element declares a transparent rectangular region that allows
|
||||
reacting to user input such as a mouse click or tap. The element forwards a callback to the <em>MainWindow</em> indicating that a user clicked the tile.
|
||||
|
||||
The <em>MainWindow</em> reacts by flipping a custom <em>open_curtain</em> property.
|
||||
Property bindings for the animated width and x properties also use the custom <em>open_curtain</em> property.
|
||||
|
||||
The following table shows more detail on the two states:
|
||||
|
||||
| _open_curtain_ value: | false | true |
|
||||
| ----------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| Left curtain rectangle | Fill the left half by setting the width _width_ to half the parent's width | Width of zero makes the rectangle invisible |
|
||||
| Right curtain rectangle | Fill the right half by setting _x_ and _width_ to half of the parent's width | _width_ of zero makes the rectangle invisible. _x_ moves to the right, sliding the curtain open when animated |
|
||||
|
||||
To make the tile extensible, replace the hard-coded icon name with an _icon_
|
||||
property that can be set when instantiating the element.
|
||||
|
||||
For the final polish, add a
|
||||
_solved_ property used to animate the color to a shade of green when a player finds a pair.
|
||||
|
||||
Replace the code inside the `ui/appwindow.slint` file with the following:
|
||||
|
||||
:::{literalinclude} main_polishing_the_tile.rs
|
||||
:language: slint,no-preview
|
||||
:lines: 10-61
|
||||
:::
|
||||
|
||||
The code uses `root` and `self`. `root` refers to the outermost
|
||||
element in the component, the <span class="hljs-title">MemoryTile</span> in this case. `self` refers
|
||||
to the current element.
|
||||
|
||||
The code exports the <span class="hljs-title">MainWindow</span> component. This is necessary so that you can later access it
|
||||
from application business logic.
|
||||
|
||||
Running the code opens a window with a rectangle that opens up to show the bus icon when clicked. Subsequent clicks close and open the curtain again.
|
||||
|
||||
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/polishing-the-tile.mp4"></video>
|
85
docs/reference/src/quickstart/running_in_a_browser.md
Normal file
85
docs/reference/src/quickstart/running_in_a_browser.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Running In A Browser Using WebAssembly
|
||||
|
||||
:::{warning}
|
||||
Only Rust supports using Slint with WebAssembly.
|
||||
:::
|
||||
|
||||
If you're using Rust, the tutorial so far used `cargo run` to build and run the code as a native application.
|
||||
Native applications are the primary target of the Slint framework, but it also supports WebAssembly
|
||||
for demonstration purposes. This section uses the standard rust tool `wasm-bindgen` and
|
||||
`wasm-pack` to run the game in the browser. Read the [wasm-bindgen documentation](https://rustwasm.github.io/docs/wasm-bindgen/examples/without-a-bundler.html)
|
||||
for more about using wasm and rust.
|
||||
|
||||
Install `wasm-pack` using cargo:
|
||||
|
||||
```sh
|
||||
cargo install wasm-pack
|
||||
```
|
||||
|
||||
Edit the `Cargo.toml` file to add the dependencies.
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
getrandom = { version = "0.2.2", features = ["js"] }
|
||||
```
|
||||
|
||||
`'cfg(target_arch = "wasm32")'` ensures that these dependencies are only active
|
||||
when compiling for the wasm32 architecture. Note that the `rand` dependency is now duplicated,
|
||||
to enable the `"wasm-bindgen"` feature.
|
||||
|
||||
While you are editing the `Cargo.toml`, make one last change. To turn the binary into
|
||||
a library by adding the following:
|
||||
|
||||
```toml
|
||||
[lib]
|
||||
path = "src/main.rs"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
This is because wasm-pack requires Rust to generate a `"cdylib"`.
|
||||
|
||||
You also need to change `main.rs` by adding the `wasm_bindgen(start)`
|
||||
attribute to the `main` function and export it with the `pub` keyword:
|
||||
|
||||
```rust,noplayground
|
||||
#[cfg_attr(target_arch = "wasm32",
|
||||
wasm_bindgen::prelude::wasm_bindgen(start))]
|
||||
pub fn main() {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Compile the program with `wasm-pack build --release --target web`. This
|
||||
creates a `pkg` directory containing several files, including a `.js` file
|
||||
named after the program name that you need to import into an HTML file.
|
||||
|
||||
Create a minimal `index.html` in the top level of the project that declares a `<canvas>` element for rendering and loads the generated wasm
|
||||
file. The Slint runtime expects the `<canvas>` element to have the id `id = "canvas"`.
|
||||
(Replace `memory.js` with the correct file name).
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<!-- canvas required by the Slint runtime -->
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
// import the generated file.
|
||||
import init from "./pkg/memory.js";
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Unfortunately, loading ES modules isn't allowed for files on the file system when accessed from a
|
||||
`file://` URL, so you can't load the `index.html`. Instead, you need to serve it through a web server.
|
||||
For example, using Python, by running:
|
||||
|
||||
```sh
|
||||
python3 -m http.server
|
||||
```
|
||||
|
||||
Now you can access the game at [http://localhost:8000](http://localhost:8000/).
|
Loading…
Add table
Add a link
Reference in a new issue