view method
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 style = activeStyle();
final lineNumberDigits = showLineNumbers ? '${_lines.length}'.length : 0;
final displayLines = _softWrappedLines(lineNumberDigits);
final buffer = StringBuffer();
if (value.isEmpty && placeholder.isNotEmpty) {
final p =
promptFunc?.call((
lineIndex: 0,
isFocused: _focused,
row: _row,
col: _col,
)) ??
prompt;
final ph = style.computedPlaceholder.render(placeholder);
buffer.write('${style.computedPrompt.render(p)}$ph');
} else {
for (var i = 0; i < displayLines.length; i++) {
final displayLine = displayLines[i];
final p =
promptFunc?.call((
lineIndex: i,
isFocused: _focused,
row: displayLine.rowIndex,
col: _col,
)) ??
prompt;
String lnNumber = '';
if (showLineNumbers) {
final lnText = displayLine.charOffset == 0
? '${(displayLine.rowIndex + 1).toString().padLeft(lineNumberDigits)} '
: ' ' * (lineNumberDigits + 1);
lnNumber = style.computedLineNumber.render(lnText);
}
final selectionStyle = Style()
.background(const AnsiColor(7))
.foreground(const AnsiColor(0));
// Compute selection overlap for this visual segment.
int? selStart;
int? selEnd;
if (_selectionStart != null && _selectionEnd != null) {
final (x1, y1) = _selectionStart!;
final (x2, y2) = _selectionEnd!;
final startY = math.min(y1, y2);
final endY = math.max(y1, y2);
final rowIdx = displayLine.rowIndex;
if (rowIdx >= startY && rowIdx <= endY) {
// Selection range in the original (unwrapped) row coordinates.
int rowStart;
int rowEnd;
if (startY == endY) {
rowStart = math.min(x1, x2);
rowEnd = math.max(x1, x2);
} else if (rowIdx == startY) {
rowStart = y1 < y2 ? x1 : x2;
rowEnd = _lines[rowIdx].length;
} else if (rowIdx == endY) {
rowStart = 0;
rowEnd = y1 < y2 ? x2 : x1;
} else {
rowStart = 0;
rowEnd = _lines[rowIdx].length;
}
rowStart = rowStart.clamp(0, _lines[rowIdx].length);
rowEnd = rowEnd.clamp(0, _lines[rowIdx].length);
// Map to this segment via charOffset.
final segStart = displayLine.charOffset;
final segLen = uni.graphemes(displayLine.text).length;
final segEnd = segStart + segLen;
final overlapStart = math.max(rowStart, segStart);
final overlapEnd = math.min(rowEnd, segEnd);
if (overlapStart < overlapEnd) {
selStart = overlapStart - segStart;
selEnd = overlapEnd - segStart;
}
}
}
final gs = uni.graphemes(displayLine.text).toList(growable: false);
final cursorCol = displayLine.hasCursor
? (_col - displayLine.charOffset)
: -1;
var renderedBody = '';
for (var j = 0; j < gs.length; j++) {
String part;
if (displayLine.hasCursor && useVirtualCursor && j == cursorCol) {
cursor = cursor.setChar(gs[j]);
part = cursor.view();
} else {
part = style.computedText.render(gs[j]);
}
final isSelected =
selStart != null && selEnd != null && j >= selStart && j < selEnd;
if (isSelected) {
part = selectionStyle.render(part);
}
renderedBody += part;
}
if (displayLine.hasCursor &&
useVirtualCursor &&
cursorCol >= gs.length) {
cursor = cursor.setChar(' ');
var part = cursor.view();
// If the selection is anchored past EOL (rare), don't attempt to style it.
renderedBody += part;
}
final renderedLine = displayLine.hasCursor && !useVirtualCursor
? style.computedCursorLine.render(renderedBody)
: renderedBody;
buffer.writeln(
'${style.computedPrompt.render(p)}$lnNumber$renderedLine',
);
}
// end of buffer indicator
final remaining = (_height - displayLines.length);
if (remaining > 0) {
final eob = style.computedEndOfBuffer.render('~');
for (var i = 0; i < remaining; i++) {
buffer.writeln(eob);
}
}
}
final content = buffer.toString().trimRight();
if (useVirtualCursor || !_focused) {
return content;
}
return View(content: content, cursor: terminalCursor);
}