mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 13:51:13 +00:00
Use Rust project templates as basis for current tutorial (#4488)
Uses the Rust project template to refactor the tutorial and numerous grammar and language changes.
This commit is contained in:
parent
858ed639ec
commit
ba98a89e9b
12 changed files with 169 additions and 139 deletions
|
@ -1,13 +1,14 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Conclusion
|
# Conclusion
|
||||||
|
|
||||||
In this tutorial, we have demonstrated how to combine some built-in Slint elements with Rust code to build a little
|
This tutorial showed you how to combine built-in Slint elements with Rust code to build a
|
||||||
game. There are many more features that we haven't talked about, 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:
|
||||||
|
|
||||||
* [Examples](https://github.com/slint-ui/slint/tree/master/examples): In the Slint repository we have collected a few demos and examples. These are a great starting point to learn how to use many Slint features.
|
- [Examples](https://github.com/slint-ui/slint/tree/master/examples): In the Slint repository we have collected 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.
|
- [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.
|
- [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 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 Rust crate that allows you to dynamically load `.slint` files.
|
- [Slint Interpreter API Docs](https://slint.dev/docs/rust/slint_interpreter/): The reference documentation for Rust crate that allows you to dynamically load Slint files.
|
||||||
|
|
|
@ -1,30 +1,34 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Creating The Tiles From Rust
|
# Creating The Tiles From Rust
|
||||||
|
|
||||||
The tiles in the game should have a random placement. We'll need to add the `rand` dependency to
|
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.
|
||||||
`Cargo.toml` for the randomization, using the `cargo` command.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo add rand@0.8
|
cargo add rand@0.8
|
||||||
```
|
```
|
||||||
|
|
||||||
What we'll do is take the list of tiles declared in the .slint language, duplicate it, and shuffle it.
|
Change the main function to the following:
|
||||||
We'll do so by accessing the `memory_tiles` property through the Rust code. For each top-level property,
|
|
||||||
a getter and a setter function is generated - in our case `get_memory_tiles` and `set_memory_tiles`.
|
|
||||||
Since `memory_tiles` is an array in the `.slint` language, it's represented as a [`Rc<dyn slint::Model>`](https://slint.dev/docs/rust/slint/trait.Model).
|
|
||||||
We can't modify the model generated by the .slint, but we can extract the tiles from it, and put it
|
|
||||||
in a [`VecModel`](https://slint.dev/docs/rust/slint/struct.VecModel) which implements the `Model` trait.
|
|
||||||
`VecModel` allows us to make modifications and we can use it to replace the static generated model.
|
|
||||||
|
|
||||||
We modify the main function like so:
|
|
||||||
|
|
||||||
```rust,noplayground
|
```rust,noplayground
|
||||||
{{#include main_tiles_from_rust.rs:tiles}}
|
{{#include main_tiles_from_rust.rs:tiles}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that we clone the `tiles_model` because we'll use it later to update the game logic.
|
The code takes the list of tiles, duplicates it, and shuffles it, accessing the `memory_tiles` property through the Rust code.
|
||||||
|
|
||||||
Running this gives us a window on the screen that now shows a 4 by 4 grid of rectangles, which can show or obscure
|
For each top-level property,
|
||||||
the icons when clicking. There's only one last aspect missing now, the rules for the game.
|
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 [`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.
|
||||||
|
|
||||||
|
Note that you clone the `tiles_model` because you'll 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>
|
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/creating-the-tiles-from-rust.mp4"></video>
|
||||||
|
|
|
@ -1,34 +1,39 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
# From One To Multiple Tiles
|
# From One To Multiple Tiles
|
||||||
|
|
||||||
After modeling a single tile, let's create a grid of them. For the grid to be our game board, we 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: This shall be an array where each element describes the tile data structure, such as the
|
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 shall be visible and if this tile has been solved. We modify the model
|
|
||||||
from Rust code.
|
|
||||||
1. A way of creating many instances of the tiles, with the above `.slint` markup code.
|
|
||||||
|
|
||||||
In Slint we can declare an array of structures using brackets, to create a model. We can use the <span class="hljs-keyword">for</span> loop
|
- URL of the image
|
||||||
to create many instances of the same element. In `.slint` the for loop is declarative and automatically updates when
|
- Whether the image is visible
|
||||||
the model changes. We instantiate all the different <span class="hljs-title">MemoryTile</span> elements and place them on a grid based on their
|
- If the player has solved this tile.
|
||||||
index with a little bit of spacing between the tiles.
|
|
||||||
|
|
||||||
First, we copy the tile data structure definition and paste it at top inside the `slint!` macro:
|
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.
|
||||||
|
|
||||||
|
With Slint the for 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
|
```slint
|
||||||
{{#include main_multiple_tiles.rs:tile_data}}
|
{{#include main_multiple_tiles.rs:tile_data}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, we replace the _export component <span class="hljs-title">MainWindow</span> inherits Window { ... }_ section at the bottom of the `slint!` macro with the following snippet:
|
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
|
```slint
|
||||||
{{#include main_multiple_tiles.rs:main_window}}
|
{{#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,
|
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. We use the `i` index to calculate the position of 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,
|
||||||
using the modulo and integer division to create a 4 by 4 grid.
|
using modulo and integer division to create a 4 by 4 grid.
|
||||||
|
|
||||||
Running this gives us a window that shows 8 tiles, which can be opened individually.
|
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>
|
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/from-one-to-multiple-tiles.mp4"></video>
|
||||||
|
|
|
@ -1,44 +1,48 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Game Logic In Rust
|
# Game Logic In Rust
|
||||||
|
|
||||||
We'll implement the rules of the game in Rust as well. The general philosophy of Slint is that merely the user
|
This step implements the rules of the game in Rust as well.
|
||||||
interface is implemented in the `.slint` language and the business logic in your favorite programming
|
|
||||||
language. The game rules shall enforce that at most two tiles have their curtain open. If the tiles match, then we
|
|
||||||
consider them solved and they remain open. Otherwise we wait for a little while, so the player can memorize
|
|
||||||
the location of the icons, and then close them again.
|
|
||||||
|
|
||||||
We'll modify the `.slint` markup inside the `slint!` macro to signal to the Rust code when the user clicks on a tile.
|
Slint's general philosophy is that you implement the user interface in Slint and the business logic in your favorite programming
|
||||||
Two changes to <span class="hljs-title">MainWindow</span> are needed: We need to add a way for the MainWindow to call to the Rust code that it should
|
language.
|
||||||
check if a pair of tiles has been solved. And we need to add a property that Rust code can toggle to disable further
|
|
||||||
tile interaction, to prevent the player from opening more tiles than allowed. No cheating allowed! First, we paste
|
The game rules enforce that at most two tiles have their curtain open. If the tiles match, then the game
|
||||||
the callback and property declarations into <span class="hljs-title">MainWindow</span>:
|
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
|
```slint
|
||||||
{{#include main_game_logic_in_rust.rs:mainwindow_interface}}
|
{{#include main_game_logic_in_rust.rs:mainwindow_interface}}
|
||||||
```
|
```
|
||||||
|
|
||||||
The last change to the `.slint` markup is to act when the <span class="hljs-title">MemoryTile</span> signals that it was clicked on.
|
This change adds a way for the <span class="hljs-title">MainWindow</span> to call to the Rust code that it should
|
||||||
We add the following handler in <span class="hljs-title">MainWindow</span>:
|
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 <span class="hljs-title">MainWindow</span>:
|
||||||
|
|
||||||
```slint
|
```slint
|
||||||
{{#include main_game_logic_in_rust.rs:tile_click_logic}}
|
{{#include main_game_logic_in_rust.rs:tile_click_logic}}
|
||||||
```
|
```
|
||||||
|
|
||||||
On the Rust side, we can now add an handler to the `check_if_pair_solved` callback, that will check if
|
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.
|
||||||
two tiles are opened. If they match, the `solved` property is set to true in the model. If they don't
|
If they match, the code sets the `solved` property to true in the model. If they don't
|
||||||
match, start a timer that will close them after one second. While the timer is running, we disable every tile so
|
match, start a timer that closes the tiles after one second. While the timer is running, disable every tile so
|
||||||
one can't click anything during this time.
|
a player can't click anything during this time.
|
||||||
|
|
||||||
Insert this code before the `main_window.run()` call:
|
Add this code before the `main_window.run().unwrap();` call:
|
||||||
|
|
||||||
```rust,noplayground
|
```rust,noplayground
|
||||||
{{#include main_game_logic_in_rust.rs:game_logic}}
|
{{#include main_game_logic_in_rust.rs:game_logic}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice that we take a [Weak](https://slint.dev/docs/rust/slint/struct.Weak) pointer of our `main_window`. This is very
|
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 a circular ownership.
|
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
|
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.
|
instead of strong to avoid a memory leak.
|
||||||
|
|
||||||
These were the last changes and running the result gives us a window on the screen that allows us
|
These were the last changes and running the code opens a window that allows a player to play the game by the rules.
|
||||||
to play the game by the rules.
|
|
|
@ -1,30 +1,31 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
We assume that you are a somewhat familiar with Rust, and that you know how to create a Rust application with
|
This tutorial assumes that you are somewhat familiar with Rust. We recommend using [rust-analyzer](https://rust-analyzer.github.io) and [our editor integrations for `.slint` files](https://github.com/slint-ui/slint/tree/master/editors) for following this tutorial.
|
||||||
`cargo new`. The [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started) can help you get set up.
|
|
||||||
|
|
||||||
We recommend using [rust-analyzer](https://rust-analyzer.github.io) and [our editor integrations for `.slint` files](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.
|
||||||
|
|
||||||
First, we create a new cargo project:
|
Before using the template, install `[cargo-generate](https://github.com/cargo-generate/cargo-generate)`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo new memory
|
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
|
cd memory
|
||||||
```
|
```
|
||||||
|
|
||||||
Then we edit `Cargo.toml` to add the slint dependency using `cargo add`:
|
Replace the contents of `src/main.rs` with the hello world program from the [Slint documentation](https://slint.dev/docs/rust/slint/):
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo add slint@1.4.1
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally we copy the hello world program from the [Slint documentation](https://slint.dev/docs/rust/slint/) into our `src/main.rs`:
|
|
||||||
|
|
||||||
```rust,noplayground
|
```rust,noplayground
|
||||||
{{#include main_initial.rs:main}}
|
{{#include main_initial.rs:main}}
|
||||||
```
|
```
|
||||||
|
|
||||||
We run this example with `cargo run` and a window will appear with the green "Hello World" greeting.
|
Run the example with `cargo run` and a window appears with the green "Hello World" greeting.
|
||||||
|
|
||||||

|

|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Ideas For The Reader
|
# Ideas For The Reader
|
||||||
|
|
||||||
The game is visually a little bare. Here are some ideas how you could make further changes to enhance it:
|
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 a little less sharp. The [border-radius](https://slint.dev/docs/slint/src/builtins/elements.html#rectangle)
|
- The tiles could have rounded corners, to look less sharp. Use the [border-radius](https://slint.dev/docs/slint/src/builtins/elements.html#rectangle)
|
||||||
property of _[Rectangle](https://slint.dev/docs/slint/src/builtins/elements#rectangle)_ can be used to achieve that.
|
property of _[Rectangle](https://slint.dev/docs/slint/src/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
|
- 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/builtins/elements.html#image)_
|
the help of another _[Image](https://slint.dev/docs/slint/src/builtins/elements.html#image)_
|
||||||
element. Note that you may have to use _Rectangle_'s _clip property_
|
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.
|
element around it to ensure that the image is clipped away when the curtain effect opens.
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
This tutorial will introduce you to the Slint UI framework in a playful way by implementing a little memory game. We're going to combine the `.slint` language for the graphics with the game rules implemented in Rust.
|
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.
|
The game consists of a grid of 16 rectangular tiles. Clicking on a tile uncovers an icon underneath.
|
||||||
We know that there are 8 different icons in total, so each tile has a sibling somewhere in the grid with the
|
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. You can uncover two tiles at the same time. If they
|
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 icons will be obscured again.
|
aren't the same, the game obscures the icons again.
|
||||||
If you uncover two tiles with the same icon, then they remain visible - they're solved.
|
If the player uncovers two tiles with the same icon, then they remain visible - they're solved.
|
||||||
|
|
||||||
This is how the game looks like in action:
|
This is how the game looks in action:
|
||||||
|
|
||||||
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/memory_clip.mp4"
|
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/memory_clip.mp4"
|
||||||
class="img-fluid img-thumbnail rounded"></video>
|
class="img-fluid img-thumbnail rounded"></video>
|
||||||
|
|
|
@ -50,9 +50,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
main_window.run().unwrap();
|
|
||||||
// ANCHOR_END: game_logic
|
// ANCHOR_END: game_logic
|
||||||
|
main_window.run().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
slint::slint! {
|
slint::slint! {
|
||||||
|
|
|
@ -8,16 +8,16 @@ fn main() {
|
||||||
slint::slint! {
|
slint::slint! {
|
||||||
// ANCHOR: tile_data
|
// ANCHOR: tile_data
|
||||||
|
|
||||||
// Added:
|
|
||||||
struct TileData {
|
struct TileData {
|
||||||
image: image,
|
image: image,
|
||||||
image_visible: bool,
|
image_visible: bool,
|
||||||
solved: bool,
|
solved: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANCHOR_END: tile_data
|
||||||
|
|
||||||
component MemoryTile inherits Rectangle {
|
component MemoryTile inherits Rectangle {
|
||||||
|
|
||||||
// ANCHOR_END: tile_data
|
|
||||||
callback clicked;
|
callback clicked;
|
||||||
in property <bool> open_curtain;
|
in property <bool> open_curtain;
|
||||||
in property <bool> solved;
|
in property <bool> solved;
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Memory Tile
|
# Memory Tile
|
||||||
|
|
||||||
With the skeleton in place, let's look at the first element of the game, the memory tile. It will be the
|
With the skeleton 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 we'll add a
|
visual building block that consists of an underlying filled rectangle background, the icon image. Later you'll add a
|
||||||
covering rectangle that acts as a curtain. The background rectangle is declared to be 64 logical pixels wide and tall,
|
covering rectangle that acts as a curtain.
|
||||||
and it's filled with a soothing tone of blue. Note how lengths in the `.slint` language have a unit, here
|
|
||||||
the `px` suffix. That makes the code easier to read and the compiler can detect when your are accidentally
|
|
||||||
mixing values with different units attached to them.
|
|
||||||
|
|
||||||
We copy the following code inside of the `slint!` macro:
|
You declare the background rectangle as 64 logical pixels wide and tall
|
||||||
|
and it's filled with a soothing tone of blue.
|
||||||
|
|
||||||
|
Note how lengths in the `.slint` language have a unit, here
|
||||||
|
the `px` suffix. That 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
|
```slint
|
||||||
{{#include main_mem ory_tile.rs:tile}}
|
{{#include main_mem ory_tile.rs:tile}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Inside the <span class="hljs-built_in">Rectangle</span> we 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.
|
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 in which the Cargo.toml is located.
|
|
||||||
When using .slint files, it's relative to the folder of the .slint file containing it.
|
When using the `slint!` macro, the path is relative to the folder that contains the `Cargo.toml` file.
|
||||||
This icon and others we're going to use later need to be installed first. You can download a
|
When using `.slint` files, it's relative to the folder of the `.slint` file containing it.
|
||||||
[Zip archive](https://slint.dev/blog/memory-game-tutorial/icons.zip) that we have prepared and extract it with the
|
|
||||||
|
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 two commands:
|
following two commands:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -27,9 +34,9 @@ curl -O https://slint.dev/blog/memory-game-tutorial/icons.zip
|
||||||
unzip icons.zip
|
unzip icons.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
This should unpack an `icons` directory containing a bunch of icons.
|
This unpacks an `icons` directory containing several icons.
|
||||||
|
|
||||||
Running the program with `cargo run` gives us a window on the screen that shows the icon of a bus on a
|
Running the program with `cargo run` now opens a window that shows the icon of a bus on a
|
||||||
blue background.
|
blue background.
|
||||||
|
|
||||||

|

|
||||||
|
|
|
@ -1,36 +1,42 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Polishing the Tile
|
# Polishing the Tile
|
||||||
|
|
||||||
Next, let's add a curtain like cover that opens up when clicking. We achieve this by declaring two rectangles
|
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 they are drawn afterwards and thus on top of the image.
|
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
|
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. We use that to forward a callback to the <em>MainWindow</em>
|
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.
|
||||||
that the tile was clicked on. In the <em>MainWindow</em> we react by flipping a custom <em>open_curtain</em> property.
|
|
||||||
That in turn is used in property bindings for the animated width and x properties. Let's look at the two states a bit
|
|
||||||
more in detail:
|
|
||||||
|
|
||||||
| *open_curtain* value: | false | true |
|
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.
|
||||||
| 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* is moved to the right, to slide the curtain open when animated |
|
|
||||||
|
|
||||||
In order to make our tile extensible, the hard-coded icon name is replaced with an *icon*
|
The following table shows more detail on the two states:
|
||||||
property that can be set from the outside when instantiating the element. For the final polish, we add a
|
|
||||||
*solved* property that we use to animate the color to a shade of green when we've found a pair, later. We
|
| _open_curtain_ value: | false | true |
|
||||||
replace the code inside the `slint!` macro with the following:
|
| ----------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| 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
|
```slint
|
||||||
{{#include main_polishing_the_tile.rs:tile}}
|
{{#include main_polishing_the_tile.rs:tile}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the use of `root` and `self` in the code. `root` refers to the outermost
|
The code uses `root` and `self`. `root` refers to the outermost
|
||||||
element in the component, that's 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
|
||||||
to the current element.
|
to the current element.
|
||||||
|
|
||||||
Note that we export the <span class="hljs-title">MainWindow</span> component. This is necessary so that we can later access it
|
The code exports the <span class="hljs-title">MainWindow</span> component. This is necessary so that you can later access it
|
||||||
from our business logic.
|
from application business logic.
|
||||||
|
|
||||||
Running this gives us a window on the screen with a rectangle that opens up to show us the bus icon, when clicking on
|
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.
|
||||||
it. Subsequent clicks will close and open the curtain again.
|
|
||||||
|
|
||||||
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/polishing-the-tile.mp4"></video>
|
<video autoplay loop muted playsinline src="https://slint.dev/blog/memory-game-tutorial/polishing-the-tile.mp4"></video>
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||||
|
|
||||||
# Running In A Browser Using WebAssembly
|
# Running In A Browser Using WebAssembly
|
||||||
|
|
||||||
Right now, we used `cargo run` to build and run our program as a native application.
|
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 we also support WebAssembly
|
Native applications are the primary target of the Slint framework, but we also support WebAssembly
|
||||||
for demonstration purposes. So in this section we'll use 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. The [wasm-bindgen](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)
|
||||||
documentation explains all you need to know about using wasm and rust.
|
for more about using wasm and rust.
|
||||||
|
|
||||||
Make sure to have `wasm-pack` installed using
|
Install `wasm-pack` using cargo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install wasm-pack
|
cargo install wasm-pack
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll need to edit your `Cargo.toml` to add the dependencies.
|
Edit the `Cargo.toml` to add the dependencies.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
@ -21,11 +22,11 @@ wasm-bindgen = { version = "0.2" }
|
||||||
getrandom = { version = "0.2.2", features = ["js"] }
|
getrandom = { version = "0.2.2", features = ["js"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
The `'cfg(target_arch = "wasm32")'` ensures that these dependencies will only be active
|
`'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,
|
when compiling for the wasm32 architecture. Note that the `rand` dependency is now duplicated,
|
||||||
in order to enable the `"wasm-bindgen"` feature.
|
to enable the `"wasm-bindgen"` feature.
|
||||||
|
|
||||||
While you are editing the Cargo.toml, one last change is needed: you need to turn the binary into
|
While you are editing the `Cargo.toml``, make one last change. To turn the binary into
|
||||||
a library by adding the following:
|
a library by adding the following:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -34,10 +35,10 @@ path = "src/main.rs"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
```
|
```
|
||||||
|
|
||||||
This is required because wasm-pack require rust to generate a `"cdylib"`.
|
This is needed because wasm-pack requires Rust to generate a `"cdylib"`.
|
||||||
|
|
||||||
You also need to modify the `main.rs` by adding the `wasm_bindgen(start)`
|
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:
|
attribute to the `main` function and export it with the `pub` keyword:
|
||||||
|
|
||||||
```rust,noplayground
|
```rust,noplayground
|
||||||
#[cfg_attr(target_arch = "wasm32",
|
#[cfg_attr(target_arch = "wasm32",
|
||||||
|
@ -47,12 +48,13 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, we can compile our program with `wasm-pack build --release --target web`. This
|
Now, you can compile the program with `wasm-pack build --release --target web`. This
|
||||||
will create a `pkg` directory containing a few files, including a `.js` file
|
creates a `pkg` directory containing several files, including a `.js` file
|
||||||
named after your program name. We just have to import that from a HTML file. So let's create a minimal
|
named after the program name that you need to import into an HTML file.
|
||||||
`index.html` that declares a `<canvas>` element for rendering and loads our generated wasm
|
|
||||||
|
Create a minimal `index.html` 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"`.
|
file. The Slint runtime expects the `<canvas>` element to have the id `id = "canvas"`.
|
||||||
(Replace `memory.js` by the correct file name).
|
(Replace `memory.js` with the correct file name).
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<html>
|
<html>
|
||||||
|
@ -69,11 +71,11 @@ file. The Slint runtime expects the `<canvas>` element to have the id `id = "can
|
||||||
```
|
```
|
||||||
|
|
||||||
Unfortunately, loading ES modules isn't allowed for files on the file system when accessed from a
|
Unfortunately, loading ES modules isn't allowed for files on the file system when accessed from a
|
||||||
`file://` URL, so we can't simply open the index.html. Instead we need to serve it through a web server.
|
`file://` URL, so you can't simply load the index.html. Instead, you need to serve it through a web server.
|
||||||
For example, using Python, it's as simple as running
|
For example, using Python, by running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 -m http.server
|
python3 -m http.server
|
||||||
```
|
```
|
||||||
|
|
||||||
and then you can now access the game on [http://localhost:8000](http://localhost:8000/).
|
And now you can now access the game at [http://localhost:8000](http://localhost:8000/).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue