Noah Petherbridge
07cefb6499
* New and completed widgets: Menu, MenuButton and MenuBar. * MenuButton is a kind of Button that opens a popup Menu when clicked. * MenuBar is a container of buttons designed to be attached to the top of an application window ("File, Edit, View, Help") * Supervisor manages the popup menus with its new concept of a Modal Widget. Modal widgets take exclusive event priority for all mouse and key events. The pop-up menu is a modal window, which means you must click an option inside the menu OR clicking outside the menu will close it and eat your click event (widgets outside the modal don't receive events, but the modal itself gets an event that you've done this).
360 lines
8.1 KiB
Go
360 lines
8.1 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
|
||
|
||
// 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
|
||
titleButtons []*Button
|
||
content *Frame
|
||
|
||
// Configured title bar buttons.
|
||
buttonsEnabled int
|
||
|
||
// 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
|
||
maximized bool // toggled by MaximizeButton
|
||
origPoint render.Point // placement before a maximize
|
||
origSize render.Rect // size before a maximize
|
||
engine render.Engine // hang onto the render engine, for Maximize support.
|
||
}
|
||
|
||
// 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.DarkGrey,
|
||
InactiveTitleForeground: render.Grey,
|
||
}
|
||
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 Window: " + w.Title)
|
||
frame.Configure(Config{
|
||
Background: w.ActiveTitleBackground,
|
||
})
|
||
|
||
// Title label.
|
||
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,
|
||
})
|
||
|
||
// Window buttons.
|
||
var buttons = []struct {
|
||
If bool
|
||
Label string
|
||
Event Event
|
||
}{
|
||
{
|
||
Label: "×",
|
||
Event: CloseWindow,
|
||
},
|
||
{
|
||
Label: "+",
|
||
Event: MaximizeWindow,
|
||
},
|
||
{
|
||
Label: "_",
|
||
Event: MinimizeWindow,
|
||
},
|
||
}
|
||
w.titleButtons = make([]*Button, len(buttons))
|
||
for i, cfg := range buttons {
|
||
cfg := cfg
|
||
btn := NewButton(
|
||
fmt.Sprintf("Title Button %d for Window: %s", i, w.Title),
|
||
NewLabel(Label{
|
||
Text: cfg.Label,
|
||
Font: render.Text{
|
||
// Color: w.ActiveTitleForeground,
|
||
Size: 8,
|
||
Padding: 2,
|
||
},
|
||
}),
|
||
)
|
||
btn.SetBorderSize(0)
|
||
btn.Handle(Click, func(ed EventData) error {
|
||
w.Event(cfg.Event, ed)
|
||
return ErrStopPropagation // TODO: doesn't work :(
|
||
})
|
||
btn.Hide()
|
||
w.titleButtons[i] = btn
|
||
|
||
frame.Pack(btn, Pack{
|
||
Side: E,
|
||
})
|
||
}
|
||
|
||
return frame, label
|
||
}
|
||
|
||
// SetButtons sets the title bar buttons to show in the window.
|
||
//
|
||
// The value should be the OR of CloseButton, MaximizeButton and MinimizeButton
|
||
// that you want to be enabled.
|
||
//
|
||
// Window buttons only work if the window is managed by Supervisor and you have
|
||
// called the Supervise() method of the window.
|
||
func (w *Window) SetButtons(buttons int) {
|
||
// Show/hide each button based on the value given.
|
||
var toggle = []struct {
|
||
Value int
|
||
Index int
|
||
}{
|
||
{
|
||
Value: CloseButton,
|
||
Index: 0,
|
||
},
|
||
{
|
||
Value: MaximizeButton,
|
||
Index: 1,
|
||
},
|
||
{
|
||
Value: MinimizeButton,
|
||
Index: 2,
|
||
},
|
||
}
|
||
|
||
for _, item := range toggle {
|
||
if buttons&item.Value == item.Value {
|
||
w.titleButtons[item.Index].Show()
|
||
} else {
|
||
w.titleButtons[item.Index].Hide()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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()
|
||
|
||
s.DragStartWidget(w)
|
||
return nil
|
||
})
|
||
|
||
// Clicking anywhere in the window focuses the window.
|
||
w.Handle(MouseDown, func(ed EventData) error {
|
||
s.FocusWindow(w)
|
||
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 {
|
||
moveTo := w.dragOrigPoint
|
||
moveTo.Add(delta)
|
||
w.MoveTo(moveTo)
|
||
}
|
||
return nil
|
||
})
|
||
|
||
// Window button handlers.
|
||
w.Handle(CloseWindow, func(ed EventData) error {
|
||
w.Hide()
|
||
return nil
|
||
})
|
||
w.Handle(MaximizeWindow, func(ed EventData) error {
|
||
w.SetMaximized(!w.maximized)
|
||
return nil
|
||
})
|
||
|
||
// Add the title bar to the supervisor.
|
||
s.Add(w.titleBar)
|
||
for _, btn := range w.titleButtons {
|
||
s.Add(btn)
|
||
}
|
||
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)
|
||
}
|
||
|
||
// Maximized returns whether the window is maximized.
|
||
func (w *Window) Maximized() bool {
|
||
return w.maximized
|
||
}
|
||
|
||
// SetMaximized sets the state of the maximized window.
|
||
// Must have called Compute() once before so the window can hang on to the
|
||
// render.Engine, to calculate the size of the parent window.
|
||
func (w *Window) SetMaximized(v bool) {
|
||
w.maximized = v
|
||
|
||
if v && w.engine != nil {
|
||
w.origPoint = w.Point()
|
||
w.origSize = w.Size()
|
||
w.MoveTo(render.Origin)
|
||
w.Resize(render.NewRect(w.engine.WindowSize()))
|
||
w.Compute(w.engine)
|
||
} else if w.engine != nil {
|
||
w.MoveTo(w.origPoint)
|
||
w.Resize(w.origSize)
|
||
w.Compute(w.engine)
|
||
}
|
||
}
|
||
|
||
// Close the window, hiding it from display and calling its CloseWindow handler.
|
||
func (w *Window) Close() {
|
||
w.Hide()
|
||
w.Event(CloseWindow, EventData{})
|
||
}
|
||
|
||
// 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.engine = e // hang onto it in case of maximize
|
||
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)
|
||
}
|