| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- package winman
- import (
- "sync"
- "github.com/gdamore/tcell/v2"
- "github.com/rivo/tview"
- )
- // WindowEdge enumerates the different window edges and corners
- type WindowEdge int16
- // Different window edges
- const (
- EdgeNone WindowEdge = iota
- EdgeTop
- EdgeRight
- EdgeBottom
- EdgeLeft
- EdgeBottomRight
- EdgeBottomLeft
- )
- // WindowZTop is used with SetZ to move a window to the top
- const WindowZTop = -1
- // WindowZBottom is used with SetZ to move a window to the bottom
- const WindowZBottom = 0
- // MinWindowWidth sets the minimum width a window can have as part of a window manager
- var MinWindowWidth = 3
- // MinWindowHeight sets the minimum height a window can have as part of a window manager
- var MinWindowHeight = 3
- // inRect returns true if the given coordinates are within the window
- func inRect(wnd Window, x, y int) bool {
- return NewRect(wnd.GetRect()).Contains(x, y)
- }
- // Manager represents a Window Manager primitive
- type Manager struct {
- *tview.Box
- // The windows to be positioned.
- windows Stack
- dragOffsetX, dragOffsetY int
- draggedWindow Window
- draggedEdge WindowEdge
- sync.Mutex
- }
- // NewWindowManager returns a ready to use window manager
- func NewWindowManager() *Manager {
- wm := &Manager{
- Box: tview.NewBox(),
- }
- return wm
- }
- // NewWindow creates a new (hidden) window and adds it to this window manager
- func (wm *Manager) NewWindow() *WindowBase {
- wnd := NewWindow()
- wm.AddWindow(wnd)
- return wnd
- }
- // AddWindow adds the given window to the window manager
- func (wm *Manager) AddWindow(window Window) *Manager {
- wm.Lock()
- defer wm.Unlock()
- wm.windows.Push(window)
- return wm
- }
- // RemoveWindow removes the given window from this window manager
- func (wm *Manager) RemoveWindow(window Window) *Manager {
- wm.Lock()
- defer wm.Unlock()
- wm.windows.Remove(window)
- return wm
- }
- // Center centers the given window relative to the window manager
- func (wm *Manager) Center(window Window) *Manager {
- mx, my, mw, mh := wm.GetInnerRect()
- _, _, width, height := window.GetRect()
- x := mx + (mw-width)/2
- y := my + (mh-height)/2
- window.SetRect(x, y, width, height)
- return wm
- }
- // WindowCount returns the number of windows managed by this window manager
- func (wm *Manager) WindowCount() int {
- wm.Lock()
- defer wm.Unlock()
- return len(wm.windows)
- }
- // Window returns the window at the given z index
- func (wm *Manager) Window(z int) Window {
- wm.Lock()
- defer wm.Unlock()
- wnd, _ := wm.windows.Item(z).(Window)
- return wnd
- }
- func (wm *Manager) getZ(window Window) int {
- return wm.windows.IndexOf(window)
- }
- // GetZ returns the z index of the given window
- // returns -1 if the given window is not part of this manager
- func (wm *Manager) GetZ(window Window) int {
- wm.Lock()
- defer wm.Unlock()
- return wm.getZ(window)
- }
- func (wm *Manager) setZ(window Window, newZ int) {
- wm.windows.Move(window, newZ)
- }
- // SetZ moves the given window to the given z index
- // The special constants WindowZTop and WindowZBottom can be used
- func (wm *Manager) SetZ(window Window, newZ int) *Manager {
- wm.Lock()
- defer wm.Unlock()
- wm.setZ(window, newZ)
- return wm
- }
- // Focus is called when this primitive receives focus
- // implements tview.Primitive.Focus
- func (wm *Manager) Focus(delegate func(p tview.Primitive)) {
- wm.Lock()
- window, _ := wm.windows.Find(func(wi interface{}) bool {
- return wi.(Window).IsVisible()
- }).(Window)
- if window != nil {
- wm.Unlock()
- window.Focus(delegate)
- return
- }
- wm.Unlock()
- }
- // HasFocus returns whether or not this primitive has focus.
- // implements tview.Focusable
- func (wm *Manager) HasFocus() bool {
- wm.Lock()
- defer wm.Unlock()
- // iterate over all windows. If any has focus, then the
- // this window manager has focus.
- return nil != wm.windows.Find(func(wi interface{}) bool {
- return wi.(Window).HasFocus()
- })
- }
- // Draw draws this primitive onto the screen.
- // implements tview.Primitive.Draw
- func (wm *Manager) Draw(screen tcell.Screen) {
- wm.Box.Draw(screen)
- wm.Lock()
- defer wm.Unlock()
- // Ensure that the window with focus has the highest Z-index:
- topWindowIndex := len(wm.windows) - 1
- for i := topWindowIndex; i >= 0; i-- {
- window := wm.windows[i].(Window)
- if window.IsVisible() && window.HasFocus() {
- if i < topWindowIndex {
- wm.setZ(window, WindowZTop) // move focused window on top
- }
- break
- }
- }
- // make sure windows are not out of bounds, too small,
- // or too big to fit within the window manager:
- for _, wndItem := range wm.windows {
- window := wndItem.(Window)
- if !window.IsVisible() {
- continue
- }
- mx, my, mw, mh := wm.GetInnerRect()
- x, y, w, h := window.GetRect()
- // Avoid window overflowing on the left:
- if x < mx {
- x = mx
- }
- // Avoid window overflowing on the top:
- if y < my {
- y = my
- }
- // Fix window that is too narrow:
- if w < MinWindowWidth {
- w = MinWindowWidth
- }
- // Fix window that is too short:
- if h < MinWindowHeight {
- h = MinWindowHeight
- }
- // reduce windows that are too wide,
- // or fix size if the window is maximized
- if w > mw || window.IsMaximized() {
- w = mw
- x = mx
- }
- // reduce windows that are too tall,
- // or fix size if the window is maximized
- if h > mh || window.IsMaximized() {
- h = mh
- y = my
- }
- // Avoid window overflowing the right edge
- if x+w > mx+mw {
- x = mx + mw - w
- }
- // Avoid window overflowing the bottom edge
- if y+h > my+mh {
- y = my + mh - h
- }
- // reposition window to the new coordinates:
- window.SetRect(x, y, w, h)
- // now we can draw it
- window.Draw(screen)
- }
- }
- // MouseHandler returns the mouse handler for this primitive.
- // implements tview.Primitive.MouseHandler
- func (wm *Manager) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
- return wm.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
- // ignore mouse events out of the bounds of the window manager
- if !wm.InRect(event.Position()) {
- return false, nil
- }
- wm.Lock()
- // check if there is an active drag operation:
- if wm.draggedWindow != nil {
- switch action {
- case tview.MouseLeftUp:
- wm.draggedWindow = nil // if the button is released, stop the drag operation
- case tview.MouseMove:
- x, y := event.Position()
- wx, wy, ww, wh := wm.draggedWindow.GetRect()
- // depending if the drag operation is on the top or edges, either move the window or resize
- if wm.draggedEdge == EdgeTop && wm.draggedWindow.IsDraggable() {
- wm.draggedWindow.SetRect(x-wm.dragOffsetX, y-wm.dragOffsetY, ww, wh) // move window
- } else {
- // resize window pulling from the corresponding edge
- if wm.draggedWindow.IsResizable() {
- switch wm.draggedEdge {
- case EdgeRight:
- wm.draggedWindow.SetRect(wx, wy, x-wx+1, wh)
- case EdgeBottom:
- wm.draggedWindow.SetRect(wx, wy, ww, y-wy+1)
- case EdgeLeft:
- wm.draggedWindow.SetRect(x, wy, ww+wx-x, wh)
- case EdgeBottomRight:
- wm.draggedWindow.SetRect(wx, wy, x-wx+1, y-wy+1)
- case EdgeBottomLeft:
- wm.draggedWindow.SetRect(x, wy, ww+wx-x, y-wy+1)
- }
- }
- }
- wm.Unlock()
- return true, nil
- }
- }
- lastModal := false
- // Pass mouse events along to the window with highest Z
- // that is hit by the mouse
- // Stop if the last window was a modal.
- for i := len(wm.windows) - 1; i >= 0 && !lastModal; i-- {
- window := wm.windows[i].(Window)
- if !window.IsVisible() { // skip hidden windows
- continue
- }
- // if this is a modal window, then don't give a chance for
- // other windows to get mouse events
- lastModal = window.IsModal() // if true, will exit loop on the next iteration
- x, y := event.Position()
- if !inRect(window, x, y) {
- // skip this window since it is not hit
- continue
- }
- if action == tview.MouseLeftDown && window.HasBorder() {
- // initiate a drag operation
- if !window.HasFocus() {
- setFocus(window)
- }
- wx, wy, ww, wh := window.GetRect()
- wm.draggedEdge = EdgeNone
- switch {
- case y == wy+wh-1:
- switch {
- case x == wx:
- wm.draggedEdge = EdgeBottomLeft
- case x == wx+ww-1:
- wm.draggedEdge = EdgeBottomRight
- default:
- wm.draggedEdge = EdgeBottom
- }
- case x == wx:
- wm.draggedEdge = EdgeLeft
- case x == wx+ww-1:
- wm.draggedEdge = EdgeRight
- case y == wy:
- wm.draggedEdge = EdgeTop
- }
- if wm.draggedEdge != EdgeNone {
- // drag detected. Remember where the drag operation started
- wm.draggedWindow = window
- wm.dragOffsetX = x - wx
- wm.dragOffsetY = y - wy
- wm.Unlock()
- return true, nil
- }
- }
- wm.Unlock()
- // no drag operation detected.
- // pass the mouse events to the window itself.
- return window.MouseHandler()(action, event, setFocus)
- }
- wm.Unlock()
- return
- })
- }
- // InputHandler returns a handler which receives key events when it has focus.
- func (wm *Manager) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
- return wm.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
- wm.Lock()
- // Pass key events along to the window with highest Z that is visible and has focus
- var window Window
- for i := len(wm.windows) - 1; i >= 0; i-- {
- window = wm.windows[i].(Window)
- if window.HasFocus() {
- break
- }
- window = nil
- }
- wm.Unlock()
- if window != nil {
- inputHandler := window.InputHandler()
- if inputHandler != nil {
- inputHandler(event, setFocus)
- }
- }
- })
- }
|