# The ZSplit interface

A `ZSplit.T` is a parent window with overlapping child windows.

Each child has a stacking order given (conceptually) by a `z` coordinate. A pixel of the parent's screen that is in the domain of more than one child is controlled by whichever of these children is highest in the `z` coordinate. The portions of the domains of the children that extend outside the parent domain will be clipped.

`Split.Succ` enumerates the children from top to bottom.

The bottom child is called the background. An initial background can be specified when the `ZSplit` is created; usually it remains the background throughout the life of the `ZSplit`. Usually the background has the same domain as the parent, and therefore controls all pixels that are not controlled by any other child. In the unusual case that the background child has a domain different from the parent domain, there may be some parent pixels that are not controlled by any child. The `ZSplit` will ignore these pixels when asked to repaint.

The shape of a `ZSplit` is the shape of its background child (if it has no children its shape is the default shape for a `VBT`). When the preferred shape of a non-background child changes, the `ZSplit` reshapes the child to its new preferred shape, preserving its offset, which is the vector between the northwest corners of the parent and child.

    INTERFACE ZSplit;

    IMPORT VBT, Rect, Split, Point;

    TYPE
      T <: Public;
      Private <: Split.T;
      Public = Private OBJECT METHODS
        <* LL <= VBT.mu *>
        init(bg: VBT.T := NIL;
          saveBits := FALSE;
          parlim: INTEGER := -1): T
      END;

The call `v.init(...)` initializes `v` as a `ZSplit`.

It is only legal to call the `init` method for a newly-allocated `ZSplit` (as in the definition of the procedure `New` below) or from the `init` method of a subclass. This restriction applies to all the `init` methods in Trestle, although it will not be repeated for each one. init method, rules for calling

The `ZSplit` will be given the initial background child `bg` if `bg#NIL`; it will be given no children if `bg=NIL`. If `bg` is non-`NIL` it will be mapped initially. If `saveBits` is `TRUE`, the split will try to save the children's old bits when reformatting; if the children don't use them anyway, it is faster to let `saveBits` default to `FALSE`. The value of `parlim` is the minimum area of a child for which a separate thread will be forked to reshape or repaint it; if it is `-1`, it is set to an appropriate default (see the `VBTTuning` interface).

    PROCEDURE New(
        bg: VBT.T := NIL;
        saveBits := FALSE;
        parlim: INTEGER := -1)
        : T; <* LL <= VBT.mu *>

`New(...)` is equivalent to `NEW(T).init(...)`.

### Inserting children

The default `Split.Insert` call is rarely useful for a `ZSplit`: it inserts the new child at the parent's northwest corner, unmapped. `Split.AddChild` is even less useful, since it adds children as the background, which is almost certainly not what you want. The following procedures are more useful for inserting children into a `ZSplit`:

    PROCEDURE InsertAfter(
        v: T;
        pred, ch: VBT.T;
        READONLY dom: Rect.T;
        alsoMap: BOOLEAN := TRUE) RAISES {Split.NotAChild};
    <* LL.sup = VBT.mu *>

Insert `ch` as a new child of `v` with domain `dom`, and mark `v` for redisplay.

The new child is inserted immediately after (that is, below) `pred`; if `pred=NIL` the new child is inserted first (that is, on top). If the height or width of `dom` does not satisfy `ch`'s size contraints, then the height and width of the child are projected into range; its offset is preserved. This is a checked runtime error if `ch` is not detached. If `alsoMap` is `TRUE`, `ch` is mapped, otherwise it is unmapped.

It is occasionally useful to insert a new child below all existing children except the background, in which case the following procedure is handy:

    TYPE Altitude = {Top, Bot};

    PROCEDURE Insert(
        v: T;
        ch: VBT.T;
        READONLY dom: Rect.T;
        alt := Altitude.Top;
        alsoMap: BOOLEAN := TRUE); <* LL.sup = VBT.mu *>

Insert `ch` at the top if `alt = Altitude.Top`; insert `ch` just above the background if `alt = Altitude.Bot`.

That is, `Insert` is equivalent to

     IF alt = Altitude.Top THEN
           pred := NIL
         ELSE
           pred := Split.Pred(v, Split.Pred(v, NIL))
         END;
         InsertAfter(v, pred, ch, dom, alsoMap)

