slint 1.4.0

GUI toolkit to efficiently develop fluid graphical user interfaces for embedded devices and desktop applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>SixtyFPS Memory Game Tutorial (C++)</title>
                <meta name="robots" content="noindex" />
                

        <!-- Custom HTML head -->
        <!--
    This file is used to add syntax highlighting of the `.60` snippets in the generated rustdoc, sphinx and mdbook documentation.
    It can be injected via the `--html-in-header sixtyfps-docs-highlight.html` option of rustdoc, is included via _templates/layout.html
    in sphinx and via head.hbs in mdbook.
-->
<link rel="stylesheet" href="https://sixtyfps.io/resources/highlightjs/11.0.1/default.min.css">
<script src="https://sixtyfps.io/resources/highlightjs/11.0.1/highlight.min.js"></script>
<script>
  hljs.registerLanguage("60", function (hljs) {
    const KEYWORDS = {
      keyword:
        'struct export import signal property animate for in if states transitions parent root self',
      literal:
        'true false',
      built_in:
        'Rectangle Image Text TouchArea Flickable Clip TextInput Window GridLayout Row HorizontalLayout VerticalLayout Path MoveTo LineTo ArcTo CubicTo QuadraticTo Close FocusScope Clip PopupWindow',
      type:
        'bool string int float length logical_length duration resource',
    };

    return {
      name: 'sixtyfps',
      aliases: ['60'],
      case_insensitive: false,
      keywords: KEYWORDS,
      contains: [
        hljs.QUOTE_STRING_MODE,
        hljs.C_LINE_COMMENT_MODE,
        hljs.C_BLOCK_COMMENT_MODE,
        hljs.COMMENT('/\\*', '\\*/', {
          contains: ['self']
        }),
        {
          className: 'number',
          begin: '\\b\\d+(\\.\\d+)?(\\w+)?',
          relevance: 0
        },
        {
          className: 'title',
          begin: '\\b[_a-zA-Z][_\\-a-zA-Z0-9]* *:=',
        },
        {
          className: 'symbol',
          begin: '\\b[_a-zA-Z][_\\-a-zA-Z0-9]*(:| *=>)',
        },
        {
          className: 'built_in',
          begin: '\\b[_a-zA-Z][_\\-a-zA-Z0-9]*!',
        },
      ],
      illegal: /@/
    };
  });

  window.addEventListener("DOMContentLoaded", () => {
    const rustDoc = document.querySelector('meta[name="generator"]')?.content == "rustdoc";
    if (rustDoc) {
      // Only highlight .60 blocks, leave the others to rustdoc
      for (dot60Block of document.querySelectorAll("pre code.language-60")) {
        hljs.highlightElement(dot60Block)
      }

      // Some of the rustdoc selectors require the pre element to have the rust class
      for (codeBlock of document.querySelectorAll(".language-60.hljs")) {
        codeBlock.parentElement.classList.add("rust")
      }

      // Change the hljs generated classes to the rustdoc
      // ones, so that the highlighting adjusts to the theme correctly.
      const highlightJSToRustDoc = [
        ["comment", "comment"],
        ["number", "number"],
        ["symbol", "struct"], // width:
        ["keyword", "kw"],
        ["built_in", "primitive"],
        ["string", "string"],
        ["title", "fnname"], // Foo :=
        ["type", "type"]
      ];

      for ([hljs_class, rustdoc_class] of highlightJSToRustDoc) {
        for (titleElement of document.querySelectorAll(`.hljs-${hljs_class}`)) {
          titleElement.classList.remove(`hljs-${hljs_class}`);
          titleElement.classList.add(rustdoc_class);
        }
      }
    } else {
      // For use with the mdbook Tutorial
      hljs.highlightAll();

      // The Sphinx/my_st generated HTML for code blocks does not use <code> tags, so highlight.js'
      // default selector "pre code" does not match. Let's do it by hand:
      for (block of document.querySelectorAll("div.highlight-60 div.highlight pre, div.highlight-60-no-preview div.highlight pre")) {
        hljs.highlightElement(block)
      }
    }
  });
