ASCII-Render Tree for Layouting in Dart
Dart Implementation of a ASCII-Render Tree
The code below demonstrates a render tree using an ASCII-Widget.
An ASCII-Widget has other ASCII-Widget children, forming a tree.
Rendering the root ACII-Widget returns a String. Which can be printed out on a console; or used as a source for rendering sprites in other UIs.
This apporach uses three classes:
1. MatrixCanvas
: A class being a two-dimensional map holding `MatrixCanvasTile`.
2. MatrixCanvasTile
: A class holding pure ASCII (and other info like colors).
2. MatrixWidget
: An abstract class extending MatrixCanvas. It renders itself and its children.
Extend MatrixWidget
to create custom Widgets.
Example Result
A typical layout with camera on the left, a top line, a bottom line and an inventory on the right.
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTIIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
IIIIIIIIII
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBXXXXXXXXXX
Usage
The code below generates the example depicted above. It creates custom Widgets that extend MatrixWidget
.
import 'democanvas.dart';
main() {
final int totalWidth = 40;
final int totalHeight = 20;
final int leftWidth = 30;
final int leftHeight = totalHeight;
final int inventoryOffset = leftWidth;
final int inventoryWidth = 10;
// --------------------------------------------------------------------------
// LEFT SIDE: TOP LINE
// --------------------------------------------------------------------------
final topLineWidget = TopLineWidget(0, 0, leftWidth, 1);
// --------------------------------------------------------------------------
// LEFT SIDE: BOTTOM LINE
// --------------------------------------------------------------------------
final bottomLineWidget = BottomLineWidget(0, totalHeight - 1, leftWidth, 1);
// --------------------------------------------------------------------------
// LEFT SIDE: ALL
// --------------------------------------------------------------------------
final leftWidget = LeftWidget(0, 0, leftWidth, leftHeight,
children: [topLineWidget, bottomLineWidget]);
// --------------------------------------------------------------------------
// RIGHT SIDE INVENTORY
// --------------------------------------------------------------------------
final inventoryBelowWidget = InventoryBelowWidget(0, 15, 10, 5);
final inventoryWidget = InventoryWidget(
inventoryOffset, 0, inventoryWidth, totalHeight,
children: [inventoryBelowWidget]);
// --------------------------------------------------------------------------
// SCREEN WIDGET
// --------------------------------------------------------------------------
final screenWidget = LeftWidget(0, 0, totalWidth, totalHeight,
children: [leftWidget, inventoryWidget]);
// --------------------------------------------------------------------------
// PAINT ALL
// --------------------------------------------------------------------------
print(screenWidget.renderWidgetTree());
}
// ============================================================================
// CUSTOM WIDGETS
// ============================================================================
class ScreenWidget extends MatrixWidget {
ScreenWidget(super.x, super.y, super.width, super.height, {super.children});
@override
void renderSelf() => write(' ' * width * height);
}
class LeftWidget extends MatrixWidget {
LeftWidget(super.x, super.y, super.width, super.height, {super.children});
@override
void renderSelf() => write(' ' * width * height);
}
class TopLineWidget extends MatrixWidget {
TopLineWidget(super.x, super.y, super.width, super.height, {super.children});
@override
void renderSelf() => write('T' * width * height);
}
class InventoryWidget extends MatrixWidget {
InventoryWidget(super.x, super.y, super.width, super.height,
{super.children});
@override
void renderSelf() => write('I' * width * height);
}
class InventoryBelowWidget extends MatrixWidget {
InventoryBelowWidget(super.x, super.y, super.width, super.height,
{super.children});
@override
void renderSelf() => write('X' * width * height);
}
class BottomLineWidget extends MatrixWidget {
BottomLineWidget(super.x, super.y, super.width, super.height,
{super.children});
@override
void renderSelf() => write('B' * width * height);
}
Dart Implementation
The class definitions used above.
// ============================================================================
// MATRIX WIDGET
// ============================================================================
/// Extend to build custom widget
abstract class MatrixWidget extends MatrixCanvas {
final List<MatrixWidget> children;
final int x;
final int y;
MatrixWidget(this.x, this.y, int width, int height,
{this.children = const []})
: super(width, height);
String renderWidgetTree() {
renderSelf();
for (final child in children) {
child.renderWidgetTree();
copyInto(child, child.x, child.y);
}
return paintMultipleLines();
}
void renderSelf();
}
// ============================================================================
// MATRIX TILE
// ============================================================================
/// A data-holder for a single tile.
/// Add other attributes as needed like a color.
class MatrixCanvasTile {
String tile;
MatrixCanvasTile(this.tile);
String paint() => tile;
}
// ============================================================================
// MATRIX CANVAS
// ============================================================================
/// A 2-dimensional canvas to be painted on using [write];
class MatrixCanvas {
final List<List<MatrixCanvasTile>> _map = [];
final String defaultTile;
int _row = 0;
int _col = 0;
MatrixCanvas(int width, int height, {this.defaultTile = ' '}) {
_clearScreen(width, height);
}
// --------------------------------------------------------------------------
//
// --------------------------------------------------------------------------
int get width => _map.first.length;
int get height => _map.length;
MatrixCanvasTile _get(int x, int y) => _map[y][x];
// --------------------------------------------------------------------------
//
// --------------------------------------------------------------------------
String paintMultipleLines() {
final StringBuffer buffer = StringBuffer();
final height = _map.length;
final width = _map.first.length;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
buffer.write(_map[y][x].paint());
}
if (y < height - 1) {
buffer.write('\n');
}
}
return buffer.toString();
}
// --------------------------------------------------------------------------
//
// --------------------------------------------------------------------------
/// Copies [source] into this [MatriCanvas] at [x0] and [y0].
void copyInto(MatrixCanvas source, int x0, int y0) {
final height = source.height;
final width = source.width;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
final tile = source._get(x, y);
_map[y0 + y][x0 + x] = MatrixCanvasTile(tile.tile);
}
}
}
void clearScreen() => _clearScreen(width, height);
/// Recreates the matrix.
void _clearScreen(int width, int height) {
_map.clear();
for (int y = 0; y < height; y++) {
List<MatrixCanvasTile> tmp = [];
for (int x = 0; x < width; x++) {
tmp.add(MatrixCanvasTile(defaultTile));
}
_map.add(tmp);
}
}
// --------------------------------------------------------------------------
//
// --------------------------------------------------------------------------
void cursorLeft() {
_col--;
if (_col < 0) {
cursorUp();
_col = width - 1;
}
}
void cursorRight() {
_col++;
if (_col >= width) {
cursorDown();
_col = 0;
}
}
void cursorUp() => _row--;
void cursorDown() => _row++;
// --------------------------------------------------------------------------
//
// --------------------------------------------------------------------------
void write(String text) {
final chars = text.split('');
final length = chars.length;
for (int index = 0; index < length; index++) {
_map[_row][_col].tile = chars[index];
cursorRight();
}
}
}