ASCII-Render Tree for Layouting in Dart

From RogueBasin
Jump to navigation Jump to search

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 and implement its method void renderSelf() to draw its ASCII content.

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. The method void renderSelf() simply fill the Widget with one character.

import 'democanvas.dart';

main() {
  final screenWidget = ScreenWidget(width: 40, height: 20, children: [
    LeftWidget(width: 30, children: [
      TopLineWidget(height: 1),
      BottomLineWidget(y: 20 - 1, height: 1)
    ]),
    InventoryWidget(x: 30, width: 10, children: [
      InventoryBelowWidget(y: 15, height: 5),
    ])
  ]);

  // --------------------------------------------------------------------------
  // PAINT ALL
  // --------------------------------------------------------------------------

  print(screenWidget.renderWidgetTree());
}

// ============================================================================
// 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 = 0,
    this.y = 0,
    int width = 0,
    int height = 0,
    this.children = const [],
  }) : super(width, height);

  String renderWidgetTree() {
    super.resetCursorPosition();
    renderSelf();
    for (final child in children) {
      if (child.width == 0) {
        child.width = width;
      }
      if (child.height == 0) {
        child.height = height;
      }

      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;
  int _width;
  int _height;

  MatrixCanvas(this._width, this._height, {this.defaultTile = ' '}) {
    clearScreen();
  }

  // --------------------------------------------------------------------------
  //
  // --------------------------------------------------------------------------

  int get width => _width;
  int get height => _height;

  set width(int value) {
    _width = value;
    clearScreen();
  }

  set height(int value) {
    _height = value;
    clearScreen();
  }

  void resetCursorPosition() => _row = _col = 0;

  // --------------------------------------------------------------------------
  //
  // --------------------------------------------------------------------------

  String paintMultipleLines() {
    final StringBuffer buffer = StringBuffer();

    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 sourceHeight = source._height;
    final sourceWidth = source.width;

    for (int y = 0; y < sourceHeight; y++) {
      for (int x = 0; x < sourceWidth; x++) {
        final tile = source._map[y][x];
        _map[y0 + y][x0 + x] = MatrixCanvasTile(tile.tile);
      }
    }
  }

  /// Recreates the matrix.
  void clearScreen() {
    _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();
    }
  }
}