ソースを参照

Place Strategy for Frame Widget

menus
Noah Petherbridge 7ヶ月前
コミット
0846fe22fc
9個のファイルの変更374行の追加7行の削除
  1. +29
    -0
      eg/frame-place/README.md
  2. +144
    -0
      eg/frame-place/main.go
  3. バイナリ
      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 ファイルの表示

@@ -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 ファイルの表示

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

バイナリ
eg/frame-place/screenshot.png ファイルの表示

変更前 変更後
幅: 555  |  高さ: 564  |  サイズ: 23 KiB

+ 5
- 1
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
- 0
frame_place.go ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

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


読み込み中…
キャンセル
保存