UI: Add MainWindow Widget and start an example app
* MainWindow is ideal for apps that just want a UI and don't manage their own SDL windows. * The example app will grow into a series of demos that test the UI toolkit to help fix bugs and grow features.
This commit is contained in:
parent
567b3158f1
commit
b06c52a705
7
lib/ui/eg/layout/layout.go
Normal file
7
lib/ui/eg/layout/layout.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package layout
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello world")
|
||||||
|
}
|
12
lib/ui/eg/layout/main.go
Normal file
12
lib/ui/eg/layout/main.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/lib/ui/eg/layout"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello world")
|
||||||
|
layout.main()
|
||||||
|
}
|
47
lib/ui/eg/main.go
Normal file
47
lib/ui/eg/main.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
|
"git.kirsle.net/apps/doodle/lib/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mw, err := ui.NewMainWindow("UI Toolkit Demo")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
leftFrame := ui.NewFrame("Left Frame")
|
||||||
|
leftFrame.Configure(ui.Config{
|
||||||
|
Width: 200,
|
||||||
|
BorderSize: 1,
|
||||||
|
BorderStyle: ui.BorderRaised,
|
||||||
|
Background: render.Grey,
|
||||||
|
})
|
||||||
|
mw.Pack(leftFrame, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
FillY: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
mainFrame := ui.NewFrame("Main Frame")
|
||||||
|
mainFrame.Configure(ui.Config{
|
||||||
|
Background: render.RGBA(255, 255, 255, 180),
|
||||||
|
})
|
||||||
|
mw.Pack(mainFrame, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
Expand: true,
|
||||||
|
PadX: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Hello world",
|
||||||
|
})
|
||||||
|
leftFrame.Pack(label, ui.Pack{
|
||||||
|
Anchor: ui.SE,
|
||||||
|
})
|
||||||
|
|
||||||
|
err = mw.MainLoop()
|
||||||
|
if err != nil {
|
||||||
|
panic("MainLoop:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
126
lib/ui/main_window.go
Normal file
126
lib/ui/main_window.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
|
"git.kirsle.net/apps/doodle/lib/render/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Target frames per second for the MainWindow to render at.
|
||||||
|
var (
|
||||||
|
FPS = 60
|
||||||
|
)
|
||||||
|
|
||||||
|
// MainWindow is the parent window of a UI application.
|
||||||
|
type MainWindow struct {
|
||||||
|
engine render.Engine
|
||||||
|
supervisor *Supervisor
|
||||||
|
frame *Frame
|
||||||
|
w int
|
||||||
|
h int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMainWindow initializes the MainWindow. You should probably only have one
|
||||||
|
// of these per application.
|
||||||
|
func NewMainWindow(title string) (*MainWindow, error) {
|
||||||
|
mw := &MainWindow{
|
||||||
|
w: 800,
|
||||||
|
h: 600,
|
||||||
|
supervisor: NewSupervisor(),
|
||||||
|
}
|
||||||
|
|
||||||
|
mw.engine = sdl.New(
|
||||||
|
title,
|
||||||
|
mw.w,
|
||||||
|
mw.h,
|
||||||
|
)
|
||||||
|
if err := mw.engine.Setup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a default frame to the window.
|
||||||
|
mw.frame = NewFrame("MainWindow Body")
|
||||||
|
mw.frame.SetBackground(render.RGBA(0, 153, 255, 100))
|
||||||
|
mw.Add(mw.frame)
|
||||||
|
|
||||||
|
// Compute initial window size.
|
||||||
|
mw.resized()
|
||||||
|
|
||||||
|
return mw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a child widget to the window.
|
||||||
|
func (mw *MainWindow) Add(w Widget) {
|
||||||
|
mw.supervisor.Add(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack a child widget into the window's default frame.
|
||||||
|
func (mw *MainWindow) Pack(w Widget, pack Pack) {
|
||||||
|
mw.Add(w)
|
||||||
|
mw.frame.Pack(w, pack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resized handles the window being resized.
|
||||||
|
func (mw *MainWindow) resized() {
|
||||||
|
mw.frame.Resize(render.Rect{
|
||||||
|
W: int32(mw.w),
|
||||||
|
H: int32(mw.h),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the window.
|
||||||
|
func (mw *MainWindow) Present() {
|
||||||
|
mw.supervisor.Present(mw.engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainLoop starts the main event loop and blocks until there's an error.
|
||||||
|
func (mw *MainWindow) MainLoop() error {
|
||||||
|
for true {
|
||||||
|
if err := mw.Loop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop does one loop of the UI.
|
||||||
|
func (mw *MainWindow) Loop() error {
|
||||||
|
mw.engine.Clear(render.White)
|
||||||
|
|
||||||
|
// Record how long this loop took.
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Poll for events.
|
||||||
|
ev, err := mw.engine.Poll()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("event poll error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ev.Resized.Now {
|
||||||
|
w, h := mw.engine.WindowSize()
|
||||||
|
if w != mw.w || h != mw.h {
|
||||||
|
mw.w = w
|
||||||
|
mw.h = h
|
||||||
|
mw.resized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mw.frame.Compute(mw.engine)
|
||||||
|
|
||||||
|
// Render the child widgets.
|
||||||
|
mw.supervisor.Present(mw.engine)
|
||||||
|
mw.engine.Present()
|
||||||
|
|
||||||
|
// Delay to maintain target frames per second.
|
||||||
|
var delay uint32
|
||||||
|
var targetFPS = 1000 / FPS
|
||||||
|
elapsed := time.Now().Sub(start) / time.Millisecond
|
||||||
|
if targetFPS-int(elapsed) > 0 {
|
||||||
|
delay = uint32(targetFPS - int(elapsed))
|
||||||
|
}
|
||||||
|
mw.engine.Delay(delay)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
99
lib/ui/menu.go
Normal file
99
lib/ui/menu.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Menu is a rectangle that holds menu items.
|
||||||
|
type Menu struct {
|
||||||
|
BaseWidget
|
||||||
|
Name string
|
||||||
|
|
||||||
|
body *Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMenu creates a new Menu. It is hidden by default. Usually you'll
|
||||||
|
// use it with a MenuButton or in a right-click handler.
|
||||||
|
func NewMenu(name string) *Menu {
|
||||||
|
w := &Menu{
|
||||||
|
Name: name,
|
||||||
|
body: NewFrame(name + ":Body"),
|
||||||
|
}
|
||||||
|
w.body.Configure(Config{
|
||||||
|
Width: 150,
|
||||||
|
BorderSize: 12,
|
||||||
|
BorderStyle: BorderRaised,
|
||||||
|
Background: render.Grey,
|
||||||
|
})
|
||||||
|
w.IDFunc(func() string {
|
||||||
|
return fmt.Sprintf("Menu<%s>", w.Name)
|
||||||
|
})
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the menu
|
||||||
|
func (w *Menu) Compute(e render.Engine) {
|
||||||
|
w.body.Compute(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the menu
|
||||||
|
func (w *Menu) Present(e render.Engine, p render.Point) {
|
||||||
|
w.body.Present(e, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddItem quickly adds an item to a menu.
|
||||||
|
func (w *Menu) AddItem(label string, command func()) *MenuItem {
|
||||||
|
menu := NewMenuItem(label, command)
|
||||||
|
w.Pack(menu)
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack a menu item onto the menu.
|
||||||
|
func (w *Menu) Pack(item *MenuItem) {
|
||||||
|
w.body.Pack(item, Pack{
|
||||||
|
Anchor: NE,
|
||||||
|
// Expand: true,
|
||||||
|
// Padding: 8,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuItem is an item in a Menu.
|
||||||
|
type MenuItem struct {
|
||||||
|
Button
|
||||||
|
Label string
|
||||||
|
Accelerator string
|
||||||
|
Command func()
|
||||||
|
button *Button
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMenuItem creates a new menu item.
|
||||||
|
func NewMenuItem(label string, command func()) *MenuItem {
|
||||||
|
w := &MenuItem{
|
||||||
|
Label: label,
|
||||||
|
Command: command,
|
||||||
|
}
|
||||||
|
w.IDFunc(func() string {
|
||||||
|
return fmt.Sprintf("MenuItem<%s>", w.Label)
|
||||||
|
})
|
||||||
|
|
||||||
|
font := DefaultFont
|
||||||
|
font.Color = render.White
|
||||||
|
font.PadX = 12
|
||||||
|
w.Button.child = NewLabel(Label{
|
||||||
|
Text: label,
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
w.Button.Configure(Config{
|
||||||
|
Background: render.Blue,
|
||||||
|
})
|
||||||
|
|
||||||
|
w.Button.Handle(Click, func(p render.Point) {
|
||||||
|
w.Command()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assign the button
|
||||||
|
return w
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user