NCL 303: The Display

We've been using the Display since our very first NCL program.

We've written text with:

D.CHR "Hello, world!"

We've prepared larger updates with:

D.TXT "Hello, world!"
D.BLT

And in the last lesson, we used the Display cursor to echo typed characters and erase them again.

So far, we've mostly treated the Display as somewhere text appears.

Now let's look more closely at where that text goes.

The Display grid

The standard NCS/e Display provides:

  • 32 character positions across;
  • 12 rows down.

We can describe every position using two coordinates:

(x, y)

x tells us how far across the Display the position is.

y tells us how far down it is.

Both begin at zero.

So the four corners are:

Position Coordinate
Upper-left (0, 0)
Upper-right (31, 0)
Lower-left (0, 11)
Lower-right (31, 11)

The coordinate (0, 0) is therefore the first character position on the Display.

The coordinate (31, 11) is the last visible one.

The cursor

The Display keeps track of a cursor position.

This is where the next character will be written.

When the Display starts, the cursor position is:

(0, 0)

Writing a character advances the cursor so that the next character can be written after it.

For example:

D.CHR "ABC"

starts at the current cursor position and writes three characters in sequence.

Afterward, the cursor is ready to write the next character after C.

We've already relied on this behavior whenever we've written several pieces of text one after another.

Moving the cursor

D.CUR lets us place the cursor at a particular coordinate.

For example:

D.CUR 10 4

moves it to:

(10, 4)

The first value is the horizontal position.

The second is the vertical position.

Anything written afterward begins there:

D.CUR 10 4
D.CHR "HELLO"

Instead of beginning in the upper-left corner, HELLO begins at column 10 on row 4.

Making the cursor visible

Before the shell starts a program, it hides the visible cursor.

The Display still has a cursor position. You simply cannot see the cursor itself.

While we're experimenting with cursor movement, it will be useful to turn it back on.

D.CUR has an optional third value that sets the cursor mode:

D.CUR 10 4 #D.CUR.BLINK

This moves the cursor to (10, 4) and makes it blink.

The available modes are:

Constant Cursor
#D.CUR.OFF Hidden
#D.CUR.ON Visible
#D.CUR.BLINK Visible and blinking

The cursor mode remains in effect until something changes it.

So once we've made the cursor blink:

D.CUR 0 0 #D.CUR.BLINK

we can move it again without specifying the mode:

D.CUR 10 4

The cursor moves and continues blinking.

If you only want to change the cursor mode without changing its position, use D.CURM:

D.CURM #D.CUR.OFF

For now, we'll mostly use the optional third value of D.CUR.

Walk around the Display

Let's make the cursor visit all four corners.

D.CUR 0 0 #D.CUR.BLINK
SYS.AKEY s0

D.CUR 31 0
SYS.AKEY s0

D.CUR 31 11
SYS.AKEY s0

D.CUR 0 11
SYS.AKEY s0

Run the program.

Each SYS.AKEY waits for keyboard input before the program continues.

Press any key after each cursor movement.

The cursor should move through:

(0, 0)
(31, 0)
(31, 11)
(0, 11)

You've now seen the coordinate system directly.

Try changing the coordinates and running the program again.

For example:

D.CUR 5 2 #D.CUR.BLINK
SYS.AKEY s0

D.CUR 20 7
SYS.AKEY s0

D.CUR 15 10

The numbers in D.CUR are simply positions on the Display.

Control characters move through the same space

In NCL 207, we introduced several special characters that affect the Display cursor.

Now that we understand the Display as a grid, their behavior is easier to see.

Carriage return

A carriage return:

\r

moves the cursor to the beginning of the current row.

In coordinates, it changes the horizontal position to zero while leaving the row unchanged.

If the cursor is at:

(12, 4)

then:

D.CHR "\r"

moves it to:

(0, 4)

Line feed

A line feed:

\n

moves the cursor down one row without moving it horizontally.

From:

(12, 4)

a line feed moves to:

(12, 5)

This is why we often use:

\r\n

together.

\r moves to the beginning of the row.

\n moves down to the next row.

Form feed

A form feed:

\f

moves the cursor to:

(0, 0)

the upper-left corner of the Display.

Backspace

Backspace:

\b

moves backward by one character.

We used this in the previous lesson before deleting the final visible character:

D.CHR "\b\d"

Delete

Delete:

\d

clears the glyph at the current cursor position without moving the cursor.

That's why:

D.CHR "\b\d"

