ui/window.go
Noah Petherbridge 7d9ba79cd2 Window Manager Basics, Work in Progress
* Adds Window Manager support to the Supervisor, so that Window widgets
  can be dragged by their title bar, clicked to focus, etc.
* Create a ui.Window as normal, but instead of Packing or Placing it
  into a parent container as before, you call .Supervise() and give it
  your Supervisor. The window registers itself to be managed and drawn
  by the Supervisor itself.
* Supervisor manages the focused window order using a doubly linked
  list. When a window takes focus it moves to the top of the list.
  Widgets in the active window take event priority.
* Extended DragDrop API to support holding a widget pointer in the drag
  operation.
* Changed widget event Handle functions to return an error: so that they
  could return ErrStopPropagation to prevent events going to more
  widgets once handled (for important events).

Some bugs remain around overlapping windows and event propagation.
2020-04-06 22:57:28 -07:00

232 lines
5.3 KiB
Go

package ui
import (
"fmt"
"git.kirsle.net/go/render"
)
// Window is a frame with a title bar.
type Window struct {
BaseWidget
Title string
Active bool
// Title bar colors. Sensible defaults are chosen in NewWindow but you
// may customize after the fact.
ActiveTitleBackground render.Color
ActiveTitleForeground render.Color
InactiveTitleBackground render.Color
InactiveTitleForeground render.Color
// Private widgets.
body *Frame
titleBar *Frame
titleLabel *Label
content *Frame
// Window manager controls.
dragging bool
startDragAt render.Point // cursor position when drag began
dragOrigPoint render.Point // original position of window at drag start
focused bool
managed bool // window is managed by Supervisor
}
// NewWindow creates a new window.
func NewWindow(title string) *Window {
w := &Window{
Title: title,
body: NewFrame("body:" + title),
// Default title bar colors.
ActiveTitleBackground: render.Blue,
ActiveTitleForeground: render.White,
InactiveTitleBackground: render.Grey,
InactiveTitleForeground: render.Black,
}
w.IDFunc(func() string {
return fmt.Sprintf("Window<%s %+v>",
w.Title, w.focused,
)
})
w.body.Configure(Config{
Background: render.Grey,
BorderSize: 2,
BorderStyle: BorderRaised,
})
// Title bar widget.
titleBar, titleLabel := w.setupTitleBar()
w.body.Pack(titleBar, Pack{
Side: N,
Fill: true,
})
w.titleBar = titleBar
w.titleLabel = titleLabel
// Window content frame.
content := NewFrame("content:" + title)
content.Configure(Config{
Background: render.Grey,
})
w.body.Pack(content, Pack{
Side: N,
Fill: true,
})
w.content = content
// Set up parent/child relationships
w.body.SetParent(w)
return w
}
// setupTitlebar creates the title bar frame of the window.
func (w *Window) setupTitleBar() (*Frame, *Label) {
frame := NewFrame("Titlebar for Windows: " + w.Title)
frame.Configure(Config{
Background: w.ActiveTitleBackground,
})
label := NewLabel(Label{
TextVariable: &w.Title,
Font: render.Text{
Color: w.ActiveTitleForeground,
Size: 10,
Stroke: w.ActiveTitleBackground.Darken(40),
Padding: 2,
},
})
frame.Pack(label, Pack{
Side: W,
})
return frame, label
}
// Supervise enables the window to be dragged around by its title bar by
// adding its relevant event hooks to your Supervisor.
func (w *Window) Supervise(s *Supervisor) {
// Add a click handler to the title bar to enable dragging.
w.titleBar.Handle(MouseDown, func(ed EventData) error {
w.startDragAt = ed.Point
w.dragOrigPoint = w.Point()
fmt.Printf("Clicked at %s window at %s!\n", ed.Point, w.dragOrigPoint)
s.DragStartWidget(w)
return nil
})
// Clicking anywhere in the window focuses the window.
w.Handle(MouseDown, func(ed EventData) error {
s.FocusWindow(w)
fmt.Printf("%s handles click event\n", w)
return nil
})
w.Handle(Click, func(ed EventData) error {
return nil
})
// Window as a whole receives DragMove events while being dragged.
w.Handle(DragMove, func(ed EventData) error {
// Get the delta of movement from where we began.
delta := w.startDragAt.Compare(ed.Point)
if delta != render.Origin {
fmt.Printf(" Dragged to: %s Delta: %s\n", ed.Point, delta)
moveTo := w.dragOrigPoint
moveTo.Add(delta)
w.MoveTo(moveTo)
}
return nil
})
// Add the title bar to the supervisor.
s.Add(w.titleBar)
s.Add(w)
// Add the window to the focus list of the supervisor.
s.addWindow(w)
}
// Focused returns whether the window is focused.
func (w *Window) Focused() bool {
return w.focused
}
// SetFocus sets the window's focus value. Note: if you're using the Supervisor
// to manage the windows, do NOT call this method -- window focus is managed
// by the Supervisor.
func (w *Window) SetFocus(v bool) {
w.focused = v
// Update the title bar colors.
var (
bg = w.ActiveTitleBackground
fg = w.ActiveTitleForeground
)
if !w.focused {
bg = w.InactiveTitleBackground
fg = w.InactiveTitleForeground
}
w.titleBar.SetBackground(bg)
w.titleLabel.Font.Color = fg
w.titleLabel.Font.Stroke = bg.Darken(40)
}
// Children returns the window's child widgets.
func (w *Window) Children() []Widget {
return []Widget{
w.body,
}
}
// Pack a child widget into the window's main frame.
func (w *Window) Pack(child Widget, config ...Pack) {
w.content.Pack(child, config...)
}
// Place a child widget into the window's main frame.
func (w *Window) Place(child Widget, config Place) {
w.content.Place(child, config)
}
// TitleBar returns the title bar widget.
func (w *Window) TitleBar() *Frame {
return w.titleBar
}
// Configure the widget. Color and style changes are passed down to the inner
// content frame of the window.
func (w *Window) Configure(C Config) {
w.BaseWidget.Configure(C)
w.body.Configure(C)
// Don't pass dimensions down any further than the body.
C.Width = 0
C.Height = 0
w.content.Configure(C)
}
// ConfigureTitle configures the title bar widget.
func (w *Window) ConfigureTitle(C Config) {
w.titleBar.Configure(C)
}
// Compute the window.
func (w *Window) Compute(e render.Engine) {
w.body.Compute(e)
// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}
// Present the window.
func (w *Window) Present(e render.Engine, P render.Point) {
w.body.Present(e, P)
// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, P)
}