Difference between revisions of "ASCII-Render Tree for Layouting in Dart"

From RogueBasin
Jump to navigation Jump to search
m
Line 48: Line 48:


main() {
main() {
   final int totalWidth = 40;
   final screenWidget = ScreenWidget(width: 40, height: 20, children: [
  final int totalHeight = 20;
    LeftWidget(width: 30, children: [
 
      TopLineWidget(height: 1),
  final int leftWidth = 30;
      BottomLineWidget(y: 20 - 1, height: 1)
  final int leftHeight = totalHeight;
    ]),
 
    InventoryWidget(x: 30, width: 10, children: [
  final int inventoryOffset = leftWidth;
      InventoryBelowWidget(y: 15, height: 5),
  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]);


   // --------------------------------------------------------------------------
   // --------------------------------------------------------------------------
Line 101: Line 66:


// ============================================================================
// ============================================================================
// CUSTOM WIDGETS
// WIDGETS
// ============================================================================
// ============================================================================


class ScreenWidget extends MatrixWidget {
class ScreenWidget extends MatrixWidget {
   ScreenWidget(super.x, super.y, super.width, super.height, {super.children});
   ScreenWidget({super.x, super.y, super.width, super.height, super.children});
   @override
   @override
   void renderSelf() => write(' ' * width * height);
   void renderSelf() => write(' ' * width * height);
Line 111: Line 76:


class LeftWidget extends MatrixWidget {
class LeftWidget extends MatrixWidget {
   LeftWidget(super.x, super.y, super.width, super.height, {super.children});
   LeftWidget({super.x, super.y, super.width, super.height, super.children});
   @override
   @override
   void renderSelf() => write(' ' * width * height);
   void renderSelf() => write(' ' * width * height);
Line 117: Line 82:


class TopLineWidget extends MatrixWidget {
class TopLineWidget extends MatrixWidget {
   TopLineWidget(super.x, super.y, super.width, super.height, {super.children});
   TopLineWidget({super.x, super.y, super.width, super.height, super.children});
   @override
   @override
   void renderSelf() => write('T' * width * height);
   void renderSelf() => write('T' * width * height);
Line 123: Line 88:


class InventoryWidget extends MatrixWidget {
class InventoryWidget extends MatrixWidget {
   InventoryWidget(super.x, super.y, super.width, super.height,
   InventoryWidget(
      {super.children});
      {super.x, super.y, super.width, super.height, super.children});
   @override
   @override
   void renderSelf() => write('I' * width * height);
   void renderSelf() => write('I' * width * height);
Line 130: Line 95:


class InventoryBelowWidget extends MatrixWidget {
class InventoryBelowWidget extends MatrixWidget {
   InventoryBelowWidget(super.x, super.y, super.width, super.height,
   InventoryBelowWidget(
      {super.children});
      {super.x, super.y, super.width, super.height, super.children});
   @override
   @override
   void renderSelf() => write('X' * width * height);
   void renderSelf() => write('X' * width * height);
Line 137: Line 102:


class BottomLineWidget extends MatrixWidget {
class BottomLineWidget extends MatrixWidget {
   BottomLineWidget(super.x, super.y, super.width, super.height,
   BottomLineWidget(
      {super.children});
      {super.x, super.y, super.width, super.height, super.children});
   @override
   @override
   void renderSelf() => write('B' * width * height);
   void renderSelf() => write('B' * width * height);

Revision as of 10:05, 5 November 2023

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, 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();
    }
  }
}