Ver código fonte

Place Strategy for Frame Widget

menus
Noah Petherbridge 8 meses atrás
pai
commit
0846fe22fc
9 arquivos alterados com 374 adições e 7 exclusões
  1. +29
    -0
      eg/frame-place/README.md
  2. +144
    -0
      eg/frame-place/main.go
  3. BIN
      eg/frame-place/screenshot.png
  4. +5
    -1
      frame.go
  5. +97
    -0
      frame_place.go
  6. +9
    -0
      go.mod
  7. +7
    -0
      go.sum
  8. +47
    -2
      image.go
  9. +36
    -4
      main_window.go

+ 29
- 0
eg/frame-place/README.md Ver arquivo

@@ -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
- 0
eg/frame-place/main.go Ver arquivo

@@ -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 Ver arquivo

Antes Depois
Largura: 555  |  Altura: 564  |  Tamanho: 23 KiB

+ 5
- 1
frame.go Ver arquivo

@@ -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
- 0
frame_place.go Ver arquivo

@@ -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
- 0
go.mod Ver arquivo

@@ -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
- 0
go.sum Ver arquivo

@@ -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=

+ 47
- 2
image.go Ver arquivo

@@ -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())


+ 36
- 4
main_window.go Ver arquivo

@@ -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


Carregando…
Cancelar
Salvar