</script>

        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

                <link rel="icon" href="favicon.svg">
                        <link rel="shortcut icon" href="favicon.png">
                <link rel="stylesheet" href="css/variables.css">
        <link rel="stylesheet" href="css/general.css">
        <link rel="stylesheet" href="css/chrome.css">
                <link rel="stylesheet" href="css/print.css" media="print">
        
        <!-- Fonts -->
        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
                <link rel="stylesheet" href="fonts/fonts.css">
        
        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="highlight.css">
        <link rel="stylesheet" href="tomorrow-night.css">
        <link rel="stylesheet" href="ayu-highlight.css">

        <!-- Custom theme stylesheets -->
        
            </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="getting_started.html"><strong aria-hidden="true">2.</strong> Getting Started</a></li><li class="chapter-item expanded "><a href="memory_tile.html"><strong aria-hidden="true">3.</strong> Memory Tile</a></li><li class="chapter-item expanded "><a href="polishing_the_tile.html"><strong aria-hidden="true">4.</strong> Polishing the Tile</a></li><li class="chapter-item expanded "><a href="from_one_to_multiple_tiles.html"><strong aria-hidden="true">5.</strong> From One To Multiple Tiles</a></li><li class="chapter-item expanded "><a href="creating_the_tiles_from_cpp.html"><strong aria-hidden="true">6.</strong> Creating The Tiles From C++</a></li><li class="chapter-item expanded "><a href="game_logic_in_cpp.html"><strong aria-hidden="true">7.</strong> Game Logic In C++</a></li><li class="chapter-item expanded "><a href="ideas_for_the_reader.html"><strong aria-hidden="true">8.</strong> Ideas For The Reader</a></li><li class="chapter-item expanded "><a href="conclusion.html"><strong aria-hidden="true">9.</strong> Conclusion</a></li></ol>            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                
                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                                                <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                                            </div>

                    <h1 class="menu-title">SixtyFPS Memory Game Tutorial (C++)</h1>

                    <div class="right-buttons">
                                                <a href="print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>
                                                                        
                    </div>
                </div>

                                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>
                
                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
<p>This tutorial will introduce you to the SixtyFPS UI framework in a playful way by implementing a little memory game. We are going to combine the <code>.60</code> language for the graphics with the game rules implemented in C++.</p>
<p>The game consists of a grid of 16 rectangular tiles. When clicking on a tile, an icon underneath is uncovered.
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. Only two tiles can be uncovered at the same time. If they
are not the same, then the icons will be obscured again. We need to remember under which tiles the matching
graphics are hiding. If two tiles with the same icon are uncovered, then they remain visible - they are solved.</p>
<p>This is how the game looks like in action:</p>
<p><video autoplay loop muted playsinline src="https://sixtyfps.io/blog/memory-game-tutorial/memory_clip.mp4"
        class="img-fluid img-thumbnail rounded"></video></p>
<p>A video-recording of this tutorial is also available on YouTube. After introducing the <code>.60</code> language the video
continues with a Rust implementation, but around minute 42 the C++ begins:</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/_-Hxr6ZrHyo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><div style="break-before: page; page-break-before: always;"></div><h1 id="getting-started"><a class="header" href="#getting-started">Getting Started</a></h1>
<p>In this tutorial, we use C++ as the host programming language. We also support other programming languages like
<a href="https://sixtyfps.io/docs/rust/sixtyfps/">Rust</a> or <a href="https://sixtyfps.io/docs/node/">JavaScript</a>.</p>
<p>You will need a development environment that can compile C++17 with CMake 3.16.
We do not provide binaries of SixtyFPS yet, so we will use the CMake integration that will automatically build
the tools and library from source. Since it is implemented in the Rust programming language, this means that
you also need to install a Rust compiler (1.48). You can easily install a Rust compiler
following the instruction from <a href="https://www.rust-lang.org/learn/get-started">the Rust website</a>.
We are going to use <code>cmake</code>'s builtin FetchContent module to fetch the source code of SixtyFPS.</p>
<p>In a new directory, we create a new <code>CMakeLists.txt</code> file.</p>
<pre><code class="language-cmake"># CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(memory LANGUAGES CXX)

include(FetchContent)
FetchContent_Declare(
    SixtyFPS
    GIT_REPOSITORY https://github.com/sixtyfpsui/sixtyfps.git
    GIT_TAG v0.0.6
    SOURCE_SUBDIR api/sixtyfps-cpp
)
FetchContent_MakeAvailable(SixtyFPS)

