Consolidate language tutorials (#5037)

All the language tutorials are merged into the Slint reference as "quick starts".
This commit is contained in:
Chris Chinchilla 2024-06-04 16:54:36 +02:00 committed by GitHub
parent 5389367895
commit ab9d7f342b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 632 additions and 1135 deletions

View file

@ -90,15 +90,6 @@ jobs:
cp -r target/aarch64-linux-android/doc/i_slint_backend_android_activity/ target/doc/ cp -r target/aarch64-linux-android/doc/i_slint_backend_android_activity/ target/doc/
cp -r target/aarch64-linux-android/doc/i_slint_backend_winit/ target/doc/ cp -r target/aarch64-linux-android/doc/i_slint_backend_winit/ target/doc/
cp -r target/aarch64-linux-android/doc/i_slint_backend_testing/ target/doc/ cp -r target/aarch64-linux-android/doc/i_slint_backend_testing/ target/doc/
- name: "Rust QuickStart"
run: mdbook build
working-directory: docs/quickstart/rust
- name: "C++ QuickStart"
run: mdbook build
working-directory: docs/quickstart/cpp
- name: "NodeJS QuickStart"
run: mdbook build
working-directory: docs/quickstart/node
- name: "Slint Language Documentation" - name: "Slint Language Documentation"
run: cargo xtask slintdocs --show-warnings run: cargo xtask slintdocs --show-warnings
- name: "Node docs" - name: "Node docs"
@ -116,11 +107,9 @@ jobs:
target/slintdocs/html target/slintdocs/html
api/node/docs api/node/docs
docs/site docs/site
docs/quickstart/rust/book/html
docs/quickstart/cpp/book/html
docs/quickstart/node/book/html
- name: "Check for docs warnings in internal crates" - name: "Check for docs warnings in internal crates"
run: cargo doc --workspace --no-deps --all-features --exclude slint-node --exclude pyslint --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter --exclude uefi-demo --exclude ffmpeg --exclude gstreamer-player --exclude slint-cpp --exclude slint-python run: cargo doc --workspace --no-deps --all-features --exclude slint-node --exclude pyslint --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter --exclude uefi-demo --exclude ffmpeg --exclude gstreamer-player --exclude slint-cpp --exclude slint-python
- name: Clean cache # Don't cache docs to avoid them including removed classes being published - name: Clean cache # Don't cache docs to avoid them including removed classes being published
run: | run: |
rm -rf target/doc target/cppdocs target/slintdocs api/node/docs docs/quickstart/rust/book docs/quickstart/cpp/book docs/quickstart/node/book rm -rf target/doc target/cppdocs target/slintdocs api/node/docs

View file

@ -385,7 +385,7 @@ jobs:
# Fix up link to Slint language documentation # Fix up link to Slint language documentation
sed -i "s!https://slint.dev/releases/.*/docs/!../../!" $output_path/docs/rust/slint/*.html sed -i "s!https://slint.dev/releases/.*/docs/!../../!" $output_path/docs/rust/slint/*.html
for lang in rust cpp node; do for lang in canon; do
mkdir -p $output_path/docs/quickstart/$lang mkdir -p $output_path/docs/quickstart/$lang
cp -a ../docs/quickstart/$lang/book/html/* $output_path/docs/quickstart/$lang cp -a ../docs/quickstart/$lang/book/html/* $output_path/docs/quickstart/$lang
done done

View file

@ -39,7 +39,7 @@ if(SLINT_BUILD_EXAMPLES)
add_subdirectory(examples) add_subdirectory(examples)
endif() endif()
if(SLINT_BUILD_TESTING AND (SLINT_FEATURE_COMPILER OR SLINT_COMPILER)) if(SLINT_BUILD_TESTING AND (SLINT_FEATURE_COMPILER OR SLINT_COMPILER))
add_subdirectory(docs/quickstart/cpp/src/) add_subdirectory(docs/reference/src/quickstart/)
endif() endif()
feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:")

View file

@ -10,7 +10,7 @@ members = [
'api/rs/slint', 'api/rs/slint',
'api/python', 'api/python',
'api/wasm-interpreter', 'api/wasm-interpreter',
'docs/quickstart/rust/src', 'docs/reference/src/quickstart',
'examples/7guis', 'examples/7guis',
'examples/gallery', 'examples/gallery',
'examples/imagefilter/rust', 'examples/imagefilter/rust',

View file

@ -1,12 +1,11 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT --> <!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Slint Build Guide # Slint Build Guide
This page explains how to build and test Slint. This page explains how to build and test Slint.
## Prerequisites ## Prerequisites
### Installing Rust ### Installing Rust
Install Rust by following the [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started). If you already Install Rust by following the [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started). If you already
@ -14,11 +13,13 @@ have Rust installed, make sure that it's at least version 1.73 or newer. You can
by running `rustc --version`. by running `rustc --version`.
Once this is done, you should have the `rustc` compiler and the `cargo` build system installed in your path. Once this is done, you should have the `rustc` compiler and the `cargo` build system installed in your path.
### Dependencies
* **FFMPEG**
* **Skia** (only few available binaries): ### Dependencies
<center>
- **FFMPEG**
- **Skia** (only few available binaries):
<center>
| Platform | Binaries | | Platform | Binaries |
| --------------------------------- | -------------------------------------------------- | | --------------------------------- | -------------------------------------------------- |
@ -28,10 +29,10 @@ Once this is done, you should have the `rustc` compiler and the `cargo` build sy
| Android | `aarch64-linux-android`<br/>`x86_64-linux-android` | | Android | `aarch64-linux-android`<br/>`x86_64-linux-android` |
| iOS | `aarch64-apple-ios`<br/>`x86_64-apple-ios` | | iOS | `aarch64-apple-ios`<br/>`x86_64-apple-ios` |
| WebAssembly | `wasm32-unknown-emscripten` | | WebAssembly | `wasm32-unknown-emscripten` |
</center>
- Use Skia capable toolchain `rustup default stable-x86_64-pc-windows-msvc` </center>
- Use Skia capable toolchain `rustup default stable-x86_64-pc-windows-msvc`
### Linux ### Linux
@ -48,42 +49,42 @@ For Linux a few additional packages beyond the usual build essentials are needed
### macOS ### macOS
- Make sure the "Xcode Command Line Tools" are installed: `xcode-select --install` - Make sure the "Xcode Command Line Tools" are installed: `xcode-select --install`
- (optional) Qt will be used when `qmake` is found in `PATH` - (optional) Qt will be used when `qmake` is found in `PATH`
- FFMPEG `brew install pkg-config ffmpeg` - FFMPEG `brew install pkg-config ffmpeg`
### Windows ### Windows
- See [System Link](#symlinks-in-the-repository-windows)
- Make sure the MSVC Build Tools are installed: `winget install Microsoft.VisualStudio.2022.BuildTools`
- (optional) make sure Qt is installed and `qmake` is in the `Path`
- FFMPEG
- Option 1:
- install [vcpkg](https://github.com/microsoft/vcpkg#quick-start-windows)
- `vcpkg install ffmpeg --triplet x64-windows`
- Make sure `VCPKG_ROOT` is set to where `vcpkg` is installed
- Make sure `%VCPKG_ROOT%\installed\x64-windows\bin` is in your path
- Option 2: - See [System Link](#symlinks-in-the-repository-windows)
- Download FFMPEG 4.4 shared and extract (https://github.com/BtbN/FFmpeg-Builds/releases/tag/latest) - Make sure the MSVC Build Tools are installed: `winget install Microsoft.VisualStudio.2022.BuildTools`
- Add FFMPEG to path: `*\ffmpeg\bin` `*\ffmpeg\include\libavutil` `*\ffmpeg\lib` - (optional) make sure Qt is installed and `qmake` is in the `Path`
- FFMPEG
- Option 1:
- install [vcpkg](https://github.com/microsoft/vcpkg#quick-start-windows)
- `vcpkg install ffmpeg --triplet x64-windows`
- Make sure `VCPKG_ROOT` is set to where `vcpkg` is installed
- Make sure `%VCPKG_ROOT%\installed\x64-windows\bin` is in your path
- Option 2:
- Download FFMPEG 4.4 shared and extract (https://github.com/BtbN/FFmpeg-Builds/releases/tag/latest)
- Add FFMPEG to path: `*\ffmpeg\bin` `*\ffmpeg\include\libavutil` `*\ffmpeg\lib`
### C++ API (optional) ### C++ API (optional)
To use Slint from C++, the following extra dependencies are needed: To use Slint from C++, the following extra dependencies are needed:
- **[cmake](https://cmake.org/download/)** (3.21 or newer) - **[cmake](https://cmake.org/download/)** (3.21 or newer)
- **[Ninja](https://ninja-build.org)** (Optional, or remove the `-GNinja` when invoking `cmake`) - **[Ninja](https://ninja-build.org)** (Optional, or remove the `-GNinja` when invoking `cmake`)
- A C++ compiler that supports C++20 (e.g., **MSVC 2022 17.3** on Windows, or **GCC 10**) - A C++ compiler that supports C++20 (e.g., **MSVC 2022 17.3** on Windows, or **GCC 10**)
### Node.js API (optional) ### Node.js API (optional)
To use Slint from Node.js, the following extra dependencies are needed. To use Slint from Node.js, the following extra dependencies are needed.
- **[Node.js](https://nodejs.org/en/)** (including npm) At this time you will need to use the version 16. - **[Node.js](https://nodejs.org/en/)** (including npm) At this time you will need to use the version 16.
- **[Python](https://www.python.org)** - **[Python](https://www.python.org)**
### Symlinks in the repository (Windows) ### Symlinks in the repository (Windows)
@ -111,17 +112,14 @@ cargo test
<center> ** <strong> Not recommended</strong> ** </center> <center> ** <strong> Not recommended</strong> ** </center>
To build all examples install the entire workplace to executables
To build all examples install the entire workplace to executables
(excluding [UEFI-demo](https://github.com/slint-ui/slint/tree/master/examples/uefi-demo) - different target) (excluding [UEFI-demo](https://github.com/slint-ui/slint/tree/master/examples/uefi-demo) - different target)
- Build workspace
- Build workspace
```sh ```sh
cargo build --workspace --exclude uefi-demo --release cargo build --workspace --exclude uefi-demo --release
``` ```
**Important:** Note that `cargo test` does not work without first calling `cargo build` because the **Important:** Note that `cargo test` does not work without first calling `cargo build` because the
the required dynamic library won't be found. the required dynamic library won't be found.
@ -148,8 +146,6 @@ cargo build -p test-driver-nodejs
For more details about the tests and how they are implemented, see [testing.md](./testing.md). For more details about the tests and how they are implemented, see [testing.md](./testing.md).
## C++ API Build ## C++ API Build
The Slint C++ API is implemented as a normal cmake build: The Slint C++ API is implemented as a normal cmake build:
@ -225,52 +221,29 @@ cargo run --release --bin slint-viewer -- examples/printerdemo/ui/printerdemo.sl
The Slint documentation consists of five parts: The Slint documentation consists of five parts:
- The tutorials - The quickstart guide
- The Rust API documentation - The Rust API documentation
- The C++ API documentation - The C++ API documentation
- The Node.js API documentation - The Node.js API documentation
- The DSL documentation - The DSL documentation
### Tutorials The quickstart guide is part of the DSL documentation.
There are three tutorials built with mdbook, one for each of the three languages supported by Slint. ### Quickstart and DSL docs
The quickstart and DSL docs are written in markdown and built with Sphinx, using the myst parser extension.
**Prerequisites**: **Prerequisites**:
- [mdbook](https://rust-lang.github.io/mdBook/guide/installation.html) - [pipenv](https://pipenv.pypa.io/en/latest/)
- [Python](https://www.python.org/downloads/)
#### Rust tutorial Use the following command line to build the documentation using `rustdoc` to the `target/slintdocs/html` folder:
```shell
mdbook build docs/quickstart/rust
```
#### C++ tutorial
```shell
mdbook build docs/quickstart/cpp
```
#### NodeJS tutorial
```shell
mdbook build docs/quickstart/node
```
### Slint DSL docs
**Prerequisites**:
- [pipenv](https://pipenv.pypa.io/en/latest/)
- [Python](https://www.python.org/downloads/)
Use the following command line to build the documentation for the Slint DSL using `rustdoc` to the `target/slintdocs/html` folder:
```shell ```shell
cargo xtask slintdocs --show-warnings cargo xtask slintdocs --show-warnings
``` ```
### Rust API docs ### Rust API docs
Run the following command to generate the documentation using rustdoc in the `target/doc/` sub-folder: Run the following command to generate the documentation using rustdoc in the `target/doc/` sub-folder:
@ -279,12 +252,13 @@ Run the following command to generate the documentation using rustdoc in the `ta
RUSTDOCFLAGS="--html-in-header=$PWD/docs/resources/slint-docs-preview.html --html-in-header=$PWD/docs/resources/slint-docs-highlight.html" cargo doc --no-deps --features slint/document-features,slint/log RUSTDOCFLAGS="--html-in-header=$PWD/docs/resources/slint-docs-preview.html --html-in-header=$PWD/docs/resources/slint-docs-highlight.html" cargo doc --no-deps --features slint/document-features,slint/log
``` ```
Note: `--html-in-header` arguments passed to rustdoc via `RUSTDOCFLAGS` are used to enable syntax highlighting and live-preview for Slint example snippets. Note: `--html-in-header` arguments passed to rustdoc via `RUSTDOCFLAGS` are used to enable syntax highlighting and live-preview for Slint example snippets.
### C++ API docs ### C++ API docs
**Prerequisites**: **Prerequisites**:
- [Doxygen](https://www.doxygen.nl/download.html) - [Doxygen](https://www.doxygen.nl/download.html)
Run the following command to generate the documentation using sphinx/exhale/breathe/doxygen/myst_parser in the `target/cppdocs` sub-folder: Run the following command to generate the documentation using sphinx/exhale/breathe/doxygen/myst_parser in the `target/cppdocs` sub-folder:
@ -299,4 +273,4 @@ Run the following commands from the `/api/node` sub-folder to generate the docs
```sh ```sh
npm install npm install
npm run docs npm run docs
``` ```

View file

@ -1,12 +0,0 @@
[book]
authors = ["Slint Developers <info@slint.dev>"]
language = "en"
multilingual = false
src = "src"
title = "Slint Memory Game QuickStart (C++)"
[output.html]
theme = "../theme"
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
optional = true

View file

@ -1,12 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Summary
- [Introduction](./introduction.md)
- [Getting Started](./getting_started.md)
- [Memory Tile](./memory_tile.md)
- [Polishing the Tile](./polishing_the_tile.md)
- [From One To Multiple Tiles](./from_one_to_multiple_tiles.md)
- [Creating The Tiles From C++](./creating_the_tiles_from_cpp.md)
- [Game Logic In C++](./game_logic_in_cpp.md)
- [Ideas For The Reader](./ideas_for_the_reader.md)
- [Conclusion](./conclusion.md)

View file

@ -1,14 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Conclusion
This tutorial showed you how to combine built-in Slint elements with C++ 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.

View file

@ -1,27 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Creating The Tiles From C++
This step places the game tiles randomly.
Change the `main` function and includes in `src/main.cpp` to the following:
```cpp
{{#include main_tiles_from_cpp.cpp:main}}
```
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.
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>

View file

@ -1,48 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Game Logic In C++
This step implements the rules of the game in C++.
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.
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.
```slint
{{#include ../../rust/src/main_game_logic_in_rust.rs:mainwindow_interface}}
```
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:
```slint
{{#include ../../rust/src/main_game_logic_in_rust.rs:tile_click_logic}}
```
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:
```cpp
{{#include main_game_logic.cpp:game_logic}}
```
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.
These were the last changes and running the code opens a window that allows a player to play the game by the rules.

View file

@ -1,75 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Getting Started
This tutorial uses C++ as the host programming language. Slint also supports other programming languages like
[Rust](https://slint.dev/docs/rust/slint/) or [JavaScript](https://slint.dev/docs/node/).
We recommend using [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, you need a C++ compiler that supports C++ 20 and to install [CMake](https://cmake.org/download/) 3.21 or newer.
Clone or download template repository:
```sh
git clone https://github.com/slint-ui/slint-cpp-template memory
cd memory
```
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:
```cpp
{{#include main_initial.cpp:main}}
```
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:
```slint
{{#include appwindow.slint:main_window}}
```
Configure with CMake:
```sh
cmake -B build
```
_Note_: 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 binary on Linux or macOS:
```sh
./build/my_application
```
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 it with
```sh
my_application
```
![Screenshot of initial tutorial app showing Hello World](https://slint.dev/blog/memory-game-tutorial/getting-started.png "Hello World")

View file

@ -1,16 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Introduction
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++.
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:
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/memory_clip.mp4"
class="img-fluid img-thumbnail rounded"></video>

View file

@ -1,94 +0,0 @@
// 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;
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();
}
}
}
// 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
}

View file

@ -1,12 +0,0 @@
[book]
authors = ["Slint Developers <info@slint.dev>"]
language = "en"
multilingual = false
src = "src"
title = "Slint Memory Game QuickStart (node)"
[output.html]
theme = "../theme"
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
optional = true

View file

@ -1,12 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Summary
- [Introduction](./introduction.md)
- [Getting Started](./getting_started.md)
- [Memory Tile](./memory_tile.md)
- [Polishing the Tile](./polishing_the_tile.md)
- [From One To Multiple Tiles](./from_one_to_multiple_tiles.md)
- [Creating The Tiles From JavaScript](./creating_the_tiles_from_js.md)
- [Game Logic In JavaScript](./game_logic_in_js.md)
- [Ideas For The Reader](./ideas_for_the_reader.md)
- [Conclusion](./conclusion.md)

View file

@ -1,25 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Creating The Tiles From JavaScript
This step places the game tiles randomly.
Change `main.js` to the following:
```js
{{#include main_tiles_from_js.js:main}}
```
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.
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>

View file

@ -1,40 +0,0 @@
<!-- 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 JavaScript model, 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.
First, add the tile data structure definition at the top of the `memory.slint` file:
```slint
{{#include ../../rust/src/main_multiple_tiles.rs:tile_data}}
```
Next, replace the _export component <span class="hljs-title">MainWindow</span> inherits Window { ... }_ section at the bottom of the `memory.slint` file with the following:
```slint
{{#include ../../rust/src/main_multiple_tiles.rs:main_window}}
```
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>

View file

@ -1,42 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Game Logic In JavaScript
This step implements the rules of the game in JavaScript.
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.
Change the contents of `memory.slint` to signal to the JavaScript code when the user clicks on a tile.
```slint
{{#include ../../rust/src/main_game_logic_in_rust.rs:mainwindow_interface}}
```
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:
```slint
{{#include ../../rust/src/main_game_logic_in_rust.rs:tile_click_logic}}
```
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:
```js
{{#include main_game_logic.js:game_logic}}
```
These were the last changes and running the code opens a window that allows a player to play the game by the rules.

View file

@ -1,43 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Getting Started
This tutorial uses JavaScript as the host programming language. Slint also supports other programming languages like
[Rust](https://slint.dev/docs/rust/slint/) or [C++](https://slint.dev/docs/cpp/).
We recommend using [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.
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
```
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:
```js
{{#include main_initial.js:main}}
```
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:
```slint
{{#include memory.slint:main_window}}
```
Run the example with `npm start` and a window appears with the green "Hello World" greeting.
![Screenshot of an initial tutorial app showing Hello World](https://slint.dev/blog/memory-game-tutorial/getting-started.png "Hello World")

View file

@ -1 +0,0 @@
../../../../examples/memory/icons

View file

@ -1,16 +0,0 @@
<!-- 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.

View file

@ -1,16 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Introduction
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 JavaScript.
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:
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/memory_clip.mp4"
class="img-fluid img-thumbnail rounded"></video>

View file

@ -1,52 +0,0 @@
<!-- 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.
Copy the following code into `ui/appwindow.slint` file, replacing the current content:
```slint
{{#include memory_tile.slint:main_window}}
```
The code exports the <span class="hljs-title">MainWindow</span> component so that the JavaScript code can access it later.
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`.
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.
Running the program with `npm start` opens a window that shows the icon of a bus on a
blue background.
![Screenshot of the first tile](https://slint.dev/blog/memory-game-tutorial/memory-tile.png "Memory Tile Screenshot")

View file

@ -1,20 +0,0 @@
// 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

View file

@ -1,81 +0,0 @@
// 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;
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 {
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;
}
}
}

View file

@ -1,42 +0,0 @@
<!-- 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 in `ui/appwindow.slint` with the following:
```slint
{{#include ../../rust/src/main_polishing_the_tile.rs:tile}}
```
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>

View file

@ -1,12 +0,0 @@
[book]
authors = ["Slint Developers <info@slint.dev>"]
language = "en"
multilingual = false
src = "src"
title = "Slint Memory Game QuickStart (Rust)"
[output.html]
theme = "../theme"
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
optional = true

View file

@ -1,13 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Summary
- [Introduction](./introduction.md)
- [Getting Started](./getting_started.md)
- [Memory Tile](./memory_tile.md)
- [Polishing the Tile](./polishing_the_tile.md)
- [From One To Multiple Tiles](./from_one_to_multiple_tiles.md)
- [Creating The Tiles From Rust](./creating_the_tiles_from_rust.md)
- [Game Logic In Rust](./game_logic_in_rust.md)
- [Ideas For The Reader](./ideas_for_the_reader.md)
- [Running In A Browser Using WebAssembly](./running_in_a_browser.md)
- [Conclusion](./conclusion.md)

View file

@ -1,14 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Conclusion
This tutorial showed you how to combine built-in Slint elements with Rust 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.

View file

@ -1,34 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Creating The Tiles From Rust
This step places the game tiles randomly. 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:
```rust,noplayground
{{#include main_tiles_from_rust.rs:tiles}}
```
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.
The code clones the `tiles_model` because you use it later to update the game logic.
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>

View file

@ -1,39 +0,0 @@
<!-- 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 Rust model, 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.
First, add the tile data structure definition at the top of the `slint!` macro:
```slint
{{#include main_multiple_tiles.rs:tile_data}}
```
Next, replace the _export component <span class="hljs-title">MainWindow</span> inherits Window { ... }_ section at the bottom of the `slint!` macro with the following:
```slint
{{#include main_multiple_tiles.rs:main_window}}
```
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>

View file

@ -1,48 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Game Logic In Rust
This step implements the rules of the game in Rust.
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.
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.
```slint
{{#include main_game_logic_in_rust.rs:mainwindow_interface}}
```
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:
```slint
{{#include main_game_logic_in_rust.rs:tile_click_logic}}
```
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:
```rust,noplayground
{{#include main_game_logic_in_rust.rs:game_logic}}
```
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.

View file

@ -1,33 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Getting Started
This tutorial uses Rust as the host programming language. Slint also supports other programming languages like
[C++](https://slint.dev/docs/cpp/) or [JavaScript](https://slint.dev/docs/node/).
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
```
Replace the contents of `src/main.rs` with the following:
```rust,noplayground
{{#include main_initial.rs:main}}
```
Run the example with `cargo run` and a window appears with the green "Hello World" greeting.
![Screenshot of an initial tutorial app showing Hello World](https://slint.dev/blog/memory-game-tutorial/getting-started.png "Hello World")

View file

@ -1 +0,0 @@
../../../../examples/memory/icons

View file

@ -1,16 +0,0 @@
<!-- 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.

View file

@ -1,15 +0,0 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Introduction
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 Rust.
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:
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/memory_clip.mp4"
class="img-fluid img-thumbnail rounded"></video>

View file

@ -1,41 +0,0 @@
<!-- 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.
Copy the following code inside of the `slint!` macro, replacing the current content:
```slint
{{#include main_memory_tile.rs:tile}}
```
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) and extract it with the
following commands:
```sh
curl -O https://slint.dev/blog/memory-game-tutorial/icons.zip
unzip icons.zip
```
This unpacks an `icons` directory containing several icons.
Running the program with `cargo run` opens a window that shows the icon of a bus on a
blue background.
![Screenshot of the first tile](https://slint.dev/blog/memory-game-tutorial/memory-tile.png "Memory Tile Screenshot")

View file

@ -1,42 +0,0 @@
<!-- 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 `slint!` macro with the following:
```slint
{{#include main_polishing_the_tile.rs:tile}}
```
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>

View file

@ -1 +0,0 @@
../../resources/slint-docs-highlight.html

View file

@ -1 +0,0 @@
/* Disabled as part of disabling mdbook's highlight.js version, see highlight.js */

View file

@ -1,6 +0,0 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
// This file is empty to override and disable mdbook's built-in highlight.js
// version, which doesn't include CMake support. Instead the appropriate version
// of highlight.js is included via head.hbs

View file

@ -10,7 +10,9 @@ verify_ssl = true
[packages] [packages]
sphinx = "==7.1.2" sphinx = "==7.1.2"
myst_parser = ">=0.17.2" myst_parser = ">=2.0.0"
sphinx-design = ">=0.5.0"
sphinx-tabs = ">=3.4.5"
sphinx-markdown-tables = ">=0.0.15" sphinx-markdown-tables = ">=0.0.15"
furo = ">=2022.12.7" furo = ">=2022.12.7"
sphinxcontrib-jquery = ">=4.1" sphinxcontrib-jquery = ">=4.1"

View file

@ -0,0 +1,16 @@
/*
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
// Based on code from stsewd her https://github.com/readthedocs/readthedocs.org/commit/738b6b2836a7e0cadad48e7f407fdeaf7ba7a1d7
*/
$(document).ready(function () {
const tabName = location.hash.substring(1);
if (tabName !== null) {
const tab = $('[data-sync-id~="' + tabName + '"]');
if (tab.length > 0) {
tab.click();
}
}
});

View file

@ -34,7 +34,7 @@ author = "Slint Developers <info@slint.dev>"
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = ["myst_parser", "sphinx_markdown_tables", "sphinx.ext.autosectionlabel", "sphinxcontrib.jquery"] extensions = ["myst_parser", "sphinx_markdown_tables", "sphinx.ext.autosectionlabel", "sphinxcontrib.jquery", "sphinx_tabs.tabs", "sphinx_design"]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ["_templates"]
@ -86,7 +86,7 @@ html_theme_options = {
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] html_static_path = ["_static"]
html_js_files = ['expand_tabs.js']
html_show_sourcelink = False html_show_sourcelink = False
html_logo = "https://slint.dev/logo/slint-logo-small-light.svg" html_logo = "https://slint.dev/logo/slint-logo-small-light.svg"
@ -96,6 +96,7 @@ myst_enable_extensions = [
] ]
myst_url_schemes = { myst_url_schemes = {
"slint-qs": f"https://slint.dev/releases/{version}/docs/quickstart/{{{{path}}}}",
"slint-cpp": f"https://slint.dev/releases/{version}/docs/cpp/{{{{path}}}}", "slint-cpp": f"https://slint.dev/releases/{version}/docs/cpp/{{{{path}}}}",
"slint-rust": f"https://slint.dev/releases/{version}/docs/rust/slint/{{{{path}}}}", "slint-rust": f"https://slint.dev/releases/{version}/docs/rust/slint/{{{{path}}}}",
"slint-build-rust": f"https://slint.dev/releases/{version}/docs/rust/slint_build/{{{{path}}}}", "slint-build-rust": f"https://slint.dev/releases/{version}/docs/rust/slint_build/{{{{path}}}}",

View file

@ -11,6 +11,7 @@
src/introduction/index.rst src/introduction/index.rst
src/introduction/supported_platforms.md src/introduction/supported_platforms.md
src/quickstart/index.rst
.. toctree:: .. toctree::
:hidden: :hidden:

View file

@ -2,7 +2,7 @@
# Conclusion # Conclusion
This tutorial showed you how to combine built-in Slint elements with JavaScript code to build a 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. game. There is much more to Slint, such as layouts, widgets, or styling.
We recommend the following links to continue: We recommend the following links to continue:

View 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>

View file

@ -4,7 +4,7 @@
After modeling a single tile, this step creates a grid of them. For the grid to be a game board, you need two features: 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 C++ model, where each element describes the tile data structure, such as: 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 - URL of the image
- Whether the image is visible - Whether the image is visible
@ -19,17 +19,63 @@ The <span class="hljs-keyword">for</span> loop is declarative and automatically
the model changes. The loop instantiates all the <span class="hljs-title">MemoryTile</span> elements and places them on a grid based on their 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. 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: First, add the tile data structure definition at the top of the `ui/appwindow.slint` file:
```slint :::
{{#include ../../rust/src/main_multiple_tiles.rs:tile_data}}
``` :::{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: 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:
```slint :::
{{#include ../../rust/src/main_multiple_tiles.rs:main_window}}
``` :::{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, 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, 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,

View 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.

View 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.
::::
:::::
![Screenshot of initial tutorial app showing Hello World](https://slint.dev/blog/memory-game-tutorial/getting-started.png "Hello World")

View 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

View file

@ -8,7 +8,7 @@ import * as slint from "slint-ui";
let ui = slint.loadFile("./memory.slint"); let ui = slint.loadFile("./memory.slint");
let mainWindow = new ui.MainWindow(); let mainWindow = new ui.MainWindow();
let initial_tiles = [...mainWindow.memory_tiles]; let initial_tiles = mainWindow.memory_tiles;
let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile))); let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile)));
for (let i = tiles.length - 1; i > 0; i--) { for (let i = tiles.length - 1; i > 0; i--) {

View file

@ -81,7 +81,6 @@ slint::slint! {
// Left curtain // Left curtain
Rectangle { Rectangle {
background: #193076; background: #193076;
x: 0px;
width: open_curtain ? 0px : (parent.width / 2); width: open_curtain ? 0px : (parent.width / 2);
height: parent.height; height: parent.height;
animate width { duration: 250ms; easing: ease-in; } animate width { duration: 250ms; easing: ease-in; }

View file

@ -37,7 +37,6 @@ component MemoryTile inherits Rectangle {
// Left curtain // Left curtain
Rectangle { Rectangle {
background: #193076; background: #193076;
x: 0px;
width: open_curtain ? 0px : (parent.width / 2); width: open_curtain ? 0px : (parent.width / 2);
height: parent.height; height: parent.height;
animate width { duration: 250ms; easing: ease-in; } animate width { duration: 250ms; easing: ease-in; }

View file

@ -7,7 +7,7 @@ import * as slint from "slint-ui";
let ui = slint.loadFile("./ui/appwindow.slint"); let ui = slint.loadFile("./ui/appwindow.slint");
let mainWindow = new ui.MainWindow(); let mainWindow = new ui.MainWindow();
let initial_tiles = [...mainWindow.memory_tiles]; let initial_tiles = mainWindow.memory_tiles;
let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile))); let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile)));
for (let i = tiles.length - 1; i > 0; i--) { for (let i = tiles.length - 1; i > 0; i--) {

View file

@ -53,7 +53,6 @@ slint::slint! {
// Left curtain // Left curtain
Rectangle { Rectangle {
background: #193076; background: #193076;
x: 0px;
width: open_curtain ? 0px : (parent.width / 2); width: open_curtain ? 0px : (parent.width / 2);
height: parent.height; height: parent.height;
animate width { duration: 250ms; easing: ease-in; } animate width { duration: 250ms; easing: ease-in; }

View file

@ -27,7 +27,6 @@ component MemoryTile inherits Rectangle {
// Left curtain // Left curtain
Rectangle { Rectangle {
background: #193076; background: #193076;
x: 0px;
width: open_curtain ? 0px : (parent.width / 2); width: open_curtain ? 0px : (parent.width / 2);
height: parent.height; height: parent.height;
animate width { duration: 250ms; easing: ease-in; } animate width { duration: 250ms; easing: ease-in; }

View file

@ -12,17 +12,67 @@ 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 This makes the code easier to read and the compiler can detect when you accidentally
mix values with different units attached to them. 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: Copy the following code into `ui/appwindow.slint` file, replacing the current content:
```slint :::
{{#include memory_tile.slint:main_window}}
```
The code exports the <span class="hljs-title">MainWindow</span> component so that the C++ code can access it later. :::{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 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`. 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 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, [Zip archive](https://slint.dev/blog/memory-game-tutorial/icons.zip) to the `ui` folder,
@ -46,6 +96,28 @@ cd ..
This unpacks an `icons` directory containing several icons. 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. 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.
:::
::::
![Screenshot of the first tile](https://slint.dev/blog/memory-game-tutorial/memory-tile.png "Memory Tile Screenshot") ![Screenshot of the first tile](https://slint.dev/blog/memory-game-tutorial/memory-tile.png "Memory Tile Screenshot")

View file

@ -27,7 +27,6 @@ component MemoryTile inherits Rectangle {
// Left curtain // Left curtain
Rectangle { Rectangle {
background: #193076; background: #193076;
x: 0px;
width: open_curtain ? 0px : (parent.width / 2); width: open_curtain ? 0px : (parent.width / 2);
height: parent.height; height: parent.height;
animate width { duration: 250ms; easing: ease-in; } animate width { duration: 250ms; easing: ease-in; }

View file

@ -1,6 +1,6 @@
{ {
"name": "memory", "name": "memory",
"version": "1.6.0", "version": "1.5.1",
"main": "main.js", "main": "main.js",
"type": "module", "type": "module",
"dependencies": { "dependencies": {

View file

@ -26,9 +26,10 @@ _solved_ property used to animate the color to a shade of green when a player fi
Replace the code inside the `ui/appwindow.slint` file with the following: Replace the code inside the `ui/appwindow.slint` file with the following:
```slint :::{literalinclude} main_polishing_the_tile.rs
{{#include ../../rust/src/main_polishing_the_tile.rs:tile}} :language: slint,no-preview
``` :lines: 10-61
:::
The code uses `root` and `self`. `root` refers to the outermost 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 element in the component, the <span class="hljs-title">MemoryTile</span> in this case. `self` refers

View file

@ -2,7 +2,11 @@
# Running In A Browser Using WebAssembly # Running In A Browser Using WebAssembly
The tutorial so far used `cargo run` to build and run the code as a native application. :::{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 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 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) `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)