Add initial support for window management

This commit is contained in:
Trevor Slocum 2024-01-25 00:06:01 -08:00
parent 7b0c2c44dc
commit 7251bcc19b
2 changed files with 167 additions and 50 deletions

View file

@ -44,11 +44,9 @@ func main() {
{
inputDemo := etk.NewFlex()
inputDemo.SetVertical(true)
inputDemo.AddChild(buffer, input)
t := etk.NewText("Input")
inputDemo.AddChild(t, buffer, input)
w.AddChildWithLabel(inputDemo, "Input")
w.AddChildWithTitle(inputDemo, "Input")
}
// Flex demo.
@ -75,11 +73,9 @@ func main() {
flexDemo := etk.NewFlex()
flexDemo.SetVertical(true)
flexDemo.AddChild(topFlex, bottomFlex)
t := etk.NewText("Flex")
flexDemo.AddChild(t, topFlex, bottomFlex)
w.AddChildWithLabel(flexDemo, "Flex")
w.AddChildWithTitle(flexDemo, "Flex")
}
etk.SetRoot(w)

205
window.go
View file

@ -2,26 +2,34 @@ package etk
import (
"image"
"image/color"
"sync"
"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
)
// Window is a widget paging mechanism. Only one widget added to a window is
// displayed at a time.
// Window displays child widgets in floating or maximized windows.
type Window struct {
*Box
allChildren []Widget
active int
labels []string
hasLabel bool
fontFace font.Face
fontMutex *sync.Mutex
frameSize int
titleSize int
titles []*Text
floating []bool
fullscreen []Widget
modified bool
}
// NewWindow returns a new Window widget.
func NewWindow() *Window {
return &Window{
Box: NewBox(),
Box: NewBox(),
fontFace: Style.TextFont,
fontMutex: Style.TextFontMutex,
frameSize: Scale(4),
titleSize: Scale(40),
}
}
@ -31,12 +39,77 @@ func (w *Window) SetRect(r image.Rectangle) {
defer w.Unlock()
w.rect = r
w.modified = true
}
for _, wgt := range w.children {
wgt.SetRect(r)
// SetFont sets the font face of the window titles.
func (w *Window) SetFont(face font.Face, mutex *sync.Mutex) {
w.Lock()
defer w.Unlock()
w.fontFace = face
w.fontMutex = mutex
for _, title := range w.titles {
title.SetFont(w.fontFace, w.fontMutex)
}
}
// SetFrameSize sets the size of the frame around each window.
func (w *Window) SetFrameSize(size int) {
w.Lock()
defer w.Unlock()
w.frameSize = size
w.modified = true
}
// SetTitleSize sets the height of the title bars.
func (w *Window) SetTitleSize(size int) {
w.Lock()
defer w.Unlock()
w.titleSize = size
w.modified = true
}
// SetFullscreen expands the specified widget to fill the netire screen, hiding
// the title bar. When -1 is provided, the currently fullscreen widget is
// restored to its a normal size.
func (w *Window) SetFullscreen(index int) {
w.Lock()
defer w.Unlock()
if index == -1 {
w.fullscreen = w.fullscreen[:0]
} else if index >= 0 && index < len(w.children) {
w.fullscreen = append(w.fullscreen[:0], w.children[index])
}
w.modified = true
}
// Children returns the children of the widget.
func (w *Window) Children() []Widget {
w.Lock()
defer w.Unlock()
if len(w.fullscreen) != 0 {
return w.fullscreen
}
return w.children
}
// Clear removes all children from the widget.
func (w *Window) Clear() {
w.Lock()
defer w.Unlock()
w.children = w.children[:0]
w.titles = w.titles[:0]
w.floating = w.floating[:0]
w.fullscreen = w.fullscreen[:0]
}
// HandleKeyboard is called when a keyboard event occurs.
func (w *Window) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
return true, nil
@ -49,45 +122,93 @@ func (w *Window) HandleMouse(cursor image.Point, pressed bool, clicked bool) (ha
// Draw draws the widget on the screen.
func (w *Window) Draw(screen *ebiten.Image) error {
// TODO draw labels
return nil
}
func (w *Window) childrenUpdated() {
if len(w.allChildren) == 0 {
w.children = nil
return
if w.modified {
if len(w.fullscreen) != 0 {
w.fullscreen[0].SetRect(w.rect)
} else {
for i, wgt := range w.children {
r := wgt.Rect()
if r.Empty() || (!w.floating[i] && !r.Eq(w.rect)) {
r = w.rect
}
if r.Max.X >= w.rect.Max.X {
r = r.Sub(image.Point{r.Max.X - w.rect.Max.X, 0})
}
if r.Max.Y >= w.rect.Max.Y {
r = r.Sub(image.Point{0, r.Max.Y - w.rect.Max.Y})
}
if r.Min.X < w.rect.Min.X {
r.Min.X = w.rect.Min.X
}
if r.Min.Y < w.rect.Min.Y {
r.Min.Y = w.rect.Min.Y
}
wgt.SetRect(r)
}
}
w.modified = false
}
w.children = []Widget{w.allChildren[w.active]}
return nil
}
// AddChild adds a child to the window.
func (w *Window) AddChild(wgt ...Widget) {
w.allChildren = append(w.allChildren, wgt...)
for _, widget := range wgt {
widget.SetRect(w.rect)
}
blankLabels := make([]string, len(wgt))
w.labels = append(w.labels, blankLabels...)
w.childrenUpdated()
}
// AddChildWithLabel adds a child to the window with the specified label.
func (w *Window) AddChildWithLabel(wgt Widget, label string) {
w.Lock()
defer w.Unlock()
wgt.SetRect(w.rect)
for _, widget := range wgt {
t := NewText("")
t.SetFont(w.fontFace, w.fontMutex)
w.allChildren = append(w.allChildren, wgt)
w.labels = append(w.labels, label)
if label != "" {
w.hasLabel = true
w.children = append(w.children, &windowWidget{NewBox(), t, widget, w})
w.titles = append(w.titles, t)
w.floating = append(w.floating, false)
}
w.childrenUpdated()
w.modified = true
}
// AddChildWithTitle adds a child to the window with the specified window title.
func (w *Window) AddChildWithTitle(wgt Widget, title string) int {
w.Lock()
defer w.Unlock()
t := NewText(title)
t.SetFont(w.fontFace, w.fontMutex)
w.children = append(w.children, &windowWidget{NewBox(), t, wgt, w})
w.titles = append(w.titles, t)
w.floating = append(w.floating, false)
w.modified = true
return len(w.children) - 1
}
type windowWidget struct {
*Box
title *Text
wgt Widget
w *Window
}
func (w *windowWidget) SetRect(r image.Rectangle) {
w.Lock()
defer w.Unlock()
w.rect = r
w.title.SetRect(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+w.w.titleSize))
w.wgt.SetRect(image.Rect(r.Min.X, r.Min.Y+w.w.titleSize, r.Max.X, r.Max.Y))
}
func (w *windowWidget) Background() color.RGBA {
return color.RGBA{0, 0, 0, 255}
}
func (w *windowWidget) Draw(screen *ebiten.Image) error {
w.title.Draw(screen)
background := w.wgt.Background()
if background.A != 0 {
screen.SubImage(w.wgt.Rect()).(*ebiten.Image).Fill(background)
}
return w.wgt.Draw(screen)
}