Skip to content

Surfaces and consoles overview

Everything you see in a SadConsole game draws through a surface. A surface is a 2D grid of cells, where each cell holds a glyph, a foreground color, and a background color. Surfaces are the foundation of every visual object in SadConsole, from simple text displays to full windowed UIs.

Pick the type that matches what you need:

TypeUse it when you want to…
ScreenObjectOrganize children without rendering any cells.
ScreenSurfaceDisplay glyphs, draw shapes, or show static content.
ConsoleAccept keyboard input or use a cursor for text printing.
ControlsConsoleHost UI controls like buttons, text boxes, and checkboxes.
WindowShow a bordered, titled, draggable panel or dialog.
AnimatedScreenObjectPlay a frame-by-frame cell animation.
LayeredScreenSurfaceStack multiple independent cell layers on one surface.

SadConsole organizes its display objects in a clear hierarchy. Each type builds on the one before it, adding more capability:

ScreenObject
├─ AnimatedScreenObject
└─ ScreenSurface
├─ LayeredScreenSurface
└─ Console
├─ ControlsConsole
└─ Window
  • ScreenObject: the base of every SadConsole object. Provides positioning, parent/child relationships, components, and input processing. It doesn’t render any cells on its own.
  • ScreenSurface: adds a cell surface, a font, and a renderer. This is the most common type you’ll use when you just want to display something on screen.
  • Console: extends ScreenSurface with a cursor for text input and keyboard handling.
  • ControlsConsole: extends Console with a built-in control host for UI controls like buttons, checkboxes, and lists.
  • Window: extends Console with a title bar, border, dragging support, and modal display. Also includes a built-in control host.
  • AnimatedScreenObject: extends ScreenObject directly and implements IScreenSurface. Holds a list of cell surface frames and plays them back over time.
  • LayeredScreenSurface: extends ScreenSurface with a LayeredSurface component, allowing multiple independent cell layers to be stacked and rendered together.

At the lowest level, a surface is an array of ColoredGlyph objects managed by an ICellSurface. Each cell stores three pieces of information:

  • Foreground color: the color of the glyph character.
  • Background color: the color behind the glyph.
  • Glyph index: the index into the font texture that determines which character or tile to display.

The CellSurface class implements ICellSurface and provides the actual cell array. You can set individual cells, print text, draw shapes, and apply effects through the surface.

// Access individual cells on a surface
screenSurface.Surface[5, 3].Glyph = (int)'A';
screenSurface.Surface[5, 3].GlyphCharacter = 'A';
screenSurface.Surface[5, 3].Foreground = Color.Yellow;
screenSurface.Surface[5, 3].Background = Color.DarkBlue;

All screen objects support a parent-child hierarchy through the Children collection. Child objects render relative to their parent’s position and inherit visibility and update behavior.

ScreenSurface parent = new(80, 25);
ScreenSurface child = new(20, 10);
child.Position = new Point(5, 5);
parent.Children.Add(child);
GameHost.Instance.Screen = parent;

Any ScreenObject-based type can serve as a child of another. This lets you compose complex layouts by nesting surfaces, consoles, and windows together.

ScreenObject is the base class for everything in SadConsole. It doesn’t draw any cells—it exists purely to provide the shared infrastructure all other types depend on:

  • Position: tracks where the object sits on screen, both relative to its parent and absolute in screen space.
  • Children: a collection of child IScreenObject instances that render and update along with their parent.
  • Components: a list of IComponent objects that extend behavior without subclassing.
  • Input: routes keyboard and mouse events to the object and its components.
  • Visibility and focus: controls whether the object renders and whether it receives keyboard input.

Use ScreenObject directly when you need an invisible container to group and position other objects without rendering anything itself.

ScreenObject container = new();
container.Position = new Point(10, 5);
ScreenSurface panel = new(20, 10);
container.Children.Add(panel);
GameHost.Instance.Screen = container;

The ScreenSurface type is the most commonly used object in SadConsole. It wraps a CellSurface with everything needed to display it on screen: a font, a font size, a renderer, and a position.

Create a ScreenSurface by specifying the width and height in cells:

ScreenSurface surface = new(80, 25);
surface.Surface.Print(1, 1, "Hello, SadConsole!");
surface.Position = new Point(5, 5);
GameHost.Instance.Screen = surface;

A ScreenSurface can have a total buffer size larger than the visible area. The view controls which portion of the buffer you see. This is useful for scrolling maps or large text areas:

// Create a surface with a 40x20 visible area, backed by a 200x100 buffer
ScreenSurface bigSurface = new(40, 20, 200, 100);
// Scroll the view
bigSurface.ViewPosition = new Point(10, 5);
PropertyDescription
SurfaceThe underlying ICellSurface containing all cells.
FontThe font used to render glyphs.
FontSizeThe pixel size of each cell.
TintA color overlay applied to the entire surface.
UsePixelPositioningWhen true, positions the surface by pixel instead of by cell.

