dascade 1.0.2
dascade: ^1.0.2 copied to clipboard
A lightweight immediate-mode TUI framework for Dart with buffered & differential rendering and ANSI styling.
The Dart ASCII Console Application Development Environment
Dascade is an experimental, immediate‑mode TUI (Terminal User Interface) framework for Dart.
It is designed to be lightweight, deterministic, and portable, enabling developers to build rich terminal applications without retained widget trees, implicit layout passes, or hidden state.
Table of Contents #
- Why Dascade?
- Project Status
- Core Philosophy
- Features
- Performance
- Installation
- Dascade API Overview
- UI System Overview
- Layout System
- Stock UI Elements (Current)
- Custom UI Elements
- Rendering Model
- Naming Conventions
- Examples
- Web-Based Development (Experimental)
- FAQ
- Contributing
- License
- Author & Maintainer
Why Dascade? #
Often, developers need a text-based user interface that is both portable and predictable. Dascade exists to make it possible to write a TUI once and run it anywhere, in native terminals or on the web, without sacrificing control, performance, or determinism.
With Dascade, you can build interactive applications directly in the terminal, visualize complex systems in real time, and reason about layout and input explicitly. There is no hidden state, no implicit reflow, and no magic.
For example: Let's say I want to port a1k0n's donut.c to Dart, and I want to see it's performance with a nice textbox near the bottom of my terminal. See image below.
Another example: Let's say I need to debug an A* implementation before it goes in one of my projects. Using Dascade, I can visualize the open sets, closed sets, and the final path as the algorithm runs — making correctness and performance issues immediately obvious. See image below.
How about this: I need a quick way to create a terminal application that organizes text-based data in a layout and allows interaction through scrollable lists. See image below.
Consider: Building a classic Snake game as a quick programming exercise becomes trivial with Dascade. Arbitrary cell-based drawing and real-time ANSI input allow the entire implementation to fit in roughly 50 lines of code. See image below.
And anything else you can think of. With Dascade, any terminal becomes your canvas.
Project Status #
EXPERIMENTAL: ACTIVE DEVELOPMENT
Dascade is under active development, but is already highly usable and customizable.
However: The core systems, rendering, layout, input, and widgets, are highly functional and efficient, exercised by real examples in the example/ directory.
Dascade is designed to be flexible enough that higher-level abstractions can be built on top of the core framework to support a wide range of systems and workflows. While this may involve some boilerplate today, continued development focuses on providing faster, more ergonomic tools for mocking up production-ready applications.
Core Philosophy #
Dascade's features are built around the following principles:
Immediate-Mode UI #
- Draw calls are issued every frame with no implicit state
- UI layout and interaction is described and parsed each frame
- No element/widget lifecycles or retained trees
- Bottom line: Application state lives in your code, not inside a Dascade-managed state.
Buffered + Line-Based Differential Rendering #
- Rendering is double‑buffered, so FLICKERING is NOT POSSIBLE.
- Only changed cells are emitted to the backend
- Optimized for native terminals, with fallback modes
- Web-based Canvas 2D terminal and ANSI emulation (experimental)
Real-time ANSI-based Input #
- Keyboard input is supported in all environments
- Mouse input is supported in ANSI-capable terminals and in the web backend
- Platform-specific input details are normalized by the backend
For example: Below is a demonstration of ANSI mouse events translated into Dascade’s immediate-mode input API.
Deterministic Layout #
- The Dascade UI Layout Engine assigns concrete
DURects - UI elements trust the rect they are given
- Elements never perform layout calculations themselves
Minimal Dependencies #
- Uses
dart_consolefor native terminal I/O - ANSI behavior implemented internally where practical
- No heavy runtime abstractions
Cross-Platform by Design #
- macOS, Linux, Windows terminals
- Experimental web backend via software terminal and ANSI emulation
Features #
- Immediate-mode primitive draw API
- Immediate‑mode UI API
- Deterministic layout engine
- Line-base buffered rendering with diffing
- ANSI color and style support
- Keyboard input (cross‑platform)
- Mouse input (ANSI SGR / X10)
- Optional element borders
- Native terminal backend with Win32 Virtual Terminal support
- Experimental web backend
Performance #
Thanks to buffered, line-based differential rendering and 256-color ANSI support, Dascade is performant enough to support real-time animations and advanced ASCII graphics, including software-rendered 3D/2D scenes displayed directly in the terminal. Why? Because reasons.
Installation #
Dascade is a Dart library distributed through the Dart package manager, pub.
Sidenote: Why Dart?: Dart may not be the fastest language in every scenario, but it offers excellent support for cross-platform compilation, strong async primitives, and a familiar C-style syntax that makes it well suited for this kind of framework. TLDR: In my opinion, Dart is somewhat slept on.
If you are new to Dart, this section walks through the basics before installing Dascade.
Prerequisites #
To use Dascade, you need:
- Dart SDK (stable)
- A terminal environment (macOS, Linux, or Windows)
You can verify Dart is installed by running:
dart --version
If Dart is not installed, follow the official installation guide: https://dart.dev/get-dart
Creating a Dart Project #
If you do not already have a Dart project, create one:
dart create my_app
cd my_app
This will generate a standard Dart project structure, including a
pubspec.yaml file where dependencies are declared.
Adding Dascade #
Open your pubspec.yaml file and add Dascade under dependencies:
dependencies:
dascade: ^1.0.0
Version numbers are subject to change while Dascade is experimental.
Then fetch dependencies:
dart pub get
Importing Dascade #
Once installed, you can import Dascade in your Dart source files:
import 'package:dascade/dascade.dart';
You are now ready to build interactive terminal experiences with Dascade, the rest is just syntax and learning Dascade's API.
Running Your App #
Dascade applications are run directly from the terminal:
dart run [path_to_your_projects_main_file].dart
For best results:
- Use a modern terminal with ANSI support
- Avoid terminals that aggressively redraw or heavily buffer output. That said, supporting challenging terminal environments is an explicit goal of Dascade’s development. If a particular environment behaves poorly, please open an issue.
Compilation and Distribution #
Dascade applications are standard Dart programs. This means they can be compiled and distributed using Dart’s built-in tooling without any special handling from the framework itself.
Below are the recommended workflows for distributing Dascade applications on each supported desktop platform.
macOS #
On macOS, Dascade applications can be compiled into a standalone native executable using Dart’s AOT compiler.
Building a Native Binary #
From your project root:
dart compile exe bin/main.dart -o dascade_app
This produces a native executable named dascade_app.
You can then distribute this binary directly, or bundle it inside a .app
wrapper if desired.
Notes #
- The executable runs in a terminal environment
- Users must launch it from Terminal or a terminal emulator
- ANSI-compatible terminals are recommended
Linux #
Linux builds follow the same process as macOS.
Building a Native Binary #
dart compile exe bin/main.dart -o dascade_app
The resulting binary can be distributed directly.
Notes #
- Most modern Linux terminals fully support ANSI escape sequences
- Ensure executable permissions are preserved when distributing:
chmod +x dascade_app
Windows #
On Windows, Dascade applications compile to a native .exe file.
Building a Native Binary #
dart compile exe bin/main.dart -o dascade_app.exe
This produces a standard Windows executable.
Notes #
- Windows Terminal is recommended
- Older terminals may require enabling Virtual Terminal processing
- Dascade uses Win32 Virtual Terminal support where available
Distribution Considerations #
Because Dascade applications are terminal-based:
- They are typically distributed as command-line tools
- Installers are optional, not required
- Cross-compilation is not supported; binaries must be built per platform
For cross-platform distribution, it is common to:
- Build binaries on each target platform
- Or distribute source code and allow users to compile locally
Dascade API Overview #
This section introduces the core Dascade API by example. Rather than presenting large, copy-paste programs, it focuses on small, composable snippets that explain how to think in Dascade.
All examples are drawn directly from patterns used throughout the example/
directory.
Application Lifecycle #
Every Dascade application starts the same way.
await Dascade.run((DascadeFramework d) async {
// Application code lives here
});
Dascade.run:
- Initializes the runtime
- Sets up rendering and input backends
- Owns the terminal lifecycle
- Provides a
DascadeFrameworkhandle (d)
All interaction with Dascade happens through this handle.
Immediate-Mode Frame Loop #
Dascade is an immediate-mode framework. UI and rendering are described every frame inside an explicit loop.
bool running = true;
while (running) {
/// Make your application has an exit strategy!
if (d.escape) running = false;
d.beginFrame();
// draw / UI calls here
d.endFrame();
/// Make sure to throttle the frame rate to avoid starving the main thread.
await Future.delayed(const Duration(milliseconds: 16));
}
Key points:
beginFrame()must be called before issuing draw or UI callsendFrame()flushes the frame to the backend- State lives in your variables, not in the framework
Drawing Cells Directly #
At the lowest level, Dascade renders individual cells.
d.draw(
x,
y,
DascadeCell.encode(
glyph: '@'.codeUnitAt(0),
fg: 46,
bg: 0,
),
);
- Coordinates are integer cell positions
- Colors use 256-color ANSI indices
- Rendering is buffered and diffed automatically
Colors and Styling #
Cells are encoded using ANSI color indices.
final int green = 46;
final int gray = 240;
final int red = 196;
Foreground and background colors are independent. No global style state is retained between frames.
Keyboard Input #
Keyboard input is exposed as immediate-mode state.
if (d.escape) {
running = false;
}
Typical usage:
- Poll keys each frame
- Update application state accordingly
- No event queues or callbacks
Mouse Input #
Mouse input is supported in ANSI-capable terminals and on the web.
final bool justClicked = d.mouseLeftDown && !previousMouseDown;
previousMouseDown = d.mouseLeftDown;
Mouse state includes:
- Button state
- Position
- Frame-accurate transitions
ANSI mouse events are normalized into the same API across platforms.
Terminal Dimensions #
Terminal size is available through the framework handle.
final int width = d.width;
final int height = d.height;
Important:
- Dimensions are only valid after a frame has begun
- Initialization that depends on size should be deferred
This pattern appears in maze and visualization examples.
Animation and Timing #
Animation is achieved by updating state each frame.
/// In your loop, you can update variables until your heart's content. Their state will be reflected next loop.
angle += 0.03;
Combined with Future.delayed, this enables:
- Smooth animation
- Deterministic timing
- Controlled frame rates
Used in examples like Snake and the ASCII donut.
Algorithms and Visualization #
Because Dascade exposes raw cell drawing, it is well suited for:
- Pathfinding visualization
- Cellular automata
- Debugging algorithms
- Educational tools
The maze and A* examples demonstrate this style.
UI System Overview #
Dascade includes a minimal immediate-mode UI system. UI is optional and layered on top of the renderer.
Every UI frame must begin with a root container.
d.ui.root(
d.ui.column([
// elements
]),
);
UI elements render into rectangles assigned by layout. They do not perform layout calculations themselves.
Layout Containers #
Layouts are explicit and composable.
Columns and Rows #
d.ui.column([
elementA,
elementB,
]);
d.ui.row([
left,
right,
], layout: DULayout.equal());
Layouts:
- Assign
DURects to children - Handle spacing and overflow
- Are deterministic
Lists #
Scrollable lists are provided via DUList.
/// Outside main loop!
final DUList vlist = DUList(border: true, borderLabel: "Vertical List");
final DUList hlist = DUList(border: true, borderLabel: "Horizontal List", horizontal: true);
/// Omitting some other widget initialization here...
/// ... in application loop, between d.beginFrame() and d.endFrame():
d.ui.root(
/// Define layout here.
d.ui.column([
/// Have a text box take up the top half of the screen.
text,
/// Have the two lists side-by-side on the bottom half of the screen.
d.ui.row([
/// Show a vertical list of 32 different text box elements.
vlist.show(
[
for(int i = 0; i < 32; i++)
DUTextBox(
initialText: "Item $i",
border: true,
editable: true,
)
], itemSize: 3
),
/// Show a horizontal list of 64 different text box elements, laid out in a column themselves.
hlist.show(
[
for(int i = 0; i < 32; i++) ...[
d.ui.column([
DUTextBox(
initialText: "Top $i",
border: true,
editable: true,
),
DUTextBox(
initialText: "Bottom $i",
border: true,
editable: true,
),
], layout: DULayout.equal())
]
], itemSize: 8
),
], layout: DULayout.equal())
], layout: DULayout.equal())
);
Lists:
- Can be vertical or horizontal
- Handle clipping and scrolling
- Accept arbitrary child elements
Lists are heavily used in UI examples.
Text Boxes #
Text boxes display and optionally edit text.
/// Outside of loop...
final DUTextBox text = DUTextBox(
initialText: "Hello",
border: true,
editable: true,
);
/// In your loop, lay it out using Dascade UI's layout engine.
Features:
- Editable or read-only
- Optional borders and labels
- Keyboard-driven input
Buttons #
Buttons provide basic interaction.
/// Outside main loop!
final DUButton button = DUButton(label: "Press Me!", borderLabel: "Button");
/// ... in main loop
/// layout button in a row, column, or list like other UI elements...
if (button.fire) {
// handle a complete press cycle (up -> down.) This also applies to 'Enter' strikes.
}
/// or...
if(button.down) {
/// do something while the button is held down.
}
Button state is immediate:
- True only on the frame it is activated
- No retained callbacks
Radio Buttons #
Radio buttons model mutually exclusive state.
/// Outside of loop...
final DURadio radio0 = DURadio(label: 'Option A', border: true, state: false, borderLabel: "Radio");
final DURadio radio1 = DURadio(label: 'Option B', border: true, state: false, borderLabel: "Radio");
final DURadio radio2 = DURadio(label: 'Option C', border: true, state: false, borderLabel: "Radio");
final DURadio radio3 = DURadio(label: 'Option D', border: true, state: false, borderLabel: "Radio");
/// Let's track state over frames to ensure we print only when states change.
bool r0s = false;
bool r1s = false;
bool r2s = false;
bool r3s = false;
/// Omitting some other widget initialization here.
/// ... in application loop, between d.beginFrame() and d.endFrame():
d.ui.root(
d.ui.column(
<DUElement>[
text,
d.ui.row([
d.ui.column([
radio0,
radio1,
radio2,
radio3
], layout: DULayout.equal()),
info
], layout: DULayout.flex([2, 8]))
],
layout: DULayout.flex([2, 1]),
gap: 0,
pad: 0,
)
);
/// Simple state machine and output of radio states to our text canvas.
if(r0s != radio0.state) {
r0s = radio0.state;
text.text += "\nRadio 0: $r0s";
}
/// radio.value() is the same as radio.state; whatever makes more sense to you!
if(r1s != radio1.value()) {
r1s = radio1.state;
text.text += "\nRadio 1: $r1s";
}
if(r2s != radio2.state) {
r2s = radio2.state;
text.text += "\nRadio 2: $r2s";
}
if(r3s != radio3.value()) {
r3s = radio3.state;
text.text += "\nRadio 3: $r3s";
}
State lives outside the widget and is read each frame.
Dropdowns #
Dropdowns allow compact selection from a list.
final DUTextBox text = DUTextBox(
borderLabel: "List View (Demo)",
initialText: "Dropdowns are great for multiple-line text-based option menus where one selection is true until the next selection has been made.",
border: true,
editable: false,
);
final DUTextBox info = DUTextBox(
borderLabel: "More Information",
initialText: 'Dropdown menu elements allow selection from a list of plain text options and retain the selected state until a new choice is explicitly made by the user or programmatically updated.',
border: true,
editable: false,
);
final DUDropdown dd = DUDropdown(
borderLabel: "Dropdown",
label: 'Color',
options: <String>[
'Red',
'Orange',
'Yellow',
'Green',
'Blue',
'Indigo',
'Violet'
],
border: true,
);
/// ... in application loop, between d.beginFrame() and d.endFrame():
d.ui.root(
/// Define layout here.
d.ui.column(
[
text,
d.ui.row([
dd.show(),
info
], layout: DULayout.flex([5, 15]))
], layout: DULayout.flex([16, dd.open() ? 6 : 1]) /// Notice how I change the layout based on Dropdown state.
)
);
/// Let's output the current dropdown menu state to the text field, for fun.
if(dd.changed) {
/// I can now read dd.value()! It will be the new changed value from dropdown.
}
Used for configuration-style UIs.
Layout System #
Dascade layouts live in ui/elements/layout and are fully explicit.
Currently provided:
DUListLayout(horizontal / vertical)DUSpacer
Layout behavior:
- Best‑effort when space is constrained
- Gaps collapse before elements are dropped
- Overflowed elements receive zero‑sized rects
Layout assigns DURects; elements render within them.
Stock UI Elements (Current) #
Stock elements included in the repository:
Text
- Static text rendering
- ANSI color support
TextBox
- Editable and read‑only modes
- Optional borders
- Keyboard input handling
Button
- Clickable via keyboard or mouse
- Immediate‑mode interaction state
Radio Button
- Mutually exclusive selection
- Stateless rendering, external state control
Dropdown
- Expandable selection list
- Keyboard and mouse interaction
Native / Misc
- Native terminal information elements
- Debug / inspection helpers
Custom UI Elements #
Dascade is designed to be extensible by default. All built-in UI widgets are implemented using the same public contracts that are available to you.
If you can describe your widget as something that:
- Can be laid out in a 2D grid
- Renders entirely inside a rectangle
- Handles interaction explicitly each frame
Then you can build it as a custom Dascade UI element.
This section walks through how to do that using DUCustomElement.
The Role of DUCustomElement #
DUCustomElement is a batteries-included base class intended to make authoring
custom UI widgets straightforward without hiding Dascade’s core philosophy.
It implements DUElement and provides:
- Rect storage and layout handling
- Border and content rect helpers
- Standard focus and press semantics
- Common drawing helpers (frames, fills, labels)
- Theme-aware rendering helpers
You are free to implement DUElement directly, but most custom widgets should
extend DUCustomElement.
Design Contract (Important) #
All custom UI elements must follow these rules:
- Elements trust the
DURectassigned during layout - Elements do not perform layout math
- Elements render entirely inside their assigned rect
- All colors should come from
DUITheme - Interaction and rendering are explicit and frame-based
Dascade does not currently support overlays or z-indexing. If your widget cannot be expressed inside a single rectangular region, it is likely out of scope for now.
Creating a Custom Element #
A custom element is a class that extends DUCustomElement and overrides two methods:
interact(DURuntime r)render(DURenderer p, DURuntime r)
Minimal structure:
final class DUMyElement extends DUCustomElement {
DUMyElement() : super(border: true);
@override
void interact(final DURuntime r) {
// Handle input and update state
}
@override
void render(final DURenderer p, final DURuntime r) {
// Draw into the assigned rect
}
}
Layout and Rects #
Each element receives its layout rectangle via layout().
DUCustomElement stores this internally and exposes helpers:
outerRect— full bounds (including border)contentRect— drawable interior (excludes border)
You should never modify the rect or assume global screen coordinates.
Example:
final DURect c = contentRect;
if (c.width <= 0 || c.height <= 0) return;
Focus and Hover #
Focus and hover are managed by the runtime.
Helpers include:
bool isFocused(DURuntime r)
bool isHovered(DURuntime r, {bool contentOnly = false})
To claim focus on click:
focusOnClick(r, outerRect);
This pattern is used by nearly all interactive widgets.
Pressable Interaction (Buttons, Steppers, Toggles) #
For button-like widgets, DUCustomElement provides standard press semantics.
Call updatePressable from interact:
updatePressable(r);
This populates:
down— true while heldfire— true for exactly one frame on release
Mouse and keyboard (Enter) behavior are normalized automatically.
Example:
@override
void interact(final DURuntime r) {
updatePressable(r);
if (fire) {
// perform action
}
}
Drawing Frames #
drawFrameIfNeeded(p, r);
Automatically uses theme colors and optional borderLabel.
Filling Content #
fillContent(p, faceColor(r));
Fills the content area with spaces using the provided theme-aware color.
Drawing Text #
drawTextLine(
p,
contentRect,
"Hello",
y: contentRect.top,
x: contentRect.left,
color: theme.text,
);
Text is clipped to the available width.
Centered Labels #
drawCenteredLabel(
p,
"Hello",
color: theme.text,
);
If the content height is even, an underline row is drawn to maintain visual centering.
Example: Custom Stepper Element #
The following is a real example of a custom element implemented by extending
DUCustomElement.
It demonstrates:
- Multiple interactive regions inside one rect
- Mouse and keyboard interaction
- Theme-aware rendering
- Explicit state management
final class DUStepper extends DUCustomElement {
final int min;
final int max;
int value;
DUStepper({
required super.border,
super.borderLabel,
super.theme,
required this.min,
required this.max,
required this.value,
});
bool _downMinus = false;
bool _downPlus = false;
bool _fireMinus = false;
bool _firePlus = false;
bool _prevEnter = false;
@override
void interact(final DURuntime r) {
focusOnClick(r, outerRect);
_fireMinus = false;
_firePlus = false;
final DURect c = contentRect;
if (c.width <= 0 || c.height <= 0) return;
final int mid = c.left + (c.width ~/ 2);
final DURect minusRect = DURect(
upperLeft: DUPoint(x: c.left, y: c.top),
lowerRight: DUPoint(x: mid, y: c.bottom),
);
final DURect plusRect = DURect(
upperLeft: DUPoint(x: mid, y: c.top),
lowerRight: DUPoint(x: c.right, y: c.bottom),
);
_downMinus = identical(r.active, this) && r.mouseDown && r.hovered(minusRect);
_downPlus = identical(r.active, this) && r.mouseDown && r.hovered(plusRect);
if (r.mousePressed && r.hovered(c)) {
r.active = this;
}
if (r.mouseReleased && identical(r.active, this)) {
if (r.hovered(minusRect)) _fireMinus = true;
if (r.hovered(plusRect)) _firePlus = true;
}
final bool enterDown = r.enter;
final bool enterReleased = !enterDown && _prevEnter;
_prevEnter = enterDown;
if (isFocused(r) && enterReleased) {
_firePlus = true;
}
if (_fireMinus) {
value = math.max(min, value - 1);
}
if (_firePlus) {
value = math.min(max, value + 1);
}
down = _downMinus || _downPlus;
fire = _fireMinus || _firePlus;
}
@override
void render(final DURenderer p, final DURuntime r) {
drawFrameIfNeeded(p, r);
final DURect c = contentRect;
if (c.width <= 0 || c.height <= 0) return;
final DUIColor baseFace = faceColor(r);
fillContent(p, baseFace);
final int mid = c.left + (c.width ~/ 2);
final DURect minusRect = DURect(
upperLeft: DUPoint(x: c.left, y: c.top),
lowerRight: DUPoint(x: mid, y: c.bottom),
);
final DURect plusRect = DURect(
upperLeft: DUPoint(x: mid, y: c.top),
lowerRight: DUPoint(x: c.right, y: c.bottom),
);
final DUIColor downFace =
isFocused(r) ? theme.buttonDownFocused : theme.buttonDown;
if (_downMinus) {
fillRect(p, minusRect, glyph: 0x20, color: downFace);
}
if (_downPlus) {
fillRect(p, plusRect, glyph: 0x20, color: downFace);
}
final DUIColor textColor = DUIColor(
fg: theme.text.fg,
bg: baseFace.bg,
bold: theme.text.bold,
);
final String label = '[-] ${value.toString().padLeft(2, "0")} [+]';
drawCenteredLabel(p, label, color: textColor);
}
}
Using Custom Elements in Layouts #
Custom elements are regular DUElements and can be used anywhere a stock element
can be used.
d.ui.root(
d.ui.column(
<DUElement>[log, stepper, progress],
layout: DULayout.flex([2, 1, 1]),
),
);
No registration or special handling is required.
Contributions are welcome, especially in expanding the set of built-in Dascade UI elements.
Rendering Model #
Rendering is cell‑based:
_nextbuffer represents desired frame_currentbuffer represents terminal state- Diffing emits only changed cells (unless disabled)
Backends:
- Native terminal renderer
- Web software terminal emulator/renderer (experimental)
Resize events trigger full buffer re‑sync.
Naming Conventions #
Dascade enforces strict naming conventions:
- Core engine types:
D* - UI types:
DU* - Geometry:
DURect, etc.
Examples:
DRendererDascadeBufferDUButtonDUTextBox
These conventions are intentional and required for contributions.
Examples #
The repository includes working examples.
- Button demo
- Radio buttons
- Dropdowns
- TextBox editing
- Mouse interaction demo
- Snake game
- Maze demo
- ASCII donut
- Immediate‑mode UI samples
Examples live in the example/ directory and double as regression tests.
Web-Based Development (Experimental) #
Dascade includes experimental support for running applications in the browser. This allows the same immediate-mode UI code used for native terminals to be rendered inside a web page via a software-emulated terminal backend.
Web support is experimental and under active development. APIs, performance characteristics, and rendering behavior may change.
Overview #
The repository contains a web/ directory that demonstrates how to run a Dascade
application on the web.
The web backend works by:
- Compiling your Dart application to JavaScript
- Rendering the terminal grid manually in the browser
- Forwarding input events to Dascade’s existing input system
This is not a native terminal emulator and does not rely on <canvas> or DOM text nodes.
All rendering is handled by Dascade’s web backend.
Project Structure #
A minimal web setup consists of:
main.dart
import '../example/ui/button/button.dart' as app;
void main() {
app.main();
}
This file simply forwards execution to an existing Dascade example or application.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dascade Web</title>
<style>
html, body {
margin: 0;
padding: 0;
background: black;
overflow: hidden;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
This page hosts the compiled Dascade application and provides a full-screen surface for rendering.
Running the Web Build #
From the web/ directory, the provided workflow is:
dart compile js main.dart -o main.js
dart run build_runner serve web:8080
Then open your browser and navigate to:
http://localhost:8080
Your Dascade application should appear and run inside the browser.
Web Limitations #
Current limitations of the web backend include:
- Performance lower than native terminals
- Manual font and cell metric handling
- Incomplete input parity with native backends
- Rendering behavior may differ between browsers
The web backend prioritizes correctness and portability over performance.
Contributing to Web Support #
Web support is an active area of development, and contributions are welcome.
Helpful areas include:
- Performance optimizations
- Improved input handling
- Font metric and scaling improvements
- Accessibility research
- Backend cleanup and documentation
If you are interested in improving web support, please open an issue or pull request.
FAQ #
Is this a GUI toolkit?
No. Dascade is a terminal UI engine.
Why immediate‑mode?
Predictability, simplicity, and performance.
Does Dascade manage state for me?
No. State lives in your application.
Is web support production‑ready?
No. It is experimental.
Contributing #
Contributions are welcome, especially in the stock Dascade UI package.
Please:
- Follow naming conventions
- Use clear Dart doc comments
- Avoid hidden state or magic behavior
- Keep APIs explicit and deterministic
PRs that simplify the system are preferred over clever abstractions.
License #
Open source under the MIT license. See LICENSE for more information.
Author & Maintainer #
Ian Wesley Wilkey
Creator and primary maintainer of Dascade.
Dascade was started as a personal exploration into building a clean, portable, immediate‑mode TUI framework for Dart.