nocterm 0.1.0 copy "nocterm: ^0.1.0" to clipboard
nocterm: ^0.1.0 copied to clipboard

A Flutter-like framework for terminal UIs.

Nocterm

Pub Version License Platform

A powerful, Flutter-inspired Terminal User Interface framework with Riverpod state management for building beautiful command-line applications in Dart.

Nocterm Example

โœจ Features #

  • ๐ŸŽฏ Flutter-like API - Familiar component-based architecture that mirrors Flutter's design patterns
  • ๐Ÿ”ฅ Hot Reload - Instant UI updates during development for rapid iteration
  • ๐ŸŽจ Rich Styling - Full color support, borders, padding, and text styling
  • โšก Reactive State - Riverpod integration plus built-in StatefulComponent and setState()
  • ๐Ÿญ Mouse Support - Full mouse interaction including wheel scrolling and click events
  • โŒจ๏ธ Input Handling - Comprehensive keyboard event system with focus management
  • ๐Ÿ“ Flexible Layouts - Row, Column, Stack, ConstrainedBox, and overlay-based layouts
  • ๐Ÿ“ Rich Text - Markdown rendering with formatted text, lists, and code blocks
  • ๐Ÿงช Testing Framework - Flutter-style testing utilities for TUI components
  • ๐ŸŒˆ Cross-Platform - Works seamlessly on Windows, macOS, and Linux

๐Ÿšฆ Project Status #

โš ๏ธ Early Experimental Version (0.1.0)

This framework is in active development. APIs may change significantly in future releases and breaking bugs are still present.

๐Ÿ“ฆ Installation #

Add nocterm to your pubspec.yaml:

dependencies:
  nocterm: ^0.1.0

๐Ÿƒ Quick Start #

import 'package:nocterm/nocterm.dart';

void main() {
  runApp(const Counter());
}

class Counter extends StatefulComponent {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Component build(BuildContext context) {
    return Focusable(
      focused: true,
      onKeyEvent: (event) {
        if (event.logicalKey == LogicalKey.space) {
          setState(() => _count++);
          return true;
        }
        return false;
      },
      child: Container(
        decoration: BoxDecoration(
          border: BoxBorder.all(color: Colors.gray),
        ),
        margin: EdgeInsets.all(8),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Counter: $_count'),
            SizedBox(height: 1),
            Text('Press SPACE to increment', style: TextStyle(color: Colors.gray)),
          ],
        ),
      ),
    );
  }
}

๐Ÿ”ฅ Hot Reload #

Experience Flutter-like hot reload in your terminal applications:

// Run with hot reload enabled
// Your UI updates instantly as you save changes!
dart --enable-vm-service example/your_app.dart

๐ŸŽจ Rich Components #

Layout #

  • โœ… Basic - Column, Row, Expanded, Container, Decoration
  • โœ… Advanced - Stack, ConstrainedBox, Overlay system
  • โœ… Scrollable - ListView, ScrollView with mouse wheel support
  • โœ… Positioning - Align, Center, Padding, Margin

Input & Interaction #

  • โœ… TextField - Multi-line input with enhanced cursor styles
  • โœ… Focusable - Keyboard event handling and focus management
  • โœ… Mouse Support - Click and wheel events

Display #

  • โœ… Text - Rich text, markdown rendering, text wrapping
  • โœ… Progress - Progress bars and indicators
  • โœ… Divider - Horizontal and vertical dividers
  • โœ… Terminal - xTerm embedding for subprocess integration

State Management #

  • โœ… Riverpod - Full reactive state management
  • โœ… StatefulComponent - Built-in setState pattern

๐Ÿงช Testing #

Write tests for your TUI components:

import 'package:test/test.dart';
import 'package:nocterm/nocterm.dart';

void main() {
  test('component renders correctly', () async {
    await testNocterm(
      'my component test',
      (tester) async {
        await tester.pumpComponent(
          Text('Hello, TUI!', style: TextStyle(color: Colors.green))
        );
        
        expect(tester.terminalState, containsText('Hello, TUI!'));
        expect(tester.terminalState, hasStyledText(
          'Hello, TUI!',
          style: TextStyle(color: Colors.green),
        ));
      },
      debugPrintAfterPump: true, // See visual output during testing
    );
  });

  test('handles keyboard input', () async {
    await testTui(
      'keyboard test',
      (tester) async {
        await tester.pumpComponent(MyInteractiveComponent());
        await tester.sendKey(LogicalKey.enter);
        
        expect(tester.terminalState, containsText('Enter pressed!'));
      },
    );
  });
}

๐Ÿ“ License #

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments #


Made with ๐Ÿ’™