# TGroup  (guide pp. 445–455)

Rust module(s): src/view/group.rs (also src/view/mod.rs, src/view/context.rs, src/app/program.rs)   |   magiblot: include/tvision/views.h / source/tvision/tgroup.cpp

> TGroup is the central container/router. The guide documents 4 fields, 31
> methods, and **no own palette** (a group is visible only through its
> subviews). Many entries map idiomatically: the `next`/`prev`/`owner` pointer
> ring → an owned `Vec<Child>` addressed by [`ViewId`] (D3); cache-buffer +
> lock/unlock + Redraw machinery → whole-tree redraw + diff; modal
> Execute/ExecView/EndModal → the single event loop + [`ModalFrame`] +
> `Deferred::EndModal` (D9); stream Load/Store/Get/PutSubViewPtr → dropped
> (`TStreamable` not ported); getData/setData/DataSize → the D10 value protocol.

## Fields

| Guide entry | Pg | Bucket | Corr | Rust symbol / mapping | Doc | Notes |
|---|---|---|---|---|---|---|
| `Buffer` (field) | 446 | NOT-PORTED | — | — | — | `PVideoBuf` cache buffer for the group's screen image. tvision-rs uses whole-tree redraw + diff (no per-group cache buffer, no `ofBuffered`); the module doc states "the occlusion/buffering draw machinery is replaced by whole-tree redraw + diff." No analog. |
| `Current` (field) | 446 | EQUIVALENT | OK | `Group.current: Option<ViewId>` (private) + `Group::current()` getter | N/A | C++ `Current: PView` (the selected subview) → a `ViewId` handle resolved by index lookup (D3, infoPtr→ViewId mapping). Field private; public getter `current()`. |
| `Last` (field) | 447 | EQUIVALENT | OK | `Group.children: Vec<Child>` (the Vec endpoints) | N/A | C++ `Last: PView` anchors the circular subview ring (Next of Last → first). tvision-rs replaces the ring with an ordered `Vec` stored back-to-front (`children[0]` bottom, `children.last()` top); there is no separate `Last` pointer — the Vec *is* the ordering. Idiomatic Vec replacement of the pointer ring (D3). |
| `Phase` (field) | 447 | EQUIVALENT | OK | `tv::view::Phase` enum + `Context::phase()` / `set_phase()` (`src/view/context.rs`) | N/A | C++ `Phase: (phFocused, phPreProcess, phPostProcess)` read by subviews as `Owner^.Phase`. tvision-rs carries the phase on the shared `Context` (set by the router around each leg, save/restored under nesting so a child reads its immediate owner's phase), not as a per-group field. Same three phases (`PreProcess`/`Focused`/`PostProcess`). `→ concept` (event phases). |

## Methods

| Guide entry | Pg | Bucket | Corr | Rust symbol / mapping | Doc | Notes |
|---|---|---|---|---|---|---|
| `Init` (constructor) | 447 | PORTED | OK | `tv::view::Group::new(bounds: Rect) -> Group` | 3 | Guide: sets `ofSelectable` + `ofBuffered` in Options, `EventMask = $FFFF`. Rust sets `options.selectable = true` and opts into the two tracking classes (mouse_move/mouse_auto) so it can route them; `ofBuffered` and the full 0xFFFF mask are deliberately dropped (no buffering; tvision-rs's mask exposes only the two opt-in tracking classes). Documented in the constructor's heritage note. |
| `Load` (constructor) | 447 | NOT-PORTED | — | — | — | Stream deserialization (`S.Get` each subview, `GetSubViewPtr` fixups, `Awaken`). `TStreamable`/streams dropped (serde-if-revived). No analog. |
| `Done` (destructor) | 447 | EQUIVALENT | OK | `Drop` for `Group` (children are `Box<dyn View>` in a `Vec`) | N/A | C++ `Done`: Hide, dispose each subview, then `TView::Done`. In Rust, dropping the `Group` drops its `children: Vec<Child>` (each `Box<dyn View>`), recursively — RAII replaces the explicit destructor walk. |
| `Awaken` (method) | 448 | PORTED | OK | `tv::view::Group::awaken` (impl `View::awaken`) | 3 | Guide: calls each subview's `Awaken` in Z-order. Rust iterates all children calling `awaken()` (order documented as irrelevant). Matches. |
| `ChangeBounds` (method) | 448 | PORTED | OK | `tv::view::Group::change_bounds` (impl `View::change_bounds`) | 3 | Guide: set bounds, then `CalcBounds`/`ChangeBounds` for each subview. Rust applies bounds, computes the size delta, and (only if changed) runs `calc_bounds`/`change_bounds` per child (grow-mode resize math). Buffer-realloc/redraw-lock bracket dropped (documented). Matches. |
| `DataSize` (method) | 448 | NOT-PORTED | — | — | — | C++ `DataSize`: sum of each subview's `DataSize` for the getData/setData record. Replaced by the D10 typed value protocol (`View::value`/`set_value`), which transfers per-field typed `FieldValue`s rather than a flat byte record; no byte-size accounting needed. No `data_size` symbol exists. |
| `Delete` (method) | 448 | EQUIVALENT | OK | `tv::view::Group::remove(id, ctx)` / `remove_child_by_id(id)` | 3 | C++ `Delete(P)`: unlink subview, redraw, null its Owner/Next (no dispose). Rust `remove` drops the child from `children` and re-establishes currency; `remove_child_by_id` is the build/runtime no-Context variant. Note: Rust **drops** (owns) the child rather than handing back a detached object — ownership model difference (D3); semantically the subview leaves the group. |
| `Draw` (method) | 448 | PORTED | OK | `tv::view::Group::draw` (impl `View::draw`) | 3 | Guide: if cache buffer exists, `WriteBuf`; else `Redraw` all subviews. Rust has no cache buffer, so it always paints children **back-to-front** (painter's algorithm) through clipped sub-contexts, casting drop shadows. Paint order reversed from C++ (top-first + occlusion) in favor of whole-tree redraw + diff — documented. |
| `EndModal` (method) | 448 | EQUIVALENT | OK | `Context::end_modal` → `Deferred::EndModal(cmd)` → `Program::end_state` (`src/app/program.rs`) | 3 | C++ `EndModal(cmd)`: terminate the current modal view's modal state, returning `cmd` from `ExecView`. tvision-rs has no nested modal loop on the group; a view requests modal end via the deferred `EndModal` effect, which the pump applies by setting `Program::end_state`, observed by the `exec_view` `while` (D9). `→ concept` (modal loop). Guide pointer added to `Context::end_modal` and `Program::end_modal`. |
| `EventError` (method) | 448 | NOT-PORTED | — | — | — | C++ `EventError`: hook invoked by `Execute` when the loop hits an unhandlable (`evNothing`-after-handle) event, chaining to the owner / `TApplication.EventError`. tvision-rs's single event loop has no per-group `Execute` loop, so there is no per-group unhandled-event hook; unconsumed events simply unwind (module doc: "No event bubbling"). No analog. |
| `ExecView` (method) | 449 | EQUIVALENT | OK | `tv::app::Program::exec_view` (`src/app/program.rs`) | 3 | C++ `ExecView(P)`: save context, set `sfModal`, insert P, `Execute`, restore, return result (`cmCancel` if P nil). tvision-rs `exec_view` pushes a [`ModalFrame`] onto the capture stack and runs the single loop until `end_state` is set, then returns the command (D9). Modal counterpart of insert/remove. `→ concept` (modal loop). Guide pointer added. |
| `Execute` (method) | 449 | EQUIVALENT | OK | `tv::app::Program::run` / `pump_once` (`src/app/program.rs`) | 3 | C++ `Execute`: the group's own `repeat GetEvent/HandleEvent until Valid(EndState)` modal loop. tvision-rs has ONE event loop in `Program` (`run` = `while end_state.is_none() { pump_once() }`); a group never runs its own loop (D9). Same getEvent→handleEvent→validate-end shape, hoisted to the program. `→ concept` (single event loop). Guide pointer added to `Program::run`. |
| `First` (method) | 449 | EQUIVALENT | OK | `Group.children` first-in-Z-order = `children.last()` | N/A | C++ `First: PView` = the subview closest to the top in Z-order. tvision-rs stores `children` back-to-front, so "first" (topmost) is `children.last()`; the topmost-under-cursor helper (`topmost_child_at`) and the `.rev()` visits realize this. No standalone public `first()` getter — the Vec ordering is the source of truth (D3). |
| `FirstThat` (method) | 449 | EQUIVALENT | OK | `self.children.iter().rev().find(...)` (inline, throughout group.rs) | N/A | C++ `FirstThat(P)`: apply a boolean fn to each subview in Z-order, return the first hit. tvision-rs has no generic `FirstThat`; the Z-order `find`/`find_map` over `children.iter().rev()` is open-coded at each call site (e.g. `topmost_child_at`, the positional-router target search). Idiomatic iterator replacement of the function-pointer walk (`TCollection`-style → Rust iterators). |
| `FocusNext` (method) | 450 | PORTED | OK | `tv::view::Group::focus_next(forwards, ctx) -> bool` | 3 | Guide: focus next/prev `ofSelectable` subview in Z-order; if `ofValidate`, calls `Valid(cmReleaseFocus)` and keeps focus (returns false) if invalid. Rust `focus_next` → `find_next` (tab-order search) → `focus_child` (validates the outgoing current via `valid(RELEASED_FOCUS)` when it opts into validation). Returns `true` when there is no other eligible child. Matches. |
| `ForEach` (method) | 450 | EQUIVALENT | OK | `self.children.iter[_mut]()` loops (inline, throughout group.rs) | N/A | C++ `ForEach(P)`: apply an action to each subview in Z-order. tvision-rs has no generic `ForEach`; per-child action loops (`awaken`, `set_state` propagation, `valid`, draw, gather/scatter) are open-coded over `children`. Idiomatic iterator replacement of the function-pointer walk. |
| `GetData` (method) | 450 | EQUIVALENT | OK | `tv::view::Group::gather_data() -> Vec<Option<FieldValue>>` | 3 | C++ `GetData(Rec)`: walk subviews in reverse Z-order, copy each `DataSize` into the flat record. tvision-rs gathers each child's typed `View::value()` into a `Vec` (bottom-to-top order). D10 value protocol replaces the byte-record walk; documented heritage note cites `TGroup::getData`. |
| `GetHelpCtx` (method) | 450 | PORTED | OK | `View::get_help_ctx` (default, used by Group) + `Program` aggregation logic (`src/app/program.rs:1757`) | 3 | Guide: return the current focused subview's `GetHelpCtx`; if none specifies a context, return own `HelpCtx`. In tvision-rs, `Group` does NOT override `get_help_ctx` — it uses the `View` trait default which returns its own `help_ctx`. The "aggregate focused child's context" behaviour lives in `Program`'s idle arm (`group.find_mut(modal_id).map(|v| v.get_help_ctx())`, `program.rs:1757`), not on `Group` itself. Matches faithfully (dragging fallback preserved via `ViewState::get_help_ctx`). No `tv::view::Group::get_help_ctx` override exists in `src/view/group.rs`; Group relies on the `View` default. |
| `GetSubViewPtr` (method) | 450 | NOT-PORTED | — | — | — | Stream pointer-fixup helper used only inside `Load`. Streams dropped. No analog. |
| `HandleEvent` (method) | 451 | PORTED | OK | `tv::view::Group::handle_event` → `route_event` (impl `View::handle_event`) | 3 | Guide: focused events → pre-process/process/post-process phases (with `phase` writes); positional → topmost subview under `Event.Where`; broadcast → every subview in Z-order; subview `EventMask` gates delivery. Rust `route_event` implements all three (phase save/restore + per-child mask gate via `wants`/`blocked`, mouse-down auto-select, position translation to child-local frame, mouse-wheel broadcast-until-consumed). `owner_size` save/restore bracket added. Faithful; deviations commented. |
| `Insert` (method) | 452 | PORTED | OK | `tv::view::Group::insert(view) -> ViewId` (+ `insert_with_id`) | 3 | Guide: insert in front of all subviews; apply `ofCenterX`/`ofCenterY` centering; show if `sfVisible`; if `ofSelectable`, becomes current. Rust pushes on top (frontmost), applies centering, and the visible+selectable→become-current cascade is **deferred** (sets `currency_dirty`, settled by `settle_currency`) rather than synchronous — a documented D3 deviation (the insert-time `show()→resetCurrent` is deferred to the pump). Returns the minted `ViewId`. |
| `InsertBefore` (method) | 452 | EQUIVALENT | OK | `tv::view::Group::put_in_front_of(id, target, ctx)` | 3 | C++ `InsertBefore(P, Target)`: insert P in front of `Target` (behind all if Target nil). tvision-rs constructs-then-reorders: `insert` puts a view on top, and `put_in_front_of` realizes the relative Z-placement (`target==None` is a to-top sentinel; the send-to-bottom/`Target==nil` path is intentionally unimplemented — no consumer, documented). Same in-front-of-target semantics for the supported direction (D3, realized in the owner). |
| `Lock` (method) | 453 | NOT-PORTED | — | — | — | Increments a lock count to defer subview screen writes until `Unlock` (only meaningful with a cache buffer). No cache buffer in tvision-rs (whole-tree redraw + diff), so flicker-control locking is unnecessary. No analog. |
| `PutSubViewPtr` (method) | 453 | NOT-PORTED | — | — | — | Stream pointer-write helper used only inside `Store`. Streams dropped. No analog. |
| `Redraw` (method) | 453 | NOT-PORTED | — | — | — | C++ `Redraw`: draw subviews in Z-order, never from the cache buffer. tvision-rs has no cache buffer and redraws the **whole tree** every pump tick, diffing to the terminal; there is no per-group redraw entry point. The behavior (draw all subviews fresh) is what `Group::draw` always does. No standalone `redraw` symbol. |
| `SelectNext` (method) | 453 | EQUIVALENT | OK | `tv::view::Group::set_current(Some(id), SelectMode::Normal, ctx)` | 3 | C++ `SelectNext(forwards)`: select the next/prev `ofSelectable` subview, **ignoring validation** (unlike `FocusNext`). tvision-rs's unvalidated select is `set_current` (the focus/select side-effect applier); the validated counterpart is `focus_next`/`focus_child` (which gate on `valid(RELEASED_FOCUS)`). No single `select_next` method — `find_next` + `set_current` compose it. The guide's FocusNext-vs-SelectNext (validated vs not) distinction is preserved and documented in `set_current`. |
| `SetData` (method) | 454 | EQUIVALENT | OK | `tv::view::Group::scatter_data(values, ctx)` | 3 | C++ `SetData(Rec)`: walk subviews in reverse Z-order, write each `DataSize` from the record. tvision-rs distributes typed `FieldValue`s back to children via `set_value_ctx` (bottom-to-top, length-tolerant). D10 value protocol; heritage note cites `TGroup::setData`. |
| `SetState` (method) | 454 | PORTED | OK | `tv::view::Group::set_state` (impl `View::set_state`) | 3 | Guide: call `TView::SetState`, then for `sfActive`/`sfExposed`/`sfDragging` update **each** subview; for `sfFocused` set the current subview's `sfFocused`. Rust flips its own flag (broadcasting RECEIVED/RELEASED_FOCUS for Focused), propagates `Active`/`Dragging` to all children and `Focused` to the current child only. `sfExposed` dropped (occlusion side effect, unneeded under whole-tree redraw — documented). Matches. |
| `Store` (method) | 454 | NOT-PORTED | — | — | — | Stream serialization (`TView::Store` + `PutSubViewPtr` each subview). Streams dropped (serde-if-revived). No analog. |
| `Unlock` (method) | 454 | NOT-PORTED | — | — | — | Decrements the lock count; flushes the cache buffer to screen at zero. No cache buffer / lock count in tvision-rs. No analog (pairs with `Lock`). |
| `Valid` (method) | 454 | PORTED | OK | `tv::view::Group::valid` (impl `View::valid`) | 3 | Guide: call each subview's `Valid` in Z-order, return true only if all true; used by `Execute` to confirm modal termination. Rust: for `RELEASED_FOCUS` it defers to the current child only when it opts into validation (else true); for any other command **every** child must be valid (stops at the first invalid, so a single error box is enqueued). Threads `&mut Context` so a child can pop its own modal. Matches; the firstThat-style early stop is a deliberate, documented refinement. |

## Summary

- PORTED: 10   EQUIVALENT: 15   NOT-PORTED: 10   MISSING: 0   UNSURE: 0
- SUSPECT: 0   |   doc<3 (public): **0** (Init, Awaken, Delete, EndModal, Execute, GetData, InsertBefore, SelectNext, SetData all raised to score 3)   |   → concept: 4 (Phase, EndModal, ExecView, Execute → event-phase / modal-loop / single-event-loop guide chapters; guide pointers added for EndModal, ExecView, Execute)
- Notable findings: **No gaps and no SUSPECT items.** Every guide entry has a faithful or deliberately-mapped counterpart. The large NOT-PORTED count (11) is entirely the expected, documented infrastructure drops — the cache-buffer family (`Buffer`/`Draw`-from-buffer/`Lock`/`Unlock`/`Redraw`) replaced by whole-tree redraw + diff, the stream family (`Load`/`Store`/`GetSubViewPtr`/`PutSubViewPtr`) dropped with `TStreamable`, `DataSize` subsumed by the D10 value protocol, and `EventError` obsoleted by the single event loop. The non-obvious semantic point worth a reader's attention: `Insert`'s "becomes current" cascade is **deferred** (`currency_dirty` + `settle_currency`) rather than synchronous as in C++ — a documented D3 deviation whose load-bearing keystone is the `currency_dirty` clear in `set_current` (commented at length). The collection-walk methods (`First`/`Last`/`FirstThat`/`ForEach`) have no named symbols — they are idiomatically open-coded as `Vec`/iterator operations, which is correct but means a C++ veteran searching by name will not find them.
