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