Finally, instead of providing the new child's domain it can be useful to provide only the northwest corner and let the child's domain be determined by its shape constraints:

    PROCEDURE InsertAt(
        v: T;
        ch: VBT.T;
        nw: Point.T;
        alt := Altitude.Top;
        alsoMap: BOOLEAN := TRUE); <* LL.sup = VBT.mu *>

Insert `ch` with its preferred shape and its northwest corner at `nw`. The `alt` and `alsoMap` parameters are interpreted as in `Insert`.

### Moving, lifting, and lowering children

    PROCEDURE Move(ch: VBT.T; READONLY dom: Rect.T);
    <* LL.sup = VBT.mu *>

Change the domain of `ch` to be `dom` and mark `ch`'s parent for redisplay.

If the height or width of `dom` do not satisfy `ch`'s size constraints, then they are projected into range, preserving the northwest corner of `dom`. The stacking order of `ch` is not changed. `Move` is a checked runtime error if `ch`'s parent is not a `ZSplit`. Note that this has nothing to do with `Split.Move`, unlike the next procedure.

    PROCEDURE Lift(ch: VBT.T; alt := Altitude.Top);
    <* LL.sup = VBT.mu *>

Lift `ch` to the top or lower it to be just above the background, depending on `alt`. `Lift` is equivalent to:

     v := VBT.Parent(ch);
     IF alt = Altitude.Top THEN
       pred := NIL
     ELSE
       pred := Split.Pred(v, Split.Pred(v, NIL))
     END;
     Split.Move(v, pred, ch)

### Mapping and unmapping children

You can unmap a child of a `ZSplit`, which reshapes the child to be empty after recording the child's shape and offset. When you later map the child, the recorded shape and offset are restored. An unmapped child is rescreened when the parent is rescreened, and its recorded shape and offset are updated when the parent is reshaped, just like the domains of the mapped children.

    PROCEDURE Unmap(ch: VBT.T); <* LL.sup = VBT.mu *>

If `ch` is mapped, unmap it and mark its parent for redisplay.

    PROCEDURE Map(ch: VBT.T); <* LL.sup = VBT.mu *>

If `ch` is unmapped, map it and mark its parent for redisplay.

    PROCEDURE IsMapped(ch: VBT.T): BOOLEAN;
    <* LL.sup = VBT.mu *>

Return `TRUE` if ch is `mapped` and `FALSE` if `ch` is unmapped.

`Map`, `Unmap`, and `IsMapped` are checked runtime errors if `ch`'s parent is not a `ZSplit`.

### Getting domains

    PROCEDURE GetDomain(ch: VBT.T): Rect.T;
    <* LL.sup = VBT.mu *>

Return the effective domain of `ch`.

The effective domain is the same as the normal domain, except (1) if the parent has been marked for redisplay, `GetDomain` returns the domain that `ch` will receive when the redisplay happens, or (2) if the domain of the parent is `Rect.Empty`, `GetDomain` returns the domain `ch` would receive if the parent were reshaped to its last non-empty domain, or (3) if the child is unmapped, `GetDomain` returns the domain the child would have if it were mapped.

`GetDomain` is a checked runtime error if the parent of `ch` is not a `ZSplit`.

    PROCEDURE GetParentDomain(v: T): Rect.T;
    <* LL.sup = VBT.mu *>

Return the last non-empty value of `v.domain`, or `Rect.Empty` if `v.domain` has always been empty.

### Moving children when the parent is reshaped

You can supply procedures to control what happens to the children when a `ZSplit` is reshaped. If you don't supply a procedure, the default behavior is as follows: the initial background child is always reshaped to have the same domain as the parent. The other children are reshaped so as to preserve their shape and their offsets (even if this makes them extend outside the parent domain). The rule is different if the parent is reshaped to `Rect.Empty`: in this case the `ZSplit` records its children's shapes and offsets and reshapes them all to `Rect.Empty`. When the `ZSplit` is later reshaped to a non-empty domain, it reshapes the initial background child to have the same domain as the parent, and restores the saved dimensions and offsets of the other children.

In the unusual case that the initial background child is deleted, subsequent background children do not automatically inherit the special reshaping behavior of the initial background child.

