2019-04-10 00:35:44 +00:00
|
|
|
|
package ui
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2019-12-23 02:21:58 +00:00
|
|
|
|
"git.kirsle.net/go/render"
|
2020-06-18 01:04:18 +00:00
|
|
|
|
"git.kirsle.net/go/ui/style"
|
2019-04-10 00:35:44 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Window is a frame with a title bar.
|
|
|
|
|
type Window struct {
|
|
|
|
|
BaseWidget
|
2020-04-09 00:29:04 +00:00
|
|
|
|
Title string
|
2019-04-10 00:35:44 +00:00
|
|
|
|
|
2020-04-07 05:57:28 +00:00
|
|
|
|
// 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
|
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
|
// Private widgets.
|
2020-06-18 01:04:18 +00:00
|
|
|
|
style *style.Window
|
2020-04-09 00:29:04 +00:00
|
|
|
|
body *Frame
|
|
|
|
|
titleBar *Frame
|
|
|
|
|
titleLabel *Label
|
|
|
|
|
titleButtons []*Button
|
|
|
|
|
content *Frame
|
|
|
|
|
|
|
|
|
|
// Configured title bar buttons.
|
|
|
|
|
buttonsEnabled int
|
2020-04-07 05:57:28 +00:00
|
|
|
|
|
|
|
|
|
// 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
|
2020-04-09 00:29:04 +00:00
|
|
|
|
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.
|
2019-04-10 00:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewWindow creates a new window.
|
|
|
|
|
func NewWindow(title string) *Window {
|
|
|
|
|
w := &Window{
|
|
|
|
|
Title: title,
|
|
|
|
|
body: NewFrame("body:" + title),
|
2020-04-07 05:57:28 +00:00
|
|
|
|
|
|
|
|
|
// Default title bar colors.
|
|
|
|
|
ActiveTitleBackground: render.Blue,
|
|
|
|
|
ActiveTitleForeground: render.White,
|
2020-04-09 00:29:04 +00:00
|
|
|
|
InactiveTitleBackground: render.DarkGrey,
|
|
|
|
|
InactiveTitleForeground: render.Grey,
|
2019-04-10 00:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
w.IDFunc(func() string {
|
2020-04-07 05:57:28 +00:00
|
|
|
|
return fmt.Sprintf("Window<%s %+v>",
|
|
|
|
|
w.Title, w.focused,
|
2019-04-10 00:35:44 +00:00
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Title bar widget.
|
2020-04-07 05:57:28 +00:00
|
|
|
|
titleBar, titleLabel := w.setupTitleBar()
|
2019-04-10 00:35:44 +00:00
|
|
|
|
w.body.Pack(titleBar, Pack{
|
2019-12-29 05:47:46 +00:00
|
|
|
|
Side: N,
|
|
|
|
|
Fill: true,
|
2019-04-10 00:35:44 +00:00
|
|
|
|
})
|
|
|
|
|
w.titleBar = titleBar
|
2020-04-07 05:57:28 +00:00
|
|
|
|
w.titleLabel = titleLabel
|
2019-04-10 00:35:44 +00:00
|
|
|
|
|
|
|
|
|
// Window content frame.
|
|
|
|
|
content := NewFrame("content:" + title)
|
|
|
|
|
content.Configure(Config{
|
|
|
|
|
Background: render.Grey,
|
|
|
|
|
})
|
|
|
|
|
w.body.Pack(content, Pack{
|
2019-12-29 05:47:46 +00:00
|
|
|
|
Side: N,
|
|
|
|
|
Fill: true,
|
2019-04-10 00:35:44 +00:00
|
|
|
|
})
|
|
|
|
|
w.content = content
|
|
|
|
|
|
2019-12-29 07:56:00 +00:00
|
|
|
|
// Set up parent/child relationships
|
|
|
|
|
w.body.SetParent(w)
|
|
|
|
|
|
2020-06-18 01:04:18 +00:00
|
|
|
|
w.SetStyle(Theme.Window)
|
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
|
return w
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 01:04:18 +00:00
|
|
|
|
// SetStyle sets the window style.
|
|
|
|
|
func (w *Window) SetStyle(v *style.Window) {
|
|
|
|
|
if v == nil {
|
|
|
|
|
v = &style.DefaultWindow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.style = v
|
|
|
|
|
w.body.Configure(Config{
|
|
|
|
|
Background: w.style.ActiveBackground,
|
|
|
|
|
BorderSize: 2,
|
|
|
|
|
BorderStyle: BorderRaised,
|
|
|
|
|
})
|
|
|
|
|
if w.focused {
|
|
|
|
|
w.titleBar.SetBackground(w.style.ActiveTitleBackground)
|
|
|
|
|
w.titleLabel.Font.Color = w.style.ActiveTitleForeground
|
|
|
|
|
} else {
|
|
|
|
|
w.titleBar.SetBackground(w.style.InactiveTitleBackground)
|
|
|
|
|
w.titleLabel.Font.Color = w.style.InactiveTitleForeground
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 05:57:28 +00:00
|
|
|
|
// setupTitlebar creates the title bar frame of the window.
|
|
|
|
|
func (w *Window) setupTitleBar() (*Frame, *Label) {
|
2020-04-09 00:29:04 +00:00
|
|
|
|
frame := NewFrame("Titlebar for Window: " + w.Title)
|
2020-04-07 05:57:28 +00:00
|
|
|
|
frame.Configure(Config{
|
|
|
|
|
Background: w.ActiveTitleBackground,
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-09 00:29:04 +00:00
|
|
|
|
// Title label.
|
2020-04-07 05:57:28 +00:00
|
|
|
|
label := NewLabel(Label{
|
|
|
|
|
TextVariable: &w.Title,
|
|
|
|
|
Font: render.Text{
|
|
|
|
|
Color: w.ActiveTitleForeground,
|
2021-10-04 04:46:36 +00:00
|
|
|
|
Size: 11,
|
2020-04-07 05:57:28 +00:00
|
|
|
|
Stroke: w.ActiveTitleBackground.Darken(40),
|
|
|
|
|
Padding: 2,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
frame.Pack(label, Pack{
|
|
|
|
|
Side: W,
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-09 00:29:04 +00:00
|
|
|
|
// 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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 05:57:28 +00:00
|
|
|
|
return frame, label
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 00:29:04 +00:00
|
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 05:57:28 +00:00
|
|
|
|
// 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
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-09 00:29:04 +00:00
|
|
|
|
// 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
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-07 05:57:28 +00:00
|
|
|
|
// Add the title bar to the supervisor.
|
|
|
|
|
s.Add(w.titleBar)
|
2020-04-09 00:29:04 +00:00
|
|
|
|
for _, btn := range w.titleButtons {
|
|
|
|
|
s.Add(btn)
|
|
|
|
|
}
|
2020-04-07 05:57:28 +00:00
|
|
|
|
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 (
|
2020-06-18 01:04:18 +00:00
|
|
|
|
bg = w.style.ActiveTitleBackground
|
|
|
|
|
fg = w.style.ActiveTitleForeground
|
2020-04-07 05:57:28 +00:00
|
|
|
|
)
|
|
|
|
|
if !w.focused {
|
2020-06-18 01:04:18 +00:00
|
|
|
|
bg = w.style.InactiveTitleBackground
|
|
|
|
|
fg = w.style.InactiveTitleForeground
|
2020-04-07 05:57:28 +00:00
|
|
|
|
}
|
|
|
|
|
w.titleBar.SetBackground(bg)
|
|
|
|
|
w.titleLabel.Font.Color = fg
|
|
|
|
|
w.titleLabel.Font.Stroke = bg.Darken(40)
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 00:29:04 +00:00
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-16 02:07:06 +00:00
|
|
|
|
// Size returns the window's size (the size of its underlying body frame,
|
|
|
|
|
// including its title bar and content frames).
|
|
|
|
|
func (w *Window) Size() render.Rect {
|
|
|
|
|
return w.body.Size()
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-02 02:43:36 +00:00
|
|
|
|
// Resize the window.
|
|
|
|
|
func (w *Window) Resize(size render.Rect) {
|
|
|
|
|
w.BaseWidget.Resize(size)
|
|
|
|
|
w.body.Resize(size)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Center the window on screen by providing your screen (app window) size.
|
|
|
|
|
func (w *Window) Center(width, height int) {
|
|
|
|
|
w.MoveTo(render.Point{
|
|
|
|
|
X: (width / 2) - (w.Size().W / 2),
|
|
|
|
|
Y: (height / 2) - (w.Size().H / 2),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-04 07:50:06 +00:00
|
|
|
|
// Close the window, hiding it from display and calling its CloseWindow handler.
|
|
|
|
|
func (w *Window) Close() {
|
|
|
|
|
w.Hide()
|
|
|
|
|
w.Event(CloseWindow, EventData{})
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
|
// Children returns the window's child widgets.
|
|
|
|
|
func (w *Window) Children() []Widget {
|
|
|
|
|
return []Widget{
|
|
|
|
|
w.body,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 05:57:28 +00:00
|
|
|
|
// 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) {
|
2021-07-26 03:53:09 +00:00
|
|
|
|
w.body.Place(child, config)
|
2020-04-07 05:57:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-10 02:29:44 +00:00
|
|
|
|
// TitleBar returns the title bar widgets.
|
|
|
|
|
func (w *Window) TitleBar() (*Frame, *Label) {
|
|
|
|
|
return w.titleBar, w.titleLabel
|
2019-04-10 00:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
2021-07-26 03:53:09 +00:00
|
|
|
|
// TODO: this causes the content frame to compute its size
|
|
|
|
|
// dynamically based on Packed widgets, but if using Place on
|
|
|
|
|
// your window, the content frame doesn't know a size by which
|
|
|
|
|
// to place the child relative to (Frame has size 0x0).
|
|
|
|
|
// Commenting out these two lines causes windows to render very
|
|
|
|
|
// incorrectly (child frame content flying off the window bottom).
|
|
|
|
|
// In the meantime, Window.Place intercepts it and draws it onto
|
|
|
|
|
// the parent window directly so it works how you expect.
|
2019-04-10 00:35:44 +00:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-26 03:53:09 +00:00
|
|
|
|
// ContentFrame returns the main content Frame of this window.
|
|
|
|
|
func (w *Window) ContentFrame() *Frame {
|
|
|
|
|
return w.content
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
|
// Compute the window.
|
|
|
|
|
func (w *Window) Compute(e render.Engine) {
|
2020-04-09 00:29:04 +00:00
|
|
|
|
w.engine = e // hang onto it in case of maximize
|
2019-04-10 00:35:44 +00:00
|
|
|
|
w.body.Compute(e)
|
2020-03-10 00:13:33 +00:00
|
|
|
|
|
|
|
|
|
// Call the BaseWidget Compute in case we have subscribers.
|
|
|
|
|
w.BaseWidget.Compute(e)
|
2019-04-10 00:35:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Present the window.
|
|
|
|
|
func (w *Window) Present(e render.Engine, P render.Point) {
|
|
|
|
|
w.body.Present(e, P)
|
2020-03-10 00:13:33 +00:00
|
|
|
|
|
|
|
|
|
// Call the BaseWidget Present in case we have subscribers.
|
|
|
|
|
w.BaseWidget.Present(e, P)
|
2019-04-10 00:35:44 +00:00
|
|
|
|
}
|
2022-01-02 02:43:36 +00:00
|
|
|
|
|
|
|
|
|
// Destroy hides the window.
|
|
|
|
|
func (w *Window) Destroy() {
|
2022-04-09 21:19:20 +00:00
|
|
|
|
w.Close()
|
2022-01-02 02:43:36 +00:00
|
|
|
|
}
|