Compare commits

..

9 Commits
master ... ui

Author SHA1 Message Date
23ce18b51b Use float32 in ui package. 2018-09-24 09:40:20 +02:00
ad697d5508 Renamed repository to opslag.de/schobers/zntg.
Renamed allegro5 package to allg5
2018-09-22 16:31:09 +02:00
bb803e4cc3 Added -static flag to LDFLAGS for static build. 2018-09-09 21:26:27 +02:00
9d4b097352 Added Rect method to Control interface.
Fixed ContentScrollbar.
Added horizontal alignment to label.
Removed some dimensional constants.
Added several controls:
- Margin;
- Button;
- Columns;
- Scroll;
- Wrapper (reusable layout control) that wraps around existing control.
2018-09-09 21:07:53 +02:00
eb0b660ab6 Added Highlight color to Palette. 2018-09-09 18:49:18 +02:00
c2009c5a8b Added SetIcon to Display. 2018-09-09 18:24:33 +02:00
239c24533d Some changes to UI elements.
- Added colors to palette and renamed white/black to lightest/darkest.
- Extracted drawing code.
- Restyled checkbox, spinner and scrollbar.
2018-08-07 10:13:05 +02:00
115f9877c1 Various additions:
- Added sub-bitmaps.
- Color implements color.Color interface.
- Added methods to retrieve font dimensions and its rendered text.
- Added DrawPolyline method.
2018-08-07 10:13:04 +02:00
8157d8b3d8 Some basic UI controls. 2018-08-03 08:46:33 +02:00
132 changed files with 3189 additions and 8877 deletions

6
.gitignore vendored
View File

@ -1,9 +1,3 @@
# Visual Studio Code
.vscode .vscode
# Go
debug debug
debug.test debug.test
# Project
ui/examples/99_playground

View File

@ -1,45 +0,0 @@
package zntg
// Action is a method without arguments or return values.
type Action func()
// Err converts the Action to an ActionErr.
func (a Action) Err() ActionErr {
return func() error {
a()
return nil
}
}
// ActionErr is a method that only returns an error.
type ActionErr func() error
// Actions is a slice of ActionErr's.
type Actions []ActionErr
// Add adds an Action and returns the new list of Actions.
func (a Actions) Add(fn Action) Actions {
return a.AddErr(fn.Err())
}
// AddErr adds an ActionErr and return the new list of Actions.
func (a Actions) AddErr(fn ActionErr) Actions {
return append(a, fn)
}
// Do executes all actions.
func (a Actions) Do() {
for _, a := range a {
a()
}
}
// DoErr executes all actions but stops on the first action that returns an error.
func (a Actions) DoErr() error {
for _, a := range a {
if err := a(); err != nil {
return err
}
}
return nil
}

View File

@ -1,23 +0,0 @@
package aferores
import (
"io"
"github.com/spf13/afero"
"opslag.de/schobers/zntg/ui"
)
type aferoResources struct {
afero.Fs
}
var _ ui.Resources = &aferoResources{}
// New provides resources from a afero file system.
func New(fs afero.Fs) ui.Resources {
return &aferoResources{fs}
}
func (r *aferoResources) Destroy() error { return nil }
func (r *aferoResources) OpenResource(name string) (io.ReadCloser, error) { return r.Fs.Open(name) }

View File

@ -1,21 +0,0 @@
package clipboard
import (
clip "github.com/atotto/clipboard" // actual implementation
"opslag.de/schobers/zntg/ui"
)
type clipboard struct {
}
func (c clipboard) WriteText(t string) error {
return clip.WriteAll(t)
}
func (c clipboard) ReadText() (string, error) {
return clip.ReadAll()
}
func init() {
ui.DefaultClipboard = &clipboard{}
}

View File

@ -1,176 +0,0 @@
#include <Windows.h>
#include <oleidl.h>
#include <stdio.h>
#include "vector.h"
#include "_cgo_export.h"
#define STDULONGMETHODIMP STDMETHODIMP_(ULONG)
#define DRAGDROP_MAXFILEPATHSIZE 32767
typedef struct
{
IDropTargetVtbl *_vtbl;
long _refCount;
uint32_t _handle;
HWND _windowHandle;
} DragDropHandler;
static void screenToClient(HWND windowHandle, POINTL *point)
{
ScreenToClient(windowHandle, (LPPOINT)point);
}
static STDMETHODIMP QueryInterface(IDropTarget *this, REFIID riid, LPVOID *ppvObj)
{
// Always set out parameter to NULL, validating it first.
if (!ppvObj)
return E_INVALIDARG;
*ppvObj = NULL;
if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDropTarget))
{
// Increment the reference count and return the pointer.
*ppvObj = (LPVOID)this;
((DragDropHandler *)this)->_vtbl->AddRef(this);
return NOERROR;
}
return E_NOINTERFACE;
}
static STDULONGMETHODIMP AddRef(IDropTarget *this)
{
InterlockedIncrement(&((DragDropHandler *)this)->_refCount);
return ((DragDropHandler *)this)->_refCount;
}
static STDULONGMETHODIMP Release(IDropTarget *this)
{
// Decrement the object's internal counter.
ULONG ulRefCount = InterlockedDecrement(&((DragDropHandler *)this)->_refCount);
if (0 == ((DragDropHandler *)this)->_refCount)
{
GlobalFree(this);
}
return ulRefCount;
}
static STDMETHODIMP DragEnter(IDropTarget *this, __RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect)
{
*pdwEffect = DROPEFFECT_COPY;
wchar_t filePath[DRAGDROP_MAXFILEPATHSIZE];
FORMATETC format;
format.cfFormat = CF_HDROP;
format.ptd = NULL;
format.dwAspect = DVASPECT_CONTENT;
format.lindex = -1;
format.tymed = TYMED_HGLOBAL;
STGMEDIUM medium;
HRESULT result = pDataObj->lpVtbl->GetData(pDataObj, &format, &medium);
if (result != S_OK)
return E_UNEXPECTED;
HDROP drop = (HDROP)GlobalLock(medium.hGlobal);
clearFiles(((DragDropHandler *)this)->_handle);
UINT numberOfFiles = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
for (UINT fileIndex = 0; fileIndex < numberOfFiles; fileIndex++)
{
UINT length = DragQueryFileW(drop, fileIndex, filePath, DRAGDROP_MAXFILEPATHSIZE);
addFile(((DragDropHandler *)this)->_handle, &filePath[0], length);
}
GlobalUnlock(medium.hGlobal);
if (medium.pUnkForRelease != NULL)
medium.pUnkForRelease->lpVtbl->Release(medium.pUnkForRelease);
screenToClient(((DragDropHandler *)this)->_windowHandle, &pt);
onDragEnter(((DragDropHandler *)this)->_handle, pt.x, pt.y);
return S_OK;
}
static STDMETHODIMP DragOver(IDropTarget *this, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect)
{
*pdwEffect = DROPEFFECT_COPY;
screenToClient(((DragDropHandler *)this)->_windowHandle, &pt);
onDragOver(((DragDropHandler *)this)->_handle, pt.x, pt.y);
return S_OK;
}
static STDMETHODIMP DragLeave(IDropTarget *this)
{
onDragLeave(((DragDropHandler *)this)->_handle);
return S_OK;
}
static STDMETHODIMP Drop(IDropTarget *this, __RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect)
{
*pdwEffect = DROPEFFECT_COPY;
screenToClient(((DragDropHandler *)this)->_windowHandle, &pt);
onDrop(((DragDropHandler *)this)->_handle, pt.x, pt.y);
return S_OK;
}
static const IDropTargetVtbl DragDropHandlerVtbl = {
QueryInterface,
AddRef,
Release,
DragEnter,
DragOver,
DragLeave,
Drop,
};
static DragDropHandler *newDragDropHandler(HWND windowHandle, uint32_t handle)
{
DragDropHandler *handler = malloc(sizeof(DragDropHandler));
handler->_vtbl = (IDropTargetVtbl *)&DragDropHandlerVtbl;
handler->_refCount = 0;
handler->_windowHandle = windowHandle;
handler->_handle = handle;
return handler;
}
BOOL dragDropInitialized = FALSE;
typedef vector(DragDropHandler *) DragDropHandlers;
DragDropHandlers handlers;
static void VerifyResult(CHAR *component, HRESULT result)
{
if (result == S_OK)
return;
printf("%s failed (error code: %d)\n", component, result);
}
static void initDragDrop(void)
{
if (dragDropInitialized == TRUE)
return;
HRESULT result = OleInitialize(NULL);
VerifyResult("OleInitialize", result);
vector_init(handlers);
dragDropInitialized = TRUE;
}
uint32_t RegisterHandler(void *window)
{
initDragDrop();
size_t handle = handlers.count + 1;
HWND windowHandle = (HWND)window;
DragDropHandler *handler = newDragDropHandler(windowHandle, handle);
vector_append(DragDropHandler *, handlers, handler);
DragAcceptFiles(windowHandle, TRUE);
HRESULT result = RegisterDragDrop(windowHandle, (IDropTarget *)handler);
VerifyResult("RegisterDragDrop", result);
return (uint32_t)handle;
}

View File

@ -1,96 +0,0 @@
// +build windows
package dragdrop
import (
"unsafe"
"golang.org/x/text/encoding/unicode"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
/*
#cgo LDFLAGS: -lole32 -luuid
#include <Windows.h>
#include <oleidl.h>
#include <stdint.h>
#include <stdlib.h>
extern uint32_t RegisterHandler(void* windowHandle);
*/
import "C"
//export clearFiles
func clearFiles(handle uint32) {
handler := handlers[handle]
handler.Files = nil
handlers[handle] = handler
}
//export addFile
func addFile(handle uint32, pathNative *C.wchar_t, pathNativeLength C.UINT) {
pathBytes := C.GoBytes(unsafe.Pointer(pathNative), C.int(pathNativeLength)*C.sizeof_wchar_t)
decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()
path, err := decoder.Bytes(pathBytes)
if err != nil {
return
}
handler := handlers[handle]
handler.Files = append(handler.Files, string(path))
handlers[handle] = handler
}
//export onDragEnter
func onDragEnter(handle uint32, x, y C.LONG) {
invokeHandler(handle, func(target ui.DragDropEventTarget, files []string) {
target.DragEnter(pos(x, y), files)
})
}
//export onDragOver
func onDragOver(handle uint32, x, y C.LONG) {
invokeHandler(handle, func(target ui.DragDropEventTarget, _ []string) {
target.DragMove(pos(x, y))
})
}
//export onDragLeave
func onDragLeave(handle uint32) {
invokeHandler(handle, func(target ui.DragDropEventTarget, _ []string) {
target.DragLeave()
})
}
//export onDrop
func onDrop(handle uint32, x, y C.LONG) {
invokeHandler(handle, func(target ui.DragDropEventTarget, files []string) {
target.Drop(pos(x, y), files)
})
}
type handler struct {
Target ui.DragDropEventTarget
Files []string
}
var handlers map[uint32]handler = map[uint32]handler{}
func invokeHandler(handle uint32, invoke func(ui.DragDropEventTarget, []string)) {
handler := handlers[handle]
invoke(handler.Target, handler.Files)
}
func pos(x, y C.LONG) geom.PointF32 { return geom.Pt(int(x), int(y)).ToF32() }
type provider struct{}
func (p provider) Register(windowHandle uintptr, target ui.DragDropEventTarget) {
handle := C.RegisterHandler(unsafe.Pointer(windowHandle))
handlers[uint32(handle)] = handler{target, nil}
}
func init() {
ui.DefaultDragDropProvider = provider{}
}

View File

@ -1,26 +0,0 @@
#ifndef __VECTOR_H__
#define __VECTOR_H__
#define vector(type) \
struct \
{ \
type *items; \
size_t count; \
}
#define vector_init(v) \
do \
{ \
(v).items = 0; \
(v).count = 0; \
} while (0);
#define vector_append(type, v, i) \
do \
{ \
(v).count++; \
(v).items = (type *)realloc((v).items, sizeof(type) * (v).count); \
(v).items[(v).count - 1] = i; \
} while (0);
#endif // __VECTOR_H__

View File

@ -1,7 +0,0 @@
// +build !windows
package drop
func Register(dropper Dropper) error {
return nil
}

View File

@ -1,63 +0,0 @@
#include <Windows.h>
#include <shellapi.h>
#include <stdint.h>
#include "_cgo_export.h"
#define droppedFilePathSize 32767
BOOL dropHookInitialized = FALSE;
HHOOK nextHook;
LRESULT CALLBACK DragAndDropHook(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
if (nCode < 0 || wParam == 0)
return CallNextHookEx(nextHook, nCode, wParam, lParam);
LPMSG message = (LPMSG)lParam;
switch (message->message)
{
case WM_DROPFILES:
{
wchar_t droppedFilePath[droppedFilePathSize];
clearDrop();
HDROP drop = (HDROP)message->wParam;
UINT numberOfFiles = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
for (UINT fileIndex = 0; fileIndex < numberOfFiles; fileIndex++)
{
UINT length = DragQueryFileW(drop, fileIndex, droppedFilePath, droppedFilePathSize);
addDroppedFile(&droppedFilePath[0], length);
}
POINT position;
DragQueryPoint(drop, &position);
dropFinished(position.x, position.y);
}
break;
}
return CallNextHookEx(nextHook, nCode, wParam, lParam);
}
void initDropHook(HWND windowHandle)
{
if (dropHookInitialized == TRUE)
return;
DWORD threadId = GetWindowThreadProcessId(windowHandle, NULL);
nextHook = SetWindowsHookEx(WH_GETMESSAGE, DragAndDropHook, NULL, threadId);
dropHookInitialized = TRUE;
}
void SetDragAndDropHook(void *window)
{
HWND windowHandle = (HWND)window;
initDropHook(windowHandle);
DragAcceptFiles(windowHandle, TRUE);
}

View File

@ -1,65 +0,0 @@
// +build windows
package drop
import (
"unsafe"
"golang.org/x/text/encoding/unicode"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
/*
#include <Windows.h>
#include <stdint.h>
void SetDragAndDropHook(void* window);
*/
import "C"
type handler struct {
Target ui.DragDropEventTarget
Files []string
}
var targets map[ui.DragDropEventTarget]struct{} = map[ui.DragDropEventTarget]struct{}{}
var droppedFiles []string
//export clearDrop
func clearDrop() {
droppedFiles = nil
}
//export dropFinished
func dropFinished(x, y C.INT) {
for target := range targets {
target.Drop(geom.PtF32(float32(x), float32(y)), droppedFiles)
}
}
//export addDroppedFile
func addDroppedFile(filePath *C.wchar_t, filePathLength C.UINT) {
pathBytes := C.GoBytes(unsafe.Pointer(filePath), C.int(filePathLength)*C.sizeof_wchar_t)
decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()
path, err := decoder.Bytes(pathBytes)
if err != nil {
return
}
droppedFiles = append(droppedFiles, string(path))
}
func RegisterAsDefaultProvider() {
ui.DefaultDragDropProvider = &provider{}
}
type provider struct{}
func (p provider) Register(windowHandle uintptr, target ui.DragDropEventTarget) {
C.SetDragAndDropHook(unsafe.Pointer(windowHandle))
targets[target] = struct{}{}
}
func init() {
ui.DefaultDragDropProvider = provider{}
}

View File

@ -1,24 +0,0 @@
package embedres
import (
"embed"
"io"
"opslag.de/schobers/zntg/ui"
)
var _ ui.Resources = &resources{}
type resources struct {
fs embed.FS
}
func New(fs embed.FS) ui.Resources {
return &resources{fs}
}
func (r resources) Destroy() error { return nil }
func (r resources) OpenResource(name string) (io.ReadCloser, error) {
return r.fs.Open(name)
}

View File

@ -1,23 +0,0 @@
package riceres
import (
"io"
rice "github.com/GeertJohan/go.rice"
"opslag.de/schobers/zntg/ui"
)
type riceResources struct {
*rice.Box
}
var _ ui.Resources = &riceResources{}
// New provides resources from a rice Box.
func New(box *rice.Box) ui.Resources {
return &riceResources{box}
}
func (r *riceResources) Destroy() error { return nil }
func (r *riceResources) OpenResource(name string) (io.ReadCloser, error) { return r.Box.Open(name) }

249
allg5/bitmap.go Normal file
View File

@ -0,0 +1,249 @@
package allg5
// #include <allegro5/allegro.h>
// #include <stdlib.h>
import "C"
import (
"errors"
"image"
"image/color"
"unsafe"
)
// Bitmap represents an in memory bitmap
type Bitmap struct {
bitmap *C.ALLEGRO_BITMAP
width int
height int
subs []*Bitmap
}
type DrawOptions struct {
Center bool
Scale Scale
Tint *Color
Rotation *Rotation
}
type Scale interface {
Horizontal() float32
Vertical() float32
}
type scale struct {
horizontal float32
vertical float32
}
func (s *scale) Horizontal() float32 { return s.horizontal }
func (s *scale) Vertical() float32 { return s.vertical }
func NewScale(horizontal, vertical float32) Scale {
return &scale{horizontal, vertical}
}
func NewUniformScale(s float32) Scale {
return &scale{s, s}
}
type Rotation struct {
Angle float32
Center bool
}
func newBitmap(width, height int, mut func(m FlagMutation), flags []NewBitmapFlag) (*Bitmap, error) {
var newBmpFlags = CaptureNewBitmapFlags()
defer newBmpFlags.Revert()
newBmpFlags.Mutate(func(m FlagMutation) {
if nil != mut {
mut(m)
}
for _, f := range flags {
m.Set(f)
}
})
b := C.al_create_bitmap(C.int(width), C.int(height))
if nil == b {
return nil, errors.New("error creating bitmap")
}
return &Bitmap{b, width, height, nil}, nil
}
// NewBitmap creates a new bitmap of given width and height and optional flags
func NewBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
return newBitmap(width, height, nil, flags)
}
// NewVideoBitmap creates a new video bitmap of given width and height and optional flags
func NewVideoBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
return newBitmap(width, height, func(m FlagMutation) {
m.Unset(NewBitmapFlagMemoryBitmap)
m.Set(NewBitmapFlagVideoBitmap)
}, flags)
}
// NewMemoryBitmap creates a new video bitmap of given width and height and optional flags
func NewMemoryBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
return newBitmap(width, height, func(m FlagMutation) {
m.Unset(NewBitmapFlagVideoBitmap)
m.Set(NewBitmapFlagMemoryBitmap)
}, flags)
}
// NewBitmapFromImage creates a new bitmap starting from a Go native image (image.Image)
func NewBitmapFromImage(im image.Image, video bool) (*Bitmap, error) {
var newBmpFlags = CaptureNewBitmapFlags()
defer newBmpFlags.Revert()
newBmpFlags.Mutate(func(m FlagMutation) {
m.Unset(NewBitmapFlagVideoBitmap)
m.Set(NewBitmapFlagMemoryBitmap)
m.Set(NewBitmapFlagMinLinear)
})
var bnd = im.Bounds()
width, height := bnd.Dx(), bnd.Dy()
var b = C.al_create_bitmap(C.int(width), C.int(height))
if nil == b {
return nil, errors.New("error creating memory bitmap")
}
row := make([]uint8, width*4)
rgn := C.al_lock_bitmap(b, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_WRITEONLY)
if nil == rgn {
C.al_destroy_bitmap(b)
return nil, errors.New("unable to lock bitmap")
}
data := (*[1 << 30]uint8)(rgn.data)
offset := 0
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
pix := color.RGBAModel.Convert(im.At(x, y)).(color.RGBA)
row[x*4] = pix.R
row[x*4+1] = pix.G
row[x*4+2] = pix.B
row[x*4+3] = pix.A
}
copy(data[offset:], row)
offset += int(rgn.pitch)
}
C.al_unlock_bitmap(b)
if video {
newBmpFlags.Mutate(func(m FlagMutation) {
m.Unset(NewBitmapFlagMemoryBitmap)
m.Set(NewBitmapFlagVideoBitmap)
m.Set(NewBitmapFlagMinLinear)
})
C.al_convert_bitmap(b)
}
return &Bitmap{b, width, height, nil}, nil
}
// LoadBitmap tries to load the image at the specified path as a bitmap
func LoadBitmap(path string) (*Bitmap, error) {
p := C.CString(path)
defer C.free(unsafe.Pointer(p))
b := C.al_load_bitmap(p)
if nil == b {
return nil, errors.New("error loading bitmap")
}
width := int(C.al_get_bitmap_width(b))
height := int(C.al_get_bitmap_height(b))
return &Bitmap{b, width, height, nil}, nil
}
// Draw draws the bitmap at the given location
func (b *Bitmap) Draw(left, top float32) {
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
}
func (b *Bitmap) DrawOptions(left, top float32, options DrawOptions) {
width := float32(b.width)
height := float32(b.height)
scale := nil != options.Scale
if scale {
width *= options.Scale.Horizontal()
height *= options.Scale.Vertical()
}
if options.Center {
left -= width * 0.5
top -= height * 0.5
}
rotated := nil != options.Rotation
var centerX C.float
var centerY C.float
if rotated && options.Rotation.Center {
centerX = C.float(b.width) * 0.5
centerY = C.float(b.height) * 0.5
}
if scale {
if nil == options.Tint { // scaled
if rotated { // scaled & rotated
C.al_draw_scaled_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Scale.Horizontal()), C.float(options.Scale.Vertical()), C.float(options.Rotation.Angle), 0)
} else { // scaled
C.al_draw_scaled_bitmap(b.bitmap, 0, 0, C.float(b.width), C.float(b.height), C.float(left), C.float(top), C.float(width), C.float(height), 0)
}
} else { // tinted & scaled
if rotated { // scaled, tinted & rotated
C.al_draw_tinted_scaled_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Scale.Horizontal()), C.float(options.Scale.Vertical()), C.float(options.Rotation.Angle), 0)
} else { // tinted, scaled
C.al_draw_tinted_scaled_bitmap(b.bitmap, options.Tint.color, 0, 0, C.float(b.width), C.float(b.height), C.float(left), C.float(top), C.float(width), C.float(height), 0)
}
}
} else {
if nil == options.Tint {
if rotated { // rotated
C.al_draw_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0)
} else {
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
}
} else { // tinted
if rotated { // tinted & rotated
C.al_draw_tinted_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0)
} else {
C.al_draw_tinted_bitmap(b.bitmap, options.Tint.color, C.float(left), C.float(top), 0)
}
}
}
}
// 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 {
return b.width
}
func (b *Bitmap) Height() int {
return b.height
}
func (b *Bitmap) SetAsTarget() {
C.al_set_target_bitmap(b.bitmap)
}
// Destroy destroys the bitmap
func (b *Bitmap) Destroy() {
var bmp = b.bitmap
if nil == bmp {
return
}
b.bitmap = nil
for _, sub := range b.subs {
sub.Destroy()
}
C.al_destroy_bitmap(bmp)
}

6
allg5/c.go Normal file
View File

@ -0,0 +1,6 @@
// +build !windows
package allg5
// #cgo pkg-config: allegro-5 allegro_font-5 allegro_image-5 allegro_primitives-5 allegro_ttf-5
import "C"

6
allg5/c_windows.go Normal file
View File

@ -0,0 +1,6 @@
// +build windows,!static
package allg5
// #cgo LDFLAGS: -lallegro -lallegro_font -lallegro_image -lallegro_primitives -lallegro_ttf
import "C"

View 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
View 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
}

104
allg5/display.go Normal file
View File

@ -0,0 +1,104 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
import (
"errors"
"unsafe"
)
// Display represents a display
type Display struct {
display *C.ALLEGRO_DISPLAY
}
type NewDisplayOptions struct {
Fullscreen bool
Resizable bool
Windowed bool
Maximized bool
Frameless bool
}
// NewDisplay creates a display
func NewDisplay(width, height int, options NewDisplayOptions) (*Display, error) {
var flags C.int = C.ALLEGRO_WINDOWED
if options.Fullscreen {
if options.Windowed {
flags |= C.ALLEGRO_FULLSCREEN_WINDOW
} else {
flags = C.ALLEGRO_FULLSCREEN
}
} else if options.Frameless {
flags |= C.ALLEGRO_FRAMELESS
}
if options.Resizable {
flags |= C.ALLEGRO_RESIZABLE
if options.Maximized {
flags |= C.ALLEGRO_MAXIMIZED
}
}
C.al_set_new_display_flags(flags)
d := C.al_create_display(C.int(width), C.int(height))
if nil == d {
return nil, errors.New("error creating display")
}
return &Display{d}, nil
}
// Flips flips the buffer to the display
func (d *Display) Flip() {
C.al_flip_display()
}
func (d *Display) Width() int {
return int(C.al_get_display_width(d.display))
}
func (d *Display) Height() int {
return int(C.al_get_display_height(d.display))
}
func (d *Display) Position() (int, int) {
var x, y C.int
C.al_get_window_position(d.display, &x, &y)
return int(x), int(y)
}
func (d *Display) Resize(w, h int) {
C.al_resize_display(d.display, C.int(w), C.int(h))
}
func (d *Display) SetAsTarget() {
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) {
C.al_set_mouse_xy(d.display, C.int(x), C.int(y))
}
func (d *Display) SetPosition(x, y int) {
C.al_set_window_position(d.display, C.int(x), C.int(y))
}
func (d *Display) SetWindowTitle(title string) {
t := C.CString(title)
defer C.free(unsafe.Pointer(t))
C.al_set_window_title(d.display, t)
}
// Destroy destroys the display
func (d *Display) Destroy() {
C.al_destroy_display(d.display)
}
func SetNewWindowTitle(title string) {
t := C.CString(title)
defer C.free(unsafe.Pointer(t))
C.al_set_new_window_title(t)
}

195
allg5/event.go Normal file
View File