add_executable(memory_game main.cpp)
target_link_libraries(memory_game PRIVATE SixtyFPS::SixtyFPS)
sixtyfps_target_60_sources(memory_game memory.60)
</code></pre>
<p>This should look familiar to people familiar with CMake. We see that this CMakeLists.txt
references a <code>main.cpp</code>, which we will add later, and it also has a line
<code>sixtyfps_target_60_sources(memory_game memory.60)</code>, which is a SixtyFPS function used to
add the <code>memory.60</code> file to the target. We must then create, in the same directory,
the <code>memory.60</code> file. Let's just fill it with a hello world for now:</p>
<pre><code class="language-60">// memory.60
MainWindow := Window {
    Text {
        text: &quot;hello world&quot;;
        color: green;
    }
}
</code></pre>
<p>What's still missing is the <code>main.cpp</code>:</p>
<pre><code class="language-cpp">// main.cpp

#include &quot;memory.h&quot; // generated header from memory.60

int main()
{
    auto main_window = MainWindow::create();
    main_window-&gt;run();
}
</code></pre>
<p>To recap, we now have a directory with a <code>CMakeLists.txt</code>, <code>memory.60</code> and <code>main.cpp</code>.</p>
<p>We can now compile and run this program:</p>
<pre><code class="language-sh">cmake -GNinja .
cmake --build .
./memory_game
</code></pre>
<p>and a window will appear with the green &quot;Hello World&quot; greeting.</p>
<p><img src="https://sixtyfps.io/blog/memory-game-tutorial/getting-started.png" alt="Screenshot of initial tutorial app showing Hello World" title="Hello World" /></p>
<p>Feel free to use your favorite IDE for this purpose, or use out-of-tree build, or Ninja, ...
We just keep it simple here for the purpose of this blog.</p>
<p><em>Note</em>: When configuring with CMake, the FetchContent module will fetch the source code of SixtyFPS via git.
this may take some time. When building for the first time, the first thing that need to be build
is the SixtyFPS runtime and compiler, this can take a few minutes.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="memory-tile"><a class="header" href="#memory-tile">Memory Tile</a></h1>
<p>With the skeleton in place, let's look at the first element of the game, the memory tile. It will be the
visual building block that consists of an underlying filled rectangle background, the icon image. Later we'll add a
covering rectangle that acts as a curtain. The background rectangle is declared to be 64 logical pixels wide and tall,
and it is filled with a soothing tone of blue. Note how lengths in the <code>.60</code> language have a unit, here
the <code>px</code> 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.</p>
<p>We copy the following code into the <code>memory.60</code> file:</p>
<pre><code class="language-60">MemoryTile := Rectangle {
    width: 64px;
    height: 64px;
    background: #3960D5;

    Image {
        source: @image-url(&quot;icons/bus.png&quot;);
        width: parent.width;
        height: parent.height;
    }
}

