Skip to content

Commit

Permalink
add js memory tutorial (slint-ui#2421)
Browse files Browse the repository at this point in the history
  • Loading branch information
FloVanGH authored Mar 28, 2023
1 parent 16fbee0 commit c6472f9
Show file tree
Hide file tree
Showing 23 changed files with 597 additions and 2 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/build_docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ jobs:
- name: "C++ Tutorial Docs"
run: mdbook build
working-directory: docs/tutorial/cpp
- name: "NodeJS Tutorial Docs"
run: mdbook build
working-directory: docs/tutorial/node
- name: "Slint Language Documentation"
run: cargo xtask slintdocs --show-warnings
- name: "Node docs"
Expand All @@ -102,8 +105,9 @@ jobs:
docs/site
docs/tutorial/rust/book/html
docs/tutorial/cpp/book/html
docs/tutorial/node/book/html
- name: "Check for docs warnings in internal crates"
run: cargo +nightly doc --workspace --no-deps --all-features --exclude slint-node --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter
- name: Clean cache # Don't cache docs to avoid them including removed classes being published
run: |
rm -rf target/doc target/cppdocs target/slintdocs api/node/docs docs/tutorial/rust/book docs/tutorial/cpp/book
rm -rf target/doc target/cppdocs target/slintdocs api/node/docs docs/tutorial/rust/book docs/tutorial/cpp/book docs/tutorial/node/book
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Refer to the README of each language directory in the `api` folder:

- [C++](api/cpp) ([Documentation](https://slint-ui.com/docs/cpp) | [Tutorial](https://slint-ui.com/docs/tutorial/cpp) | [Tutorial Video](https://youtu.be/_-Hxr6ZrHyo) | [Getting Started Template](https://github.com/slint-ui/slint-cpp-template))
- [Rust](api/rs/slint) [![Crates.io](https://img.shields.io/crates/v/slint)](https://crates.io/crates/slint) ([Documentation](https://slint-ui.com/docs/rust/slint/) | [Tutorial](https://slint-ui.com/docs/tutorial/rust) | [Tutorial Video](https://youtu.be/_-Hxr6ZrHyo) | [Getting Started Template](https://github.com/slint-ui/slint-rust-template))
- [JavaScript/NodeJS (Beta)](api/node) [![npm](https://img.shields.io/npm/v/slint-ui)](https://www.npmjs.com/package/sixtyfps) ([Documentation](https://slint-ui.com/docs/node))
- [JavaScript/NodeJS (Beta)](api/node) [![npm](https://img.shields.io/npm/v/slint-ui)](https://www.npmjs.com/package/sixtyfps) ([Documentation](https://slint-ui.com/docs/node) | [Tutorial](https://slint-ui.com/docs/tutorial/node) | [Getting Started Template](https://github.com/slint-ui/slint-nodejs-template))

## Demos

Expand Down
2 changes: 2 additions & 0 deletions docs/site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,8 @@ <h2>Documentation</h2>
/>
<strong>Using Slint in a NodeJS Project (Beta)</strong>
<a class="btn" href="node">NodeJS Documentation</a>
<br />
<a class="btn" href="tutorial/node">NodeJS Tutorial</a>
</li>
</ul>
</section>
Expand Down
12 changes: 12 additions & 0 deletions docs/tutorial/node/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[book]
authors = ["Slint Developers <info@slint-ui.com>"]
language = "en"
multilingual = false
src = "src"
title = "Slint Memory Game Tutorial (node)"

[output.html]
theme = "../theme"

[output.linkcheck] # enable the "mdbook-linkcheck" renderer
optional = true
11 changes: 11 additions & 0 deletions docs/tutorial/node/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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)
11 changes: 11 additions & 0 deletions docs/tutorial/node/src/conclusion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Conclusion

In this tutorial, we have demonstrated how to combine some built-in Slint elements with JavaScript code to build a little
game. There are many more features that we haven't talked about, such as layouts, widgets, or styling.

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.
* [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-ui.com/demos/memory/" target="_blank">play the wasm version</a> in your browser.
* [Slint API Docs](https://slint-ui.com/docs/node/): The reference documentation for the NodeJS library.
19 changes: 19 additions & 0 deletions docs/tutorial/node/src/creating_the_tiles_from_js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Creating The Tiles From JavaScript

What we'll do is take the list of tiles declared in the .slint language, duplicate it, and shuffle it.
We'll do so by accessing the `memory_tiles` property through the JavaScript code - in our case `memory_tiles`.
Since `memory_tiles` is an array in the `.slint` language, it's represented as a JavaScript [`Array`](https://slint-ui.com/releases/0.3.5/docs/node/).
We can't modify the model generated by the .slint, but we can extract the tiles from it, and put it
in a [`slint.ArrayModel`](https://slint-ui.com/releases/0.3.5/docs/node/classes/arraymodel.html) which implements the [`Model`](https://slint-ui.com/releases/0.3.5/docs/node/interfaces/model.html) interface.
`ArrayModel` allows us to make modifications and we can use it to replace the static generated model.

We modify the main function like so:

```js
{{#include main_tiles_from_js.js:main}}
```

Running this gives us a window on the screen that now shows a 4 by 4 grid of rectangles, which can show or obscure
the icons when clicking. There's only one last aspect missing now, the rules for the game.

<video autoplay loop muted playsinline src="https://slint-ui.com/blog/memory-game-tutorial/creating-the-tiles-from-rust.mp4"></video>
33 changes: 33 additions & 0 deletions docs/tutorial/node/src/from_one_to_multiple_tiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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:

1. A data model: This shall be an array where each element describes the tile data structure, such as the
url of the image, whether the image shall be visible and if this tile has been solved. We modify the model
from JS code.
2. 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
to create many instances of the same element. In `.slint` the for loop is declarative and automatically updates when
the model changes. We instantiate all the different <span class="hljs-title">MemoryTile</span> elements and place them on a grid based on their
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 `memory.slint` file:

```slint
{{#include ../../rust/src/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 `memory.slint` file with the following snippet:

```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. We use the `i` index to calculate the position of tile based on its row and column,
using the 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.

<video autoplay loop muted playsinline src="https://slint-ui.com/blog/memory-game-tutorial/from-one-to-multiple-tiles.mp4"></video>
38 changes: 38 additions & 0 deletions docs/tutorial/node/src/game_logic_in_js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Game Logic In JavaScript

We'll implement the rules of the game in JavaScript as well. The general philosophy of Slint is that merely the user
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 in the `memory.slint` file to signal to the JavaScript code when the user clicks on a tile.
Two changes to <span class="hljs-title">MainWindow</span> are needed: We need to add a way for the MainWindow to call to the JavaScript code that it should
check if a pair of tiles has been solved. And we need to add a property that JavaScript 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 callback and property declarations into <span class="hljs-title">MainWindow</span>:

```slint
{{#include ../../rust/src/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.
We add the following handler in <span class="hljs-title">MainWindow</span>:

```slint
{{#include ../../rust/src/main_game_logic_in_rust.rs:tile_click_logic}}
```

On the JavaScript side, we can now add an handler to the `check_if_pair_solved` callback, that will check if
two tiles are opened. If they match, the `solved` property is set 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
one 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 result gives us a window on the screen that allows us
to play the game by the rules.
46 changes: 46 additions & 0 deletions docs/tutorial/node/src/getting_started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Getting Started

In this tutorial, we use JavaScript as the host programming language. We also support other programming languages like
[Rust](https://slint-ui.com/docs/rust/slint/) or [C++](https://slint-ui.com/docs/cpp/).

You'll need a development environment with [Node.js 16](https://nodejs.org/download/release/v16.19.1/) and [npm](https://www.npmjs.com/) installed. More recent
versions of NodeJS are currently not supported, for details check [Issue #2220](https://github.com/slint-ui/slint/issues/2220).
Since Slint is implemented in the Rust programming language, you also need to install a Rust compiler (1.66 or newer). You can easily install a Rust compiler
following the instruction from [the Rust website](https://www.rust-lang.org/learn/get-started).

We're going to use `slint-ui` as `npm` dependency.

In a new directory, we create a new `package.json` file.

```json
{{#include package.json}}
```

This should look familiar to people familiar with NodeJS. We see that this package.json
references a `main.js`, which we will add later. We must then create, in the same directory,
the `memory.slint` file. Let's just fill it with a hello world for now:

```slint
{{#include memory.slint:main_window}}
```

What's still missing is the `main.js`:

```js
{{#include main_initial.js:main}}
```

To recap, we now have a directory with a `package.json`, `memory.slint`, and `main.js`.

We can now compile and run the program:

```sh
npm install
npm start
```

and a window will appear with the green "Hello World" greeting.

![Screenshot of initial tutorial app showing Hello World](https://slint-ui.com/blog/memory-game-tutorial/getting-started.png "Hello World")

Feel free to use your favorite IDE for this purpose.
1 change: 1 addition & 0 deletions docs/tutorial/node/src/icons
14 changes: 14 additions & 0 deletions docs/tutorial/node/src/ideas_for_the_reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 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 tiles could have rounded corners, to look a little less sharp. The [border-radius](https://slint-ui.com/docs/slint/src/builtins/elements.html#rectangle)
property of _Rectangle_ can be used 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-ui.com/docs/slint/src/builtins/elements.html#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.
14 changes: 14 additions & 0 deletions docs/tutorial/node/src/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 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 JavaScript.

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
same icon. The objective is to locate all icon pairs. You can uncover two tiles at the same time. If they
aren't the same, the icons will be obscured again.
If you uncover two tiles with the same icon, then they remain visible - they're solved.

This is how the game looks like in action:

<video autoplay loop muted playsinline src="https://slint-ui.com/blog/memory-game-tutorial/memory_clip.mp4"
class="img-fluid img-thumbnail rounded"></video>
66 changes: 66 additions & 0 deletions docs/tutorial/node/src/main_game_logic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial


// main.js
let slint = require("slint-ui");
let ui = require("./memory.slint");
let mainWindow = new ui.MainWindow();

let initial_tiles = mainWindow.memory_tiles;
let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile)));

for (let i = tiles.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i);
[tiles[i], tiles[j]] = [tiles[j], tiles[i]];
}

// ANCHOR: game_logic
let model = new slint.ArrayModel(tiles);
mainWindow.memory_tiles = model;

mainWindow.check_if_pair_solved.setHandler(function () {
let flipped_tiles = [];
tiles.forEach((tile, index) => {
if (tile.image_visible && !tile.solved) {
flipped_tiles.push({
index,
tile
});
}
});

if (flipped_tiles.length == 2) {
let {
tile: tile1,
index: tile1_index
} = flipped_tiles[0];

let {
tile: tile2,
index: tile2_index
} = flipped_tiles[1];

let is_pair_solved = tile1.image === tile2.image;
if (is_pair_solved) {
tile1.solved = true;
model.setRowData(tile1_index, tile1);
tile2.solved = true;
model.setRowData(tile2_index, tile2);
} else {
mainWindow.disable_tiles = true;
slint.Timer.singleShot(1000, () => {
mainWindow.disable_tiles = false;
tile1.image_visible = false;
model.setRowData(tile1_index, tile1);
tile2.image_visible = false;
model.setRowData(tile2_index, tile2);
})

}
}
});

mainWindow.run();

// ANCHOR_END: game_logic
11 changes: 11 additions & 0 deletions docs/tutorial/node/src/main_initial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial

// ANCHOR: main
// main.js
require("slint-ui");
let ui = require("./memory.slint");
let mainWindow = new ui.MainWindow();
mainWindow.run();

// ANCHOR_END: main
23 changes: 23 additions & 0 deletions docs/tutorial/node/src/main_tiles_from_js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial

// ANCHOR: main
// main.js
let slint = require("slint-ui");
let ui = require("./memory.slint");
let mainWindow = new ui.MainWindow();

let initial_tiles = mainWindow.memory_tiles;
let tiles = initial_tiles.concat(initial_tiles.map((tile) => Object.assign({}, tile)));

for (let i = tiles.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i);
[tiles[i], tiles[j]] = [tiles[j], tiles[i]];
}

let model = new slint.ArrayModel(tiles);
mainWindow.memory_tiles = model;

mainWindow.run();

// ANCHOR_END: main
12 changes: 12 additions & 0 deletions docs/tutorial/node/src/memory.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial

// ANCHOR: main_window
// memory.slint
export component MainWindow inherits Window {
Text {
text: "hello world";
color: green;
}
}
// ANCHOR_END: main_window
Loading

0 comments on commit c6472f9

Please sign in to comment.