Some basic UI controls.
This commit is contained in:
parent
a761b21775
commit
8157d8b3d8
133
ui/checkbox.go
Normal file
133
ui/checkbox.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckboxValueChangedFn func(bool)
|
||||||
|
|
||||||
|
func createOnBitmap(fill, stroke color.Color) *allegro5.Bitmap {
|
||||||
|
var sz = float64(checkboxSize)
|
||||||
|
on := image.NewRGBA(image.Rect(0, 0, checkboxSize, checkboxSize))
|
||||||
|
|
||||||
|
gc := draw2dimg.NewGraphicContext(on)
|
||||||
|
gc.SetFillColor(color.Transparent)
|
||||||
|
gc.Clear()
|
||||||
|
gc.SetFillColor(fill)
|
||||||
|
draw2dkit.RoundedRectangle(gc, 0, 0, sz, sz, 3, 3)
|
||||||
|
gc.Fill()
|
||||||
|
|
||||||
|
gc.SetStrokeColor(stroke)
|
||||||
|
gc.SetLineWidth(2)
|
||||||
|
gc.MoveTo(2.5, 5.5)
|
||||||
|
gc.LineTo(5.5, 8.5)
|
||||||
|
gc.LineTo(10, 3.5)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
bmp, err := allegro5.NewBitmapFromImage(on, false)
|
||||||
|
if nil != err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOffBitmap(fill, stroke color.Color) *allegro5.Bitmap {
|
||||||
|
var sz = float64(checkboxSize)
|
||||||
|
off := image.NewRGBA(image.Rect(0, 0, checkboxSize, checkboxSize))
|
||||||
|
|
||||||
|
gc := draw2dimg.NewGraphicContext(off)
|
||||||
|
gc.SetFillColor(color.Transparent)
|
||||||
|
gc.Clear()
|
||||||
|
gc.SetFillColor(stroke)
|
||||||
|
draw2dkit.RoundedRectangle(gc, 0, 0, sz, sz, 4, 4)
|
||||||
|
gc.Fill()
|
||||||
|
gc.SetFillColor(fill)
|
||||||
|
draw2dkit.RoundedRectangle(gc, 1, 1, sz-1, sz-1, 3, 3)
|
||||||
|
gc.Fill()
|
||||||
|
|
||||||
|
bmp, err := allegro5.NewBitmapFromImage(off, false)
|
||||||
|
if nil != err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
type Checkbox struct {
|
||||||
|
ControlBase
|
||||||
|
Value bool
|
||||||
|
Text string
|
||||||
|
OnChanged CheckboxValueChangedFn
|
||||||
|
on *allegro5.Bitmap
|
||||||
|
off *allegro5.Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) Created(ctx Context, p Container) error {
|
||||||
|
var err = c.ControlBase.Created(ctx, p)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var plt = ctx.Palette()
|
||||||
|
c.on = createOnBitmap(plt.Primary(), plt.White())
|
||||||
|
c.off = createOffBitmap(plt.White(), plt.Black())
|
||||||
|
if nil == c.on || nil == c.off {
|
||||||
|
return fmt.Errorf("error creating checkboxes")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) Handle(ctx Context, ev allegro5.Event) {
|
||||||
|
var pressed = c.IsPressed
|
||||||
|
c.ControlBase.Handle(ctx, ev)
|
||||||
|
switch ev.(type) {
|
||||||
|
case *allegro5.MouseButtonUpEvent:
|
||||||
|
if !c.Disabled && pressed && c.IsOver {
|
||||||
|
c.Value = !c.Value
|
||||||
|
var onChanged = c.OnChanged
|
||||||
|
if nil != onChanged {
|
||||||
|
onChanged(c.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) DesiredSize(ctx Context) geom.PointF {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var w = fnt.TextWidth(c.Text)
|
||||||
|
return geom.PtF(float64(w+2*leftMargin+checkboxSize), 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) box() *allegro5.Bitmap {
|
||||||
|
if c.Value {
|
||||||
|
return c.on
|
||||||
|
}
|
||||||
|
return c.off
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = c.Bounds.Min.To32()
|
||||||
|
min = geom.PointF32{X: min.X + leftMargin, Y: min.Y + topMargin}
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
|
||||||
|
var _, textY, _, textH = fnt.TextDims(c.Text)
|
||||||
|
fnt.Draw(min.X+leftMargin+checkboxSize, min.Y-textY, ctx.Palette().Black(), allegro5.AlignLeft, c.Text)
|
||||||
|
var checkboxTop = min.Y + textH - checkboxSize
|
||||||
|
if c.Disabled {
|
||||||
|
var disabled = ctx.Palette().Disabled()
|
||||||
|
c.box().DrawOptions(min.X, checkboxTop, allegro5.DrawOptions{Tint: &disabled})
|
||||||
|
} else {
|
||||||
|
c.box().Draw(min.X, checkboxTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ControlBase.Render(ctx)
|
||||||
|
}
|
283
ui/colors.go
Normal file
283
ui/colors.go
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Red50 = &color.RGBA{R: 0xff, G: 0xeb, B: 0xee, A: 0xff}
|
||||||
|
Red100 = &color.RGBA{R: 0xff, G: 0xcd, B: 0xd2, A: 0xff}
|
||||||
|
Red200 = &color.RGBA{R: 0xef, G: 0x9a, B: 0x9a, A: 0xff}
|
||||||
|
Red300 = &color.RGBA{R: 0xe5, G: 0x73, B: 0x73, A: 0xff}
|
||||||
|
Red400 = &color.RGBA{R: 0xef, G: 0x53, B: 0x50, A: 0xff}
|
||||||
|
Red500 = &color.RGBA{R: 0xf4, G: 0x43, B: 0x36, A: 0xff}
|
||||||
|
Red600 = &color.RGBA{R: 0xe5, G: 0x39, B: 0x35, A: 0xff}
|
||||||
|
Red700 = &color.RGBA{R: 0xd3, G: 0x2f, B: 0x2f, A: 0xff}
|
||||||
|
Red800 = &color.RGBA{R: 0xc6, G: 0x28, B: 0x28, A: 0xff}
|
||||||
|
Red900 = &color.RGBA{R: 0xb7, G: 0x1c, B: 0x1c, A: 0xff}
|
||||||
|
RedA100 = &color.RGBA{R: 0xff, G: 0x8a, B: 0x80, A: 0xff}
|
||||||
|
RedA200 = &color.RGBA{R: 0xff, G: 0x52, B: 0x52, A: 0xff}
|
||||||
|
RedA400 = &color.RGBA{R: 0xff, G: 0x17, B: 0x44, A: 0xff}
|
||||||
|
RedA700 = &color.RGBA{R: 0xd5, G: 0x00, B: 0x00, A: 0xff}
|
||||||
|
|
||||||
|
Pink50 = &color.RGBA{R: 0xfc, G: 0xe4, B: 0xec, A: 0xff}
|
||||||
|
Pink100 = &color.RGBA{R: 0xf8, G: 0xbb, B: 0xd0, A: 0xff}
|
||||||
|
Pink200 = &color.RGBA{R: 0xf4, G: 0x8f, B: 0xb1, A: 0xff}
|
||||||
|
Pink300 = &color.RGBA{R: 0xf0, G: 0x62, B: 0x92, A: 0xff}
|
||||||
|
Pink400 = &color.RGBA{R: 0xec, G: 0x40, B: 0x7a, A: 0xff}
|
||||||
|
Pink500 = &color.RGBA{R: 0xe9, G: 0x1e, B: 0x63, A: 0xff}
|
||||||
|
Pink600 = &color.RGBA{R: 0xd8, G: 0x1b, B: 0x60, A: 0xff}
|
||||||
|
Pink700 = &color.RGBA{R: 0xc2, G: 0x18, B: 0x5b, A: 0xff}
|
||||||
|
Pink800 = &color.RGBA{R: 0xad, G: 0x14, B: 0x57, A: 0xff}
|
||||||
|
Pink900 = &color.RGBA{R: 0x88, G: 0x0e, B: 0x4f, A: 0xff}
|
||||||
|
PinkA100 = &color.RGBA{R: 0xff, G: 0x80, B: 0xab, A: 0xff}
|
||||||
|
PinkA200 = &color.RGBA{R: 0xff, G: 0x40, B: 0x81, A: 0xff}
|
||||||
|
PinkA400 = &color.RGBA{R: 0xf5, G: 0x00, B: 0x57, A: 0xff}
|
||||||
|
PinkA700 = &color.RGBA{R: 0xc5, G: 0x11, B: 0x62, A: 0xff}
|
||||||
|
|
||||||
|
Purple50 = &color.RGBA{R: 0xf3, G: 0xe5, B: 0xf5, A: 0xff}
|
||||||
|
Purple100 = &color.RGBA{R: 0xe1, G: 0xbe, B: 0xe7, A: 0xff}
|
||||||
|
Purple200 = &color.RGBA{R: 0xce, G: 0x93, B: 0xd8, A: 0xff}
|
||||||
|
Purple300 = &color.RGBA{R: 0xba, G: 0x68, B: 0xc8, A: 0xff}
|
||||||
|
Purple400 = &color.RGBA{R: 0xab, G: 0x47, B: 0xbc, A: 0xff}
|
||||||
|
Purple500 = &color.RGBA{R: 0x9c, G: 0x27, B: 0xb0, A: 0xff}
|
||||||
|
Purple600 = &color.RGBA{R: 0x8e, G: 0x24, B: 0xaa, A: 0xff}
|
||||||
|
Purple700 = &color.RGBA{R: 0x7b, G: 0x1f, B: 0xa2, A: 0xff}
|
||||||
|
Purple800 = &color.RGBA{R: 0x6a, G: 0x1b, B: 0x9a, A: 0xff}
|
||||||
|
Purple900 = &color.RGBA{R: 0x4a, G: 0x14, B: 0x8c, A: 0xff}
|
||||||
|
PurpleA100 = &color.RGBA{R: 0xea, G: 0x80, B: 0xfc, A: 0xff}
|
||||||
|
PurpleA200 = &color.RGBA{R: 0xe0, G: 0x40, B: 0xfb, A: 0xff}
|
||||||
|
PurpleA400 = &color.RGBA{R: 0xd5, G: 0x00, B: 0xf9, A: 0xff}
|
||||||
|
PurpleA700 = &color.RGBA{R: 0xaa, G: 0x00, B: 0xff, A: 0xff}
|
||||||
|
|
||||||
|
DeepPurple50 = &color.RGBA{R: 0xed, G: 0xe7, B: 0xf6, A: 0xff}
|
||||||
|
DeepPurple100 = &color.RGBA{R: 0xd1, G: 0xc4, B: 0xe9, A: 0xff}
|
||||||
|
DeepPurple200 = &color.RGBA{R: 0xb3, G: 0x9d, B: 0xdb, A: 0xff}
|
||||||
|
DeepPurple300 = &color.RGBA{R: 0x95, G: 0x75, B: 0xcd, A: 0xff}
|
||||||
|
DeepPurple400 = &color.RGBA{R: 0x7e, G: 0x57, B: 0xc2, A: 0xff}
|
||||||
|
DeepPurple500 = &color.RGBA{R: 0x67, G: 0x3a, B: 0xb7, A: 0xff}
|
||||||
|
DeepPurple600 = &color.RGBA{R: 0x5e, G: 0x35, B: 0xb1, A: 0xff}
|
||||||
|
DeepPurple700 = &color.RGBA{R: 0x51, G: 0x2d, B: 0xa8, A: 0xff}
|
||||||
|
DeepPurple800 = &color.RGBA{R: 0x45, G: 0x27, B: 0xa0, A: 0xff}
|
||||||
|
DeepPurple900 = &color.RGBA{R: 0x31, G: 0x1b, B: 0x92, A: 0xff}
|
||||||
|
DeepPurpleA100 = &color.RGBA{R: 0xb3, G: 0x88, B: 0xff, A: 0xff}
|
||||||
|
DeepPurpleA200 = &color.RGBA{R: 0x7c, G: 0x4d, B: 0xff, A: 0xff}
|
||||||
|
DeepPurpleA400 = &color.RGBA{R: 0x65, G: 0x1f, B: 0xff, A: 0xff}
|
||||||
|
DeepPurpleA700 = &color.RGBA{R: 0x62, G: 0x00, B: 0xea, A: 0xff}
|
||||||
|
|
||||||
|
Indigo50 = &color.RGBA{R: 0xe8, G: 0xea, B: 0xf6, A: 0xff}
|
||||||
|
Indigo100 = &color.RGBA{R: 0xc5, G: 0xca, B: 0xe9, A: 0xff}
|
||||||
|
Indigo200 = &color.RGBA{R: 0x9f, G: 0xa8, B: 0xda, A: 0xff}
|
||||||
|
Indigo300 = &color.RGBA{R: 0x79, G: 0x86, B: 0xcb, A: 0xff}
|
||||||
|
Indigo400 = &color.RGBA{R: 0x5c, G: 0x6b, B: 0xc0, A: 0xff}
|
||||||
|
Indigo500 = &color.RGBA{R: 0x3f, G: 0x51, B: 0xb5, A: 0xff}
|
||||||
|
Indigo600 = &color.RGBA{R: 0x39, G: 0x49, B: 0xab, A: 0xff}
|
||||||
|
Indigo700 = &color.RGBA{R: 0x30, G: 0x3f, B: 0x9f, A: 0xff}
|
||||||
|
Indigo800 = &color.RGBA{R: 0x28, G: 0x35, B: 0x93, A: 0xff}
|
||||||
|
Indigo900 = &color.RGBA{R: 0x1a, G: 0x23, B: 0x7e, A: 0xff}
|
||||||
|
IndigoA100 = &color.RGBA{R: 0x8c, G: 0x9e, B: 0xff, A: 0xff}
|
||||||
|
IndigoA200 = &color.RGBA{R: 0x53, G: 0x6d, B: 0xfe, A: 0xff}
|
||||||
|
IndigoA400 = &color.RGBA{R: 0x3d, G: 0x5a, B: 0xfe, A: 0xff}
|
||||||
|
IndigoA700 = &color.RGBA{R: 0x30, G: 0x4f, B: 0xfe, A: 0xff}
|
||||||
|
|
||||||
|
Blue50 = &color.RGBA{R: 0xe3, G: 0xf2, B: 0xfd, A: 0xff}
|
||||||
|
Blue100 = &color.RGBA{R: 0xbb, G: 0xde, B: 0xfb, A: 0xff}
|
||||||
|
Blue200 = &color.RGBA{R: 0x90, G: 0xca, B: 0xf9, A: 0xff}
|
||||||
|
Blue300 = &color.RGBA{R: 0x64, G: 0xb5, B: 0xf6, A: 0xff}
|
||||||
|
Blue400 = &color.RGBA{R: 0x42, G: 0xa5, B: 0xf5, A: 0xff}
|
||||||
|
Blue500 = &color.RGBA{R: 0x21, G: 0x96, B: 0xf3, A: 0xff}
|
||||||
|
Blue600 = &color.RGBA{R: 0x1e, G: 0x88, B: 0xe5, A: 0xff}
|
||||||
|
Blue700 = &color.RGBA{R: 0x19, G: 0x76, B: 0xd2, A: 0xff}
|
||||||
|
Blue800 = &color.RGBA{R: 0x15, G: 0x65, B: 0xc0, A: 0xff}
|
||||||
|
Blue900 = &color.RGBA{R: 0x0d, G: 0x47, B: 0xa1, A: 0xff}
|
||||||
|
BlueA100 = &color.RGBA{R: 0x82, G: 0xb1, B: 0xff, A: 0xff}
|
||||||
|
BlueA200 = &color.RGBA{R: 0x44, G: 0x8a, B: 0xff, A: 0xff}
|
||||||
|
BlueA400 = &color.RGBA{R: 0x29, G: 0x79, B: 0xff, A: 0xff}
|
||||||
|
BlueA700 = &color.RGBA{R: 0x29, G: 0x62, B: 0xff, A: 0xff}
|
||||||
|
|
||||||
|
LightBlue50 = &color.RGBA{R: 0xe1, G: 0xf5, B: 0xfe, A: 0xff}
|
||||||
|
LightBlue100 = &color.RGBA{R: 0xb3, G: 0xe5, B: 0xfc, A: 0xff}
|
||||||
|
LightBlue200 = &color.RGBA{R: 0x81, G: 0xd4, B: 0xfa, A: 0xff}
|
||||||
|
LightBlue300 = &color.RGBA{R: 0x4f, G: 0xc3, B: 0xf7, A: 0xff}
|
||||||
|
LightBlue400 = &color.RGBA{R: 0x29, G: 0xb6, B: 0xf6, A: 0xff}
|
||||||
|
LightBlue500 = &color.RGBA{R: 0x03, G: 0xa9, B: 0xf4, A: 0xff}
|
||||||
|
LightBlue600 = &color.RGBA{R: 0x03, G: 0x9b, B: 0xe5, A: 0xff}
|
||||||
|
LightBlue700 = &color.RGBA{R: 0x02, G: 0x88, B: 0xd1, A: 0xff}
|
||||||
|
LightBlue800 = &color.RGBA{R: 0x02, G: 0x77, B: 0xbd, A: 0xff}
|
||||||
|
LightBlue900 = &color.RGBA{R: 0x01, G: 0x57, B: 0x9b, A: 0xff}
|
||||||
|
LightBlueA100 = &color.RGBA{R: 0x80, G: 0xd8, B: 0xff, A: 0xff}
|
||||||
|
LightBlueA200 = &color.RGBA{R: 0x40, G: 0xc4, B: 0xff, A: 0xff}
|
||||||
|
LightBlueA400 = &color.RGBA{R: 0x00, G: 0xb0, B: 0xff, A: 0xff}
|
||||||
|
LightBlueA700 = &color.RGBA{R: 0x00, G: 0x91, B: 0xea, A: 0xff}
|
||||||
|
|
||||||
|
Cyan50 = &color.RGBA{R: 0xe0, G: 0xf7, B: 0xfa, A: 0xff}
|
||||||
|
Cyan100 = &color.RGBA{R: 0xb2, G: 0xeb, B: 0xf2, A: 0xff}
|
||||||
|
Cyan200 = &color.RGBA{R: 0x80, G: 0xde, B: 0xea, A: 0xff}
|
||||||
|
Cyan300 = &color.RGBA{R: 0x4d, G: 0xd0, B: 0xe1, A: 0xff}
|
||||||
|
Cyan400 = &color.RGBA{R: 0x26, G: 0xc6, B: 0xda, A: 0xff}
|
||||||
|
Cyan500 = &color.RGBA{R: 0x00, G: 0xbc, B: 0xd4, A: 0xff}
|
||||||
|
Cyan600 = &color.RGBA{R: 0x00, G: 0xac, B: 0xc1, A: 0xff}
|
||||||
|
Cyan700 = &color.RGBA{R: 0x00, G: 0x97, B: 0xa7, A: 0xff}
|
||||||
|
Cyan800 = &color.RGBA{R: 0x00, G: 0x83, B: 0x8f, A: 0xff}
|
||||||
|
Cyan900 = &color.RGBA{R: 0x00, G: 0x60, B: 0x64, A: 0xff}
|
||||||
|
CyanA100 = &color.RGBA{R: 0x84, G: 0xff, B: 0xff, A: 0xff}
|
||||||
|
CyanA200 = &color.RGBA{R: 0x18, G: 0xff, B: 0xff, A: 0xff}
|
||||||
|
CyanA400 = &color.RGBA{R: 0x00, G: 0xe5, B: 0xff, A: 0xff}
|
||||||
|
CyanA700 = &color.RGBA{R: 0x00, G: 0xb8, B: 0xd4, A: 0xff}
|
||||||
|
|
||||||
|
Teal50 = &color.RGBA{R: 0xe0, G: 0xf2, B: 0xf1, A: 0xff}
|
||||||
|
Teal100 = &color.RGBA{R: 0xb2, G: 0xdf, B: 0xdb, A: 0xff}
|
||||||
|
Teal200 = &color.RGBA{R: 0x80, G: 0xcb, B: 0xc4, A: 0xff}
|
||||||
|
Teal300 = &color.RGBA{R: 0x4d, G: 0xb6, B: 0xac, A: 0xff}
|
||||||
|
Teal400 = &color.RGBA{R: 0x26, G: 0xa6, B: 0x9a, A: 0xff}
|
||||||
|
Teal500 = &color.RGBA{R: 0x00, G: 0x96, B: 0x88, A: 0xff}
|
||||||
|
Teal600 = &color.RGBA{R: 0x00, G: 0x89, B: 0x7b, A: 0xff}
|
||||||
|
Teal700 = &color.RGBA{R: 0x00, G: 0x79, B: 0x6b, A: 0xff}
|
||||||
|
Teal800 = &color.RGBA{R: 0x00, G: 0x69, B: 0x5c, A: 0xff}
|
||||||
|
Teal900 = &color.RGBA{R: 0x00, G: 0x4d, B: 0x40, A: 0xff}
|
||||||
|
TealA100 = &color.RGBA{R: 0xa7, G: 0xff, B: 0xeb, A: 0xff}
|
||||||
|
TealA200 = &color.RGBA{R: 0x64, G: 0xff, B: 0xda, A: 0xff}
|
||||||
|
TealA400 = &color.RGBA{R: 0x1d, G: 0xe9, B: 0xb6, A: 0xff}
|
||||||
|
TealA700 = &color.RGBA{R: 0x00, G: 0xbf, B: 0xa5, A: 0xff}
|
||||||
|
|
||||||
|
Green50 = &color.RGBA{R: 0xe8, G: 0xf5, B: 0xe9, A: 0xff}
|
||||||
|
Green100 = &color.RGBA{R: 0xc8, G: 0xe6, B: 0xc9, A: 0xff}
|
||||||
|
Green200 = &color.RGBA{R: 0xa5, G: 0xd6, B: 0xa7, A: 0xff}
|
||||||
|
Green300 = &color.RGBA{R: 0x81, G: 0xc7, B: 0x84, A: 0xff}
|
||||||
|
Green400 = &color.RGBA{R: 0x66, G: 0xbb, B: 0x6a, A: 0xff}
|
||||||
|
Green500 = &color.RGBA{R: 0x4c, G: 0xaf, B: 0x50, A: 0xff}
|
||||||
|
Green600 = &color.RGBA{R: 0x43, G: 0xa0, B: 0x47, A: 0xff}
|
||||||
|
Green700 = &color.RGBA{R: 0x38, G: 0x8e, B: 0x3c, A: 0xff}
|
||||||
|
Green800 = &color.RGBA{R: 0x2e, G: 0x7d, B: 0x32, A: 0xff}
|
||||||
|
Green900 = &color.RGBA{R: 0x1b, G: 0x5e, B: 0x20, A: 0xff}
|
||||||
|
GreenA100 = &color.RGBA{R: 0xb9, G: 0xf6, B: 0xca, A: 0xff}
|
||||||
|
GreenA200 = &color.RGBA{R: 0x69, G: 0xf0, B: 0xae, A: 0xff}
|
||||||
|
GreenA400 = &color.RGBA{R: 0x00, G: 0xe6, B: 0x76, A: 0xff}
|
||||||
|
GreenA700 = &color.RGBA{R: 0x00, G: 0xc8, B: 0x53, A: 0xff}
|
||||||
|
|
||||||
|
LightGreen50 = &color.RGBA{R: 0xf1, G: 0xf8, B: 0xe9, A: 0xff}
|
||||||
|
LightGreen100 = &color.RGBA{R: 0xdc, G: 0xed, B: 0xc8, A: 0xff}
|
||||||
|
LightGreen200 = &color.RGBA{R: 0xc5, G: 0xe1, B: 0xa5, A: 0xff}
|
||||||
|
LightGreen300 = &color.RGBA{R: 0xae, G: 0xd5, B: 0x81, A: 0xff}
|
||||||
|
LightGreen400 = &color.RGBA{R: 0x9c, G: 0xcc, B: 0x65, A: 0xff}
|
||||||
|
LightGreen500 = &color.RGBA{R: 0x8b, G: 0xc3, B: 0x4a, A: 0xff}
|
||||||
|
LightGreen600 = &color.RGBA{R: 0x7c, G: 0xb3, B: 0x42, A: 0xff}
|
||||||
|
LightGreen700 = &color.RGBA{R: 0x68, G: 0x9f, B: 0x38, A: 0xff}
|
||||||
|
LightGreen800 = &color.RGBA{R: 0x55, G: 0x8b, B: 0x2f, A: 0xff}
|
||||||
|
LightGreen900 = &color.RGBA{R: 0x33, G: 0x69, B: 0x1e, A: 0xff}
|
||||||
|
LightGreenA100 = &color.RGBA{R: 0xcc, G: 0xff, B: 0x90, A: 0xff}
|
||||||
|
LightGreenA200 = &color.RGBA{R: 0xb2, G: 0xff, B: 0x59, A: 0xff}
|
||||||
|
LightGreenA400 = &color.RGBA{R: 0x76, G: 0xff, B: 0x03, A: 0xff}
|
||||||
|
LightGreenA700 = &color.RGBA{R: 0x64, G: 0xdd, B: 0x17, A: 0xff}
|
||||||
|
|
||||||
|
Lime50 = &color.RGBA{R: 0xf9, G: 0xfb, B: 0xe7, A: 0xff}
|
||||||
|
Lime100 = &color.RGBA{R: 0xf0, G: 0xf4, B: 0xc3, A: 0xff}
|
||||||
|
Lime200 = &color.RGBA{R: 0xe6, G: 0xee, B: 0x9c, A: 0xff}
|
||||||
|
Lime300 = &color.RGBA{R: 0xdc, G: 0xe7, B: 0x75, A: 0xff}
|
||||||
|
Lime400 = &color.RGBA{R: 0xd4, G: 0xe1, B: 0x57, A: 0xff}
|
||||||
|
Lime500 = &color.RGBA{R: 0xcd, G: 0xdc, B: 0x39, A: 0xff}
|
||||||
|
Lime600 = &color.RGBA{R: 0xc0, G: 0xca, B: 0x33, A: 0xff}
|
||||||
|
Lime700 = &color.RGBA{R: 0xaf, G: 0xb4, B: 0x2b, A: 0xff}
|
||||||
|
Lime800 = &color.RGBA{R: 0x9e, G: 0x9d, B: 0x24, A: 0xff}
|
||||||
|
Lime900 = &color.RGBA{R: 0x82, G: 0x77, B: 0x17, A: 0xff}
|
||||||
|
LimeA100 = &color.RGBA{R: 0xf4, G: 0xff, B: 0x81, A: 0xff}
|
||||||
|
LimeA200 = &color.RGBA{R: 0xee, G: 0xff, B: 0x41, A: 0xff}
|
||||||
|
LimeA400 = &color.RGBA{R: 0xc6, G: 0xff, B: 0x00, A: 0xff}
|
||||||
|
LimeA700 = &color.RGBA{R: 0xae, G: 0xea, B: 0x00, A: 0xff}
|
||||||
|
|
||||||
|
Yellow50 = &color.RGBA{R: 0xff, G: 0xfd, B: 0xe7, A: 0xff}
|
||||||
|
Yellow100 = &color.RGBA{R: 0xff, G: 0xf9, B: 0xc4, A: 0xff}
|
||||||
|
Yellow200 = &color.RGBA{R: 0xff, G: 0xf5, B: 0x9d, A: 0xff}
|
||||||
|
Yellow300 = &color.RGBA{R: 0xff, G: 0xf1, B: 0x76, A: 0xff}
|
||||||
|
Yellow400 = &color.RGBA{R: 0xff, G: 0xee, B: 0x58, A: 0xff}
|
||||||
|
Yellow500 = &color.RGBA{R: 0xff, G: 0xeb, B: 0x3b, A: 0xff}
|
||||||
|
Yellow600 = &color.RGBA{R: 0xfd, G: 0xd8, B: 0x35, A: 0xff}
|
||||||
|
Yellow700 = &color.RGBA{R: 0xfb, G: 0xc0, B: 0x2d, A: 0xff}
|
||||||
|
Yellow800 = &color.RGBA{R: 0xf9, G: 0xa8, B: 0x25, A: 0xff}
|
||||||
|
Yellow900 = &color.RGBA{R: 0xf5, G: 0x7f, B: 0x17, A: 0xff}
|
||||||
|
YellowA100 = &color.RGBA{R: 0xff, G: 0xff, B: 0x8d, A: 0xff}
|
||||||
|
YellowA200 = &color.RGBA{R: 0xff, G: 0xff, B: 0x00, A: 0xff}
|
||||||
|
YellowA400 = &color.RGBA{R: 0xff, G: 0xea, B: 0x00, A: 0xff}
|
||||||
|
YellowA700 = &color.RGBA{R: 0xff, G: 0xd6, B: 0x00, A: 0xff}
|
||||||
|
|
||||||
|
Amber50 = &color.RGBA{R: 0xff, G: 0xf8, B: 0xe1, A: 0xff}
|
||||||
|
Amber100 = &color.RGBA{R: 0xff, G: 0xec, B: 0xb3, A: 0xff}
|
||||||
|
Amber200 = &color.RGBA{R: 0xff, G: 0xe0, B: 0x82, A: 0xff}
|
||||||
|
Amber300 = &color.RGBA{R: 0xff, G: 0xd5, B: 0x4f, A: 0xff}
|
||||||
|
Amber400 = &color.RGBA{R: 0xff, G: 0xca, B: 0x28, A: 0xff}
|
||||||
|
Amber500 = &color.RGBA{R: 0xff, G: 0xc1, B: 0x07, A: 0xff}
|
||||||
|
Amber600 = &color.RGBA{R: 0xff, G: 0xb3, B: 0x00, A: 0xff}
|
||||||
|
Amber700 = &color.RGBA{R: 0xff, G: 0xa0, B: 0x00, A: 0xff}
|
||||||
|
Amber800 = &color.RGBA{R: 0xff, G: 0x8f, B: 0x00, A: 0xff}
|
||||||
|
Amber900 = &color.RGBA{R: 0xff, G: 0x6f, B: 0x00, A: 0xff}
|
||||||
|
AmberA100 = &color.RGBA{R: 0xff, G: 0xe5, B: 0x7f, A: 0xff}
|
||||||
|
AmberA200 = &color.RGBA{R: 0xff, G: 0xd7, B: 0x40, A: 0xff}
|
||||||
|
AmberA400 = &color.RGBA{R: 0xff, G: 0xc4, B: 0x00, A: 0xff}
|
||||||
|
AmberA700 = &color.RGBA{R: 0xff, G: 0xab, B: 0x00, A: 0xff}
|
||||||
|
|
||||||
|
Orange50 = &color.RGBA{R: 0xff, G: 0xf3, B: 0xe0, A: 0xff}
|
||||||
|
Orange100 = &color.RGBA{R: 0xff, G: 0xe0, B: 0xb2, A: 0xff}
|
||||||
|
Orange200 = &color.RGBA{R: 0xff, G: 0xcc, B: 0x80, A: 0xff}
|
||||||
|
Orange300 = &color.RGBA{R: 0xff, G: 0xb7, B: 0x4d, A: 0xff}
|
||||||
|
Orange400 = &color.RGBA{R: 0xff, G: 0xa7, B: 0x26, A: 0xff}
|
||||||
|
Orange500 = &color.RGBA{R: 0xff, G: 0x98, B: 0x00, A: 0xff}
|
||||||
|
Orange600 = &color.RGBA{R: 0xfb, G: 0x8c, B: 0x00, A: 0xff}
|
||||||
|
Orange700 = &color.RGBA{R: 0xf5, G: 0x7c, B: 0x00, A: 0xff}
|
||||||
|
Orange800 = &color.RGBA{R: 0xef, G: 0x6c, B: 0x00, A: 0xff}
|
||||||
|
Orange900 = &color.RGBA{R: 0xe6, G: 0x51, B: 0x00, A: 0xff}
|
||||||
|
OrangeA100 = &color.RGBA{R: 0xff, G: 0xd1, B: 0x80, A: 0xff}
|
||||||
|
OrangeA200 = &color.RGBA{R: 0xff, G: 0xab, B: 0x40, A: 0xff}
|
||||||
|
OrangeA400 = &color.RGBA{R: 0xff, G: 0x91, B: 0x00, A: 0xff}
|
||||||
|
OrangeA700 = &color.RGBA{R: 0xff, G: 0x6d, B: 0x00, A: 0xff}
|
||||||
|
|
||||||
|
DeepOrange50 = &color.RGBA{R: 0xfb, G: 0xe9, B: 0xe7, A: 0xff}
|
||||||
|
DeepOrange100 = &color.RGBA{R: 0xff, G: 0xcc, B: 0xbc, A: 0xff}
|
||||||
|
DeepOrange200 = &color.RGBA{R: 0xff, G: 0xab, B: 0x91, A: 0xff}
|
||||||
|
DeepOrange300 = &color.RGBA{R: 0xff, G: 0x8a, B: 0x65, A: 0xff}
|
||||||
|
DeepOrange400 = &color.RGBA{R: 0xff, G: 0x70, B: 0x43, A: 0xff}
|
||||||
|
DeepOrange500 = &color.RGBA{R: 0xff, G: 0x57, B: 0x22, A: 0xff}
|
||||||
|
DeepOrange600 = &color.RGBA{R: 0xf4, G: 0x51, B: 0x1e, A: 0xff}
|
||||||
|
DeepOrange700 = &color.RGBA{R: 0xe6, G: 0x4a, B: 0x19, A: 0xff}
|
||||||
|
DeepOrange800 = &color.RGBA{R: 0xd8, G: 0x43, B: 0x15, A: 0xff}
|
||||||
|
DeepOrange900 = &color.RGBA{R: 0xbf, G: 0x36, B: 0x0c, A: 0xff}
|
||||||
|
DeepOrangeA100 = &color.RGBA{R: 0xff, G: 0x9e, B: 0x80, A: 0xff}
|
||||||
|
DeepOrangeA200 = &color.RGBA{R: 0xff, G: 0x6e, B: 0x40, A: 0xff}
|
||||||
|
DeepOrangeA400 = &color.RGBA{R: 0xff, G: 0x3d, B: 0x00, A: 0xff}
|
||||||
|
DeepOrangeA700 = &color.RGBA{R: 0xdd, G: 0x2c, B: 0x00, A: 0xff}
|
||||||
|
|
||||||
|
Brown50 = &color.RGBA{R: 0xef, G: 0xeb, B: 0xe9, A: 0xff}
|
||||||
|
Brown100 = &color.RGBA{R: 0xd7, G: 0xcc, B: 0xc8, A: 0xff}
|
||||||
|
Brown200 = &color.RGBA{R: 0xbc, G: 0xaa, B: 0xa4, A: 0xff}
|
||||||
|
Brown300 = &color.RGBA{R: 0xa1, G: 0x88, B: 0x7f, A: 0xff}
|
||||||
|
Brown400 = &color.RGBA{R: 0x8d, G: 0x6e, B: 0x63, A: 0xff}
|
||||||
|
Brown500 = &color.RGBA{R: 0x79, G: 0x55, B: 0x48, A: 0xff}
|
||||||
|
Brown600 = &color.RGBA{R: 0x6d, G: 0x4c, B: 0x41, A: 0xff}
|
||||||
|
Brown700 = &color.RGBA{R: 0x5d, G: 0x40, B: 0x37, A: 0xff}
|
||||||
|
Brown800 = &color.RGBA{R: 0x4e, G: 0x34, B: 0x2e, A: 0xff}
|
||||||
|
Brown900 = &color.RGBA{R: 0x3e, G: 0x27, B: 0x23, A: 0xff}
|
||||||
|
|
||||||
|
Grey50 = &color.RGBA{R: 0xfa, G: 0xfa, B: 0xfa, A: 0xff}
|
||||||
|
Grey100 = &color.RGBA{R: 0xf5, G: 0xf5, B: 0xf5, A: 0xff}
|
||||||
|
Grey200 = &color.RGBA{R: 0xee, G: 0xee, B: 0xee, A: 0xff}
|
||||||
|
Grey300 = &color.RGBA{R: 0xe0, G: 0xe0, B: 0xe0, A: 0xff}
|
||||||
|
Grey400 = &color.RGBA{R: 0xbd, G: 0xbd, B: 0xbd, A: 0xff}
|
||||||
|
Grey500 = &color.RGBA{R: 0x9e, G: 0x9e, B: 0x9e, A: 0xff}
|
||||||
|
Grey600 = &color.RGBA{R: 0x75, G: 0x75, B: 0x75, A: 0xff}
|
||||||
|
Grey700 = &color.RGBA{R: 0x61, G: 0x61, B: 0x61, A: 0xff}
|
||||||
|
Grey800 = &color.RGBA{R: 0x42, G: 0x42, B: 0x42, A: 0xff}
|
||||||
|
Grey900 = &color.RGBA{R: 0x21, G: 0x21, B: 0x21, A: 0xff}
|
||||||
|
|
||||||
|
BlueGrey50 = &color.RGBA{R: 0xec, G: 0xef, B: 0xf1, A: 0xff}
|
||||||
|
BlueGrey100 = &color.RGBA{R: 0xcf, G: 0xd8, B: 0xdc, A: 0xff}
|
||||||
|
BlueGrey200 = &color.RGBA{R: 0xb0, G: 0xbe, B: 0xc5, A: 0xff}
|
||||||
|
BlueGrey300 = &color.RGBA{R: 0x90, G: 0xa4, B: 0xae, A: 0xff}
|
||||||
|
BlueGrey400 = &color.RGBA{R: 0x78, G: 0x90, B: 0x9c, A: 0xff}
|
||||||
|
BlueGrey500 = &color.RGBA{R: 0x60, G: 0x7d, B: 0x8b, A: 0xff}
|
||||||
|
BlueGrey600 = &color.RGBA{R: 0x54, G: 0x6e, B: 0x7a, A: 0xff}
|
||||||
|
BlueGrey700 = &color.RGBA{R: 0x45, G: 0x5a, B: 0x64, A: 0xff}
|
||||||
|
BlueGrey800 = &color.RGBA{R: 0x37, G: 0x47, B: 0x4f, A: 0xff}
|
||||||
|
BlueGrey900 = &color.RGBA{R: 0x26, G: 0x32, B: 0x38, A: 0xff}
|
||||||
|
|
||||||
|
Black = &color.RGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}
|
||||||
|
White = &color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
||||||
|
)
|
||||||
|
|
||||||
|
var Colors500 = []*color.RGBA{Red500, Pink500, Purple500, DeepPurple500, Indigo500, Blue500, LightBlue500, Cyan500, Teal500, Green500, LightGreen500, Lime500, Yellow500, Amber500, Orange500, DeepOrange500}
|
106
ui/container.go
Normal file
106
ui/container.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container interface {
|
||||||
|
Control
|
||||||
|
Children() []Control
|
||||||
|
Arrange(Context, geom.RectangleF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainer(parent Container, children ...Control) *ContainerBase {
|
||||||
|
return &ContainerBase{ControlBase{}, false, parent, children}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerBase struct {
|
||||||
|
ControlBase
|
||||||
|
created bool
|
||||||
|
parent Container
|
||||||
|
children []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Children() []Control {
|
||||||
|
return c.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Arrange(ctx Context, rect geom.RectangleF) {
|
||||||
|
c.ControlBase.SetRect(rect)
|
||||||
|
for _, child := range c.children {
|
||||||
|
Arrange(ctx, child, rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Append(child Control) {
|
||||||
|
if c.created {
|
||||||
|
panic("error append, must use append create when control is already created")
|
||||||
|
}
|
||||||
|
c.children = append(c.children, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) AppendCreate(ctx Context, child Control) error {
|
||||||
|
if !c.created {
|
||||||
|
panic("error append create, must use append when control is not yet created")
|
||||||
|
}
|
||||||
|
c.children = append(c.children, child)
|
||||||
|
return child.Created(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) RemoveFn(ctx Context, pred func(Control) bool) {
|
||||||
|
var i = 0
|
||||||
|
for i < len(c.children) {
|
||||||
|
if pred(c.children[i]) {
|
||||||
|
c.children[i].Destroyed(ctx)
|
||||||
|
c.children = c.children[:i+copy(c.children[i:], c.children[i+1:])]
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Created(ctx Context, p Container) error {
|
||||||
|
if c.created {
|
||||||
|
panic("already created")
|
||||||
|
}
|
||||||
|
c.parent = p
|
||||||
|
for _, child := range c.children {
|
||||||
|
err := child.Created(ctx, p)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.created = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Destroyed(ctx Context) {
|
||||||
|
if !c.created {
|
||||||
|
panic("not yet created or already destroyed")
|
||||||
|
}
|
||||||
|
for _, child := range c.children {
|
||||||
|
child.Destroyed(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Update(ctx Context, dt time.Duration) {
|
||||||
|
for _, child := range c.children {
|
||||||
|
child.Update(ctx, dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Handle(ctx Context, ev allegro5.Event) {
|
||||||
|
for _, child := range c.children {
|
||||||
|
child.Handle(ctx, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerBase) Render(ctx Context) {
|
||||||
|
for _, child := range c.children {
|
||||||
|
child.Render(ctx)
|
||||||
|
}
|
||||||
|
c.ControlBase.Render(ctx)
|
||||||
|
}
|
189
ui/contentscrollbar.go
Normal file
189
ui/contentscrollbar.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &ContentScrollbar{}
|
||||||
|
|
||||||
|
type ContentScrollbarValueChangedFn func(float64)
|
||||||
|
|
||||||
|
type ContentScrollbar struct {
|
||||||
|
ControlBase
|
||||||
|
Length float64
|
||||||
|
Value float64
|
||||||
|
Orientation Orientation
|
||||||
|
OnChanged ContentScrollbarValueChangedFn
|
||||||
|
handle *contentScrollbarHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentScrollbarHandle struct {
|
||||||
|
ControlBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *contentScrollbarHandle) Render(ctx Context) {
|
||||||
|
var c = ctx.Palette().Primary()
|
||||||
|
if h.IsOver {
|
||||||
|
if h.IsPressed {
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var min = h.Bounds.Min.To32()
|
||||||
|
var max = h.Bounds.Max.To32()
|
||||||
|
allegro5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) Created(ctx Context, p Container) error {
|
||||||
|
s.ControlBase.Created(ctx, p)
|
||||||
|
s.handle = &contentScrollbarHandle{}
|
||||||
|
s.handle.Created(ctx, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) Destroyed(ctx Context) {
|
||||||
|
s.handle.Destroyed(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) barViewRange() (float64, float64) {
|
||||||
|
var min, max float64
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
min = s.Bounds.Min.X
|
||||||
|
max = s.Bounds.Max.X
|
||||||
|
default:
|
||||||
|
min = s.Bounds.Max.Y
|
||||||
|
max = s.Bounds.Min.Y
|
||||||
|
}
|
||||||
|
var length = (max - min)
|
||||||
|
var bar = length
|
||||||
|
if s.Length > length {
|
||||||
|
bar = length * length / s.Length
|
||||||
|
}
|
||||||
|
if bar < 20 {
|
||||||
|
if length < 40 {
|
||||||
|
bar = .5 * length
|
||||||
|
} else {
|
||||||
|
bar = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return min + .5*bar, max - .5*bar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) barViewCenter() float64 {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return (s.Bounds.Min.Y + s.Bounds.Max.Y) * .5
|
||||||
|
default:
|
||||||
|
return (s.Bounds.Min.X + s.Bounds.Max.X) * .5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) toValue(x, y float64) float64 {
|
||||||
|
var n = y
|
||||||
|
if OrientationHorizontal == s.Orientation {
|
||||||
|
n = x
|
||||||
|
}
|
||||||
|
var min, max = s.barViewRange()
|
||||||
|
if min == max {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var delta = s.Length - (max - min)
|
||||||
|
var off = (n - min) / (max - min)
|
||||||
|
var v = off * (s.Length - (max - min))
|
||||||
|
if v < 0 {
|
||||||
|
v = 0
|
||||||
|
} else if v > delta {
|
||||||
|
v = delta
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) change(v float64) {
|
||||||
|
if v != s.Value {
|
||||||
|
s.Value = v
|
||||||
|
var onChanged = s.OnChanged
|
||||||
|
if nil != onChanged {
|
||||||
|
onChanged(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) snapTo(x, y int) {
|
||||||
|
var val = s.toValue(float64(x), float64(y))
|
||||||
|
s.change(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) increment(d int) {
|
||||||
|
// var val = s.Value + d
|
||||||
|
// if val < s.Minimum {
|
||||||
|
// val = s.Minimum
|
||||||
|
// } else if val > s.Maximum {
|
||||||
|
// val = s.Maximum
|
||||||
|
// }
|
||||||
|
// s.change(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) Handle(ctx Context, ev allegro5.Event) {
|
||||||
|
s.ControlBase.Handle(ctx, ev)
|
||||||
|
s.handle.Handle(ctx, ev)
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allegro5.MouseMoveEvent:
|
||||||
|
if s.handle.IsPressed {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
if 0 != e.DeltaZ && s.IsOver {
|
||||||
|
var d = e.DeltaZ
|
||||||
|
if allegro5.IsAnyKeyDown(allegro5.KeyLShift, allegro5.KeyRShift) {
|
||||||
|
d *= 10
|
||||||
|
}
|
||||||
|
s.increment(d)
|
||||||
|
}
|
||||||
|
case *allegro5.MouseButtonDownEvent:
|
||||||
|
if !s.handle.IsPressed && s.IsOver {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) DesiredSize(Context) geom.PointF {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return geom.PtF(math.NaN(), ScrollbarWidth)
|
||||||
|
}
|
||||||
|
return geom.PtF(ScrollbarWidth, math.NaN())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) SetRect(rect geom.RectangleF) {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
if rect.Dy() > ScrollbarWidth {
|
||||||
|
rect.Min.Y = rect.Max.Y - ScrollbarWidth
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if rect.Dx() > ScrollbarWidth {
|
||||||
|
rect.Min.X = rect.Max.X - ScrollbarWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ControlBase.SetRect(rect)
|
||||||
|
|
||||||
|
// var min, max = s.barViewRange()
|
||||||
|
// var off = float64(s.Value-s.Minimum) / float64(s.Maximum-s.Minimum)
|
||||||
|
// var centerH = min + (max-min)*off
|
||||||
|
// var r = 0.5 * float64(s.handles[0].Width())
|
||||||
|
|
||||||
|
// var center = s.barViewCenter()
|
||||||
|
// switch s.Orientation {
|
||||||
|
// case OrientationHorizontal:
|
||||||
|
// s.handle.SetRect(geom.RectF(centerH-r, center-r, centerH+r, center+r))
|
||||||
|
// default:
|
||||||
|
// s.handle.SetRect(geom.RectF(center-r, centerH-r, center+r, centerH+r))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) Render(ctx Context) {
|
||||||
|
s.handle.Render(ctx)
|
||||||
|
s.ControlBase.Render(ctx)
|
||||||
|
}
|
72
ui/context.go
Normal file
72
ui/context.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "opslag.de/schobers/galleg/allegro5"
|
||||||
|
|
||||||
|
type Context interface {
|
||||||
|
Display() *allegro5.Display
|
||||||
|
Fonts() Fonts
|
||||||
|
Palette() Palette
|
||||||
|
Debug() Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
type Debug interface {
|
||||||
|
IsEnabled() bool
|
||||||
|
Rainbow() allegro5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Context = &context{}
|
||||||
|
|
||||||
|
type context struct {
|
||||||
|
disp *allegro5.Display
|
||||||
|
fts Fonts
|
||||||
|
pal Palette
|
||||||
|
dbg *debug
|
||||||
|
}
|
||||||
|
|
||||||
|
type debug struct {
|
||||||
|
enbl bool
|
||||||
|
rainb []allegro5.Color
|
||||||
|
col int
|
||||||
|
}
|
||||||
|
|
||||||
|
func rainbow() []allegro5.Color {
|
||||||
|
var colors = make([]allegro5.Color, len(Colors500))
|
||||||
|
for i, c := range Colors500 {
|
||||||
|
colors[i] = NewColorAlpha(c, 0xbf)
|
||||||
|
}
|
||||||
|
return colors
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContext(disp *allegro5.Display, f Fonts) *context {
|
||||||
|
return &context{disp, f, DefaultPalette(), &debug{rainb: rainbow()}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Display() *allegro5.Display {
|
||||||
|
return c.disp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Fonts() Fonts {
|
||||||
|
return c.fts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Palette() Palette {
|
||||||
|
return c.pal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Debug() Debug {
|
||||||
|
return c.dbg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debug) IsEnabled() bool {
|
||||||
|
return d.enbl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debug) resetRainbow() {
|
||||||
|
d.col = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debug) Rainbow() allegro5.Color {
|
||||||
|
var col = d.col
|
||||||
|
d.col = (col + 1) % len(d.rainb)
|
||||||
|
return d.rainb[col]
|
||||||
|
}
|
89
ui/control.go
Normal file
89
ui/control.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Control interface {
|
||||||
|
Created(Context, Container) error
|
||||||
|
Destroyed(Context)
|
||||||
|
|
||||||
|
Update(Context, time.Duration)
|
||||||
|
Handle(Context, allegro5.Event)
|
||||||
|
DesiredSize(Context) geom.PointF
|
||||||
|
SetRect(geom.RectangleF)
|
||||||
|
Render(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MouseClickFn func(Control)
|
||||||
|
|
||||||
|
var _ Control = &ControlBase{}
|
||||||
|
|
||||||
|
type ControlBase struct {
|
||||||
|
Parent Container
|
||||||
|
Bounds geom.RectangleF
|
||||||
|
Disabled bool
|
||||||
|
IsOver bool
|
||||||
|
IsPressed bool
|
||||||
|
OnClick MouseClickFn
|
||||||
|
MinSize geom.PointF
|
||||||
|
Background *allegro5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) Created(_ Context, p Container) error {
|
||||||
|
c.Parent = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) Destroyed(Context) {}
|
||||||
|
|
||||||
|
func (c *ControlBase) Update(Context, time.Duration) {}
|
||||||
|
|
||||||
|
func (c *ControlBase) Handle(ctx Context, ev allegro5.Event) {
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allegro5.MouseMoveEvent:
|
||||||
|
c.IsOver = c.IsInRect(float64(e.X), float64(e.Y))
|
||||||
|
case *allegro5.MouseButtonDownEvent:
|
||||||
|
if c.IsOver {
|
||||||
|
c.IsPressed = true
|
||||||
|
}
|
||||||
|
case *allegro5.MouseButtonUpEvent:
|
||||||
|
if c.IsPressed && c.IsOver {
|
||||||
|
var onClick = c.OnClick
|
||||||
|
if nil != onClick {
|
||||||
|
onClick(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.IsPressed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) DesiredSize(Context) geom.PointF {
|
||||||
|
return c.MinSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) SetRect(rect geom.RectangleF) {
|
||||||
|
c.Bounds = rect
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) Render(ctx Context) {
|
||||||
|
var min = c.Bounds.Min.To32()
|
||||||
|
var max = c.Bounds.Max.To32()
|
||||||
|
if nil != c.Background {
|
||||||
|
allegro5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, *c.Background)
|
||||||
|
}
|
||||||
|
if ctx.Debug().IsEnabled() {
|
||||||
|
allegro5.DrawRectangle(min.X, min.Y, max.X, max.Y, ctx.Debug().Rainbow(), 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) Rect() geom.RectangleF {
|
||||||
|
return c.Bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) IsInRect(x, y float64) bool {
|
||||||
|
return geom.PtF(x, y).In(c.Bounds)
|
||||||
|
}
|
5
ui/dimensions.go
Normal file
5
ui/dimensions.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
const topMargin = 4
|
||||||
|
const leftMargin = 8
|
||||||
|
const checkboxSize = 12
|
146
ui/dockpanel.go
Normal file
146
ui/dockpanel.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DockPanel interface {
|
||||||
|
Container
|
||||||
|
Append(o Dock, children ...Control) error
|
||||||
|
AppendCreate(ctx Context, o Dock, children ...Control) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dock int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DockLeft Dock = iota
|
||||||
|
DockTop
|
||||||
|
DockRight
|
||||||
|
DockBottom
|
||||||
|
)
|
||||||
|
|
||||||
|
type dockPanel struct {
|
||||||
|
ContainerBase
|
||||||
|
docks []Dock
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDockPanel(parent Container) DockPanel {
|
||||||
|
return &dockPanel{ContainerBase: ContainerBase{parent: parent}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) Append(d Dock, children ...Control) error {
|
||||||
|
for _, child := range children {
|
||||||
|
p.ContainerBase.Append(child)
|
||||||
|
p.docks = append(p.docks, d)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) AppendCreate(ctx Context, d Dock, children ...Control) error {
|
||||||
|
for _, child := range children {
|
||||||
|
err := p.ContainerBase.AppendCreate(ctx, child)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.docks = append(p.docks, d)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) DesiredSize(ctx Context) geom.PointF {
|
||||||
|
var width, height float64 = 0, 0
|
||||||
|
for i, child := range p.children {
|
||||||
|
var d = p.docks[i]
|
||||||
|
var desired = child.DesiredSize(ctx)
|
||||||
|
switch {
|
||||||
|
case d == DockLeft || d == DockRight:
|
||||||
|
if !math.IsNaN(width) {
|
||||||
|
if math.IsNaN(desired.X) {
|
||||||
|
width = desired.X
|
||||||
|
} else {
|
||||||
|
width += desired.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !math.IsNaN(desired.Y) {
|
||||||
|
height = math.Max(height, desired.Y)
|
||||||
|
}
|
||||||
|
case d == DockTop || d == DockBottom:
|
||||||
|
if !math.IsNaN(height) {
|
||||||
|
if math.IsNaN(desired.Y) {
|
||||||
|
height = desired.Y
|
||||||
|
} else {
|
||||||
|
height += desired.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !math.IsNaN(desired.X) {
|
||||||
|
width = math.Max(width, desired.X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return geom.PtF(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) arrangeChildren(ctx Context) []geom.RectangleF {
|
||||||
|
var n = len(p.children)
|
||||||
|
var rects = make([]geom.RectangleF, n)
|
||||||
|
|
||||||
|
var rem = p.Bounds
|
||||||
|
var last = n - 1
|
||||||
|
for i, child := range p.children {
|
||||||
|
if last == i {
|
||||||
|
rects[i] = rem
|
||||||
|
} else {
|
||||||
|
var d = p.docks[i]
|
||||||
|
var desired = child.DesiredSize(ctx)
|
||||||
|
var width, height, top, left float64
|
||||||
|
switch {
|
||||||
|
case d == DockLeft || d == DockRight:
|
||||||
|
width = rem.Dx()
|
||||||
|
if !math.IsNaN(desired.X) {
|
||||||
|
width = math.Min(desired.X, width)
|
||||||
|
}
|
||||||
|
if d == DockLeft {
|
||||||
|
left = rem.Min.X
|
||||||
|
} else {
|
||||||
|
left = rem.Max.X - width
|
||||||
|
}
|
||||||
|
top = rem.Min.Y
|
||||||
|
height = rem.Dy()
|
||||||
|
case d == DockTop || d == DockBottom:
|
||||||
|
height = rem.Dy()
|
||||||
|
if !math.IsNaN(desired.Y) {
|
||||||
|
height = math.Min(desired.Y, height)
|
||||||
|
}
|
||||||
|
if d == DockTop {
|
||||||
|
top = rem.Min.Y
|
||||||
|
} else {
|
||||||
|
top = rem.Max.Y - height
|
||||||
|
}
|
||||||
|
left = rem.Min.X
|
||||||
|
width = rem.Dx()
|
||||||
|
}
|
||||||
|
rects[i] = geom.RectF(left, top, left+width, top+height)
|
||||||
|
switch d {
|
||||||
|
case DockLeft:
|
||||||
|
rem = geom.RectF(rem.Min.X+width, rem.Min.Y, rem.Max.X, rem.Max.Y)
|
||||||
|
case DockTop:
|
||||||
|
rem = geom.RectF(rem.Min.X, rem.Min.Y+height, rem.Max.X, rem.Max.Y)
|
||||||
|
case DockRight:
|
||||||
|
rem = geom.RectF(rem.Min.X, rem.Min.Y, rem.Max.X-width, rem.Max.Y)
|
||||||
|
case DockBottom:
|
||||||
|
rem = geom.RectF(rem.Min.X, rem.Min.Y, rem.Max.X, rem.Max.Y-height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) Arrange(ctx Context, rect geom.RectangleF) {
|
||||||
|
p.ContainerBase.SetRect(rect)
|
||||||
|
var rects = p.arrangeChildren(ctx)
|
||||||
|
for i, child := range p.children {
|
||||||
|
Arrange(ctx, child, rects[i])
|
||||||
|
}
|
||||||
|
}
|
65
ui/fonts.go
Normal file
65
ui/fonts.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"opslag.de/schobers/fs/vfs"
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fonts interface {
|
||||||
|
Register(name, path string, size int) error
|
||||||
|
Get(name string) *allegro5.Font
|
||||||
|
Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
type fonts struct {
|
||||||
|
fts map[string]*allegro5.Font
|
||||||
|
dir vfs.CopyDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFonts(dir vfs.CopyDir) *fonts {
|
||||||
|
return &fonts{make(map[string]*allegro5.Font), dir}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFonts(fs afero.Fs) (Fonts, error) {
|
||||||
|
var dir, err = vfs.NewCopyDir(fs)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newFonts(dir), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fts *fonts) load(name, path string, size int) error {
|
||||||
|
f, err := allegro5.LoadTTFFont(path, size)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fts.fts[name] = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fts *fonts) loadFromBox(name, path string, size int) error {
|
||||||
|
path, err := fts.dir.Retrieve(path)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fts.load(name, path, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fts *fonts) Register(name, path string, size int) error {
|
||||||
|
if nil != fts.dir {
|
||||||
|
return fts.loadFromBox(name, path, size)
|
||||||
|
}
|
||||||
|
return fts.load(name, path, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fts *fonts) Get(name string) *allegro5.Font {
|
||||||
|
return fts.fts[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fts *fonts) Destroy() {
|
||||||
|
for _, f := range fts.fts {
|
||||||
|
f.Destroy()
|
||||||
|
}
|
||||||
|
fts.dir.Destroy()
|
||||||
|
}
|
19
ui/label.go
Normal file
19
ui/label.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "opslag.de/schobers/galleg/allegro5"
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Label) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = l.Bounds.Min.To32()
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
fnt.Draw(min.X+leftMargin, min.Y+topMargin, ctx.Palette().Black(), allegro5.AlignLeft, l.Text)
|
||||||
|
|
||||||
|
l.ControlBase.Render(ctx)
|
||||||
|
}
|
121
ui/loop.go
Normal file
121
ui/loop.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shouldClose(ev allegro5.Event) bool {
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allegro5.KeyCharEvent:
|
||||||
|
switch e.KeyCode {
|
||||||
|
case allegro5.KeyEscape:
|
||||||
|
return true
|
||||||
|
case allegro5.KeyF4:
|
||||||
|
if e.Modifiers&allegro5.KeyModAlt == allegro5.KeyModAlt {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *allegro5.DisplayCloseEvent:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldToggleDebug(ev allegro5.Event) bool {
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allegro5.KeyCharEvent:
|
||||||
|
switch e.KeyCode {
|
||||||
|
case allegro5.KeyD:
|
||||||
|
return e.Modifiers&allegro5.KeyModAlt == allegro5.KeyModAlt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func switchState(ctx Context, curr State, s State) error {
|
||||||
|
var err error
|
||||||
|
err = curr.Leave(ctx)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.Enter(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init() error {
|
||||||
|
return allegro5.Init(allegro5.InitAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(w, h int, title string, s State, f Fonts, opts *allegro5.NewDisplayOptions) error {
|
||||||
|
if nil == opts {
|
||||||
|
opts = &allegro5.NewDisplayOptions{}
|
||||||
|
}
|
||||||
|
disp, err := allegro5.NewDisplay(w, h, *opts)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
disp.SetWindowTitle(title)
|
||||||
|
defer disp.Destroy()
|
||||||
|
|
||||||
|
evq, err := allegro5.NewEventQueue()
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
evq.RegisterDisplay(disp)
|
||||||
|
evq.RegisterKeyboard()
|
||||||
|
evq.RegisterMouse()
|
||||||
|
defer evq.Destroy()
|
||||||
|
|
||||||
|
ctx := newContext(disp, f)
|
||||||
|
|
||||||
|
var state = s
|
||||||
|
err = state.Enter(ctx)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var t = time.Now()
|
||||||
|
main:
|
||||||
|
for nil != state {
|
||||||
|
for ev := evq.Get(); ev != nil; ev = evq.Get() {
|
||||||
|
if shouldClose(ev) {
|
||||||
|
break main
|
||||||
|
}
|
||||||
|
if shouldToggleDebug(ev) {
|
||||||
|
ctx.dbg.enbl = !ctx.dbg.enbl
|
||||||
|
}
|
||||||
|
err := state.Handle(ctx, ev)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var now = time.Now()
|
||||||
|
var dt = now.Sub(t)
|
||||||
|
var s, err = state.Update(ctx, dt)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t = now
|
||||||
|
if s != nil {
|
||||||
|
err = switchState(ctx, state, s)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
state = s
|
||||||
|
}
|
||||||
|
ctx.dbg.resetRainbow()
|
||||||
|
err = state.Render(ctx)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
disp.Flip()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
err = state.Leave(ctx)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
19
ui/orientation.go
Normal file
19
ui/orientation.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type Orientation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OrientationVertical Orientation = iota
|
||||||
|
OrientationHorizontal
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o Orientation) String() string {
|
||||||
|
switch o {
|
||||||
|
case OrientationVertical:
|
||||||
|
return "vertical"
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return "horizontal"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
51
ui/palette.go
Normal file
51
ui/palette.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Palette interface {
|
||||||
|
Primary() allegro5.Color
|
||||||
|
White() allegro5.Color
|
||||||
|
Black() allegro5.Color
|
||||||
|
Disabled() allegro5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type palette struct {
|
||||||
|
primary allegro5.Color
|
||||||
|
white allegro5.Color
|
||||||
|
black allegro5.Color
|
||||||
|
disabled allegro5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) Primary() allegro5.Color {
|
||||||
|
return p.primary
|
||||||
|
}
|
||||||
|
func (p *palette) White() allegro5.Color {
|
||||||
|
return p.white
|
||||||
|
}
|
||||||
|
func (p *palette) Black() allegro5.Color {
|
||||||
|
return p.black
|
||||||
|
}
|
||||||
|
func (p *palette) Disabled() allegro5.Color {
|
||||||
|
return p.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColor(c *color.RGBA) allegro5.Color {
|
||||||
|
return allegro5.NewColorAlpha(c.R, c.G, c.B, c.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColorAlpha(c *color.RGBA, a uint8) allegro5.Color {
|
||||||
|
return allegro5.NewColorAlpha(c.R, c.G, c.B, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultPalette() Palette {
|
||||||
|
return &palette{
|
||||||
|
primary: NewColor(Blue500),
|
||||||
|
white: allegro5.NewColor(0xff, 0xff, 0xff),
|
||||||
|
black: allegro5.NewColor(0, 0, 0),
|
||||||
|
disabled: allegro5.NewColorAlpha(0x1f, 0x1f, 0x1f, 0x1f),
|
||||||
|
}
|
||||||
|
}
|
26
ui/queue.go
Normal file
26
ui/queue.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type Queue struct {
|
||||||
|
q chan func(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueue() *Queue {
|
||||||
|
return &Queue{make(chan func(Context), 4)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Do(act func(Context)) {
|
||||||
|
go func() { // Non-blocking queue
|
||||||
|
q.q <- act
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Process(ctx Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case act := <-q.q:
|
||||||
|
act(ctx)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
213
ui/scrollbar.go
Normal file
213
ui/scrollbar.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &Scrollbar{}
|
||||||
|
|
||||||
|
const ScrollbarWidth = 26
|
||||||
|
const ScrollbarHandleThickness = 4
|
||||||
|
const ScrollbarHandlePadding = 1
|
||||||
|
|
||||||
|
type ScrollbarValueChangedFn func(int)
|
||||||
|
|
||||||
|
type Scrollbar struct {
|
||||||
|
ControlBase
|
||||||
|
Minimum int
|
||||||
|
Maximum int
|
||||||
|
Value int
|
||||||
|
Orientation Orientation
|
||||||
|
OnChanged ScrollbarValueChangedFn
|
||||||
|
handle *ControlBase
|
||||||
|
handles []*allegro5.Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) Created(ctx Context, p Container) error {
|
||||||
|
s.ControlBase.Created(ctx, p)
|
||||||
|
s.handles = []*allegro5.Bitmap{
|
||||||
|
createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue500),
|
||||||
|
createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue300),
|
||||||
|
createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue700),
|
||||||
|
}
|
||||||
|
s.handle = &ControlBase{}
|
||||||
|
s.handle.Created(ctx, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) Destroyed(ctx Context) {
|
||||||
|
if nil != s.handles {
|
||||||
|
for _, h := range s.handles {
|
||||||
|
h.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.handle.Destroyed(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) barViewRange() (float64, float64) {
|
||||||
|
var small bool
|
||||||
|
var min, max float64
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
min = s.Bounds.Min.X + ScrollbarWidth*.5
|
||||||
|
max = s.Bounds.Max.X - ScrollbarWidth*.5
|
||||||
|
small = min > max
|
||||||
|
default:
|
||||||
|
min = s.Bounds.Max.Y - ScrollbarWidth*.5
|
||||||
|
max = s.Bounds.Min.Y + ScrollbarWidth*.5
|
||||||
|
small = max > min
|
||||||
|
}
|
||||||
|
if small {
|
||||||
|
var center = (min + max) * .5
|
||||||
|
min, max = center, center
|
||||||
|
}
|
||||||
|
return min, max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) barViewCenter() float64 {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return (s.Bounds.Min.Y + s.Bounds.Max.Y) * .5
|
||||||
|
default:
|
||||||
|
return (s.Bounds.Min.X + s.Bounds.Max.X) * .5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) toValue(x, y float64) int {
|
||||||
|
var n = y
|
||||||
|
if OrientationHorizontal == s.Orientation {
|
||||||
|
n = x
|
||||||
|
}
|
||||||
|
var min, max = s.barViewRange()
|
||||||
|
if min == max {
|
||||||
|
return s.Minimum
|
||||||
|
}
|
||||||
|
var off = (n - min) / (max - min)
|
||||||
|
var v = s.Minimum + int(off*float64(s.Maximum-s.Minimum)+.5)
|
||||||
|
if v < s.Minimum {
|
||||||
|
v = s.Minimum
|
||||||
|
} else if v > s.Maximum {
|
||||||
|
v = s.Maximum
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) change(v int) {
|
||||||
|
if v != s.Value {
|
||||||
|
s.Value = v
|
||||||
|
var onChanged = s.OnChanged
|
||||||
|
if nil != onChanged {
|
||||||
|
onChanged(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) snapTo(x, y int) {
|
||||||
|
var val = s.toValue(float64(x), float64(y))
|
||||||
|
s.change(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) increment(d int) {
|
||||||
|
var val = s.Value + d
|
||||||
|
if val < s.Minimum {
|
||||||
|
val = s.Minimum
|
||||||
|
} else if val > s.Maximum {
|
||||||
|
val = s.Maximum
|
||||||
|
}
|
||||||
|
s.change(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) Handle(ctx Context, ev allegro5.Event) {
|
||||||
|
s.ControlBase.Handle(ctx, ev)
|
||||||
|
s.handle.Handle(ctx, ev)
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allegro5.MouseMoveEvent:
|
||||||
|
if s.handle.IsPressed {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
if 0 != e.DeltaZ && s.IsOver {
|
||||||
|
var d = e.DeltaZ
|
||||||
|
if allegro5.IsAnyKeyDown(allegro5.KeyLShift, allegro5.KeyRShift) {
|
||||||
|
d *= 10
|
||||||
|
}
|
||||||
|
s.increment(d)
|
||||||
|
}
|
||||||
|
case *allegro5.MouseButtonDownEvent:
|
||||||
|
if !s.handle.IsPressed && s.IsOver {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) DesiredSize(Context) geom.PointF {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return geom.PtF(math.NaN(), ScrollbarWidth)
|
||||||
|
}
|
||||||
|
return geom.PtF(ScrollbarWidth, math.NaN())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) SetRect(rect geom.RectangleF) {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
if rect.Dy() > ScrollbarWidth {
|
||||||
|
rect.Min.Y = rect.Max.Y - ScrollbarWidth
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if rect.Dx() > ScrollbarWidth {
|
||||||
|
rect.Min.X = rect.Max.X - ScrollbarWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ControlBase.SetRect(rect)
|
||||||
|
|
||||||
|
var min, max = s.barViewRange()
|
||||||
|
var off = float64(s.Value-s.Minimum) / float64(s.Maximum-s.Minimum)
|
||||||
|
var centerH = min + (max-min)*off
|
||||||
|
var r = 0.5 * float64(s.handles[0].Width())
|
||||||
|
|
||||||
|
var center = s.barViewCenter()
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
s.handle.SetRect(geom.RectF(centerH-r, center-r, centerH+r, center+r))
|
||||||
|
default:
|
||||||
|
s.handle.SetRect(geom.RectF(center-r, centerH-r, center+r, centerH+r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) Render(ctx Context) {
|
||||||
|
var center = float32(s.barViewCenter())
|
||||||
|
var min64, max64 = s.barViewRange()
|
||||||
|
var min, max = float32(min64), float32(max64)
|
||||||
|
|
||||||
|
var minH = s.handle.Bounds.Min.To32()
|
||||||
|
var stateH = 0
|
||||||
|
if s.handle.IsOver {
|
||||||
|
stateH = 1
|
||||||
|
if s.handle.IsPressed {
|
||||||
|
stateH = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.handles[stateH].Draw(minH.X, minH.Y)
|
||||||
|
var maxH = s.handle.Bounds.Max.To32()
|
||||||
|
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
if min < minH.X-1 { // Top line
|
||||||
|
allegro5.DrawLine(min, center, minH.X-1, center, ctx.Palette().Black(), 1)
|
||||||
|
}
|
||||||
|
if max > maxH.X+1 { // Bottom line
|
||||||
|
allegro5.DrawLine(maxH.X+1, center, max, center, ctx.Palette().Black(), 1)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if max < minH.Y-1 { // Top line
|
||||||
|
allegro5.DrawLine(center, max, center, minH.Y-1, ctx.Palette().Black(), 1)
|
||||||
|
}
|
||||||
|
if min > maxH.Y+1 { // Bottom line
|
||||||
|
allegro5.DrawLine(center, maxH.Y+1, center, min, ctx.Palette().Black(), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ControlBase.Render(ctx)
|
||||||
|
}
|
29
ui/shadow.go
Normal file
29
ui/shadow.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dropShadowPalette []allegro5.Color
|
||||||
|
|
||||||
|
func initDropShadowPalette() {
|
||||||
|
if nil != dropShadowPalette {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dropShadowPalette = []allegro5.Color{
|
||||||
|
allegro5.NewColorAlpha(0, 0, 0, 0x0f),
|
||||||
|
allegro5.NewColorAlpha(0, 0, 0, 0x07),
|
||||||
|
allegro5.NewColorAlpha(0, 0, 0, 0x03),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DropShadow(x1, y1, x2, y2 float32) {
|
||||||
|
initDropShadowPalette()
|
||||||
|
|
||||||
|
allegro5.DrawLine(x1, y2+1, x2+1, y2+1, dropShadowPalette[0], 1)
|
||||||
|
allegro5.DrawLine(x2+1, y1, x2+1, y2, dropShadowPalette[0], 1)
|
||||||
|
allegro5.DrawLine(x1, y2+2, x2+2, y2+2, dropShadowPalette[1], 1)
|
||||||
|
allegro5.DrawLine(x2+2, y1, x2+2, y2+1, dropShadowPalette[1], 1)
|
||||||
|
allegro5.DrawLine(x1+1, y2+3, x2+2, y2+3, dropShadowPalette[2], 1)
|
||||||
|
allegro5.DrawLine(x2+3, y1+1, x2+3, y2+2, dropShadowPalette[2], 1)
|
||||||
|
}
|
97
ui/spinner.go
Normal file
97
ui/spinner.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &Spinner{}
|
||||||
|
|
||||||
|
type Spinner struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
spin float32
|
||||||
|
circs []*allegro5.Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCirle(r, w int, startAngle, a float64, c color.Color) *allegro5.Bitmap {
|
||||||
|
var width = 2*r + w
|
||||||
|
dest := image.NewRGBA(image.Rect(0, 0, width, width))
|
||||||
|
|
||||||
|
gc := draw2dimg.NewGraphicContext(dest)
|
||||||
|
gc.SetFillColor(color.Transparent)
|
||||||
|
gc.Clear()
|
||||||
|
gc.SetFillColor(c)
|
||||||
|
gc.SetStrokeColor(c)
|
||||||
|
gc.SetLineWidth(float64(w))
|
||||||
|
var rad = float64(r)
|
||||||
|
var cnt = float64(width) * .5
|
||||||
|
var dx1, dy1 = cnt + math.Cos(startAngle)*rad, cnt + math.Sin(startAngle)*rad
|
||||||
|
gc.MoveTo(dx1, dy1)
|
||||||
|
gc.ArcTo(cnt, cnt, rad, rad, startAngle, a)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
bmp, err := allegro5.NewBitmapFromImage(dest, false)
|
||||||
|
if nil != err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Created(ctx Context, p Container) error {
|
||||||
|
s.ControlBase.Created(ctx, p)
|
||||||
|
const numCircs = 32
|
||||||
|
s.circs = make([]*allegro5.Bitmap, numCircs)
|
||||||
|
var am = math.Pi * 2 / float64(numCircs)
|
||||||
|
for i := range s.circs {
|
||||||
|
s.circs[i] = createCirle(8, 3, float64(i)*am, math.Pi, Blue500)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Destroyed(ctx Context) {
|
||||||
|
for _, circ := range s.circs {
|
||||||
|
circ.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Update(ctx Context, dt time.Duration) {
|
||||||
|
var spin = float64(s.spin)
|
||||||
|
spin += dt.Seconds()
|
||||||
|
for spin > 1. {
|
||||||
|
spin -= 1.
|
||||||
|
}
|
||||||
|
s.spin = float32(spin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Render(ctx Context) {
|
||||||
|
var disp = ctx.Display()
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var width = float32(disp.Width())
|
||||||
|
var height = float32(disp.Height())
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var textW = fnt.TextWidth(s.Text)
|
||||||
|
|
||||||
|
allegro5.DrawFilledRectangle(0, 0, width, height, ctx.Palette().Disabled())
|
||||||
|
|
||||||
|
const marginH, marginV float32 = 64, 16
|
||||||
|
const textH float32 = 12
|
||||||
|
var rectW, rectH float32 = textW + 2*marginH, 3*marginV + textH + 32
|
||||||
|
var rectX, rectY = (width - rectW) * .5, (height - rectH) * .5
|
||||||
|
allegro5.DrawFilledRectangle(rectX, rectY, rectX+rectW, rectY+rectH, ctx.Palette().White())
|
||||||
|
DropShadow(rectX, rectY, rectX+rectW, rectY+rectH)
|
||||||
|
fnt.Draw(rectX+marginH, rectY+marginV, ctx.Palette().Black(), allegro5.AlignLeft, s.Text)
|
||||||
|
|
||||||
|
const numCircs = 32
|
||||||
|
var i = int(math.Floor(float64(s.spin) * numCircs))
|
||||||
|
s.circs[i].DrawOptions(width*.5, rectY+2*marginV+2*textH, allegro5.DrawOptions{Center: true})
|
||||||
|
|
||||||
|
s.ControlBase.Render(ctx)
|
||||||
|
}
|
70
ui/state.go
Normal file
70
ui/state.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State interface {
|
||||||
|
Enter(Context) error
|
||||||
|
Leave(Context) error
|
||||||
|
Update(Context, time.Duration) (State, error)
|
||||||
|
Handle(Context, allegro5.Event) error
|
||||||
|
Render(Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateBase struct {
|
||||||
|
Control Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateBase) Enter(ctx Context) error {
|
||||||
|
if nil != s.Control {
|
||||||
|
err := s.Control.Created(ctx, nil)
|
||||||
|
if nil != err {
|
||||||
|
log.Println("error creating control")
|
||||||
|
s.Control = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateBase) Leave(ctx Context) error {
|
||||||
|
if nil != s.Control {
|
||||||
|
s.Control.Destroyed(ctx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateBase) Update(ctx Context, dt time.Duration) (State, error) {
|
||||||
|
if nil != s.Control {
|
||||||
|
s.Control.Update(ctx, dt)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateBase) Handle(ctx Context, ev allegro5.Event) error {
|
||||||
|
if nil != s.Control {
|
||||||
|
s.Control.Handle(ctx, ev)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Arrange(ctx Context, c Control, rect geom.RectangleF) {
|
||||||
|
if cont, ok := c.(Container); ok {
|
||||||
|
cont.Arrange(ctx, rect)
|
||||||
|
} else {
|
||||||
|
c.SetRect(rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateBase) Render(ctx Context) error {
|
||||||
|
if nil != s.Control {
|
||||||
|
var disp = ctx.Display()
|
||||||
|
Arrange(ctx, s.Control, geom.RectF(0, 0, float64(disp.Width()), float64(disp.Height())))
|
||||||
|
s.Control.Render(ctx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
36
ui/statusbar.go
Normal file
36
ui/statusbar.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"opslag.de/schobers/galleg/allegro5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &StatusBar{}
|
||||||
|
|
||||||
|
const statusBarHeight = 24
|
||||||
|
|
||||||
|
type StatusBar struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
RightText string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatusBar) DesiredSize(Context) geom.PointF {
|
||||||
|
return geom.PtF(math.NaN(), statusBarHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatusBar) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = b.Bounds.Min.To32()
|
||||||
|
var max = b.Bounds.Max.To32()
|
||||||
|
allegro5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, ctx.Palette().Primary())
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
fnt.Draw(min.X+leftMargin, min.Y+topMargin, ctx.Palette().White(), allegro5.AlignLeft, b.Text)
|
||||||
|
fnt.Draw(max.X-leftMargin, min.Y+topMargin, ctx.Palette().White(), allegro5.AlignRight, b.RightText)
|
||||||
|
|
||||||
|
b.ControlBase.Render(ctx)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user