Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
23ce18b51b | |||
ad697d5508 | |||
bb803e4cc3 | |||
9d4b097352 | |||
eb0b660ab6 | |||
c2009c5a8b | |||
239c24533d | |||
115f9877c1 | |||
8157d8b3d8 |
@ -1,6 +0,0 @@
|
|||||||
// +build windows,static
|
|
||||||
|
|
||||||
package allegro5
|
|
||||||
|
|
||||||
// #cgo LDFLAGS: -lallegro_monolith-static -ljpeg -ldumb -lFLAC -lfreetype -lvorbisfile -lvorbis -logg -lphysfs -lpng16 -lzlib -luuid -lkernel32 -lwinmm -lpsapi -lopengl32 -lglu32 -luser32 -lcomdlg32 -lgdi32 -lshell32 -lole32 -ladvapi32 -lws2_32 -lshlwapi -lstdc++
|
|
||||||
import "C"
|
|
@ -1,16 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
type Color struct {
|
|
||||||
color C.ALLEGRO_COLOR
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewColor(r, g, b byte) Color {
|
|
||||||
return Color{C.al_map_rgb(C.uchar(r), C.uchar(g), C.uchar(b))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewColorAlpha(r, g, b, a byte) Color {
|
|
||||||
return Color{C.al_map_rgba(C.uchar(r), C.uchar(g), C.uchar(b), C.uchar(a))}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
@ -16,6 +16,7 @@ type Bitmap struct {
|
|||||||
bitmap *C.ALLEGRO_BITMAP
|
bitmap *C.ALLEGRO_BITMAP
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
|
subs []*Bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
type DrawOptions struct {
|
type DrawOptions struct {
|
||||||
@ -66,7 +67,7 @@ func newBitmap(width, height int, mut func(m FlagMutation), flags []NewBitmapFla
|
|||||||
if nil == b {
|
if nil == b {
|
||||||
return nil, errors.New("error creating bitmap")
|
return nil, errors.New("error creating bitmap")
|
||||||
}
|
}
|
||||||
return &Bitmap{b, width, height}, nil
|
return &Bitmap{b, width, height, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBitmap creates a new bitmap of given width and height and optional flags
|
// NewBitmap creates a new bitmap of given width and height and optional flags
|
||||||
@ -133,7 +134,7 @@ func NewBitmapFromImage(im image.Image, video bool) (*Bitmap, error) {
|
|||||||
})
|
})
|
||||||
C.al_convert_bitmap(b)
|
C.al_convert_bitmap(b)
|
||||||
}
|
}
|
||||||
return &Bitmap{b, width, height}, nil
|
return &Bitmap{b, width, height, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadBitmap tries to load the image at the specified path as a bitmap
|
// LoadBitmap tries to load the image at the specified path as a bitmap
|
||||||
@ -146,7 +147,7 @@ func LoadBitmap(path string) (*Bitmap, error) {
|
|||||||
}
|
}
|
||||||
width := int(C.al_get_bitmap_width(b))
|
width := int(C.al_get_bitmap_width(b))
|
||||||
height := int(C.al_get_bitmap_height(b))
|
height := int(C.al_get_bitmap_height(b))
|
||||||
return &Bitmap{b, width, height}, nil
|
return &Bitmap{b, width, height, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws the bitmap at the given location
|
// Draw draws the bitmap at the given location
|
||||||
@ -206,6 +207,22 @@ func (b *Bitmap) DrawOptions(left, top float32, options DrawOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sub creates a sub-bitmap of the original bitmap
|
||||||
|
func (b *Bitmap) Sub(x, y, w, h int) *Bitmap {
|
||||||
|
var sub = C.al_create_sub_bitmap(b.bitmap, C.int(x), C.int(y), C.int(w), C.int(h))
|
||||||
|
if nil == sub {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bmp = &Bitmap{sub, w, h, nil}
|
||||||
|
b.subs = append(b.subs, bmp)
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subs returns the slice of sub-bitmaps
|
||||||
|
func (b *Bitmap) Subs() []*Bitmap {
|
||||||
|
return b.subs
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Bitmap) Width() int {
|
func (b *Bitmap) Width() int {
|
||||||
return b.width
|
return b.width
|
||||||
}
|
}
|
||||||
@ -220,5 +237,13 @@ func (b *Bitmap) SetAsTarget() {
|
|||||||
|
|
||||||
// Destroy destroys the bitmap
|
// Destroy destroys the bitmap
|
||||||
func (b *Bitmap) Destroy() {
|
func (b *Bitmap) Destroy() {
|
||||||
C.al_destroy_bitmap(b.bitmap)
|
var bmp = b.bitmap
|
||||||
|
if nil == bmp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.bitmap = nil
|
||||||
|
for _, sub := range b.subs {
|
||||||
|
sub.Destroy()
|
||||||
|
}
|
||||||
|
C.al_destroy_bitmap(bmp)
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #cgo pkg-config: allegro-5 allegro_font-5 allegro_image-5 allegro_primitives-5 allegro_ttf-5
|
// #cgo pkg-config: allegro-5 allegro_font-5 allegro_image-5 allegro_primitives-5 allegro_ttf-5
|
||||||
import "C"
|
import "C"
|
@ -1,6 +1,6 @@
|
|||||||
// +build windows,!static
|
// +build windows,!static
|
||||||
|
|
||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #cgo LDFLAGS: -lallegro -lallegro_font -lallegro_image -lallegro_primitives -lallegro_ttf
|
// #cgo LDFLAGS: -lallegro -lallegro_font -lallegro_image -lallegro_primitives -lallegro_ttf
|
||||||
import "C"
|
import "C"
|
6
allg5/c_windows_static.go
Normal file
6
allg5/c_windows_static.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// +build windows,static
|
||||||
|
|
||||||
|
package allg5
|
||||||
|
|
||||||
|
// #cgo LDFLAGS: -lallegro_monolith-static -static -ljpeg -ldumb -lFLAC -lfreetype -lvorbisfile -lvorbis -logg -lphysfs -lpng16 -lzlib -luuid -lkernel32 -lwinmm -lpsapi -lopengl32 -lglu32 -luser32 -lcomdlg32 -lgdi32 -lshell32 -lole32 -ladvapi32 -lws2_32 -lshlwapi -lstdc++
|
||||||
|
import "C"
|
31
allg5/color.go
Normal file
31
allg5/color.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package allg5
|
||||||
|
|
||||||
|
// #include <allegro5/allegro.h>
|
||||||
|
import "C"
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
var _ color.Color = &Color{}
|
||||||
|
|
||||||
|
type Color struct {
|
||||||
|
color C.ALLEGRO_COLOR
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColor(r, g, b byte) Color {
|
||||||
|
return Color{C.al_map_rgb(C.uchar(r), C.uchar(g), C.uchar(b))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColorAlpha(r, g, b, a byte) Color {
|
||||||
|
return Color{C.al_map_rgba(C.uchar(r), C.uchar(g), C.uchar(b), C.uchar(a))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGBA implements the color.Color interface.
|
||||||
|
func (c Color) RGBA() (r, g, b, a uint32) {
|
||||||
|
var cr, cg, cb, ca C.uchar
|
||||||
|
C.al_unmap_rgba(c.color, &cr, &cg, &cb, &ca)
|
||||||
|
a = uint32(ca)
|
||||||
|
r = uint32(cr) * a
|
||||||
|
g = uint32(cg) * a
|
||||||
|
b = uint32(cb) * a
|
||||||
|
a *= a
|
||||||
|
return
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
||||||
@ -74,6 +74,10 @@ func (d *Display) SetAsTarget() {
|
|||||||
C.al_set_target_backbuffer(d.display)
|
C.al_set_target_backbuffer(d.display)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Display) SetIcon(i *Bitmap) {
|
||||||
|
C.al_set_display_icon(d.display, i.bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Display) SetMousePosition(x, y int) {
|
func (d *Display) SetMousePosition(x, y int) {
|
||||||
C.al_set_mouse_xy(d.display, C.int(x), C.int(y))
|
C.al_set_mouse_xy(d.display, C.int(x), C.int(y))
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
import "C"
|
import "C"
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
// #include <allegro5/allegro_font.h>
|
// #include <allegro5/allegro_font.h>
|
||||||
@ -12,6 +12,9 @@ import (
|
|||||||
|
|
||||||
type Font struct {
|
type Font struct {
|
||||||
font *C.ALLEGRO_FONT
|
font *C.ALLEGRO_FONT
|
||||||
|
hght float32
|
||||||
|
asc float32
|
||||||
|
desc float32
|
||||||
}
|
}
|
||||||
|
|
||||||
type HorizontalAlignment int
|
type HorizontalAlignment int
|
||||||
@ -30,7 +33,7 @@ func LoadTTFFont(path string, size int) (*Font, error) {
|
|||||||
if nil == f {
|
if nil == f {
|
||||||
return nil, fmt.Errorf("unable to load ttf font '%s'", path)
|
return nil, fmt.Errorf("unable to load ttf font '%s'", path)
|
||||||
}
|
}
|
||||||
return &Font{f}, nil
|
return &Font{f, 0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) drawFlags(a HorizontalAlignment) C.int {
|
func (f *Font) drawFlags(a HorizontalAlignment) C.int {
|
||||||
@ -53,6 +56,45 @@ func (f *Font) Draw(left, top float32, color Color, align HorizontalAlignment, t
|
|||||||
C.al_draw_text(f.font, color.color, C.float(left), C.float(top), flags, t)
|
C.al_draw_text(f.font, color.color, C.float(left), C.float(top), flags, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ascent returns the ascent of the font
|
||||||
|
func (f *Font) Ascent() float32 {
|
||||||
|
if 0 == f.asc {
|
||||||
|
f.asc = float32(C.al_get_font_ascent(f.font))
|
||||||
|
}
|
||||||
|
return f.asc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descent returns the descent of the font.
|
||||||
|
func (f *Font) Descent() float32 {
|
||||||
|
if 0 == f.desc {
|
||||||
|
f.desc = float32(C.al_get_font_descent(f.font))
|
||||||
|
}
|
||||||
|
return f.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the height of the font
|
||||||
|
func (f *Font) Height() float32 {
|
||||||
|
if 0 == f.hght {
|
||||||
|
f.hght = f.Ascent() + f.Descent()
|
||||||
|
}
|
||||||
|
return f.hght
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextDimensions returns the bounding box of the rendered text.
|
||||||
|
func (f *Font) TextDimensions(text string) (x, y, w, h float32) {
|
||||||
|
t := C.CString(text)
|
||||||
|
defer C.free(unsafe.Pointer(t))
|
||||||
|
|
||||||
|
var bbx, bby, bbw, bbh C.int
|
||||||
|
C.al_get_text_dimensions(f.font, t, &bbx, &bby, &bbw, &bbh)
|
||||||
|
x = float32(bbx)
|
||||||
|
y = float32(bby)
|
||||||
|
w = float32(bbw)
|
||||||
|
h = float32(bbh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextWidth returns the width of the rendered text.
|
||||||
func (f *Font) TextWidth(text string) float32 {
|
func (f *Font) TextWidth(text string) float32 {
|
||||||
t := C.CString(text)
|
t := C.CString(text)
|
||||||
defer C.free(unsafe.Pointer(t))
|
defer C.free(unsafe.Pointer(t))
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
import "C"
|
import "C"
|
@ -1,6 +1,6 @@
|
|||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
// #include <allegro5/allegro_windows.h>
|
// #include <allegro5/allegro_windows.h>
|
@ -1,8 +1,9 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
// #include <allegro5/allegro_primitives.h>
|
// #include <allegro5/allegro_primitives.h>
|
||||||
import "C"
|
import "C"
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
func DrawFilledRectangle(x1, y1, x2, y2 float32, c Color) {
|
func DrawFilledRectangle(x1, y1, x2, y2 float32, c Color) {
|
||||||
C.al_draw_filled_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color)
|
C.al_draw_filled_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color)
|
||||||
@ -16,6 +17,10 @@ func DrawLine(x1, y1, x2, y2 float32, c Color, thickness float32) {
|
|||||||
C.al_draw_line(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
|
C.al_draw_line(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DrawPolyline(vertices []float32, c Color, thickness float32) {
|
||||||
|
C.al_draw_polyline((*C.float)(unsafe.Pointer(&vertices[0])), 8, C.int(len(vertices)>>1), C.ALLEGRO_LINE_JOIN_ROUND, C.ALLEGRO_LINE_CAP_ROUND, c.color, C.float(thickness), 1)
|
||||||
|
}
|
||||||
|
|
||||||
func DrawRectangle(x1, y1, x2, y2 float32, c Color, thickness float32) {
|
func DrawRectangle(x1, y1, x2, y2 float32, c Color, thickness float32) {
|
||||||
C.al_draw_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
|
C.al_draw_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package allegro5
|
package allg5
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
// #include <allegro5/allegro.h>
|
||||||
// #include <allegro5/allegro_font.h>
|
// #include <allegro5/allegro_font.h>
|
54
ui/button.go
Normal file
54
ui/button.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Button struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
HorizontalAlignment allg5.HorizontalAlignment
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewButton(text string, click MouseClickFn) *Button {
|
||||||
|
return &Button{ControlBase: ControlBase{OnClick: click}, Text: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewButtonAlign(text string, click MouseClickFn, align allg5.HorizontalAlignment) *Button {
|
||||||
|
return &Button{ControlBase: ControlBase{OnClick: click}, Text: text, HorizontalAlignment: align}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Button) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var w = fnt.TextWidth(b.Text)
|
||||||
|
var fntH = fnt.Height()
|
||||||
|
return geom.PtF32(w+fntH, 2*fntH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Button) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = b.Bounds.Min
|
||||||
|
var max = b.Bounds.Max
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var fntH = fnt.Height()
|
||||||
|
|
||||||
|
var back = ctx.Palette().Primary()
|
||||||
|
if b.IsOver && !b.IsPressed {
|
||||||
|
back = ctx.Palette().PrimaryHighlight()
|
||||||
|
}
|
||||||
|
allg5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, back)
|
||||||
|
switch b.HorizontalAlignment {
|
||||||
|
case allg5.AlignLeft:
|
||||||
|
fnt.Draw(min.X+.5*fntH, min.Y+.5*fntH, ctx.Palette().Lightest(), allg5.AlignLeft, b.Text)
|
||||||
|
case allg5.AlignCenter:
|
||||||
|
fnt.Draw(.5*(min.X+max.X), min.Y+.5*fntH, ctx.Palette().Lightest(), allg5.AlignCenter, b.Text)
|
||||||
|
case allg5.AlignRight:
|
||||||
|
fnt.Draw(min.X-.5*fntH, min.Y+.5*fntH, ctx.Palette().Lightest(), allg5.AlignRight, b.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ControlBase.Render(ctx)
|
||||||
|
}
|
112
ui/checkbox.go
Normal file
112
ui/checkbox.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckboxValueChangedFn func(bool)
|
||||||
|
|
||||||
|
func drawCheckedBitmap(fill, stroke color.Color) *allg5.Bitmap {
|
||||||
|
return drawBitmap(checkboxSize, checkboxSize, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
var size float64 = checkboxSize
|
||||||
|
var margin float64 = checkboxMargin
|
||||||
|
|
||||||
|
gc.SetFillColor(fill)
|
||||||
|
draw2dkit.RoundedRectangle(gc, margin, margin, size-margin, size-margin, margin, margin)
|
||||||
|
gc.Fill()
|
||||||
|
|
||||||
|
gc.SetStrokeColor(stroke)
|
||||||
|
gc.SetLineWidth(1.5)
|
||||||
|
gc.MoveTo(7, 12)
|
||||||
|
gc.LineTo(10.5, 15.5)
|
||||||
|
gc.LineTo(17, 9)
|
||||||
|
gc.Stroke()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawUncheckedBitmap(fill, stroke color.Color) *allg5.Bitmap {
|
||||||
|
return drawBitmap(checkboxSize, checkboxSize, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
var size float64 = checkboxSize
|
||||||
|
var margin float64 = checkboxMargin
|
||||||
|
|
||||||
|
gc.SetLineWidth(2)
|
||||||
|
gc.SetStrokeColor(stroke)
|
||||||
|
draw2dkit.RoundedRectangle(gc, margin+1, margin+1, size-margin-1, size-margin-1, margin-1, margin-1)
|
||||||
|
gc.Stroke()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Checkbox struct {
|
||||||
|
ControlBase
|
||||||
|
Value bool
|
||||||
|
Text string
|
||||||
|
OnChanged CheckboxValueChangedFn
|
||||||
|
checked *allg5.Bitmap
|
||||||
|
unchecked *allg5.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.checked = drawCheckedBitmap(plt.Primary(), plt.Lightest())
|
||||||
|
c.unchecked = drawUncheckedBitmap(plt.Lightest(), plt.Darkest())
|
||||||
|
if nil == c.checked || nil == c.unchecked {
|
||||||
|
return fmt.Errorf("error creating checkboxes")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) Handle(ctx Context, ev allg5.Event) {
|
||||||
|
var pressed = c.IsPressed
|
||||||
|
c.ControlBase.Handle(ctx, ev)
|
||||||
|
switch ev.(type) {
|
||||||
|
case *allg5.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.PointF32 {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var w = fnt.TextWidth(c.Text)
|
||||||
|
return geom.PtF32(w+checkboxSize, checkboxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) box() *allg5.Bitmap {
|
||||||
|
if c.Value {
|
||||||
|
return c.checked
|
||||||
|
}
|
||||||
|
return c.unchecked
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = c.Bounds.Min
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
|
||||||
|
fnt.Draw(min.X+checkboxSize, min.Y-.67*fnt.Ascent()+.5*checkboxSize, ctx.Palette().Darkest(), allg5.AlignLeft, c.Text)
|
||||||
|
if c.Disabled {
|
||||||
|
var disabled = ctx.Palette().Disabled()
|
||||||
|
c.box().DrawOptions(min.X, min.Y, allg5.DrawOptions{Tint: &disabled})
|
||||||
|
} else {
|
||||||
|
c.box().Draw(min.X, min.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
112
ui/columns.go
Normal file
112
ui/columns.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type columns struct {
|
||||||
|
ContainerBase
|
||||||
|
cols []*column
|
||||||
|
col int
|
||||||
|
}
|
||||||
|
|
||||||
|
type column struct {
|
||||||
|
p DockPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *column) Append(ctrl Control) {
|
||||||
|
c.p.Append(DockTop, ctrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *column) AppendCreate(ctx Context, ctrl Control) {
|
||||||
|
c.p.AppendCreate(ctx, DockTop, ctrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Columns interface {
|
||||||
|
Container
|
||||||
|
Columns() []Column
|
||||||
|
Append(Control)
|
||||||
|
AppendCreate(Context, Control)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Column interface {
|
||||||
|
Append(Control)
|
||||||
|
AppendCreate(Context, Control)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColumns(cols int, ctrls ...Control) Columns {
|
||||||
|
var c = &columns{}
|
||||||
|
for i := 0; i < cols; i++ {
|
||||||
|
c.addColumn()
|
||||||
|
}
|
||||||
|
for _, ctrl := range ctrls {
|
||||||
|
c.Append(ctrl)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) addColumn() {
|
||||||
|
var p = NewDockPanel(c)
|
||||||
|
c.ContainerBase.Append(p)
|
||||||
|
c.cols = append(c.cols, &column{p})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) next() {
|
||||||
|
c.col = (c.col + 1) % len(c.cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) Columns() []Column {
|
||||||
|
var cols = make([]Column, len(c.cols))
|
||||||
|
for i, col := range c.cols {
|
||||||
|
cols[i] = col
|
||||||
|
}
|
||||||
|
return cols
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) Append(ctrl Control) {
|
||||||
|
c.cols[c.col].Append(ctrl)
|
||||||
|
c.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) AppendCreate(ctx Context, ctrl Control) {
|
||||||
|
c.cols[c.col].AppendCreate(ctx, ctrl)
|
||||||
|
c.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
var w float32
|
||||||
|
var h float32
|
||||||
|
for _, col := range c.cols {
|
||||||
|
var sz = col.p.DesiredSize(ctx)
|
||||||
|
if !geom.IsNaN32(w) {
|
||||||
|
if geom.IsNaN32(sz.X) {
|
||||||
|
w = sz.X
|
||||||
|
} else {
|
||||||
|
w += sz.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !geom.IsNaN32(h) {
|
||||||
|
if geom.IsNaN32(sz.Y) {
|
||||||
|
h = sz.Y
|
||||||
|
} else {
|
||||||
|
h = geom.Max32(h, sz.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return geom.PtF32(w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *columns) Arrange(ctx Context, rect geom.RectangleF32) {
|
||||||
|
c.ContainerBase.SetRect(rect)
|
||||||
|
var w, h = rect.Dx(), rect.Dy()
|
||||||
|
var cols = float32(len(c.cols))
|
||||||
|
for i, col := range c.cols {
|
||||||
|
var ii = float32(i)
|
||||||
|
var colH = col.p.DesiredSize(ctx).Y
|
||||||
|
if colH > h {
|
||||||
|
colH = h
|
||||||
|
}
|
||||||
|
var colR = geom.RectF32(rect.Min.X+ii*w/cols, rect.Min.Y, rect.Min.X+(ii+1)*w/cols, rect.Min.Y+colH)
|
||||||
|
Arrange(ctx, col.p, colR)
|
||||||
|
}
|
||||||
|
}
|
106
ui/container.go
Normal file
106
ui/container.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container interface {
|
||||||
|
Control
|
||||||
|
Children() []Control
|
||||||
|
Arrange(Context, geom.RectangleF32)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.RectangleF32) {
|
||||||
|
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 allg5.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)
|
||||||
|
}
|
201
ui/contentscrollbar.go
Normal file
201
ui/contentscrollbar.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &ContentScrollbar{}
|
||||||
|
|
||||||
|
type ContentScrollbarValueChangedFn func(float32)
|
||||||
|
|
||||||
|
type ContentScrollbar struct {
|
||||||
|
ControlBase
|
||||||
|
Length float32
|
||||||
|
Value float32
|
||||||
|
Orientation Orientation
|
||||||
|
OnChanged ContentScrollbarValueChangedFn
|
||||||
|
handle *contentScrollbarHandle
|
||||||
|
barLength float32
|
||||||
|
scrollDistance float32
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
var max = h.Bounds.Max
|
||||||
|
allg5.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) length() (float32, float32) {
|
||||||
|
var min, max float32
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
min = s.Bounds.Min.X
|
||||||
|
max = s.Bounds.Max.X
|
||||||
|
default:
|
||||||
|
min = s.Bounds.Min.Y
|
||||||
|
max = s.Bounds.Max.Y
|
||||||
|
}
|
||||||
|
return max - min, min
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) updateBarLength() {
|
||||||
|
var length, _ = s.length()
|
||||||
|
var bar = length
|
||||||
|
if s.Length > length {
|
||||||
|
bar = length * length / s.Length
|
||||||
|
}
|
||||||
|
if bar < 20 {
|
||||||
|
if length < 40 {
|
||||||
|
bar = .5 * length
|
||||||
|
} else {
|
||||||
|
bar = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.barLength = bar
|
||||||
|
var d = s.Length - length
|
||||||
|
if d < 0 {
|
||||||
|
d = 0
|
||||||
|
}
|
||||||
|
s.scrollDistance = d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) barViewCenter() float32 {
|
||||||
|
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 float32) float32 {
|
||||||
|
var pos = y
|
||||||
|
if OrientationHorizontal == s.Orientation {
|
||||||
|
pos = x
|
||||||
|
}
|
||||||
|
var length, min = s.length()
|
||||||
|
if length == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var offset = (pos - .5*s.barLength - min) / (length - s.barLength)
|
||||||
|
if offset < 0 {
|
||||||
|
return 0
|
||||||
|
} else if offset > 1 {
|
||||||
|
return s.scrollDistance
|
||||||
|
}
|
||||||
|
return s.scrollDistance * offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) change(v float32) {
|
||||||
|
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(float32(x), float32(y))
|
||||||
|
s.change(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) increment(d int) {
|
||||||
|
if s.Orientation == OrientationVertical {
|
||||||
|
d *= -1
|
||||||
|
}
|
||||||
|
var val = s.Value + float32(d)*ScrollbarWidth
|
||||||
|
if val < 0 {
|
||||||
|
val = 0
|
||||||
|
} else if val > s.scrollDistance {
|
||||||
|
val = s.scrollDistance
|
||||||
|
}
|
||||||
|
s.change(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) Handle(ctx Context, ev allg5.Event) {
|
||||||
|
s.ControlBase.Handle(ctx, ev)
|
||||||
|
s.handle.Handle(ctx, ev)
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allg5.MouseMoveEvent:
|
||||||
|
if s.handle.IsPressed {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
if 0 != e.DeltaZ && s.IsOver {
|
||||||
|
var d = e.DeltaZ
|
||||||
|
if allg5.IsAnyKeyDown(allg5.KeyLShift, allg5.KeyRShift) {
|
||||||
|
d *= 10
|
||||||
|
}
|
||||||
|
s.increment(d)
|
||||||
|
}
|
||||||
|
case *allg5.MouseButtonDownEvent:
|
||||||
|
if !s.handle.IsPressed && s.IsOver {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) DesiredSize(Context) geom.PointF32 {
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return geom.PtF32(geom.NaN32(), ScrollbarWidth)
|
||||||
|
}
|
||||||
|
return geom.PtF32(ScrollbarWidth, geom.NaN32())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentScrollbar) SetRect(rect geom.RectangleF32) {
|
||||||
|
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)
|
||||||
|
s.updateBarLength()
|
||||||
|
|
||||||
|
var offset float32
|
||||||
|
if 0 < s.scrollDistance {
|
||||||
|
offset = s.Value / s.scrollDistance
|
||||||
|
}
|
||||||
|
var length, min = s.length()
|
||||||
|
var begin = min + (length-s.barLength)*offset
|
||||||
|
var end = begin + s.barLength
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
s.handle.SetRect(geom.RectF32(begin, rect.Min.Y, end, rect.Max.Y))
|
||||||
|
default:
|
||||||
|
s.handle.SetRect(geom.RectF32(rect.Min.X, begin, rect.Max.X, end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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/zntg/allg5"
|
||||||
|
|
||||||
|
type Context interface {
|
||||||
|
Display() *allg5.Display
|
||||||
|
Fonts() Fonts
|
||||||
|
Palette() Palette
|
||||||
|
Debug() Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
type Debug interface {
|
||||||
|
IsEnabled() bool
|
||||||
|
Rainbow() allg5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Context = &context{}
|
||||||
|
|
||||||
|
type context struct {
|
||||||
|
disp *allg5.Display
|
||||||
|
fts Fonts
|
||||||
|
pal Palette
|
||||||
|
dbg *debug
|
||||||
|
}
|
||||||
|
|
||||||
|
type debug struct {
|
||||||
|
enbl bool
|
||||||
|
rainb []allg5.Color
|
||||||
|
col int
|
||||||
|
}
|
||||||
|
|
||||||
|
func rainbow() []allg5.Color {
|
||||||
|
var colors = make([]allg5.Color, len(Colors500))
|
||||||
|
for i, c := range Colors500 {
|
||||||
|
colors[i] = NewColorAlpha(c, 0x7f)
|
||||||
|
}
|
||||||
|
return colors
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContext(disp *allg5.Display, f Fonts) *context {
|
||||||
|
return &context{disp, f, DefaultPalette(), &debug{rainb: rainbow()}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Display() *allg5.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() allg5.Color {
|
||||||
|
var col = d.col
|
||||||
|
d.col = (col + 1) % len(d.rainb)
|
||||||
|
return d.rainb[col]
|
||||||
|
}
|
90
ui/control.go
Normal file
90
ui/control.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Control interface {
|
||||||
|
Created(Context, Container) error
|
||||||
|
Destroyed(Context)
|
||||||
|
|
||||||
|
Update(Context, time.Duration)
|
||||||
|
Handle(Context, allg5.Event)
|
||||||
|
DesiredSize(Context) geom.PointF32
|
||||||
|
Rect() geom.RectangleF32
|
||||||
|
SetRect(geom.RectangleF32)
|
||||||
|
Render(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MouseClickFn func(Control)
|
||||||
|
|
||||||
|
var _ Control = &ControlBase{}
|
||||||
|
|
||||||
|
type ControlBase struct {
|
||||||
|
Parent Container
|
||||||
|
Bounds geom.RectangleF32
|
||||||
|
Disabled bool
|
||||||
|
IsOver bool
|
||||||
|
IsPressed bool
|
||||||
|
OnClick MouseClickFn
|
||||||
|
MinSize geom.PointF32
|
||||||
|
Background *allg5.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 allg5.Event) {
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allg5.MouseMoveEvent:
|
||||||
|
c.IsOver = c.IsInRect(float32(e.X), float32(e.Y))
|
||||||
|
case *allg5.MouseButtonDownEvent:
|
||||||
|
if c.IsOver {
|
||||||
|
c.IsPressed = true
|
||||||
|
}
|
||||||
|
case *allg5.MouseButtonUpEvent:
|
||||||
|
if c.IsPressed && c.IsOver {
|
||||||
|
var onClick = c.OnClick
|
||||||
|
if nil != onClick {
|
||||||
|
onClick(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.IsPressed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) DesiredSize(Context) geom.PointF32 {
|
||||||
|
return c.MinSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) SetRect(rect geom.RectangleF32) {
|
||||||
|
c.Bounds = rect
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) Render(ctx Context) {
|
||||||
|
var min = c.Bounds.Min
|
||||||
|
var max = c.Bounds.Max
|
||||||
|
if nil != c.Background {
|
||||||
|
allg5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, *c.Background)
|
||||||
|
}
|
||||||
|
if ctx.Debug().IsEnabled() {
|
||||||
|
allg5.DrawRectangle(min.X, min.Y, max.X, max.Y, ctx.Debug().Rainbow(), 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) Rect() geom.RectangleF32 {
|
||||||
|
return c.Bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ControlBase) IsInRect(x, y float32) bool {
|
||||||
|
return geom.PtF32(x, y).In(c.Bounds)
|
||||||
|
}
|
5
ui/dimensions.go
Normal file
5
ui/dimensions.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
const leftMargin = 8
|
||||||
|
const checkboxMargin = 4
|
||||||
|
const checkboxSize = 24
|
152
ui/dockpanel.go
Normal file
152
ui/dockpanel.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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 NewDockPanelContent(parent Container, d Dock, controls ...Control) DockPanel {
|
||||||
|
var p = NewDockPanel(parent)
|
||||||
|
for _, c := range controls {
|
||||||
|
p.Append(d, c)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
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.PointF32 {
|
||||||
|
var width, height float32 = 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 !geom.IsNaN32(width) {
|
||||||
|
if geom.IsNaN32(desired.X) {
|
||||||
|
width = desired.X
|
||||||
|
} else {
|
||||||
|
width += desired.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !geom.IsNaN32(desired.Y) {
|
||||||
|
height = geom.Max32(height, desired.Y)
|
||||||
|
}
|
||||||
|
case d == DockTop || d == DockBottom:
|
||||||
|
if !geom.IsNaN32(height) {
|
||||||
|
if geom.IsNaN32(desired.Y) {
|
||||||
|
height = desired.Y
|
||||||
|
} else {
|
||||||
|
height += desired.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !geom.IsNaN32(desired.X) {
|
||||||
|
width = geom.Max32(width, desired.X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return geom.PtF32(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) arrangeChildren(ctx Context) []geom.RectangleF32 {
|
||||||
|
var n = len(p.children)
|
||||||
|
var rects = make([]geom.RectangleF32, 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 float32
|
||||||
|
switch {
|
||||||
|
case d == DockLeft || d == DockRight:
|
||||||
|
width = rem.Dx()
|
||||||
|
if !geom.IsNaN32(desired.X) {
|
||||||
|
width = geom.Min32(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 !geom.IsNaN32(desired.Y) {
|
||||||
|
height = geom.Min32(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.RectF32(left, top, left+width, top+height)
|
||||||
|
switch d {
|
||||||
|
case DockLeft:
|
||||||
|
rem = geom.RectF32(rem.Min.X+width, rem.Min.Y, rem.Max.X, rem.Max.Y)
|
||||||
|
case DockTop:
|
||||||
|
rem = geom.RectF32(rem.Min.X, rem.Min.Y+height, rem.Max.X, rem.Max.Y)
|
||||||
|
case DockRight:
|
||||||
|
rem = geom.RectF32(rem.Min.X, rem.Min.Y, rem.Max.X-width, rem.Max.Y)
|
||||||
|
case DockBottom:
|
||||||
|
rem = geom.RectF32(rem.Min.X, rem.Min.Y, rem.Max.X, rem.Max.Y-height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dockPanel) Arrange(ctx Context, rect geom.RectangleF32) {
|
||||||
|
p.ContainerBase.SetRect(rect)
|
||||||
|
var rects = p.arrangeChildren(ctx)
|
||||||
|
for i, child := range p.children {
|
||||||
|
Arrange(ctx, child, rects[i])
|
||||||
|
}
|
||||||
|
}
|
38
ui/draw.go
Normal file
38
ui/draw.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawBitmap(w, h int, draw func(*draw2dimg.GraphicContext)) *allg5.Bitmap {
|
||||||
|
dest := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||||
|
gc := draw2dimg.NewGraphicContext(dest)
|
||||||
|
gc.SetFillColor(color.Transparent)
|
||||||
|
gc.Clear()
|
||||||
|
draw(gc)
|
||||||
|
bmp, err := allg5.NewBitmapFromImage(dest, false)
|
||||||
|
if nil != err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawCircle(r, w int, startAngle, a float64, c color.Color) *allg5.Bitmap {
|
||||||
|
var width = 2*r + w
|
||||||
|
return drawBitmap(width, width, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
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/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fonts interface {
|
||||||
|
Register(name, path string, size int) error
|
||||||
|
Get(name string) *allg5.Font
|
||||||
|
Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
type fonts struct {
|
||||||
|
fts map[string]*allg5.Font
|
||||||
|
dir vfs.CopyDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFonts(dir vfs.CopyDir) *fonts {
|
||||||
|
return &fonts{make(map[string]*allg5.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 := allg5.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) *allg5.Font {
|
||||||
|
return fts.fts[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fts *fonts) Destroy() {
|
||||||
|
for _, f := range fts.fts {
|
||||||
|
f.Destroy()
|
||||||
|
}
|
||||||
|
fts.dir.Destroy()
|
||||||
|
}
|
30
ui/label.go
Normal file
30
ui/label.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
HorizontalAlignment allg5.HorizontalAlignment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Label) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var _, _, w, h = fnt.TextDimensions(l.Text)
|
||||||
|
return geom.PtF32(w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Label) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = l.Bounds.Min
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
fnt.Draw(min.X, min.Y+fnt.Height()-fnt.Ascent(), ctx.Palette().Darkest(), l.HorizontalAlignment, 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/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shouldClose(ev allg5.Event) bool {
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allg5.KeyCharEvent:
|
||||||
|
switch e.KeyCode {
|
||||||
|
case allg5.KeyEscape:
|
||||||
|
return true
|
||||||
|
case allg5.KeyF4:
|
||||||
|
if e.Modifiers&allg5.KeyModAlt == allg5.KeyModAlt {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *allg5.DisplayCloseEvent:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldToggleDebug(ev allg5.Event) bool {
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allg5.KeyCharEvent:
|
||||||
|
switch e.KeyCode {
|
||||||
|
case allg5.KeyD:
|
||||||
|
return e.Modifiers&allg5.KeyModAlt == allg5.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 allg5.Init(allg5.InitAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(w, h int, title string, s State, f Fonts, opts *allg5.NewDisplayOptions) error {
|
||||||
|
if nil == opts {
|
||||||
|
opts = &allg5.NewDisplayOptions{}
|
||||||
|
}
|
||||||
|
disp, err := allg5.NewDisplay(w, h, *opts)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
disp.SetWindowTitle(title)
|
||||||
|
defer disp.Destroy()
|
||||||
|
|
||||||
|
evq, err := allg5.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
|
||||||
|
}
|
50
ui/margin.go
Normal file
50
ui/margin.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type margin struct {
|
||||||
|
Wrapper
|
||||||
|
Left, Top, Right, Bottom float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func Margin(c Control, m float32) Control {
|
||||||
|
return &margin{Wrap(c), m, m, m, m}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LeftMargin(c Control, m float32) Control {
|
||||||
|
return &margin{Wrap(c), m, 0, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TopMargin(c Control, m float32) Control {
|
||||||
|
return &margin{Wrap(c), 0, m, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HorizontalMargin(c Control, m float32) Control {
|
||||||
|
return &margin{Wrap(c), m, 0, m, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerticalMargin(c Control, m float32) Control {
|
||||||
|
return &margin{Wrap(c), 0, m, 0, m}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *margin) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
var sz = m.Wrapper.DesiredSize(ctx)
|
||||||
|
return geom.PtF32(sz.X+m.Left+m.Right, sz.Y+m.Top+m.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *margin) Arrange(ctx Context, rect geom.RectangleF32) {
|
||||||
|
m.Wrapper.SetRect(rect)
|
||||||
|
rect.Min.X += m.Left
|
||||||
|
rect.Min.Y += m.Top
|
||||||
|
rect.Max.X -= m.Right
|
||||||
|
rect.Max.Y -= m.Bottom
|
||||||
|
if rect.Min.X > rect.Max.X {
|
||||||
|
var x = .5 * (rect.Min.X + rect.Max.X)
|
||||||
|
rect.Min.X, rect.Max.X = x, x
|
||||||
|
}
|
||||||
|
if rect.Min.Y > rect.Max.Y {
|
||||||
|
var y = .5 * (rect.Min.Y + rect.Max.Y)
|
||||||
|
rect.Min.Y, rect.Max.Y = y, y
|
||||||
|
}
|
||||||
|
Arrange(ctx, m.Wrapped, rect)
|
||||||
|
}
|
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"
|
||||||
|
}
|
||||||
|
}
|
76
ui/palette.go
Normal file
76
ui/palette.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Palette interface {
|
||||||
|
Primary() allg5.Color
|
||||||
|
PrimaryHighlight() allg5.Color
|
||||||
|
PrimaryTransparent() allg5.Color
|
||||||
|
Lightest() allg5.Color
|
||||||
|
Darker() allg5.Color
|
||||||
|
Darkest() allg5.Color
|
||||||
|
Disabled() allg5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type palette struct {
|
||||||
|
primary allg5.Color
|
||||||
|
primaryH allg5.Color
|
||||||
|
primaryT allg5.Color
|
||||||
|
lightest allg5.Color
|
||||||
|
darker allg5.Color
|
||||||
|
darkest allg5.Color
|
||||||
|
disabled allg5.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) Primary() allg5.Color {
|
||||||
|
return p.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) PrimaryHighlight() allg5.Color {
|
||||||
|
return p.primaryH
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) PrimaryTransparent() allg5.Color {
|
||||||
|
return p.primaryT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) Lightest() allg5.Color {
|
||||||
|
return p.lightest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) Darker() allg5.Color {
|
||||||
|
return p.darker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) Darkest() allg5.Color {
|
||||||
|
return p.darkest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *palette) Disabled() allg5.Color {
|
||||||
|
return p.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColor(c *color.RGBA) allg5.Color {
|
||||||
|
return allg5.NewColorAlpha(c.R, c.G, c.B, c.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColorAlpha(c *color.RGBA, a uint8) allg5.Color {
|
||||||
|
return allg5.NewColorAlpha(c.R, c.G, c.B, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultPalette() Palette {
|
||||||
|
var primary = Blue500
|
||||||
|
return &palette{
|
||||||
|
primary: NewColor(primary),
|
||||||
|
primaryH: NewColor(Blue400),
|
||||||
|
primaryT: NewColorAlpha(primary, 96),
|
||||||
|
lightest: allg5.NewColor(0xff, 0xff, 0xff),
|
||||||
|
darker: allg5.NewColorAlpha(0, 0, 0, 188),
|
||||||
|
darkest: allg5.NewColorAlpha(0, 0, 0, 222),
|
||||||
|
disabled: allg5.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
ui/scroll.go
Normal file
77
ui/scroll.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scroll struct {
|
||||||
|
Wrapper
|
||||||
|
Content Control
|
||||||
|
Bar *ContentScrollbar
|
||||||
|
}
|
||||||
|
|
||||||
|
func Scroll(c Control, o Orientation) Control {
|
||||||
|
var dock = NewDockPanel(nil)
|
||||||
|
var bar = &ContentScrollbar{Orientation: o}
|
||||||
|
switch o {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
dock.Append(DockBottom, TopMargin(bar, ScrollbarWidth))
|
||||||
|
dock.Append(DockBottom, c)
|
||||||
|
case OrientationVertical:
|
||||||
|
dock.Append(DockRight, LeftMargin(bar, ScrollbarWidth))
|
||||||
|
dock.Append(DockRight, c)
|
||||||
|
}
|
||||||
|
var s = &scroll{Wrap(dock), c, bar}
|
||||||
|
bar.OnChanged = func(v float32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scroll) Handle(ctx Context, ev allg5.Event) {
|
||||||
|
s.Wrapper.Handle(ctx, ev)
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allg5.MouseMoveEvent:
|
||||||
|
if 0 != e.DeltaZ && !s.Bar.IsOver && geom.PtF32(float32(e.X), float32(e.Y)).In(s.Bounds) {
|
||||||
|
var d = e.DeltaZ
|
||||||
|
if allg5.IsAnyKeyDown(allg5.KeyLShift, allg5.KeyRShift) {
|
||||||
|
d *= 10
|
||||||
|
}
|
||||||
|
s.Bar.increment(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scroll) Render(ctx Context) {
|
||||||
|
var bounds = s.Content.Rect()
|
||||||
|
var w, h = bounds.Dx(), bounds.Dy()
|
||||||
|
var bmp, err = allg5.NewVideoBitmap(int(w), int(h))
|
||||||
|
if nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer bmp.Destroy()
|
||||||
|
bmp.SetAsTarget()
|
||||||
|
switch s.Bar.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
Arrange(ctx, s.Content, geom.RectF32(-s.Bar.Value, 0, w, h))
|
||||||
|
case OrientationVertical:
|
||||||
|
Arrange(ctx, s.Content, geom.RectF32(0, -s.Bar.Value, w, h))
|
||||||
|
}
|
||||||
|
s.Content.Render(ctx)
|
||||||
|
ctx.Display().SetAsTarget()
|
||||||
|
var min = s.Bounds.Min
|
||||||
|
bmp.Draw(min.X, min.Y)
|
||||||
|
s.Bar.Render(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scroll) Arrange(ctx Context, rect geom.RectangleF32) {
|
||||||
|
var sz = s.Content.DesiredSize(ctx)
|
||||||
|
switch s.Bar.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
s.Bar.Length = sz.X
|
||||||
|
case OrientationVertical:
|
||||||
|
s.Bar.Length = sz.Y
|
||||||
|
}
|
||||||
|
s.Wrapper.Arrange(ctx, rect)
|
||||||
|
}
|
243
ui/scrollbar.go
Normal file
243
ui/scrollbar.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &Scrollbar{}
|
||||||
|
|
||||||
|
const ScrollbarWidth = 12
|
||||||
|
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
|
||||||
|
normal *allg5.Bitmap
|
||||||
|
hover *allg5.Bitmap
|
||||||
|
pressed *allg5.Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) Created(ctx Context, p Container) error {
|
||||||
|
s.ControlBase.Created(ctx, p)
|
||||||
|
var center = float64(ScrollbarWidth) * 1.5
|
||||||
|
var rad = float64(ScrollbarWidth) * .5
|
||||||
|
s.normal = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
gc.SetFillColor(ctx.Palette().Primary())
|
||||||
|
gc.MoveTo(center, center)
|
||||||
|
gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
})
|
||||||
|
s.hover = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
gc.SetFillColor(ctx.Palette().PrimaryTransparent())
|
||||||
|
gc.MoveTo(center, center)
|
||||||
|
gc.ArcTo(center, center, center, center, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
gc.SetFillColor(ctx.Palette().Primary())
|
||||||
|
gc.MoveTo(center, center)
|
||||||
|
gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
})
|
||||||
|
s.pressed = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
gc.SetFillColor(ctx.Palette().PrimaryTransparent())
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
gc.MoveTo(center, center)
|
||||||
|
gc.ArcTo(center, center, center, center, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
}
|
||||||
|
gc.SetFillColor(ctx.Palette().Primary())
|
||||||
|
gc.MoveTo(center, center)
|
||||||
|
gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
})
|
||||||
|
s.handle = &ControlBase{}
|
||||||
|
s.handle.Created(ctx, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) Destroyed(ctx Context) {
|
||||||
|
var d = func(b *allg5.Bitmap) {
|
||||||
|
if nil != b {
|
||||||
|
b.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d(s.normal)
|
||||||
|
d(s.hover)
|
||||||
|
d(s.pressed)
|
||||||
|
s.handle.Destroyed(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) barViewRange() (float32, float32) {
|
||||||
|
var small bool
|
||||||
|
var min, max float32
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
min = s.Bounds.Min.X + ScrollbarWidth
|
||||||
|
max = s.Bounds.Max.X - ScrollbarWidth
|
||||||
|
small = min > max
|
||||||
|
default:
|
||||||
|
min = s.Bounds.Max.Y - ScrollbarWidth
|
||||||
|
max = s.Bounds.Min.Y + ScrollbarWidth
|
||||||
|
small = max > min
|
||||||
|
}
|
||||||
|
if small {
|
||||||
|
var center = (min + max) * .5
|
||||||
|
min, max = center, center
|
||||||
|
}
|
||||||
|
return min, max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) barViewCenter() float32 {
|
||||||
|
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 float32) 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*float32(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(float32(x), float32(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 allg5.Event) {
|
||||||
|
s.ControlBase.Handle(ctx, ev)
|
||||||
|
s.handle.Handle(ctx, ev)
|
||||||
|
switch e := ev.(type) {
|
||||||
|
case *allg5.MouseMoveEvent:
|
||||||
|
if s.handle.IsPressed {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
if 0 != e.DeltaZ && s.IsOver {
|
||||||
|
var d = e.DeltaZ
|
||||||
|
if allg5.IsAnyKeyDown(allg5.KeyLShift, allg5.KeyRShift) {
|
||||||
|
d *= 10
|
||||||
|
}
|
||||||
|
s.increment(d)
|
||||||
|
}
|
||||||
|
case *allg5.MouseButtonDownEvent:
|
||||||
|
if !s.handle.IsPressed && s.IsOver {
|
||||||
|
s.snapTo(e.X, e.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) DesiredSize(Context) geom.PointF32 {
|
||||||
|
var width = float32(2 * ScrollbarWidth)
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return geom.PtF32(geom.NaN32(), width)
|
||||||
|
}
|
||||||
|
return geom.PtF32(width, geom.NaN32())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrollbar) SetRect(rect geom.RectangleF32) {
|
||||||
|
var width = float32(2 * ScrollbarWidth)
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
if rect.Dy() > width {
|
||||||
|
rect.Min.Y = rect.Max.Y - width
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if rect.Dx() > width {
|
||||||
|
rect.Min.X = rect.Max.X - width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ControlBase.SetRect(rect)
|
||||||
|
|
||||||
|
var min, max = s.barViewRange()
|
||||||
|
var off = float32(s.Value-s.Minimum) / float32(s.Maximum-s.Minimum)
|
||||||
|
var centerH = min + (max-min)*off
|
||||||
|
var r = 0.5 * float32(s.normal.Width())
|
||||||
|
|
||||||
|
var center = s.barViewCenter()
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
s.handle.SetRect(geom.RectF32(centerH-r, center-r, centerH+r, center+r))
|
||||||
|
default:
|
||||||
|
s.handle.SetRect(geom.RectF32(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 centerH = s.handle.Bounds.Center()
|
||||||
|
|
||||||
|
switch s.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
// Left line
|
||||||
|
allg5.DrawLine(min, center, centerH.X, center, ctx.Palette().Primary(), 2)
|
||||||
|
allg5.DrawLine(centerH.X, center, max, center, ctx.Palette().PrimaryTransparent(), 2)
|
||||||
|
default:
|
||||||
|
allg5.DrawLine(center, max, center, centerH.Y, ctx.Palette().PrimaryTransparent(), 2)
|
||||||
|
allg5.DrawLine(center, centerH.Y, center, min, ctx.Palette().Primary(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var minH = s.handle.Bounds.Min
|
||||||
|
var state = s.normal
|
||||||
|
if s.handle.IsOver {
|
||||||
|
if s.handle.IsPressed {
|
||||||
|
state = s.pressed
|
||||||
|
} else {
|
||||||
|
state = s.hover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.Draw(minH.X, minH.Y)
|
||||||
|
|
||||||
|
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/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dropShadowPalette []allg5.Color
|
||||||
|
|
||||||
|
func initDropShadowPalette() {
|
||||||
|
if nil != dropShadowPalette {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dropShadowPalette = []allg5.Color{
|
||||||
|
allg5.NewColorAlpha(0, 0, 0, 0x0f),
|
||||||
|
allg5.NewColorAlpha(0, 0, 0, 0x07),
|
||||||
|
allg5.NewColorAlpha(0, 0, 0, 0x03),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DropShadow(x1, y1, x2, y2 float32) {
|
||||||
|
initDropShadowPalette()
|
||||||
|
|
||||||
|
allg5.DrawLine(x1, y2+1, x2+1, y2+1, dropShadowPalette[0], 1)
|
||||||
|
allg5.DrawLine(x2+1, y1, x2+1, y2, dropShadowPalette[0], 1)
|
||||||
|
allg5.DrawLine(x1, y2+2, x2+2, y2+2, dropShadowPalette[1], 1)
|
||||||
|
allg5.DrawLine(x2+2, y1, x2+2, y2+1, dropShadowPalette[1], 1)
|
||||||
|
allg5.DrawLine(x1+1, y2+3, x2+2, y2+3, dropShadowPalette[2], 1)
|
||||||
|
allg5.DrawLine(x2+3, y1+1, x2+3, y2+2, dropShadowPalette[2], 1)
|
||||||
|
}
|
99
ui/spinner.go
Normal file
99
ui/spinner.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &Spinner{}
|
||||||
|
|
||||||
|
type Spinner struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
spin float32
|
||||||
|
circs *allg5.Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Created(ctx Context, p Container) error {
|
||||||
|
s.ControlBase.Created(ctx, p)
|
||||||
|
const row = 6
|
||||||
|
const numCircs = row * row
|
||||||
|
const width = 48
|
||||||
|
const center = width * .5
|
||||||
|
const full = 2 * math.Pi
|
||||||
|
s.circs = drawBitmap(row*width, row*width, func(gc *draw2dimg.GraphicContext) {
|
||||||
|
var start, a float64 = 0, .2 * math.Pi
|
||||||
|
var inc = true
|
||||||
|
gc.SetLineWidth(4)
|
||||||
|
gc.SetStrokeColor(ctx.Palette().Primary())
|
||||||
|
for i := 0; i < numCircs; i++ {
|
||||||
|
var left = float64(i%row) * width
|
||||||
|
var top = float64(i/row) * width
|
||||||
|
gc.ArcTo(left+center, top+center, center-8, center-8, start, a)
|
||||||
|
gc.Stroke()
|
||||||
|
if inc {
|
||||||
|
start += full / numCircs
|
||||||
|
a += 1.90 * full / numCircs
|
||||||
|
if a >= full {
|
||||||
|
inc = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start += 2.90 * full / numCircs
|
||||||
|
a -= 1.90 * full / numCircs
|
||||||
|
// if a <= .1*math.Pi {
|
||||||
|
// inc = true
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for i := 0; i < numCircs; i++ {
|
||||||
|
var left = (i % row) * width
|
||||||
|
var top = (i / row) * width
|
||||||
|
s.circs.Sub(left, top, width, width)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Destroyed(ctx Context) {
|
||||||
|
s.circs.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spinner) Update(ctx Context, dt time.Duration) {
|
||||||
|
var spin = float64(s.spin)
|
||||||
|
spin += dt.Seconds() * .5
|
||||||
|
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)
|
||||||
|
|
||||||
|
allg5.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
|
||||||
|
allg5.DrawFilledRectangle(rectX, rectY, rectX+rectW, rectY+rectH, ctx.Palette().Lightest())
|
||||||
|
DropShadow(rectX, rectY, rectX+rectW, rectY+rectH)
|
||||||
|
fnt.Draw(rectX+marginH, rectY+marginV, ctx.Palette().Darkest(), allg5.AlignLeft, s.Text)
|
||||||
|
|
||||||
|
const numCircs = 36
|
||||||
|
var i = int(math.Floor(float64(s.spin) * numCircs))
|
||||||
|
s.circs.Subs()[i].DrawOptions(width*.5, rectY+2*marginV+2*textH, allg5.DrawOptions{Center: true})
|
||||||
|
|
||||||
|
s.ControlBase.Render(ctx)
|
||||||
|
}
|
71
ui/state.go
Normal file
71
ui/state.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State interface {
|
||||||
|
Enter(Context) error
|
||||||
|
Leave(Context) error
|
||||||
|
Update(Context, time.Duration) (State, error)
|
||||||
|
Handle(Context, allg5.Event) error
|
||||||
|
Render(Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateBase struct {
|
||||||
|
Control Control
|
||||||
|
ChangeTo State
|
||||||
|
}
|
||||||
|
|
||||||
|
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 s.ChangeTo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateBase) Handle(ctx Context, ev allg5.Event) error {
|
||||||
|
if nil != s.Control {
|
||||||
|
s.Control.Handle(ctx, ev)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Arrange(ctx Context, c Control, rect geom.RectangleF32) {
|
||||||
|
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.RectF32(0, 0, float32(disp.Width()), float32(disp.Height())))
|
||||||
|
s.Control.Render(ctx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
35
ui/statusbar.go
Normal file
35
ui/statusbar.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &StatusBar{}
|
||||||
|
|
||||||
|
const statusBarHeight = 24
|
||||||
|
|
||||||
|
type StatusBar struct {
|
||||||
|
ControlBase
|
||||||
|
Text string
|
||||||
|
RightText string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatusBar) DesiredSize(Context) geom.PointF32 {
|
||||||
|
return geom.PtF32(geom.NaN32(), statusBarHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatusBar) Render(ctx Context) {
|
||||||
|
var fonts = ctx.Fonts()
|
||||||
|
|
||||||
|
var min = b.Bounds.Min
|
||||||
|
var max = b.Bounds.Max
|
||||||
|
allg5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, ctx.Palette().Primary())
|
||||||
|
|
||||||
|
var fnt = fonts.Get("default")
|
||||||
|
var y = min.Y + float32(.5*b.Bounds.Dy()) - .67*fnt.Ascent()
|
||||||
|
fnt.Draw(min.X+leftMargin, y, ctx.Palette().Lightest(), allg5.AlignLeft, b.Text)
|
||||||
|
fnt.Draw(max.X-leftMargin, y, ctx.Palette().Lightest(), allg5.AlignRight, b.RightText)
|
||||||
|
|
||||||
|
b.ControlBase.Render(ctx)
|
||||||
|
}
|
58
ui/wrapper.go
Normal file
58
ui/wrapper.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/allg5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Wrapper struct {
|
||||||
|
Wrapped Control
|
||||||
|
Bounds geom.RectangleF32
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wrap(c Control) Wrapper {
|
||||||
|
return Wrapper{Wrapped: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Created(ctx Context, p Container) error {
|
||||||
|
return w.Wrapped.Created(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Destroyed(ctx Context) {
|
||||||
|
w.Wrapped.Destroyed(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Update(ctx Context, d time.Duration) {
|
||||||
|
w.Wrapped.Update(ctx, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Handle(ctx Context, ev allg5.Event) {
|
||||||
|
w.Wrapped.Handle(ctx, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
return w.Wrapped.DesiredSize(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Rect() geom.RectangleF32 {
|
||||||
|
return w.Bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) SetRect(rect geom.RectangleF32) {
|
||||||
|
w.Bounds = rect
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Render(ctx Context) {
|
||||||
|
w.Wrapped.Render(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Children() []Control {
|
||||||
|
return []Control{w.Wrapped}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) Arrange(ctx Context, rect geom.RectangleF32) {
|
||||||
|
w.Bounds = rect
|
||||||
|
Arrange(ctx, w.Wrapped, rect)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user