@ -0,0 +1,195 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
import (
"errors"
"unsafe"
)
type EventQueue struct {
queue *C.ALLEGRO_EVENT_QUEUE
}
type Event interface {
Stamp() float64
}
type EventBase struct {
stamp float64
}
func (eb EventBase) Stamp() float64 {
return eb.stamp
}
type DisplayCloseEvent struct {
EventBase
}
type DisplayResizeEvent struct {
EventBase
X, Y int
Width int
Height int
}
type DisplayOrientation int
const (
DisplayOrientation0Degrees DisplayOrientation = iota
DisplayOrientation90Degrees
DisplayOrientation180Degrees
DisplayOrientation270Degrees
DisplayOrientationFaceUp
DisplayOrientationFaceDown
)
func toDisplayOrientation(o C.int) DisplayOrientation {
switch o {
case C.ALLEGRO_DISPLAY_ORIENTATION_0_DEGREES:
return DisplayOrientation0Degrees
case C.ALLEGRO_DISPLAY_ORIENTATION_90_DEGREES:
return DisplayOrientation90Degrees
case C.ALLEGRO_DISPLAY_ORIENTATION_180_DEGREES:
return DisplayOrientation180Degrees
case C.ALLEGRO_DISPLAY_ORIENTATION_270_DEGREES:
return DisplayOrientation270Degrees
case C.ALLEGRO_DISPLAY_ORIENTATION_FACE_UP:
return DisplayOrientationFaceUp
case C.ALLEGRO_DISPLAY_ORIENTATION_FACE_DOWN:
return DisplayOrientationFaceDown
default:
panic("not supported")
}
}
type DisplayOrientationEvent struct {
EventBase
Orientation DisplayOrientation
}
type KeyEvent struct {
EventBase
KeyCode Key
Display *Display
}
type KeyCharEvent struct {
KeyEvent
UnicodeCharacter rune
Modifiers KeyMod
Repeat bool
}
type KeyDownEvent struct {
KeyEvent
}
type KeyUpEvent struct {
KeyEvent
}
type MouseButtonDownEvent struct {
MouseEvent
Button MouseButton
Pressure float32
}
type MouseButtonUpEvent struct {
MouseEvent
Button MouseButton
Pressure float32
}
type MouseEvent struct {
EventBase
X, Y int
Z, W int
Display *Display
}
type MouseMoveEvent struct {
MouseEvent
DeltaX, DeltaY int
DeltaZ, DeltaW int
Pressure float32
}
func NewEventQueue() (*EventQueue, error) {
q := C.al_create_event_queue()
if nil == q {
return nil, errors.New("unable to create event queue")
}
return &EventQueue{q}, nil
}
func (eq *EventQueue) register(source *C.ALLEGRO_EVENT_SOURCE) {
C.al_register_event_source(eq.queue, source)
}
func (eq *EventQueue) RegisterDisplay(d *Display) {
eq.register(C.al_get_display_event_source(d.display))
}
func (eq *EventQueue) RegisterMouse() {
eq.register(C.al_get_mouse_event_source())
}
func (eq *EventQueue) RegisterKeyboard() {
eq.register(C.al_get_keyboard_event_source())
}
func (eq *EventQueue) mapEvent(e *C.ALLEGRO_EVENT) Event {
any := (*C.ALLEGRO_ANY_EVENT)(unsafe.Pointer(e))
eb := EventBase{float64(any.timestamp)}
switch any._type {
case C.ALLEGRO_EVENT_DISPLAY_CLOSE:
return &DisplayCloseEvent{eb}
case C.ALLEGRO_EVENT_DISPLAY_ORIENTATION:
display := (*C.ALLEGRO_DISPLAY_EVENT)(unsafe.Pointer(e))
return &DisplayOrientationEvent{eb, toDisplayOrientation(display.orientation)}
case C.ALLEGRO_EVENT_DISPLAY_RESIZE:
display := (*C.ALLEGRO_DISPLAY_EVENT)(unsafe.Pointer(e))
C.al_acknowledge_resize(display.source)
return &DisplayResizeEvent{eb, int(display.x), int(display.y), int(display.width), int(display.height)}
case C.ALLEGRO_EVENT_MOUSE_AXES:
mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
return &MouseMoveEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, int(mouse.dx), int(mouse.dy), int(mouse.dz), int(mouse.dw), float32(mouse.pressure)}
case C.ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
return &MouseButtonDownEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, MouseButton(mouse.button), float32(mouse.pressure)}
case C.ALLEGRO_EVENT_MOUSE_BUTTON_UP:
mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
return &MouseButtonUpEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, MouseButton(mouse.button), float32(mouse.pressure)}
case C.ALLEGRO_EVENT_KEY_DOWN:
key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e))
return &KeyDownEvent{KeyEvent{eb, Key(key.keycode), nil}}
case C.ALLEGRO_EVENT_KEY_UP:
key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e))
return &KeyUpEvent{KeyEvent{eb, Key(key.keycode), nil}}
case C.ALLEGRO_EVENT_KEY_CHAR:
key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e))
return &KeyCharEvent{KeyEvent{eb, Key(key.keycode), nil}, rune(key.unichar), KeyMod(key.modifiers), bool(key.repeat)}
}
return nil
}
func (eq *EventQueue) Get() Event {
var event C.ALLEGRO_EVENT
if !bool(C.al_get_next_event(eq.queue, &event)) {
return nil
}
return eq.mapEvent(&event)
}
func (eq *EventQueue) GetWait() Event {
var event C.ALLEGRO_EVENT
C.al_wait_for_event(eq.queue, &event)
return eq.mapEvent(&event)
}
func (eq *EventQueue) Destroy() {
C.al_destroy_event_queue(eq.queue)
}

23
allg5/flagmut.go Normal file
View File

@ -0,0 +1,23 @@
package allg5
// #include <stdlib.h>
import "C"
type FlagMutation interface {
Set(f NewBitmapFlag)
Unset(f NewBitmapFlag)
}
type flagMut struct {
flg C.int
}
func (m *flagMut) Set(f NewBitmapFlag) {
m.flg |= C.int(f)
}
func (m *flagMut) Unset(f NewBitmapFlag) {
if m.flg&C.int(f) == C.int(f) {
m.flg ^= C.int(f)
}
}

107
allg5/font.go Normal file
View File

@ -0,0 +1,107 @@
package allg5
// #include <allegro5/allegro.h>
// #include <allegro5/allegro_font.h>
// #include <allegro5/allegro_ttf.h>
import "C"
import (
"fmt"
"unsafe"
)
type Font struct {
font *C.ALLEGRO_FONT
hght float32
asc float32
desc float32
}
type HorizontalAlignment int
const (
AlignLeft HorizontalAlignment = iota
AlignCenter
AlignRight
)
func LoadTTFFont(path string, size int) (*Font, error) {
p := C.CString(path)
defer C.free(unsafe.Pointer(p))
f := C.al_load_ttf_font(p, C.int(size), 0)
if nil == f {
return nil, fmt.Errorf("unable to load ttf font '%s'", path)
}
return &Font{f, 0, 0, 0}, nil
}
func (f *Font) drawFlags(a HorizontalAlignment) C.int {
switch a {
case AlignLeft:
return C.ALLEGRO_ALIGN_LEFT
case AlignCenter:
return C.ALLEGRO_ALIGN_CENTRE
case AlignRight:
return C.ALLEGRO_ALIGN_RIGHT
}
return C.ALLEGRO_ALIGN_LEFT
}
func (f *Font) Draw(left, top float32, color Color, align HorizontalAlignment, text string) {
t := C.CString(text)
defer C.free(unsafe.Pointer(t))
flags := f.drawFlags(align)
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 {
t := C.CString(text)
defer C.free(unsafe.Pointer(t))
return float32(C.al_get_text_width(f.font, t))
}
func (f *Font) Destroy() {
C.al_destroy_font(f.font)
}

26
allg5/graphics.go Normal file
View File

@ -0,0 +1,26 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
// BitmapFlag is extra information provided for creating a bitmap
type BitmapFlag int
const (
// BitmapFlagLinearScaleDown enables linear scaling when scaling down. Gives better results when combined with BitmapFlagMipMap
BitmapFlagLinearScaleDown BitmapFlag = C.ALLEGRO_MIN_LINEAR
// BitmapFlagLinearScaleUp enables linear scaling when scaling up.
BitmapFlagLinearScaleUp = C.ALLEGRO_MAG_LINEAR
// BitmapFlagMipMap enables mipmaps for drawing a scaled down version. Bitmap must square and its sides must be a power of two.
BitmapFlagMipMap = C.ALLEGRO_MIPMAP
)
// ClearToColor clears the target bitmap to the color
func ClearToColor(c Color) {
C.al_clear_to_color(c.color)
}
// SetNewBitmapFlags sets the default bitmap flags for a newly created bitmap
func SetNewBitmapFlags(flags BitmapFlag) {
C.al_set_new_bitmap_flags(C.int(flags))
}

188
allg5/keyboard.go Normal file
View File

@ -0,0 +1,188 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
type Key int
const (
KeyA Key = 1
KeyB = 2
KeyC = 3
KeyD = 4
KeyE = 5
KeyF = 6
KeyG = 7
KeyH = 8
KeyI = 9
KeyJ = 10
KeyK = 11
KeyL = 12
KeyM = 13
KeyN = 14
KeyO = 15
KeyP = 16
KeyQ = 17
KeyR = 18
KeyS = 19
KeyT = 20
KeyU = 21
KeyV = 22
KeyW = 23
KeyX = 24
KeyY = 25
KeyZ = 26
Key0 = 27
Key1 = 28
Key2 = 29
Key3 = 30
Key4 = 31
Key5 = 32
Key6 = 33
Key7 = 34
Key8 = 35
Key9 = 36
KeyPad0 = 37
KeyPad1 = 38
KeyPad2 = 39
KeyPad3 = 40
KeyPad4 = 41
KeyPad5 = 42
KeyPad6 = 43
KeyPad7 = 44
KeyPad8 = 45
KeyPad9 = 46
KeyF1 = 47
KeyF2 = 48
KeyF3 = 49
KeyF4 = 50
KeyF5 = 51
KeyF6 = 52
KeyF7 = 53
KeyF8 = 54
KeyF9 = 55
KeyF10 = 56
KeyF11 = 57
KeyF12 = 58
KeyEscape = 59
KeyTilde = 60
KeyMinus = 61
KeyEquals = 62
KeyBackspace = 63
KeyTab = 64
KeyOpenBrace = 65
KeyCloseBrace = 66
KeyEnter = 67
KeySemicolon = 68
KeyQuote = 69
KeyBackslash = 70
KeyBackslash2 = 71 /* DirectInput calls this DIK_OEM_102: "< > | on UK/Germany keyboards" */
KeyComma = 72
KeyFullstop = 73
KeySlash = 74
KeySpace = 75
KeyInsert = 76
KeyDelete = 77
KeyHome = 78
KeyEnd = 79
KeyPageUp = 80
KeyPageDown = 81
KeyLeft = 82
KeyRight = 83
KeyUp = 84
KeyDown = 85
KeyPadSlash = 86
KeyPadAsterisk = 87
KeyPadMinus = 88
KeyPadPlus = 89
KeyPadDelete = 90
KeyPadEnter = 91
KeyPrintScreen = 92
KeyPause = 93
KeyAbntC1 = 94
KeyYen = 95
KeyKana = 96
KeyConvert = 97
KeyNoConvert = 98
KeyAt = 99
KeyCircumflex = 100
KeyColon2 = 101
KeyKanji = 102
KeyPadEquals = 103 /* MacOS X */
KeyBackQuote = 104 /* MacOS X */
KeySemicolon2 = 105 /* MacOS X -- TODO: ask lillo what this should be */
KeyCommand = 106 /* MacOS X */
KeyBack = 107 /* Android back key */
KeyVolumeUp = 108
KeyVolumeDown = 109
KeySearch = 110
KeyDPadCenter = 111
KeyButtonX = 112
KeyButtonY = 113
KeyDPadUp = 114
KeyDPadDown = 115
KeyDPadLeft = 116
KeyDPadRight = 117
KeySelect = 118
KeyStart = 119
KeyButtonL1 = 120
KeyButtonR1 = 121
KeyButtonL2 = 122
KeyButtonR2 = 123
KeyButtonA = 124
KeyButtonB = 125
KeyThumbL = 126
KeyThumbR = 127
KeyUnknown = 128
KeyModifiers = 215
KeyLShift = 215
KeyRShift = 216
KeyLCtrl = 217
KeyRCtrl = 218
KeyAlt = 219
KeyAltGr = 220
KeyLWin = 221
KeyRWin = 222
KeyMenu = 223
KeyScrollLock = 224
KeyNumLock = 225
KeyCapsLock = 226
)
type KeyMod uint
const (
KeyModShift KeyMod = 0x00001
KeyModCtrl = 0x00002
KeyModAlt = 0x00004
KeyModLWin = 0x00008
KeyModRWin = 0x00010
KeyModMenu = 0x00020
KeyModAltGr = 0x00040
KeyModCommand = 0x00080
KeyModScrollLock = 0x00100
KeyModNumlock = 0x00200
KeyModCapsLock = 0x00400
KeyModInaltseq = 0x00800
KeyModAccent1 = 0x01000
KeyModAccent2 = 0x02000
KeyModAccent3 = 0x04000
KeyModAccent4 = 0x08000
)
func IsKeyDown(k Key) bool {
var state C.ALLEGRO_KEYBOARD_STATE
C.al_get_keyboard_state(&state)
return bool(C.al_key_down(&state, C.int(k)))
}
func IsAnyKeyDown(keys ...Key) bool {
var state C.ALLEGRO_KEYBOARD_STATE
C.al_get_keyboard_state(&state)
for _, k := range keys {
if bool(C.al_key_down(&state, C.int(k))) {
return true
}
}
return false
}

34
allg5/monitor.go Normal file
View File

@ -0,0 +1,34 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
type Monitor struct {
X1, Y1 int
X2, Y2 int
}
func monitor(m *C.ALLEGRO_MONITOR_INFO) Monitor {
return Monitor{int(m.x1), int(m.y1), int(m.x2), int(m.y2)}
}
func DefaultMonitor() Monitor {
var m C.ALLEGRO_MONITOR_INFO
C.al_get_monitor_info(C.ALLEGRO_DEFAULT_DISPLAY_ADAPTER, &m)
return monitor(&m)
}
func Monitors() []Monitor {
var n = NumberOfVideoAdapters()
var mons []Monitor
var m C.ALLEGRO_MONITOR_INFO
for i := 0; i < n; i++ {
C.al_get_monitor_info(C.int(i), &m)
mons = append(mons, monitor(&m))
}
return mons
}
func NumberOfVideoAdapters() int {
return int(C.al_get_num_video_adapters())
}

29
allg5/mouse.go Normal file
View File

@ -0,0 +1,29 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
type MouseButton uint
const (
MouseButtonLeft MouseButton = 1
MouseButtonRight = 2
MouseButtonMiddle = 3
)
func IsMouseButtonDown(b MouseButton) bool {
var state C.ALLEGRO_MOUSE_STATE
C.al_get_mouse_state(&state)
return bool(C.al_mouse_button_down(&state, C.int(b)))
}
func IsAnyMouseButtonDown(buttons ...MouseButton) bool {
var state C.ALLEGRO_MOUSE_STATE
C.al_get_mouse_state(&state)
for _, b := range buttons {
if bool(C.al_mouse_button_down(&state, C.int(b))) {
return true
}
}
return false
}

31
allg5/newbitmapflag.go Normal file
View File

@ -0,0 +1,31 @@
package allg5
// #include <allegro5/allegro.h>
import "C"
type NewBitmapFlag int
const (
NewBitmapFlagMemoryBitmap = NewBitmapFlag(C.ALLEGRO_MEMORY_BITMAP)
NewBitmapFlagVideoBitmap = NewBitmapFlag(C.ALLEGRO_VIDEO_BITMAP)
NewBitmapFlagMinLinear = NewBitmapFlag(C.ALLEGRO_MIN_LINEAR)
)
type NewBitmapFlagsCapture struct {
cap C.int
}
func CaptureNewBitmapFlags() *NewBitmapFlagsCapture {
var cap = C.al_get_new_bitmap_flags()
return &NewBitmapFlagsCapture{cap}
}
func (c *NewBitmapFlagsCapture) Mutate(mut func(FlagMutation)) {
var m = &flagMut{c.cap}
mut(m)
C.al_set_new_bitmap_flags(m.flg)
}
func (c *NewBitmapFlagsCapture) Revert() {
C.al_set_new_bitmap_flags(c.cap)
}

12
allg5/platform_windows.go Normal file
View File

@ -0,0 +1,12 @@
// +build windows
package allg5
// #include <allegro5/allegro.h>
// #include <allegro5/allegro_windows.h>
import "C"
import "unsafe"
func (d *Display) WindowHandle() unsafe.Pointer {
return unsafe.Pointer(C.al_get_win_window_handle(d.display))
}

30
allg5/primitives.go Normal file
View File

@ -0,0 +1,30 @@
package allg5
// #include <allegro5/allegro.h>
// #include <allegro5/allegro_primitives.h>
import "C"
import "unsafe"
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)
}
func DrawFilledTriangle(x1, y1, x2, y2, x3, y3 float32, c Color) {
C.al_draw_filled_triangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), C.float(x3), C.float(y3), c.color)
}
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))
}
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) {
C.al_draw_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
}
func DrawTriangle(x1, y1, x2, y2, x3, y3 float32, c Color, thickness float32) {
C.al_draw_triangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), C.float(x3), C.float(y3), c.color, C.float(thickness))
}

56
allg5/system.go Normal file
View File

@ -0,0 +1,56 @@
package allg5
// #include <allegro5/allegro.h>
// #include <allegro5/allegro_font.h>
// #include <allegro5/allegro_image.h>
// #include <allegro5/allegro_primitives.h>
// #include <allegro5/allegro_ttf.h>
// bool init() {
// return al_init();
// }
import "C"
import (
"errors"
"runtime"
)
func init() {
runtime.LockOSThread()
}
type InitConfig struct {
Font bool
Image bool
Primitives bool
Keyboard bool
Mouse bool
}
var InitAll = InitConfig{true, true, true, true, true}
// Init initializes the Allegro system
func Init(config InitConfig) error {
if !bool(C.init()) {
return errors.New("failed to initialize Allegro")
}
if config.Font && !bool(C.al_init_font_addon()) {
return errors.New("failed to initialize font addon")
}
if config.Font && !bool(C.al_init_ttf_addon()) {
return errors.New("failed to initialize ttf addon")
}
if config.Image && !bool(C.al_init_image_addon()) {
return errors.New("failed to initialize image addon")
}
if config.Primitives && !bool(C.al_init_primitives_addon()) {
return errors.New("failed to initialize primitives addon")
}
if config.Keyboard && !bool(C.al_install_keyboard()) {
return errors.New("failed to install keyboard")
}
if config.Mouse && !bool(C.al_install_mouse()) {
return errors.New("failed to install mouse")
}
return nil
}

View File

@ -1,49 +0,0 @@
package allg5ui
import (
"opslag.de/schobers/allg5"
"opslag.de/schobers/geom"
)
type FontDefinition struct {
Name string
Size int
}
func NewFontDefinition(name string, size int) FontDefinition {
return FontDefinition{Name: name, Size: size}
}
type font struct {
*allg5.Font
}
func newFont(f *allg5.Font) *font {
return &font{f}
}
func (f *font) Destroy() error {
f.Font.Destroy()
return nil
}
func (f *font) Height() float32 {
if f == nil {
return 0
}
return f.Font.Height()
}
func (f *font) WidthOf(t string) float32 {
return f.TextWidth(t)
}
func (f *font) Measure(t string) geom.RectangleF32 {
if f == nil {
return geom.RectangleF32{}
}
// allg5.Font.TextDimentions (previous implementation) seems to return the closest fit rectangle to the drawn text (so depending on the glyphs). allg5.Font.TextWidth is giving the full width (not trimmed) which gives a better result together with allg5.Font.Height.
w := f.TextWidth(t)
h := f.Height()
return geom.RectRelF32(0, 0, w, h)
}

View File

@ -1,276 +0,0 @@
package allg5ui
import (
"opslag.de/schobers/allg5"
"opslag.de/schobers/zntg/ui"
)
func key(key allg5.Key) ui.Key {
switch key {
case allg5.Key0:
return ui.Key0
case allg5.Key1:
return ui.Key1
case allg5.Key2:
return ui.Key2
case allg5.Key3:
return ui.Key3
case allg5.Key4:
return ui.Key4
case allg5.Key5:
return ui.Key5
case allg5.Key6:
return ui.Key6
case allg5.Key7:
return ui.Key7
case allg5.Key8:
return ui.Key8
case allg5.Key9:
return ui.Key9
case allg5.KeyA:
return ui.KeyA
case allg5.KeyAlt:
return ui.KeyAlt
case allg5.KeyAltGr:
return ui.KeyAltGr
case allg5.KeyAt:
return ui.KeyAt
case allg5.KeyB:
return ui.KeyB
case allg5.KeyBack:
return ui.KeyBack
case allg5.KeyBackslash:
return ui.KeyBackslash
case allg5.KeyBackslash2:
return ui.KeyBackslash
case allg5.KeyBackspace:
return ui.KeyBackspace
case allg5.KeyBackQuote:
return ui.KeyBacktick
case allg5.KeyButtonA:
return ui.KeyButtonA
case allg5.KeyButtonB:
return ui.KeyButtonB
case allg5.KeyButtonL1:
return ui.KeyButtonL1
case allg5.KeyButtonL2:
return ui.KeyButtonL2
case allg5.KeyButtonR1:
return ui.KeyButtonR1
case allg5.KeyButtonR2:
return ui.KeyButtonR2
case allg5.KeyButtonX:
return ui.KeyButtonX
case allg5.KeyButtonY:
return ui.KeyButtonY
case allg5.KeyC:
return ui.KeyC
case allg5.KeyCapsLock:
return ui.KeyCapsLock
case allg5.KeyCircumflex:
return ui.KeyCircumflex
case allg5.KeyCloseBrace:
return ui.KeyCloseBrace
case allg5.KeyColon2:
return ui.KeyColon2
case allg5.KeyComma:
return ui.KeyComma
case allg5.KeyCommand:
return ui.KeyCommand
case allg5.KeyD:
return ui.KeyD
case allg5.KeyDelete:
return ui.KeyDelete
case allg5.KeyDown:
return ui.KeyDown
case allg5.KeyDPadCenter:
return ui.KeyDPadCenter
case allg5.KeyDPadDown:
return ui.KeyDPadDown
case allg5.KeyDPadLeft:
return ui.KeyDPadLeft
case allg5.KeyDPadRight:
return ui.KeyDPadRight
case allg5.KeyDPadUp:
return ui.KeyDPadUp
case allg5.KeyE:
return ui.KeyE
case allg5.KeyEnd:
return ui.KeyEnd
case allg5.KeyEnter:
return ui.KeyEnter
case allg5.KeyEquals:
return ui.KeyEquals
case allg5.KeyEscape:
return ui.KeyEscape
case allg5.KeyF:
return ui.KeyF
case allg5.KeyF1:
return ui.KeyF1
case allg5.KeyF2:
return ui.KeyF2
case allg5.KeyF3:
return ui.KeyF3
case allg5.KeyF4:
return ui.KeyF4
case allg5.KeyF5:
return ui.KeyF5
case allg5.KeyF6:
return ui.KeyF6
case allg5.KeyF7:
return ui.KeyF7
case allg5.KeyF8:
return ui.KeyF8
case allg5.KeyF9:
return ui.KeyF9
case allg5.KeyF10:
return ui.KeyF10
case allg5.KeyF11:
return ui.KeyF11
case allg5.KeyF12:
return ui.KeyF12
case allg5.KeyFullstop:
return ui.KeyFullstop
case allg5.KeyG:
return ui.KeyG
case allg5.KeyH:
return ui.KeyH
case allg5.KeyHome:
return ui.KeyHome
case allg5.KeyI:
return ui.KeyI
case allg5.KeyInsert:
return ui.KeyInsert
case allg5.KeyJ:
return ui.KeyJ
case allg5.KeyK:
return ui.KeyK
case allg5.KeyL:
return ui.KeyL
case allg5.KeyLeft:
return ui.KeyLeft
case allg5.KeyLCtrl:
return ui.KeyLeftControl
case allg5.KeyLShift:
return ui.KeyLeftShift
case allg5.KeyLWin:
return ui.KeyLeftWin
case allg5.KeyM:
return ui.KeyM
case allg5.KeyMenu:
return ui.KeyMenu
case allg5.KeyMinus:
return ui.KeyMinus
case allg5.KeyN:
return ui.KeyN
case allg5.KeyNumLock:
return ui.KeyNumLock
case allg5.KeyO:
return ui.KeyO
case allg5.KeyOpenBrace:
return ui.KeyOpenBrace
case allg5.KeyP:
return ui.KeyP
case allg5.KeyPad0:
return ui.KeyPad0
case allg5.KeyPad1:
return ui.KeyPad1
case allg5.KeyPad2:
return ui.KeyPad2
case allg5.KeyPad3:
return ui.KeyPad3
case allg5.KeyPad4:
return ui.KeyPad4
case allg5.KeyPad5:
return ui.KeyPad5
case allg5.KeyPad6:
return ui.KeyPad6
case allg5.KeyPad7:
return ui.KeyPad7
case allg5.KeyPad8:
return ui.KeyPad8
case allg5.KeyPad9:
return ui.KeyPad9
case allg5.KeyPadAsterisk:
return ui.KeyPadAsterisk
case allg5.KeyPadDelete:
return ui.KeyPadDelete
case allg5.KeyPadEnter:
return ui.KeyPadEnter
case allg5.KeyPadEquals:
return ui.KeyPadEquals
case allg5.KeyPadMinus:
return ui.KeyPadMinus
case allg5.KeyPadPlus:
return ui.KeyPadPlus
case allg5.KeyPadSlash:
return ui.KeyPadSlash
case allg5.KeyPageDown:
return ui.KeyPageDown
case allg5.KeyPageUp:
return ui.KeyPageUp
case allg5.KeyPause:
return ui.KeyPause
case allg5.KeyPrintScreen:
return ui.KeyPrintScreen
case allg5.KeyQ:
return ui.KeyQ
case allg5.KeyQuote:
return ui.KeyQuote
case allg5.KeyR:
return ui.KeyR
case allg5.KeyRight:
return ui.KeyRight
case allg5.KeyRCtrl:
return ui.KeyRightControl
case allg5.KeyRShift:
return ui.KeyRightShift
case allg5.KeyRWin:
return ui.KeyRightWin
case allg5.KeyS:
return ui.KeyS
case allg5.KeyScrollLock:
return ui.KeyScrollLock
case allg5.KeySearch:
return ui.KeySearch
case allg5.KeySelect:
return ui.KeySelect
case allg5.KeySemicolon:
return ui.KeySemicolon
case allg5.KeySlash:
return ui.KeySlash
case allg5.KeySpace:
return ui.KeySpace
case allg5.KeyStart:
return ui.KeyStart
case allg5.KeyT:
return ui.KeyT
case allg5.KeyTab:
return ui.KeyTab
case allg5.KeyThumbL:
return ui.KeyThumbL
case allg5.KeyThumbR:
return ui.KeyThumbR
case allg5.KeyTilde:
return ui.KeyTilde
case allg5.KeyU:
return ui.KeyU
case allg5.KeyUp:
return ui.KeyUp
case allg5.KeyV:
return ui.KeyV
case allg5.KeyVolumeDown:
return ui.KeyVolumeDown
case allg5.KeyVolumeUp:
return ui.KeyVolumeUp
case allg5.KeyW:
return ui.KeyW
case allg5.KeyX:
return ui.KeyX
case allg5.KeyY:
return ui.KeyY
case allg5.KeyZ:
return ui.KeyZ
}
return ui.KeyNone
}

