view method

  1. @override
Object view()
override

Renders the current model state for display.

This method is called after every update to refresh the screen. It should return either a String or a View object.

Guidelines

  • Keep view functions pure - no side effects
  • View should only depend on model state
  • Use string interpolation or StringBuffer for complex views
  • Consider terminal width/height for responsive layouts

Example

@override
String view() {
  final buffer = StringBuffer();

  // Header
  buffer.writeln('╔════════════════════════════╗');
  buffer.writeln('║      My Application        ║');
  buffer.writeln('╚════════════════════════════╝');
  buffer.writeln();

  // Content
  if (loading) {
    buffer.writeln('Loading...');
  } else {
    for (final item in items) {
      final prefix = item == selectedItem ? '▸ ' : '  ';
      buffer.writeln('$prefix$item');
    }
  }

  buffer.writeln();

  // Footer
  buffer.writeln('↑/↓: Navigate  Enter: Select  q: Quit');

  return buffer.toString();
}

Implementation

@override
Object view() {
  final span = TuiTrace.begin(
    'TextInputModel.view',
    tag: TraceTag.render,
    extra: 'len=${_value.length} offset=$_offset offsetR=$_offsetRight',
  );
  // Placeholder text
  if (_value.isEmpty && placeholder.isNotEmpty) {
    span.end(extra: 'placeholder');
    return _placeholderView();
  }

  // Multi-line rendering path.
  if (multiline) {
    final result = _multilineView();
    span.end(extra: 'multiline lines=$lineCount');
    return result;
  }

  final styles = activeStyle();
  final textInlineStyle = styles.text.inline(true);
  String styleText(String s) => textInlineStyle.render(s);

  final visibleValue = _value.sublist(_offset, _offsetRight);
  final pos = math.max(0, _pos - _offset);

  // Selection range in visible space
  int? selStart, selEnd;
  if (selectionStart != null && selectionEnd != null) {
    final start = math.min(selectionStart!, selectionEnd!);
    final end = math.max(selectionStart!, selectionEnd!);

    selStart = math.max(0, start - _offset);
    selEnd = math.min(visibleValue.length, end - _offset);

    if (selStart >= visibleValue.length || selEnd <= 0) {
      selStart = null;
      selEnd = null;
    }
  }

  final v = StringBuffer();
  final selectionStyle = styles.selection;
  final normalEcho = echoMode == EchoMode.normal;
  final hasSelection = selStart != null && selEnd != null;
  final visibleSelStart = selStart ?? -1;
  final visibleSelEnd = selEnd ?? -1;
  var valWidth = 0;

  for (var i = 0; i < visibleValue.length; i++) {
    final raw = visibleValue[i];
    final char = normalEcho ? raw : _echoTransform(raw);
    valWidth += normalEcho
        ? runeWidth(uni.firstCodePoint(raw))
        : stringWidth(char);
    final isSelected =
        hasSelection && i >= visibleSelStart && i < visibleSelEnd;

    if (i == pos) {
      cursor = cursor.setChar(char);
      var cv = cursor.view();
      if (isSelected) {
        cv = selectionStyle.render(cv);
      }
      v.write(cv);
    } else {
      final rendered = styleText(char);
      v.write(isSelected ? selectionStyle.render(rendered) : rendered);
    }
  }

  if (pos >= visibleValue.length) {
    if (_focused && _canAcceptSuggestion()) {
      final suggestion = _matchedSuggestions[_currentSuggestionIndex];
      if (_value.length < suggestion.length) {
        cursor = cursor.setChar(_echoTransform(suggestion[_value.length]));
        v.write(cursor.view());
        v.write(_completionView(1));
      } else {
        cursor = cursor.setChar(' ');
        v.write(cursor.view());
      }
    } else {
      cursor = cursor.setChar(' ');
      v.write(cursor.view());
    }
  }

  // Padding for fixed width
  if (width > 0 && valWidth <= width) {
    var padding = math.max(0, width - valWidth);
    if (valWidth + padding <= width && pos < visibleValue.length) {
      padding++;
    }
    v.write(_renderPadding(styles.text, styles.prompt, padding));
  }

  final styledPrompt = styles.prompt.render(prompt);
  final content = '$styledPrompt${v.toString()}';

  if (useVirtualCursor || !_focused) {
    span.end(extra: 'chars=${visibleValue.length}');
    return content;
  }

  span.end(extra: 'chars=${visibleValue.length}');
  return View(content: content, cursor: terminalCursor);
}