# 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. ```modula2 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. ```modula2 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: ```modula2 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 ```modula2 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: ```modula2 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 ```modula2 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`: ```modula2 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 ```modula2 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; 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: ```modula2 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.