Documentation Site

Flame’s documentation is written in Markdown. It is then rendered into HTML with the help of the Sphinx engine and its MyST plugin. The rendered files are then manually (but with the help of a script) published to flame-docs-site, where the site is served via GitHub Pages.

Markdown

The main documentation site is written in Markdown. We assume that you’re already familiar with the basics of the Markdown syntax (if not, there are plenty of guides on the Internet). Instead, this section will focus on the Markdown extensions that are enabled in our build system.

Table of contents

The table of contents for the site must be created manually. This is done using special {toctree} blocks, one per each subdirectory:

```{toctree}
:hidden:

First Topic    <topic1.md>
Second Topic   <topic2.md>
```

When adding new documents into the documentation site, make sure that they are mentioned in one of the toctrees – otherwise you will see a warning during the build that the document is orphaned.

Admonitions

Admonitions are emphasized blocks of text with a distinct appearance. They are created using the triple-backticks syntax:

```{note}
Please note this very important caveat.
```
```{warning}
Don't look down, or you will encounter an error.
```
```{error}
I told you so.
```
```{seealso}
Also check out this cool thingy.
```

Note

Please note this very important caveat.

Warning

Don’t look down, or you will encounter an error.

Error

I told you so.

See also

Also check out this cool thingy.

Deprecations

The special {deprecated} block can be used to mark some part of documentation or syntax as being deprecated. This block requires specifying the version when the deprecation has occurred

```{deprecated} v1.3.0

Please use this **other** thing instead.
```

Which would be rendered like this:

Deprecated since version v1.3.0: Please use this other thing instead.

Live examples

Our documentation site includes a custom-built flutter-app directive which allows creating Flutter widgets and embedding them alongside with the overall documentation content.

In Markdown, the code for inserting an embed looks like this:

```{flutter-app}
:sources: ../flame/examples
:page: tap_events
:show: widget code popup
```

Here’s what the different options mean:

  • sources: specifies the name of the root directory where the Flutter code that you wish to run is located. This directory must be a Flutter repository, and there must be a pubspec.yaml file there. The path is considered relative to the doc/_sphinx directory.

  • page: a sub-path within the root directory given in sources. This option has two effects: first, it is appended to the path of the html page of the widget, like so: main.dart.html?$page. Secondly, the button to show the source code of the embed will display the code from the file or directory with the name given by page.

    The purpose of this option is to be able to bundle multiple examples into a single executable. When using this option, the main.dart file of the app should route the execution to the proper widget according to the page being passed.

  • show: contains a subset of modes: widget, code, infobox, and popup. The widget mode creates an iframe with the embedded example, directly within the page. The code mode will show a button that allows the user to see the code that produced this example. The popup mode also shows a button, which displays the example in an overlay window. This is more suitable for demoing larger apps. Using both “widget” and “popup” modes at the same time is not recommended. Finally, the infobox mode will display the result in a floating window – this mode is best combined with widget and code.