MainWindow := Window {
    MemoryTile {}
}
</code></pre>
<p>Inside the <span class="hljs-built_in">Rectangle</span> we 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 folder in which
the <code>memory.60</code> is located. This icon and others we're going to use later need to be installed first. You can download a
<a href="https://sixtyfps.io/blog/memory-game-tutorial/icons.zip">Zip archive</a> that we have prepared and extract it with the
following two commands:</p>
<pre><code class="language-sh">curl -O https://sixtyfps.io/blog/memory-game-tutorial/icons.zip
unzip icons.zip
</code></pre>
<p>This should unpack an <code>icons</code> directory containing a bunch of icons.</p>
<p>We compile the program with <code>cmake --build .</code> and running with the <code>./memory_game</code> gives us a
window on the screen that shows the icon of a bus on a blue background.</p>
<p><img src="https://sixtyfps.io/blog/memory-game-tutorial/memory-tile.png" alt="Screenshot of the first tile" title="Memory Tile Screenshot" /></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="polishing-the-tile"><a class="header" href="#polishing-the-tile">Polishing the Tile</a></h1>
<p>Next, let's add a curtain like cover that opens up when clicking. We achieve 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.
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>
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:</p>
<table><thead><tr><th><em>open_curtain</em> value:</th><th>false</th><th>true</th></tr></thead><tbody>
<tr><td>Left curtain rectangle</td><td>Fill the left half by setting the width <em>width</em> to half the parent's width</td><td>Width of zero makes the rectangle invisible</td></tr>
<tr><td>Right curtain rectangle</td><td>Fill the right half by setting <em>x</em> and <em>width</em> to half of the parent's width</td><td><em>width</em> of zero makes the rectangle invisible. <em>x</em> is moved to the right, to slide the curtain open when animated</td></tr>
</tbody></table>
<p>In order to make our tile extensible, the hard-coded icon name is replaced with an <em>icon</em>
property that can be set from the outside when instantiating the element. For the final polish, we add a
<em>solved</em> property that we use to animate the color to a shade of green when we've found a pair, later. We
replace the code inside the <code>memory.60</code> file with the following:</p>
<pre><code class="language-60">MemoryTile := Rectangle {
    callback clicked;
    property &lt;bool&gt; open_curtain;
    property &lt;bool&gt; solved;
    property &lt;image&gt; icon;

    height: 64px;
    width: 64px;
    background: solved ? #34CE57 : #3960D5;
    animate background { duration: 800ms; }

    Image {
        source: icon;
        width: parent.width;
        height: parent.height;
    }

    // Left curtain
    Rectangle {
        background: #193076;
        width: open_curtain ? 0px : (parent.width / 2);
        height: parent.height;
        animate width { duration: 250ms; easing: ease-in; }
    }

    // Right curtain
    Rectangle {
        background: #193076;
        x: open_curtain ? parent.width : (parent.width / 2);
        width: open_curtain ? 0px : (parent.width / 2);
        height: parent.height;
        animate width { duration: 250ms; easing: ease-in; }
        animate x { duration: 250ms; easing: ease-in; }
    }

    TouchArea {
        clicked =&gt; {
            // Delegate to the user of this element
            root.clicked();
        }
    }
}
MainWindow := Window {
    MemoryTile {
        icon: @image-url(&quot;icons/bus.png&quot;);
        clicked =&gt; {
            self.open_curtain = !self.open_curtain;
        }
    }
}
</code></pre>
<p>Note the use of <code>root</code> and <code>self</code> in the code. <code>root</code> refers to the outermost
element in the component, that's the <span class="hljs-title">MemoryTile</span> in this case. <code>self</code> refers
to the current element.</p>
<p>Running this gives us a window on the screen with a rectangle that opens up to show us the bus icon, when clicking on
it. Subsequent clicks will close and open the curtain again.</p>
<p><video autoplay loop muted playsinline src="https://sixtyfps.io/blog/memory-game-tutorial/polishing-the-tile.mp4"></video></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="from-one-to-multiple-tiles"><a class="header" href="#from-one-to-multiple-tiles">From One To Multiple Tiles</a></h1>
<p>After modeling a single tile, let's create a grid of them. For the grid to be our game board, we need two features:</p>
<ol>
<li>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 Rust code.</li>
<li>A way of creating many instances of the tiles, with the above <code>.60</code> markup code.</li>
</ol>
<p>In SixtyFPS 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 <code>.60</code> 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.</p>
<p>First, we copy the tile data structure definition and paste it at top inside the <code>memory.60</code> file:</p>
<pre><code class="language-60">
// Added:
struct TileData := {
    image: image,
    image_visible: bool,
    solved: bool,
}

