ui/window_manager.go

176 lines
4.2 KiB
Go

package ui
import (
"errors"
"fmt"
"git.kirsle.net/go/render"
)
/*
window_manager.go holds data types and Supervisor methods related to the
management of ui.Window widgets.
*/
// Window button options. OR these together in a call to Window.SetButtons().
const (
CloseButton = 0x01
// NOTICE: MaximizeButton behavior is currently buggy, window doesn't
// redraw itself at the new size properly.
MaximizeButton = 0x02
// Minimize button has no default behavior attached; you can bind it with
// window.Handle(MinimizeWindow) to set your own event handler.
MinimizeButton = 0x04
)
// FocusedWindow is a doubly-linked list of recently focused Windows, with
// the current and most-recently focused on top. TODO make not exported.
type FocusedWindow struct {
window *Window
prev *FocusedWindow
next *FocusedWindow
}
// String of the FocusedWindow returns the underlying Window's String().
func (fw FocusedWindow) String() string {
return fw.window.String()
}
// Print the structure of the linked list from top to bottom.
func (fw *FocusedWindow) Print() {
var (
node = fw
i = 0
)
for node != nil {
fmt.Printf("[%d] window=%s prev=%s next=%s\n",
i, node.window, node.prev, node.next,
)
node = node.next
i++
}
}
// addWindow installs a Window into the supervisor to be managed. It is called
// by ui.Window.Supervise() and the newly added window becomes the focused
// one by default at the top of the linked list.
func (s *Supervisor) addWindow(win *Window) {
// Record in the window that it is managed by Supervisor, useful to control
// event propagation to non-focused windows.
win.managed = true
if s.winFocus == nil {
// First window added.
s.winFocus = &FocusedWindow{
window: win,
}
s.winTop = s.winFocus
s.winBottom = s.winFocus
win.SetFocus(true)
} else {
// New window, make it the top one.
oldTop := s.winFocus
s.winFocus = &FocusedWindow{
window: win,
next: oldTop,
}
oldTop.prev = s.winFocus
oldTop.window.SetFocus(false)
win.SetFocus(true)
}
}
// FocusWindow brings the given window to the top of the supervisor's focus.
//
// The window must have previously been added to the supervisor's Window Manager
// by calling the Supervise() method of the window.
func (s *Supervisor) FocusWindow(win *Window) error {
if s.winFocus == nil {
return errors.New("no windows managed by supervisor")
}
// If the top window is already the target, return.
if s.winFocus.window == win {
return nil
}
// Find the window in the linked list.
var (
item = s.winFocus // item as we iterate the list
oldTop = s.winFocus // original first item in the list
target *FocusedWindow // identified target window to raise
newBottom *FocusedWindow // if the target was the bottom, this is new bottom
i = 0
)
for item != nil {
if item.window == win {
// Found it!
target = item
// Is it the last window in the list? Record the new bottom node.
if item.next == nil && item.prev != nil {
newBottom = item.prev
}
// Remove it from its position in the linked list. Join its
// previous and next nodes to bridge the gap.
if item.next != nil {
item.next.prev = item.prev
}
if item.prev != nil {
item.prev.next = item.next
}
break
}
item = item.next
i++
}
// Found it?
if target != nil {
// Put the target at the top of the list, pointing to the old top.
target.next = oldTop
target.prev = nil
oldTop.prev = target
s.winFocus = target
// Fix the top and bottom pointers.
s.winTop = s.winFocus
if newBottom != nil {
s.winBottom = newBottom
}
// Toggle the focus states.
oldTop.window.SetFocus(false)
target.window.SetFocus(true)
}
return nil
}
// IsPointInWindow returns whether the given Point overlaps with a window managed
// by the Supervisor.
func (s *Supervisor) IsPointInWindow(point render.Point) bool {
node := s.winFocus
for node != nil {
if point.Inside(AbsoluteRect(node.window)) && !node.window.hidden {
return true
}
node = node.next
}
return false
}
// presentWindows draws the windows from bottom to top.
func (s *Supervisor) presentWindows(e render.Engine) {
item := s.winBottom
for item != nil {
item.window.Compute(e)
item.window.Present(e, item.window.Point())
item = item.prev
}
}