Compare commits
157 Commits
Author | SHA1 | Date | |
---|---|---|---|
5c29e37e68 | |||
f56c5dd389 | |||
ddf9476920 | |||
eb46741165 | |||
a6415a1d60 | |||
5e4afbe038 | |||
98a75206bc | |||
12a9f5ee39 | |||
de8ce3e7bc | |||
5dcecb8cc1 | |||
c0586c1d8f | |||
764f2a0dd2 | |||
5a4dcd52b0 | |||
bcd32f8372 | |||
e5bfd1394c | |||
7fa5601307 | |||
b63fc999e1 | |||
6839870055 | |||
3c89748eac | |||
302ae1c338 | |||
4cff23cd37 | |||
72138bb8e3 | |||
7aa88e2dc6 | |||
dbeacc3794 | |||
480e864b53 | |||
9dc301eed8 | |||
102c187566 | |||
11e37af9c2 | |||
5babda0ca9 | |||
b1cdbea90f | |||
7d5168614e | |||
b0a13d1a3c | |||
cc32cf5bc3 | |||
0f03760e66 | |||
de87c5d3aa | |||
67e73a8671 | |||
869f87dd4f | |||
43d49a0dbb | |||
352984d6d9 | |||
cdc999ad42 | |||
7793fe823f | |||
2238f8749a | |||
b434a71f00 | |||
3bab08a0a6 | |||
23115b8a0f | |||
9371a8738e | |||
a2cb2d03ca | |||
39766e9f01 | |||
0fe9a2ce63 | |||
7dde894bf0 | |||
16d4e26cd0 | |||
e7ada7fea0 | |||
ea5e1a4989 | |||
7f2e155edd | |||
32c53eb947 | |||
0f54224cc7 | |||
bcf3093c87 | |||
0c399a8d93 | |||
8560204c39 | |||
22cc3ce444 | |||
c78c4052d0 | |||
f20397c684 | |||
e2472cffef | |||
b78f215c8c | |||
b9534ee255 | |||
75fce53716 | |||
add33c6e7e | |||
ae46d2a1f2 | |||
d673653d3f | |||
8c48c949e9 | |||
9af85d79a6 | |||
7f3d836254 | |||
ff4b04262c | |||
661a11fecd | |||
d742fba7e9 | |||
4e37f4b23e | |||
3a18d3adf9 | |||
6db13c8f46 | |||
3591e22c97 | |||
02ee819a99 | |||
b28b3e1838 | |||
a0660a9650 | |||
5d297c98b8 | |||
c0c5235d5a | |||
5a1e5f6f7f | |||
cf12afe2bb | |||
744c639abd | |||
9b04eeb7a3 | |||
893bf513ad | |||
cdfb863ab0 | |||
f618c55b25 | |||
8c11aec276 | |||
1aad3bf11a | |||
48aaf30182 | |||
2c9007ce9b | |||
5ecfd10754 | |||
280b4842e8 | |||
4ca400d985 | |||
4ae1db7969 | |||
06a38d8e4a | |||
52d22e7a18 | |||
757758d8e9 | |||
432281f08d | |||
4d05127c6d | |||
36d620108c | |||
9e577ab1aa | |||
3adf44a516 | |||
3bd4001cc3 | |||
a6bb05794f | |||
e867925de8 | |||
ff064bbf1f | |||
c709d906d0 | |||
4e6d089efe | |||
eee707c4ec | |||
fe4a2b7b73 | |||
6a71720b71 | |||
7f9f10075f | |||
dbc017507c | |||
3e7e2ab682 | |||
99c76545ef | |||
8d78083583 | |||
6bcab622ae | |||
d072e202ab | |||
15f02d4d0e | |||
70d9e23b0c | |||
8f17c02634 | |||
28af8aba06 | |||
3390d46f34 | |||
3acb3f09af | |||
f550220120 | |||
fcf31b381b | |||
eb0e8322ef | |||
04779d6588 | |||
67658d3bee | |||
71cdc234ee | |||
fa7796a4ae | |||
552de0c748 | |||
2ffb579340 | |||
c024416702 | |||
a6718e335d | |||
4d518849e5 | |||
ff51378aff | |||
a3aa398909 | |||
3ea0d76efb | |||
d14a9bd0e7 | |||
f31cd28771 | |||
c83a6d5aad | |||
c973305b6d | |||
1d6587adbd | |||
0387e313de | |||
c08474a01c | |||
ff2ece7d06 | |||
19daffd110 | |||
89a3d49992 | |||
fd9be87470 | |||
5a3818976a | |||
8bdf7d0b76 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,3 +1,9 @@
|
|||||||
|
# Visual Studio Code
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# Go
|
||||||
debug
|
debug
|
||||||
debug.test
|
debug.test
|
||||||
|
|
||||||
|
# Project
|
||||||
|
ui/examples/99_playground
|
||||||
|
45
action.go
Normal file
45
action.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
}
|
23
addons/aferores/afero.go
Normal file
23
addons/aferores/afero.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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) }
|
21
addons/clipboard/clipboard.go
Normal file
21
addons/clipboard/clipboard.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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{}
|
||||||
|
}
|
176
addons/dragdrop/dragdrop_windows.c
Normal file
176
addons/dragdrop/dragdrop_windows.c
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
#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;
|
||||||
|
}
|
96
addons/dragdrop/dragdrop_windows.go
Normal file
96
addons/dragdrop/dragdrop_windows.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// +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{}
|
||||||
|
}
|
26
addons/dragdrop/vector.h
Normal file
26
addons/dragdrop/vector.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#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__
|
7
addons/drop/drop.go
Normal file
7
addons/drop/drop.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package drop
|
||||||
|
|
||||||
|
func Register(dropper Dropper) error {
|
||||||
|
return nil
|
||||||
|
}
|
63
addons/drop/drop_windows.c
Normal file
63
addons/drop/drop_windows.c
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
65
addons/drop/drop_windows.go
Normal file
65
addons/drop/drop_windows.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// +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{}
|
||||||
|
}
|
24
addons/embedres/embedres.go
Normal file
24
addons/embedres/embedres.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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)
|
||||||
|
}
|
23
addons/riceres/rice.go
Normal file
23
addons/riceres/rice.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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) }
|
@ -1,224 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
C.al_destroy_bitmap(b.bitmap)
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package allegro5
|
|
||||||
|
|
||||||
// #cgo pkg-config: allegro-5 allegro_font-5 allegro_image-5 allegro_primitives-5 allegro_ttf-5
|
|
||||||
import "C"
|
|
@ -1,6 +0,0 @@
|
|||||||
// +build windows,!static
|
|
||||||
|
|
||||||
package allegro5
|
|
||||||
|
|
||||||
// #cgo LDFLAGS: -lallegro -lallegro_font -lallegro_image -lallegro_primitives -lallegro_ttf
|
|
||||||
import "C"
|
|
@ -1,6 +0,0 @@
|
|||||||
// +build windows,static
|
|
||||||
|
|
||||||
package allegro5
|
|
||||||
|
|
||||||
// #cgo LDFLAGS: -lallegro_monolith-static -ljpeg -ldumb -lFLAC -lfreetype -lvorbisfile -lvorbis -logg -lphysfs -lpng16 -lzlib -luuid -lkernel32 -lwinmm -lpsapi -lopengl32 -lglu32 -luser32 -lcomdlg32 -lgdi32 -lshell32 -lole32 -ladvapi32 -lws2_32 -lshlwapi -lstdc++
|
|
||||||
import "C"
|
|
@ -1,16 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
type Color struct {
|
|
||||||
color C.ALLEGRO_COLOR
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewColor(r, g, b byte) Color {
|
|
||||||
return Color{C.al_map_rgb(C.uchar(r), C.uchar(g), C.uchar(b))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewColorAlpha(r, g, b, a byte) Color {
|
|
||||||
return Color{C.al_map_rgba(C.uchar(r), C.uchar(g), C.uchar(b), C.uchar(a))}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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) 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)
|
|
||||||
}
|
|
@ -1,195 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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)
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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
|
|
||||||
}
|
|
||||||
|
|
||||||
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}, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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))
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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())
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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)
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package allegro5
|
|
||||||
|
|
||||||
// #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))
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #include <allegro5/allegro.h>
|
|
||||||
// #include <allegro5/allegro_primitives.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
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 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))
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package allegro5
|
|
||||||
|
|
||||||
// #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
|
|
||||||
}
|
|
49
allg5ui/font.go
Normal file
49
allg5ui/font.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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)
|
||||||
|
}
|
276
allg5ui/key.go
Normal file
276
allg5ui/key.go
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
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
|
||||||
|
}
|
458
allg5ui/renderer.go
Normal file
458
allg5ui/renderer.go
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
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)
|
||||||
|
}
|
5
allg5ui/renderer_default.go
Normal file
5
allg5ui/renderer_default.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package allg5ui
|
||||||
|
|
||||||
|
func (r *Renderer) WindowHandle() uintptr { return 0 }
|
7
allg5ui/renderer_windows.go
Normal file
7
allg5ui/renderer_windows.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package allg5ui
|
||||||
|
|
||||||
|
func (r *Renderer) WindowHandle() uintptr {
|
||||||
|
return uintptr(r.disp.WindowHandle())
|
||||||
|
}
|
28
allg5ui/rendererfactory.go
Normal file
28
allg5ui/rendererfactory.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
36
allg5ui/texture.go
Normal file
36
allg5ui/texture.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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()
|
||||||
|
}
|
81
animation.go
Normal file
81
animation.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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
|
||||||
|
}
|
74
color.go
Normal file
74
color.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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}
|
||||||
|
}
|
33
color_test.go
Normal file
33
color_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
events.go
Normal file
46
events.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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
Normal file
22
go.mod
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
Normal file
468
go.sum
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
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
Normal file
187
io.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
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
|
||||||
|
}
|
71
play/fps.go
Normal file
71
play/fps.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
163
play/isometricprojection.go
Normal file
163
play/isometricprojection.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
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)
|
||||||
|
}
|
36
play/isometricprojection_test.go
Normal file
36
play/isometricprojection_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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)))
|
||||||
|
}
|
11
sdlui/color.go
Normal file
11
sdlui/color.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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))
|
||||||
|
}
|
526
sdlui/events.go
Normal file
526
sdlui/events.go
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
13
sdlui/events_test.go
Normal file
13
sdlui/events_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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))
|
||||||
|
}
|
29
sdlui/font.go
Normal file
29
sdlui/font.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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)
|
||||||
|
}
|
26
sdlui/image.go
Normal file
26
sdlui/image.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
}
|
38
sdlui/rectangle.go
Normal file
38
sdlui/rectangle.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
}
|
565
sdlui/renderer.go
Normal file
565
sdlui/renderer.go
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
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 }
|
26
sdlui/rendererfactory.go
Normal file
26
sdlui/rendererfactory.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
64
sdlui/texture.go
Normal file
64
sdlui/texture.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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()
|
||||||
|
}
|
9
ui/alignment.go
Normal file
9
ui/alignment.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type HorizontalAlignment int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlignLeft HorizontalAlignment = iota
|
||||||
|
AlignCenter
|
||||||
|
AlignRight
|
||||||
|
)
|
78
ui/buffer.go
Normal file
78
ui/buffer.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
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) {}
|
235
ui/button.go
Normal file
235
ui/button.go
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Button struct {
|
||||||
|
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
|
||||||
|
Type ButtonType
|
||||||
|
|
||||||
|
clicked ControlClickedEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
type ButtonType int
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var pad = b.ActualTextPadding(ctx)
|
||||||
|
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) {
|
||||||
|
if b.Icon == "" {
|
||||||
|
return nil, 0, 0
|
||||||
|
}
|
||||||
|
icon := ctx.Textures().Texture(b.Icon)
|
||||||
|
iconW, iconH := float32(icon.Width()), float32(icon.Height())
|
||||||
|
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) {
|
||||||
|
var style = ctx.Style()
|
||||||
|
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)
|
||||||
|
bounds = pad.InsetRect(bounds)
|
||||||
|
boundsH := bounds.Dy()
|
||||||
|
pos := bounds.Min
|
||||||
|
icon, iconW, iconH := b.icon(ctx)
|
||||||
|
var iconOffsetY float32
|
||||||
|
if icon != nil && iconH > 0 {
|
||||||
|
if b.Text != "" {
|
||||||
|
if b.IconHeight == 0 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
iconOffsetY = .5 * (boundsH - iconH)
|
||||||
|
}
|
||||||
|
ctx.Renderer().DrawTextureOptions(icon, geom.RectRelF32(pos.X, pos.Y+iconOffsetY, iconW, iconH), DrawOptions{Tint: textColor})
|
||||||
|
pos.X += iconW + pad.Right
|
||||||
|
}
|
||||||
|
if len(b.Text) != 0 {
|
||||||
|
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.RenderOutlineDefault(ctx, textColor)
|
||||||
|
}
|
||||||
|
}
|
64
ui/cache.go
Normal file
64
ui/cache.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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
|
||||||
|
}
|
134
ui/checkbox.go
Normal file
134
ui/checkbox.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Checkbox struct {
|
||||||
|
ControlBase
|
||||||
|
|
||||||
|
selectedChanged Events
|
||||||
|
|
||||||
|
Selected bool
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildCheckbox(text string, fn func(c *Checkbox)) *Checkbox {
|
||||||
|
var c = &Checkbox{Text: text}
|
||||||
|
if fn != nil {
|
||||||
|
fn(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 {
|
||||||
|
pad := c.ActualTextPadding(ctx)
|
||||||
|
font := c.ActualFont(ctx)
|
||||||
|
var w, h float32 = 0, font.Height()
|
||||||
|
if len(c.Text) != 0 {
|
||||||
|
w += pad.Left + font.WidthOf(c.Text)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
if c.Selected {
|
||||||
|
return GetOrCreateIcon(ctx, "ui-default-checkbox-selected", c.selectedIcon)
|
||||||
|
} else if c.over && !c.Disabled {
|
||||||
|
return GetOrCreateIcon(ctx, "ui-default-checkbox-hover", c.hoverIcon)
|
||||||
|
}
|
||||||
|
return c.getOrCreateNormalIcon(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkbox) getOrCreateNormalIcon(ctx Context) Texture {
|
||||||
|
return GetOrCreateIcon(ctx, "ui-default-checkbox", c.normalIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
c.RenderBackground(ctx)
|
||||||
|
|
||||||
|
var style = ctx.Style()
|
||||||
|
var palette = style.Palette
|
||||||
|
fore := c.TextColor(ctx)
|
||||||
|
bounds := c.bounds
|
||||||
|
|
||||||
|
pad := c.ActualTextPadding(ctx)
|
||||||
|
bounds = pad.InsetRect(bounds)
|
||||||
|
boundsH := bounds.Dy()
|
||||||
|
pos := bounds.Min
|
||||||
|
icon := c.icon(ctx)
|
||||||
|
if icon != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
21
ui/clipboard.go
Normal file
21
ui/clipboard.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
}
|
71
ui/containerbase.go
Normal file
71
ui/containerbase.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
111
ui/context.go
Normal file
111
ui/context.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context interface {
|
||||||
|
Animate()
|
||||||
|
Fonts() *Fonts
|
||||||
|
HasQuit() bool
|
||||||
|
KeyModifiers() KeyModifier
|
||||||
|
MousePosition() geom.PointF32
|
||||||
|
Overlays() *Overlays
|
||||||
|
Quit()
|
||||||
|
Renderer() Renderer
|
||||||
|
Resources() Resources
|
||||||
|
ShowTooltip(t string)
|
||||||
|
Style() *Style
|
||||||
|
Textures() *Textures
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Context = &context{}
|
||||||
|
var _ EventTarget = &context{}
|
||||||
|
|
||||||
|
type context struct {
|
||||||
|
animate bool
|
||||||
|
quit chan struct{}
|
||||||
|
renderer Renderer
|
||||||
|
view Control
|
||||||
|
modifiers KeyModifier
|
||||||
|
mouse geom.PointF32
|
||||||
|
tooltip *Tooltip
|
||||||
|
overlays *Overlays
|
||||||
|
fonts *Fonts
|
||||||
|
textures *Textures
|
||||||
|
style *Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContext(r Renderer, s *Style, view Control) *context {
|
||||||
|
ctx := &context{
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
renderer: r,
|
||||||
|
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 (c *context) Destroy() {
|
||||||
|
c.fonts.Destroy()
|
||||||
|
c.textures.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Fonts() *Fonts { return c.fonts }
|
||||||
|
|
||||||
|
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) 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) Quit() {
|
||||||
|
if !c.HasQuit() {
|
||||||
|
close(c.quit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) Textures() *Textures { return c.textures }
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
51
ui/control.go
Normal file
51
ui/control.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Control interface {
|
||||||
|
Arrange(Context, geom.RectangleF32, geom.PointF32, Control)
|
||||||
|
DesiredSize(Context, geom.PointF32) geom.PointF32
|
||||||
|
Handle(Context, Event) bool
|
||||||
|
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
|
||||||
|
|
||||||
|
func (p ControlPath) BoundsUnclipped(ctx Context, c Control) geom.RectangleF32 {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return c.Bounds()
|
||||||
|
}
|
||||||
|
// switch next := p[0].(type) {
|
||||||
|
// case *ContainerBase:
|
||||||
|
// return next.BoundsUnclipped(ctx, p[1:])
|
||||||
|
// case *StackPanel:
|
||||||
|
// return next.BoundsUnclipped(ctx, p[1:])
|
||||||
|
// }
|
||||||
|
return p[0].BoundsUnclipped(ctx, p[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ControlPath) Prepend(control Control) ControlPath {
|
||||||
|
return append(ControlPath{control}, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RootControl interface {
|
||||||
|
Control
|
||||||
|
|
||||||
|
Init(Context) error
|
||||||
|
}
|
306
ui/controlbase.go
Normal file
306
ui/controlbase.go
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
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) }
|
67
ui/copyresources.go
Normal file
67
ui/copyresources.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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()
|
||||||
|
}
|
161
ui/debug.go
Normal file
161
ui/debug.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
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
|
||||||
|
}
|
21
ui/debug_test.go
Normal file
21
ui/debug_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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)
|
||||||
|
}
|
44
ui/dragable.go
Normal file
44
ui/dragable.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
|
}
|
46
ui/dragdrop.go
Normal file
46
ui/dragdrop.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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})
|
||||||
|
}
|
20
ui/drawoptions.go
Normal file
20
ui/drawoptions.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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
|
||||||
|
}
|
133
ui/event.go
Normal file
133
ui/event.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
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
|
||||||
|
}
|
37
ui/events.go
Normal file
37
ui/events.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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) })
|
||||||
|
}
|
5
ui/eventtarget.go
Normal file
5
ui/eventtarget.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type EventTarget interface {
|
||||||
|
Handle(Event)
|
||||||
|
}
|
133
ui/examples/01_basic/basic.go
Normal file
133
ui/examples/01_basic/basic.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
110
ui/examples/02_drop/drop.go
Normal file
110
ui/examples/02_drop/drop.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
122
ui/examples/03_dragdrop/dragdrop.go
Normal file
122
ui/examples/03_dragdrop/dragdrop.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
202
ui/examples/resources/font/LICENSE.txt
Normal file
202
ui/examples/resources/font/LICENSE.txt
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
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.
|
BIN
ui/examples/resources/font/OpenSans-Regular.ttf
Normal file
BIN
ui/examples/resources/font/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
ui/examples/resources/images/plus.png
Normal file
BIN
ui/examples/resources/images/plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
34
ui/fallbackresources.go
Normal file
34
ui/fallbackresources.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
|
}
|
19
ui/font.go
Normal file
19
ui/font.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
}
|
79
ui/fonts.go
Normal file
79
ui/fonts.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fonts struct {
|
||||||
|
render Renderer
|
||||||
|
fonts map[string]Font
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFonts(render Renderer) *Fonts {
|
||||||
|
return &Fonts{render, map[string]Font{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fonts) AddFont(name string, font Font) {
|
||||||
|
curr := f.fonts[name]
|
||||||
|
if curr != nil {
|
||||||
|
curr.Destroy()
|
||||||
|
}
|
||||||
|
f.fonts[name] = font
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fonts) createFont(name string, create func() (Font, error)) (Font, error) {
|
||||||
|
font, err := create()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.AddFont(name, font)
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fonts) CreateFontPath(name, path string, size int) (Font, error) {
|
||||||
|
return f.createFont(name, func() (Font, error) {
|
||||||
|
return f.render.CreateFontPath(path, size)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fonts) Text(fontName string, p geom.PointF32, color color.Color, text string) {
|
||||||
|
font := f.Font(fontName)
|
||||||
|
if font == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.render.Text(font, p, color, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fonts) TextAlign(fontName string, p geom.PointF32, color color.Color, text string, align HorizontalAlignment) {
|
||||||
|
font := f.Font(fontName)
|
||||||
|
if font == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.render.TextAlign(font, p, color, text, align)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fonts) TextTexture(fontName string, color color.Color, text string) (Texture, error) {
|
||||||
|
font := f.Font(fontName)
|
||||||
|
if font == nil {
|
||||||
|
return nil, fmt.Errorf("no font with name '%s'", fontName)
|
||||||
|
}
|
||||||
|
return f.render.TextTexture(font, color, text)
|
||||||
|
}
|
109
ui/icon.go
Normal file
109
ui/icon.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
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
|
||||||
|
}
|
70
ui/imagesource.go
Normal file
70
ui/imagesource.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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
Normal file
157
ui/key.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
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
|
||||||
|
}
|
77
ui/label.go
Normal file
77
ui/label.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TextOverflow int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TextOverflowClip TextOverflow = iota
|
||||||
|
TextOverflowEllipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ControlBase
|
||||||
|
|
||||||
|
Text string
|
||||||
|
TextOverflow TextOverflow
|
||||||
|
DropShadow color.Color
|
||||||
|
|
||||||
|
desired CachedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildLabel(text string, fn func(*Label)) *Label {
|
||||||
|
var l = &Label{Text: text}
|
||||||
|
if fn != nil {
|
||||||
|
fn(l)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
l.RenderBackground(ctx)
|
||||||
|
fontColor := l.TextColor(ctx)
|
||||||
|
font := l.ActualFont(ctx)
|
||||||
|
topLeft := l.getLabelTopLeft(ctx)
|
||||||
|
text := l.Text
|
||||||
|
availableWidth := l.bounds.Dx()
|
||||||
|
if l.TextOverflow == TextOverflowEllipsis {
|
||||||
|
text = fitTextEllipsis(font, text, availableWidth)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
77
ui/length.go
Normal file
77
ui/length.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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
|
||||||
|
}
|
11
ui/mouse.go
Normal file
11
ui/mouse.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type MouseCursor int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MouseCursorNone MouseCursor = iota
|
||||||
|
MouseCursorDefault
|
||||||
|
MouseCursorNotAllowed
|
||||||
|
MouseCursorPointer
|
||||||
|
MouseCursorText
|
||||||
|
)
|
5
ui/notifier.go
Normal file
5
ui/notifier.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type Notifier interface {
|
||||||
|
Notify(Context, interface{}) bool
|
||||||
|
}
|
76
ui/orientation.go
Normal file
76
ui/orientation.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type Orientation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OrientationVertical Orientation = iota
|
||||||
|
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 {
|
||||||
|
switch o {
|
||||||
|
case OrientationVertical:
|
||||||
|
return "vertical"
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return "horizontal"
|
||||||
|
}
|
||||||
|
return "undefined"
|
||||||
|
}
|
34
ui/osresources.go
Normal file
34
ui/osresources.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ PhysicalResources = &OSResources{}
|
||||||
|
|
||||||
|
// DefaultResources returns the default Resources implementation (OSResources).
|
||||||
|
func DefaultResources() PhysicalResources {
|
||||||
|
return &OSResources{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSResources is Resources implementation that uses the default file system directly.
|
||||||
|
type OSResources struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchResource checks if file is available and returns the specified path.
|
||||||
|
func (r *OSResources) FetchResource(name string) (string, error) {
|
||||||
|
_, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenResource opens the specified file on disk.
|
||||||
|
func (r *OSResources) OpenResource(name string) (io.ReadCloser, error) {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy does nothing.
|
||||||
|
func (r *OSResources) Destroy() error { return nil }
|
221
ui/overflow.go
Normal file
221
ui/overflow.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type overflow struct {
|
||||||
|
Proxy
|
||||||
|
|
||||||
|
Background color.Color
|
||||||
|
|
||||||
|
ClipHorizontal bool
|
||||||
|
ClipVertical bool
|
||||||
|
|
||||||
|
barWidth float32
|
||||||
|
desired geom.PointF32
|
||||||
|
bounds geom.RectangleF32
|
||||||
|
offset geom.PointF32
|
||||||
|
parent Control
|
||||||
|
content Buffer
|
||||||
|
proxied Control
|
||||||
|
|
||||||
|
hor *Scrollbar
|
||||||
|
ver *Scrollbar
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScrollControl interface {
|
||||||
|
Control
|
||||||
|
|
||||||
|
SetBackgroundColor(color.Color)
|
||||||
|
SetClipHorizontal(bool)
|
||||||
|
SetClipVertical(bool)
|
||||||
|
SetScrollbarColor(bar color.Color, hover color.Color)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildOverflow(content Control, build func(c ScrollControl)) ScrollControl {
|
||||||
|
o := &overflow{Proxy: Proxy{Content: content}}
|
||||||
|
o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {})
|
||||||
|
o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {})
|
||||||
|
if build != nil {
|
||||||
|
build(o)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func Overflow(content Control) ScrollControl {
|
||||||
|
return OverflowBackground(content, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverflowBackground(content Control, back color.Color) ScrollControl {
|
||||||
|
return BuildOverflow(content, func(c ScrollControl) {
|
||||||
|
c.SetBackgroundColor(back)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) shouldScroll(bounds geom.RectangleF32) (hor bool, ver bool) {
|
||||||
|
var scroll = func(need, actual float32) bool {
|
||||||
|
return !geom.IsNaN32(need) && need > actual
|
||||||
|
}
|
||||||
|
var size = o.desired
|
||||||
|
hor = scroll(size.X, bounds.Dx())
|
||||||
|
ver = scroll(size.Y, bounds.Dy())
|
||||||
|
if ver && !hor {
|
||||||
|
hor = scroll(size.X+o.barWidth, bounds.Dx())
|
||||||
|
}
|
||||||
|
if hor && !ver {
|
||||||
|
ver = scroll(size.Y+o.barWidth, bounds.Dy())
|
||||||
|
}
|
||||||
|
hor = hor && !o.ClipHorizontal
|
||||||
|
ver = ver && !o.ClipVertical
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) doOnVisibleBars(fn func(bar *Scrollbar)) {
|
||||||
|
hor, ver := o.shouldScroll(o.bounds)
|
||||||
|
if hor {
|
||||||
|
fn(o.hor)
|
||||||
|
}
|
||||||
|
if ver {
|
||||||
|
fn(o.ver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
|
||||||
|
o.barWidth = ctx.Style().Dimensions.ScrollbarWidth
|
||||||
|
o.desired = o.Content.DesiredSize(ctx, bounds.Size())
|
||||||
|
o.bounds = bounds
|
||||||
|
o.offset = offset
|
||||||
|
o.parent = parent
|
||||||
|
|
||||||
|
var hor, ver = o.shouldScroll(bounds)
|
||||||
|
var contentX, contentY float32 = 0, 0
|
||||||
|
var contentW, contentH = bounds.Dx(), bounds.Dy()
|
||||||
|
if hor {
|
||||||
|
contentX -= o.hor.ContentOffset
|
||||||
|
contentH = geom.Max32(0, contentH-o.barWidth)
|
||||||
|
}
|
||||||
|
if ver {
|
||||||
|
contentY -= o.ver.ContentOffset
|
||||||
|
contentW = geom.Max32(0, contentW-o.barWidth)
|
||||||
|
}
|
||||||
|
o.Content.Arrange(ctx, geom.RectF32(contentX, contentY, contentW, contentH), offset.Add(bounds.Min), o)
|
||||||
|
if hor {
|
||||||
|
o.hor.ContentLength = o.desired.X
|
||||||
|
o.hor.Arrange(ctx, geom.RectF32(bounds.Min.X, bounds.Min.Y+contentH, bounds.Min.X+contentW, bounds.Max.Y), offset, o)
|
||||||
|
}
|
||||||
|
if ver {
|
||||||
|
o.ver.ContentLength = o.desired.Y
|
||||||
|
o.ver.Arrange(ctx, geom.RectF32(bounds.Min.X+contentW, bounds.Min.Y, bounds.Max.X, bounds.Min.Y+contentH), offset, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) Bounds() geom.RectangleF32 { return o.bounds }
|
||||||
|
|
||||||
|
func (o *overflow) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
|
||||||
|
return path.BoundsUnclipped(ctx, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) DesiredSize(Context, geom.PointF32) geom.PointF32 {
|
||||||
|
return geom.PtF32(geom.NaN32(), geom.NaN32())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) Handle(ctx Context, e Event) bool {
|
||||||
|
if o.Content != o.proxied {
|
||||||
|
o.hor.ContentOffset = 0
|
||||||
|
o.ver.ContentOffset = 0
|
||||||
|
o.proxied = o.Content
|
||||||
|
}
|
||||||
|
hor, ver := o.shouldScroll(o.bounds)
|
||||||
|
if hor {
|
||||||
|
o.hor.Handle(ctx, e)
|
||||||
|
}
|
||||||
|
if ver {
|
||||||
|
o.ver.Handle(ctx, e)
|
||||||
|
}
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *MouseMoveEvent:
|
||||||
|
if ver {
|
||||||
|
var contentO = o.Content.Offset()
|
||||||
|
var content = o.Content.Bounds()
|
||||||
|
content.Min = contentO
|
||||||
|
content.Max = content.Max.Add(contentO)
|
||||||
|
if e.MouseWheel != 0 && e.Pos().In(content) {
|
||||||
|
o.ver.ContentOffset = geom.Max32(0, geom.Min32(o.ver.ContentLength-content.Dy(), o.ver.ContentOffset-36*e.MouseWheel))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o.Content.Handle(ctx, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) IsInBounds(p geom.PointF32) bool { return p.Sub(o.offset).In(o.bounds) }
|
||||||
|
|
||||||
|
func (o *overflow) Offset() geom.PointF32 { return o.offset }
|
||||||
|
|
||||||
|
func (o *overflow) Parent() Control { return o.parent }
|
||||||
|
|
||||||
|
func (o *overflow) Render(ctx Context) {
|
||||||
|
var renderer = ctx.Renderer()
|
||||||
|
|
||||||
|
if o.Background != nil {
|
||||||
|
renderer.FillRectangle(o.bounds, o.Background)
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = o.Content.Bounds()
|
||||||
|
content.Min = geom.ZeroPtF32
|
||||||
|
err := o.content.Update(ctx, content.Size())
|
||||||
|
if err != nil && err != ErrNewBufferSize {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
o.content.Render(ctx, o.bounds.Min, func(Context, geom.PointF32) {
|
||||||
|
renderer.Clear(color.Transparent)
|
||||||
|
o.Content.Render(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
o.doOnVisibleBars(func(bar *Scrollbar) {
|
||||||
|
bar.Render(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (o *overflow) SetBackgroundColor(c color.Color) {
|
||||||
|
o.Background = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) SetClipHorizontal(clip bool) {
|
||||||
|
o.ClipHorizontal = clip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) SetClipVertical(clip bool) {
|
||||||
|
o.ClipVertical = clip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) SetScrollbarColor(bar color.Color, hover color.Color) {
|
||||||
|
o.hor.BarColor = bar
|
||||||
|
o.hor.BarHoverColor = hover
|
||||||
|
o.ver.BarColor = bar
|
||||||
|
o.ver.BarHoverColor = hover
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *overflow) ScrollIntoView(ctx Context, path ControlPath) {
|
||||||
|
view := path.BoundsUnclipped(ctx, o)
|
||||||
|
size := geom.PtF32(o.hor.Bounds().Dx(), o.ver.Bounds().Dy())
|
||||||
|
view.Min.Y += o.ver.ContentOffset
|
||||||
|
view.Max.Y += o.ver.ContentOffset
|
||||||
|
if view.Max.Y > o.ver.ContentLength {
|
||||||
|
view.Max.Y = o.ver.ContentLength
|
||||||
|
if view.Min.Y > view.Max.Y {
|
||||||
|
view.Min.Y = view.Max.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if view.Min.Y < 0 {
|
||||||
|
view.Min.Y = 0
|
||||||
|
}
|
||||||
|
if view.Max.Y > o.ver.ContentOffset+size.Y {
|
||||||
|
o.ver.ContentOffset = view.Max.Y - size.Y
|
||||||
|
}
|
||||||
|
if view.Min.Y < o.ver.ContentOffset {
|
||||||
|
o.ver.ContentOffset = view.Min.Y
|
||||||
|
}
|
||||||
|
}
|
8
ui/overlay.go
Normal file
8
ui/overlay.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type Overlay interface {
|
||||||
|
Control
|
||||||
|
|
||||||
|
Shown()
|
||||||
|
Hidden()
|
||||||
|
}
|
9
ui/overlaybase.go
Normal file
9
ui/overlaybase.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
type OverlayBase struct {
|
||||||
|
ControlBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OverlayBase) Hidden() {}
|
||||||
|
|
||||||
|
func (o *OverlayBase) Shown() {}
|
109
ui/overlays.go
Normal file
109
ui/overlays.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &Overlays{}
|
||||||
|
|
||||||
|
type OverlayVisibilityChangedArgs struct {
|
||||||
|
Name string
|
||||||
|
Visible bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Overlays struct {
|
||||||
|
Proxy
|
||||||
|
|
||||||
|
overlays map[string]Overlay
|
||||||
|
order []string
|
||||||
|
visible map[string]bool
|
||||||
|
|
||||||
|
overlayHidden zntg.Events
|
||||||
|
overlayShown zntg.Events
|
||||||
|
visibilityChanged zntg.Events
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOverlays(over Control) *Overlays {
|
||||||
|
return &Overlays{
|
||||||
|
Proxy: Proxy{Content: over},
|
||||||
|
|
||||||
|
overlays: map[string]Overlay{},
|
||||||
|
visible: map[string]bool{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) setVisibility(name string, visible bool) {
|
||||||
|
if o.visible[name] == visible {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.visible[name] = visible
|
||||||
|
overlay, ok := o.overlays[name].(Overlay)
|
||||||
|
if ok {
|
||||||
|
if visible {
|
||||||
|
overlay.Shown()
|
||||||
|
} else {
|
||||||
|
overlay.Hidden()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.visibilityChanged.Notify(OverlayVisibilityChangedArgs{Name: name, Visible: visible})
|
||||||
|
if visible {
|
||||||
|
o.overlayShown.Notify(name)
|
||||||
|
} else {
|
||||||
|
o.overlayHidden.Notify(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) AddOnTop(name string, overlay Overlay, visible bool) {
|
||||||
|
o.order = append(o.order, name)
|
||||||
|
o.overlays[name] = overlay
|
||||||
|
o.visible[name] = false
|
||||||
|
if visible {
|
||||||
|
o.Show(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) AddOnBottom(name string, overlay Overlay, visible bool) {
|
||||||
|
o.order = append([]string{name}, o.order...)
|
||||||
|
o.overlays[name] = overlay
|
||||||
|
o.visible[name] = visible
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
|
||||||
|
o.Proxy.Arrange(ctx, bounds, offset, parent)
|
||||||
|
for _, overlay := range o.overlays {
|
||||||
|
overlay.Arrange(ctx, bounds, offset, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) Handle(ctx Context, e Event) bool {
|
||||||
|
var handled bool
|
||||||
|
for _, overlay := range o.order {
|
||||||
|
if o.visible[overlay] {
|
||||||
|
if o.overlays[overlay].Handle(ctx, e) { // handle all overlays regardless of return value
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if handled {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return o.Proxy.Handle(ctx, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) Hide(name string) { o.SetVisibility(name, false) }
|
||||||
|
|
||||||
|
func (o *Overlays) Render(ctx Context) {
|
||||||
|
o.Proxy.Render(ctx)
|
||||||
|
for _, overlay := range o.order {
|
||||||
|
if o.visible[overlay] {
|
||||||
|
o.overlays[overlay].Render(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlays) SetVisibility(name string, visible bool) { o.setVisibility(name, visible) }
|
||||||
|
|
||||||
|
func (o *Overlays) Show(name string) { o.SetVisibility(name, true) }
|
||||||
|
|
||||||
|
func (o *Overlays) Toggle(name string) { o.SetVisibility(name, !o.visible[name]) }
|
97
ui/paragraph.go
Normal file
97
ui/paragraph.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Paragraph struct {
|
||||||
|
Label
|
||||||
|
|
||||||
|
width float32
|
||||||
|
lines CachedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildParagraph(text string, fn func(*Paragraph)) *Paragraph {
|
||||||
|
var p = &Paragraph{}
|
||||||
|
p.Text = text
|
||||||
|
if fn != nil {
|
||||||
|
fn(p)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastFormatFloat32(f float32) string { return strconv.FormatFloat(float64(f), 'b', 32, 32) }
|
||||||
|
|
||||||
|
func (p *Paragraph) desiredSize(ctx Context) interface{} {
|
||||||
|
font := p.ActualFont(ctx)
|
||||||
|
|
||||||
|
pad := p.ActualTextPadding(ctx)
|
||||||
|
lines := p.splitInLines(ctx, p.width-pad.Left-pad.Right)
|
||||||
|
return geom.PtF32(p.width, float32(len(lines))*font.Height()+pad.Top+pad.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) hashTextArranged(ctx Context) string {
|
||||||
|
return p.FontName(ctx) + p.Text + fastFormatFloat32(p.Bounds().Dx())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) hashTextDesired(ctx Context) string {
|
||||||
|
return p.FontName(ctx) + p.Text + fastFormatFloat32(p.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
|
||||||
|
font := p.ActualFont(ctx)
|
||||||
|
var lines []string
|
||||||
|
for _, line := range strings.Split(p.Text, "\n") {
|
||||||
|
if len(line) == 0 {
|
||||||
|
lines = append(lines, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(line) > 0 {
|
||||||
|
clipped := fitTextWord(font, line, width)
|
||||||
|
lines = append(lines, clipped)
|
||||||
|
line = strings.TrimLeft(line[len(clipped):], " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) updateLines(ctx Context) interface{} {
|
||||||
|
pad := p.ActualTextPadding(ctx)
|
||||||
|
return p.splitInLines(ctx, p.Bounds().Dx()-pad.Left-pad.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
|
||||||
|
// stores the given width, is used when calculating the new desired size (and thus used in the hash method as well)
|
||||||
|
p.width = size.X
|
||||||
|
return p.desired.GetContext(ctx, p.desiredSize, p.hashTextDesired).(geom.PointF32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paragraph) Render(ctx Context) {
|
||||||
|
p.RenderBackground(ctx)
|
||||||
|
|
||||||
|
pad := p.ActualTextPadding(ctx)
|
||||||
|
width := p.Bounds().Dx() - pad.Left - pad.Right
|
||||||
|
lines := p.lines.GetContext(ctx, p.updateLines, p.hashTextArranged).([]string)
|
||||||
|
|
||||||
|
fontColor := p.TextColor(ctx)
|
||||||
|
fontName := p.FontName(ctx)
|
||||||
|
fontHeight := ctx.Fonts().Font(fontName).Height()
|
||||||
|
bounds := pad.InsetRect(p.bounds)
|
||||||
|
|
||||||
|
left := bounds.Min.X
|
||||||
|
switch p.TextAlignment {
|
||||||
|
case AlignRight:
|
||||||
|
left = bounds.Max.X
|
||||||
|
case AlignCenter:
|
||||||
|
left += .5 * width
|
||||||
|
}
|
||||||
|
offset := bounds.Min.Y
|
||||||
|
for _, line := range lines {
|
||||||
|
ctx.Fonts().TextAlign(fontName, geom.PtF32(left, offset), fontColor, line, p.TextAlignment)
|
||||||
|
offset += fontHeight
|
||||||
|
}
|
||||||
|
}
|
62
ui/pathresources.go
Normal file
62
ui/pathresources.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Resources = &PathResources{}
|
||||||
|
|
||||||
|
// PathResources implements Resources by adding a prefix to the requested source name before proxying it to its source.
|
||||||
|
type PathResources struct {
|
||||||
|
Source Resources
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPathResources creates a new Resources by adding a prefix to the requested source name before proxying it to its source. If source is nil it will use the OS file system for its resources.
|
||||||
|
func NewPathResources(source Resources, prefix string) *PathResources {
|
||||||
|
if source == nil {
|
||||||
|
source = &OSResources{}
|
||||||
|
}
|
||||||
|
return &PathResources{source, prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroys the source.
|
||||||
|
func (r *PathResources) Destroy() error { return r.Source.Destroy() }
|
||||||
|
|
||||||
|
// OpenResource opens the resource with the prefixed name.
|
||||||
|
func (r *PathResources) OpenResource(name string) (io.ReadCloser, error) {
|
||||||
|
path := filepath.Join(r.Prefix, name)
|
||||||
|
return r.Source.OpenResource(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PhysicalResources = &PathPhysicalResources{}
|
||||||
|
|
||||||
|
// PathPhysicalResources implements PhysicalResources by adding a prefix to the requested source name before proxying it to its source.
|
||||||
|
type PathPhysicalResources struct {
|
||||||
|
Source PhysicalResources
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPathPhysicalResources creates a new PhysicalResources by adding a prefix to the requested source name before proxying it to its source. If source is nil it will use the OS file system for its resources.
|
||||||
|
func NewPathPhysicalResources(source PhysicalResources, prefix string) *PathPhysicalResources {
|
||||||
|
if source == nil {
|
||||||
|
source = &OSResources{}
|
||||||
|
}
|
||||||
|
return &PathPhysicalResources{source, prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroys the source.
|
||||||
|
func (r *PathPhysicalResources) Destroy() error { return r.Source.Destroy() }
|
||||||
|
|
||||||
|
// FetchResource fetches the resource with the prefixed name.
|
||||||
|
func (r *PathPhysicalResources) FetchResource(name string) (string, error) {
|
||||||
|
path := filepath.Join(r.Prefix, name)
|
||||||
|
return r.Source.FetchResource(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenResource opens the resource with the prefixed name.
|
||||||
|
func (r *PathPhysicalResources) OpenResource(name string) (io.ReadCloser, error) {
|
||||||
|
path := filepath.Join(r.Prefix, name)
|
||||||
|
return r.Source.OpenResource(path)
|
||||||
|
}
|
72
ui/proxy.go
Normal file
72
ui/proxy.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
var _ Control = &Proxy{}
|
||||||
|
var _ Overlay = &Proxy{}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
Content Control
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
|
||||||
|
p.Content.Arrange(ctx, bounds, offset, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
|
||||||
|
return p.Content.DesiredSize(ctx, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Handle(ctx Context, e Event) bool {
|
||||||
|
return p.Content.Handle(ctx, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Render(ctx Context) {
|
||||||
|
p.Content.Render(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Bounds() geom.RectangleF32 {
|
||||||
|
return p.Content.Bounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
|
||||||
|
return path.BoundsUnclipped(ctx, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Disable() { p.Content.Disable() }
|
||||||
|
|
||||||
|
func (p *Proxy) Enable() { p.Content.Enable() }
|
||||||
|
|
||||||
|
func (p *Proxy) Hidden() {
|
||||||
|
overlay, ok := p.Content.(Overlay)
|
||||||
|
if ok {
|
||||||
|
overlay.Hidden()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) IsDisabled() bool { return p.Content.IsDisabled() }
|
||||||
|
|
||||||
|
func (p *Proxy) IsInBounds(pt geom.PointF32) bool { return p.Content.IsInBounds(pt) }
|
||||||
|
|
||||||
|
func (p *Proxy) IsOver() bool { return p.Content.IsOver() }
|
||||||
|
|
||||||
|
func (p *Proxy) Offset() geom.PointF32 { return p.Content.Offset() }
|
||||||
|
|
||||||
|
func (p *Proxy) Parent() Control { return p.Content.Parent() }
|
||||||
|
|
||||||
|
func (p *Proxy) Shown() {
|
||||||
|
overlay, ok := p.Content.(Overlay)
|
||||||
|
if ok {
|
||||||
|
overlay.Shown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) ScrollIntoView(ctx Context, path ControlPath) {
|
||||||
|
p.Content.Parent().ScrollIntoView(ctx, path.Prepend(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Self() Control {
|
||||||
|
return p.Content.Self()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) SetSelf(self Control) { p.Content.SetSelf(self) }
|
67
ui/renderer.go
Normal file
67
ui/renderer.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Renderer interface {
|
||||||
|
// Events
|
||||||
|
PushEvents(t EventTarget, wait bool) bool
|
||||||
|
Refresh()
|
||||||
|
Stamp() float64 // in seconds
|
||||||
|
|
||||||
|
// Lifetime
|
||||||
|
Destroy() error
|
||||||
|
|
||||||
|
// Drawing
|
||||||
|
Clear(c color.Color)
|
||||||
|
CreateFontPath(path string, size int) (Font, error)
|
||||||
|
CreateTexture(m ImageSource) (Texture, error)
|
||||||
|
CreateTextureGo(m image.Image, source bool) (Texture, error)
|
||||||
|
CreateTexturePath(path string, source bool) (Texture, error)
|
||||||
|
CreateTextureTarget(w, h float32) (Texture, error)
|
||||||
|
DefaultTarget() Texture
|
||||||
|
DrawTexture(t Texture, p geom.RectangleF32)
|
||||||
|
DrawTextureOptions(t Texture, p geom.RectangleF32, opts DrawOptions)
|
||||||
|
DrawTexturePoint(t Texture, p geom.PointF32)
|
||||||
|
DrawTexturePointOptions(t Texture, p geom.PointF32, opts DrawOptions)
|
||||||
|
FillRectangle(r geom.RectangleF32, c color.Color)
|
||||||
|
Line(p, q geom.PointF32, color color.Color, thickness float32)
|
||||||
|
Location() geom.Point
|
||||||
|
Move(geom.Point)
|
||||||
|
Rectangle(r geom.RectangleF32, c color.Color, thickness float32)
|
||||||
|
RenderTo(Texture)
|
||||||
|
RenderToDisplay()
|
||||||
|
Resize(width, height int)
|
||||||
|
SetIcon(source ImageSource)
|
||||||
|
SetMouseCursor(c MouseCursor)
|
||||||
|
Size() geom.Point
|
||||||
|
Target() Texture
|
||||||
|
Text(font Font, p geom.PointF32, color color.Color, text string)
|
||||||
|
TextAlign(font Font, p geom.PointF32, color color.Color, text string, align HorizontalAlignment)
|
||||||
|
TextTexture(font Font, color color.Color, text string) (Texture, error)
|
||||||
|
WindowHandle() uintptr
|
||||||
|
|
||||||
|
// Resources
|
||||||
|
Resources() Resources
|
||||||
|
SetResourceProvider(resources Resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextTexture renders specified text to a new texture.
|
||||||
|
func TextTexture(render Renderer, font Font, color color.Color, text string) (Texture, error) {
|
||||||
|
target := render.Target()
|
||||||
|
defer render.RenderTo(target)
|
||||||
|
bounds := font.Measure(text)
|
||||||
|
texture, err := render.CreateTextureTarget(bounds.Dx(), bounds.Dy())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
render.RenderTo(texture)
|
||||||
|
render.Clear(zntg.MustHexColor(`#00000000`))
|
||||||
|
render.Text(font, bounds.Min.Invert(), color, text)
|
||||||
|
return texture, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user