tap_events.dart
  1import 'dart:math';
  2
  3import 'package:flame/components.dart';
  4import 'package:flame/experimental.dart';
  5import 'package:flame/game.dart';
  6import 'package:flutter/rendering.dart';
  7
  8/// The main [FlameGame] class uses [HasTappableComponents] in order to enable
  9/// tap events propagation.
 10class TapEventsGame extends FlameGame with HasTappableComponents {
 11  @override
 12  Future<void> onLoad() async {
 13    add(TapTarget());
 14  }
 15}
 16
 17/// This component is the tappable blue-ish rectangle in the center of the
 18/// game. It uses the [TapCallbacks] mixin in order to inform the game that it
 19/// wants to receive tap events.
 20class TapTarget extends PositionComponent with TapCallbacks {
 21  TapTarget() : super(anchor: Anchor.center);
 22
 23  final _paint = Paint()..color = const Color(0x448BA8FF);
 24
 25  /// We will store all current circles into this map, keyed by the `pointerId`
 26  /// of the event that created the circle.
 27  final Map<int, ExpandingCircle> _circles = {};
 28
 29  @override
 30  void onGameResize(Vector2 canvasSize) {
 31    super.onGameResize(canvasSize);
 32    size = canvasSize - Vector2(100, 75);
 33    if (size.x < 100 || size.y < 100) {
 34      size = canvasSize * 0.9;
 35    }
 36    position = canvasSize / 2;
 37  }
 38
 39  @override
 40  void render(Canvas canvas) {
 41    canvas.drawRect(size.toRect(), _paint);
 42  }
 43
 44  @override
 45  void onTapDown(TapDownEvent event) {
 46    final circle = ExpandingCircle(event.localPosition);
 47    _circles[event.pointerId] = circle;
 48    add(circle);
 49  }
 50
 51  @override
 52  void onLongTapDown(TapDownEvent event) {
 53    _circles[event.pointerId]!.accent();
 54  }
 55
 56  @override
 57  void onTapUp(TapUpEvent event) {
 58    _circles.remove(event.pointerId)!.release();
 59  }
 60
 61  @override
 62  void onTapCancel(TapCancelEvent event) {
 63    _circles.remove(event.pointerId)!.cancel();
 64  }
 65}
 66
 67class ExpandingCircle extends Component {
 68  ExpandingCircle(this._center)
 69      : _baseColor =
 70            HSLColor.fromAHSL(1, random.nextDouble() * 360, 1, 0.8).toColor();
 71
 72  final Color _baseColor;
 73  final Vector2 _center;
 74  double _outerRadius = 0;
 75  double _innerRadius = 0;
 76  bool _released = false;
 77  bool _cancelled = false;
 78  late final _paint = Paint()
 79    ..style = PaintingStyle.stroke
 80    ..color = _baseColor;
 81
 82  /// "Accent" is thin white circle generated by `onLongTapDown`. We use
 83  /// negative radius to indicate that the circle should not be drawn yet.
 84  double _accentRadius = -1e10;
 85  late final _accentPaint = Paint()
 86    ..style = PaintingStyle.stroke
 87    ..strokeWidth = 0
 88    ..color = const Color(0xFFFFFFFF);
 89
 90  /// At this radius the circle will disappear.
 91  static const maxRadius = 175;
 92  static final random = Random();
 93
 94  double get radius => (_innerRadius + _outerRadius) / 2;
 95
 96  void release() => _released = true;
 97  void cancel() => _cancelled = true;
 98  void accent() => _accentRadius = 0;
 99
100  @override
101  void render(Canvas canvas) {
102    canvas.drawCircle(_center.toOffset(), radius, _paint);
103    if (_accentRadius >= 0) {
104      canvas.drawCircle(_center.toOffset(), _accentRadius, _accentPaint);
105    }
106  }
107
108  @override
109  void update(double dt) {
110    if (_cancelled) {
111      _innerRadius += dt * 100; // implosion
112    } else {
113      _outerRadius += dt * 20;
114      _innerRadius += dt * (_released ? 20 : 6);
115      _accentRadius += dt * 20;
116    }
117    if (radius >= maxRadius || _innerRadius > _outerRadius) {
118      removeFromParent();
119    } else {
120      final opacity = 1 - radius / maxRadius;
121      _paint.color = _baseColor.withOpacity(opacity);
122      _paint.strokeWidth = _outerRadius - _innerRadius;
123    }
124  }
125}

Building documentation locally

Building the documentation site on your own computer is fairly simple. All you need is the following:

  1. A working Flutter installation, accessible from the command line;

  2. A Python environment, with python version 3.6 or higher;

    • You can verify this by running python --version from the command line;

    • Having a dedicated python virtual environment is recommended but not required;

  3. A set of python modules listed in the doc/_sphinx/requirements.txt file;

    • The easiest way to install these is to run

      $ pip install -r doc/_sphinx/requirements.txt
      

Once these prerequisites are met, you can build the documentation by switching to the doc/_sphinx directory and running make html:

$ cd doc/_sphinx
$ make html

The make html command here renders the documentation site into HTML. This command needs to be re-run every time you make changes to any of the documents. Luckily, it is smart enough to only rebuild the documents that have changed since the previous run, so usually a rebuild takes only a second or two.

There are other make commands that you may find occasionally useful too: make clean removes all cached generated files (in case the system gets stuck in a bad state); and make linkcheck to check whether there are any broken links in the documentation.

The generated html files will be in the doc/_build/html directory, you can view them directly by opening the file doc/_build/html/index.html in your browser. The only drawback is that the browser won’t allow any dynamic content in a file opened from a local drive. The solution to this is to run your own local http server:

$ python -m http.server 8000 --directory doc/_build/html

Then you can open the site at http://localhost:8000/.

If you ever run the make clean command, the server will need to be restarted, because the clean command deletes the entire html directory.