View File

@ -1,458 +0,0 @@
package allg5ui
import (
"image"
"image/color"
"math"
"unicode"
"opslag.de/schobers/zntg"
"opslag.de/schobers/allg5"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
var _ ui.Renderer = &Renderer{}
func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
var clean zntg.Actions
defer func() { clean.Do() }()
var err = allg5.Init(allg5.InitAll)
if err != nil {
return nil, err
}
disp, err := allg5.NewDisplay(w, h, opts)
if err != nil {
return nil, err
}
clean = clean.Add(disp.Destroy)
eq, err := allg5.NewEventQueue()
if err != nil {
return nil, err
}
clean = clean.Add(eq.Destroy)
user := allg5.NewUserEventSource()
eq.RegisterKeyboard()
eq.RegisterMouse()
eq.RegisterDisplay(disp)
eq.RegisterUserEvents(user)
allg5.CaptureNewBitmapFlags().Mutate(func(m allg5.FlagMutation) {
m.Set(allg5.NewBitmapFlagMinLinear)
})
clean = nil
return &Renderer{disp, eq, nil, user, &ui.OSResources{}, dispPos(disp), ui.KeyState{}, ui.KeyModifierNone, ui.MouseCursorDefault}, nil
}
// Renderer implements ui.Renderer using Allegro 5.
type Renderer struct {
disp *allg5.Display
eq *allg5.EventQueue
unh func(allg5.Event)
user *allg5.UserEventSource
res ui.PhysicalResources
dispPos geom.Point
keys ui.KeyState
modifiers ui.KeyModifier
cursor ui.MouseCursor
}
// Renderer implementation (events)
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) bool {
r.disp.Flip()
var ev = eventWait(r.eq, wait)
if ev == nil {
return false
}
cursor := r.cursor
r.cursor = ui.MouseCursorDefault
var unhandled bool
for ev != nil {
switch e := ev.(type) {
case *allg5.DisplayCloseEvent:
t.Handle(&ui.DisplayCloseEvent{EventBase: eventBase(e)})
case *allg5.DisplayResizeEvent:
t.Handle(&ui.DisplayResizeEvent{EventBase: eventBase(e), Bounds: geom.RectF32(float32(e.X), float32(e.Y), float32(e.X+e.Width), float32(e.Y+e.Height))})
case *allg5.KeyCharEvent:
if r.modifiers&ui.KeyModifierControl == ui.KeyModifierNone && !unicode.IsControl(e.UnicodeCharacter) {
t.Handle(&ui.TextInputEvent{EventBase: eventBase(e), Character: e.UnicodeCharacter})
} else {
unhandled = true
}
case *allg5.KeyDownEvent:
key := key(e.KeyCode)
r.keys[key] = true
r.modifiers = r.keys.Modifiers()
t.Handle(&ui.KeyDownEvent{EventBase: eventBase(e), Key: key, Modifiers: r.modifiers})
case *allg5.KeyUpEvent:
key := key(e.KeyCode)
r.keys[key] = false
r.modifiers = r.keys.Modifiers()
t.Handle(&ui.KeyUpEvent{EventBase: eventBase(e), Key: key, Modifiers: r.modifiers})
case *allg5.MouseButtonDownEvent:
t.Handle(&ui.MouseButtonDownEvent{MouseEvent: mouseEvent(e.MouseEvent), Button: ui.MouseButton(e.Button)})
case *allg5.MouseButtonUpEvent:
t.Handle(&ui.MouseButtonUpEvent{MouseEvent: mouseEvent(e.MouseEvent), Button: ui.MouseButton(e.Button)})
case *allg5.MouseEnterEvent:
t.Handle(&ui.MouseLeaveEvent{MouseEvent: mouseEvent(e.MouseEvent)})
case *allg5.MouseLeaveEvent:
t.Handle(&ui.MouseLeaveEvent{MouseEvent: mouseEvent(e.MouseEvent)})
case *allg5.MouseMoveEvent:
t.Handle(&ui.MouseMoveEvent{MouseEvent: mouseEvent(e.MouseEvent), MouseWheel: float32(e.DeltaZ)})
case *allg5.UserEvent:
t.Handle(&ui.RefreshEvent{EventBase: eventBase(e)})
default:
if r.unh != nil {
r.unh(e)
}
unhandled = true
}
ev = r.eq.Get()
}
dispPos := dispPos(r.disp)
if dispPos != r.dispPos {
r.dispPos = dispPos
w := r.disp.Width()
h := r.disp.Height()
t.Handle(&ui.DisplayMoveEvent{Bounds: r.dispPos.RectRel2D(w, h).ToF32()})
}
if !unhandled && cursor != r.cursor {
switch r.cursor {
case ui.MouseCursorNone:
r.disp.SetMouseCursor(allg5.MouseCursorNone)
case ui.MouseCursorDefault:
r.disp.SetMouseCursor(allg5.MouseCursorDefault)
case ui.MouseCursorNotAllowed:
r.disp.SetMouseCursor(allg5.MouseCursorUnavailable)
case ui.MouseCursorPointer:
r.disp.SetMouseCursor(allg5.MouseCursorLink)
case ui.MouseCursorText:
r.disp.SetMouseCursor(allg5.MouseCursorEdit)
}
}
return true
}
func (r *Renderer) RegisterRecorder(rec *allg5.Recorder) {
r.eq.RegisterRecorder(rec)
}
func (r *Renderer) Refresh() {
r.user.EmitEvent()
}
func (r *Renderer) Stamp() float64 {
return allg5.GetTime()
}
// Renderer implementation (lifetime)
func (r *Renderer) Destroy() error {
r.user.Destroy()
r.eq.Destroy()
r.disp.Destroy()
r.res.Destroy()
return nil
}
// Renderer implementation (drawing)
func (r *Renderer) Clear(c color.Color) {
allg5.ClearToColor(newColor(c))
}
func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
path, err := r.res.FetchResource(path)
if err != nil {
return nil, err
}
f, err := allg5.LoadTTFFont(path, size)
if err != nil {
return nil, err
}
return &font{f}, nil
}
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (*texture, error) {
im, err := source.CreateImage()
if err != nil {
return nil, err
}
bmp, err := allg5.NewBitmapFromImage(im, true)
if err != nil {
return nil, err
}
if keepSource {
return &texture{bmp, source}, nil
}
return &texture{bmp, nil}, nil
}
func (r *Renderer) CreateTexture(source ui.ImageSource) (ui.Texture, error) {
return r.createTexture(source, true)
}
func (r *Renderer) CreateTextureGo(im image.Image, source bool) (ui.Texture, error) {
return r.createTexture(ui.ImageSourceGo{Image: im}, source)
}
func (r *Renderer) CreateTexturePath(path string, source bool) (ui.Texture, error) {
resourcePath, err := r.res.FetchResource(path)
if err != nil {
return nil, err
}
bmp, err := allg5.LoadBitmap(resourcePath)
if err != nil {
return nil, err
}
if source {
return &texture{bmp, ui.ImageSourceResource{Resources: r.res, Name: path}}, nil
}
return &texture{bmp, nil}, nil
}
func (r *Renderer) CreateTextureTarget(w, h float32) (ui.Texture, error) {
bmp, err := allg5.NewVideoBitmap(int(w), int(h))
if err != nil {
return nil, err
}
return &texture{bmp, nil}, nil
}
func (r *Renderer) DefaultTarget() ui.Texture {
return &texture{r.disp.Target(), nil}
}
func (r *Renderer) Display() *allg5.Display { return r.disp }
func (r *Renderer) DrawTexture(texture ui.Texture, p geom.RectangleF32) {
r.DrawTextureOptions(texture, p, ui.DrawOptions{})
}
func (r *Renderer) DrawTextureOptions(texture ui.Texture, p geom.RectangleF32, opts ui.DrawOptions) {
bmp, ok := r.mustGetSubBitmap(texture, opts.Source)
if ok {
defer bmp.Destroy()
}
x, y := snap(p.Min)
var o allg5.DrawOptions
if opts.Tint != nil {
tint := newColor(opts.Tint)
o.Tint = &tint
}
w, h := p.Dx(), p.Dy()
bmpW, bmpH := float32(bmp.Width()), float32(bmp.Height())
if w != bmpW || h != bmpH {
o.Scale = &allg5.Scale{Horizontal: w / bmpW, Vertical: h / bmpH}
}
bmp.DrawOptions(x, y, o)
}
func (r *Renderer) DrawTexturePoint(texture ui.Texture, p geom.PointF32) {
bmp := r.mustGetBitmap(texture)
x, y := snap(p)
bmp.Draw(x, y)
}
func (r *Renderer) DrawTexturePointOptions(texture ui.Texture, p geom.PointF32, opts ui.DrawOptions) {
bmp, ok := r.mustGetSubBitmap(texture, opts.Source)
if ok {
defer bmp.Destroy()
}
var o allg5.DrawOptions
if opts.Tint != nil {
tint := newColor(opts.Tint)
o.Tint = &tint
}
x, y := snap(p)
bmp.DrawOptions(x, y, o)
}
func (r *Renderer) FillRectangle(rect geom.RectangleF32, c color.Color) {
allg5.DrawFilledRectangle(rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y, newColor(c))
}
func (r *Renderer) Line(p, q geom.PointF32, color color.Color, thickness float32) {
allg5.DrawLine(p.X, p.Y, q.X, q.Y, newColor(color), thickness)
}
func (r *Renderer) Location() geom.Point {
x, y := r.disp.Position()
return geom.Pt(x, y)
}
func (r *Renderer) Move(to geom.Point) {
r.disp.SetPosition(to.X, to.Y)
}
func (r *Renderer) mustGetBitmap(t ui.Texture) *allg5.Bitmap {
texture, ok := t.(*texture)
if !ok {
panic("texture must be created on same renderer")
}
return texture.bmp
}
func (r *Renderer) mustGetSubBitmap(t ui.Texture, source *geom.RectangleF32) (*allg5.Bitmap, bool) {
texture, ok := t.(*texture)
if !ok {
panic("texture must be created on same renderer")
}
if source == nil {
return texture.bmp, false
}
src := source.ToInt()
return texture.bmp.Sub(src.Min.X, src.Min.Y, src.Dx(), src.Dy()), true
}
func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness float32) {
minX, minY := snap(rect.Min)
maxX, maxY := snap(rect.Max)
allg5.DrawRectangle(minX, minY, maxX, maxY, newColor(c), thickness)
}
func (r *Renderer) RenderTo(texture ui.Texture) {
bmp := r.mustGetBitmap(texture)
bmp.SetAsTarget()
}
func (r *Renderer) RenderToDisplay() {
r.disp.SetAsTarget()
}
func (r *Renderer) Resize(width, height int) {
r.disp.Resize(width, height)
}
func (r *Renderer) Resources() ui.Resources { return r.res }
func (r *Renderer) Size() geom.Point {
return geom.Pt(r.disp.Width(), r.disp.Height())
}
func (r *Renderer) SetIcon(source ui.ImageSource) {
texture, err := r.createTexture(source, false)
if err != nil {
return
}
defer texture.Destroy()
r.disp.SetIcon(texture.bmp)
}
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
r.cursor = c
}
func (r *Renderer) SetPosition(p geom.PointF32) { r.disp.SetPosition(int(p.X), int(p.Y)) }
func (r *Renderer) SetResourceProvider(res ui.Resources) {
if r.res != nil {
r.res.Destroy()
}
if phys, ok := res.(ui.PhysicalResources); ok {
r.res = phys
} else {
copy, err := ui.NewCopyResources("allg5ui", res, false)
if err != nil {
return
}
r.res = copy
}
}
func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) {
r.unh = handler
}
func (r *Renderer) SetWindowTitle(t string) {
r.disp.SetWindowTitle(t)
}
func (r *Renderer) Target() ui.Texture {
return &texture{allg5.CurrentTarget(), nil}
}
func (r *Renderer) text(f ui.Font, p geom.PointF32, c color.Color, t string, align allg5.HorizontalAlignment) {
font, ok := f.(*font)
if !ok {
return
}
x, y := snap(p)
font.Draw(x, y, newColor(c), align, t)
}
func (r *Renderer) Text(font ui.Font, p geom.PointF32, c color.Color, t string) {
r.text(font, p, c, t, allg5.AlignLeft)
}
func (r *Renderer) TextAlign(font ui.Font, p geom.PointF32, c color.Color, t string, align ui.HorizontalAlignment) {
var alignment = allg5.AlignLeft
switch align {
case ui.AlignCenter:
alignment = allg5.AlignCenter
case ui.AlignRight:
alignment = allg5.AlignRight
}
r.text(font, p, c, t, alignment)
}
func (r *Renderer) TextTexture(font ui.Font, color color.Color, text string) (ui.Texture, error) {
return ui.TextTexture(r, font, color, text)
}
// Utility functions
func eventWait(eq *allg5.EventQueue, wait bool) allg5.Event {
if wait {
return eq.GetWait()
}
return eq.Get()
}
func eventBase(e allg5.Event) ui.EventBase {
return ui.EventBase{StampInSeconds: e.Stamp()}
}
func keyModifiers(mods allg5.KeyMod) ui.KeyModifier {
var m ui.KeyModifier
if mods&allg5.KeyModShift == allg5.KeyModShift {
m |= ui.KeyModifierShift
} else if mods&allg5.KeyModCtrl == allg5.KeyModCtrl {
m |= ui.KeyModifierControl
} else if mods&allg5.KeyModAlt == allg5.KeyModAlt {
m |= ui.KeyModifierAlt
}
return m
}
func mouseEvent(e allg5.MouseEvent) ui.MouseEvent {
return ui.MouseEvent{EventBase: eventBase(e), X: float32(e.X), Y: float32(e.Y)}
}
func newColor(c color.Color) allg5.Color {
if c == nil {
return newColor(color.Black)
}
return allg5.NewColorGo(c)
}
func snap(p geom.PointF32) (float32, float32) {
return float32(math.Round(float64(p.X))), float32(math.Round(float64(p.Y)))
}
func dispPos(disp *allg5.Display) geom.Point {
x, y := disp.Position()
return geom.Pt(x, y)
}

View File

@ -1,5 +0,0 @@
// +build !windows
package allg5ui
func (r *Renderer) WindowHandle() uintptr { return 0 }

View File

@ -1,7 +0,0 @@
// +build windows
package allg5ui
func (r *Renderer) WindowHandle() uintptr {
return uintptr(r.disp.WindowHandle())
}

View File

@ -1,28 +0,0 @@
package allg5ui
import (
"opslag.de/schobers/allg5"
"opslag.de/schobers/zntg/ui"
)
func init() {
ui.SetRendererFactory(&rendererFactory{})
}
type rendererFactory struct{}
func (f rendererFactory) New(title string, width, height int, opts ui.NewRendererOptions) (ui.Renderer, error) {
renderer, err := NewRenderer(width, height, allg5.NewDisplayOptions{
Frameless: opts.Borderless,
Resizable: opts.Resizable,
Vsync: opts.VSync,
})
if err != nil {
return nil, err
}
renderer.SetWindowTitle(title)
if opts.Location != nil {
renderer.SetPosition(*opts.Location)
}
return renderer, nil
}

View File

@ -1,36 +0,0 @@
package allg5ui
import (
"image"
"opslag.de/schobers/allg5"
"opslag.de/schobers/zntg/ui"
)
var _ ui.Texture = &texture{}
var _ ui.ImageSource = &texture{}
type texture struct {
bmp *allg5.Bitmap
source ui.ImageSource
}
func (t *texture) Destroy() error {
t.bmp.Destroy()
return nil
}
func (t *texture) Height() int {
return t.bmp.Height()
}
func (t *texture) CreateImage() (image.Image, error) {
if t.source == nil {
return t.bmp.Image(), nil
}
return t.source.CreateImage()
}
func (t *texture) Width() int {
return t.bmp.Width()
}

View File

@ -1,81 +0,0 @@
package zntg
import "time"
// Animation is a struct that keeps track of time for animation.
type Animation struct {
// The interval of the animation
Interval time.Duration
active bool
start time.Time
lastUpdate time.Duration
}
// NewAnimation creates an Animation given the specified interval. The animation is immediately started.
func NewAnimation(interval time.Duration) Animation {
return Animation{
Interval: interval,
active: true,
start: time.Now(),
}
}
// NewAnimationPtr creates an Animation given the specified interval and returns a pointer to it. The animation is immediately started.
func NewAnimationPtr(interval time.Duration) *Animation {
ani := NewAnimation(interval)
return &ani
}
// Animate checks if enough time as elapsed to animate a single interval and advances the single interval.
func (a *Animation) Animate() bool {
since := time.Since(a.start)
if !a.active || since < a.lastUpdate+a.Interval {
return false
}
a.lastUpdate += a.Interval
return true
}
// AnimateDelta checks how many natural intervals have elapsed and advances that many intervals. Returns the total of time that has been advanced and the number of intervals.
func (a *Animation) AnimateDelta() (time.Duration, int) {
if !a.active {
return 0, 0
}
since := time.Since(a.start)
n := (since - a.lastUpdate) / a.Interval
delta := n * a.Interval
a.lastUpdate += delta
return delta, int(n)
}
// AnimateFn calls fn for every interval and advances that interval for every interval that has elapsed until it caught up again.
func (a *Animation) AnimateFn(fn func()) {
if !a.active {
return
}
since := time.Since(a.start)
for a.active && since > a.lastUpdate+a.Interval {
fn()
a.lastUpdate += a.Interval
}
}
// Pause pauses the animation causing the Animate{,Delta,Fn} methods to do nothing.
func (a *Animation) Pause() {
a.active = false
}
// IsActive returns true when the animation is started (and false when it either was never started or paused)
func (a *Animation) IsActive() bool { return a.active }
// Start starts the animation (when paused or not started yet).
func (a *Animation) Start() {
if a.active {
return
}
a.active = true
a.start = time.Now()
a.lastUpdate = 0
}

View File

@ -1,74 +0,0 @@
package zntg
import (
"errors"
"image/color"
"regexp"
)
var hexColorRE = regexp.MustCompile(`^#?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})?$`)
// HexColor parses hexadecimal string s and returns the RGBA color. Accepted are 3 or 4 components (R, G, B, A) following an optional hash character '#'.
func HexColor(s string) (color.RGBA, error) {
match := hexColorRE.FindStringSubmatch(s)
if match == nil {
return color.RGBA{}, errors.New("invalid color format")
}
values, err := HexToInts(match[1:]...)
if err != nil {
return color.RGBA{}, err
}
a := 255
if len(match[4]) > 0 {
a = values[3]
}
return RGBA(uint8(values[0]), uint8(values[1]), uint8(values[2]), uint8(a)), nil
}
// HexToInt tries to convert a string with hexadecimal characters to an integer.
func HexToInt(s string) (int, error) {
var i int
for _, c := range s {
i *= 16
if c >= '0' && c <= '9' {
i += int(c - '0')
} else if c >= 'A' && c <= 'F' {
i += int(c - 'A' + 10)
} else if c >= 'a' && c <= 'f' {
i += int(c - 'a' + 10)
} else {
return 0, errors.New("hex digit not supported")
}
}
return i, nil
}
// HexToInts tries to convert multiple strings with hexadecimal characters to a slice of integers.
func HexToInts(s ...string) ([]int, error) {
ints := make([]int, len(s))
for i, s := range s {
value, err := HexToInt(s)
if err != nil {
return nil, err
}
ints[i] = value
}
return ints, nil
}
// MustHexColor parses hexadecimal string s and returns the RGBA color. If the conversion fails it panics.
func MustHexColor(s string) color.RGBA {
color, err := HexColor(s)
if err != nil {
panic(err)
}
return color
}
// RGB creates an opaque color with the specified red, green and red values.
func RGB(r, g, b byte) color.RGBA { return RGBA(r, g, b, 0xff) }
// RGB creates a color with the specified red, green, red and alpha values.
func RGBA(r, g, b, a byte) color.RGBA {
return color.RGBA{R: r, G: g, B: b, A: a}
}

View File

