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