works as a visible Backspace operation.

First:

\b

moves onto the previous character.

Then:

\d

blanks that character while leaving the cursor in the correct position for replacement text.

These control characters aren't separate from the Display coordinate system.

They are simply other ways of changing or using its current cursor position.

Character positions and physical tiles

The Display gives our programs 32 horizontal character positions, but the physical Display is actually built from:

16 × 12 tiles

Each tile normally contains two character positions beside each other.

So:

32 character positions

fit across:

16 physical tiles

Most of the characters we've used so far occupy half of one tile.

For example:

Text Physical space
"A" Half a tile
"AB" One tile
"ABCD" Two tiles

This physical arrangement matters because the two character positions in a tile share Display attributes such as colour.

So although text positioning uses 32 columns, some parts of the Display operate at the 16-tile level underneath.

We'll make more use of that distinction as we build complete screens.

Fullwidth characters

Some Unicode characters occupy an entire Display tile.

These are fullwidth characters.

If a fullwidth character is written while the cursor is on the left side of a tile, it occupies that complete tile.

If the cursor is on the right side, the Display first writes a space there and then places the fullwidth character in the next tile.

For example, starting from the left edge:

Text Placement
"AB" A and B share one tile
"中" occupies one complete tile
"A中" A, then a blank half, then in the next tile
"AB中" A and B share the first tile, then occupies the second

The cursor also cannot remain in the right half of an existing fullwidth character.

If cursor movement or Backspace would place it there, the cursor moves to the left side of that character instead.

You do not need to calculate fullwidth layout yet.

The important distinction is simply:

SLEN measures characters in a string. Display space is a separate concern.

A fullwidth character can have a string length of one while occupying an entire physical tile.

Display state and what you can see

We've used both D.CHR and D.TXT, but there is an important detail behind the difference.

The Display keeps its own internal state.

Writing with:

D.TXT "HELLO"

changes that state, including the stored characters and cursor position.

It does not immediately refresh the physical Display.

To make those changes visible, we use:

D.BLT

D.BLT refreshes the complete physical Display from its current state.

D.CHR also changes the Display state, but it immediately refreshes the affected tiles.

So:

Operation Changes Display state Refreshes the physical Display
D.TXT Yes No
D.CHR Yes Yes, for affected tiles
D.BLT No new text Yes, the complete Display

This is the behavior we took advantage of in NCL 302.

While typing, each new character needed to appear immediately:

D.CHR #key

When Enter was pressed, we prepared a larger update:

D.TXT "\r\nHELLO, "
D.TXT #buffer
D.TXT "!"
D.BLT

The three D.TXT instructions changed the Display state.

Then one D.BLT made the complete result visible.

So our earlier practical rule still holds:

Use D.CHR for small changes that should appear immediately.

Use D.TXT to prepare larger changes, then use D.BLT to show them together.

Positioning text deliberately

Now we can combine cursor positioning with normal Display output.

Try:

D.CUR 2 1
D.TXT "TOP"

D.CUR 12 5
D.TXT "MIDDLE"

D.CUR 24 10
D.TXT "BOTTOM"

D.BLT

Each D.CUR chooses where the next text begins.

The text can be prepared in several different places before the final:

D.BLT

refreshes the Display.

This is very different from the programs we've written so far.

Until now, most of our output has simply flowed from wherever the previous text ended.

Now we can deliberately construct a layout.

Try it

Create a program that writes three different messages in three different parts of the Display.

Use D.CUR to choose their positions.

For example, you might place one near:

(2, 1)

another near:

(12, 5)

and another near:

(20, 9)

Prepare all three using D.TXT, then use one:

D.BLT

to show the finished result.

Then try:

  • changing only the coordinates without changing the text;
  • making the cursor visible with #D.CUR.BLINK;
  • moving the cursor around the four corners again;
  • replacing one D.TXT with D.CHR and observing when it becomes visible;
  • using \r, \n, or \f to move the cursor instead of D.CUR;
  • writing a fullwidth character beside ordinary text and observing how it aligns to the physical tiles.

You don't need to build anything elaborate yet.

The goal is to become comfortable thinking of the Display as a space that your program can arrange deliberately.


We've used the Display as a stream of text since NCL 101.

Now we know that underneath that stream is a 32×12 coordinate system, a movable cursor, persistent Display state, and a 16×12 physical tile layout.

In NCL 304: Drawing a Screen, we'll use that geometry to build a complete screen instead of simply writing one piece of text after another.