Browse Source
* 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.menus
5 changed files with 291 additions and 0 deletions
@ -0,0 +1,7 @@ |
|||
package layout |
|||
|
|||
import "fmt" |
|||
|
|||
func main() { |
|||
fmt.Println("Hello world") |
|||
} |
@ -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() |
|||
} |
@ -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()) |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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