@ -1,33 +0,0 @@
package zntg
import (
"image/color"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHexColor(t *testing.T) {
type test struct {
Hex string
Success bool
Expected color.RGBA
}
tests := []test{
test{Hex: "#AA3939", Success: true, Expected: color.RGBA{R: 170, G: 57, B: 57, A: 255}},
test{Hex: "AA3939", Success: true, Expected: color.RGBA{R: 170, G: 57, B: 57, A: 255}},
test{Hex: "#AA3939BB", Success: true, Expected: color.RGBA{R: 170, G: 57, B: 57, A: 187}},
test{Hex: "AG3939", Success: false, Expected: color.RGBA{}},
test{Hex: "AA3939A", Success: false, Expected: color.RGBA{}},
test{Hex: "AA39A", Success: false, Expected: color.RGBA{}},
}
for _, test := range tests {
color, err := HexColor(test.Hex)
if test.Success {
assert.Nil(t, err)
assert.Equal(t, color, test.Expected)
} else {
assert.NotNil(t, err)
}
}
}

View File

@ -1,46 +0,0 @@
package zntg
type EventEmptyFn func()
type EventFn func(interface{})
func NewEvents() *Events {
return &Events{events: map[uint]EventFn{}}
}
type Events struct {
nextID uint
events map[uint]EventFn
}
type EventHandler interface {
AddHandler(EventFn) uint
AddHandlerEmpty(EventEmptyFn) uint
RemoveHandler(uint)
}
func (e *Events) Notify(state interface{}) bool {
if e.events == nil {
return false
}
for _, handler := range e.events {
handler(state)
}
return len(e.events) > 0
}
func (e *Events) AddHandler(handler EventFn) uint {
if e.events == nil {
e.events = map[uint]EventFn{}
}
id := e.nextID
e.nextID++
e.events[id] = handler
return id
}
func (e *Events) AddHandlerEmpty(handler EventEmptyFn) uint {
return e.AddHandler(func(interface{}) { handler() })
}
func (e *Events) RemoveHandler(id uint) { delete(e.events, id) }

22
go.mod
View File

@ -1,22 +0,0 @@
module opslag.de/schobers/zntg
go 1.20
require (
github.com/GeertJohan/go.rice v1.0.3
github.com/atotto/clipboard v0.1.4
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/spf13/afero v1.9.5
github.com/stretchr/testify v1.8.4
github.com/veandco/go-sdl2 v0.4.35
golang.org/x/text v0.10.0
opslag.de/schobers/allg5 v0.0.0-20220501103818-24f2f9691c81
opslag.de/schobers/geom v0.0.0-20210808233716-e01aa3242dc8
)
require (
github.com/daaku/go.zipexe v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

468
go.sum
View File

@ -1,468 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI=
github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk=
github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
opslag.de/schobers/allg5 v0.0.0-20220501103818-24f2f9691c81 h1:Zfa0GMHLmu4ZvzBp/fUtLchKQbKCMfiqid+DoQPWvOI=
opslag.de/schobers/allg5 v0.0.0-20220501103818-24f2f9691c81/go.mod h1:QAMuvQKxgb3NMghDp8w9mxROxXg9c/skwUQII+2AvRs=
opslag.de/schobers/geom v0.0.0-20210808233716-e01aa3242dc8 h1:Mfni39F8c/ix6fJdEwaZQSprBRxz3E9L61E5KnE6Kx4=
opslag.de/schobers/geom v0.0.0-20210808233716-e01aa3242dc8/go.mod h1:zkcRVIMwRHtxqb2qP2XZ0neyVFdX1tc24PObzuBL9IE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

187
io.go
View File

@ -1,187 +0,0 @@
package zntg
import (
"encoding/json"
"image"
_ "image/gif" // decoding of GIF
_ "image/jpeg" // decoding of JPEG
"image/png"
"io"
"io/ioutil"
"os"
"path/filepath"
)
// CreateJSONDecoder create a new a generic JSON decoder for the specified value.
func CreateJSONDecoder(value interface{}) DecoderFn {
return func(r io.Reader) (interface{}, error) {
decoder := json.NewDecoder(r)
return value, decoder.Decode(value)
}
}
// DecoderFn describes a generic decoder.
type DecoderFn func(io.Reader) (interface{}, error)
// DecodeFile is a generic decode method to decode content from disk.
func DecodeFile(path string, decoder func(io.Reader) (interface{}, error)) (interface{}, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return decoder(f)
}
// DecodeImage tries to decode a file to an image.
func DecodeImage(path string) (image.Image, error) {
value, err := DecodeFile(path, ImageDecoder)
if err != nil {
return nil, err
}
return value.(image.Image), nil
}
// DecodeJSON tries to decode a file to specifed value.
func DecodeJSON(path string, value interface{}) error {
_, err := DecodeFile(path, CreateJSONDecoder(value))
return err
}
// Dir is a convenience struct for representing a path to a directory.
type Dir struct {
Path string
}
// FilePath returns the path of a file with the specified name in the directory.
func (d *Dir) FilePath(name string) string {
return filepath.Join(d.Path, name)
}
// Write writes the content of the reader into a file with the specified name.
func (d *Dir) Write(name string, r io.Reader) error {
path := d.FilePath(name)
dir := filepath.Dir(path)
os.MkdirAll(dir, 0777)
return EncodeFile(path, r, CopyReader)
}
// Destroy removes the directory.
func (d *Dir) Destroy() error {
return os.RemoveAll(d.Path)
}
// EncoderFn describes a generic encoder.
type EncoderFn func(io.Writer, interface{}) error
// EncodeFile is a generic encode method to encode content to disk.
func EncodeFile(path string, value interface{}, encoder EncoderFn) error {
dst, err := os.Create(path)
if err != nil {
return err
}
defer dst.Close()
return encoder(dst, value)
}
// EncodeJSON encodes a JSON struct to a file.
func EncodeJSON(path string, value interface{}) error {
return EncodeFile(path, value, JSONEncoder)
}
// EncodePNG encodes an image to a file.
func EncodePNG(path string, im image.Image) error {
return EncodeFile(path, im, PNGEncoder)
}
// FileExists returns if file exists on specified path.
func FileExists(path string) bool {
if _, err := os.Stat(path); os.IsNotExist(err) {
return false
}
return true
}
var _ DecoderFn = ImageDecoder
// ImageDecoder is a generic image decoder.
func ImageDecoder(r io.Reader) (interface{}, error) {
im, _, err := image.Decode(r)
return im, err
}
var _ EncoderFn = JSONEncoder
// JSONEncoder is a generic JSON encoder.
func JSONEncoder(w io.Writer, value interface{}) error {
encoder := json.NewEncoder(w)
return encoder.Encode(value)
}
var _ EncoderFn = PNGEncoder
// PNGEncoder is a generic PNG encoder.
func PNGEncoder(w io.Writer, value interface{}) error { return png.Encode(w, value.(image.Image)) }
// NewTempDir creates a temporary directory.
func NewTempDir(prefix string) (*Dir, error) {
path, err := ioutil.TempDir("", prefix)
if nil != err {
return nil, err
}
return &Dir{path}, nil
}
// UserCacheDir gives back the user configuration directory with given name.
func UserCacheDir(name string) (string, error) {
config, err := os.UserCacheDir()
if err != nil {
return "", err
}
dir := filepath.Join(config, name)
err = os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
return dir, nil
}
// UserCacheFile gives back the path to the file with the specified name and the directory named app.
func UserCacheFile(app, name string) (string, error) {
dir, err := UserCacheDir(app)
if err != nil {
return "", err
}
return filepath.Join(dir, name), nil
}
// UserConfigDir gives back the user configuration directory with given name.
func UserConfigDir(name string) (string, error) {
config, err := os.UserConfigDir()
if err != nil {
return "", err
}
dir := filepath.Join(config, name)
err = os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
return dir, nil
}
// UserConfigFile gives back the path to the file with the specified name and the directory named app.
func UserConfigFile(app, name string) (string, error) {
dir, err := UserConfigDir(app)
if err != nil {
return "", err
}
return filepath.Join(dir, name), nil
}
var _ EncoderFn = CopyReader
// CopyReader copies the provided value to the output.
func CopyReader(w io.Writer, value interface{}) error {
_, err := io.Copy(w, value.(io.Reader))
return err
}

View File

@ -1,71 +0,0 @@
package play
import (
"fmt"
"time"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
type FPS struct {
ui.ControlBase
Align ui.HorizontalAlignment
update zntg.Animation
i int
frames []int
total int
}
func (f *FPS) Shown() {
f.update.Interval = 20 * time.Millisecond
f.update.Start()
f.i = 0
f.frames = make([]int, 51)
f.total = 0
}
func (f *FPS) Hidden() {
f.update.Pause()
}
func (f *FPS) font(ctx ui.Context) ui.Font {
font := ctx.Fonts().Font("debug")
if font != nil {
return font
}
return ctx.Fonts().Font("default")
}
func (f *FPS) Render(ctx ui.Context) {
_, n := f.update.AnimateDelta()
for i := 0; i < n; i++ {
f.total += f.frames[f.i]
f.i = (f.i + 1) % len(f.frames)
f.total -= f.frames[f.i]
f.frames[f.i] = 0
}
f.frames[f.i]++
font := f.font(ctx)
fps := fmt.Sprintf("FPS: %d", f.total)
renderer := ctx.Renderer()
switch f.Align {
case ui.AlignLeft:
ctx.Renderer().Text(font, geom.PtF32(5, 5), ctx.Style().Palette.Background, fps)
ctx.Renderer().Text(font, geom.PtF32(4, 4), ctx.Style().Palette.Text, fps)
case ui.AlignCenter:
center := .5 * renderer.Size().ToF32().X
ctx.Renderer().TextAlign(font, geom.PtF32(center, 5), ctx.Style().Palette.Background, fps, ui.AlignCenter)
ctx.Renderer().TextAlign(font, geom.PtF32(center+1, 4), ctx.Style().Palette.Text, fps, ui.AlignCenter)
case ui.AlignRight:
right := renderer.Size().ToF32().X - 5
ctx.Renderer().TextAlign(font, geom.PtF32(right, 5), ctx.Style().Palette.Background, fps, ui.AlignRight)
ctx.Renderer().TextAlign(font, geom.PtF32(right+1, 4), ctx.Style().Palette.Text, fps, ui.AlignRight)
}
}

View File

@ -1,163 +0,0 @@
package play
import (
"opslag.de/schobers/geom"
)
// IsometricProjection represents an 2D area (view) that contains isometric tiles.
type IsometricProjection struct {
center geom.PointF32 // tile coordinate
zoom float32 // factor a tile is blown up (negative is smaller, possitive is larger)
zoomInverse float32 // 1/zoom; calculated
tileSize geom.PointF32 // size of a single tile (maximum width & height difference of its corners)
viewBounds geom.RectangleF32 // bounds of the view (screen coordinates)
viewCenter geom.PointF32 // center of view; calculated
tileSizeTransformed geom.PointF32 // calculated
tileToViewTransformation geom.PointF32 // calculated
viewToTileTransformation geom.PointF32 // calculated
}
// NewIsometricProjection creates a new isometric projection. By default the tile with the coordinate (0, 0) will be centered in the viewBounds. The tile size is represented with maximum width & height difference of its corners.
func NewIsometricProjection(tileSize geom.PointF32, viewBounds geom.RectangleF32) *IsometricProjection {
p := &IsometricProjection{zoom: 1, tileSize: tileSize, viewBounds: viewBounds}
p.update()
return p
}
func (p *IsometricProjection) update() {
if p.zoom == 0 {
p.zoom = 1
}
p.zoomInverse = 1 / p.zoom
p.viewCenter = p.viewBounds.Center()
p.tileSizeTransformed = p.tileSize.Mul(p.zoom)
p.tileToViewTransformation = p.tileSize.Mul(.5 * p.zoom)
p.viewToTileTransformation = geom.PtF32(1/p.tileSizeTransformed.X, 1/p.tileSizeTransformed.Y)
}
// Center gives back the coordinate of the center tile
func (p *IsometricProjection) Center() geom.PointF32 { return p.center }
// Enumerate enumerates all tiles in the set view bounds and calls action for every tile.
func (p *IsometricProjection) Enumerate(action func(tile geom.PointF32, view geom.PointF32)) {
p.EnumerateInt(func(tile geom.Point, view geom.PointF32) {
action(tile.ToF32(), view)
})
}
// EnumerateInt enumerates all tiles in the set view bounds and calls action for every tile.
func (p *IsometricProjection) EnumerateInt(action func(tile geom.Point, view geom.PointF32)) {
visible := p.viewBounds
visible.Max.Y += p.tileSize.Y * p.zoom
topLeft := p.ViewToTile(geom.PtF32(visible.Min.X, visible.Min.Y))
topRight := p.ViewToTile(geom.PtF32(visible.Max.X, visible.Min.Y))
bottomLeft := p.ViewToTile(geom.PtF32(visible.Min.X, visible.Max.Y))
bottomRight := p.ViewToTile(geom.PtF32(visible.Max.X, visible.Max.Y))
minY, maxY := int(geom.Floor32(topRight.Y)), int(geom.Ceil32(bottomLeft.Y))
minX, maxX := int(geom.Floor32(topLeft.X)), int(geom.Ceil32(bottomRight.X))
tileOffset := p.tileSizeTransformed.Mul(.5)
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
tile := geom.Pt(x, y)
view := p.TileToView(tile.ToF32())
if view.X+tileOffset.X < visible.Min.X || view.Y+tileOffset.Y < visible.Min.Y {
continue
}
if view.X-tileOffset.X > visible.Max.X || view.Y-tileOffset.Y > visible.Max.Y {
break
}
action(tile, view)
}
}
}
// MoveCenterTo moves the center of the projection to the given tile.
func (p *IsometricProjection) MoveCenterTo(tile geom.PointF32) {
p.center = tile
p.update()
}
// Pan translates the center of the projection with the given delta in view coordinates.
func (p *IsometricProjection) Pan(delta geom.PointF32) {
p.PanTile(p.ViewToTileRelative(delta))
}
// PanTile translates the center of the projection with the given delta in tile coordinates.
func (p *IsometricProjection) PanTile(delta geom.PointF32) {
p.MoveCenterTo(p.center.Add(delta))
}
// SetTileSize sets the size of a single tile (maximum width & height difference of its corners).
func (p *IsometricProjection) SetTileSize(size geom.PointF32) {
p.tileSize = size
p.update()
}
// SetViewBounds sets the bounds of the view coordinates. Used to calculate the center with & for calculating the visible tiles.
func (p *IsometricProjection) SetViewBounds(bounds geom.RectangleF32) {
p.viewBounds = bounds
p.update()
}
// SetZoom changes the zoom to and keeps the around (tile) coordinate on the same position.
func (p *IsometricProjection) SetZoom(around geom.PointF32, zoom float32) {
if p.zoom == zoom {
return
}
p.center = around.Sub(around.Sub(p.center).Mul(p.zoom / zoom))
p.zoom = zoom
p.update()
}
// TileInt gives the integer tile coordinate.
func (p *IsometricProjection) TileInt(tile geom.PointF32) geom.Point {
return geom.Pt(int(geom.Round32(tile.X)), int(geom.Round32(tile.Y)))
}
// TileToView transforms the tile coordinate to the corresponding view coordinate.
func (p *IsometricProjection) TileToView(tile geom.PointF32) geom.PointF32 {
translated := tile.Sub(p.center)
return p.viewCenter.Add2D((translated.X-translated.Y)*p.tileToViewTransformation.X, (translated.X+translated.Y)*p.tileToViewTransformation.Y)
}
// ViewCenter returns the center of the view (calculated from the set view bounds).
func (p *IsometricProjection) ViewCenter() geom.PointF32 { return p.viewCenter }
// ViewToTile transforms the view coordinate to the corresponding tile coordinate.
func (p *IsometricProjection) ViewToTile(view geom.PointF32) geom.PointF32 {
return p.ViewToTileRelative(view.Sub(p.viewCenter)).Add(p.center)
}
// ViewToTileInt transforms the view coordinate to the corresponding integer tile coordinate.
func (p *IsometricProjection) ViewToTileInt(view geom.PointF32) geom.Point {
tile := p.ViewToTile(view)
return p.TileInt(tile)
}
// ViewToTileRelative transforms the relative (to 0,0) view coordinate to the corresponding tile coordinate
func (p *IsometricProjection) ViewToTileRelative(view geom.PointF32) geom.PointF32 {
return geom.PtF32(view.X*p.viewToTileTransformation.X+view.Y*p.viewToTileTransformation.Y, -view.X*p.viewToTileTransformation.X+view.Y*p.viewToTileTransformation.Y)
}
// Zoom returns the current zoom.
func (p *IsometricProjection) Zoom() float32 { return p.zoom }
// ZoomIn zooms in around the given tile coordinate.
func (p *IsometricProjection) ZoomIn(around geom.PointF32) {
if p.zoom >= 2 {
return
}
p.SetZoom(around, 2*p.zoom)
}
// ZoomOut zooms in around the given tile coordinate.
func (p *IsometricProjection) ZoomOut(around geom.PointF32) {
if p.zoom <= .25 {
return
}
p.SetZoom(around, .5*p.zoom)
}

View File

@ -1,36 +0,0 @@
package play
import (
"testing"
"github.com/stretchr/testify/assert"
"opslag.de/schobers/geom"
)
func createIsometricProjection() *IsometricProjection {
return NewIsometricProjection(geom.PtF32(23, 11), geom.RectRelF32(0, 0, 160, 160))
}
func TestViewToTile(t *testing.T) {
p := createIsometricProjection()
assert.Equal(t, geom.PtF32(0, 0), p.ViewToTile(geom.PtF32(80, 80)))
assert.Equal(t, geom.PtF32(-1, 1), p.ViewToTile(geom.PtF32(57, 80)))
assert.Equal(t, geom.PtF32(2, 2), p.ViewToTile(geom.PtF32(80, 102)))
assert.Equal(t, geom.PtF32(-1, -3), p.ViewToTile(geom.PtF32(103, 58)))
}
func TestViewToTileInt(t *testing.T) {
p := createIsometricProjection()
assert.Equal(t, geom.Pt(0, 0), p.ViewToTileInt(geom.PtF32(80, 80)))
assert.Equal(t, geom.Pt(0, 0), p.ViewToTileInt(geom.PtF32(69, 80)))
assert.Equal(t, geom.Pt(-1, 1), p.ViewToTileInt(geom.PtF32(68, 80)))
}
func TestTileToView(t *testing.T) {
p := createIsometricProjection()
assert.Equal(t, geom.PtF32(80, 80), p.TileToView(geom.PtF32(0, 0)))
assert.Equal(t, geom.PtF32(57, 80), p.TileToView(geom.PtF32(-1, 1)))
assert.Equal(t, geom.PtF32(80, 102), p.TileToView(geom.PtF32(2, 2)))
assert.Equal(t, geom.PtF32(103, 58), p.TileToView(geom.PtF32(-1, -3)))
}

View File

@ -1,11 +0,0 @@
package sdlui
import (
"image/color"
"github.com/veandco/go-sdl2/sdl"
)
func ColorSDL(c color.Color) sdl.Color {
return sdl.Color(color.RGBAModel.Convert(c).(color.RGBA))
}

View File

@ -1,526 +0,0 @@
package sdlui
import (
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/zntg/ui"
)
func eventBase(e sdl.Event) ui.EventBase {
return ui.EventBase{StampInSeconds: .001 * float64(e.GetTimestamp())}
}
func key(code sdl.Keycode) ui.Key {
switch code {
case sdl.K_UNKNOWN:
return ui.KeyNone
case sdl.K_RETURN:
return ui.KeyEnter
case sdl.K_ESCAPE:
return ui.KeyEscape
case sdl.K_BACKSPACE:
return ui.KeyBackspace
case sdl.K_TAB:
return ui.KeyTab
case sdl.K_SPACE:
return ui.KeySpace
// case sdl.K_EXCLAIM:
// return ui.KeyNone
// case sdl.K_QUOTEDBL:
// return ui.KeyNone
// case sdl.K_HASH:
// return ui.KeyNone
// case sdl.K_PERCENT:
// return ui.KeyNone
// case sdl.K_DOLLAR:
// return ui.KeyNone
// case sdl.K_AMPERSAND:
// return ui.KeyNone
case sdl.K_QUOTE:
return ui.KeyQuote
// case sdl.K_LEFTPAREN:
// return ui.KeyNone
// case sdl.K_RIGHTPAREN:
// return ui.KeyNone
case sdl.K_ASTERISK:
return ui.KeyPadAsterisk
case sdl.K_PLUS:
return ui.KeyPadPlus
case sdl.K_COMMA:
return ui.KeyComma
case sdl.K_MINUS:
return ui.KeyMinus
case sdl.K_PERIOD:
return ui.KeyFullstop
case sdl.K_SLASH:
return ui.KeySlash
case sdl.K_0:
return ui.Key0
case sdl.K_1:
return ui.Key1
case sdl.K_2:
return ui.Key2
case sdl.K_3:
return ui.Key3
case sdl.K_4:
return ui.Key4
case sdl.K_5:
return ui.Key5
case sdl.K_6:
return ui.Key6
case sdl.K_7:
return ui.Key7
case sdl.K_8:
return ui.Key8
case sdl.K_9:
return ui.Key9
// case sdl.K_COLON:
// return ui.KeyNone
case sdl.K_SEMICOLON:
return ui.KeySemicolon
// case sdl.K_LESS:
// return ui.KeyNone
case sdl.K_EQUALS:
return ui.KeyEquals
// case sdl.K_GREATER:
// return ui.KeyNone
// case sdl.K_QUESTION:
// return ui.KeyNone
// case sdl.K_AT:
// return ui.KeyNone
case sdl.K_LEFTBRACKET:
return ui.KeyOpenBrace
case sdl.K_BACKSLASH:
return ui.KeyBackslash
case sdl.K_RIGHTBRACKET:
return ui.KeyCloseBrace
// case sdl.K_CARET:
// return ui.KeyNone
// case sdl.K_UNDERSCORE:
// return ui.KeyNone
case sdl.K_BACKQUOTE:
return ui.KeyBacktick
case sdl.K_a:
return ui.KeyA
case sdl.K_b:
return ui.KeyB
case sdl.K_c:
return ui.KeyC
case sdl.K_d:
return ui.KeyD
case sdl.K_e:
return ui.KeyE
case sdl.K_f:
return ui.KeyF
case sdl.K_g:
return ui.KeyG
case sdl.K_h:
return ui.KeyH
case sdl.K_i:
return ui.KeyI
case sdl.K_j:
return ui.KeyJ
case sdl.K_k:
return ui.KeyK
case sdl.K_l:
return ui.KeyL
case sdl.K_m:
return ui.KeyM
case sdl.K_n:
return ui.KeyN
case sdl.K_o:
return ui.KeyO
case sdl.K_p:
return ui.KeyP
case sdl.K_q:
return ui.KeyQ
case sdl.K_r:
return ui.KeyR
case sdl.K_s:
return ui.KeyS
case sdl.K_t:
return ui.KeyT
case sdl.K_u:
return ui.KeyU
case sdl.K_v:
return ui.KeyV
case sdl.K_w:
return ui.KeyW
case sdl.K_x:
return ui.KeyX
case sdl.K_y:
return ui.KeyY
case sdl.K_z:
return ui.KeyZ
case sdl.K_CAPSLOCK:
return ui.KeyCapsLock
case sdl.K_F1:
return ui.KeyF1
case sdl.K_F2:
return ui.KeyF2
case sdl.K_F3:
return ui.KeyF3
case sdl.K_F4:
return ui.KeyF4
case sdl.K_F5:
return ui.KeyF5
case sdl.K_F6:
return ui.KeyF6
case sdl.K_F7:
return ui.KeyF7
case sdl.K_F8:
return ui.KeyF8
case sdl.K_F9:
return ui.KeyF9
case sdl.K_F10:
return ui.KeyF10
case sdl.K_F11:
return ui.KeyF11
case sdl.K_F12:
return ui.KeyF12
case sdl.K_PRINTSCREEN:
return ui.KeyPrintScreen
case sdl.K_SCROLLLOCK:
return ui.KeyScrollLock
case sdl.K_PAUSE:
return ui.KeyPause
case sdl.K_INSERT:
return ui.KeyInsert
case sdl.K_HOME:
return ui.KeyHome
case sdl.K_PAGEUP:
return ui.KeyPageUp
case sdl.K_DELETE:
return ui.KeyDelete
case sdl.K_END:
return ui.KeyEnd
case sdl.K_PAGEDOWN:
return ui.KeyPageDown
case sdl.K_RIGHT:
return ui.KeyRight
case sdl.K_LEFT:
return ui.KeyLeft
case sdl.K_DOWN:
return ui.KeyDown
case sdl.K_UP:
return ui.KeyUp
// case sdl.K_NUMLOCKCLEAR:
// return ui.KeyNone
case sdl.K_KP_DIVIDE:
return ui.KeyPadSlash
case sdl.K_KP_MULTIPLY:
return ui.KeyPadAsterisk
case sdl.K_KP_MINUS:
return ui.KeyPadMinus
case sdl.K_KP_PLUS:
return ui.KeyPadPlus
case sdl.K_KP_ENTER:
return ui.KeyPadEnter
case sdl.K_KP_1:
return ui.KeyPad1
case sdl.K_KP_2:
return ui.KeyPad2
case sdl.K_KP_3:
return ui.KeyPad3
case sdl.K_KP_4:
return ui.KeyPad4
case sdl.K_KP_5:
return ui.KeyPad5
case sdl.K_KP_6:
return ui.KeyPad6
case sdl.K_KP_7:
return ui.KeyPad7
case sdl.K_KP_8:
return ui.KeyPad8
case sdl.K_KP_9:
return ui.KeyPad9
case sdl.K_KP_0:
return ui.KeyPad0
// case sdl.K_KP_PERIOD:
// return ui.KeyNone
// case sdl.K_APPLICATION:
// return ui.KeyNone
// case sdl.K_POWER:
// return ui.KeyNone
case sdl.K_KP_EQUALS:
return ui.KeyPadEquals
// case sdl.K_F13:
// return ui.KeyNone
// case sdl.K_F14:
// return ui.KeyNone
// case sdl.K_F15:
// return ui.KeyNone
// case sdl.K_F16:
// return ui.KeyNone
// case sdl.K_F17:
// return ui.KeyNone
// case sdl.K_F18:
// return ui.KeyNone
// case sdl.K_F19:
// return ui.KeyNone
// case sdl.K_F20:
// return ui.KeyNone
// case sdl.K_F21:
// return ui.KeyNone
// case sdl.K_F22:
// return ui.KeyNone
// case sdl.K_F23:
// return ui.KeyNone
// case sdl.K_F24:
// return ui.KeyNone
// case sdl.K_EXECUTE:
// return ui.KeyNone
// case sdl.K_HELP:
// return ui.KeyNone
case sdl.K_MENU:
return ui.KeyNone
case sdl.K_SELECT:
return ui.KeyNone
case sdl.K_STOP:
return ui.KeyNone
case sdl.K_AGAIN:
return ui.KeyNone
case sdl.K_UNDO:
return ui.KeyNone
case sdl.K_CUT:
return ui.KeyNone
case sdl.K_COPY:
return ui.KeyNone
case sdl.K_PASTE:
return ui.KeyNone
case sdl.K_FIND:
return ui.KeyNone
case sdl.K_MUTE:
return ui.KeyNone
case sdl.K_VOLUMEUP:
return ui.KeyVolumeUp
case sdl.K_VOLUMEDOWN:
return ui.KeyVolumeDown
// case sdl.K_KP_COMMA:
// return ui.KeyNone
// case sdl.K_KP_EQUALSAS400:
// return ui.KeyNone
// case sdl.K_ALTERASE:
// return ui.KeyNone
// case sdl.K_SYSREQ:
// return ui.KeyNone
// case sdl.K_CANCEL:
// return ui.KeyNone
// case sdl.K_CLEAR:
// return ui.KeyNone
// case sdl.K_PRIOR:
// return ui.KeyNone
// case sdl.K_RETURN2:
// return ui.KeyNone
// case sdl.K_SEPARATOR:
// return ui.KeyNone
// case sdl.K_OUT:
// return ui.KeyNone
// case sdl.K_OPER:
// return ui.KeyNone
// case sdl.K_CLEARAGAIN:
// return ui.KeyNone
// case sdl.K_CRSEL:
// return ui.KeyNone
// case sdl.K_EXSEL:
// return ui.KeyNone
// case sdl.K_KP_00:
// return ui.KeyNone
// case sdl.K_KP_000:
// return ui.KeyNone
// case sdl.K_THOUSANDSSEPARATOR:
// return ui.KeyNone
// case sdl.K_DECIMALSEPARATOR:
// return ui.KeyNone
// case sdl.K_CURRENCYUNIT:
// return ui.KeyNone
// case sdl.K_CURRENCYSUBUNIT:
// return ui.KeyNone
// case sdl.K_KP_LEFTPAREN:
// return ui.KeyNone
// case sdl.K_KP_RIGHTPAREN:
// return ui.KeyNone
case sdl.K_KP_LEFTBRACE:
return ui.KeyOpenBrace // generic equivalent
case sdl.K_KP_RIGHTBRACE:
return ui.KeyCloseBrace // generic equivalent
case sdl.K_KP_TAB:
return ui.KeyTab // generic equivalent
case sdl.K_KP_BACKSPACE:
return ui.KeyBackspace // generic equivalent
case sdl.K_KP_A:
return ui.KeyA // generic equivalent
case sdl.K_KP_B:
return ui.KeyB // generic equivalent
case sdl.K_KP_C:
return ui.KeyC // generic equivalent
case sdl.K_KP_D:
return ui.KeyD // generic equivalent
case sdl.K_KP_E:
return ui.KeyE // generic equivalent
case sdl.K_KP_F:
return ui.KeyF // generic equivalent
// case sdl.K_KP_XOR:
// return ui.KeyNone
// case sdl.K_KP_POWER:
// return ui.KeyNone
// case sdl.K_KP_PERCENT:
// return ui.KeyNone
// case sdl.K_KP_LESS:
// return ui.KeyNone
// case sdl.K_KP_GREATER:
// return ui.KeyNone
// case sdl.K_KP_AMPERSAND:
// return ui.KeyNone
// case sdl.K_KP_DBLAMPERSAND:
// return ui.KeyNone
// case sdl.K_KP_VERTICALBAR:
// return ui.KeyNone
// case sdl.K_KP_DBLVERTICALBAR:
// return ui.KeyNone
// case sdl.K_KP_COLON:
// return ui.KeyNone
// case sdl.K_KP_HASH:
// return ui.KeyNone
case sdl.K_KP_SPACE:
return ui.KeySpace // generic equivalent
// case sdl.K_KP_AT:
// return ui.KeyNone
// case sdl.K_KP_EXCLAM:
// return ui.KeyNone
// case sdl.K_KP_MEMSTORE:
// return ui.KeyNone
// case sdl.K_KP_MEMRECALL:
// return ui.KeyNone
// case sdl.K_KP_MEMCLEAR:
// return ui.KeyNone
// case sdl.K_KP_MEMADD:
// return ui.KeyNone
// case sdl.K_KP_MEMSUBTRACT:
// return ui.KeyNone
// case sdl.K_KP_MEMMULTIPLY:
// return ui.KeyNone
// case sdl.K_KP_MEMDIVIDE:
// return ui.KeyNone
// case sdl.K_KP_PLUSMINUS:
// return ui.KeyNone
// case sdl.K_KP_CLEAR:
// return ui.KeyNone
// case sdl.K_KP_CLEARENTRY:
// return ui.KeyNone
// case sdl.K_KP_BINARY:
// return ui.KeyNone
// case sdl.K_KP_OCTAL:
// return ui.KeyNone
// case sdl.K_KP_DECIMAL:
// return ui.KeyNone
// case sdl.K_KP_HEXADECIMAL:
// return ui.KeyNone
case sdl.K_LCTRL:
return ui.KeyLeftControl
case sdl.K_LSHIFT:
return ui.KeyLeftShift
case sdl.K_LALT:
return ui.KeyAlt
case sdl.K_LGUI:
return ui.KeyLeftWin
case sdl.K_RCTRL:
return ui.KeyRightControl
case sdl.K_RSHIFT:
return ui.KeyRightShift
case sdl.K_RALT:
return ui.KeyAltGr
case sdl.K_RGUI:
return ui.KeyRightWin
// case sdl.K_MODE:
// return ui.KeyNone
// case sdl.K_AUDIONEXT:
// return ui.KeyNone
// case sdl.K_AUDIOPREV:
// return ui.KeyNone
// case sdl.K_AUDIOSTOP:
// return ui.KeyNone
// case sdl.K_AUDIOPLAY:
// return ui.KeyNone
// case sdl.K_AUDIOMUTE:
// return ui.KeyNone
// case sdl.K_MEDIASELECT:
// return ui.KeyNone
// case sdl.K_WWW:
// return ui.KeyNone
// case sdl.K_MAIL:
// return ui.KeyNone
// case sdl.K_CALCULATOR:
// return ui.KeyNone
// case sdl.K_COMPUTER:
// return ui.KeyNone
// case sdl.K_AC_SEARCH:
// return ui.KeyNone
// case sdl.K_AC_HOME:
// return ui.KeyNone
// case sdl.K_AC_BACK:
// return ui.KeyNone
// case sdl.K_AC_FORWARD:
// return ui.KeyNone
// case sdl.K_AC_STOP:
// return ui.KeyNone
// case sdl.K_AC_REFRESH:
// return ui.KeyNone
// case sdl.K_AC_BOOKMARKS:
// return ui.KeyNone
// case sdl.K_BRIGHTNESSDOWN:
// return ui.KeyNone
// case sdl.K_BRIGHTNESSUP:
// return ui.KeyNone
// case sdl.K_DISPLAYSWITCH:
// return ui.KeyNone
// case sdl.K_KBDILLUMTOGGLE:
// return ui.KeyNone
// case sdl.K_KBDILLUMDOWN:
// return ui.KeyNone
// case sdl.K_KBDILLUMUP:
// return ui.KeyNone
// case sdl.K_EJECT:
// return ui.KeyNone
// case sdl.K_SLEEP:
// return ui.KeyNone
default:
return ui.KeyNone
}
}
func keyModifiers(mod uint16) ui.KeyModifier {
var modifiers ui.KeyModifier
if mod&uint16(sdl.KMOD_ALT|sdl.KMOD_LALT) != 0 {
modifiers |= ui.KeyModifierAlt
}
if mod&uint16(sdl.KMOD_CTRL|sdl.KMOD_LCTRL) != 0 {
modifiers |= ui.KeyModifierControl
}
if mod&uint16(sdl.KMOD_SHIFT|sdl.KMOD_LSHIFT) != 0 {
modifiers |= ui.KeyModifierShift
}
if mod&uint16(sdl.KMOD_GUI|sdl.KMOD_LGUI) != 0 {
modifiers |= ui.KeyModifierOSCommand
}
return modifiers
}
func mouseButton(b uint8) ui.MouseButton {
switch b {
case sdl.BUTTON_LEFT:
return ui.MouseButtonLeft
case sdl.BUTTON_MIDDLE:
return ui.MouseButtonMiddle
case sdl.BUTTON_RIGHT:
return ui.MouseButtonRight
}
return ui.MouseButtonLeft
}
func mouseEvent(e sdl.Event, x, y int32) ui.MouseEvent {
return ui.MouseEvent{
X: float32(x),
Y: float32(y),
EventBase: eventBase(e),
}
}

View File

@ -1,13 +0,0 @@
package sdlui
import (
"testing"
"github.com/stretchr/testify/assert"
"opslag.de/schobers/zntg/ui"
)
func TestKeyModifiers(t *testing.T) {
var mod uint16 = 4097
assert.Equal(t, ui.KeyModifier(ui.KeyModifierShift), keyModifiers(mod))
}

View File

@ -1,29 +0,0 @@
package sdlui
import (
"github.com/veandco/go-sdl2/ttf"
"opslag.de/schobers/geom"
)
type Font struct {
*ttf.Font
}
func (f *Font) Destroy() error {
f.Font.Close()
return nil
}
func (f *Font) Height() float32 {
return float32(f.Font.Height())
}
func (f *Font) Measure(t string) geom.RectangleF32 {
w, h, _ := f.SizeUTF8(t)
return geom.RectF32(0, 0, float32(w), float32(h))
}
func (f *Font) WidthOf(t string) float32 {
w, _, _ := f.SizeUTF8(t)
return float32(w)
}

View File

@ -1,26 +0,0 @@
package sdlui
import (
"image"
"image/draw"
)
func NRGBAImage(m image.Image) *image.NRGBA {
nrgba, ok := m.(*image.NRGBA)
if ok {
return nrgba
}
nrgba = image.NewNRGBA(m.Bounds())
draw.Draw(nrgba, nrgba.Bounds(), m, image.ZP, draw.Over)
return nrgba
}
func RGBAImage(m image.Image) *image.RGBA {
rgba, ok := m.(*image.RGBA)
if ok {
return rgba
}
rgba = image.NewRGBA(m.Bounds())
draw.Draw(rgba, rgba.Bounds(), m, image.ZP, draw.Over)
return rgba
}

View File

@ -1,38 +0,0 @@
package sdlui
import (
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
)
func Rect(x, y, w, h int32) sdl.Rect {
return sdl.Rect{X: x, Y: y, W: w, H: h}
}
func RectAbs(x1, y1, x2, y2 int32) sdl.Rect {
if x1 > x2 {
x1, x2 = x2, x1
}
if y1 > y2 {
y1, y2 = y2, y1
}
return Rect(x1, y1, x2-x1, y2-y1)
}
func RectAbsPtr(x1, y1, x2, y2 int32) *sdl.Rect {
rect := RectAbs(x1, y1, x2, y2)
return &rect
}
func RectPtr(x, y, w, h int32) *sdl.Rect {
return &sdl.Rect{X: x, Y: y, W: w, H: h}
}
func SDLRectangle(r geom.RectangleF32) sdl.Rect {
return sdl.Rect{X: int32(r.Min.X), Y: int32(r.Min.Y), W: int32(r.Dx()), H: int32(r.Dy())}
}
func SDLRectanglePtr(r geom.RectangleF32) *sdl.Rect {
rect := SDLRectangle(r)
return &rect
}

View File

@ -1,565 +0,0 @@
package sdlui
import (
"errors"
"image"
"image/color"
_ "image/jpeg" // add JPEG for CreateSurfacePath
_ "image/png" // add PNG for CreateSurfacePath
"math"
"unsafe"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
var errNotImplemented = errors.New(`not implemented`)
type Renderer struct {
window *sdl.Window
renderer *sdl.Renderer
refresh uint32
resources ui.PhysicalResources
mouse geom.PointF32
cursor ui.MouseCursor
cursors map[sdl.SystemCursor]*sdl.Cursor
}
var _ ui.Renderer = &Renderer{}
var _ ui.Texture = &Renderer{}
type NewRendererOptions struct {
Borderless bool
Location sdl.Point
Resizable bool
VSync bool
}
func NewRenderer(title string, width, height int32, opts NewRendererOptions) (*Renderer, error) {
var clean zntg.Actions
defer func() { clean.Do() }()
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
return nil, err
}
clean = clean.Add(sdl.Quit)
if err := ttf.Init(); err != nil {
return nil, err
}
clean = clean.Add(ttf.Quit)
if opts.VSync {
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1")
}
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
windowFlags := uint32(sdl.WINDOW_SHOWN)
if opts.Borderless {
windowFlags |= sdl.WINDOW_BORDERLESS
}
if opts.Resizable {
windowFlags |= sdl.WINDOW_RESIZABLE
}
window, err := sdl.CreateWindow(title, opts.Location.X, opts.Location.Y, width, height, windowFlags)
if err != nil {
return nil, err
}
clean = clean.AddErr(window.Destroy)
rendererFlags := uint32(sdl.RENDERER_ACCELERATED)
if opts.VSync {
rendererFlags |= sdl.RENDERER_PRESENTVSYNC
}
renderer, err := sdl.CreateRenderer(window, -1, rendererFlags)
if err != nil {
return nil, err
}
renderer.SetDrawBlendMode(sdl.BLENDMODE_BLEND)
clean = clean.AddErr(renderer.Destroy)
refresh := sdl.RegisterEvents(1)
if refresh == math.MaxUint32 {
return nil, errors.New("couldn't register user event")
}
clean = nil
return &Renderer{
window: window,
renderer: renderer,
refresh: refresh,
resources: &ui.OSResources{},
cursors: map[sdl.SystemCursor]*sdl.Cursor{},
}, nil
}
// Events
func (r *Renderer) WindowBounds() geom.RectangleF32 {
x, y := r.window.GetPosition()
w, h := r.window.GetSize()
return geom.RectF32(float32(x), float32(y), float32(x+w), float32(y+h))
}
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) bool {
r.renderer.Present()
waitOrPoll := func() sdl.Event {
if wait {
return sdl.WaitEvent()
}
return sdl.PollEvent()
}
cursor := r.cursor
var pushed bool
for event := waitOrPoll(); event != nil; event = sdl.PollEvent() {
pushed = true
r.cursor = ui.MouseCursorDefault
var unhandled bool
switch e := event.(type) {
case *sdl.WindowEvent:
switch e.Event {
case sdl.WINDOWEVENT_CLOSE:
t.Handle(&ui.DisplayCloseEvent{EventBase: eventBase(e)})
case sdl.WINDOWEVENT_MOVED:
t.Handle(&ui.DisplayMoveEvent{EventBase: eventBase(e), Bounds: r.WindowBounds()})
case sdl.WINDOWEVENT_RESIZED:
t.Handle(&ui.DisplayResizeEvent{EventBase: eventBase(e), Bounds: r.WindowBounds()})
case sdl.WINDOWEVENT_ENTER:
t.Handle(&ui.MouseEnterEvent{MouseEvent: ui.MouseEvent{EventBase: eventBase(e), X: r.mouse.X, Y: r.mouse.Y}})
case sdl.WINDOWEVENT_LEAVE:
t.Handle(&ui.MouseLeaveEvent{MouseEvent: ui.MouseEvent{EventBase: eventBase(e), X: r.mouse.X, Y: r.mouse.Y}})
default:
unhandled = true
}
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
t.Handle(&ui.KeyDownEvent{EventBase: eventBase(e), Key: key(e.Keysym.Sym), Modifiers: keyModifiers(e.Keysym.Mod)})
} else if e.Type == sdl.KEYUP {
t.Handle(&ui.KeyDownEvent{EventBase: eventBase(e), Key: 0, Modifiers: 0})
} else {
unhandled = true
}
case *sdl.TextInputEvent:
if e.Type == sdl.TEXTINPUT {
text := e.GetText()
for _, character := range text {
t.Handle(&ui.TextInputEvent{
EventBase: eventBase(e),
Character: character,
})
}
} else {
unhandled = true
}
case *sdl.MouseButtonEvent:
if e.Type == sdl.MOUSEBUTTONDOWN {
t.Handle(&ui.MouseButtonDownEvent{MouseEvent: mouseEvent(e, e.X, e.Y), Button: mouseButton(e.Button)})
} else {
t.Handle(&ui.MouseButtonUpEvent{MouseEvent: mouseEvent(e, e.X, e.Y), Button: mouseButton(e.Button)})
}
case *sdl.MouseMotionEvent:
r.mouse = geom.PtF32(float32(e.X), float32(e.Y))
t.Handle(&ui.MouseMoveEvent{MouseEvent: ui.MouseEvent{EventBase: eventBase(e), X: r.mouse.X, Y: r.mouse.Y}, MouseWheel: 0})
case *sdl.MouseWheelEvent:
t.Handle(&ui.MouseMoveEvent{MouseEvent: ui.MouseEvent{EventBase: eventBase(e), X: r.mouse.X, Y: r.mouse.Y}, MouseWheel: float32(e.Y)})
case *sdl.UserEvent:
if r.refresh == e.Type {
t.Handle(&ui.RefreshEvent{EventBase: eventBase(e)})
} else {
unhandled = true
}
default:
unhandled = true // not handled by EventTarget.Handle
}
if unhandled {
r.cursor = cursor
}
}
if r.cursor != cursor {
switch r.cursor {
case ui.MouseCursorDefault:
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_ARROW))
case ui.MouseCursorNotAllowed:
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_NO))
case ui.MouseCursorPointer:
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_HAND))
case ui.MouseCursorText:
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_IBEAM))
}
}
return pushed
}
func (r *Renderer) Refresh() {
windowID, _ := r.window.GetID()
e := &sdl.UserEvent{
Type: r.refresh,
WindowID: windowID,
}
sdl.PushEvent(e)
}
func (r *Renderer) Stamp() float64 {
return .001 * float64(sdl.GetTicks())
}
// Lifetime
func (r *Renderer) Destroy() error {
r.renderer.Destroy()
r.window.Destroy()
ttf.Quit()
sdl.Quit()
r.resources.Destroy()
return nil
}
// Drawing
func (r *Renderer) Clear(c color.Color) {
if c == color.Transparent {
return
}
r.SetDrawColorGo(c)
r.renderer.Clear()
}
func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
path, err := r.resources.FetchResource(path)
if err != nil {
return nil, err
}
font, err := ttf.OpenFont(path, size)
if err != nil {
return nil, err
}
return &Font{font}, nil
}
func (r *Renderer) createSurface(source ui.ImageSource) (*sdl.Surface, error) {
m, err := source.CreateImage()
if err != nil {
return nil, err
}
rgba := NRGBAImage(m)
width := int32(rgba.Bounds().Dx())
height := int32(rgba.Bounds().Dy())
surface, err := sdl.CreateRGBSurfaceWithFormatFrom(
unsafe.Pointer(&rgba.Pix[0]),
width, height, 32, int32(rgba.Stride), sdl.PIXELFORMAT_ABGR8888)
if err != nil {
return nil, err
}
return surface, nil
}
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) {
surface, err := r.createSurface(source)
if err != nil {
return nil, err
}
defer surface.Free()
texture, err := r.renderer.CreateTextureFromSurface(surface)
if err != nil {
return nil, err
}
texture.SetBlendMode(sdl.BLENDMODE_BLEND)
if keepSource {
return &TextureImageSource{&Texture{texture}, source}, nil
}
return &Texture{texture}, nil
}
func (r *Renderer) createTextureTarget(w, h float32) (*Texture, error) {
format, err := r.window.GetPixelFormat()
if err != nil {
return nil, err
}
texture, err := r.renderer.CreateTexture(format, sdl.TEXTUREACCESS_TARGET, int32(w), int32(h))
if err != nil {
return nil, err
}
texture.SetBlendMode(sdl.BLENDMODE_BLEND)
return &Texture{texture}, nil
}
func (r *Renderer) CreateTexture(source ui.ImageSource) (ui.Texture, error) {
return r.createTexture(source, true)
}
func (r *Renderer) CreateTextureGo(m image.Image, source bool) (ui.Texture, error) {
return r.createTexture(ui.ImageSourceGo{Image: m}, source)
}
func (r *Renderer) CreateTexturePath(path string, source bool) (ui.Texture, error) {
return r.createTexture(ui.ImageSourceResource{Resources: r.resources, Name: path}, source)
}
func (r *Renderer) CreateTextureTarget(w, h float32) (ui.Texture, error) {
return r.createTextureTarget(w, h)
}
func (r *Renderer) DefaultTarget() ui.Texture { return r }
func (r *Renderer) drawTexture(t sdlTexture, src, dst sdl.Rect, opts ui.DrawOptions) {
if opts.Tint != nil {
t.SetColor(opts.Tint)
}
r.renderer.Copy(t.Native(), &src, &dst)
}
func (r *Renderer) DrawTexture(t ui.Texture, dst geom.RectangleF32) {
r.DrawTextureOptions(t, dst, ui.DrawOptions{})
}
func (r *Renderer) DrawTextureOptions(t ui.Texture, dst geom.RectangleF32, opts ui.DrawOptions) {
texture, ok := t.(sdlTexture)
if !ok {
return
}
var source sdl.Rect
if opts.Source != nil {
source = RectAbs(int32(opts.Source.Min.X), int32(opts.Source.Min.Y), int32(opts.Source.Max.X), int32(opts.Source.Max.Y))
} else {
width, height, err := texture.Size()
if err != nil {
return
}
source = Rect(0, 0, width, height)
}
r.drawTexture(texture, source, RectAbs(int32(dst.Min.X), int32(dst.Min.Y), int32(dst.Max.X), int32(dst.Max.Y)), opts)
}
func (r *Renderer) DrawTexturePoint(t ui.Texture, dst geom.PointF32) {
r.DrawTexturePointOptions(t, dst, ui.DrawOptions{})
}
func (r *Renderer) DrawTexturePointOptions(t ui.Texture, dst geom.PointF32, opts ui.DrawOptions) {
texture, ok := t.(sdlTexture)
if !ok {
return
}
var source, destination sdl.Rect
if opts.Source != nil {
source = RectAbs(int32(opts.Source.Min.X), int32(opts.Source.Min.Y), int32(opts.Source.Max.X), int32(opts.Source.Max.Y))
destination = Rect(int32(dst.X), int32(dst.Y), source.W, source.H)
} else {
width, height, err := texture.Size()
if err != nil {
return
}
source = Rect(0, 0, width, height)
destination = Rect(int32(dst.X), int32(dst.Y), width, height)
}
r.drawTexture(texture, source, destination, opts)
}
func (r *Renderer) FillRectangle(rect geom.RectangleF32, c color.Color) {
r.SetDrawColorGo(c)
r.renderer.FillRect(SDLRectanglePtr(rect))
}
func (r *Renderer) Line(p, q geom.PointF32, color color.Color, thickness float32) {
r.SetDrawColorGo(color)
r.renderer.DrawLineF(p.X, p.Y, q.X, q.Y)
}
func (r *Renderer) Location() geom.Point {
x, y := r.window.GetPosition()
return geom.Pt(int(x), int(y))
}
func (r *Renderer) Move(to geom.Point) {
r.window.SetPosition(int32(to.X), int32(to.Y))
}
func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness float32) {
r.SetDrawColorGo(c)
if rect.Dx() == 0 { // SDL doesn't draw a 1 px wide line when Dx() == 0 && thickness == 1
offset := int32(rect.Min.X - .5*thickness)
for thick := int32(thickness); thick > 0; thick-- {
r.renderer.DrawLine(offset, int32(rect.Min.Y), offset, int32(rect.Max.Y))
offset++
}
} else if rect.Dy() == 0 {
offset := int32(rect.Min.Y - .5*thickness)
for thick := int32(thickness); thick > 0; thick-- {
r.renderer.DrawLine(int32(rect.Min.X), offset, int32(rect.Max.X), offset)
offset++
}
} else {
for thick := int32(thickness); thick > 0; thick-- {
r.renderer.DrawRect(SDLRectanglePtr(rect))
rect = rect.Inset(1)
}
}
}
func (r *Renderer) RenderTo(t ui.Texture) {
texture, ok := t.(sdlTexture)
if ok {
err := r.renderer.SetRenderTarget(texture.Native())
if err != nil {
panic(err)
}
} else {
r.RenderToDisplay()
}
}
func (r *Renderer) RenderToDisplay() {
r.renderer.SetRenderTarget(nil)
}
func (r *Renderer) Resize(width, height int) {
r.window.SetSize(int32(width), int32(height))
}
func (r *Renderer) SetDrawColor(c sdl.Color) {
r.renderer.SetDrawColor(c.R, c.G, c.B, c.A)
}
func (r *Renderer) SetDrawColorGo(c color.Color) {
r.SetDrawColor(ColorSDL(c))
}
func (r *Renderer) SetIcon(source ui.ImageSource) {
window := r.window
if window == nil {
return
}
surface, err := r.createSurface(source)
if err != nil {
return
}
defer surface.Free()
window.SetIcon(surface)
}
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) { r.cursor = c }
func (r *Renderer) Size() geom.Point {
w, h, err := r.renderer.GetOutputSize()
if err != nil {
return geom.ZeroPt
}
return geom.Pt(int(w), int(h))
}
func (r *Renderer) SystemCursor(id sdl.SystemCursor) *sdl.Cursor {
if cursor, ok := r.cursors[id]; ok {
return cursor
}
cursor := sdl.CreateSystemCursor(id)
r.cursors[id] = cursor
return cursor
}
func (r *Renderer) Target() ui.Texture {
target := r.renderer.GetRenderTarget()
if target == nil {
return r
}
return &Texture{target}
}
func (r *Renderer) text(font ui.Font, color color.Color, text string) (*Texture, error) {
f, ok := font.(*Font)
if !ok {
return nil, errors.New("font not created with renderer")
}
surface, err := f.RenderUTF8Blended(text, ColorSDL(color))
if err != nil {
return nil, err
}
defer surface.Free()
texture, err := r.renderer.CreateTextureFromSurface(surface)
if err != nil {
return nil, err
}
return &Texture{texture}, nil
}
func (r *Renderer) Text(font ui.Font, p geom.PointF32, color color.Color, text string) {
texture, err := r.text(font, color, text)
if err != nil {
return
}
defer texture.Destroy()
r.DrawTexturePoint(texture, p)
}
func (r *Renderer) TextAlign(font ui.Font, p geom.PointF32, color color.Color, text string, align ui.HorizontalAlignment) {
switch align {
case ui.AlignLeft:
r.Text(font, p, color, text)
case ui.AlignCenter:
width := font.WidthOf(text)
r.Text(font, p.Add2D(-.5*width, 0), color, text)
case ui.AlignRight:
width := font.WidthOf(text)
r.Text(font, p.Add2D(-width, 0), color, text)
}
}
func (r *Renderer) TextTexture(font ui.Font, color color.Color, text string) (ui.Texture, error) {
return r.text(font, color, text)
}
func (r *Renderer) WindowHandle() uintptr {
info, err := r.window.GetWMInfo()
if err != nil {
return 0
}
switch info.Subsystem {
case sdl.SYSWM_COCOA:
return uintptr(info.GetCocoaInfo().Window)
case sdl.SYSWM_DIRECTFB:
return uintptr(info.GetDFBInfo().Window)
case sdl.SYSWM_UIKIT:
return uintptr(info.GetUIKitInfo().Window)
case sdl.SYSWM_WINDOWS:
return uintptr(info.GetWindowsInfo().Window)
case sdl.SYSWM_X11:
return uintptr(info.GetX11Info().Window)
}
return 0
}
// Resources
func (r *Renderer) Resources() ui.Resources { return r.resources }
func (r *Renderer) SetResourceProvider(resources ui.Resources) {
if r.resources != nil {
r.resources.Destroy()
}
if physical, ok := resources.(ui.PhysicalResources); ok {
r.resources = physical
} else {
copy, err := ui.NewCopyResources("sdlui", resources, false)
if err != nil {
return
}
r.resources = copy
}
}
// Texture
func (r *Renderer) Image() image.Image { return nil }
func (r *Renderer) Height() int { return r.Size().Y }
func (r *Renderer) Width() int { return r.Size().X }