The Console type inherits from ScreenSurface and adds a Cursor component. The cursor lets you print text at a tracked position, handle keyboard input, and create classic terminal-style interactions.

Console console = new(80, 25);
console.Cursor.IsVisible = true;
console.Cursor.IsEnabled = true;
console.Cursor.PrintAppearanceMatchesHost = false;
console.Cursor.Print("Type something: ");
GameHost.Instance.Screen = console;

Key differences from ScreenSurface:

  • Has a Cursor property for text input and printing.
  • Keyboard input is enabled by default.
  • Supports AutoCursorOnFocus to show or hide the cursor based on focus state.

The ControlsConsole type combines a Console with a ControlHost component. This gives you a surface that can display and manage UI controls like buttons, text boxes, checkboxes, and list boxes.

ControlsConsole controlsConsole = new(60, 20);
Button button = new("Click Me", 12);
button.Position = new Point(2, 2);
button.Click += (s, e) => controlsConsole.Cursor.Print("Button clicked!");
controlsConsole.Controls.Add(button);
GameHost.Instance.Screen = controlsConsole;

The Controls property exposes the ControlHost, where you add, remove, and manage controls. You can also add a ControlHost manually to any ScreenSurface without using ControlsConsole:

ScreenSurface surface = new(60, 20);
ControlHost controls = new();
surface.SadComponents.Add(controls);
Button button = new("OK", 6);
controls.Add(button);

For more details about UI controls, see the controls overview.

The Window type provides a bordered, titled surface that you can drag, show modally, and dismiss. It inherits from Console and includes its own ControlHost, making it ideal for dialog boxes, settings panels, and popup menus.

Window window = new(40, 15);
window.Title = "Settings";
window.CanDrag = true;
window.CloseOnEscKey = true;
window.Center();
window.Show(true); // Show as modal
  • Title bar: displays a title string along the top border. Control alignment with the TitleAlignment property.
  • Dragging: set CanDrag to true to let users move the window with the mouse.
  • Modal display: call Show(true) to display the window modally, blocking input to objects behind it.
  • Close on Escape: set CloseOnEscKey to true so pressing Escape hides the window.
  • Built-in controls: access the Controls property to add UI controls directly.
Window dialog = new(30, 10);
dialog.Title = "Confirm";
dialog.CanDrag = false;
Button yesButton = new("Yes", 6);
yesButton.Position = new Point(5, 5);
yesButton.Click += (s, e) => { dialog.DialogResult = true; dialog.Hide(); };
Button noButton = new("No", 6);
noButton.Position = new Point(15, 5);
noButton.Click += (s, e) => { dialog.DialogResult = false; dialog.Hide(); };
dialog.Controls.Add(yesButton);
dialog.Controls.Add(noButton);
dialog.Center();
dialog.Show(true);

LayeredScreenSurface extends ScreenSurface and adds a LayeredSurface component. This lets you stack multiple independent cell layers on a single surface—useful for tile maps with separate foreground, background, and decoration layers.

The base surface from ScreenSurface automatically becomes the first layer. Add more layers through the Layers property:

LayeredScreenSurface layered = new(80, 25);
// The base surface is layer 0 automatically
// Add a second layer
ICellSurface layer1 = layered.Layers.Add();
layer1.Print(5, 5, "Overlay text");
GameHost.Instance.Screen = layered;

Each layer is a full ICellSurface rendered in order, with transparent cells letting layers below show through. Resize operations on LayeredScreenSurface propagate to all layers through the Layers component.

AnimatedScreenObject extends ScreenObject directly and implements IScreenSurface. Instead of a single cell surface, it holds a Frames collection of ICellSurface objects and advances through them automatically based on a configurable AnimationDuration.

Create an animation by adding frames and then calling Start():

AnimatedScreenObject animation = new("walk", 4, 4);
// Add frames and draw content into each one
ICellSurface frame1 = animation.CreateFrame();
frame1.SetGlyph(1, 1, '@');
ICellSurface frame2 = animation.CreateFrame();
frame2.SetGlyph(2, 1, '@');
animation.AnimationDuration = TimeSpan.FromSeconds(0.5);
animation.Repeat = true;
animation.Start();
GameHost.Instance.Screen = animation;

SadConsole also provides a static helper to generate noise-effect animations:

AnimatedScreenObject noise = AnimatedScreenObject.CreateStatic(20, 10, 8, 0.3);
GameHost.Instance.Screen = noise;

Key properties and methods:

  • Frames: The list of ICellSurface objects that make up the animation.
  • AnimationDuration: The total time to play through all frames once.
  • Repeat: When true, the animation loops.
  • Start() / Stop() / Restart(): Control playback.
  • CurrentFrameIndex: The index of the frame currently displayed.