To override the default behavior, use `SetReshapeControl`:

    PROCEDURE SetReshapeControl(
        ch: VBT.T;
        rc: ReshapeControl); <* LL.sup = VBT.mu *>

Set the reshape control object for the child `ch` to be `rc`.

    TYPE ReshapeControl = OBJECT METHODS
      apply(ch:VBT.T; READONLY old, new, prev: Rect.T)
      : Rect.T <* LL.sup = VBT.mu.ch *>
    END;

`SetReshapeControl` arranges that whenever the `ZSplit` parent `v` of `ch` is reshaped from domain `old` to domain `new`, then if the previous domain of `ch` is `prev`, the new domain of `ch` will become `rc.apply(ch, old, new, prev)` (if this rectangle doesn't satisfy `ch`'s size constraints, its height and width will be projected into range, preserving its offset).

These methods of the `ReshapeControl` objects may be called concurrently for different children. (This is why the apply method has only a share of `VBT.mu`.) The stacking order is not changed by reshaping.

When a `ZSplit` child is replaced by `Split.Replace`, the new child inherits the old child's reshape control object.

`SetReshapeControl` is a checked runtime error if the parent of `ch` is not a `ZSplit`.

If the `ZSplit` is reshaped to `Rect.Empty`, it will reshape its children to `Rect.Empty` without calling their reshape control methods. Similarly, if the parent is subsequently reshaped to its original rectangle, it will restore the children's previous domains without calling the methods.

By default, the background is chained absolutely to the parent domain, using `Background`:

    VAR (*CONST*)
      Background: ReshapeControl;

       (* One useful reshape control method provided by this interface is
       "ChainReshape", in which some set of the child's west, east, north,
       and south edges are ``chained'' to the corresponding edges of the
       parent.  Chaining an edge means that the distance between the child
       edge and the corresponding parent edge will be preserved.  For
       example, if both the west and east edges are chained, then the child's
       horizontal extent will be inset into the parent's horizontal extent
       by fixed amounts on both sides.  For another example, suppose that
       the the east edge is chained and the west edge is not.  In this case
       the distance between the east edges of the child and parent will
       be preserved, but the west edge of the child will move so as to
       preserve the width of the child.  The north and south edges control
       the vertical extent in a similar manner.  *)

    TYPE
      Ch = {W, E, N, S};
      ChainSet = SET OF Ch;
      ChainReshapeControl = ReshapeControl OBJECT
        chains: ChainSet
      OVERRIDES
        apply := ChainedReshape
      END;

    VAR (*CONST*)
      NoChains, WChains, EChains, WEChains, NChains,
      WNChains, ENChains, WENChains, SChains,
      WSChains, ESChains, WESChains, NSChains,
      WNSChains, ENSChains, WENSChains: ChainReshapeControl;

The ``variables'' above are constants for the following reshape control objects:

     NEW(ChainReshapeControl, chains := ChainSet{}),
         NEW(ChainReshapeControl, chains := ChainSet{Ch.W}),

         ...

         NEW(ChainReshapeControl,
             chains := ChainSet{Ch.W,Ch.E,Ch.N,Ch.S})

    PROCEDURE ChainedReshape(
      self: ChainReshapeControl;
      ch: VBT.T;
      READONLY oldParentDomain, newParentDomain,
        oldChildDomain: Rect.T): Rect.T;

Return the rectangle that results from chaining each edge in `self.chains` to the corresponding edge of the parent domain, and leaving the other edges unconstrained.

If both edges in a dimension are chained, the offset and extent of the child will both vary to satisfy the chain constraints; if one edge is chained, the offset will vary and the extent will be fixed; if both edges are unchained, the offset and the extent will both be fixed.

The default behavior for the initial background child is `Background`, and the default behavior for all other children is `WNChains`.

One final reshape control method is sometimes useful:

    PROCEDURE ScaledReshape(
      self: ReshapeControl;
      ch: VBT.T;
      READONLY oldParentDomain, newParentDomain,
        oldChildDomain: Rect.T) : Rect.T;

Return the integer approximation to the rectangle that results from scaling the old child domain to occupy the same relative position of the changing parent domain.

    VAR (*CONST*) Scaled: ReshapeControl;

This ``variable'' is really a constant for the following reshape control object:

     NEW(ReshapeControl, apply := ScaledReshape)

    END ZSplit.