View File

@ -1,26 +0,0 @@
package sdlui
import (
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/zntg/ui"
)
func init() {
ui.SetRendererFactory(&rendererFactory{})
}
type rendererFactory struct{}
func (f rendererFactory) New(title string, width, height int, opts ui.NewRendererOptions) (ui.Renderer, error) {
location := sdl.Point{X: sdl.WINDOWPOS_UNDEFINED, Y: sdl.WINDOWPOS_UNDEFINED}
if opts.Location != nil {
location.X = int32(opts.Location.X)
location.Y = int32(opts.Location.Y)
}
return NewRenderer(title, int32(width), int32(height), NewRendererOptions{
Borderless: opts.Borderless,
Location: location,
Resizable: opts.Resizable,
VSync: opts.VSync,
})
}

View File

@ -1,64 +0,0 @@
package sdlui
import (
"image"
"image/color"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/zntg/ui"
)
type sdlTexture interface {
Native() *sdl.Texture
SetColor(color.Color)
Size() (int32, int32, error)
}
type Texture struct {
*sdl.Texture
}
var _ ui.Texture = &Texture{}
func (t *Texture) Height() int {
_, _, _, height, err := t.Texture.Query()
if err != nil {
return -1
}
return int(height)
}
func (t *Texture) Native() *sdl.Texture { return t.Texture }
func (t *Texture) SetColor(c color.Color) {
color := ColorSDL(c)
t.SetColorMod(color.R, color.G, color.B)
}
func (t *Texture) Size() (int32, int32, error) {
_, _, width, height, err := t.Texture.Query()
if err != nil {
return 0, 0, err
}
return width, height, err
}
func (t *Texture) Width() int {
_, _, width, _, err := t.Texture.Query()
if err != nil {
return -1
}
return int(width)
}
var _ ui.ImageSource = &TextureImageSource{}
type TextureImageSource struct {
*Texture
source ui.ImageSource
}
func (s TextureImageSource) CreateImage() (image.Image, error) {
return s.source.CreateImage()
}

