TUTORIAL.md 5.5 KB

tvision-m2 - tutorial

Build the library once:

cd tvision-m2
gm2 -fiso -c tvtty.def
gm2 -fiso -I. -c tv.mod

Each program is then:

gm2 -fiso -I. tv.o myapp.mod -o myapp
./myapp          # run from a real terminal

1. The smallest program

An empty blue desktop with a status line; Esc quits.

MODULE hello;
IMPORT tv;
VAR e : tv.Event; quit : BOOLEAN; st : tv.ViewPtr;
BEGIN
    IF NOT tv.Init() THEN HALT END;
    st := tv.NewView(tv.VText, 0, tv.Rows()-1, tv.Cols(), 1);
    tv.SetText(st, " Hello - press Esc");
    tv.AddView(tv.Desk(), st);
    tv.Finish;
    quit := FALSE;
    WHILE NOT quit DO
        IF tv.Resized() THEN tv.Finish END;
        IF tv.ReadEvent(e) THEN
            IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN quit := TRUE END;
            IF NOT quit THEN tv.Finish END
        END
    END;
    tv.Done
END hello.

The shape of every program: Init; add views to Desk(); loop calling Resized and ReadEvent; redraw with Finish; Done on exit.

2. A window with widgets

Views use absolute screen coordinates. NewView(kind, x, y, w, h) creates one; AddView(parent, child) attaches it.

w := tv.NewView(tv.VWindow, 3, 2, 50, 12);
tv.SetText(w, "Editor");                     (* window title *)

t := tv.NewView(tv.VText, 5, 4, 20, 1);
tv.SetText(t, "Name:"); tv.AddView(w, t);

input := tv.NewView(tv.VInput, 12, 4, 34, 1);
tv.SetText(input, "Modula-2"); tv.AddView(w, input);

b := tv.NewView(tv.VButton, 12, 10, 8, 1);
tv.SetText(b, "OK"); tv.SetTag(b, tv.cmOK); tv.AddView(w, b);

tv.AddView(tv.Desk(), w);
tv.FocusView(input);

A VWindow clips its children, can be dragged by its title row and closed with the X box. Tab/arrows move focus; inputs are edited with the usual keys.

3. Reacting to commands

HandleEvent returns cmNone, cmClose, or the tag you gave a control:

cmd := tv.HandleEvent(e);
IF cmd = tv.cmClose THEN
    IF tv.Sender() # NIL THEN tv.DelView(tv.Sender()) END
ELSIF cmd = tv.cmOK THEN
    tv.Message("Info", "OK pressed")
END;

Use custom tags >= 100 to distinguish your own buttons.

4. Menus

CONST mQuit = 101; mAbout = 102;

tv.MenuBarInit;
tv.MenuAdd("File");
tv.MenuItemAdd("Open", 100);
tv.MenuSep;
tv.MenuItemAdd("Quit", mQuit);
tv.MenuAdd("Help");
tv.MenuItemAdd("About", mAbout);

The bar appears on row 0. Clicking a title opens the drop-down; F10, arrows, Enter and Esc work too. Selecting an item makes HandleEvent return its tag:

IF cmd = mQuit THEN quit := TRUE
ELSIF cmd = mAbout THEN
    IF tv.MessageBox("About", "my app", tv.mbOK) = tv.cmOK THEN END
END;

5. Dialogs

VAR iName, cOn, cmd : INTEGER; s : ARRAY [0..127] OF CHAR;
...
tv.Dialog("Options", 44, 12);
iName := tv.DlgInput("Name:", "Modula-2");
cOn   := tv.DlgCheck("Enabled", TRUE);
tv.DlgButton("OK", tv.cmOK);
tv.DlgButton("Cancel", tv.cmCancel);
cmd := tv.DlgRun;                 (* blocks until a button / Esc *)
IF cmd = tv.cmOK THEN
    tv.DlgTextAt(iName, s);       (* read the field *)
    IF tv.DlgValue(cOn) = 1 THEN tv.Message("Info", "Enabled") END
END;

Radios added with DlgRadio are automatically exclusive. For simple "notice" dialogs use Message / MessageBox.

6. Lists and mouse

VList shows a caller-owned ListData:

VAR data : tv.ListData;
data.items[0] := "Alpha"; data.items[1] := "Bravo"; data.count := 2;
list := tv.NewView(tv.VList, 5, 4, 20, 6);
list^.list := CAST(tv.ListDataPtr, ADR(data));
tv.AddView(w, list);

The mouse works automatically: click to focus/press buttons, drag window titles, click list rows and scroll with the wheel. You only need HandleEvent; the toolkit does the hit-testing.

7. A text editor

VAR ed : tv.EditorData; edp : tv.EditorDataPtr; ev : tv.ViewPtr;
...
edp := CAST(tv.EditorDataPtr, ADR(ed));
IF NOT tv.EditorLoad(edp, "notes.txt") THEN
    tv.EditorInit(edp); tv.EditorAddLine(edp, "Type here...")
END;
tv.EditorSetLanguage(edp, tv.langModula2);   (* or langPascal / langOberon *)
tv.EditorSetIndent(edp, 2);                  (* auto-indent: 2 spaces per level; 0 = off *)
ev := tv.NewView(tv.VEditor, 2, 2, 60, 20);
tv.EditorAttach(ev, edp);
tv.AddView(win, ev);
tv.FocusView(ev);
...
IF tv.EditorSave(edp, "notes.txt") THEN ... END;

See examples/edit.mod for a full-screen editor with a File menu.

8. Running in a graphics window (tigr)

tvg is a drop-in replacement for the terminal loop:

IMPORT tv, tvg;
...
IF NOT tvg.InitGfx("my app", 90, 30) THEN HALT END;
tv.MenuBarInit; ... build views on tv.Desk() ...
tvg.Finish;
WHILE NOT quit DO
    IF tvg.ReadEvent(e) THEN
        cmd := tv.HandleEvent(e);          (* same events, same commands *)
        IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN quit := TRUE END;
        tvg.Finish
    END;
    IF tvg.Closed() THEN quit := TRUE END
END;
tvg.Done

Build it with make tvg.o then make -C examples gfx; see examples/gfx.mod. The toolkit code (windows, widgets, menus, dialogs) is identical.

9. More examples

See examples/ (empty, window, dialog, menus) and build them with make. See API.md for the full reference.

Pitfalls (GNU Modula-2)

  • Pointer fields need ^ (v^.rect).
  • Value-returning no-arg procedures need () in a .def (Cols() : INTEGER).
  • INTEGER compared with HIGH(array) needs VAL(INTEGER, HIGH(a)).
  • A BEGIN ... END block cannot be a CASE branch - use a helper.
  • Present is diffed; if you draw outside the loop (e.g. before Init), the cells do not exist yet.