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
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.
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.
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.
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;
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.
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.
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.
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.
See examples/ (empty, window, dialog, menus) and build them with
make. See API.md for the full reference.
^ (v^.rect).() in a .def
(Cols() : INTEGER).INTEGER compared with HIGH(array) needs VAL(INTEGER, HIGH(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.