View File

@ -1,9 +0,0 @@
package ui
type HorizontalAlignment int
const (
AlignLeft HorizontalAlignment = iota
AlignCenter
AlignRight
)

View File

@ -1,78 +0,0 @@
package ui
import (
"errors"
"opslag.de/schobers/geom"
)
type RenderBufferFn func(ctx Context, size geom.PointF32)
type Buffer struct {
texture Texture
size geom.PointF32
}
var ErrNewBufferSize = errors.New("buffer has been resized")
func (b *Buffer) Update(ctx Context, size geom.PointF32) error {
if b.texture != nil {
if size == b.size {
return nil
}
b.texture.Destroy()
b.texture = nil
b.size = geom.ZeroPtF32
}
texture, err := ctx.Renderer().CreateTextureTarget(size.X, size.Y)
if err != nil {
return err
}
b.texture = texture
b.size = size
return ErrNewBufferSize
}
func (b *Buffer) Render(ctx Context, pos geom.PointF32, fn RenderBufferFn) {
if b.texture == nil {
return
}
b.RenderContent(ctx, fn)
b.RenderToDisplay(ctx, pos)
}
func (b *Buffer) RenderContent(ctx Context, fn RenderBufferFn) {
renderer := ctx.Renderer()
currTarget := renderer.Target()
renderer.RenderTo(b.texture)
fn(ctx, b.size)
renderer.RenderTo(currTarget)
}
func (b *Buffer) RenderToDisplay(ctx Context, pos geom.PointF32) {
if b.texture == nil {
return
}
ctx.Renderer().DrawTexturePoint(b.texture, pos)
}
type BufferControl struct {
ControlBase
buffer Buffer
}
func (c *BufferControl) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
c.ControlBase.Arrange(ctx, bounds, offset, parent)
c.buffer.Update(ctx, bounds.Size())
}
func (c *BufferControl) Render(ctx Context) {
c.RenderFn(ctx, c.RenderBuffer)
}
func (c *BufferControl) RenderFn(ctx Context, render RenderBufferFn) {
c.buffer.Render(ctx, c.bounds.Min, render)
}
func (c *BufferControl) RenderBuffer(ctx Context, size geom.PointF32) {}

View File

@ -1,235 +1,54 @@
package ui package ui
import ( import (
"image/color"
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
"opslag.de/schobers/zntg/allg5"
) )
type Button struct { type Button struct {
ControlBase ControlBase
DisabledColor color.Color
HoverColor color.Color
Icon string // optional: icon to display in front of the text.
IconHeight float32 // overrides the height of the icon (overrides auto-scaling when text is provided).
Text string Text string
Type ButtonType HorizontalAlignment allg5.HorizontalAlignment
clicked ControlClickedEvents
} }
type ButtonType int func NewButton(text string, click MouseClickFn) *Button {
return &Button{ControlBase: ControlBase{OnClick: click}, Text: text}
const (
ButtonTypeContained ButtonType = iota
ButtonTypeIcon
ButtonTypeOutlined
ButtonTypeText
)
func BuildButton(text string, fn func(b *Button)) *Button { return BuildIconButton("", text, fn) }
func BuildIconButton(icon, text string, fn func(b *Button)) *Button {
var b = &Button{Text: text, Icon: icon}
if fn != nil {
fn(b)
}
return b
} }
func (b *Button) desiredSize(ctx Context) geom.PointF32 { func NewButtonAlign(text string, click MouseClickFn, align allg5.HorizontalAlignment) *Button {
var pad = b.ActualTextPadding(ctx) return &Button{ControlBase: ControlBase{OnClick: click}, Text: text, HorizontalAlignment: align}
var font = b.ActualFont(ctx)
var w, h float32 = 0, font.Height()
icon, iconW, iconH := b.icon(ctx)
if len(b.Text) == 0 {
if icon != nil && iconH > 0 {
w = pad.Left + iconW + pad.Right
h = iconH
}
} else {
w += pad.Left + font.WidthOf(b.Text) + pad.Right
if icon != nil && iconH > 0 {
if b.IconHeight == 0 {
iconW = iconW * h / iconH
// iconH = h
}
w += iconW + pad.Right
}
}
if w == 0 {
return geom.ZeroPtF32
}
return geom.PtF32(w, pad.Top+h+pad.Bottom)
} }
func (b *Button) icon(ctx Context) (Texture, float32, float32) { func (b *Button) DesiredSize(ctx Context) geom.PointF32 {
if b.Icon == "" { var fonts = ctx.Fonts()
return nil, 0, 0 var fnt = fonts.Get("default")
} var w = fnt.TextWidth(b.Text)
icon := ctx.Textures().Texture(b.Icon) var fntH = fnt.Height()
iconW, iconH := float32(icon.Width()), float32(icon.Height()) return geom.PtF32(w+fntH, 2*fntH)
if b.IconHeight != 0 {
iconW = b.IconHeight * iconW / iconH
iconH = b.IconHeight
}
return icon, iconW, iconH
}
func (b *Button) ButtonClicked() ControlClickedEventHandler { return &b.clicked }
func (b *Button) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
return b.desiredSize(ctx)
}
func (b *Button) Handle(ctx Context, e Event) bool {
result := b.ControlBase.HandleNotify(ctx, e, b)
if b.over {
if b.Disabled {
return true
}
ctx.Renderer().SetMouseCursor(MouseCursorPointer)
}
return result
}
func (b *Button) Notify(ctx Context, state interface{}) bool {
switch state.(type) {
case ControlClickedArgs:
if !b.Disabled {
if b.clicked.Notify(ctx, state) {
return true
}
}
}
return b.ControlBase.Notify(ctx, state)
}
func (b *Button) disabledColor(p *Palette) color.Color {
if b.DisabledColor != nil {
return b.DisabledColor
}
return p.Disabled
}
func (b *Button) fillColor(p *Palette) color.Color {
if b.Type == ButtonTypeIcon {
return nil
}
if b.Disabled {
if b.Background != nil {
return b.disabledColor(p)
}
switch b.Type {
case ButtonTypeContained:
return b.disabledColor(p)
default:
return nil
}
}
if b.Background != nil {
if b.over && b.HoverColor != nil {
return b.HoverColor
}
return b.Background
}
if b.over {
if b.HoverColor != nil {
return b.HoverColor
}
switch b.Type {
case ButtonTypeContained:
return p.PrimaryLight
default:
return p.PrimaryHighlight
}
}
switch b.Type {
case ButtonTypeContained:
return p.Primary
}
return nil
}
func (b *Button) fontColor(c color.Color) color.Color {
if b.Font.Color == nil {
return c
}
return b.Font.Color
}
func (b *Button) textColor(p *Palette) color.Color {
if b.Disabled {
if b.Background != nil {
return p.TextOnDisabled
}
switch b.Type {
case ButtonTypeContained:
return p.TextOnDisabled
}
return b.disabledColor(p)
}
switch b.Type {
case ButtonTypeContained:
return b.fontColor(p.TextOnPrimary)
case ButtonTypeIcon:
if b.over {
if b.HoverColor != nil {
return b.HoverColor
}
return b.fontColor(p.Primary)
}
return b.fontColor(p.Text)
default:
return b.fontColor(p.Primary)
}
} }
func (b *Button) Render(ctx Context) { func (b *Button) Render(ctx Context) {
var style = ctx.Style() var fonts = ctx.Fonts()
var palette = style.Palette
textColor := b.textColor(palette)
fillColor := b.fillColor(palette)
if fillColor != nil {
ctx.Renderer().FillRectangle(b.bounds, fillColor)
}
size := b.desiredSize(ctx)
bounds := b.bounds
deltaX, deltaY := bounds.Dx()-size.X, bounds.Dy()-size.Y
bounds.Min.X += .5 * deltaX
bounds.Min.Y += .5 * deltaY
pad := b.ActualTextPadding(ctx) var min = b.Bounds.Min
bounds = pad.InsetRect(bounds) var max = b.Bounds.Max
boundsH := bounds.Dy()
pos := bounds.Min var fnt = fonts.Get("default")
icon, iconW, iconH := b.icon(ctx) var fntH = fnt.Height()
var iconOffsetY float32
if icon != nil && iconH > 0 { var back = ctx.Palette().Primary()
if b.Text != "" { if b.IsOver && !b.IsPressed {
if b.IconHeight == 0 { back = ctx.Palette().PrimaryHighlight()
iconH = boundsH
scaled, _ := ctx.Textures().ScaledHeight(icon, iconH) // try to pre-scale scaled
if scaled != nil { // let the renderer scale
icon = scaled
} }
_, iconW = ScaleToHeight(SizeOfTexture(icon).ToF32(), iconH) allg5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, back)
} switch b.HorizontalAlignment {
iconOffsetY = .5 * (boundsH - iconH) case allg5.AlignLeft:
} fnt.Draw(min.X+.5*fntH, min.Y+.5*fntH, ctx.Palette().Lightest(), allg5.AlignLeft, b.Text)
ctx.Renderer().DrawTextureOptions(icon, geom.RectRelF32(pos.X, pos.Y+iconOffsetY, iconW, iconH), DrawOptions{Tint: textColor}) case allg5.AlignCenter:
pos.X += iconW + pad.Right fnt.Draw(.5*(min.X+max.X), min.Y+.5*fntH, ctx.Palette().Lightest(), allg5.AlignCenter, b.Text)
} case allg5.AlignRight:
if len(b.Text) != 0 { fnt.Draw(min.X-.5*fntH, min.Y+.5*fntH, ctx.Palette().Lightest(), allg5.AlignRight, b.Text)
font := b.ActualFont(ctx)
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), textColor, b.Text)
} }
if b.Type == ButtonTypeOutlined { b.ControlBase.Render(ctx)
b.RenderOutlineDefault(ctx, textColor)
}
} }

View File

@ -1,64 +0,0 @@
package ui
type CacheHashFn func(interface{}) string
type CacheHashContextFn func(Context) string
func (c CacheHashContextFn) Fn() CacheHashFn {
return func(state interface{}) string { return c(state.(Context)) }
}
type CacheUpdateFn func(interface{}) interface{}
type CacheUpdateContextFn func(Context) interface{}
func (c CacheUpdateContextFn) Fn() CacheUpdateFn {
return func(state interface{}) interface{} { return c(state.(Context)) }
}
type Cache struct {
value CachedValue
update CacheUpdateFn
hash CacheHashFn
}
func NewCache(update CacheUpdateFn, hash CacheHashFn) *Cache {
return &Cache{update: update, hash: hash}
}
func (c *Cache) Get(state interface{}) interface{} {
return c.value.Get(state, c.update, c.hash)
}
type CacheContext struct {
value CachedValue
update CacheUpdateContextFn
hash CacheHashContextFn
}
func NewCacheContext(update CacheUpdateContextFn, hash CacheHashContextFn) *CacheContext {
return &CacheContext{update: update, hash: hash}
}
func (c *CacheContext) Get(ctx Context) interface{} {
return c.value.GetContext(ctx, c.update, c.hash)
}
type CachedValue struct {
value interface{}
hash string
}
func (c *CachedValue) Get(state interface{}, update CacheUpdateFn, hash CacheHashFn) interface{} {
if hash(state) != c.hash {
c.value = update(state)
}
return c.value
}
func (c *CachedValue) GetContext(ctx Context, update CacheUpdateContextFn, hash CacheHashContextFn) interface{} {
if hash(ctx) != c.hash {
c.value = update(ctx)
}
return c.value
}

View File

@ -1,134 +1,112 @@
package ui package ui
import ( import (
"fmt"
"image/color"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/llgcode/draw2d/draw2dkit"
"opslag.de/schobers/geom" "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 { type Checkbox struct {
ControlBase ControlBase
Value bool
selectedChanged Events
Selected bool
Text string Text string
OnChanged CheckboxValueChangedFn
checked *allg5.Bitmap
unchecked *allg5.Bitmap
} }
func BuildCheckbox(text string, fn func(c *Checkbox)) *Checkbox { func (c *Checkbox) Created(ctx Context, p Container) error {
var c = &Checkbox{Text: text} var err = c.ControlBase.Created(ctx, p)
if fn != nil { if nil != err {
fn(c) return err
} }
return c 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) desiredSize(ctx Context) geom.PointF32 { func (c *Checkbox) Handle(ctx Context, ev allg5.Event) {
pad := c.ActualTextPadding(ctx) var pressed = c.IsPressed
font := c.ActualFont(ctx) c.ControlBase.Handle(ctx, ev)
var w, h float32 = 0, font.Height() switch ev.(type) {
if len(c.Text) != 0 { case *allg5.MouseButtonUpEvent:
w += pad.Left + font.WidthOf(c.Text) if !c.Disabled && pressed && c.IsOver {
c.Value = !c.Value
var onChanged = c.OnChanged
if nil != onChanged {
onChanged(c.Value)
}
}
} }
icon := c.getOrCreateNormalIcon(ctx)
_, iconWidth := ScaleToHeight(SizeOfTexture(icon).ToF32(), h)
w += pad.Left + iconWidth
return geom.PtF32(w+pad.Right, pad.Top+h+pad.Bottom)
} }
func (c *Checkbox) icon(ctx Context) Texture { func (c *Checkbox) DesiredSize(ctx Context) geom.PointF32 {
if c.Selected { var fonts = ctx.Fonts()
return GetOrCreateIcon(ctx, "ui-default-checkbox-selected", c.selectedIcon) var fnt = fonts.Get("default")
} else if c.over && !c.Disabled { var w = fnt.TextWidth(c.Text)
return GetOrCreateIcon(ctx, "ui-default-checkbox-hover", c.hoverIcon) return geom.PtF32(w+checkboxSize, checkboxSize)
}
return c.getOrCreateNormalIcon(ctx)
} }
func (c *Checkbox) getOrCreateNormalIcon(ctx Context) Texture { func (c *Checkbox) box() *allg5.Bitmap {
return GetOrCreateIcon(ctx, "ui-default-checkbox", c.normalIcon) if c.Value {
return c.checked
} }
return c.unchecked
var checkBoxIconBorder = geom.PolF32(
geom.PtF32(48, 80),
geom.PtF32(400, 80),
geom.PtF32(400, 432),
geom.PtF32(48, 432),
)
var checkBoxCheckMark = geom.PointsF32{
geom.PtF32(96, 256),
geom.PtF32(180, 340),
geom.PtF32(340, 150),
} }
func (c *Checkbox) hoverIcon(pt geom.PointF32) bool {
return (pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)) || pt.DistanceToLines(checkBoxCheckMark) < 24
}
func (c *Checkbox) normalIcon(pt geom.PointF32) bool {
return pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)
}
func (c *Checkbox) selectedIcon(pt geom.PointF32) bool {
if pt.DistanceToPolygon(checkBoxIconBorder) < 48 || pt.InPolygon(checkBoxIconBorder) {
return pt.DistanceToLines(checkBoxCheckMark) > 24
}
return false
}
func (c *Checkbox) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 { return c.desiredSize(ctx) }
func (c *Checkbox) Handle(ctx Context, e Event) bool {
result := c.ControlBase.Handle(ctx, e)
if c.over {
if c.Disabled {
return true
}
ctx.Renderer().SetMouseCursor(MouseCursorPointer)
}
if result {
return true
}
switch e := e.(type) {
case *MouseButtonDownEvent:
if e.Button == MouseButtonLeft && c.over {
c.Selected = !c.Selected
return c.selectedChanged.Notify(ctx, c.Selected)
}
}
return false
}
func (c *Checkbox) SelectedChanged() EventHandler { return &c.selectedChanged }
func (c *Checkbox) Render(ctx Context) { func (c *Checkbox) Render(ctx Context) {
c.RenderBackground(ctx) var fonts = ctx.Fonts()
var style = ctx.Style() var min = c.Bounds.Min
var palette = style.Palette var fnt = fonts.Get("default")
fore := c.TextColor(ctx)
bounds := c.bounds
pad := c.ActualTextPadding(ctx) fnt.Draw(min.X+checkboxSize, min.Y-.67*fnt.Ascent()+.5*checkboxSize, ctx.Palette().Darkest(), allg5.AlignLeft, c.Text)
bounds = pad.InsetRect(bounds) if c.Disabled {
boundsH := bounds.Dy() var disabled = ctx.Palette().Disabled()
pos := bounds.Min c.box().DrawOptions(min.X, min.Y, allg5.DrawOptions{Tint: &disabled})
icon := c.icon(ctx) } else {
if icon != nil { c.box().Draw(min.X, min.Y)
iconColor := fore
if c.Selected {
iconColor = c.FontColor(ctx, palette.Primary)
}
scaledIcon, _ := ctx.Textures().ScaledHeight(icon, boundsH) // try to pre-scale icon
if scaledIcon == nil { // let the renderer scale
scaledIcon = icon
}
_, iconWidth := ScaleToHeight(SizeOfTexture(scaledIcon).ToF32(), boundsH)
rect := geom.RectRelF32(pos.X, pos.Y, iconWidth, boundsH)
ctx.Renderer().DrawTextureOptions(scaledIcon, rect, DrawOptions{Tint: iconColor})
pos.X += iconWidth + pad.Right
}
if len(c.Text) != 0 {
font := c.ActualFont(ctx)
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fore, c.Text)
} }
c.ControlBase.Render(ctx)
} }

View File

@ -1,21 +0,0 @@
package ui
type Clipboard interface {
WriteText(t string) error
ReadText() (string, error)
}
var DefaultClipboard Clipboard = &clipboard{}
type clipboard struct {
value string
}
func (c *clipboard) WriteText(t string) error {
c.value = t
return nil
}
func (c *clipboard) ReadText() (string, error) {
return c.value, nil
}

283
ui/colors.go Normal file
View 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
View 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
View 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)
}

View File

@ -1,71 +0,0 @@
package ui
import (
"opslag.de/schobers/geom"
)
type ContainerBase struct {
ControlBase
Children []Control
}
func BuildContainerBase(controls ...Control) ContainerBase {
return ContainerBase{
Children: controls,
}
}
func (c *ContainerBase) AddChild(child ...Control) {
c.Children = append(c.Children, child...)
for _, child := range child {
child.SetSelf(child)
}
}
func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
for _, child := range c.Children {
child.Arrange(ctx, bounds, offset, c)
}
c.ControlBase.Arrange(ctx, bounds, offset, parent)
}
func (c *ContainerBase) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
if len(path) == 0 {
return c.bounds
}
next := path[0]
for _, child := range c.Children {
if child == next {
return child.BoundsUnclipped(ctx, path[1:])
}
}
panic("child not found in path")
}
func (c *ContainerBase) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
var max geom.PointF32
for _, child := range c.Children {
s := child.DesiredSize(ctx, size)
max = geom.MaxPtF32(max, s)
}
return max
}
func (c *ContainerBase) Handle(ctx Context, e Event) bool {
if c.ControlBase.Handle(ctx, e) {
return true
}
for _, child := range c.Children {
if child.Handle(ctx, e) {
return true
}
}
return false
}
func (c *ContainerBase) Render(ctx Context) {
c.RenderBackground(ctx)
for _, child := range c.Children {
child.Render(ctx)
}
}

201
ui/contentscrollbar.go Normal file
View 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)
}

View File

@ -1,111 +1,72 @@
package ui package ui
import ( import "opslag.de/schobers/zntg/allg5"
"opslag.de/schobers/geom"
)
type Context interface { type Context interface {
Animate() Display() *allg5.Display
Fonts() *Fonts Fonts() Fonts
HasQuit() bool Palette() Palette
KeyModifiers() KeyModifier Debug() Debug
MousePosition() geom.PointF32 }
Overlays() *Overlays
Quit() type Debug interface {
Renderer() Renderer IsEnabled() bool
Resources() Resources Rainbow() allg5.Color
ShowTooltip(t string)
Style() *Style
Textures() *Textures
} }
var _ Context = &context{} var _ Context = &context{}
var _ EventTarget = &context{}
type context struct { type context struct {
animate bool disp *allg5.Display
quit chan struct{} fts Fonts
renderer Renderer pal Palette
view Control dbg *debug
modifiers KeyModifier
mouse geom.PointF32
tooltip *Tooltip
overlays *Overlays
fonts *Fonts
textures *Textures
style *Style
} }
func newContext(r Renderer, s *Style, view Control) *context { type debug struct {
ctx := &context{ enbl bool
quit: make(chan struct{}), rainb []allg5.Color
renderer: r, col int
style: s,
view: view,
tooltip: &Tooltip{},
overlays: NewOverlays(view),
fonts: NewFonts(r),
textures: NewTextures(r)}
ctx.overlays.AddOnTop(uiDefaultTooltipOverlay, ctx.tooltip, false)
ctx.overlays.AddOnTop(DefaultDebugOverlay, NewDebugOverlay(view), false)
return ctx
} }
func (c *context) Animate() { c.animate = true } func rainbow() []allg5.Color {
var colors = make([]allg5.Color, len(Colors500))
func (c *context) Destroy() { for i, c := range Colors500 {
c.fonts.Destroy() colors[i] = NewColorAlpha(c, 0x7f)
c.textures.Destroy() }
return colors
} }
func (c *context) Fonts() *Fonts { return c.fonts } func newContext(disp *allg5.Display, f Fonts) *context {
return &context{disp, f, DefaultPalette(), &debug{rainb: rainbow()}}
func (c *context) HasQuit() bool {
select {
case <-c.quit:
return true
default:
return false
}
} }
func (c *context) KeyModifiers() KeyModifier { return c.modifiers } func (c *context) Display() *allg5.Display {
return c.disp
func (c *context) MousePosition() geom.PointF32 { return c.mouse }
func (c *context) Overlays() *Overlays { return c.overlays }
func (c *context) Renderer() Renderer { return c.renderer }
func (c *context) Resources() Resources { return c.renderer.Resources() }
func (c *context) ShowTooltip(t string) {
c.tooltip.Text = t
} }
func (c *context) Style() *Style { return c.style } func (c *context) Fonts() Fonts {
return c.fts
func (c *context) Quit() {
if !c.HasQuit() {
close(c.quit)
}
} }
func (c *context) Textures() *Textures { return c.textures } func (c *context) Palette() Palette {
return c.pal
// Handle implement EventTarget
func (c *context) Handle(e Event) {
switch e := e.(type) {
case *DisplayCloseEvent:
c.Quit()
return
case *MouseMoveEvent:
c.mouse = e.Pos()
case *KeyDownEvent:
c.modifiers = e.Modifiers
case *KeyUpEvent:
c.modifiers = e.Modifiers
} }
c.overlays.Handle(c, e)
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]
} }

View File

@ -1,51 +1,90 @@
package ui package ui
import ( import (
"time"
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
"opslag.de/schobers/zntg/allg5"
) )
type Control interface { type Control interface {
Arrange(Context, geom.RectangleF32, geom.PointF32, Control) Created(Context, Container) error
DesiredSize(Context, geom.PointF32) geom.PointF32 Destroyed(Context)
Handle(Context, Event) bool
Update(Context, time.Duration)
Handle(Context, allg5.Event)
DesiredSize(Context) geom.PointF32
Rect() geom.RectangleF32
SetRect(geom.RectangleF32)
Render(Context) Render(Context)
Bounds() geom.RectangleF32
BoundsUnclipped(Context, ControlPath) geom.RectangleF32
Disable()
Enable()
IsDisabled() bool
IsInBounds(p geom.PointF32) bool
IsOver() bool
Offset() geom.PointF32
ScrollIntoView(Context, ControlPath)
Self() Control
SetSelf(Control)
Parent() Control
} }
type ControlPath []Control type MouseClickFn func(Control)
func (p ControlPath) BoundsUnclipped(ctx Context, c Control) geom.RectangleF32 { var _ Control = &ControlBase{}
if len(p) == 0 {
return c.Bounds() type ControlBase struct {
} Parent Container
// switch next := p[0].(type) { Bounds geom.RectangleF32
// case *ContainerBase: Disabled bool
// return next.BoundsUnclipped(ctx, p[1:]) IsOver bool
// case *StackPanel: IsPressed bool
// return next.BoundsUnclipped(ctx, p[1:]) OnClick MouseClickFn
// } MinSize geom.PointF32
return p[0].BoundsUnclipped(ctx, p[1:]) Background *allg5.Color
} }
func (p ControlPath) Prepend(control Control) ControlPath { func (c *ControlBase) Created(_ Context, p Container) error {
return append(ControlPath{control}, p...) c.Parent = p
return nil
} }
type RootControl interface { func (c *ControlBase) Destroyed(Context) {}
Control
Init(Context) error 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)
} }