MemoryTile := Rectangle {

</code></pre>
<p>Next, we replace the <em><span class="hljs-title">MainWindow</span> := { ... }</em> section at the bottom of the <code>memory.60</code> file with the following snippet:</p>
<pre><code class="language-60">MainWindow := Window {
    width: 326px;
    height: 326px;

    property &lt;[TileData]&gt; memory_tiles: [
        { image: @image-url(&quot;icons/at.png&quot;) },
        { image: @image-url(&quot;icons/balance-scale.png&quot;) },
        { image: @image-url(&quot;icons/bicycle.png&quot;) },
        { image: @image-url(&quot;icons/bus.png&quot;) },
        { image: @image-url(&quot;icons/cloud.png&quot;) },
        { image: @image-url(&quot;icons/cogs.png&quot;) },
        { image: @image-url(&quot;icons/motorcycle.png&quot;) },
        { image: @image-url(&quot;icons/video.png&quot;) },
    ];
    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 =&gt; {
            tile.image_visible = !tile.image_visible;
        }
    }
}
</code></pre>
<p>The <code><span class="hljs-keyword">for</span> tile[i] <span class="hljs-keyword">in</span> memory_tiles:</code> syntax declares a variable <code>tile</code> which contains the data of one element from the <code>memory_tiles</code> array,
and a variable <code>i</code> which is the index of the tile. We use the <code>i</code> 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.</p>
<p>Running this gives us a window that shows 8 tiles, which can be opened individually.</p>
<p><video autoplay loop muted playsinline src="https://sixtyfps.io/blog/memory-game-tutorial/from-one-to-multiple-tiles.mp4"></video></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="creating-the-tiles-from-c"><a class="header" href="#creating-the-tiles-from-c">Creating The Tiles From C++</a></h1>
<p>What we'll do is take the list of tiles declared in the .60 language, duplicate it, and shuffle it.
We'll do so by accessing the <code>memory_tiles</code> property through the Rust code. For each top-level property,
a getter and a setter function is generated - in our case <code>get_memory_tiles</code> and <code>set_memory_tiles</code>.
Since <code>memory_tiles</code> is an array in the <code>.60</code> language, it is represented as a <a href="https://sixtyfps.io/docs/cpp/api/classsixtyfps_1_1model"><code>std::shared_ptr&lt;sixtyfps::Model&gt;</code></a>.
We can't modify the model generated by the .60, but we can extract the tiles from it, and put it
in a <a href="https://sixtyfps.io/docs/cpp/api/classsixtyfps_1_1vectormodel"><code>sixtyfps::VectorModel</code></a> which inherits from <code>Model</code>.
<code>VectorModel</code> allows us to make modifications and we can use it to replace the static generated model.</p>
<p>We modify the main function like so:</p>
<pre><code class="language-cpp">// ...

#include &lt;random&gt; // Added

