Compare commits
103 Commits
projection
...
master
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 |
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) }
|
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) }
|
260
allg5/bitmap.go
260
allg5/bitmap.go
@ -1,260 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Bitmap represents an in memory bitmap
|
||||
type Bitmap struct {
|
||||
bitmap *C.ALLEGRO_BITMAP
|
||||
width int
|
||||
height int
|
||||
subs []*Bitmap
|
||||
}
|
||||
|
||||
type DrawOptions struct {
|
||||
Center bool
|
||||
Scale *Scale
|
||||
Tint *Color
|
||||
Rotation *Rotation
|
||||
}
|
||||
|
||||
type Scale struct {
|
||||
Horizontal float32
|
||||
Vertical float32
|
||||
}
|
||||
|
||||
func NewScale(hor, ver float32) *Scale {
|
||||
return &Scale{hor, ver}
|
||||
}
|
||||
|
||||
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 mut != nil {
|
||||
mut(m)
|
||||
}
|
||||
for _, f := range flags {
|
||||
m.Set(f)
|
||||
}
|
||||
})
|
||||
b := C.al_create_bitmap(C.int(width), C.int(height))
|
||||
if b == nil {
|
||||
return nil, errors.New("error creating bitmap")
|
||||
}
|
||||
return &Bitmap{b, width, height, nil}, nil
|
||||
}
|
||||
|
||||
// NewBitmap creates a new bitmap of given width and height and optional flags
|
||||
func NewBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
|
||||
return newBitmap(width, height, nil, flags)
|
||||
}
|
||||
|
||||
// NewVideoBitmap creates a new video bitmap of given width and height and optional flags
|
||||
func NewVideoBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
|
||||
return newBitmap(width, height, func(m FlagMutation) {
|
||||
m.Unset(NewBitmapFlagMemoryBitmap)
|
||||
m.Set(NewBitmapFlagVideoBitmap)
|
||||
}, flags)
|
||||
}
|
||||
|
||||
// NewMemoryBitmap creates a new video bitmap of given width and height and optional flags
|
||||
func NewMemoryBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
|
||||
return newBitmap(width, height, func(m FlagMutation) {
|
||||
m.Unset(NewBitmapFlagVideoBitmap)
|
||||
m.Set(NewBitmapFlagMemoryBitmap)
|
||||
}, flags)
|
||||
}
|
||||
|
||||
// NewBitmapFromImage creates a new bitmap starting from a Go native image (image.Image)
|
||||
func NewBitmapFromImage(src 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 = src.Bounds()
|
||||
width, height := bnd.Dx(), bnd.Dy()
|
||||
var b = C.al_create_bitmap(C.int(width), C.int(height))
|
||||
if b == nil {
|
||||
return nil, errors.New("error creating memory bitmap")
|
||||
}
|
||||
region := C.al_lock_bitmap(b, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_WRITEONLY)
|
||||
if region == nil {
|
||||
C.al_destroy_bitmap(b)
|
||||
return nil, errors.New("unable to lock bitmap")
|
||||
}
|
||||
dst := (*[1 << 30]uint8)(region.data)
|
||||
left, top := bnd.Min.X, bnd.Min.Y
|
||||
for y := 0; y < height; y++ {
|
||||
row := dst[y*int(region.pitch):]
|
||||
for x := 0; x < width; x++ {
|
||||
r, g, b, a := src.At(left+x, top+y).RGBA()
|
||||
row[x*4] = uint8(r >> 8)
|
||||
row[x*4+1] = uint8(g >> 8)
|
||||
row[x*4+2] = uint8(b >> 8)
|
||||
row[x*4+3] = uint8(a >> 8)
|
||||
}
|
||||
}
|
||||
C.al_unlock_bitmap(b)
|
||||
if video {
|
||||
newBmpFlags.Mutate(func(m FlagMutation) {
|
||||
m.Unset(NewBitmapFlagMemoryBitmap)
|
||||
m.Set(NewBitmapFlagVideoBitmap)
|
||||
m.Set(NewBitmapFlagMinLinear)
|
||||
})
|
||||
C.al_convert_bitmap(b)
|
||||
}
|
||||
return &Bitmap{b, width, height, nil}, nil
|
||||
}
|
||||
|
||||
// LoadBitmap tries to load the image at the specified path as a bitmap
|
||||
func LoadBitmap(path string) (*Bitmap, error) {
|
||||
p := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(p))
|
||||
b := C.al_load_bitmap(p)
|
||||
if b == nil {
|
||||
return nil, errors.New("error loading bitmap")
|
||||
}
|
||||
width := int(C.al_get_bitmap_width(b))
|
||||
height := int(C.al_get_bitmap_height(b))
|
||||
return &Bitmap{b, width, height, nil}, nil
|
||||
}
|
||||
|
||||
// Draw draws the bitmap at the given location
|
||||
func (b *Bitmap) Draw(left, top float32) {
|
||||
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
|
||||
}
|
||||
|
||||
func (b *Bitmap) DrawOptions(left, top float32, options DrawOptions) {
|
||||
width := float32(b.width)
|
||||
height := float32(b.height)
|
||||
|
||||
scale := options.Scale != nil
|
||||
if scale {
|
||||
width *= options.Scale.Horizontal
|
||||
height *= options.Scale.Vertical
|
||||
}
|
||||
if options.Center {
|
||||
left -= width * 0.5
|
||||
top -= height * 0.5
|
||||
}
|
||||
rotated := options.Rotation != nil
|
||||
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 options.Tint == nil { // 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 options.Tint == nil {
|
||||
if rotated { // rotated
|
||||
C.al_draw_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0)
|
||||
} else {
|
||||
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
|
||||
}
|
||||
} else { // tinted
|
||||
if rotated { // tinted & rotated
|
||||
C.al_draw_tinted_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0)
|
||||
} else {
|
||||
C.al_draw_tinted_bitmap(b.bitmap, options.Tint.color, C.float(left), C.float(top), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub creates a sub-bitmap of the original bitmap
|
||||
func (b *Bitmap) Sub(x, y, w, h int) *Bitmap {
|
||||
var sub = C.al_create_sub_bitmap(b.bitmap, C.int(x), C.int(y), C.int(w), C.int(h))
|
||||
if sub == nil {
|
||||
return nil
|
||||
}
|
||||
var bmp = &Bitmap{sub, w, h, nil}
|
||||
b.subs = append(b.subs, bmp)
|
||||
return bmp
|
||||
}
|
||||
|
||||
// Subs returns the slice of sub-bitmaps
|
||||
func (b *Bitmap) Subs() []*Bitmap {
|
||||
return b.subs
|
||||
}
|
||||
|
||||
func (b *Bitmap) Width() int {
|
||||
return b.width
|
||||
}
|
||||
|
||||
func (b *Bitmap) Height() int {
|
||||
return b.height
|
||||
}
|
||||
|
||||
func (b *Bitmap) IsVideo() bool {
|
||||
return C.al_get_bitmap_flags(b.bitmap)&C.ALLEGRO_VIDEO_BITMAP == C.ALLEGRO_VIDEO_BITMAP
|
||||
}
|
||||
|
||||
func (b *Bitmap) Image() image.Image {
|
||||
im := image.NewRGBA(image.Rect(0, 0, b.width, b.height))
|
||||
region := C.al_lock_bitmap(b.bitmap, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_READONLY)
|
||||
if region == nil {
|
||||
return nil
|
||||
}
|
||||
defer C.al_unlock_bitmap(b.bitmap)
|
||||
src := (*[1 << 30]uint8)(region.data)
|
||||
dst := im.Pix
|
||||
var srcOff, dstOff int
|
||||
for y := 0; y < b.height; y++ {
|
||||
copy(dst[dstOff:], src[srcOff:srcOff+b.width*4])
|
||||
srcOff += int(region.pitch)
|
||||
dstOff += im.Stride
|
||||
}
|
||||
return im
|
||||
}
|
||||
|
||||
func (b *Bitmap) SetAsTarget() {
|
||||
C.al_set_target_bitmap(b.bitmap)
|
||||
}
|
||||
|
||||
// Destroy destroys the bitmap
|
||||
func (b *Bitmap) Destroy() {
|
||||
var bmp = b.bitmap
|
||||
if bmp == nil {
|
||||
return
|
||||
}
|
||||
b.bitmap = nil
|
||||
for _, sub := range b.subs {
|
||||
sub.Destroy()
|
||||
}
|
||||
C.al_destroy_bitmap(bmp)
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package allg5
|
||||
|
||||
// #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 allg5
|
||||
|
||||
// #cgo LDFLAGS: -lallegro -lallegro_font -lallegro_image -lallegro_primitives -lallegro_ttf
|
||||
import "C"
|
@ -1,6 +0,0 @@
|
||||
// +build windows,static
|
||||
|
||||
package allg5
|
||||
|
||||
// #cgo LDFLAGS: -lallegro_monolith-static -static -ljpeg -ldumb -lFLAC -lfreetype -lvorbisfile -lvorbis -logg -lphysfs -lpng16 -lzlib -luuid -lkernel32 -lwinmm -lpsapi -lopengl32 -lglu32 -luser32 -lcomdlg32 -lgdi32 -lshell32 -lole32 -ladvapi32 -lws2_32 -lshlwapi -lstdc++ -lwebp
|
||||
import "C"
|
@ -1,36 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
import "image/color"
|
||||
|
||||
var _ color.Color = &Color{}
|
||||
|
||||
type Color struct {
|
||||
color C.ALLEGRO_COLOR
|
||||
}
|
||||
|
||||
func NewColor(r, g, b byte) Color {
|
||||
return Color{C.al_map_rgb(C.uchar(r), C.uchar(g), C.uchar(b))}
|
||||
}
|
||||
|
||||
func NewColorAlpha(r, g, b, a byte) Color {
|
||||
return Color{C.al_map_rgba(C.uchar(r), C.uchar(g), C.uchar(b), C.uchar(a))}
|
||||
}
|
||||
|
||||
func NewColorGo(c color.Color) Color {
|
||||
r, g, b, a := c.RGBA()
|
||||
return Color{C.al_premul_rgba(C.uchar(r>>8), C.uchar(g>>8), C.uchar(b>>8), C.uchar(a>>8))}
|
||||
}
|
||||
|
||||
// RGBA implements the color.Color interface.
|
||||
func (c Color) RGBA() (r, g, b, a uint32) {
|
||||
var cr, cg, cb, ca C.uchar
|
||||
C.al_unmap_rgba(c.color, &cr, &cg, &cb, &ca)
|
||||
a = uint32(ca)
|
||||
r = uint32(cr) * a
|
||||
g = uint32(cg) * a
|
||||
b = uint32(cb) * a
|
||||
a *= a
|
||||
return
|
||||
}
|
125
allg5/display.go
125
allg5/display.go
@ -1,125 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Display represents a display
|
||||
type Display struct {
|
||||
display *C.ALLEGRO_DISPLAY
|
||||
}
|
||||
|
||||
type NewDisplayOptions struct {
|
||||
Fullscreen bool
|
||||
Resizable bool
|
||||
Windowed bool
|
||||
Maximized bool
|
||||
Frameless bool
|
||||
Shaders bool
|
||||
OpenGL 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
|
||||
}
|
||||
}
|
||||
if options.OpenGL {
|
||||
flags |= C.ALLEGRO_OPENGL
|
||||
}
|
||||
if options.Shaders {
|
||||
flags |= C.ALLEGRO_PROGRAMMABLE_PIPELINE
|
||||
}
|
||||
C.al_set_new_display_flags(flags)
|
||||
d := C.al_create_display(C.int(width), C.int(height))
|
||||
if d == nil {
|
||||
return nil, errors.New("error creating display")
|
||||
}
|
||||
return &Display{d}, nil
|
||||
}
|
||||
|
||||
// Flip flips the buffer to the display
|
||||
func (d *Display) Flip() {
|
||||
C.al_flip_display()
|
||||
}
|
||||
|
||||
func (d *Display) Width() int {
|
||||
return int(C.al_get_display_width(d.display))
|
||||
}
|
||||
|
||||
func (d *Display) Height() int {
|
||||
return int(C.al_get_display_height(d.display))
|
||||
}
|
||||
|
||||
func (d *Display) Position() (int, int) {
|
||||
var x, y C.int
|
||||
C.al_get_window_position(d.display, &x, &y)
|
||||
return int(x), int(y)
|
||||
}
|
||||
|
||||
func (d *Display) Resize(w, h int) {
|
||||
C.al_resize_display(d.display, C.int(w), C.int(h))
|
||||
}
|
||||
|
||||
func (d *Display) SetAsTarget() {
|
||||
C.al_set_target_backbuffer(d.display)
|
||||
}
|
||||
|
||||
func (d *Display) SetIcon(i *Bitmap) {
|
||||
C.al_set_display_icon(d.display, i.bitmap)
|
||||
}
|
||||
|
||||
func (d *Display) SetMouseCursor(c MouseCursor) {
|
||||
C.al_set_system_mouse_cursor(d.display, C.ALLEGRO_SYSTEM_MOUSE_CURSOR(c))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (d *Display) Target() *Bitmap {
|
||||
return &Bitmap{C.al_get_backbuffer(d.display), d.Width(), d.Height(), nil}
|
||||
}
|
||||
|
||||
// Destroy destroys the display
|
||||
func (d *Display) Destroy() {
|
||||
C.al_destroy_display(d.display)
|
||||
}
|
||||
|
||||
func CurrentTarget() *Bitmap {
|
||||
var bmp = C.al_get_target_bitmap()
|
||||
return &Bitmap{bmp, int(C.al_get_bitmap_width(bmp)), int(C.al_get_bitmap_height(bmp)), nil}
|
||||
}
|
||||
|
||||
func SetNewWindowTitle(title string) {
|
||||
t := C.CString(title)
|
||||
defer C.free(unsafe.Pointer(t))
|
||||
C.al_set_new_window_title(t)
|
||||
}
|
249
allg5/event.go
249
allg5/event.go
@ -1,249 +0,0 @@
|
||||
package allg5
|
||||
|
||||
/*
|
||||
#include <allegro5/allegro.h>
|
||||
|
||||
#define USER_EVENT_TYPE ALLEGRO_GET_EVENT_TYPE('u', 's', 'e', 'r')
|
||||
|
||||
void init_user_event(ALLEGRO_EVENT* e)
|
||||
{
|
||||
e->user.type = USER_EVENT_TYPE;
|
||||
}
|
||||
*/
|
||||
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 MouseEnterEvent struct {
|
||||
MouseEvent
|
||||
}
|
||||
|
||||
type MouseEvent struct {
|
||||
EventBase
|
||||
X, Y int
|
||||
Z, W int
|
||||
Display *Display
|
||||
}
|
||||
type MouseLeaveEvent struct {
|
||||
MouseEvent
|
||||
}
|
||||
|
||||
type MouseMoveEvent struct {
|
||||
MouseEvent
|
||||
DeltaX, DeltaY int
|
||||
DeltaZ, DeltaW int
|
||||
Pressure float32
|
||||
}
|
||||
|
||||
type UserEvent struct {
|
||||
EventBase
|
||||
}
|
||||
|
||||
type UserEventSource struct {
|
||||
source *C.ALLEGRO_EVENT_SOURCE
|
||||
}
|
||||
|
||||
func NewEventQueue() (*EventQueue, error) {
|
||||
q := C.al_create_event_queue()
|
||||
if q == nil {
|
||||
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) RegisterUserEvents(source *UserEventSource) {
|
||||
eq.register(source.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_ENTER_DISPLAY:
|
||||
mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
|
||||
return &MouseEnterEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}}
|
||||
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_MOUSE_LEAVE_DISPLAY:
|
||||
mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e))
|
||||
return &MouseLeaveEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}}
|
||||
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)}
|
||||
case C.USER_EVENT_TYPE:
|
||||
return &UserEvent{eb}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func NewUserEventSource() *UserEventSource {
|
||||
s := (*C.ALLEGRO_EVENT_SOURCE)(C.malloc(C.sizeof_ALLEGRO_EVENT_SOURCE))
|
||||
source := &UserEventSource{s}
|
||||
C.al_init_user_event_source(s)
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *UserEventSource) Destroy() {
|
||||
C.al_destroy_user_event_source(s.source)
|
||||
C.free(unsafe.Pointer(s.source))
|
||||
}
|
||||
|
||||
func (s *UserEventSource) EmitEvent() bool {
|
||||
e := (*C.ALLEGRO_EVENT)(C.malloc(C.sizeof_ALLEGRO_EVENT))
|
||||
C.init_user_event(e)
|
||||
return bool(C.al_emit_user_event(s.source, e, nil))
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
type FlagMutation interface {
|
||||
Set(f NewBitmapFlag)
|
||||
Unset(f NewBitmapFlag)
|
||||
}
|
||||
|
||||
type flagMut struct {
|
||||
flg C.int
|
||||
}
|
||||
|
||||
func (m *flagMut) Set(f NewBitmapFlag) {
|
||||
m.flg |= C.int(f)
|
||||
}
|
||||
|
||||
func (m *flagMut) Unset(f NewBitmapFlag) {
|
||||
if m.flg&C.int(f) == C.int(f) {
|
||||
m.flg ^= C.int(f)
|
||||
}
|
||||
}
|
107
allg5/font.go
107
allg5/font.go
@ -1,107 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
// #include <allegro5/allegro_font.h>
|
||||
// #include <allegro5/allegro_ttf.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Font struct {
|
||||
font *C.ALLEGRO_FONT
|
||||
hght float32
|
||||
asc float32
|
||||
desc float32
|
||||
}
|
||||
|
||||
type HorizontalAlignment int
|
||||
|
||||
const (
|
||||
AlignLeft HorizontalAlignment = iota
|
||||
AlignCenter
|
||||
AlignRight
|
||||
)
|
||||
|
||||
func LoadTTFFont(path string, size int) (*Font, error) {
|
||||
p := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(p))
|
||||
|
||||
f := C.al_load_ttf_font(p, C.int(size), 0)
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("unable to load ttf font '%s'", path)
|
||||
}
|
||||
return &Font{f, 0, 0, 0}, nil
|
||||
}
|
||||
|
||||
func (f *Font) drawFlags(a HorizontalAlignment) C.int {
|
||||
switch a {
|
||||
case AlignLeft:
|
||||
return C.ALLEGRO_ALIGN_LEFT
|
||||
case AlignCenter:
|
||||
return C.ALLEGRO_ALIGN_CENTRE
|
||||
case AlignRight:
|
||||
return C.ALLEGRO_ALIGN_RIGHT
|
||||
}
|
||||
return C.ALLEGRO_ALIGN_LEFT
|
||||
}
|
||||
|
||||
func (f *Font) Draw(left, top float32, color Color, align HorizontalAlignment, text string) {
|
||||
t := C.CString(text)
|
||||
defer C.free(unsafe.Pointer(t))
|
||||
|
||||
flags := f.drawFlags(align)
|
||||
C.al_draw_text(f.font, color.color, C.float(left), C.float(top), flags, t)
|
||||
}
|
||||
|
||||
// Ascent returns the ascent of the font
|
||||
func (f *Font) Ascent() float32 {
|
||||
if f.asc == 0 {
|
||||
f.asc = float32(C.al_get_font_ascent(f.font))
|
||||
}
|
||||
return f.asc
|
||||
}
|
||||
|
||||
// Descent returns the descent of the font.
|
||||
func (f *Font) Descent() float32 {
|
||||
if f.desc == 0 {
|
||||
f.desc = float32(C.al_get_font_descent(f.font))
|
||||
}
|
||||
return f.desc
|
||||
}
|
||||
|
||||
// Height returns the height of the font
|
||||
func (f *Font) Height() float32 {
|
||||
if f.hght == 0 {
|
||||
f.hght = f.Ascent() + f.Descent()
|
||||
}
|
||||
return f.hght
|
||||
}
|
||||
|
||||
// TextDimensions returns the bounding box of the rendered text.
|
||||
func (f *Font) TextDimensions(text string) (x, y, w, h float32) {
|
||||
t := C.CString(text)
|
||||
defer C.free(unsafe.Pointer(t))
|
||||
|
||||
var bbx, bby, bbw, bbh C.int
|
||||
C.al_get_text_dimensions(f.font, t, &bbx, &bby, &bbw, &bbh)
|
||||
x = float32(bbx)
|
||||
y = float32(bby)
|
||||
w = float32(bbw)
|
||||
h = float32(bbh)
|
||||
return
|
||||
}
|
||||
|
||||
// TextWidth returns the width of the rendered text.
|
||||
func (f *Font) TextWidth(text string) float32 {
|
||||
t := C.CString(text)
|
||||
defer C.free(unsafe.Pointer(t))
|
||||
|
||||
return float32(C.al_get_text_width(f.font, t))
|
||||
}
|
||||
|
||||
func (f *Font) Destroy() {
|
||||
C.al_destroy_font(f.font)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
|
||||
// BitmapFlag is extra information provided for creating a bitmap
|
||||
type BitmapFlag int
|
||||
|
||||
const (
|
||||
// BitmapFlagLinearScaleDown enables linear scaling when scaling down. Gives better results when combined with BitmapFlagMipMap
|
||||
BitmapFlagLinearScaleDown BitmapFlag = C.ALLEGRO_MIN_LINEAR
|
||||
// BitmapFlagLinearScaleUp enables linear scaling when scaling up.
|
||||
BitmapFlagLinearScaleUp = C.ALLEGRO_MAG_LINEAR
|
||||
// BitmapFlagMipMap enables mipmaps for drawing a scaled down version. Bitmap must square and its sides must be a power of two.
|
||||
BitmapFlagMipMap = C.ALLEGRO_MIPMAP
|
||||
)
|
||||
|
||||
// ClearToColor clears the target bitmap to the color
|
||||
func ClearToColor(c Color) {
|
||||
C.al_clear_to_color(c.color)
|
||||
}
|
||||
|
||||
// SetNewBitmapFlags sets the default bitmap flags for a newly created bitmap
|
||||
func SetNewBitmapFlags(flags BitmapFlag) {
|
||||
C.al_set_new_bitmap_flags(C.int(flags))
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
|
||||
type Key int
|
||||
|
||||
const (
|
||||
KeyA Key = 1
|
||||
KeyB = 2
|
||||
KeyC = 3
|
||||
KeyD = 4
|
||||
KeyE = 5
|
||||
KeyF = 6
|
||||
KeyG = 7
|
||||
KeyH = 8
|
||||
KeyI = 9
|
||||
KeyJ = 10
|
||||
KeyK = 11
|
||||
KeyL = 12
|
||||
KeyM = 13
|
||||
KeyN = 14
|
||||
KeyO = 15
|
||||
KeyP = 16
|
||||
KeyQ = 17
|
||||
KeyR = 18
|
||||
KeyS = 19
|
||||
KeyT = 20
|
||||
KeyU = 21
|
||||
KeyV = 22
|
||||
KeyW = 23
|
||||
KeyX = 24
|
||||
KeyY = 25
|
||||
KeyZ = 26
|
||||
Key0 = 27
|
||||
Key1 = 28
|
||||
Key2 = 29
|
||||
Key3 = 30
|
||||
Key4 = 31
|
||||
Key5 = 32
|
||||
Key6 = 33
|
||||
Key7 = 34
|
||||
Key8 = 35
|
||||
Key9 = 36
|
||||
KeyPad0 = 37
|
||||
KeyPad1 = 38
|
||||
KeyPad2 = 39
|
||||
KeyPad3 = 40
|
||||
KeyPad4 = 41
|
||||
KeyPad5 = 42
|
||||
KeyPad6 = 43
|
||||
KeyPad7 = 44
|
||||
KeyPad8 = 45
|
||||
KeyPad9 = 46
|
||||
KeyF1 = 47
|
||||
KeyF2 = 48
|
||||
KeyF3 = 49
|
||||
KeyF4 = 50
|
||||
KeyF5 = 51
|
||||
KeyF6 = 52
|
||||
KeyF7 = 53
|
||||
KeyF8 = 54
|
||||
KeyF9 = 55
|
||||
KeyF10 = 56
|
||||
KeyF11 = 57
|
||||
KeyF12 = 58
|
||||
KeyEscape = 59
|
||||
KeyTilde = 60
|
||||
KeyMinus = 61
|
||||
KeyEquals = 62
|
||||
KeyBackspace = 63
|
||||
KeyTab = 64
|
||||
KeyOpenBrace = 65
|
||||
KeyCloseBrace = 66
|
||||
KeyEnter = 67
|
||||
KeySemicolon = 68
|
||||
KeyQuote = 69
|
||||
KeyBackslash = 70
|
||||
KeyBackslash2 = 71 /* DirectInput calls this DIK_OEM_102: "< > | on UK/Germany keyboards" */
|
||||
KeyComma = 72
|
||||
KeyFullstop = 73
|
||||
KeySlash = 74
|
||||
KeySpace = 75
|
||||
KeyInsert = 76
|
||||
KeyDelete = 77
|
||||
KeyHome = 78
|
||||
KeyEnd = 79
|
||||
KeyPageUp = 80
|
||||
KeyPageDown = 81
|
||||
KeyLeft = 82
|
||||
KeyRight = 83
|
||||
KeyUp = 84
|
||||
KeyDown = 85
|
||||
KeyPadSlash = 86
|
||||
KeyPadAsterisk = 87
|
||||
KeyPadMinus = 88
|
||||
KeyPadPlus = 89
|
||||
KeyPadDelete = 90
|
||||
KeyPadEnter = 91
|
||||
KeyPrintScreen = 92
|
||||
KeyPause = 93
|
||||
KeyAbntC1 = 94
|
||||
KeyYen = 95
|
||||
KeyKana = 96
|
||||
KeyConvert = 97
|
||||
KeyNoConvert = 98
|
||||
KeyAt = 99
|
||||
KeyCircumflex = 100
|
||||
KeyColon2 = 101
|
||||
KeyKanji = 102
|
||||
KeyPadEquals = 103 /* MacOS X */
|
||||
KeyBackQuote = 104 /* MacOS X */
|
||||
KeySemicolon2 = 105 /* MacOS X -- TODO: ask lillo what this should be */
|
||||
KeyCommand = 106 /* MacOS X */
|
||||
KeyBack = 107 /* Android back key */
|
||||
KeyVolumeUp = 108
|
||||
KeyVolumeDown = 109
|
||||
KeySearch = 110
|
||||
KeyDPadCenter = 111
|
||||
KeyButtonX = 112
|
||||
KeyButtonY = 113
|
||||
KeyDPadUp = 114
|
||||
KeyDPadDown = 115
|
||||
KeyDPadLeft = 116
|
||||
KeyDPadRight = 117
|
||||
KeySelect = 118
|
||||
KeyStart = 119
|
||||
KeyButtonL1 = 120
|
||||
KeyButtonR1 = 121
|
||||
KeyButtonL2 = 122
|
||||
KeyButtonR2 = 123
|
||||
KeyButtonA = 124
|
||||
KeyButtonB = 125
|
||||
KeyThumbL = 126
|
||||
KeyThumbR = 127
|
||||
KeyUnknown = 128
|
||||
KeyModifiers = 215
|
||||
KeyLShift = 215
|
||||
KeyRShift = 216
|
||||
KeyLCtrl = 217
|
||||
KeyRCtrl = 218
|
||||
KeyAlt = 219
|
||||
KeyAltGr = 220
|
||||
KeyLWin = 221
|
||||
KeyRWin = 222
|
||||
KeyMenu = 223
|
||||
KeyScrollLock = 224
|
||||
KeyNumLock = 225
|
||||
KeyCapsLock = 226
|
||||
)
|
||||
|
||||
type KeyMod uint
|
||||
|
||||
const (
|
||||
KeyModShift KeyMod = 0x00001
|
||||
KeyModCtrl = 0x00002
|
||||
KeyModAlt = 0x00004
|
||||
KeyModLWin = 0x00008
|
||||
KeyModRWin = 0x00010
|
||||
KeyModMenu = 0x00020
|
||||
KeyModAltGr = 0x00040
|
||||
KeyModCommand = 0x00080
|
||||
KeyModScrollLock = 0x00100
|
||||
KeyModNumlock = 0x00200
|
||||
KeyModCapsLock = 0x00400
|
||||
KeyModInaltseq = 0x00800
|
||||
KeyModAccent1 = 0x01000
|
||||
KeyModAccent2 = 0x02000
|
||||
KeyModAccent3 = 0x04000
|
||||
KeyModAccent4 = 0x08000
|
||||
)
|
||||
|
||||
func IsKeyDown(k Key) bool {
|
||||
var state C.ALLEGRO_KEYBOARD_STATE
|
||||
C.al_get_keyboard_state(&state)
|
||||
return bool(C.al_key_down(&state, C.int(k)))
|
||||
}
|
||||
|
||||
func IsAnyKeyDown(keys ...Key) bool {
|
||||
var state C.ALLEGRO_KEYBOARD_STATE
|
||||
C.al_get_keyboard_state(&state)
|
||||
for _, k := range keys {
|
||||
if bool(C.al_key_down(&state, C.int(k))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
|
||||
type Monitor struct {
|
||||
X1, Y1 int
|
||||
X2, Y2 int
|
||||
}
|
||||
|
||||
func monitor(m *C.ALLEGRO_MONITOR_INFO) Monitor {
|
||||
return Monitor{int(m.x1), int(m.y1), int(m.x2), int(m.y2)}
|
||||
}
|
||||
|
||||
func DefaultMonitor() Monitor {
|
||||
var m C.ALLEGRO_MONITOR_INFO
|
||||
C.al_get_monitor_info(C.ALLEGRO_DEFAULT_DISPLAY_ADAPTER, &m)
|
||||
return monitor(&m)
|
||||
}
|
||||
|
||||
func Monitors() []Monitor {
|
||||
var n = NumberOfVideoAdapters()
|
||||
var mons []Monitor
|
||||
var m C.ALLEGRO_MONITOR_INFO
|
||||
for i := 0; i < n; i++ {
|
||||
C.al_get_monitor_info(C.int(i), &m)
|
||||
mons = append(mons, monitor(&m))
|
||||
}
|
||||
return mons
|
||||
}
|
||||
|
||||
func NumberOfVideoAdapters() int {
|
||||
return int(C.al_get_num_video_adapters())
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
|
||||
type MouseButton uint
|
||||
|
||||
const (
|
||||
MouseButtonLeft MouseButton = 1
|
||||
MouseButtonRight = 2
|
||||
MouseButtonMiddle = 3
|
||||
)
|
||||
|
||||
type MouseCursor uint
|
||||
|
||||
const (
|
||||
MouseCursorNone MouseCursor = 0
|
||||
MouseCursorDefault = 1
|
||||
MouseCursorArrow = 2
|
||||
MouseCursorBusy = 3
|
||||
MouseCursorQuestion = 4
|
||||
MouseCursorEdit = 5
|
||||
MouseCursorMove = 6
|
||||
MouseCursorResizeN = 7
|
||||
MouseCursorResizeW = 8
|
||||
MouseCursorResizeS = 9
|
||||
MouseCursorResizeE = 10
|
||||
MouseCursorResizeNW = 11
|
||||
MouseCursorResizeSW = 12
|
||||
MouseCursorResizeSE = 13
|
||||
MouseCursorResizeNE = 14
|
||||
MouseCursorProgress = 15
|
||||
MouseCursorPrecision = 16
|
||||
MouseCursorLink = 17
|
||||
MouseCursorAltSelect = 18
|
||||
MouseCursorUnavailable = 19
|
||||
)
|
||||
|
||||
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 allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
import "C"
|
||||
|
||||
type NewBitmapFlag int
|
||||
|
||||
const (
|
||||
NewBitmapFlagMemoryBitmap = NewBitmapFlag(C.ALLEGRO_MEMORY_BITMAP)
|
||||
NewBitmapFlagVideoBitmap = NewBitmapFlag(C.ALLEGRO_VIDEO_BITMAP)
|
||||
NewBitmapFlagMinLinear = NewBitmapFlag(C.ALLEGRO_MIN_LINEAR)
|
||||
)
|
||||
|
||||
type NewBitmapFlagsCapture struct {
|
||||
cap C.int
|
||||
}
|
||||
|
||||
func CaptureNewBitmapFlags() *NewBitmapFlagsCapture {
|
||||
var cap = C.al_get_new_bitmap_flags()
|
||||
return &NewBitmapFlagsCapture{cap}
|
||||
}
|
||||
|
||||
func (c *NewBitmapFlagsCapture) Mutate(mut func(FlagMutation)) {
|
||||
var m = &flagMut{c.cap}
|
||||
mut(m)
|
||||
C.al_set_new_bitmap_flags(m.flg)
|
||||
}
|
||||
|
||||
func (c *NewBitmapFlagsCapture) Revert() {
|
||||
C.al_set_new_bitmap_flags(c.cap)
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
// #include <allegro5/allegro_windows.h>
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
func (d *Display) WindowHandle() unsafe.Pointer {
|
||||
return unsafe.Pointer(C.al_get_win_window_handle(d.display))
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
// #include <allegro5/allegro_primitives.h>
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
func DrawFilledRectangle(x1, y1, x2, y2 float32, c Color) {
|
||||
C.al_draw_filled_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color)
|
||||
}
|
||||
|
||||
func DrawFilledTriangle(x1, y1, x2, y2, x3, y3 float32, c Color) {
|
||||
C.al_draw_filled_triangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), C.float(x3), C.float(y3), c.color)
|
||||
}
|
||||
|
||||
func DrawLine(x1, y1, x2, y2 float32, c Color, thickness float32) {
|
||||
C.al_draw_line(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
|
||||
}
|
||||
|
||||
func DrawPolyline(vertices []float32, c Color, thickness float32) {
|
||||
C.al_draw_polyline((*C.float)(unsafe.Pointer(&vertices[0])), 8, C.int(len(vertices)>>1), C.ALLEGRO_LINE_JOIN_ROUND, C.ALLEGRO_LINE_CAP_ROUND, c.color, C.float(thickness), 1)
|
||||
}
|
||||
|
||||
func DrawRectangle(x1, y1, x2, y2 float32, c Color, thickness float32) {
|
||||
C.al_draw_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness))
|
||||
}
|
||||
|
||||
func DrawTriangle(x1, y1, x2, y2, x3, y3 float32, c Color, thickness float32) {
|
||||
C.al_draw_triangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), C.float(x3), C.float(y3), c.color, C.float(thickness))
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package allg5
|
||||
|
||||
// #include <allegro5/allegro.h>
|
||||
// #include <allegro5/allegro_font.h>
|
||||
// #include <allegro5/allegro_image.h>
|
||||
// #include <allegro5/allegro_primitives.h>
|
||||
// #include <allegro5/allegro_ttf.h>
|
||||
// bool init() {
|
||||
// return al_init();
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type InitConfig struct {
|
||||
Font bool
|
||||
Image bool
|
||||
Primitives bool
|
||||
Keyboard bool
|
||||
Mouse bool
|
||||
}
|
||||
|
||||
var InitAll = InitConfig{true, true, true, true, true}
|
||||
|
||||
// Init initializes the Allegro system
|
||||
func Init(config InitConfig) error {
|
||||
if !bool(C.init()) {
|
||||
return errors.New("failed to initialize Allegro")
|
||||
}
|
||||
if config.Font && !bool(C.al_init_font_addon()) {
|
||||
return errors.New("failed to initialize font addon")
|
||||
}
|
||||
if config.Font && !bool(C.al_init_ttf_addon()) {
|
||||
return errors.New("failed to initialize ttf addon")
|
||||
}
|
||||
if config.Image && !bool(C.al_init_image_addon()) {
|
||||
return errors.New("failed to initialize image addon")
|
||||
}
|
||||
if config.Primitives && !bool(C.al_init_primitives_addon()) {
|
||||
return errors.New("failed to initialize primitives addon")
|
||||
}
|
||||
if config.Keyboard && !bool(C.al_install_keyboard()) {
|
||||
return errors.New("failed to install keyboard")
|
||||
}
|
||||
if config.Mouse && !bool(C.al_install_mouse()) {
|
||||
return errors.New("failed to install mouse")
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package allg5ui
|
||||
|
||||
import (
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/zntg/allg5"
|
||||
)
|
||||
|
||||
type FontDefinition struct {
|
||||
@ -15,32 +15,35 @@ func NewFontDefinition(name string, size int) FontDefinition {
|
||||
}
|
||||
|
||||
type font struct {
|
||||
f *allg5.Font
|
||||
*allg5.Font
|
||||
}
|
||||
|
||||
func newFont(f *allg5.Font) *font {
|
||||
return &font{f}
|
||||
}
|
||||
|
||||
func (f *font) Destroy() {
|
||||
f.f.Destroy()
|
||||
func (f *font) Destroy() error {
|
||||
f.Font.Destroy()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *font) Height() float32 {
|
||||
if f == nil {
|
||||
return 0
|
||||
}
|
||||
return f.f.Height()
|
||||
return f.Font.Height()
|
||||
}
|
||||
|
||||
func (f *font) WidthOf(t string) float32 {
|
||||
return f.f.TextWidth(t)
|
||||
return f.TextWidth(t)
|
||||
}
|
||||
|
||||
func (f *font) Measure(t string) geom.RectangleF32 {
|
||||
if f == nil {
|
||||
return geom.RectangleF32{}
|
||||
}
|
||||
var x, y, w, h = f.f.TextDimensions(t)
|
||||
return geom.RectF32(x, y, x+w, y+h)
|
||||
// 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()
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package allg5ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"opslag.de/schobers/zntg/allg5"
|
||||
"opslag.de/schobers/zntg/ui"
|
||||
)
|
||||
|
||||
var _ ui.Image = &uiImage{}
|
||||
|
||||
type uiImage struct {
|
||||
bmp *allg5.Bitmap
|
||||
}
|
||||
|
||||
func (i *uiImage) Destroy() {
|
||||
i.bmp.Destroy()
|
||||
}
|
||||
|
||||
func (i *uiImage) Height() float32 {
|
||||
return float32(i.bmp.Height())
|
||||
}
|
||||
|
||||
func (i *uiImage) Image() image.Image {
|
||||
return i.bmp.Image()
|
||||
}
|
||||
|
||||
func (i *uiImage) Width() float32 {
|
||||
return float32(i.bmp.Width())
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
package allg5ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/zntg/allg5"
|
||||
"opslag.de/schobers/zntg/ui"
|
||||
)
|
||||
|
||||
var _ ui.Renderer = &Renderer{}
|
||||
|
||||
func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
||||
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
|
||||
}
|
||||
|
||||
eq, err := allg5.NewEventQueue()
|
||||
if err != nil {
|
||||
disp.Destroy()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := allg5.NewUserEventSource()
|
||||
eq.RegisterKeyboard()
|
||||
eq.RegisterMouse()
|
||||
eq.RegisterDisplay(disp)
|
||||
eq.RegisterUserEvents(user)
|
||||
|
||||
return &Renderer{disp, eq, map[string]*font{}, user, ui.MouseCursorDefault, ui.MouseCursorDefault}, nil
|
||||
}
|
||||
|
||||
// Renderer implements ui.Renderer using Allegro 5.
|
||||
type Renderer struct {
|
||||
disp *allg5.Display
|
||||
eq *allg5.EventQueue
|
||||
ft map[string]*font
|
||||
user *allg5.UserEventSource
|
||||
cursor ui.MouseCursor
|
||||
newCursor ui.MouseCursor
|
||||
}
|
||||
|
||||
// Renderer implementation (events)
|
||||
|
||||
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
||||
r.disp.Flip()
|
||||
|
||||
r.newCursor = ui.MouseCursorDefault
|
||||
var ev = eventWait(r.eq, wait)
|
||||
if ev == nil {
|
||||
return
|
||||
}
|
||||
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:
|
||||
t.Handle(&ui.KeyPressEvent{EventBase: eventBase(e), Key: key(e.KeyCode), Modifiers: keyModifiers(e.Modifiers), Character: e.UnicodeCharacter})
|
||||
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)})
|
||||
}
|
||||
ev = r.eq.Get()
|
||||
}
|
||||
|
||||
if r.newCursor != r.cursor {
|
||||
r.cursor = r.newCursor
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Refresh() {
|
||||
r.user.EmitEvent()
|
||||
}
|
||||
|
||||
// Renderer implementation (lifetime)
|
||||
|
||||
func (r *Renderer) Destroy() error {
|
||||
r.user.Destroy()
|
||||
r.eq.Destroy()
|
||||
for _, f := range r.ft {
|
||||
f.Destroy()
|
||||
}
|
||||
r.ft = nil
|
||||
r.disp.Destroy()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Renderer implementation (drawing)
|
||||
|
||||
func (r *Renderer) Clear(c color.Color) {
|
||||
allg5.ClearToColor(newColor(c))
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateImage(im image.Image) (ui.Image, error) {
|
||||
bmp, err := allg5.NewBitmapFromImage(im, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &uiImage{bmp}, nil
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateImagePath(path string) (ui.Image, error) {
|
||||
bmp, err := allg5.LoadBitmap(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &uiImage{bmp}, nil
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateImageSize(w, h float32) (ui.Image, error) {
|
||||
bmp, err := allg5.NewVideoBitmap(int(w), int(h))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &uiImage{bmp}, nil
|
||||
}
|
||||
|
||||
func (r *Renderer) DefaultTarget() ui.Image {
|
||||
return &uiImage{r.disp.Target()}
|
||||
}
|
||||
|
||||
func (r *Renderer) Display() *allg5.Display { return r.disp }
|
||||
|
||||
func (r *Renderer) DrawImage(im ui.Image, p geom.PointF32) {
|
||||
bmp := r.mustGetBitmap(im)
|
||||
x, y := snap(p)
|
||||
bmp.Draw(x, y)
|
||||
}
|
||||
|
||||
func (r *Renderer) DrawImageOptions(im ui.Image, p geom.PointF32, opts ui.DrawOptions) {
|
||||
bmp := r.mustGetBitmap(im)
|
||||
var o allg5.DrawOptions
|
||||
if opts.Tint != nil {
|
||||
tint := newColor(opts.Tint)
|
||||
o.Tint = &tint
|
||||
}
|
||||
if opts.Scale != nil {
|
||||
o.Scale = &allg5.Scale{Horizontal: opts.Scale.X, Vertical: opts.Scale.Y}
|
||||
}
|
||||
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) Font(name string) ui.Font {
|
||||
return r.ft[name]
|
||||
}
|
||||
|
||||
func (r *Renderer) mustGetBitmap(im ui.Image) *allg5.Bitmap {
|
||||
m, ok := im.(*uiImage)
|
||||
if !ok {
|
||||
panic("image must be created on same renderer")
|
||||
}
|
||||
return m.bmp
|
||||
}
|
||||
|
||||
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) RegisterFont(path, name string, size int) error {
|
||||
var f, err = allg5.LoadTTFFont(path, int(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var prev = r.ft[name]
|
||||
if prev != nil {
|
||||
prev.Destroy()
|
||||
}
|
||||
r.ft[name] = newFont(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) RegisterFonts(path string, fonts ...FontDefinition) error {
|
||||
for _, f := range fonts {
|
||||
err := r.RegisterFont(path, f.Name, f.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) RenderTo(im ui.Image) {
|
||||
bmp := r.mustGetBitmap(im)
|
||||
bmp.SetAsTarget()
|
||||
}
|
||||
|
||||
func (r *Renderer) RenderToDisplay() {
|
||||
r.disp.SetAsTarget()
|
||||
}
|
||||
|
||||
func (r *Renderer) Size() geom.PointF32 {
|
||||
return geom.PtF32(float32(r.disp.Width()), float32(r.disp.Height()))
|
||||
}
|
||||
|
||||
func (r *Renderer) SetIcon(im ui.Image) {
|
||||
bmp := r.mustGetBitmap(im)
|
||||
r.disp.SetIcon(bmp)
|
||||
}
|
||||
|
||||
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
|
||||
r.newCursor = c
|
||||
}
|
||||
|
||||
func (r *Renderer) SetWindowTitle(t string) {
|
||||
r.disp.SetWindowTitle(t)
|
||||
}
|
||||
|
||||
func (r *Renderer) Target() ui.Image {
|
||||
return &uiImage{allg5.CurrentTarget()}
|
||||
}
|
||||
|
||||
func (r *Renderer) text(p geom.PointF32, font string, c color.Color, t string, align allg5.HorizontalAlignment) {
|
||||
var f = r.ft[font]
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
x, y := snap(p)
|
||||
f.f.Draw(x, y, newColor(c), align, t)
|
||||
}
|
||||
|
||||
func (r *Renderer) Text(p geom.PointF32, font string, c color.Color, t string) {
|
||||
r.text(p, font, c, t, allg5.AlignLeft)
|
||||
}
|
||||
|
||||
func (r *Renderer) TextAlign(p geom.PointF32, font string, 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(p, font, c, t, alignment)
|
||||
}
|
||||
|
||||
// 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 key(key allg5.Key) ui.Key {
|
||||
switch key {
|
||||
case allg5.KeyBackspace:
|
||||
return ui.KeyBackspace
|
||||
case allg5.KeyDelete:
|
||||
return ui.KeyDelete
|
||||
case allg5.KeyDown:
|
||||
return ui.KeyDown
|
||||
case allg5.KeyEnd:
|
||||
return ui.KeyEnd
|
||||
case allg5.KeyEscape:
|
||||
return ui.KeyEscape
|
||||
case allg5.KeyHome:
|
||||
return ui.KeyHome
|
||||
case allg5.KeyLeft:
|
||||
return ui.KeyLeft
|
||||
case allg5.KeyRight:
|
||||
return ui.KeyRight
|
||||
case allg5.KeyUp:
|
||||
return ui.KeyUp
|
||||
}
|
||||
return ui.KeyNone
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
39
ui/buffer.go
39
ui/buffer.go
@ -1,42 +1,59 @@
|
||||
package ui
|
||||
|
||||
import "opslag.de/schobers/geom"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type RenderBufferFn func(ctx Context, size geom.PointF32)
|
||||
|
||||
type Buffer struct {
|
||||
im Image
|
||||
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.im != nil {
|
||||
if b.texture != nil {
|
||||
if size == b.size {
|
||||
return nil
|
||||
}
|
||||
b.im.Destroy()
|
||||
b.im = nil
|
||||
b.texture.Destroy()
|
||||
b.texture = nil
|
||||
b.size = geom.ZeroPtF32
|
||||
}
|
||||
im, err := ctx.Renderer().CreateImageSize(size.X, size.Y)
|
||||
texture, err := ctx.Renderer().CreateTextureTarget(size.X, size.Y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.im = im
|
||||
b.texture = texture
|
||||
b.size = size
|
||||
return nil
|
||||
return ErrNewBufferSize
|
||||
}
|
||||
|
||||
func (b *Buffer) Render(ctx Context, pos geom.PointF32, fn RenderBufferFn) {
|
||||
if b.im == nil {
|
||||
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.im)
|
||||
renderer.RenderTo(b.texture)
|
||||
fn(ctx, b.size)
|
||||
renderer.RenderTo(currTarget)
|
||||
renderer.DrawImage(b.im, pos)
|
||||
}
|
||||
|
||||
func (b *Buffer) RenderToDisplay(ctx Context, pos geom.PointF32) {
|
||||
if b.texture == nil {
|
||||
return
|
||||
}
|
||||
ctx.Renderer().DrawTexturePoint(b.texture, pos)
|
||||
}
|
||||
|
||||
type BufferControl struct {
|
||||
|
164
ui/button.go
164
ui/button.go
@ -9,11 +9,15 @@ import (
|
||||
type Button struct {
|
||||
ControlBase
|
||||
|
||||
DisabledColor color.Color
|
||||
HoverColor color.Color
|
||||
Icon Image
|
||||
IconScale float32
|
||||
|
||||
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
|
||||
@ -25,10 +29,10 @@ const (
|
||||
ButtonTypeText
|
||||
)
|
||||
|
||||
func BuildButton(text string, fn func(b *Button)) *Button { return BuildIconButton(nil, text, fn) }
|
||||
func BuildButton(text string, fn func(b *Button)) *Button { return BuildIconButton("", text, fn) }
|
||||
|
||||
func BuildIconButton(i Image, text string, fn func(b *Button)) *Button {
|
||||
var b = &Button{Text: text, Icon: i}
|
||||
func BuildIconButton(icon, text string, fn func(b *Button)) *Button {
|
||||
var b = &Button{Text: text, Icon: icon}
|
||||
if fn != nil {
|
||||
fn(b)
|
||||
}
|
||||
@ -36,34 +40,97 @@ func BuildIconButton(i Image, text string, fn func(b *Button)) *Button {
|
||||
}
|
||||
|
||||
func (b *Button) desiredSize(ctx Context) geom.PointF32 {
|
||||
var pad = ctx.Style().Dimensions.TextPadding
|
||||
var font = ctx.Renderer().Font(b.FontName(ctx))
|
||||
var pad = b.ActualTextPadding(ctx)
|
||||
var font = b.ActualFont(ctx)
|
||||
var w, h float32 = 0, font.Height()
|
||||
if len(b.Text) != 0 {
|
||||
w += pad + font.WidthOf(b.Text)
|
||||
|
||||
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 b.Icon != nil && b.Icon.Height() > 0 {
|
||||
iconW := b.scale(b.Icon.Width() * h / b.Icon.Height())
|
||||
w += pad + iconW
|
||||
}
|
||||
if w == 0 {
|
||||
return geom.ZeroPtF32
|
||||
}
|
||||
return geom.PtF32(w+pad, pad+h+pad)
|
||||
return geom.PtF32(w, pad.Top+h+pad.Bottom)
|
||||
}
|
||||
|
||||
func (b *Button) DesiredSize(ctx Context) geom.PointF32 {
|
||||
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) {
|
||||
b.ControlBase.Handle(ctx, e)
|
||||
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
|
||||
@ -77,7 +144,6 @@ func (b *Button) fillColor(p *Palette) color.Color {
|
||||
switch b.Type {
|
||||
case ButtonTypeContained:
|
||||
return p.PrimaryLight
|
||||
case ButtonTypeIcon:
|
||||
default:
|
||||
return p.PrimaryHighlight
|
||||
}
|
||||
@ -85,33 +151,41 @@ func (b *Button) fillColor(p *Palette) color.Color {
|
||||
switch b.Type {
|
||||
case ButtonTypeContained:
|
||||
return p.Primary
|
||||
case ButtonTypeIcon:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Button) scale(f float32) float32 {
|
||||
if b.IconScale == 0 {
|
||||
return f
|
||||
func (b *Button) fontColor(c color.Color) color.Color {
|
||||
if b.Font.Color == nil {
|
||||
return c
|
||||
}
|
||||
return b.IconScale * f
|
||||
return b.Font.Color
|
||||
}
|
||||
|
||||
func (b *Button) textColor(p *Palette) color.Color {
|
||||
if b.Font.Color != nil {
|
||||
return b.Font.Color
|
||||
if b.Disabled {
|
||||
if b.Background != nil {
|
||||
return p.TextOnDisabled
|
||||
}
|
||||
switch b.Type {
|
||||
case ButtonTypeContained:
|
||||
return p.TextOnPrimary
|
||||
return p.TextOnDisabled
|
||||
}
|
||||
return b.disabledColor(p)
|
||||
}
|
||||
switch b.Type {
|
||||
case ButtonTypeContained:
|
||||
return b.fontColor(p.TextOnPrimary)
|
||||
case ButtonTypeIcon:
|
||||
if b.over {
|
||||
return p.Primary
|
||||
if b.HoverColor != nil {
|
||||
return b.HoverColor
|
||||
}
|
||||
return p.Text
|
||||
return b.fontColor(p.Primary)
|
||||
}
|
||||
return b.fontColor(p.Text)
|
||||
default:
|
||||
return p.Primary
|
||||
return b.fontColor(p.Primary)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,23 +203,33 @@ func (b *Button) Render(ctx Context) {
|
||||
bounds.Min.X += .5 * deltaX
|
||||
bounds.Min.Y += .5 * deltaY
|
||||
|
||||
var pad = style.Dimensions.TextPadding
|
||||
bounds = bounds.Inset(pad)
|
||||
pad := b.ActualTextPadding(ctx)
|
||||
bounds = pad.InsetRect(bounds)
|
||||
boundsH := bounds.Dy()
|
||||
pos := bounds.Min
|
||||
if b.Icon != nil && b.Icon.Height() > 0 {
|
||||
icon, _ := ctx.Images().ScaledHeight(b.Icon, b.scale(bounds.Dy()))
|
||||
if icon != nil {
|
||||
ctx.Renderer().DrawImageOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: textColor})
|
||||
pos.X += icon.Width() + pad
|
||||
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 {
|
||||
var fontName = b.FontName(ctx)
|
||||
var font = ctx.Renderer().Font(fontName)
|
||||
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-font.Height())), fontName, textColor, b.Text)
|
||||
font := b.ActualFont(ctx)
|
||||
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), textColor, b.Text)
|
||||
}
|
||||
|
||||
if b.Type == ButtonTypeOutlined {
|
||||
b.RenderOutline(ctx)
|
||||
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
|
||||
}
|
116
ui/checkbox.go
116
ui/checkbox.go
@ -4,12 +4,10 @@ import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type SelectedChangedFn func(selected bool)
|
||||
|
||||
type Checkbox struct {
|
||||
ControlBase
|
||||
|
||||
onSelectedChanged SelectedChangedFn
|
||||
selectedChanged Events
|
||||
|
||||
Selected bool
|
||||
Text string
|
||||
@ -24,119 +22,113 @@ func BuildCheckbox(text string, fn func(c *Checkbox)) *Checkbox {
|
||||
}
|
||||
|
||||
func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 {
|
||||
var pad = ctx.Style().Dimensions.TextPadding
|
||||
var font = ctx.Renderer().Font(c.FontName(ctx))
|
||||
pad := c.ActualTextPadding(ctx)
|
||||
font := c.ActualFont(ctx)
|
||||
var w, h float32 = 0, font.Height()
|
||||
if len(c.Text) != 0 {
|
||||
w += pad + font.WidthOf(c.Text)
|
||||
w += pad.Left + font.WidthOf(c.Text)
|
||||
}
|
||||
icon, _ := ctx.Images().ScaledHeight(c.getOrCreateNormalIcon(ctx), h)
|
||||
w += pad + icon.Width()
|
||||
return geom.PtF32(w+pad, pad+h+pad)
|
||||
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) Image {
|
||||
func (c *Checkbox) icon(ctx Context) Texture {
|
||||
if c.Selected {
|
||||
return GetOrCreateIcon(ctx, "ui-default-checkbox-selected", c.selectedIcon)
|
||||
} else if c.over {
|
||||
} 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) Image {
|
||||
func (c *Checkbox) getOrCreateNormalIcon(ctx Context) Texture {
|
||||
return GetOrCreateIcon(ctx, "ui-default-checkbox", c.normalIcon)
|
||||
}
|
||||
|
||||
func (c *Checkbox) iconBorder() geom.PolygonF32 {
|
||||
return geom.PolF32(
|
||||
var checkBoxIconBorder = geom.PolF32(
|
||||
geom.PtF32(48, 80),
|
||||
geom.PtF32(400, 80),
|
||||
geom.PtF32(400, 432),
|
||||
geom.PtF32(48, 432),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Checkbox) checkMark() geom.PointsF32 {
|
||||
return geom.PointsF32{
|
||||
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) hoverIcon() IconPixelTestFn {
|
||||
border := c.iconBorder()
|
||||
check := c.checkMark()
|
||||
return func(pt geom.PointF32) bool {
|
||||
return (pt.DistanceToPolygon(border) < 48 && !pt.InPolygon(border)) || pt.DistanceToLines(check) < 24
|
||||
}
|
||||
func (c *Checkbox) normalIcon(pt geom.PointF32) bool {
|
||||
return pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)
|
||||
}
|
||||
|
||||
func (c *Checkbox) normalIcon() IconPixelTestFn {
|
||||
border := c.iconBorder()
|
||||
return func(pt geom.PointF32) bool {
|
||||
return pt.DistanceToPolygon(border) < 48 && !pt.InPolygon(border)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checkbox) selectedIcon() IconPixelTestFn {
|
||||
border := c.iconBorder()
|
||||
check := c.checkMark()
|
||||
return func(pt geom.PointF32) bool {
|
||||
if pt.DistanceToPolygon(border) < 48 || pt.InPolygon(border) {
|
||||
return pt.DistanceToLines(check) > 24
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Checkbox) DesiredSize(ctx Context) geom.PointF32 { return c.desiredSize(ctx) }
|
||||
|
||||
func (c *Checkbox) Handle(ctx Context, e Event) {
|
||||
switch e := e.(type) {
|
||||
case *MouseButtonDownEvent:
|
||||
if e.Button == MouseButtonLeft && c.over {
|
||||
c.Selected = !c.Selected
|
||||
onSelectedChanged := c.onSelectedChanged
|
||||
if onSelectedChanged != nil {
|
||||
onSelectedChanged(c.Selected)
|
||||
return c.selectedChanged.Notify(ctx, c.Selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.ControlBase.Handle(ctx, e)
|
||||
if c.over {
|
||||
ctx.Renderer().SetMouseCursor(MouseCursorPointer)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Checkbox) OnSelectedChanged(fn SelectedChangedFn) {
|
||||
c.onSelectedChanged = fn
|
||||
}
|
||||
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.FontColor(ctx)
|
||||
fore := c.TextColor(ctx)
|
||||
bounds := c.bounds
|
||||
|
||||
var pad = style.Dimensions.TextPadding
|
||||
bounds = bounds.Inset(pad)
|
||||
pad := c.ActualTextPadding(ctx)
|
||||
bounds = pad.InsetRect(bounds)
|
||||
boundsH := bounds.Dy()
|
||||
pos := bounds.Min
|
||||
icon, _ := ctx.Images().ScaledHeight(c.icon(ctx), bounds.Dy())
|
||||
icon := c.icon(ctx)
|
||||
if icon != nil {
|
||||
iconColor := fore
|
||||
if c.Selected && c.Font.Color == nil {
|
||||
iconColor = palette.Primary
|
||||
if c.Selected {
|
||||
iconColor = c.FontColor(ctx, palette.Primary)
|
||||
}
|
||||
ctx.Renderer().DrawImageOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: iconColor})
|
||||
pos.X += icon.Width() + pad
|
||||
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 {
|
||||
var fontName = c.FontName(ctx)
|
||||
var font = ctx.Renderer().Font(fontName)
|
||||
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-font.Height())), fontName, fore, c.Text)
|
||||
font := c.ActualFont(ctx)
|
||||
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fore, c.Text)
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ type Clipboard interface {
|
||||
|
||||
var DefaultClipboard Clipboard = &clipboard{}
|
||||
|
||||
func SetClipboard(c Clipboard) { DefaultClipboard = c }
|
||||
|
||||
type clipboard struct {
|
||||
value string
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ func BuildContainerBase(controls ...Control) ContainerBase {
|
||||
|
||||
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) {
|
||||
@ -26,11 +29,38 @@ func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset ge
|
||||
c.ControlBase.Arrange(ctx, bounds, offset, parent)
|
||||
}
|
||||
|
||||
func (c *ContainerBase) Handle(ctx Context, e Event) {
|
||||
c.ControlBase.Handle(ctx, e)
|
||||
for _, child := range c.Children {
|
||||
child.Handle(ctx, e)
|
||||
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) {
|
||||
|
@ -1,12 +1,22 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type Context interface {
|
||||
Animate()
|
||||
Fonts() *Fonts
|
||||
HasQuit() bool
|
||||
Images() *Images
|
||||
KeyModifiers() KeyModifier
|
||||
MousePosition() geom.PointF32
|
||||
Overlays() *Overlays
|
||||
Quit()
|
||||
Renderer() Renderer
|
||||
Resources() Resources
|
||||
ShowTooltip(t string)
|
||||
Style() *Style
|
||||
Textures() *Textures
|
||||
}
|
||||
|
||||
var _ Context = &context{}
|
||||
@ -15,14 +25,41 @@ var _ EventTarget = &context{}
|
||||
type context struct {
|
||||
animate bool
|
||||
quit chan struct{}
|
||||
r Renderer
|
||||
renderer Renderer
|
||||
view Control
|
||||
ims *Images
|
||||
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:
|
||||
@ -32,7 +69,21 @@ func (c *context) HasQuit() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Images() *Images { return c.ims }
|
||||
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() {
|
||||
@ -40,16 +91,21 @@ func (c *context) Quit() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Renderer() Renderer { return c.r }
|
||||
|
||||
func (c *context) Style() *Style { return c.style }
|
||||
func (c *context) Textures() *Textures { return c.textures }
|
||||
|
||||
// Handle implement EventTarget
|
||||
|
||||
func (c *context) Handle(e Event) {
|
||||
switch e.(type) {
|
||||
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.view.Handle(c, e)
|
||||
c.overlays.Handle(c, e)
|
||||
}
|
||||
|
@ -6,21 +6,44 @@ import (
|
||||
|
||||
type Control interface {
|
||||
Arrange(Context, geom.RectangleF32, geom.PointF32, Control)
|
||||
DesiredSize(Context) geom.PointF32
|
||||
Handle(Context, Event)
|
||||
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
|
||||
OnClick(ClickFn)
|
||||
OnDragStart(DragStartFn)
|
||||
OnDragMove(DragMoveFn)
|
||||
OnDragEnd(DragEndFn)
|
||||
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
|
||||
|
||||
|
@ -6,31 +6,109 @@ import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type ClickFn func(pos geom.PointF32, btn MouseButton)
|
||||
type DragEndFn func(start, end geom.PointF32)
|
||||
type DragMoveFn func(start, move geom.PointF32)
|
||||
type DragStartFn func(start geom.PointF32)
|
||||
type MouseEnterFn func()
|
||||
type MouseLeaveFn func()
|
||||
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
|
||||
dragStart *geom.PointF32
|
||||
drag Dragable
|
||||
over bool
|
||||
pressed bool
|
||||
|
||||
onClick ClickFn
|
||||
onDragEnd DragEndFn
|
||||
onDragMove DragMoveFn
|
||||
onDragStart DragStartFn
|
||||
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) {
|
||||
@ -41,9 +119,33 @@ func (c *ControlBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom
|
||||
|
||||
func (c *ControlBase) Bounds() geom.RectangleF32 { return c.bounds }
|
||||
|
||||
func (c *ControlBase) DesiredSize(Context) geom.PointF32 { return geom.ZeroPtF32 }
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
func (c *ControlBase) Handle(ctx Context, e Event) {
|
||||
var over = func(e MouseEvent) bool {
|
||||
pos := e.Pos()
|
||||
if !c.IsInBounds(pos) {
|
||||
@ -62,19 +164,14 @@ func (c *ControlBase) Handle(ctx Context, e Event) {
|
||||
case *MouseMoveEvent:
|
||||
c.over = over(e.MouseEvent)
|
||||
if c.pressed {
|
||||
if c.dragStart == nil {
|
||||
var start = c.ToControlPosition(e.Pos())
|
||||
c.dragStart = &start
|
||||
if c.onDragStart != nil {
|
||||
c.onDragStart(start)
|
||||
}
|
||||
} else {
|
||||
var start = *c.dragStart
|
||||
if start, ok := c.drag.IsDragging(); ok {
|
||||
var move = c.ToControlPosition(e.Pos())
|
||||
if c.onDragMove != nil {
|
||||
c.onDragMove(start, move)
|
||||
}
|
||||
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
|
||||
@ -82,31 +179,37 @@ func (c *ControlBase) Handle(ctx Context, e Event) {
|
||||
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 {
|
||||
if c.dragStart != nil {
|
||||
var start = *c.dragStart
|
||||
var end = c.ToControlPosition(e.Pos())
|
||||
c.dragStart = nil
|
||||
if c.onDragEnd != nil {
|
||||
c.onDragEnd(start, end)
|
||||
}
|
||||
}
|
||||
if c.pressed {
|
||||
if c.onClick != nil {
|
||||
c.onClick(c.ToControlPosition(e.Pos()), e.Button)
|
||||
}
|
||||
}
|
||||
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) FontColor(ctx Context) color.Color {
|
||||
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 = ctx.Style().Palette.Text
|
||||
text = color
|
||||
}
|
||||
return text
|
||||
}
|
||||
@ -119,6 +222,8 @@ func (c *ControlBase) FontName(ctx Context) string {
|
||||
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 {
|
||||
@ -132,24 +237,31 @@ func (c *ControlBase) IsInBounds(p geom.PointF32) bool {
|
||||
|
||||
func (c *ControlBase) IsOver() bool { return c.over }
|
||||
|
||||
func (c *ControlBase) Parent() Control { return c.parent }
|
||||
|
||||
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) OnClick(fn ClickFn) { c.onClick = fn }
|
||||
|
||||
func (c *ControlBase) OnDragStart(fn DragStartFn) {
|
||||
c.onDragStart = fn
|
||||
func (c *ControlBase) OutlineColor(ctx Context) color.Color {
|
||||
return c.FontColor(ctx, ctx.Style().Palette.Primary)
|
||||
}
|
||||
|
||||
func (c *ControlBase) OnDragMove(fn DragMoveFn) {
|
||||
c.onDragMove = fn
|
||||
}
|
||||
|
||||
func (c *ControlBase) OnDragEnd(fn DragEndFn) { c.onDragEnd = fn }
|
||||
|
||||
func (c *ControlBase) Render(Context) {}
|
||||
|
||||
func (c *ControlBase) RenderBackground(ctx Context) {
|
||||
@ -159,13 +271,36 @@ func (c *ControlBase) RenderBackground(ctx Context) {
|
||||
}
|
||||
|
||||
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
|
||||
color := style.Palette.Primary
|
||||
if c.Font.Color != nil {
|
||||
color = c.Font.Color
|
||||
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)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/minio/highwayhash"
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type desiredSizeCache struct {
|
||||
sum [32]byte
|
||||
size geom.PointF32
|
||||
}
|
||||
|
||||
func (c *desiredSizeCache) Update(ctx Context, data string, calcFn func(Context) geom.PointF32) geom.PointF32 {
|
||||
var key = [32]byte{}
|
||||
sum := highwayhash.Sum([]byte(data), key[:])
|
||||
if c.sum != sum {
|
||||
c.size = calcFn(ctx)
|
||||
c.sum = sum
|
||||
}
|
||||
return c.size
|
||||
}
|
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})
|
||||
}
|
@ -7,6 +7,14 @@ import (
|
||||
)
|
||||
|
||||
type DrawOptions struct {
|
||||
Source *geom.RectangleF32
|
||||
Tint color.Color
|
||||
Scale *geom.PointF32
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
68
ui/event.go
68
ui/event.go
@ -6,6 +6,44 @@ 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
|
||||
@ -23,21 +61,6 @@ func (e *EventBase) Stamp() float64 {
|
||||
return e.StampInSeconds
|
||||
}
|
||||
|
||||
type Key int
|
||||
|
||||
const (
|
||||
KeyNone Key = iota
|
||||
KeyBackspace
|
||||
KeyDelete
|
||||
KeyDown
|
||||
KeyEnd
|
||||
KeyEscape
|
||||
KeyHome
|
||||
KeyLeft
|
||||
KeyRight
|
||||
KeyUp
|
||||
)
|
||||
|
||||
type KeyModifier int
|
||||
|
||||
const (
|
||||
@ -45,13 +68,19 @@ const (
|
||||
KeyModifierShift = 1 << iota
|
||||
KeyModifierControl
|
||||
KeyModifierAlt
|
||||
KeyModifierOSCommand
|
||||
)
|
||||
|
||||
type KeyPressEvent struct {
|
||||
type KeyDownEvent struct {
|
||||
EventBase
|
||||
Key Key
|
||||
Modifiers KeyModifier
|
||||
}
|
||||
|
||||
type KeyUpEvent struct {
|
||||
EventBase
|
||||
Key Key
|
||||
Modifiers KeyModifier
|
||||
Character rune
|
||||
}
|
||||
|
||||
type MouseButton int
|
||||
@ -97,3 +126,8 @@ type MouseMoveEvent struct {
|
||||
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) })
|
||||
}
|
@ -4,30 +4,28 @@ import (
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
_ "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/allg5"
|
||||
"opslag.de/schobers/zntg/ui"
|
||||
"opslag.de/schobers/zntg/ui/allg5ui"
|
||||
)
|
||||
|
||||
func run() error {
|
||||
var render, err = allg5ui.NewRenderer(800, 600, allg5.NewDisplayOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
type basic struct {
|
||||
ui.StackPanel
|
||||
}
|
||||
defer render.Destroy()
|
||||
|
||||
err = render.RegisterFont("../resources/font/OpenSans-Regular.ttf", "default", 14)
|
||||
func (b *basic) Init(ctx ui.Context) error {
|
||||
_, err := ctx.Fonts().CreateFontPath("default", "../resources/font/OpenSans-Regular.ttf", 14)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plus, err := render.CreateImagePath("../resources/images/plus.png")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer plus.Destroy()
|
||||
|
||||
_, 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()
|
||||
@ -38,17 +36,35 @@ func run() error {
|
||||
})
|
||||
}
|
||||
|
||||
var style = ui.DefaultStyle()
|
||||
var view = ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
|
||||
p.Background = color.White
|
||||
p.Children = []ui.Control{
|
||||
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),
|
||||
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) {
|
||||
@ -57,23 +73,56 @@ func run() error {
|
||||
ui.BuildCheckbox("Check me!", nil),
|
||||
}
|
||||
}),
|
||||
ui.Stretch(&ui.Label{Text: "Content"}),
|
||||
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.OnClick(func(ctx ui.Context, _ ui.Control, _ geom.PointF32, _ ui.MouseButton) {
|
||||
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
|
||||
}
|
||||
|
||||
return ui.RunWait(render, style, view, true)
|
||||
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() {
|
||||
|
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)
|
||||
}
|
||||
}
|
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
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type Font interface {
|
||||
Destroy() error
|
||||
Height() float32
|
||||
Measure(t string) geom.RectangleF32
|
||||
WidthOf(t string) float32
|
||||
|
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)
|
||||
}
|
91
ui/icon.go
91
ui/icon.go
@ -7,23 +7,54 @@ import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type IconPixelTestFn func(geom.PointF32) bool
|
||||
type AlphaPixelImageSource struct {
|
||||
ImageAlphaPixelTestFn
|
||||
|
||||
func IconSize() geom.Point {
|
||||
return geom.Pt(448, 512)
|
||||
Size geom.Point
|
||||
}
|
||||
|
||||
func CreateIcon(ctx Context, test IconPixelTestFn) Image {
|
||||
icon := DrawIcon(test)
|
||||
im, err := ctx.Renderer().CreateImage(icon)
|
||||
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 im
|
||||
return texture
|
||||
}
|
||||
|
||||
func DrawIcon(test IconPixelTestFn) image.Image {
|
||||
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++ {
|
||||
@ -37,16 +68,42 @@ func DrawIcon(test IconPixelTestFn) image.Image {
|
||||
return icon
|
||||
}
|
||||
|
||||
func GetOrCreateIcon(ctx Context, name string, testFactory func() IconPixelTestFn) Image {
|
||||
im := ctx.Images().Image(name)
|
||||
if im != nil {
|
||||
return im
|
||||
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})
|
||||
}
|
||||
test := testFactory()
|
||||
im = CreateIcon(ctx, test)
|
||||
if im == nil {
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
ctx.Images().AddImage(name, im)
|
||||
return im
|
||||
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
|
||||
}
|
||||
|
10
ui/image.go
10
ui/image.go
@ -1,10 +0,0 @@
|
||||
package ui
|
||||
|
||||
import "image"
|
||||
|
||||
type Image interface {
|
||||
Destroy()
|
||||
Height() float32
|
||||
Image() image.Image
|
||||
Width() float32
|
||||
}
|
123
ui/images.go
123
ui/images.go
@ -1,123 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/nfnt/resize"
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type CreateImageFn func() (image.Image, error)
|
||||
|
||||
func ScaleImage(render Renderer, im Image, scale float32) Image {
|
||||
w := uint(im.Width() * scale)
|
||||
if w == 0 {
|
||||
return nil
|
||||
}
|
||||
scaled := resize.Resize(w, 0, im.Image(), resize.Bilinear)
|
||||
res, err := render.CreateImage(scaled)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type Images struct {
|
||||
render Renderer
|
||||
ims map[string]Image
|
||||
scaled map[Image]ScaledImages
|
||||
}
|
||||
|
||||
func NewImages(render Renderer) *Images {
|
||||
return &Images{render, map[string]Image{}, map[Image]ScaledImages{}}
|
||||
}
|
||||
|
||||
func (i *Images) AddImage(name string, im Image) {
|
||||
curr := i.ims[name]
|
||||
if curr != nil {
|
||||
curr.Destroy()
|
||||
}
|
||||
i.ims[name] = im
|
||||
}
|
||||
|
||||
func (i *Images) AddImageFn(name string, create CreateImageFn) error {
|
||||
im, err := create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return i.AddImageNative(name, im)
|
||||
}
|
||||
|
||||
func (i *Images) AddImageNative(name string, im image.Image) error {
|
||||
m, err := i.render.CreateImage(im)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.AddImage(name, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Images) Destroy() {
|
||||
for _, im := range i.ims {
|
||||
im.Destroy()
|
||||
}
|
||||
i.ims = nil
|
||||
for _, ims := range i.scaled {
|
||||
ims.Destroy()
|
||||
}
|
||||
i.scaled = nil
|
||||
}
|
||||
|
||||
func (i *Images) Image(name string) Image {
|
||||
im, ok := i.ims[name]
|
||||
if ok {
|
||||
return im
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Images) Scaled(im Image, scale float32) Image {
|
||||
if scale <= 0 {
|
||||
return nil
|
||||
}
|
||||
if scale == 1 {
|
||||
return im
|
||||
}
|
||||
ims := i.scaled[im]
|
||||
if ims == nil {
|
||||
ims = make(ScaledImages)
|
||||
} else {
|
||||
scaled := ims[scale]
|
||||
if scaled != nil {
|
||||
return scaled
|
||||
}
|
||||
}
|
||||
scaled := ScaleImage(i.render, im, scale)
|
||||
ims[scale] = scaled
|
||||
i.scaled[im] = ims
|
||||
return scaled
|
||||
}
|
||||
|
||||
func (i *Images) ScaledHeight(im Image, height float32) (Image, float32) {
|
||||
scale := height / im.Height()
|
||||
if geom.IsNaN32(scale) {
|
||||
return nil, 0
|
||||
}
|
||||
return i.Scaled(im, scale), scale
|
||||
}
|
||||
|
||||
func (i *Images) ScaledByName(name string, scale float32) Image {
|
||||
im := i.Image(name)
|
||||
if im == nil {
|
||||
return nil
|
||||
}
|
||||
return i.Scaled(im, scale)
|
||||
}
|
||||
|
||||
type ScaledImages map[float32]Image
|
||||
|
||||
func (i ScaledImages) Destroy() {
|
||||
for _, im := range i {
|
||||
im.Destroy()
|
||||
}
|
||||
}
|
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
|
||||
}
|
65
ui/label.go
65
ui/label.go
@ -1,15 +1,26 @@
|
||||
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
|
||||
|
||||
size desiredSizeCache
|
||||
desired CachedValue
|
||||
}
|
||||
|
||||
func BuildLabel(text string, fn func(*Label)) *Label {
|
||||
@ -20,21 +31,47 @@ func BuildLabel(text string, fn func(*Label)) *Label {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Label) DesiredSize(ctx Context) geom.PointF32 {
|
||||
var fontName = l.FontName(ctx)
|
||||
return l.size.Update(ctx, fontName+l.Text, func(ctx Context) geom.PointF32 {
|
||||
var font = ctx.Renderer().Font(fontName)
|
||||
var width = font.WidthOf(l.Text)
|
||||
var height = font.Height()
|
||||
var pad = ctx.Style().Dimensions.TextPadding
|
||||
return geom.PtF32(width+pad*2, height+pad*2)
|
||||
})
|
||||
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)
|
||||
var c = l.FontColor(ctx)
|
||||
var f = l.FontName(ctx)
|
||||
var pad = ctx.Style().Dimensions.TextPadding
|
||||
ctx.Renderer().TextAlign(l.bounds.Min.Add(geom.PtF32(pad, pad)), f, c, l.Text, l.TextAlignment)
|
||||
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)
|
||||
}
|
||||
|
45
ui/length.go
45
ui/length.go
@ -24,9 +24,54 @@ 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
|
||||
}
|
||||
|
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
|
||||
}
|
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 }
|
@ -11,6 +11,9 @@ type overflow struct {
|
||||
|
||||
Background color.Color
|
||||
|
||||
ClipHorizontal bool
|
||||
ClipVertical bool
|
||||
|
||||
barWidth float32
|
||||
desired geom.PointF32
|
||||
bounds geom.RectangleF32
|
||||
@ -23,15 +26,33 @@ type overflow struct {
|
||||
ver *Scrollbar
|
||||
}
|
||||
|
||||
func Overflow(content Control) Control {
|
||||
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) Control {
|
||||
var o = &overflow{Proxy: Proxy{Content: content}, Background: back}
|
||||
o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {})
|
||||
o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {})
|
||||
return o
|
||||
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) {
|
||||
@ -47,6 +68,8 @@ func (o *overflow) shouldScroll(bounds geom.RectangleF32) (hor bool, ver bool) {
|
||||
if hor && !ver {
|
||||
ver = scroll(size.Y+o.barWidth, bounds.Dy())
|
||||
}
|
||||
hor = hor && !o.ClipHorizontal
|
||||
ver = ver && !o.ClipVertical
|
||||
return
|
||||
}
|
||||
|
||||
@ -62,7 +85,7 @@ func (o *overflow) doOnVisibleBars(fn func(bar *Scrollbar)) {
|
||||
|
||||
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)
|
||||
o.desired = o.Content.DesiredSize(ctx, bounds.Size())
|
||||
o.bounds = bounds
|
||||
o.offset = offset
|
||||
o.parent = parent
|
||||
@ -91,11 +114,15 @@ func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.Po
|
||||
|
||||
func (o *overflow) Bounds() geom.RectangleF32 { return o.bounds }
|
||||
|
||||
func (o *overflow) DesiredSize(ctx Context) geom.PointF32 {
|
||||
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) {
|
||||
func (o *overflow) Handle(ctx Context, e Event) bool {
|
||||
if o.Content != o.proxied {
|
||||
o.hor.ContentOffset = 0
|
||||
o.ver.ContentOffset = 0
|
||||
@ -117,10 +144,11 @@ func (o *overflow) Handle(ctx Context, e Event) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
o.Content.Handle(ctx, e)
|
||||
return o.Content.Handle(ctx, e)
|
||||
}
|
||||
|
||||
func (o *overflow) IsInBounds(p geom.PointF32) bool { return p.Sub(o.offset).In(o.bounds) }
|
||||
@ -139,7 +167,7 @@ func (o *overflow) Render(ctx Context) {
|
||||
var content = o.Content.Bounds()
|
||||
content.Min = geom.ZeroPtF32
|
||||
err := o.content.Update(ctx, content.Size())
|
||||
if err != nil {
|
||||
if err != nil && err != ErrNewBufferSize {
|
||||
panic(err)
|
||||
}
|
||||
o.content.Render(ctx, o.bounds.Min, func(Context, geom.PointF32) {
|
||||
@ -151,3 +179,43 @@ func (o *overflow) Render(ctx Context) {
|
||||
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)
|
||||
}
|
59
ui/proxy.go
59
ui/proxy.go
@ -3,6 +3,7 @@ package ui
|
||||
import "opslag.de/schobers/geom"
|
||||
|
||||
var _ Control = &Proxy{}
|
||||
var _ Overlay = &Proxy{}
|
||||
|
||||
type Proxy struct {
|
||||
Content Control
|
||||
@ -12,12 +13,12 @@ func (p *Proxy) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.Point
|
||||
p.Content.Arrange(ctx, bounds, offset, parent)
|
||||
}
|
||||
|
||||
func (p *Proxy) DesiredSize(ctx Context) geom.PointF32 {
|
||||
return p.Content.DesiredSize(ctx)
|
||||
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) {
|
||||
p.Content.Handle(ctx, e)
|
||||
func (p *Proxy) Handle(ctx Context, e Event) bool {
|
||||
return p.Content.Handle(ctx, e)
|
||||
}
|
||||
|
||||
func (p *Proxy) Render(ctx Context) {
|
||||
@ -28,26 +29,44 @@ 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) OnClick(fn ClickFn) {
|
||||
p.Content.OnClick(fn)
|
||||
}
|
||||
|
||||
func (p *Proxy) OnDragStart(fn DragStartFn) {
|
||||
p.Content.OnDragStart(fn)
|
||||
}
|
||||
|
||||
func (p *Proxy) OnDragMove(fn DragMoveFn) {
|
||||
p.Content.OnDragMove(fn)
|
||||
}
|
||||
|
||||
func (p *Proxy) OnDragEnd(fn DragEndFn) {
|
||||
p.Content.OnDragEnd(fn)
|
||||
}
|
||||
|
||||
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) }
|
||||
|
@ -5,32 +5,63 @@ import (
|
||||
"image/color"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/zntg"
|
||||
)
|
||||
|
||||
type Renderer interface {
|
||||
// Events
|
||||
PushEvents(t EventTarget, wait bool)
|
||||
PushEvents(t EventTarget, wait bool) bool
|
||||
Refresh()
|
||||
Stamp() float64 // in seconds
|
||||
|
||||
// Lifetime
|
||||
Destroy() error
|
||||
|
||||
// Drawing
|
||||
Clear(c color.Color)
|
||||
CreateImage(m image.Image) (Image, error)
|
||||
CreateImagePath(path string) (Image, error)
|
||||
CreateImageSize(w, h float32) (Image, error)
|
||||
DefaultTarget() Image
|
||||
DrawImage(im Image, p geom.PointF32)
|
||||
DrawImageOptions(im Image, p geom.PointF32, opts DrawOptions)
|
||||
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)
|
||||
Font(font string) Font
|
||||
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(Image)
|
||||
RenderTo(Texture)
|
||||
RenderToDisplay()
|
||||
Resize(width, height int)
|
||||
SetIcon(source ImageSource)
|
||||
SetMouseCursor(c MouseCursor)
|
||||
Size() geom.PointF32
|
||||
Target() Image
|
||||
Text(p geom.PointF32, font string, color color.Color, text string)
|
||||
TextAlign(p geom.PointF32, font string, color color.Color, text string, align HorizontalAlignment)
|
||||
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
|
||||
}
|
||||
|
47
ui/rendererfactory.go
Normal file
47
ui/rendererfactory.go
Normal file
@ -0,0 +1,47 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
// RendererFactory can be used to inject a new factory for creating renderers.
|
||||
type RendererFactory interface {
|
||||
New(title string, width, height int, opts NewRendererOptions) (Renderer, error)
|
||||
}
|
||||
|
||||
var rendererFactory RendererFactory
|
||||
|
||||
// NewRenderer creates a new renderer based on the registered renderer factory.
|
||||
func NewRenderer(title string, width, height int, opts NewRendererOptions) (Renderer, error) {
|
||||
if rendererFactory == nil {
|
||||
return nil, errors.New("no renderer factory registered")
|
||||
}
|
||||
renderer, err := rendererFactory.New(title, width, height, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
renderer.SetResourceProvider(DefaultResources())
|
||||
return renderer, nil
|
||||
}
|
||||
|
||||
// NewRendererDefault creates a new renderer with default options set based on the registered renderer factory.
|
||||
func NewRendererDefault(title string, width, height int) (Renderer, error) {
|
||||
return NewRenderer(title, width, height, NewRendererOptions{
|
||||
Resizable: true,
|
||||
})
|
||||
}
|
||||
|
||||
// SetRendererFactory sets the new factory that is used to create a new renderer.
|
||||
func SetRendererFactory(factory RendererFactory) {
|
||||
rendererFactory = factory
|
||||
}
|
||||
|
||||
// NewRendererOptions provides options when creating a new renderer.
|
||||
type NewRendererOptions struct {
|
||||
Location *geom.PointF32
|
||||
Borderless bool
|
||||
Resizable bool
|
||||
VSync bool
|
||||
}
|
19
ui/resources.go
Normal file
19
ui/resources.go
Normal file
@ -0,0 +1,19 @@
|
||||
package ui
|
||||
|
||||
import "io"
|
||||
|
||||
// Resources is an abstraction for opening resources.
|
||||
type Resources interface {
|
||||
// OpenResource should open the resource with the specified name. The user is responsible for closing the resource.
|
||||
OpenResource(name string) (io.ReadCloser, error)
|
||||
// Destroy can be used for cleaning up at the end of the applications lifetime.
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
// PhysicalResources is an abstraction for opening and fetching (to disk) resources.
|
||||
type PhysicalResources interface {
|
||||
Resources
|
||||
|
||||
// FetchResource should fetch the resource with the specified name and return a path (on disk) where the resource can be accessed.
|
||||
FetchResource(name string) (string, error)
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/zntg"
|
||||
)
|
||||
|
||||
type Scrollbar struct {
|
||||
@ -9,6 +12,8 @@ type Scrollbar struct {
|
||||
|
||||
Orientation Orientation
|
||||
|
||||
BarColor color.Color
|
||||
BarHoverColor color.Color
|
||||
ContentLength float32
|
||||
ContentOffset float32
|
||||
|
||||
@ -18,14 +23,14 @@ type Scrollbar struct {
|
||||
|
||||
func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar {
|
||||
var s = &Scrollbar{Orientation: o, ContentLength: 0, ContentOffset: 0}
|
||||
s.handle.OnDragStart(func(_ geom.PointF32) {
|
||||
s.handle.DragStarted().AddHandler(func(Context, DragStartedArgs) {
|
||||
s.startDragOffset = s.ContentOffset
|
||||
})
|
||||
s.handle.OnDragMove(func(start, move geom.PointF32) {
|
||||
s.handle.DragMoved().AddHandler(func(_ Context, args DragMovedArgs) {
|
||||
var length = s.Orientation.SizeParallel(s.bounds)
|
||||
var handleMaxOffset = length - s.Orientation.SizeParallel(s.handle.bounds)
|
||||
var hidden = s.ContentLength - length
|
||||
var offset = (s.Orientation.LengthParallel(move) - s.Orientation.LengthParallel(start)) / handleMaxOffset
|
||||
var offset = (s.Orientation.LengthParallel(args.Current) - s.Orientation.LengthParallel(args.Start)) / handleMaxOffset
|
||||
s.ContentOffset = geom.Max32(0, geom.Min32(s.startDragOffset+offset*hidden, hidden))
|
||||
})
|
||||
if fn != nil {
|
||||
@ -39,24 +44,25 @@ func (s *Scrollbar) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.P
|
||||
s.updateBar(ctx)
|
||||
}
|
||||
|
||||
func (s *Scrollbar) DesiredSize(ctx Context) geom.PointF32 {
|
||||
func (s *Scrollbar) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
|
||||
return s.Orientation.Pt(geom.NaN32(), ctx.Style().Dimensions.ScrollbarWidth)
|
||||
}
|
||||
|
||||
func (s *Scrollbar) Handle(ctx Context, e Event) {
|
||||
func (s *Scrollbar) Handle(ctx Context, e Event) bool {
|
||||
s.handle.Handle(ctx, e)
|
||||
switch e := e.(type) {
|
||||
case *MouseMoveEvent:
|
||||
if e.MouseWheel != 0 && e.Pos().Sub(s.offset).In(s.bounds) {
|
||||
s.ContentOffset = geom.Max32(0, geom.Min32(s.ContentLength-s.Orientation.SizeParallel(s.bounds), s.ContentOffset-36*e.MouseWheel))
|
||||
return true
|
||||
}
|
||||
}
|
||||
s.ControlBase.Handle(ctx, e)
|
||||
return s.ControlBase.Handle(ctx, e)
|
||||
}
|
||||
|
||||
func (s *Scrollbar) Render(ctx Context) {
|
||||
ctx.Renderer().FillRectangle(s.bounds, RGBA(0, 0, 0, 1))
|
||||
s.handle.Render(ctx)
|
||||
ctx.Renderer().FillRectangle(s.bounds, zntg.RGBA(0, 0, 0, 1))
|
||||
s.handle.Render(ctx, s)
|
||||
}
|
||||
|
||||
func (s *Scrollbar) updateBar(ctx Context) {
|
||||
@ -81,12 +87,22 @@ type ScrollbarHandle struct {
|
||||
ControlBase
|
||||
}
|
||||
|
||||
func (h *ScrollbarHandle) Render(ctx Context) {
|
||||
func (h *ScrollbarHandle) Render(ctx Context, s *Scrollbar) {
|
||||
h.RenderBackground(ctx)
|
||||
p := ctx.Style().Palette
|
||||
fill := p.Primary
|
||||
var fill color.Color
|
||||
if h.over {
|
||||
if s.BarHoverColor == nil {
|
||||
fill = p.PrimaryLight
|
||||
} else {
|
||||
fill = s.BarHoverColor
|
||||
}
|
||||
} else {
|
||||
if s.BarColor == nil {
|
||||
fill = p.Primary
|
||||
} else {
|
||||
fill = s.BarColor
|
||||
}
|
||||
}
|
||||
ctx.Renderer().FillRectangle(h.bounds.Inset(1), fill)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user