View File

@ -1,306 +0,0 @@
package ui
import (
"image/color"
"opslag.de/schobers/geom"
)
type ControlClickedArgs struct {
Position geom.PointF32
Button MouseButton
}
type ControlClickedEventHandler interface {
AddHandler(func(Context, ControlClickedArgs)) uint
}
type ControlClickedEvents struct {
Events
}
func (e *ControlClickedEvents) AddHandler(handler func(Context, ControlClickedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(ControlClickedArgs)
handler(ctx, args)
})
}
type DragEndedArgs struct {
Start geom.PointF32
End geom.PointF32
}
type DragEndedEventHandler interface {
AddHandler(func(Context, DragEndedArgs)) uint
}
type DragEndedEvents struct {
Events
}
func (e *DragEndedEvents) AddHandler(handler func(Context, DragEndedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(DragEndedArgs)
handler(ctx, args)
})
}
type DragMovedArgs struct {
Start geom.PointF32
Current geom.PointF32
}
type DragMovedEventHandler interface {
AddHandler(func(Context, DragMovedArgs)) uint
}
type DragMovedEvents struct {
Events
}
func (e *DragMovedEvents) AddHandler(handler func(Context, DragMovedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(DragMovedArgs)
handler(ctx, args)
})
}
type DragStartedArgs struct {
Start geom.PointF32
}
type DragStartedEventHandler interface {
AddHandler(func(Context, DragStartedArgs)) uint
}
type DragStartedEvents struct {
Events
}
func (e *DragStartedEvents) AddHandler(handler func(Context, DragStartedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(DragStartedArgs)
handler(ctx, args)
})
}
var _ Control = &ControlBase{}
type ControlBase struct {
bounds geom.RectangleF32
offset geom.PointF32
self Control
parent Control
drag Dragable
over bool
pressed bool
clicked ControlClickedEvents
dragEnded DragEndedEvents
dragMoved DragMovedEvents
dragStarted DragStartedEvents
Background color.Color
Font FontStyle
TextAlignment HorizontalAlignment
TextPadding SideLengths
Disabled bool
Tooltip string
}
func (c *ControlBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
c.bounds = bounds
c.offset = offset
c.parent = parent
}
func (c *ControlBase) Bounds() geom.RectangleF32 { return c.bounds }
func (c *ControlBase) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
return path.BoundsUnclipped(ctx, c)
}
func (c *ControlBase) ControlClicked() ControlClickedEventHandler { return &c.clicked }
func (c *ControlBase) DesiredSize(Context, geom.PointF32) geom.PointF32 { return geom.ZeroPtF32 }
func (c *ControlBase) Disable() { c.Disabled = true }
func (c *ControlBase) DragEnded() DragEndedEventHandler { return &c.dragEnded }
func (c *ControlBase) DragMoved() DragMovedEventHandler { return &c.dragMoved }
func (c *ControlBase) DragStarted() DragStartedEventHandler { return &c.dragStarted }
func (c *ControlBase) Enable() { c.Disabled = false }
func (c *ControlBase) Handle(ctx Context, e Event) bool { return c.HandleNotify(ctx, e, c) }
func (c *ControlBase) HandleNotify(ctx Context, e Event, notifier Notifier) bool {
defer func() {
if c.Tooltip != "" && c.over {
ctx.ShowTooltip(c.Tooltip)
}
}()
var over = func(e MouseEvent) bool {
pos := e.Pos()
if !c.IsInBounds(pos) {
return false
}
parent := c.Parent()
for parent != nil {
if !parent.IsInBounds(pos) {
return false
}
parent = parent.Parent()
}
return true
}
switch e := e.(type) {
case *MouseMoveEvent:
c.over = over(e.MouseEvent)
if c.pressed {
if start, ok := c.drag.IsDragging(); ok {
var move = c.ToControlPosition(e.Pos())
c.drag.Move(move)
return notifier.Notify(ctx, DragMovedArgs{Start: start, Current: move})
}
var start = c.ToControlPosition(e.Pos())
c.drag.Start(start)
return notifier.Notify(ctx, DragStartedArgs{Start: start})
}
case *MouseLeaveEvent:
c.over = false
case *MouseButtonDownEvent:
c.over = over(e.MouseEvent)
if c.over && e.Button == MouseButtonLeft {
c.pressed = true
return notifier.Notify(ctx, ControlClickedArgs{Position: e.Pos(), Button: e.Button})
}
case *MouseButtonUpEvent:
if e.Button == MouseButtonLeft {
c.pressed = false
if start, ok := c.drag.IsDragging(); ok {
var end = c.ToControlPosition(e.Pos())
c.drag.Cancel()
return notifier.Notify(ctx, DragEndedArgs{Start: start, End: end})
}
}
}
return false
}
func (c *ControlBase) ActualFont(ctx Context) Font {
name := c.FontName(ctx)
return ctx.Fonts().Font(name)
}
func (c *ControlBase) ActualTextPadding(ctx Context) Sides {
return c.TextPadding.Zero(ctx.Style().Dimensions.TextPadding)
}
func (c *ControlBase) FontColor(ctx Context, color color.Color) color.Color {
if c.Disabled {
return ctx.Style().Palette.TextOnDisabled
}
var text = c.Font.Color
if text == nil {
text = color
}
return text
}
func (c *ControlBase) FontName(ctx Context) string {
var name = c.Font.Name
if len(name) == 0 {
name = ctx.Style().Fonts.Default
}
return name
}
func (c *ControlBase) IsDisabled() bool { return c.Disabled }
func (c *ControlBase) IsInBounds(p geom.PointF32) bool {
bounds := c.bounds
if bounds.Min.X < 0 {
bounds.Min.X = 0
}
if bounds.Min.Y < 0 {
bounds.Min.Y = 0
}
return c.ToControlPosition(p).In(c.bounds)
}
func (c *ControlBase) IsOver() bool { return c.over }
func (c *ControlBase) IsPressed() bool { return c.pressed }
func (c *ControlBase) Notify(ctx Context, state interface{}) bool {
switch state.(type) {
case ControlClickedArgs:
return c.clicked.Notify(ctx, state)
case DragEndedArgs:
return c.dragEnded.Notify(ctx, state)
case DragMovedArgs:
return c.dragMoved.Notify(ctx, state)
case DragStartedArgs:
return c.dragStarted.Notify(ctx, state)
default:
return false
}
}
func (c *ControlBase) Parent() Control { return c.parent }
func (c *ControlBase) Offset() geom.PointF32 { return c.offset }
func (c *ControlBase) OutlineColor(ctx Context) color.Color {
return c.FontColor(ctx, ctx.Style().Palette.Primary)
}
func (c *ControlBase) Render(Context) {}
func (c *ControlBase) RenderBackground(ctx Context) {
if c.Background != nil {
ctx.Renderer().FillRectangle(c.bounds, c.Background)
}
}
func (c *ControlBase) RenderOutline(ctx Context) {
c.RenderOutlineDefault(ctx, nil)
}
func (c *ControlBase) RenderOutlineDefault(ctx Context, color color.Color) {
style := ctx.Style()
width := style.Dimensions.OutlineWidth
if color == nil {
color = c.OutlineColor(ctx)
}
ctx.Renderer().Rectangle(c.bounds.Inset(.5*width), color, width)
}
func (c *ControlBase) TextColor(ctx Context) color.Color {
return c.FontColor(ctx, ctx.Style().Palette.Text)
}
func (c *ControlBase) ScrollIntoView(ctx Context, path ControlPath) {
if c.parent == nil {
return
}
c.parent.ScrollIntoView(ctx, path.Prepend(c))
}
func (c *ControlBase) Self() Control {
if c.self == nil {
return c
}
return c.self
}
func (c *ControlBase) SetSelf(self Control) { c.self = self }
func (c *ControlBase) ToControlPosition(p geom.PointF32) geom.PointF32 { return p.Sub(c.offset) }

View File

@ -1,67 +0,0 @@
package ui
import (
"io"
"os"
"opslag.de/schobers/zntg"
)
var _ PhysicalResources = &CopyResources{}
// CopyResources copies the resource to a temporary directory when fetched. Optionally the resource is fetched as well before opening.
type CopyResources struct {
Source Resources
copy *zntg.Dir
mustCopyBeforeOpen bool
}
func newCopyResources(prefix string, source Resources, mustCopyBeforeOpen bool) (*CopyResources, error) {
copy, err := zntg.NewTempDir(prefix)
if nil != err {
return nil, err
}
return &CopyResources{source, copy, mustCopyBeforeOpen}, nil
}
// NewCopyResources creates a proxy that copies resources first to disk. Copy on OpenResource only happens when mustCopyBeforeOpen is set to true.
func NewCopyResources(prefix string, source Resources, mustCopyBeforeOpen bool) (*CopyResources, error) {
return newCopyResources(prefix, source, false)
}
// FetchResource copies the file from the source to disk and returns the path to it.
func (r *CopyResources) FetchResource(name string) (string, error) {
path := r.copy.FilePath(name)
if !zntg.FileExists(path) {
src, err := r.Source.OpenResource(name)
if err != nil {
return "", err
}
defer src.Close()
err = r.copy.Write(name, src)
if nil != err {
return "", err
}
}
return path, nil
}
// OpenResource opens the (optionally copied) resource.
func (r *CopyResources) OpenResource(name string) (io.ReadCloser, error) {
if r.mustCopyBeforeOpen {
path, err := r.FetchResource(name)
if err != nil {
return nil, err
}
return os.Open(path)
}
return r.Source.OpenResource(name)
}
// Destroy destroy the copy of the resources.
func (r *CopyResources) Destroy() error {
return r.copy.Destroy()
}

View File

@ -1,161 +0,0 @@
package ui
import (
"image/color"
"reflect"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
)
const DefaultDebugOverlay = "ui-default-debug"
func controlChildren(control Control) []Control {
container, ok := control.(*ContainerBase)
if ok {
return container.Children
}
proxy, ok := control.(*Proxy)
if ok {
return []Control{proxy.Content}
}
return controlChildrenReflect(reflect.ValueOf(control))
}
func controlChildrenReflect(control reflect.Value) []Control {
switch control.Kind() {
case reflect.Interface:
return controlChildrenReflect(control.Elem())
case reflect.Ptr:
return controlChildrenReflect(control.Elem())
}
if reflect.TypeOf(ContainerBase{}) == control.Type() {
container := control.Interface().(ContainerBase)
return container.Children
}
if reflect.TypeOf(Proxy{}) == control.Type() {
proxy := control.Interface().(Proxy)
return []Control{proxy.Content}
}
if control.NumField() == 0 {
return nil
}
field := control.Type().Field(0)
if !field.Anonymous {
return nil
}
return controlChildrenReflect(control.Field(0))
}
func controlName(control Control) string {
typ := reflect.TypeOf(control)
return typ.Elem().Name()
}
type debugOverlay struct {
ControlBase
root Control
hoverNodes *controlNode
boundsColor color.Color
textColor color.Color
textShadowColor color.Color
}
func NewDebugOverlay(root Control) *debugOverlay {
return &debugOverlay{
root: root,
boundsColor: zntg.MustHexColor(`#00FF003F`),
textColor: zntg.MustHexColor(`#FFFFFF3F`),
textShadowColor: zntg.MustHexColor(`#0000003F`),
}
}
func (o *debugOverlay) renderControl(ctx Context, control Control) {
renderer := ctx.Renderer()
currentColor := zntg.MustHexColor("#FF0000")
parentColor := zntg.MustHexColor("#0000FF")
var maxY float32
var renderHoverNode func(pos geom.PointF32, node *controlNode)
renderHoverNode = func(pos geom.PointF32, node *controlNode) {
if node == nil {
return
}
nameTexture, err := ctx.Fonts().TextTexture("debug", color.White, node.Name)
if err != nil {
return
}
defer nameTexture.Destroy()
nameTextureWidth := float32(nameTexture.Width())
nameTextureHeight := float32(nameTexture.Height())
renderer.FillRectangle(pos.RectRel2D(nameTextureWidth, nameTextureHeight), color.Black)
renderer.DrawTexturePoint(nameTexture, pos)
childPos := pos.Add2D(nameTextureWidth+ctx.Style().Dimensions.Margin, 0)
if len(node.Children) == 0 {
renderer.Rectangle(node.Parent.Bounds, parentColor, 1)
renderer.Rectangle(node.Bounds, currentColor, 1)
}
for _, child := range node.Children {
if childPos.Y == maxY {
childPos.Y = maxY + nameTextureHeight
}
renderHoverNode(childPos, child)
maxY = childPos.Y
childPos.Y += nameTextureHeight + ctx.Style().Dimensions.Margin
}
}
renderHoverNode(geom.PtF32(4, 4), o.hoverNodes)
children := controlChildren(control)
for _, child := range children {
o.renderControl(ctx, child)
}
}
func createHoverNodes(hover geom.PointF32, control Control) *controlNode {
bounds := control.Bounds()
if !hover.In(bounds) {
return nil
}
node := &controlNode{Name: controlName(control), Bounds: bounds}
for _, child := range controlChildren(control) {
childNode := createHoverNodes(hover, child)
if childNode == nil {
continue
}
childNode.Parent = node
node.Children = append(node.Children, childNode)
}
return node
}
func (o *debugOverlay) Handle(ctx Context, e Event) bool {
switch e := e.(type) {
case *MouseMoveEvent:
o.hoverNodes = createHoverNodes(e.Pos(), o.root)
case *MouseLeaveEvent:
o.hoverNodes = nil
}
return false
}
func (o *debugOverlay) Hidden() {}
func (o *debugOverlay) Render(ctx Context) {
o.renderControl(ctx, o.root)
}
func (o *debugOverlay) Shown() {}
type controlNode struct {
Name string
Bounds geom.RectangleF32
Parent *controlNode
Children []*controlNode
}

View File

@ -1,21 +0,0 @@
package ui
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestControlName(t *testing.T) {
assert.Equal(t, "ControlBase", controlName(&ControlBase{}))
assert.Equal(t, "Label", controlName(&Label{}))
}
func TestControlChildren(t *testing.T) {
assert.Len(t, controlChildren(&ContainerBase{}), 0)
assert.Len(t, controlChildren(&ControlBase{}), 0)
assert.Len(t, controlChildren(&ContainerBase{Children: []Control{nil, nil}}), 2)
assert.Len(t, controlChildren(&StackPanel{ContainerBase: ContainerBase{Children: []Control{nil, nil}}}), 2)
assert.Len(t, controlChildren(&Proxy{Content: &ControlBase{}}), 1)
assert.Len(t, controlChildren(&overflow{Proxy: Proxy{Content: &ControlBase{}}}), 1)
}

5
ui/dimensions.go Normal file
View File

@ -0,0 +1,5 @@
package ui
const leftMargin = 8
const checkboxMargin = 4
const checkboxSize = 24

152
ui/dockpanel.go Normal file
View 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])
}
}

View File

@ -1,44 +0,0 @@
package ui
import "opslag.de/schobers/geom"
// Dragable keeps track of the mouse position during a drag operation.
type Dragable struct {
start *geom.PointF32
current *geom.PointF32
}
// Cancel cancels the drag operation and returns the start and end position.
func (d *Dragable) Cancel() (geom.PointF32, geom.PointF32) {
if d.start == nil {
return geom.ZeroPtF32, geom.ZeroPtF32
}
start, end := *d.start, *d.current
d.start = nil
d.current = nil
return start, end
}
// IsDragging returns if the drag operation is in progress and the start location if so.
func (d *Dragable) IsDragging() (geom.PointF32, bool) {
if d.start != nil {
return *d.start, true
}
return geom.ZeroPtF32, false
}
// Move calculates the delta between the start point and the current position.
func (d *Dragable) Move(p geom.PointF32) (geom.PointF32, bool) {
if d.start == nil {
return geom.ZeroPtF32, false
}
delta := p.Sub(*d.current)
d.current = &p
return delta, true
}
// Start set the start point of the drag operation.
func (d *Dragable) Start(p geom.PointF32) {
d.start = &p
d.current = &p
}

View File

@ -1,46 +0,0 @@
package ui
import "opslag.de/schobers/geom"
type DragDropEventTarget interface {
DragEnter(geom.PointF32, []string)
DragMove(geom.PointF32)
DragLeave()
Drop(geom.PointF32, []string)
}
type DragDropProvider interface {
Register(windowHandle uintptr, target DragDropEventTarget)
}
var DefaultDragDropProvider DragDropProvider = nil
type dragDropEventTarget struct {
renderer Renderer
events []Event
}
func (t *dragDropEventTarget) eventBase() EventBase {
return EventBase{StampInSeconds: t.renderer.Stamp()}
}
func (t *dragDropEventTarget) pushEvent(e Event) {
t.events = append(t.events, e)
t.renderer.Refresh()
}
func (t *dragDropEventTarget) DragEnter(pos geom.PointF32, files []string) {
t.pushEvent(&DisplayDragEnterEvent{EventBase: t.eventBase(), X: pos.X, Y: pos.Y, Files: files})
}
func (t *dragDropEventTarget) DragMove(pos geom.PointF32) {
t.pushEvent(&DisplayDragMoveEvent{EventBase: t.eventBase(), X: pos.X, Y: pos.Y})
}
func (t *dragDropEventTarget) DragLeave() {
t.pushEvent(&DisplayDragLeaveEvent{EventBase: t.eventBase()})
}
func (t *dragDropEventTarget) Drop(pos geom.PointF32, files []string) {
t.pushEvent(&DisplayDropEvent{EventBase: t.eventBase(), X: pos.X, Y: pos.Y, Files: files})
}

38
ui/draw.go Normal file
View 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()
})
}

View File

@ -1,20 +0,0 @@
package ui
import (
"image/color"
"opslag.de/schobers/geom"
)
type DrawOptions struct {
Source *geom.RectangleF32
Tint color.Color
}
func ScaleToHeight(size geom.PointF32, height float32) (*geom.PointF32, float32) {
if size.Y == height {
return nil, size.X
}
factor := height / size.Y
return &geom.PointF32{X: factor, Y: factor}, factor * size.X
}

View File

@ -1,133 +0,0 @@
package ui
import "opslag.de/schobers/geom"
type DisplayCloseEvent struct {
EventBase
}
type DisplayDragEnterEvent struct {
EventBase
X, Y float32
Files []string
}
func (e DisplayDragEnterEvent) Pos() geom.PointF32 {
return geom.PtF32(e.X, e.Y)
}
type DisplayDragLeaveEvent struct {
EventBase
}
type DisplayDragMoveEvent struct {
EventBase
X, Y float32
}
func (e DisplayDragMoveEvent) Pos() geom.PointF32 {
return geom.PtF32(e.X, e.Y)
}
type DisplayDropEvent struct {
EventBase
X, Y float32
Files []string
}
func (e DisplayDropEvent) Pos() geom.PointF32 {
return geom.PtF32(e.X, e.Y)
}
type DisplayMoveEvent struct {
EventBase
Bounds geom.RectangleF32
}
type DisplayResizeEvent struct {
EventBase
Bounds geom.RectangleF32
}
type Event interface {
Stamp() float64
}
type EventBase struct {
StampInSeconds float64
}
func (e *EventBase) Stamp() float64 {
return e.StampInSeconds
}
type KeyModifier int
const (
KeyModifierNone KeyModifier = 0
KeyModifierShift = 1 << iota
KeyModifierControl
KeyModifierAlt
KeyModifierOSCommand
)
type KeyDownEvent struct {
EventBase
Key Key
Modifiers KeyModifier
}
type KeyUpEvent struct {
EventBase
Key Key
Modifiers KeyModifier
}
type MouseButton int
const (
MouseButtonLeft MouseButton = 1
MouseButtonRight MouseButton = 2
MouseButtonMiddle MouseButton = 3
)
type MouseButtonDownEvent struct {
MouseEvent
Button MouseButton
}
type MouseButtonUpEvent struct {
MouseEvent
Button MouseButton
}
type MouseEnterEvent struct {
MouseEvent
}
type MouseEvent struct {
EventBase
X, Y float32
}
func (e *MouseEvent) Pos() geom.PointF32 {
return geom.PtF32(e.X, e.Y)
}
type MouseLeaveEvent struct {
MouseEvent
}
type MouseMoveEvent struct {
MouseEvent
MouseWheel float32
}
type RefreshEvent struct {
EventBase
}
type TextInputEvent struct {
EventBase
Character rune
}

View File

@ -1,37 +0,0 @@
package ui
import "opslag.de/schobers/zntg"
type EventEmptyFn func(Context)
type EventFn func(Context, interface{})
type EventArgs struct {
Context Context
State interface{}
}
type Events struct {
zntg.Events
}
type EventHandler interface {
AddHandler(EventFn) uint
AddHandlerEmpty(EventEmptyFn) uint
RemoveHandler(uint)
}
func (e *Events) Notify(ctx Context, state interface{}) bool {
return e.Events.Notify(EventArgs{Context: ctx, State: state})
}
func (e *Events) AddHandler(handler EventFn) uint {
return e.Events.AddHandler(func(state interface{}) {
args := state.(EventArgs)
handler(args.Context, args.State)
})
}
func (e *Events) AddHandlerEmpty(handler EventEmptyFn) uint {
return e.AddHandler(func(ctx Context, _ interface{}) { handler(ctx) })
}

View File

@ -1,5 +0,0 @@
package ui
type EventTarget interface {
Handle(Event)
}

View File

