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 <relative_path/to_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 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
:width: 180
:height: 160
```
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 thedoc/_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 bypage
.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 thepage
being passed.show: contains a subset of modes:
widget
,code
,infobox
, andpopup
. Thewidget
mode creates an iframe with the embedded example, directly within the page. Thecode
mode will show a button that allows the user to see the code that produced this example. Thepopup
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, theinfobox
mode will display the result in a floating window – this mode is best combined withwidget
andcode
.width: an integer that defines the width of the embedded application. If this is not defined, the width will be 100%.
height: an integer that defines the height of the embedded application. If this is not defined, the height will be 350px.
1import 'dart:math';
2
3import 'package:flame/components.dart';
4import 'package:flame/events.dart';
5import 'package:flame/game.dart';
6import 'package:flutter/rendering.dart';
7
8class TapEventsGame extends FlameGame {
9 @override
10 Future<void> onLoad() async {
11 add(TapTarget());
12 }
13}
14
15/// This component is the tappable blue-ish rectangle in the center of the game.
16/// It uses the [TapCallbacks] mixin to receive tap events.
17class TapTarget extends PositionComponent with TapCallbacks {
18 TapTarget() : super(anchor: Anchor.center);
19
20 final _paint = Paint()..color = const Color(0x448BA8FF);
21
22 /// We will store all current circles into this map, keyed by the `pointerId`
23 /// of the event that created the circle.
24 final Map<int, ExpandingCircle> _circles = {};
25
26 @override
27 void onGameResize(Vector2 size) {
28 super.onGameResize(size);
29 this.size = size - Vector2(100, 75);
30 if (this.size.x < 100 || this.size.y < 100) {
31 this.size = size * 0.9;
32 }
33 position = size / 2;
34 }
35
36 @override
37 void render(Canvas canvas) {
38 canvas.drawRect(size.toRect(), _paint);
39 }
40
41 @override
42 void onTapDown(TapDownEvent event) {
43 final circle = ExpandingCircle(event.localPosition);
44 _circles[event.pointerId] = circle;
45 add(circle);
46 }
47
48 @override
49 void onLongTapDown(TapDownEvent event) {
50 _circles[event.pointerId]!.accent();
51 }
52
53 @override
54 void onTapUp(TapUpEvent event) {
55 _circles.remove(event.pointerId)!.release();
56 }
57
58 @override
59 void onTapCancel(TapCancelEvent event) {
60 _circles.remove(event.pointerId)!.cancel();
61 }
62}
63
64class ExpandingCircle extends Component {
65 ExpandingCircle(this._center)
66 : _baseColor =
67 HSLColor.fromAHSL(1, random.nextDouble() * 360, 1, 0.8).toColor();
68
69 final Color _baseColor;
70 final Vector2 _center;
71 double _outerRadius = 0;
72 double _innerRadius = 0;
73 bool _released = false;
74 bool _cancelled = false;
75 late final _paint = Paint()
76 ..style = PaintingStyle.stroke
77 ..color = _baseColor;
78
79 /// "Accent" is thin white circle generated by `onLongTapDown`. We use
80 /// negative radius to indicate that the circle should not be drawn yet.
81 double _accentRadius = -1e10;
82 late final _accentPaint = Paint()
83 ..style = PaintingStyle.stroke
84 ..strokeWidth = 0
85 ..color = const Color(0xFFFFFFFF);
86
87 /// At this radius the circle will disappear.
88 static const maxRadius = 175;
89 static final random = Random();
90
91 double get radius => (_innerRadius + _outerRadius) / 2;
92
93 void release() => _released = true;
94 void cancel() => _cancelled = true;
95 void accent() => _accentRadius = 0;
96
97 @override
98 void render(Canvas canvas) {
99 canvas.drawCircle(_center.toOffset(), radius, _paint);
100 if (_accentRadius >= 0) {
101 canvas.drawCircle(_center.toOffset(), _accentRadius, _accentPaint);
102 }
103 }
104
105 @override
106 void update(double dt) {
107 if (_cancelled) {
108 _innerRadius += dt * 100; // implosion
109 } else {
110 _outerRadius += dt * 20;
111 _innerRadius += dt * (_released ? 20 : 6);
112 _accentRadius += dt * 20;
113 }
114 if (radius >= maxRadius || _innerRadius > _outerRadius) {
115 removeFromParent();
116 } else {
117 final opacity = 1 - radius / maxRadius;
118 _paint.color = _baseColor.withValues(alpha: opacity);
119 _paint.strokeWidth = _outerRadius - _innerRadius;
120 }
121 }
122}
Standardization and Templates¶
For every section or package added to the documentation, naming conventions, directory structure, and standardized table of contents are important. Every section and package must have a table of contents or an entry in the parent markdown file to allow navigation from the left sidebar menu in logical or alphabetical order. Additionally, naming conventions should be followed for organization, such as:
bridge_packages/package_name/package_name.md
documentation_section/documentation_section.md
Note
Avoid having spaces in the paths to the docs since that will keep you from building the project due to this bug.
Building documentation locally¶
Building the documentation site on your own computer is fairly simple. All you need is the following:
A working Flutter installation, accessible from the command line.
Melos command-line tool, as per the contributing guide.
A Python environment, with python version 3.8+ or higher. Having a dedicated python virtual environment is recommended but not required.
Install the remaining requirements using the command
melos run doc-setup
Once these prerequisites are met, you can build the documentation by using the built-in Melos target:
melos doc-build
The melos doc-build 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.
If you want to automatically recompile the docs every time there is a change to one of the files you can use the built-in Melos target below, which will also serve and open your default browser with the docs.
melos doc-serve
When using the melos doc-serve command, the melos doc-build is only needed when
there are changes to the sphinx theme. This is because the serve command both automatically
compiles the docs on changes and also hosts them locally. The docs are served at
http://localhost:8000/
by default.
There are other make commands that you may find occasionally useful too:
melos doc-clean removes all cached generated files (in case the system gets stuck in a bad state).
melos doc-linkcheck to check whether there are any broken links in the documentation.
melos doc-kill removes any orphaned TCP threads running on port 8000.
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 melos doc-serve.
If you ever run the melos doc-clean command, the server will need to be restarted, because the
clean command deletes the entire html
directory.
Note
Avoid having spaces in the paths to the docs since that will keep you from building the project due to this bug.