int main()
{
    auto main_window = MainWindow::create();
    auto old_tiles = main_window-&gt;get_memory_tiles();
    std::vector&lt;TileData&gt; new_tiles;
    new_tiles.reserve(old_tiles-&gt;row_count() * 2);
    for (int i = 0; i &lt; old_tiles-&gt;row_count(); ++i) {
        new_tiles.push_back(old_tiles-&gt;row_data(i));
        new_tiles.push_back(old_tiles-&gt;row_data(i));
    }
    std::default_random_engine rng {};
    std::shuffle(new_tiles.begin(), new_tiles.end(), rng);
    auto tiles_model = std::make_shared&lt;sixtyfps::VectorModel&lt;TileData&gt;&gt;(new_tiles);
    main_window-&gt;set_memory_tiles(tiles_model);

    main_window-&gt;run();
}
</code></pre>
<p>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.</p>
<p><video autoplay loop muted playsinline src="https://sixtyfps.io/blog/memory-game-tutorial/creating-the-tiles-from-rust.mp4"></video></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="game-logic-in-c"><a class="header" href="#game-logic-in-c">Game Logic In C++</a></h1>
<p>We'll implement the rules of the game in C++ as well. The general philosophy of SixtyFPS is that merely the user
interface is implemented in the <code>.60</code> 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.</p>
<p>We'll modify the <code>.60</code> markup in the <code>memory.60</code> file to signal to the C++ 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 C++ code that it should
check if a pair of tiles has been solved. And we need to add a property that C++ 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>:</p>
<pre><code class="language-60">    MainWindow := Window {
        width: 326px;
        height: 326px;

        callback check_if_pair_solved(); // Added
        property &lt;bool&gt; disable_tiles; // Added

        property &lt;[TileData]&gt; memory_tiles: [
           { image: @image-url(&quot;icons/at.png&quot;) },
</code></pre>
<p>The last change to the <code>.60</code> 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>:</p>
<pre><code class="language-60">        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 =&gt; {
                // old: tile.image_visible = !tile.image_visible;
                // new:
                if (!root.disable_tiles) {
                    tile.image_visible = !tile.image_visible;
                    root.check_if_pair_solved();
                }
            }
        }
</code></pre>
<p>On the C++ side, we can now add an handler to the <code>check_if_pair_solved</code> callback, that will check if
two tiles are opened. If they match, the <code>solved</code> 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 cannot click anything during this time.</p>
<p>Insert this code before the <code>main_window-&gt;run()</code> call:</p>
<pre><code class="language-cpp">
    auto tiles_model = std::make_shared&lt;sixtyfps::VectorModel&lt;TileData&gt;&gt;(new_tiles);
    main_window-&gt;set_memory_tiles(tiles_model);

    main_window-&gt;on_check_if_pair_solved(
            [main_window_weak = sixtyfps::ComponentWeakHandle(main_window)] {
                auto main_window = *main_window_weak.lock();
                auto tiles_model = main_window-&gt;get_memory_tiles();
                int first_visible_index = -1;
                TileData first_visible_tile;
                for (int i = 0; i &lt; tiles_model-&gt;row_count(); ++i) {
                    auto tile = tiles_model-&gt;row_data(i);
                    if (!tile.image_visible || tile.solved)
                        continue;
                    if (first_visible_index == -1) {
                        first_visible_index = i;
                        first_visible_tile = tile;
                        continue;
                    }
                    bool is_pair_solved = tile == first_visible_tile;
                    if (is_pair_solved) {
                        first_visible_tile.solved = true;
                        tiles_model-&gt;set_row_data(first_visible_index,
                                                  first_visible_tile);
                        tile.solved = true;
                        tiles_model-&gt;set_row_data(i, tile);
                        return;
                    }
                    main_window-&gt;set_disable_tiles(true);

                    sixtyfps::Timer::single_shot(std::chrono::seconds(1),
                        [=]() mutable {
                            main_window-&gt;set_disable_tiles(false);
                            first_visible_tile.image_visible = false;
                            tiles_model-&gt;set_row_data(first_visible_index,
                                                      first_visible_tile);
                            tile.image_visible = false;
                            tiles_model-&gt;set_row_data(i, tile);
                        });
                }
            });

    main_window-&gt;run();
}
</code></pre>
<p>Notice that we take a weak pointer of our <code>main_window</code>. This is very
important because capturing a copy of the <code>main_window</code> itself within the callback handler would result in a circular ownership.
The <code>MainWindow</code> owns the callback handler, which itself owns a reference to the <code>MainWindow</code>, which must be weak
instead of strong to avoid a memory leak.</p>
<p>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.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="ideas-for-the-reader"><a class="header" href="#ideas-for-the-reader">Ideas For The Reader</a></h1>
<p>The game is visually a little bare. Here are some ideas how you could make further changes to enhance it:</p>
<ul>
<li>
<p>The tiles could have rounded corners, to look a little less sharp. The <a href="https://sixtyfps.io/docs/rust/sixtyfps/docs/builtin_elements/index.html#rectangle">border-radius</a>
property of <em>Rectangle</em> can be used to achieve that.</p>
</li>
<li>
<p>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 <em><a href="https://sixtyfps.io/docs/rust/sixtyfps/docs/builtin_elements/index.html#image">Image</a></em>
element. Note that you may have to use <em>Rectangle</em>'s <em><a href="https://sixtyfps.io/docs/rust/sixtyfps/docs/builtin_elements/index.html#properties-1">clip</a> property</em>
element around it to ensure that the image is clipped away when the curtain effect opens.</p>
</li>
</ul>
<p>Let us know in the comments on Github Discussions how you polished your code, or feel free to ask questions about
how to implement something.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="conclusion"><a class="header" href="#conclusion">Conclusion</a></h1>
<p>In this tutorial, we have demonstrated how to combine some built-in SixtyFPS elements with C++ code to build a little
game. There are many more features that we have not talked about, such as layouts, widgets, or styling. Have a look
at the <a href="https://github.com/sixtyfpsui/sixtyfps/tree/master/examples">examples</a> in the SixtyFPS repo to
see how these look like and can be used, such as the <a href="https://github.com/sixtyfpsui/sixtyfps/tree/master/examples/todo">todo example</a>.</p>
<p>A slightly more polished version of this memory puzzle game is <a href="https://github.com/sixtyfpsui/sixtyfps/tree/master/examples/memory">available in the SixtyFPS repository</a>. And you can <a href="https://sixtyfps.io/demos/memory/" target="_blank">play the wasm version</a> in your browser.</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                        
                        
                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                
                            </nav>

        </div>

        
        
        
                <script type="text/javascript">
            window.playground_copyable = true;
        </script>
        
        
                <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="searcher.js" type="text/javascript" charset="utf-8"></script>
        
        <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->
        
                        <script type="text/javascript">
        window.addEventListener('load', function() {
            window.setTimeout(window.print, 100);
        });
        </script>
                
    </body>
</html>