@ -1,133 +0,0 @@
package main
import (
"image/color"
"log"
_ "opslag.de/schobers/zntg/sdlui" // import the renderer for the UI
// _ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI
"opslag.de/schobers/zntg/ui"
)
type basic struct {
ui.StackPanel
}
func (b *basic) Init(ctx ui.Context) error {
_, err := ctx.Fonts().CreateFontPath("default", "../resources/font/OpenSans-Regular.ttf", 14)
if err != nil {
return err
}
_, err = ctx.Textures().CreateTexturePath("plus", "../resources/images/plus.png", true)
if err != nil {
return err
}
var style = ctx.Style()
var stretch = func(content ui.Control, margin float32) ui.Control {
return ui.BuildSpacing(content, func(s *ui.Spacing) {
s.Width = ui.Infinite()
s.Margin.Left = ui.Fixed(margin)
s.Margin.Top = ui.Fixed(margin)
s.Margin.Right = ui.Fixed(margin)
s.Margin.Bottom = ui.Fixed(margin)
})
}
b.Background = color.White
b.Children = []ui.Control{
&ui.Label{Text: "Hello, world!"},
ui.BuildStackPanel(ui.OrientationHorizontal, func(p *ui.StackPanel) {
p.Children = []ui.Control{
stretch(ui.BuildIconButton("plus", "Contained", func(b *ui.Button) { b.Type = ui.ButtonTypeContained }), 8),
stretch(ui.BuildIconButton("plus", "Icon", func(b *ui.Button) { b.Type = ui.ButtonTypeIcon }), 8),
stretch(ui.BuildIconButton("plus", "Outlined", func(b *ui.Button) { b.Type = ui.ButtonTypeOutlined }), 8),
stretch(ui.BuildIconButton("plus", "Text", func(b *ui.Button) { b.Type = ui.ButtonTypeText }), 8),
}
}),
ui.BuildStackPanel(ui.OrientationHorizontal, func(p *ui.StackPanel) {
p.Children = []ui.Control{
stretch(ui.BuildIconButton("plus", "Contained", func(b *ui.Button) {
b.Type = ui.ButtonTypeContained
b.Disabled = true
}), 8),
stretch(ui.BuildIconButton("plus", "Icon", func(b *ui.Button) {
b.Type = ui.ButtonTypeIcon
b.Disabled = true
}), 8),
stretch(ui.BuildIconButton("plus", "Outlined", func(b *ui.Button) {
b.Type = ui.ButtonTypeOutlined
b.Disabled = true
}), 8),
stretch(ui.BuildIconButton("plus", "Text", func(b *ui.Button) {
b.Type = ui.ButtonTypeText
b.Disabled = true
}), 8),
}
}),
ui.BuildStackPanel(ui.OrientationHorizontal, func(p *ui.StackPanel) {
p.Children = []ui.Control{
&ui.Checkbox{},
ui.BuildCheckbox("Check me!", nil),
}
}),
ui.BuildStackPanel(ui.OrientationHorizontal, func(p *ui.StackPanel) {
p.Children = []ui.Control{
ui.BuildCheckbox("", func(b *ui.Checkbox) { b.Disabled = true }),
ui.BuildCheckbox("You can't check me!", func(b *ui.Checkbox) {
b.Selected = true
b.Disabled = true
}),
}
}),
ui.Stretch(ui.BuildParagraph(
"Content"+
"\n\n"+
"Could be on multiple lines...\n"+
"And if the line is long enough (and without line breaks in the string) it will wrap around to the next line on the screen. You can test this behaviour by resizing the window. Go ahead!", nil)),
ui.Margin(ui.StretchWidth(ui.BuildTextBox(func(b *ui.TextBox) {
b.Text = "Type here..."
})), 8),
ui.Margin(ui.StretchWidth(ui.BuildTextBox(func(b *ui.TextBox) {
b.Text = "You can't type here..."
b.Disabled = true
})), 8),
ui.Margin(ui.BuildButton("Quit", func(b *ui.Button) {
b.ButtonClicked().AddHandler(func(ui.Context, ui.ControlClickedArgs) {
ctx.Quit()
})
b.Tooltip = "Will quit the application"
}), 8),
ui.Margin(ui.BuildButton("Can't quit", func(b *ui.Button) {
b.ButtonClicked().AddHandler(func(ui.Context, ui.ControlClickedArgs) {
ctx.Quit()
})
b.Tooltip = "Will not quit the application"
b.Disabled = true
}), 8),
ui.BuildLabel("Status...", func(l *ui.Label) {
l.Background = style.Palette.PrimaryDark
l.Font.Color = style.Palette.TextOnPrimary
}),
}
return nil
}
func run() error {
var render, err = ui.NewRendererDefault("Basic Example", 800, 600)
if err != nil {
return err
}
defer render.Destroy()
return ui.RunWait(render, ui.DefaultStyle(), &basic{}, true)
}
func main() {
var err = run()
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,110 +0,0 @@
package main
import (
"image/color"
"log"
"strings"
"time"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/addons/drop"
_ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI
"opslag.de/schobers/zntg/ui"
)
type dropFiles struct {
ui.StackPanel
ctx ui.Context
files *ui.Paragraph
ping *ping
}
type ping struct {
ui.OverlayBase
circle ui.Texture
position geom.PointF32
tick time.Time
}
func (p *ping) Render(ctx ui.Context) {
elapsed := time.Since(p.tick)
const animationDuration = 500 * time.Millisecond
if elapsed > animationDuration {
return
}
tint := color.Gray{Y: uint8(elapsed * 255 / animationDuration)}
center := geom.PtF32(float32(p.circle.Width()), float32(p.circle.Height())).Mul(.5)
ctx.Renderer().DrawTexturePointOptions(p.circle, p.position.Sub(center), ui.DrawOptions{Tint: tint})
ctx.Animate()
}
func (d *dropFiles) WindowHandle() uintptr { return d.ctx.Renderer().WindowHandle() }
func (d *dropFiles) FilesDropped(position geom.PointF32, files []string) {
d.files.Text = "Files dropped:\n" + strings.Join(files, "\n")
d.ping.tick = time.Now()
d.ping.position = position
d.ctx.Animate()
}
func newCircle() ui.ImageSource {
const side = 16
center := geom.PtF32(.5*side, .5*side)
return &ui.AlphaPixelImageSource{
ImageAlphaPixelTestFn: func(p geom.PointF32) uint8 {
dist := p.Distance(center)
if dist < 7 {
return 255
} else if dist > 8 {
return 0
}
return uint8((8 - dist) * 255 * 0.5)
},
Size: geom.Pt(side, side),
}
}
func (d *dropFiles) Init(ctx ui.Context) error {
d.ctx = ctx
_, err := ctx.Fonts().CreateFontPath("default", "../resources/font/OpenSans-Regular.ttf", 14)
if err != nil {
return err
}
d.files = ui.BuildParagraph("", nil)
pingCircle, err := ctx.Renderer().CreateTexture(newCircle())
if err != nil {
return err
}
d.ping = &ping{circle: pingCircle}
ctx.Overlays().AddOnTop("ping", d.ping, true)
d.Background = color.White
d.Children = []ui.Control{
&ui.Label{Text: "Drop files on this window!"},
d.files,
}
return nil
}
func run() error {
var render, err = ui.NewRendererDefault("Files Drop Example", 800, 600)
if err != nil {
return err
}
defer render.Destroy()
return ui.RunWait(render, ui.DefaultStyle(), &dropFiles{}, true)
}
func main() {
var err = run()
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,122 +0,0 @@
package main
import (
"image/color"
"log"
"strings"
"time"
"opslag.de/schobers/geom"
_ "opslag.de/schobers/zntg/addons/dragdrop" // import drag & drop functionality
_ "opslag.de/schobers/zntg/sdlui" // import the renderer for the UI
"opslag.de/schobers/zntg/ui"
)
type dropFiles struct {
ui.StackPanel
ctx ui.Context
files *ui.Paragraph
ping *ping
}
type ping struct {
ui.OverlayBase
circle ui.Texture
position geom.PointF32
over bool
tick time.Time
}
func (p *ping) Render(ctx ui.Context) {
elapsed := time.Since(p.tick)
const animationDuration = 500 * time.Millisecond
if elapsed > animationDuration {
return
}
tint := color.Gray{Y: uint8(elapsed * 255 / animationDuration)}
center := geom.PtF32(float32(p.circle.Width()), float32(p.circle.Height())).Mul(.5)
ctx.Renderer().DrawTexturePointOptions(p.circle, p.position.Sub(center), ui.DrawOptions{Tint: tint})
ctx.Animate()
}
func (d *dropFiles) Handle(ctx ui.Context, e ui.Event) bool {
switch e := e.(type) {
case *ui.DisplayDragMoveEvent:
d.ping.tick = time.Now()
d.ping.position = geom.PtF32(e.X, e.Y)
d.ping.over = true
d.ctx.Animate()
return true
case *ui.DisplayDropEvent:
d.files.Text = "Files dropped:\n" + strings.Join(e.Files, "\n")
d.ping.tick = time.Now()
d.ping.position = geom.PtF32(e.X, e.Y)
d.ping.over = false
d.ctx.Animate()
return true
}
return d.StackPanel.Handle(ctx, e)
}
func newCircle() ui.ImageSource {
const side = 16
center := geom.PtF32(.5*side, .5*side)
return &ui.AlphaPixelImageSource{
ImageAlphaPixelTestFn: func(p geom.PointF32) uint8 {
dist := p.Distance(center)
if dist < 7 {
return 255
} else if dist > 8 {
return 0
}
return uint8((8 - dist) * 255 * 0.5)
},
Size: geom.Pt(side, side),
}
}
func (d *dropFiles) Init(ctx ui.Context) error {
d.ctx = ctx
_, err := ctx.Fonts().CreateFontPath("default", "../resources/font/OpenSans-Regular.ttf", 14)
if err != nil {
return err
}
d.files = ui.BuildParagraph("", nil)
pingCircle, err := ctx.Renderer().CreateTexture(newCircle())
if err != nil {
return err
}
d.ping = &ping{circle: pingCircle}
ctx.Overlays().AddOnTop("ping", d.ping, true)
d.Background = color.White
d.Children = []ui.Control{
&ui.Label{Text: "Drop files on this window!"},
d.files,
}
return nil
}
func run() error {
var render, err = ui.NewRendererDefault("Files Drop Example", 800, 600)
if err != nil {
return err
}
defer render.Destroy()
return ui.RunWait(render, ui.DefaultStyle(), &dropFiles{}, true)
}
func main() {
var err = run()
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,34 +0,0 @@
package ui
import "io"
var _ Resources = &fallbackResources{}
type fallbackResources struct {
resources Resources
fallback Resources
}
// NewFallbackResources creates a Resources that first will try to access resources and on failure will try to access the fallback resources. Will take ownership of both resources (Destroy).
func NewFallbackResources(resources, fallback Resources) Resources {
return &fallbackResources{resources, fallback}
}
func (r *fallbackResources) OpenResource(name string) (io.ReadCloser, error) {
if reader, err := r.resources.OpenResource(name); err == nil {
return reader, nil
}
return r.fallback.OpenResource(name)
}
func (r *fallbackResources) Destroy() error {
errResources := r.resources.Destroy()
errFallback := r.fallback.Destroy()
if errResources != nil {
return errResources
}
if errFallback != nil {
return errFallback
}
return nil
}

View File

@ -1,19 +0,0 @@
package ui
import (
"image/color"
"opslag.de/schobers/geom"
)
type Font interface {
Destroy() error
Height() float32
Measure(t string) geom.RectangleF32
WidthOf(t string) float32
}
type FontStyle struct {
Name string
Color color.Color
}

View File

@ -1,79 +1,65 @@
package ui package ui
import ( import (
"fmt" "github.com/spf13/afero"
"image/color" "opslag.de/schobers/fs/vfs"
"opslag.de/schobers/zntg/allg5"
"opslag.de/schobers/geom"
) )
type Fonts struct { type Fonts interface {
render Renderer Register(name, path string, size int) error
fonts map[string]Font Get(name string) *allg5.Font
Destroy()
} }
func NewFonts(render Renderer) *Fonts { type fonts struct {
return &Fonts{render, map[string]Font{}} fts map[string]*allg5.Font
dir vfs.CopyDir
} }
func (f *Fonts) AddFont(name string, font Font) { func newFonts(dir vfs.CopyDir) *fonts {
curr := f.fonts[name] return &fonts{make(map[string]*allg5.Font), dir}
if curr != nil {
curr.Destroy()
}
f.fonts[name] = font
} }
func (f *Fonts) createFont(name string, create func() (Font, error)) (Font, error) { func NewFonts(fs afero.Fs) (Fonts, error) {
font, err := create() var dir, err = vfs.NewCopyDir(fs)
if err != nil { if nil != err {
return nil, err return nil, err
} }
f.AddFont(name, font) return newFonts(dir), nil
return font, nil
} }
func (f *Fonts) CreateFontPath(name, path string, size int) (Font, error) { func (fts *fonts) load(name, path string, size int) error {
return f.createFont(name, func() (Font, error) { f, err := allg5.LoadTTFFont(path, size)
return f.render.CreateFontPath(path, size) if nil != err {
}) return err
}
func (f *Fonts) Destroy() {
for _, font := range f.fonts {
font.Destroy()
}
f.fonts = nil
}
func (f *Fonts) Font(name string) Font {
font, ok := f.fonts[name]
if ok {
return font
} }
fts.fts[name] = f
return nil return nil
} }
func (f *Fonts) Text(fontName string, p geom.PointF32, color color.Color, text string) { func (fts *fonts) loadFromBox(name, path string, size int) error {
font := f.Font(fontName) path, err := fts.dir.Retrieve(path)
if font == nil { if nil != err {
return return err
} }
f.render.Text(font, p, color, text) return fts.load(name, path, size)
} }
func (f *Fonts) TextAlign(fontName string, p geom.PointF32, color color.Color, text string, align HorizontalAlignment) { func (fts *fonts) Register(name, path string, size int) error {
font := f.Font(fontName) if nil != fts.dir {
if font == nil { return fts.loadFromBox(name, path, size)
return
} }
f.render.TextAlign(font, p, color, text, align) return fts.load(name, path, size)
} }
func (f *Fonts) TextTexture(fontName string, color color.Color, text string) (Texture, error) { func (fts *fonts) Get(name string) *allg5.Font {
font := f.Font(fontName) return fts.fts[name]
if font == nil {
return nil, fmt.Errorf("no font with name '%s'", fontName)
} }
return f.render.TextTexture(font, color, text)
func (fts *fonts) Destroy() {
for _, f := range fts.fts {
f.Destroy()
}
fts.dir.Destroy()
} }

View File

@ -1,109 +0,0 @@
package ui
import (
"image"
"image/color"
"opslag.de/schobers/geom"
)
type AlphaPixelImageSource struct {
ImageAlphaPixelTestFn
Size geom.Point
}
func (s *AlphaPixelImageSource) CreateImage() (image.Image, error) {
return DrawImageAlpha(s.Size, s.ImageAlphaPixelTestFn), nil
}
type ImageAlphaPixelTestFn func(geom.PointF32) uint8
func (f ImageAlphaPixelTestFn) CreateImageSource(size geom.Point) ImageSource {
return &AlphaPixelImageSource{f, size}
}
type ImagePixelTestFn func(geom.PointF32) bool
func (f ImagePixelTestFn) CreateImageSource(size geom.Point) ImageSource {
return &PixelImageSource{f, size}
}
func createTexture(ctx Context, source ImageSource) Texture {
texture, err := ctx.Renderer().CreateTexture(source)
if err != nil {
return nil
}
return texture
}
func CreateIcon(ctx Context, test ImagePixelTestFn) Texture {
return createTexture(ctx, test.CreateImageSource(IconSize()))
}
func CreateTexture(ctx Context, size geom.Point, test ImagePixelTestFn) Texture {
return createTexture(ctx, test.CreateImageSource(size))
}
func CreateTextureAlpha(ctx Context, size geom.Point, test ImageAlphaPixelTestFn) Texture {
return createTexture(ctx, test.CreateImageSource(size))
}
func DrawIcon(test ImagePixelTestFn) image.Image {
size := IconSize()
return DrawImage(size, test)
}
func DrawImage(size geom.Point, test ImagePixelTestFn) image.Image {
r := image.Rect(0, 0, size.X, size.Y)
icon := image.NewRGBA(r)
for y := 0; y < size.Y; y++ {
for x := 0; x < size.X; x++ {
pt := geom.PtF32(float32(x), float32(y))
if test(pt) {
icon.Set(x, y, color.White)
}
}
}
return icon
}
func DrawImageAlpha(size geom.Point, test ImageAlphaPixelTestFn) image.Image {
r := image.Rect(0, 0, size.X, size.Y)
icon := image.NewAlpha(r)
for y := 0; y < size.Y; y++ {
for x := 0; x < size.X; x++ {
pt := geom.PtF32(float32(x), float32(y))
if a := test(pt); a > 0 {
icon.Set(x, y, color.Alpha{A: a})
}
}
}
return icon
}
func GetOrCreateIcon(ctx Context, name string, test ImagePixelTestFn) Texture {
texture := ctx.Textures().Texture(name)
if texture != nil {
return texture
}
texture, err := ctx.Textures().CreateTexture(name, test.CreateImageSource(IconSize()))
if err != nil {
return nil
}
return texture
}
func IconSize() geom.Point {
return geom.Pt(448, 512)
}
type PixelImageSource struct {
ImagePixelTestFn
Size geom.Point
}
func (s *PixelImageSource) CreateImage() (image.Image, error) {
return DrawImage(s.Size, s.ImagePixelTestFn), nil
}

View File

@ -1,70 +0,0 @@
package ui
import (
"image"
"github.com/nfnt/resize"
"opslag.de/schobers/zntg"
)
type ImageSource interface {
CreateImage() (image.Image, error)
}
type ImageSourceFile string
var _ ImageSource = ImageSourceFile("")
func (s ImageSourceFile) CreateImage() (image.Image, error) {
return zntg.DecodeImage(string(s))
}
type ImageSourceGo struct {
image.Image
}
var _ ImageSource = ImageSourceGo{}
func (s ImageSourceGo) CreateImage() (image.Image, error) {
return s.Image, nil
}
type ImageSourceResource struct {
Resources Resources
Name string
}
func (s ImageSourceResource) CreateImage() (image.Image, error) {
src, err := s.Resources.OpenResource(s.Name)
if err != nil {
return nil, err
}
defer src.Close()
value, err := zntg.ImageDecoder(src)
if err != nil {
return nil, err
}
return value.(image.Image), nil
}
func ImageSourceFromResources(res Resources, name string) ImageSourceResource {
return ImageSourceResource{Resources: res, Name: name}
}
func Scaled(source ImageSource, width, height int) ImageSource {
return scaledImageSource{ImageSource: source, width: width, height: height}
}
type scaledImageSource struct {
ImageSource
width, height int
}
func (s scaledImageSource) CreateImage() (image.Image, error) {
im, err := s.ImageSource.CreateImage()
if err != nil {
return nil, err
}
return resize.Resize(uint(s.width), uint(s.height), im, resize.Bicubic), nil
}

157
ui/key.go
View File

@ -1,157 +0,0 @@
package ui
type Key int
const (
KeyNone Key = iota
Key0
Key1
Key2
Key3
Key4
Key5
Key6
Key7
Key8
Key9
KeyA
KeyAlt
KeyAltGr
KeyAt
KeyB
KeyBack
KeyBackslash //x2
KeyBackspace
KeyBacktick
KeyButtonA
KeyButtonB
KeyButtonL1
KeyButtonL2
KeyButtonR1
KeyButtonR2
KeyButtonX
KeyButtonY
KeyC
KeyCapsLock
KeyCircumflex
KeyCloseBrace
KeyColon2
KeyComma
KeyCommand
KeyD
KeyDelete
KeyDown
KeyDPadCenter
KeyDPadDown
KeyDPadLeft
KeyDPadRight
KeyDPadUp
KeyE
KeyEnd
KeyEnter
KeyEquals
KeyEscape
KeyF
KeyF1
KeyF2
KeyF3
KeyF4
KeyF5
KeyF6
KeyF7
KeyF8
KeyF9
KeyF10
KeyF11
KeyF12
KeyFullstop
KeyG
KeyH
KeyHome
KeyI
KeyInsert
KeyJ
KeyK
KeyL
KeyLeft
KeyLeftControl
KeyLeftShift
KeyLeftWin
KeyM
KeyMenu
KeyMinus
KeyN
KeyNumLock
KeyO
KeyOpenBrace
KeyP
KeyPad0
KeyPad1
KeyPad2
KeyPad3
KeyPad4
KeyPad5
KeyPad6
KeyPad7
KeyPad8
KeyPad9
KeyPadAsterisk
KeyPadDelete
KeyPadEnter
KeyPadEquals
KeyPadMinus
KeyPadPlus
KeyPadSlash
KeyPageDown
KeyPageUp
KeyPause
KeyPrintScreen
KeyQ
KeyQuote
KeyR
KeyRight
KeyRightControl
KeyRightShift
KeyRightWin
KeyS
KeyScrollLock
KeySearch
KeySelect
KeySemicolon
KeySlash
KeySpace
KeyStart
KeyT
KeyTab
KeyThumbL
KeyThumbR
KeyTilde
KeyU
KeyUp
KeyV
KeyVolumeDown
KeyVolumeUp
KeyW
KeyX
KeyY
KeyZ
)
type KeyState map[Key]bool
func (s KeyState) Modifiers() KeyModifier {
var mods KeyModifier
if s[KeyAlt] || s[KeyAltGr] {
mods |= KeyModifierAlt
}
if s[KeyLeftControl] || s[KeyRightControl] {
mods |= KeyModifierControl
}
if s[KeyLeftShift] || s[KeyRightShift] {
mods |= KeyModifierShift
}
if s[KeyLeftWin] || s[KeyRightWin] || s[KeyCommand] {
mods |= KeyModifierOSCommand
}
return mods
}

View File

@ -1,77 +1,30 @@
package ui package ui
import ( import (
"image/color"
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
) "opslag.de/schobers/zntg/allg5"
type TextOverflow int
const (
TextOverflowClip TextOverflow = iota
TextOverflowEllipsis
) )
type Label struct { type Label struct {
ControlBase ControlBase
Text string Text string
TextOverflow TextOverflow HorizontalAlignment allg5.HorizontalAlignment
DropShadow color.Color
desired CachedValue
} }
func BuildLabel(text string, fn func(*Label)) *Label { func (l *Label) DesiredSize(ctx Context) geom.PointF32 {
var l = &Label{Text: text} var fonts = ctx.Fonts()
if fn != nil { var fnt = fonts.Get("default")
fn(l) var _, _, w, h = fnt.TextDimensions(l.Text)
} return geom.PtF32(w, h)
return l
}
func (l *Label) hashDesiredSize(ctx Context) string {
return l.FontName(ctx) + l.Text
}
func (l *Label) desiredSize(ctx Context) interface{} {
font := l.ActualFont(ctx)
width := font.WidthOf(l.Text)
height := font.Height()
pad := l.ActualTextPadding(ctx)
return geom.PtF32(pad.Left+width+pad.Right, pad.Top+height+pad.Bottom)
}
func (l *Label) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
return l.desired.GetContext(ctx, l.desiredSize, l.hashDesiredSize).(geom.PointF32)
}
func (l *Label) getLabelTopLeft(ctx Context) geom.PointF32 {
pad := l.ActualTextPadding(ctx)
bounds := pad.InsetRect(l.bounds)
switch l.TextAlignment {
case AlignRight:
return geom.PtF32(bounds.Max.X, bounds.Min.Y)
case AlignCenter:
return geom.PtF32(.5*(bounds.Min.X+bounds.Max.X), bounds.Min.Y)
default:
return bounds.Min
}
} }
func (l *Label) Render(ctx Context) { func (l *Label) Render(ctx Context) {
l.RenderBackground(ctx) var fonts = ctx.Fonts()
fontColor := l.TextColor(ctx)
font := l.ActualFont(ctx) var min = l.Bounds.Min
topLeft := l.getLabelTopLeft(ctx)
text := l.Text var fnt = fonts.Get("default")
availableWidth := l.bounds.Dx() fnt.Draw(min.X, min.Y+fnt.Height()-fnt.Ascent(), ctx.Palette().Darkest(), l.HorizontalAlignment, l.Text)
if l.TextOverflow == TextOverflowEllipsis {
text = fitTextEllipsis(font, text, availableWidth) l.ControlBase.Render(ctx)
}
if l.DropShadow != nil {
ctx.Renderer().TextAlign(font, topLeft.Add2D(1, 1), l.DropShadow, text, l.TextAlignment)
}
ctx.Renderer().TextAlign(font, topLeft, fontColor, text, l.TextAlignment)
} }

View File

@ -1,77 +0,0 @@
package ui
import "opslag.de/schobers/geom"
type Length struct {
float32
}
func (l *Length) Value() float32 {
if l == nil {
return 0
}
return l.float32
}
func (l *Length) Zero(value float32) float32 {
if l == nil {
return value
}
return l.Value()
}
func Fixed(l float32) *Length { return &Length{l} }
func Infinite() *Length { return &Length{geom.NaN32()} }
func ZL() *Length { return &Length{0} }
type SideLengths struct {
Left *Length
Top *Length
Right *Length
Bottom *Length
}
func (l SideLengths) InsetRect(r geom.RectangleF32) geom.RectangleF32 {
return Sides{
Left: l.Left.Value(),
Top: l.Top.Value(),
Right: l.Right.Value(),
Bottom: l.Bottom.Value(),
}.InsetRect(r)
}
func (l SideLengths) Zero(value float32) Sides {
return Sides{
Left: l.Left.Zero(value),
Top: l.Top.Zero(value),
Right: l.Right.Zero(value),
Bottom: l.Bottom.Zero(value),
}
}
type Sides struct {
Left float32
Top float32
Right float32
Bottom float32
}
func (s Sides) InsetRect(r geom.RectangleF32) geom.RectangleF32 {
if r.Dx() < (s.Left + s.Right) {
r.Min.X += r.Dx() * s.Left / (s.Left + s.Right)
r.Max.X = r.Min.X
} else {
r.Min.X += s.Left
r.Max.X -= s.Right
}
if r.Dy() < (s.Top + s.Bottom) {
r.Min.Y += r.Dy() * s.Top / (s.Top + s.Bottom)
r.Max.Y = r.Min.Y
} else {
r.Min.Y += s.Top
r.Max.Y -= s.Bottom
}
return r
}

121
ui/loop.go Normal file
View 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
View 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)
}

View File

@ -1,11 +0,0 @@
package ui
type MouseCursor int
const (
MouseCursorNone MouseCursor = iota
MouseCursorDefault
MouseCursorNotAllowed
MouseCursorPointer
MouseCursorText
)

View File

@ -1,5 +0,0 @@
package ui
type Notifier interface {
Notify(Context, interface{}) bool
}

View File

@ -1,7 +1,5 @@
package ui package ui
import "opslag.de/schobers/geom"
type Orientation int type Orientation int
const ( const (
@ -9,68 +7,13 @@ const (
OrientationHorizontal OrientationHorizontal
) )
func (o Orientation) FlipPt(p geom.PointF32) geom.PointF32 {
if o == OrientationHorizontal {
return geom.PtF32(p.Y, p.X)
}
return p
}
func (o Orientation) FlipRect(r geom.RectangleF32) geom.RectangleF32 {
if o == OrientationHorizontal {
return geom.RectF32(r.Min.Y, r.Min.X, r.Max.Y, r.Max.X)
}
return r
}
func (o Orientation) LengthParallel(pt geom.PointF32) float32 {
if o == OrientationVertical {
return pt.Y
}
return pt.X
}
func (o Orientation) LengthPerpendicular(pt geom.PointF32) float32 {
if o == OrientationVertical {
return pt.X
}
return pt.Y
}
func (o Orientation) Pt(parallel, perpendicular float32) geom.PointF32 {
if o == OrientationVertical {
return geom.PtF32(perpendicular, parallel)
}
return geom.PtF32(parallel, perpendicular)
}
func (o Orientation) Rect(min geom.PointF32, parallel, perpendicular float32) geom.RectangleF32 {
if o == OrientationVertical {
return geom.RectF32(min.X, min.Y, min.X+perpendicular, min.Y+parallel)
}
return geom.RectF32(min.X, min.Y, min.X+parallel, min.Y+perpendicular)
}
func (o Orientation) SizeParallel(r geom.RectangleF32) float32 {
if o == OrientationVertical {
return r.Dy()
}
return r.Dx()
}
func (o Orientation) SizePerpendicular(r geom.RectangleF32) float32 {
if o == OrientationVertical {
return r.Dx()
}
return r.Dy()
}
func (o Orientation) String() string { func (o Orientation) String() string {
switch o { switch o {
case OrientationVertical: case OrientationVertical:
return "vertical" return "vertical"
case OrientationHorizontal: case OrientationHorizontal:
return "horizontal" return "horizontal"
default:
return "unknown"
} }
return "undefined"
} }

Some files were not shown because too many files have changed in this diff Show More