Place Strategy for Frame Widget
This commit is contained in:
parent
4ba563d48d
commit
0846fe22fc
29
eg/frame-place/README.md
Normal file
29
eg/frame-place/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Frame Placement Example
|
||||
|
||||
![Screenshot](screenshot.png)
|
||||
|
||||
## About
|
||||
|
||||
This demonstrates using the Place() method of the MainWindow and Frame to
|
||||
position widgets around the window. The MainWindow itself and two child frames
|
||||
(red and blue) are given the same set of buttons, placed relative to their own
|
||||
parent widget.
|
||||
|
||||
The options for frame placing are:
|
||||
|
||||
* **Point:** Absolute X,Y coordinate relative to parent
|
||||
* **Side:** binding the widget relative to a side of its parent.
|
||||
* Top, Bottom, Left and Right to anchor to a side. In Bottom and Right, the
|
||||
child widget's size is taken into account, so the right edge of the widget
|
||||
would be `Right` pixels from the parent's right edge.
|
||||
* Center and Middle options allow to anchor it to the center horizontally or
|
||||
middle vertically.
|
||||
|
||||
Click any button and the title bar will update to show the name of the
|
||||
button clicked and which parent it belonged to.
|
||||
|
||||
## Run it
|
||||
|
||||
```
|
||||
go run main.go
|
||||
```
|
144
eg/frame-place/main.go
Normal file
144
eg/frame-place/main.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
// Example script for using the Place strategy of ui.Frame.
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mw, err := ui.NewMainWindow("Frame Placement Demo | Click a Button", 800, 600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mw.SetBackground(render.White)
|
||||
|
||||
// Create a sub-frame with its own buttons packed within.
|
||||
frame := ui.NewFrame("Blue Frame")
|
||||
frame.Configure(ui.Config{
|
||||
Width: 300,
|
||||
Height: 150,
|
||||
Background: render.DarkBlue,
|
||||
BorderSize: 1,
|
||||
BorderStyle: ui.BorderSunken,
|
||||
})
|
||||
mw.Place(frame, ui.Place{
|
||||
Point: render.NewPoint(80, 80),
|
||||
})
|
||||
|
||||
// Create another frame that attaches itself to the bottom right
|
||||
// of the window.
|
||||
frame2 := ui.NewFrame("Red Frame")
|
||||
frame2.Configure(ui.Config{
|
||||
Width: 300,
|
||||
Height: 150,
|
||||
Background: render.DarkRed,
|
||||
})
|
||||
mw.Place(frame2, ui.Place{
|
||||
Right: 80,
|
||||
Bottom: 80,
|
||||
})
|
||||
|
||||
// Draw rings of buttons around various widgets. The buttons say things
|
||||
// like "Top Left", "Top Center", "Left Middle", "Center" etc. encompassing
|
||||
// all 9 side placement options.
|
||||
CreateButtons(mw, frame)
|
||||
CreateButtons(mw, frame2)
|
||||
CreateButtons(mw, mw.Frame())
|
||||
|
||||
mw.MainLoop()
|
||||
}
|
||||
|
||||
// CreateButtons creates a set of Placed buttons around all the edges and
|
||||
// center of the parent frame.
|
||||
func CreateButtons(window *ui.MainWindow, parent *ui.Frame) {
|
||||
// Draw buttons around the edges of the window.
|
||||
buttons := []struct {
|
||||
Label string
|
||||
Place ui.Place
|
||||
}{
|
||||
{
|
||||
Label: "Top Left",
|
||||
Place: ui.Place{
|
||||
Point: render.NewPoint(12, 12),
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Top Middle",
|
||||
Place: ui.Place{
|
||||
Top: 12,
|
||||
Center: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Top Right",
|
||||
Place: ui.Place{
|
||||
Top: 12,
|
||||
Right: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Left Middle",
|
||||
Place: ui.Place{
|
||||
Left: 12,
|
||||
Middle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Center",
|
||||
Place: ui.Place{
|
||||
Center: true,
|
||||
Middle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Right Middle",
|
||||
Place: ui.Place{
|
||||
Right: 12,
|
||||
Middle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Bottom Left",
|
||||
Place: ui.Place{
|
||||
Left: 12,
|
||||
Bottom: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Bottom Center",
|
||||
Place: ui.Place{
|
||||
Bottom: 12,
|
||||
Center: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Bottom Right",
|
||||
Place: ui.Place{
|
||||
Bottom: 12,
|
||||
Right: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, setting := range buttons {
|
||||
setting := setting
|
||||
|
||||
button := ui.NewButton(setting.Label, ui.NewLabel(ui.Label{
|
||||
Text: setting.Label,
|
||||
Font: render.Text{
|
||||
FontFilename: "../DejaVuSans.ttf",
|
||||
Size: 12,
|
||||
Color: render.Black,
|
||||
},
|
||||
}))
|
||||
|
||||
// When clicked, change the window title to ID this button.
|
||||
button.Handle(ui.Click, func(p render.Point) {
|
||||
window.SetTitle(parent.Name + ": " + setting.Label)
|
||||
})
|
||||
|
||||
parent.Place(button, setting.Place)
|
||||
window.Add(button)
|
||||
}
|
||||
}
|
BIN
eg/frame-place/screenshot.png
Normal file
BIN
eg/frame-place/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
6
frame.go
6
frame.go
|
@ -10,7 +10,10 @@ import (
|
|||
type Frame struct {
|
||||
Name string
|
||||
BaseWidget
|
||||
packs map[Side][]packedWidget
|
||||
|
||||
// Widget placement settings.
|
||||
packs map[Side][]packedWidget // Packed widgets
|
||||
placed []placedWidget // Placed widgets
|
||||
widgets []Widget
|
||||
}
|
||||
|
||||
|
@ -48,6 +51,7 @@ func (w *Frame) Children() []Widget {
|
|||
// Compute the size of the Frame.
|
||||
func (w *Frame) Compute(e render.Engine) {
|
||||
w.computePacked(e)
|
||||
w.computePlaced(e)
|
||||
}
|
||||
|
||||
// Present the Frame.
|
||||
|
|
97
frame_place.go
Normal file
97
frame_place.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"git.kirsle.net/go/render"
|
||||
)
|
||||
|
||||
// Place provides configuration fields for Frame.Place().
|
||||
type Place struct {
|
||||
// X and Y coordinates for explicit location of widget within its parent.
|
||||
// This placement option trumps all others.
|
||||
Point render.Point
|
||||
|
||||
// Place relative to an edge of the window. The widget will stick to the
|
||||
// edge of the window even as it resizes. Options are ignored if Point
|
||||
// is set.
|
||||
Top int
|
||||
Left int
|
||||
Right int
|
||||
Bottom int
|
||||
Center bool
|
||||
Middle bool
|
||||
}
|
||||
|
||||
// Strategy returns the placement strategy for a Place config struct.
|
||||
// Returns 'Point' if a render.Point is used (even if zero, zero)
|
||||
// Returns 'Side' if the side values are set.
|
||||
func (p Place) Strategy() string {
|
||||
if p.Top != 0 || p.Left != 0 || p.Right != 0 || p.Bottom != 0 || p.Center || p.Middle {
|
||||
return "Side"
|
||||
}
|
||||
return "Point"
|
||||
}
|
||||
|
||||
// placedWidget holds the data for a widget placed in a frame.
|
||||
type placedWidget struct {
|
||||
widget Widget
|
||||
place Place
|
||||
}
|
||||
|
||||
// Place a widget into the frame.
|
||||
func (w *Frame) Place(child Widget, config Place) {
|
||||
w.placed = append(w.placed, placedWidget{
|
||||
widget: child,
|
||||
place: config,
|
||||
})
|
||||
w.widgets = append(w.widgets, child)
|
||||
|
||||
// Adopt the child widget so it can access the Frame.
|
||||
child.SetParent(w)
|
||||
}
|
||||
|
||||
// computePlaced processes all the Place layout widgets in the Frame,
|
||||
// determining their X,Y location and whether they need to change.
|
||||
func (w *Frame) computePlaced(e render.Engine) {
|
||||
var (
|
||||
frameSize = w.BoxSize()
|
||||
)
|
||||
|
||||
for _, row := range w.placed {
|
||||
// X,Y placement takes priority.
|
||||
switch row.place.Strategy() {
|
||||
case "Point":
|
||||
row.widget.MoveTo(row.place.Point)
|
||||
case "Side":
|
||||
var moveTo render.Point
|
||||
|
||||
// Compute the initial X,Y based on Top, Left, Right, Bottom.
|
||||
if row.place.Left > 0 {
|
||||
moveTo.X = row.place.Left
|
||||
}
|
||||
if row.place.Top > 0 {
|
||||
moveTo.Y = row.place.Top
|
||||
}
|
||||
if row.place.Right > 0 {
|
||||
moveTo.X = frameSize.W - row.widget.Size().W - row.place.Right
|
||||
}
|
||||
if row.place.Bottom > 0 {
|
||||
moveTo.Y = frameSize.H - row.widget.Size().H - row.place.Bottom
|
||||
}
|
||||
|
||||
// Center and Middle aligned values override Left/Right, Top/Bottom
|
||||
// settings respectively.
|
||||
if row.place.Center {
|
||||
moveTo.X = frameSize.W - (w.Size().W / 2) - (row.widget.Size().W / 2)
|
||||
}
|
||||
if row.place.Middle {
|
||||
moveTo.Y = frameSize.H - (w.Size().H / 2) - (row.widget.Size().H / 2)
|
||||
}
|
||||
row.widget.MoveTo(moveTo)
|
||||
}
|
||||
|
||||
// If this widget itself has placed widgets, call its function too.
|
||||
if frame, ok := row.widget.(*Frame); ok {
|
||||
frame.computePlaced(e)
|
||||
}
|
||||
}
|
||||
}
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
|||
module git.kirsle.net/go/ui
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d
|
||||
github.com/veandco/go-sdl2 v0.4.1 // indirect
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect
|
||||
)
|
7
go.sum
Normal file
7
go.sum
Normal file
|
@ -0,0 +1,7 @@
|
|||
git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d h1:vErak6oVRT2dosyQzcwkjXyWQ2NRIVL8q9R8NOUTtsg=
|
||||
git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d/go.mod h1:ywZtC+zE2SpeObfkw0OvG01pWHQadsVQ4WDKOYzaejc=
|
||||
github.com/veandco/go-sdl2 v0.4.1 h1:HmSBvVmKWI8LAOeCfTTM8R33rMyPcs6U3o8n325c9Qg=
|
||||
github.com/veandco/go-sdl2 v0.4.1/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
49
image.go
49
image.go
|
@ -2,6 +2,9 @@ package ui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
@ -13,8 +16,9 @@ type ImageType string
|
|||
|
||||
// Supported image formats.
|
||||
const (
|
||||
BMP ImageType = "bmp"
|
||||
PNG = "png"
|
||||
BMP ImageType = "bmp"
|
||||
PNG = "png"
|
||||
JPEG = "jpg"
|
||||
)
|
||||
|
||||
// Image is a widget that is backed by an image file.
|
||||
|
@ -23,6 +27,7 @@ type Image struct {
|
|||
|
||||
// Configurable fields for the constructor.
|
||||
Type ImageType
|
||||
Image image.Image
|
||||
texture render.Texturer
|
||||
}
|
||||
|
||||
|
@ -48,6 +53,29 @@ func ImageFromTexture(tex render.Texturer) *Image {
|
|||
}
|
||||
}
|
||||
|
||||
// ImageFromFile creates an Image by opening a file from disk.
|
||||
func ImageFromFile(e render.Engine, filename string) (*Image, error) {
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img, err := jpeg.Decode(fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tex, err := e.StoreTexture(filename, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Image{
|
||||
Image: img,
|
||||
texture: tex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OpenImage initializes an Image with a given file name.
|
||||
//
|
||||
// The file extension is important and should be a supported ImageType.
|
||||
|
@ -58,6 +86,10 @@ func OpenImage(e render.Engine, filename string) (*Image, error) {
|
|||
w.Type = BMP
|
||||
case ".png":
|
||||
w.Type = PNG
|
||||
case ".jpg":
|
||||
w.Type = JPEG
|
||||
case ".jpeg":
|
||||
w.Type = JPEG
|
||||
default:
|
||||
return nil, fmt.Errorf("OpenImage: %s: not a supported image type", filename)
|
||||
}
|
||||
|
@ -71,6 +103,19 @@ func OpenImage(e render.Engine, filename string) (*Image, error) {
|
|||
return w, nil
|
||||
}
|
||||
|
||||
// GetRGBA returns an image.RGBA from the image data.
|
||||
func (w *Image) GetRGBA() *image.RGBA {
|
||||
var bounds = w.Image.Bounds()
|
||||
var rgba = image.NewRGBA(bounds)
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
color := w.Image.At(x, y)
|
||||
rgba.Set(x, y, color)
|
||||
}
|
||||
}
|
||||
return rgba
|
||||
}
|
||||
|
||||
// Compute the widget.
|
||||
func (w *Image) Compute(e render.Engine) {
|
||||
w.Resize(w.texture.Size())
|
||||
|
|
|
@ -16,6 +16,12 @@ var (
|
|||
FPS = 60
|
||||
)
|
||||
|
||||
// Default width and height for MainWindow.
|
||||
var (
|
||||
DefaultWidth = 640
|
||||
DefaultHeight = 480
|
||||
)
|
||||
|
||||
// MainWindow is the parent window of a UI application.
|
||||
type MainWindow struct {
|
||||
Engine render.Engine
|
||||
|
@ -27,11 +33,26 @@ type MainWindow struct {
|
|||
}
|
||||
|
||||
// NewMainWindow initializes the MainWindow. You should probably only have one
|
||||
// of these per application.
|
||||
func NewMainWindow(title string) (*MainWindow, error) {
|
||||
// of these per application. Dimensions are the width and height of the window.
|
||||
//
|
||||
// Example: NewMainWindow("Title Bar") // default 640x480 window
|
||||
// NewMainWindow("Title", 800, 600) // both required
|
||||
func NewMainWindow(title string, dimensions ...int) (*MainWindow, error) {
|
||||
var (
|
||||
width = DefaultWidth
|
||||
height = DefaultHeight
|
||||
)
|
||||
|
||||
if len(dimensions) > 0 {
|
||||
if len(dimensions) != 2 {
|
||||
return nil, fmt.Errorf("provide width and height dimensions, like NewMainWindow(title, 800, 600)")
|
||||
}
|
||||
width, height = dimensions[0], dimensions[1]
|
||||
}
|
||||
|
||||
mw := &MainWindow{
|
||||
w: 800,
|
||||
h: 600,
|
||||
w: width,
|
||||
h: height,
|
||||
supervisor: NewSupervisor(),
|
||||
loopCallbacks: []func(*event.State){},
|
||||
}
|
||||
|
@ -56,6 +77,11 @@ func NewMainWindow(title string) (*MainWindow, error) {
|
|||
return mw, nil
|
||||
}
|
||||
|
||||
// SetTitle changes the title of the window.
|
||||
func (mw *MainWindow) SetTitle(title string) {
|
||||
mw.Engine.SetTitle(title)
|
||||
}
|
||||
|
||||
// Add a child widget to the window.
|
||||
func (mw *MainWindow) Add(w Widget) {
|
||||
mw.supervisor.Add(w)
|
||||
|
@ -67,6 +93,12 @@ func (mw *MainWindow) Pack(w Widget, pack Pack) {
|
|||
mw.frame.Pack(w, pack)
|
||||
}
|
||||
|
||||
// Place a child widget into the window's default frame.
|
||||
func (mw *MainWindow) Place(w Widget, config Place) {
|
||||
mw.Add(w)
|
||||
mw.frame.Place(w, config)
|
||||
}
|
||||
|
||||
// Frame returns the window's main frame, if needed.
|
||||
func (mw *MainWindow) Frame() *Frame {
|
||||
return mw.frame
|
||||
|
|
Loading…
Reference in New Issue
Block a user