-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This guide takes you from zero to a running TUI in Delphi. It covers system requirements, installation, building the library, and building your first interactive application from scratch.
| Requirement | Detail |
|---|---|
| Delphi / RAD Studio | 13.1 Florence (37.0) or later |
| Target platform | Win32 console ({$APPTYPE CONSOLE}) |
| Windows version | Windows 10 build 10586 (v1511) minimum |
| True Color (24-bit) | Windows 10 build 18362 (v1903) or later |
| Framework | None — Blinki does not depend on VCL or FMX |
| Linux64 | Real POSIX backend (termios raw mode, poll(2)); build via the IDE's Linux64 platform + PAServer |
Linux / macOS: The POSIX console backend (
Blinki.Core.Console.Posix.pas,TTuiPosixConsoleBackend) is a real, complete Linux64 implementation — raw-modetermios, non-blockingpoll(2)-based input, a unit-tested escape-sequence decoder (arrow/F-keys, Shift+Tab, SGR mouse), UTF-8 input, and SIGINT/SIGTERM handling that restores the terminal before the process dies. macOS and other POSIX systems are not tested. The codebase includes{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}guards in all core units as a preparatory step for Free Pascal / Lazarus compatibility.
Blinki has no design-time components and does not require any package installation in the IDE. The only step is making the library's source units visible to your project's compiler.
git clone https://github.com/marcobreveglieri/blinki.git
Open your project's Options → Delphi Compiler → Search Path and add the absolute path to the
Source\ folder, for example:
C:\Libs\blinki\Source
When building from the command line with MSBuild you can pass the path directly:
msbuild MyApp.dproj /p:Platform=Win32 /p:Config=Debug /p:DCC_UnitSearchPath="C:\Libs\blinki\Source"
That is all. No .bpl to install, no GetIt manifest — the library ships as plain .pas source
files.
Blinki is also published in the official Blocks community repository
as marcobreveglieri.blinki. Blocks downloads,
compiles, and wires versioned Delphi packages into your IDE for you.
:: Install the Blocks CLI once (requires Windows + winget)
winget install DelphiBlocks.Blocks
:: From your project folder: initialize the workspace and pick your Delphi version
blocks init
:: Add Blinki to the project
blocks install marcobreveglieri.blinkiBlocks fetches the sources, compiles the Blinki runtime package with MSBuild, and registers the
library and search paths in your IDE — then just uses the units you need. To pin a specific
release, append a SemVer constraint, e.g. blocks install marcobreveglieri.blinki@^0.1.0.
Blinki uses MSBuild from the command line as its sole build method. Never use the IDE's Build
button directly for this repository — always call MSBuild after sourcing rsvars.bat.
# From the repository root:
.\do_build.batdo_build.bat sources rsvars.bat from the RAD Studio 37.0 installation, then builds in order:
- The runtime package (
Source\Blinki.dproj, Release/Win32) - All smoke tests (
Tests\SmokeTests\Blinki.SmokeTests.groupproj) - All demos (
Demos\Blinki.Demos.groupproj) - The unit tests (
Tests\UnitTests\)
The script stops at the first MSBuild error.
If you need to build only a specific project:
# Source the RAD Studio environment variables
& "C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat"
# Build the library package (Release)
msbuild Source\Blinki.dproj /t:Build /p:Config=Release /p:Platform=Win32
# Build a single demo (Debug)
msbuild Demos\Form\Form.dproj /t:Build /p:Config=Debug /p:Platform=Win32Build output lands in <project>\Win32\<Config>\.
After a successful build, launch a demo directly:
.\Demos\Form\Win32\Debug\Form.exeKeyboard shortcuts common to most demos:
- Tab / Shift-Tab — move focus between widgets
- Enter / Space — activate the focused widget
- Ctrl+T — toggle Dark / Light theme
- Ctrl+Q — quit
This tutorial builds a small account-registration form step by step, explaining the key concepts
as we go. The result is close to the Demos\Form\Form.dpr demo that ships with the library.
| Concept | What it means |
|---|---|
| Root widget | The single top-level widget passed to TTuiApp.SetRoot
|
| Parent owns children | Pass the parent as the first constructor argument; the parent frees all children automatically |
| Layout constraints | Replace Left/Top/Width/Height — tell the container how to size a child, not where |
| Event loop |
TTuiApp.Run blocks until Quit is called; the terminal is always restored on exit |
In RAD Studio, create a new Console Application (File → New → Console Application). Remove any boilerplate and set the file encoding to UTF-8 with BOM and CRLF line endings before saving (the compiler requires this).
program MyForm;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Blinki.Core.App,
Blinki.Core.Input,
Blinki.Core.Widget,
Blinki.Core.Geometry,
Blinki.Core.Style,
Blinki.Core.Theme,
Blinki.Widgets.Labels,
Blinki.Widgets.Box,
Blinki.Widgets.TextInput,
Blinki.Widgets.Button,
Blinki.Widgets.Toast,
Blinki.Widgets.Alert,
Blinki.Layout.Stack;var
LApp: TTuiApp;
LRoot: TTuiVStack;
LHeader: TTuiLabel;
LFormBox: TTuiBox;
LFormStack: TTuiVStack;
LNameLabel: TTuiLabel;
LNameInput: TTuiTextInput;
LBtnRow: TTuiHStack;
LBtnSubmit: TTuiButton;
LToast: TTuiToast;
begin
ReportMemoryLeaksOnShutdown := True;
LApp := TTuiApp.Create;
LRoot := TTuiVStack.Create; // NOT created with a parent — will be owned by App via SetRoot
try
// ---- Header bar ----
LHeader := TTuiLabel.Create(LRoot); // LRoot is the parent → LRoot owns LHeader
LHeader.Text := ' My First Blinki Form | Tab=focus Ctrl+Q=quit';
LHeader.LayoutConstraint := TTuiLayoutConstraint.Fixed(1); // exactly 1 row tall
// ---- Toast notification area ----
LToast := TTuiToast.Create(LRoot);
LToast.DurationMs := 3000;
LToast.LayoutConstraint := TTuiLayoutConstraint.Fixed(3);
// ---- Main panel (box with rounded border) ----
LFormBox := TTuiBox.Create(LRoot);
LFormBox.Title := ' Registration ';
LFormBox.BoxStyle := bsRounded;
LFormBox.LayoutConstraint := TTuiLayoutConstraint.Fill(1); // fill remaining space
// TTuiBox accepts exactly one child; we place a VStack inside it
LFormStack := TTuiVStack.Create(LFormBox);
LNameLabel := TTuiLabel.Create(LFormStack);
LNameLabel.Text := ' Name';
LNameLabel.LayoutConstraint := TTuiLayoutConstraint.Fixed(1);
LNameInput := TTuiTextInput.Create(LFormStack);
LNameInput.Placeholder := 'Your full name...';
LNameInput.MaxLength := 64;
LNameInput.LayoutConstraint := TTuiLayoutConstraint.Fixed(1);
LBtnRow := TTuiHStack.Create(LFormStack);
LBtnRow.LayoutConstraint := TTuiLayoutConstraint.Fixed(1);
LBtnSubmit := TTuiButton.Create(LBtnRow);
LBtnSubmit.Caption := ' Submit ';
LBtnSubmit.LayoutConstraint := TTuiLayoutConstraint.Fill(1);Key insight: every widget receives its parent as the first constructor argument. The parent's
TObjectList<TTuiWidget> takes ownership and frees the child automatically when the parent is
destroyed. Never call Free on a widget that has a parent.
Layout constraints replace absolute positioning:
| Constraint | Meaning |
|---|---|
Fixed(N) |
Exactly N rows (or columns, depending on the container axis) |
Fill(W) |
Grow to fill available space; W is the relative weight among siblings |
Min(N) |
At least N rows / columns |
Max(N) |
At most N rows / columns |
Percentage(P) |
P percent of the container's available space (0–100) |
// ---- Button click handler ----
LBtnSubmit.OnClick := procedure
begin
var LName := Trim(LNameInput.Text);
if LName = '' then
LToast.Show(' Please enter your name.', alWarning)
else
LToast.Show(Format(' Hello, %s!', [LName]), alSuccess);
end;Events are anonymous methods (reference to procedure). They capture variables from the
enclosing scope so you can reference LNameInput, LToast, and LApp directly.
// ---- App wiring ----
LApp.SetRoot(LRoot); // SetRoot transfers ownership of LRoot to App
LApp.OnKeyPress := procedure(const AKey: TTuiKeyEvent)
begin
if (AKey.Code = kcChar) and (kmCtrl in AKey.Modifiers) and (AKey.Character = #17) then
LApp.Quit; // Ctrl+Q = ASCII 17
end;
LApp.Run; // blocks here; restores the terminal even if an exception is raised
finally
LApp.Free; // frees App → frees LRoot → frees the entire widget tree
end;
end.TTuiApp.Run switches the terminal to an alternate screen buffer, hides the cursor, and enters
the event loop. When Quit is called it tears down everything and returns to the normal shell
prompt — even if an exception is raised inside the loop, because teardown runs in a finally block.
& "C:\Program Files (x86)\Embarcadero\Studio\37.0\bin\rsvars.bat"
msbuild MyForm.dproj /t:Build /p:Config=Debug /p:Platform=Win32
.\Win32\Debug\MyForm.exeYou should see a form with a rounded border, a text input, a Submit button, and toast notifications. Tab moves focus; Enter or Space activates the focused button.
- Explore the 16 demo applications in
Demos\for real-world patterns (modals, data tables, animations, a full Tetris game, a team-chat client, …). - Read Architecture & Advanced Usage to understand the event loop, the rendering pipeline, how to write custom widgets, and how to optimise refresh performance.
- Check Contributing before sending a pull request.
Blinki v0.1.0 · MIT License · Copyright © 2026 Marco Breveglieri · GitHub · Home · Getting Started · Architecture · Contributing