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

From RogueBasin
Jump to navigation Jump to search
(initial)
 
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Dart Implementation of a ASCII-Render Tree ==
== Dart Implementation of a ASCII-Render Tree ==


The code below demonstrates a render tree using an ASCII-Widget.
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.
 
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:
This apporach uses three classes:
Line 15: Line 11:
2. <code>MatrixWidget</code>: An abstract class extending MatrixCanvas. It renders itself and its children.  
2. <code>MatrixWidget</code>: An abstract class extending MatrixCanvas. It renders itself and its children.  


Extend <code>MatrixWidget</code> to create custom Widgets.
Extend <code>MatrixWidget</code> to create custom Widgets and implement its method <code>void renderSelf()</code> to draw its ASCII content.


== Example Result  ==
== Example Result  ==
Line 46: Line 42:
== Usage ==
== Usage ==


The code below generates the example depicted above. It creates custom Widgets that extend <code>MatrixWidget</code>.
The code below generates the example depicted above. It creates custom Widgets that extend <code>MatrixWidget</code>. The method <code>void renderSelf()</code> simply fill the Widget with one character.


<syntaxhighlight lang="dart" line>
<syntaxhighlight lang="dart" line>
Line 52: 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 105: 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 115: 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 121: 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 127: 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 134: 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 141: 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);
Line 163: Line 124:
   final int y;
   final int y;


   MatrixWidget(this.x, this.y, int width, int height,
   MatrixWidget({
      {this.children = const []})
    this.x = 0,
      : super(width, height);
    this.y = 0,
    int width = 0,
    int height = 0,
    this.children = const [],
  }) : super(width, height);


   String renderWidgetTree() {
   String renderWidgetTree() {
    super.resetCursorPosition();
     renderSelf();
     renderSelf();
     for (final child in children) {
     for (final child in children) {
      if (child.width == 0) {
        child.width = width;
      }
      if (child.height == 0) {
        child.height = height;
      }
       child.renderWidgetTree();
       child.renderWidgetTree();
       copyInto(child, child.x, child.y);
       copyInto(child, child.x, child.y);
Line 204: Line 177:
   int _row = 0;
   int _row = 0;
   int _col = 0;
   int _col = 0;
  int _width;
  int _height;


   MatrixCanvas(int width, int height, {this.defaultTile = ' '}) {
   MatrixCanvas(this._width, this._height, {this.defaultTile = ' '}) {
     _clearScreen(width, height);
     clearScreen();
   }
   }


Line 213: Line 188:
   // --------------------------------------------------------------------------
   // --------------------------------------------------------------------------


   int get width => _map.first.length;
   int get width => _width;
   int get height => _map.length;
   int get height => _height;
   MatrixCanvasTile _get(int x, int y) => _map[y][x];
 
   set width(int value) {
    _width = value;
    clearScreen();
  }
 
  set height(int value) {
    _height = value;
    clearScreen();
  }
 
  void resetCursorPosition() => _row = _col = 0;


   // --------------------------------------------------------------------------
   // --------------------------------------------------------------------------
Line 223: Line 209:
   String paintMultipleLines() {
   String paintMultipleLines() {
     final StringBuffer buffer = StringBuffer();
     final StringBuffer buffer = StringBuffer();
    final height = _map.length;
    final width = _map.first.length;


     for (int y = 0; y < height; y++) {
     for (int y = 0; y < height; y++) {
Line 244: Line 228:
   /// Copies [source] into this [MatriCanvas] at [x0] and [y0].
   /// Copies [source] into this [MatriCanvas] at [x0] and [y0].
   void copyInto(MatrixCanvas source, int x0, int y0) {
   void copyInto(MatrixCanvas source, int x0, int y0) {
     final height = source.height;
     final sourceHeight = source._height;
     final width = source.width;
     final sourceWidth = source.width;


     for (int y = 0; y < height; y++) {
     for (int y = 0; y < sourceHeight; y++) {
       for (int x = 0; x < width; x++) {
       for (int x = 0; x < sourceWidth; x++) {
         final tile = source._get(x, y);
         final tile = source._map[y][x];
         _map[y0 + y][x0 + x] = MatrixCanvasTile(tile.tile);
         _map[y0 + y][x0 + x] = MatrixCanvasTile(tile.tile);
       }
       }
     }
     }
   }
   }
  void clearScreen() => _clearScreen(width, height);


   /// Recreates the matrix.
   /// Recreates the matrix.
   void _clearScreen(int width, int height) {
   void clearScreen() {
     _map.clear();
     _map.clear();
     for (int y = 0; y < height; y++) {
     for (int y = 0; y < _height; y++) {
       List<MatrixCanvasTile> tmp = [];
       List<MatrixCanvasTile> tmp = [];
       for (int x = 0; x < width; x++) {
       for (int x = 0; x < width; x++) {

Latest revision as of 10:10, 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 = 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();
    }
  }
}