// Package ui is the widget framework the editor is built from: a desktop // holding movable windows, a menu bar with drop-down menus, modal dialogs, // buttons, input lines and list boxes — the Turbo Vision furniture, drawn on // tcell. // // Nothing here knows about Go source, buffers or language servers. Widgets // draw through a clipping Painter and answer key and mouse events; what they // mean is decided by the package above. package ui // Rect is a rectangle of terminal cells: its top-left corner and its size. // // A rectangle with a zero or negative width or height is empty and covers no // cell at all, which is what an off-screen or fully clipped widget reduces to. type Rect struct { X, Y, W, H int } // Right returns the column just past the rectangle's right edge. func (r Rect) Right() int { return r.X + r.W } // Bottom returns the row just past the rectangle's bottom edge. func (r Rect) Bottom() int { return r.Y + r.H } // IsEmpty reports whether the rectangle covers no cell. func (r Rect) IsEmpty() bool { return r.W <= 0 || r.H <= 0 } // Contains reports whether the cell at (x, y) is inside the rectangle. func (r Rect) Contains(x, y int) bool { return x >= r.X && x < r.Right() && y >= r.Y && y < r.Bottom() } // Intersect returns the rectangle covered by both r and other, which is empty // when they do not overlap. func (r Rect) Intersect(other Rect) Rect { x := max(r.X, other.X) y := max(r.Y, other.Y) right := min(r.Right(), other.Right()) bottom := min(r.Bottom(), other.Bottom()) if right <= x || bottom <= y { return Rect{X: x, Y: y} } return Rect{X: x, Y: y, W: right - x, H: bottom - y} } // Inset returns the rectangle shrunk by dx on the left and right and by dy on // the top and bottom. It is how a widget moves inside its own frame. // // body := window.Bounds().Inset(1, 1) func (r Rect) Inset(dx, dy int) Rect { return Rect{X: r.X + dx, Y: r.Y + dy, W: r.W - 2*dx, H: r.H - 2*dy} } // Move returns the rectangle shifted by (dx, dy), keeping its size. func (r Rect) Move(dx, dy int) Rect { return Rect{X: r.X + dx, Y: r.Y + dy, W: r.W, H: r.H} } // WithSize returns the rectangle with a different size and the same corner. func (r Rect) WithSize(w, h int) Rect { return Rect{X: r.X, Y: r.Y, W: w, H: h} } // CenteredIn returns a rectangle of the same size placed in the middle of // outer, which is where a dialog opens. func (r Rect) CenteredIn(outer Rect) Rect { return Rect{ X: outer.X + (outer.W-r.W)/2, Y: outer.Y + (outer.H-r.H)/2, W: r.W, H: r.H, } } // ClampInto returns the rectangle moved — never resized — so that it lies // inside outer, as far as its size allows. A window dragged past the edge of // the screen comes back this way. func (r Rect) ClampInto(outer Rect) Rect { out := r out.X = min(out.X, outer.Right()-out.W) out.Y = min(out.Y, outer.Bottom()-out.H) out.X = max(out.X, outer.X) out.Y = max(out.Y, outer.Y) return out }