vendor dependencies
commit
d29abc7a0d
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2018 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,726 @@
|
||||
// +build darwin
|
||||
|
||||
// Package mtl provides access to Apple's Metal API (https://developer.apple.com/documentation/metal).
|
||||
//
|
||||
// Package mtl requires macOS version 10.13 or newer.
|
||||
//
|
||||
// This package is in very early stages of development.
|
||||
// The API will change when opportunities for improvement are discovered; it is not yet frozen.
|
||||
// Less than 20% of the Metal API surface is implemented.
|
||||
// Current functionality is sufficient to render very basic geometry.
|
||||
package mtl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework Metal -framework Foundation
|
||||
#include <stdlib.h>
|
||||
#include "mtl.h"
|
||||
struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) {
|
||||
return Device_MakeLibrary(device, _GoStringPtr(source), _GoStringLen(source));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// FeatureSet defines a specific platform, hardware, and software configuration.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
|
||||
type FeatureSet uint16
|
||||
|
||||
// The device feature sets that define specific platform, hardware, and software configurations.
|
||||
const (
|
||||
MacOSGPUFamily1V1 FeatureSet = 10000 // The GPU family 1, version 1 feature set for macOS.
|
||||
MacOSGPUFamily1V2 FeatureSet = 10001 // The GPU family 1, version 2 feature set for macOS.
|
||||
MacOSReadWriteTextureTier2 FeatureSet = 10002 // The read-write texture, tier 2 feature set for macOS.
|
||||
MacOSGPUFamily1V3 FeatureSet = 10003 // The GPU family 1, version 3 feature set for macOS.
|
||||
MacOSGPUFamily1V4 FeatureSet = 10004 // The GPU family 1, version 4 feature set for macOS.
|
||||
MacOSGPUFamily2V1 FeatureSet = 10005 // The GPU family 2, version 1 feature set for macOS.
|
||||
)
|
||||
|
||||
// PixelFormat defines data formats that describe the organization
|
||||
// and characteristics of individual pixels in a texture.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat.
|
||||
type PixelFormat uint8
|
||||
|
||||
// The data formats that describe the organization and characteristics
|
||||
// of individual pixels in a texture.
|
||||
const (
|
||||
PixelFormatRGBA8UNorm PixelFormat = 70 // Ordinary format with four 8-bit normalized unsigned integer components in RGBA order.
|
||||
PixelFormatBGRA8UNorm PixelFormat = 80 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order.
|
||||
PixelFormatBGRA8UNormSRGB PixelFormat = 81 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order with conversion between sRGB and linear space.
|
||||
)
|
||||
|
||||
// PrimitiveType defines geometric primitive types for drawing commands.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype.
|
||||
type PrimitiveType uint8
|
||||
|
||||
// Geometric primitive types for drawing commands.
|
||||
const (
|
||||
PrimitiveTypePoint PrimitiveType = 0
|
||||
PrimitiveTypeLine PrimitiveType = 1
|
||||
PrimitiveTypeLineStrip PrimitiveType = 2
|
||||
PrimitiveTypeTriangle PrimitiveType = 3
|
||||
PrimitiveTypeTriangleStrip PrimitiveType = 4
|
||||
)
|
||||
|
||||
// LoadAction defines actions performed at the start of a rendering pass
|
||||
// for a render command encoder.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction.
|
||||
type LoadAction uint8
|
||||
|
||||
// Actions performed at the start of a rendering pass for a render command encoder.
|
||||
const (
|
||||
LoadActionDontCare LoadAction = 0
|
||||
LoadActionLoad LoadAction = 1
|
||||
LoadActionClear LoadAction = 2
|
||||
)
|
||||
|
||||
// StoreAction defines actions performed at the end of a rendering pass
|
||||
// for a render command encoder.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction.
|
||||
type StoreAction uint8
|
||||
|
||||
// Actions performed at the end of a rendering pass for a render command encoder.
|
||||
const (
|
||||
StoreActionDontCare StoreAction = 0
|
||||
StoreActionStore StoreAction = 1
|
||||
StoreActionMultisampleResolve StoreAction = 2
|
||||
StoreActionStoreAndMultisampleResolve StoreAction = 3
|
||||
StoreActionUnknown StoreAction = 4
|
||||
StoreActionCustomSampleDepthStore StoreAction = 5
|
||||
)
|
||||
|
||||
// StorageMode defines defines the memory location and access permissions of a resource.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode.
|
||||
type StorageMode uint8
|
||||
|
||||
const (
|
||||
// StorageModeShared indicates that the resource is stored in system memory
|
||||
// accessible to both the CPU and the GPU.
|
||||
StorageModeShared StorageMode = 0
|
||||
|
||||
// StorageModeManaged indicates that the resource exists as a synchronized
|
||||
// memory pair with one copy stored in system memory accessible to the CPU
|
||||
// and another copy stored in video memory accessible to the GPU.
|
||||
StorageModeManaged StorageMode = 1
|
||||
|
||||
// StorageModePrivate indicates that the resource is stored in memory
|
||||
// only accessible to the GPU. In iOS and tvOS, the resource is stored in
|
||||
// system memory. In macOS, the resource is stored in video memory.
|
||||
StorageModePrivate StorageMode = 2
|
||||
|
||||
// StorageModeMemoryless indicates that the resource is stored in on-tile memory,
|
||||
// without CPU or GPU memory backing. The contents of the on-tile memory are undefined
|
||||
// and do not persist; the only way to populate the resource is to render into it.
|
||||
// Memoryless resources are limited to temporary render targets (i.e., Textures configured
|
||||
// with a TextureDescriptor and used with a RenderPassAttachmentDescriptor).
|
||||
StorageModeMemoryless StorageMode = 3
|
||||
)
|
||||
|
||||
// ResourceOptions defines optional arguments used to create
|
||||
// and influence behavior of buffer and texture objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions.
|
||||
type ResourceOptions uint16
|
||||
|
||||
const (
|
||||
// ResourceCPUCacheModeDefaultCache is the default CPU cache mode for the resource.
|
||||
// Guarantees that read and write operations are executed in the expected order.
|
||||
ResourceCPUCacheModeDefaultCache ResourceOptions = ResourceOptions(CPUCacheModeDefaultCache) << resourceCPUCacheModeShift
|
||||
|
||||
// ResourceCPUCacheModeWriteCombined is a write-combined CPU cache mode for the resource.
|
||||
// Optimized for resources that the CPU will write into, but never read.
|
||||
ResourceCPUCacheModeWriteCombined ResourceOptions = ResourceOptions(CPUCacheModeWriteCombined) << resourceCPUCacheModeShift
|
||||
|
||||
// ResourceStorageModeShared indicates that the resource is stored in system memory
|
||||
// accessible to both the CPU and the GPU.
|
||||
ResourceStorageModeShared ResourceOptions = ResourceOptions(StorageModeShared) << resourceStorageModeShift
|
||||
|
||||
// ResourceStorageModeManaged indicates that the resource exists as a synchronized
|
||||
// memory pair with one copy stored in system memory accessible to the CPU
|
||||
// and another copy stored in video memory accessible to the GPU.
|
||||
ResourceStorageModeManaged ResourceOptions = ResourceOptions(StorageModeManaged) << resourceStorageModeShift
|
||||
|
||||
// ResourceStorageModePrivate indicates that the resource is stored in memory
|
||||
// only accessible to the GPU. In iOS and tvOS, the resource is stored
|
||||
// in system memory. In macOS, the resource is stored in video memory.
|
||||
ResourceStorageModePrivate ResourceOptions = ResourceOptions(StorageModePrivate) << resourceStorageModeShift
|
||||
|
||||
// ResourceStorageModeMemoryless indicates that the resource is stored in on-tile memory,
|
||||
// without CPU or GPU memory backing. The contents of the on-tile memory are undefined
|
||||
// and do not persist; the only way to populate the resource is to render into it.
|
||||
// Memoryless resources are limited to temporary render targets (i.e., Textures configured
|
||||
// with a TextureDescriptor and used with a RenderPassAttachmentDescriptor).
|
||||
ResourceStorageModeMemoryless ResourceOptions = ResourceOptions(StorageModeMemoryless) << resourceStorageModeShift
|
||||
|
||||
// ResourceHazardTrackingModeUntracked indicates that the command encoder dependencies
|
||||
// for this resource are tracked manually with Fence objects. This value is always set
|
||||
// for resources sub-allocated from a Heap object and may optionally be specified for
|
||||
// non-heap resources.
|
||||
ResourceHazardTrackingModeUntracked ResourceOptions = 1 << resourceHazardTrackingModeShift
|
||||
)
|
||||
|
||||
const (
|
||||
resourceCPUCacheModeShift = 0
|
||||
resourceStorageModeShift = 4
|
||||
resourceHazardTrackingModeShift = 8
|
||||
)
|
||||
|
||||
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode.
|
||||
type CPUCacheMode uint8
|
||||
|
||||
const (
|
||||
// CPUCacheModeDefaultCache is the default CPU cache mode for the resource.
|
||||
// Guarantees that read and write operations are executed in the expected order.
|
||||
CPUCacheModeDefaultCache CPUCacheMode = 0
|
||||
|
||||
// CPUCacheModeWriteCombined is a write-combined CPU cache mode for the resource.
|
||||
// Optimized for resources that the CPU will write into, but never read.
|
||||
CPUCacheModeWriteCombined CPUCacheMode = 1
|
||||
)
|
||||
|
||||
// Resource represents a memory allocation for storing specialized data
|
||||
// that is accessible to the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
|
||||
type Resource interface {
|
||||
// resource returns the underlying id<MTLResource> pointer.
|
||||
resource() unsafe.Pointer
|
||||
}
|
||||
|
||||
// RenderPipelineDescriptor configures new RenderPipelineState objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor.
|
||||
type RenderPipelineDescriptor struct {
|
||||
// VertexFunction is a programmable function that processes individual vertices in a rendering pass.
|
||||
VertexFunction Function
|
||||
|
||||
// FragmentFunction is a programmable function that processes individual fragments in a rendering pass.
|
||||
FragmentFunction Function
|
||||
|
||||
// ColorAttachments is an array of attachments that store color data.
|
||||
ColorAttachments [1]RenderPipelineColorAttachmentDescriptor
|
||||
}
|
||||
|
||||
// RenderPipelineColorAttachmentDescriptor describes a color render target that specifies
|
||||
// the color configuration and color operations associated with a render pipeline.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor.
|
||||
type RenderPipelineColorAttachmentDescriptor struct {
|
||||
// PixelFormat is the pixel format of the color attachment's texture.
|
||||
PixelFormat PixelFormat
|
||||
}
|
||||
|
||||
// RenderPassDescriptor describes a group of render targets that serve as
|
||||
// the output destination for pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor.
|
||||
type RenderPassDescriptor struct {
|
||||
// ColorAttachments is array of state information for attachments that store color data.
|
||||
ColorAttachments [1]RenderPassColorAttachmentDescriptor
|
||||
}
|
||||
|
||||
// RenderPassColorAttachmentDescriptor describes a color render target that serves
|
||||
// as the output destination for color pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor.
|
||||
type RenderPassColorAttachmentDescriptor struct {
|
||||
RenderPassAttachmentDescriptor
|
||||
ClearColor ClearColor
|
||||
}
|
||||
|
||||
// RenderPassAttachmentDescriptor describes a render target that serves
|
||||
// as the output destination for pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor.
|
||||
type RenderPassAttachmentDescriptor struct {
|
||||
LoadAction LoadAction
|
||||
StoreAction StoreAction
|
||||
Texture Texture
|
||||
}
|
||||
|
||||
// ClearColor is an RGBA value used for a color pixel.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor.
|
||||
type ClearColor struct {
|
||||
Red, Green, Blue, Alpha float64
|
||||
}
|
||||
|
||||
// TextureDescriptor configures new Texture objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor.
|
||||
type TextureDescriptor struct {
|
||||
PixelFormat PixelFormat
|
||||
Width int
|
||||
Height int
|
||||
StorageMode StorageMode
|
||||
}
|
||||
|
||||
// Device is abstract representation of the GPU that
|
||||
// serves as the primary interface for a Metal app.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice.
|
||||
type Device struct {
|
||||
device unsafe.Pointer
|
||||
|
||||
// Headless indicates whether a device is configured as headless.
|
||||
Headless bool
|
||||
|
||||
// LowPower indicates whether a device is low-power.
|
||||
LowPower bool
|
||||
|
||||
// Removable determines whether or not a GPU is removable.
|
||||
Removable bool
|
||||
|
||||
// RegistryID is the registry ID value for the device.
|
||||
RegistryID uint64
|
||||
|
||||
// Name is the name of the device.
|
||||
Name string
|
||||
}
|
||||
|
||||
// CreateSystemDefaultDevice returns the preferred system default Metal device.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice.
|
||||
func CreateSystemDefaultDevice() (Device, error) {
|
||||
d := C.CreateSystemDefaultDevice()
|
||||
if d.Device == nil {
|
||||
return Device{}, errors.New("Metal is not supported on this system")
|
||||
}
|
||||
|
||||
return Device{
|
||||
device: d.Device,
|
||||
Headless: d.Headless != 0,
|
||||
LowPower: d.LowPower != 0,
|
||||
Removable: d.Removable != 0,
|
||||
RegistryID: uint64(d.RegistryID),
|
||||
Name: C.GoString(d.Name),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CopyAllDevices returns all Metal devices in the system.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/1433367-mtlcopyalldevices.
|
||||
func CopyAllDevices() []Device {
|
||||
d := C.CopyAllDevices()
|
||||
defer C.free(unsafe.Pointer(d.Devices))
|
||||
|
||||
ds := make([]Device, d.Length)
|
||||
for i := 0; i < len(ds); i++ {
|
||||
d := (*C.struct_Device)(unsafe.Pointer(uintptr(unsafe.Pointer(d.Devices)) + uintptr(i)*C.sizeof_struct_Device))
|
||||
|
||||
ds[i].device = d.Device
|
||||
ds[i].Headless = d.Headless != 0
|
||||
ds[i].LowPower = d.LowPower != 0
|
||||
ds[i].Removable = d.Removable != 0
|
||||
ds[i].RegistryID = uint64(d.RegistryID)
|
||||
ds[i].Name = C.GoString(d.Name)
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
// Device returns the underlying id<MTLDevice> pointer.
|
||||
func (d Device) Device() unsafe.Pointer { return d.device }
|
||||
|
||||
// SupportsFeatureSet reports whether device d supports feature set fs.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
|
||||
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
|
||||
return C.Device_SupportsFeatureSet(d.device, C.uint16_t(fs)) != 0
|
||||
}
|
||||
|
||||
// MakeCommandQueue creates a serial command submission queue.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue.
|
||||
func (d Device) MakeCommandQueue() CommandQueue {
|
||||
return CommandQueue{C.Device_MakeCommandQueue(d.device)}
|
||||
}
|
||||
|
||||
// MakeLibrary creates a new library that contains
|
||||
// the functions stored in the specified source string.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary.
|
||||
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) {
|
||||
l := C.Go_Device_MakeLibrary(d.device, source) // TODO: opt.
|
||||
if l.Library == nil {
|
||||
return Library{}, errors.New(C.GoString(l.Error))
|
||||
}
|
||||
|
||||
return Library{l.Library}, nil
|
||||
}
|
||||
|
||||
// MakeRenderPipelineState creates a render pipeline state object.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate.
|
||||
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
|
||||
descriptor := C.struct_RenderPipelineDescriptor{
|
||||
VertexFunction: rpd.VertexFunction.function,
|
||||
FragmentFunction: rpd.FragmentFunction.function,
|
||||
ColorAttachment0PixelFormat: C.uint16_t(rpd.ColorAttachments[0].PixelFormat),
|
||||
}
|
||||
rps := C.Device_MakeRenderPipelineState(d.device, descriptor)
|
||||
if rps.RenderPipelineState == nil {
|
||||
return RenderPipelineState{}, errors.New(C.GoString(rps.Error))
|
||||
}
|
||||
|
||||
return RenderPipelineState{rps.RenderPipelineState}, nil
|
||||
}
|
||||
|
||||
// MakeBuffer allocates a new buffer of a given length
|
||||
// and initializes its contents by copying existing data into it.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer.
|
||||
func (d Device) MakeBuffer(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
|
||||
return Buffer{C.Device_MakeBuffer(d.device, bytes, C.size_t(length), C.uint16_t(opt))}
|
||||
}
|
||||
|
||||
// MakeTexture creates a texture object with privately owned storage
|
||||
// that contains texture state.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture.
|
||||
func (d Device) MakeTexture(td TextureDescriptor) Texture {
|
||||
descriptor := C.struct_TextureDescriptor{
|
||||
PixelFormat: C.uint16_t(td.PixelFormat),
|
||||
Width: C.uint_t(td.Width),
|
||||
Height: C.uint_t(td.Height),
|
||||
StorageMode: C.uint8_t(td.StorageMode),
|
||||
}
|
||||
return Texture{
|
||||
texture: C.Device_MakeTexture(d.device, descriptor),
|
||||
Width: td.Width, // TODO: Fetch dimensions of actually created texture.
|
||||
Height: td.Height, // TODO: Fetch dimensions of actually created texture.
|
||||
}
|
||||
}
|
||||
|
||||
// CompileOptions specifies optional compilation settings for
|
||||
// the graphics or compute functions within a library.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
|
||||
type CompileOptions struct {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
// Drawable is a displayable resource that can be rendered or written to.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
|
||||
type Drawable interface {
|
||||
// Drawable returns the underlying id<MTLDrawable> pointer.
|
||||
Drawable() unsafe.Pointer
|
||||
}
|
||||
|
||||
// CommandQueue is a queue that organizes the order
|
||||
// in which command buffers are executed by the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue.
|
||||
type CommandQueue struct {
|
||||
commandQueue unsafe.Pointer
|
||||
}
|
||||
|
||||
// MakeCommandBuffer creates a command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer.
|
||||
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
|
||||
return CommandBuffer{C.CommandQueue_MakeCommandBuffer(cq.commandQueue)}
|
||||
}
|
||||
|
||||
// CommandBuffer is a container that stores encoded commands
|
||||
// that are committed to and executed by the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
|
||||
type CommandBuffer struct {
|
||||
commandBuffer unsafe.Pointer
|
||||
}
|
||||
|
||||
// PresentDrawable registers a drawable presentation to occur as soon as possible.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
|
||||
func (cb CommandBuffer) PresentDrawable(d Drawable) {
|
||||
C.CommandBuffer_PresentDrawable(cb.commandBuffer, d.Drawable())
|
||||
}
|
||||
|
||||
// Commit commits this command buffer for execution as soon as possible.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
|
||||
func (cb CommandBuffer) Commit() {
|
||||
C.CommandBuffer_Commit(cb.commandBuffer)
|
||||
}
|
||||
|
||||
// WaitUntilCompleted waits for the execution of this command buffer to complete.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted.
|
||||
func (cb CommandBuffer) WaitUntilCompleted() {
|
||||
C.CommandBuffer_WaitUntilCompleted(cb.commandBuffer)
|
||||
}
|
||||
|
||||
// MakeRenderCommandEncoder creates an encoder object that can
|
||||
// encode graphics rendering commands into this command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder.
|
||||
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder {
|
||||
descriptor := C.struct_RenderPassDescriptor{
|
||||
ColorAttachment0LoadAction: C.uint8_t(rpd.ColorAttachments[0].LoadAction),
|
||||
ColorAttachment0StoreAction: C.uint8_t(rpd.ColorAttachments[0].StoreAction),
|
||||
ColorAttachment0ClearColor: C.struct_ClearColor{
|
||||
Red: C.double(rpd.ColorAttachments[0].ClearColor.Red),
|
||||
Green: C.double(rpd.ColorAttachments[0].ClearColor.Green),
|
||||
Blue: C.double(rpd.ColorAttachments[0].ClearColor.Blue),
|
||||
Alpha: C.double(rpd.ColorAttachments[0].ClearColor.Alpha),
|
||||
},
|
||||
ColorAttachment0Texture: rpd.ColorAttachments[0].Texture.texture,
|
||||
}
|
||||
return RenderCommandEncoder{CommandEncoder{C.CommandBuffer_MakeRenderCommandEncoder(cb.commandBuffer, descriptor)}}
|
||||
}
|
||||
|
||||
// MakeBlitCommandEncoder creates an encoder object that can encode
|
||||
// memory operation (blit) commands into this command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder.
|
||||
func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
|
||||
return BlitCommandEncoder{CommandEncoder{C.CommandBuffer_MakeBlitCommandEncoder(cb.commandBuffer)}}
|
||||
}
|
||||
|
||||
// CommandEncoder is an encoder that writes sequential GPU commands
|
||||
// into a command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder.
|
||||
type CommandEncoder struct {
|
||||
commandEncoder unsafe.Pointer
|
||||
}
|
||||
|
||||
// EndEncoding declares that all command generation from this encoder is completed.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding.
|
||||
func (ce CommandEncoder) EndEncoding() {
|
||||
C.CommandEncoder_EndEncoding(ce.commandEncoder)
|
||||
}
|
||||
|
||||
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands
|
||||
// and executes graphics functions.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder.
|
||||
type RenderCommandEncoder struct {
|
||||
CommandEncoder
|
||||
}
|
||||
|
||||
// SetRenderPipelineState sets the current render pipeline state object.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate.
|
||||
func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
|
||||
C.RenderCommandEncoder_SetRenderPipelineState(rce.commandEncoder, rps.renderPipelineState)
|
||||
}
|
||||
|
||||
// SetVertexBuffer sets a buffer for the vertex shader function at an index
|
||||
// in the buffer argument table with an offset that specifies the start of the data.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer.
|
||||
func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
|
||||
C.RenderCommandEncoder_SetVertexBuffer(rce.commandEncoder, buf.buffer, C.uint_t(offset), C.uint_t(index))
|
||||
}
|
||||
|
||||
// SetVertexBytes sets a block of data for the vertex function.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes.
|
||||
func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
|
||||
C.RenderCommandEncoder_SetVertexBytes(rce.commandEncoder, bytes, C.size_t(length), C.uint_t(index))
|
||||
}
|
||||
|
||||
// DrawPrimitives renders one instance of primitives using vertex data
|
||||
// in contiguous array elements.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives.
|
||||
func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
|
||||
C.RenderCommandEncoder_DrawPrimitives(rce.commandEncoder, C.uint8_t(typ), C.uint_t(vertexStart), C.uint_t(vertexCount))
|
||||
}
|
||||
|
||||
// BlitCommandEncoder is an encoder that specifies resource copy
|
||||
// and resource synchronization commands.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder.
|
||||
type BlitCommandEncoder struct {
|
||||
CommandEncoder
|
||||
}
|
||||
|
||||
// CopyFromTexture encodes a command to copy image data from a slice of
|
||||
// a source texture into a slice of a destination texture.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture.
|
||||
func (bce BlitCommandEncoder) CopyFromTexture(
|
||||
src Texture, srcSlice, srcLevel int, srcOrigin Origin, srcSize Size,
|
||||
dst Texture, dstSlice, dstLevel int, dstOrigin Origin,
|
||||
) {
|
||||
C.BlitCommandEncoder_CopyFromTexture(
|
||||
bce.commandEncoder,
|
||||
src.texture, C.uint_t(srcSlice), C.uint_t(srcLevel),
|
||||
C.struct_Origin{
|
||||
X: C.uint_t(srcOrigin.X),
|
||||
Y: C.uint_t(srcOrigin.Y),
|
||||
Z: C.uint_t(srcOrigin.Z),
|
||||
},
|
||||
C.struct_Size{
|
||||
Width: C.uint_t(srcSize.Width),
|
||||
Height: C.uint_t(srcSize.Height),
|
||||
Depth: C.uint_t(srcSize.Depth),
|
||||
},
|
||||
dst.texture, C.uint_t(dstSlice), C.uint_t(dstLevel),
|
||||
C.struct_Origin{
|
||||
X: C.uint_t(dstOrigin.X),
|
||||
Y: C.uint_t(dstOrigin.Y),
|
||||
Z: C.uint_t(dstOrigin.Z),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Synchronize flushes any copy of the specified resource from its corresponding
|
||||
// Device caches and, if needed, invalidates any CPU caches.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize.
|
||||
func (bce BlitCommandEncoder) Synchronize(resource Resource) {
|
||||
C.BlitCommandEncoder_Synchronize(bce.commandEncoder, resource.resource())
|
||||
}
|
||||
|
||||
// Library is a collection of compiled graphics or compute functions.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
|
||||
type Library struct {
|
||||
library unsafe.Pointer
|
||||
}
|
||||
|
||||
// MakeFunction returns a pre-compiled, non-specialized function.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-makefunction.
|
||||
func (l Library) MakeFunction(name string) (Function, error) {
|
||||
f := C.Library_MakeFunction(l.library, C.CString(name))
|
||||
if f == nil {
|
||||
return Function{}, fmt.Errorf("function %q not found", name)
|
||||
}
|
||||
|
||||
return Function{f}, nil
|
||||
}
|
||||
|
||||
// Texture is a memory allocation for storing formatted
|
||||
// image data that is accessible to the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
|
||||
type Texture struct {
|
||||
texture unsafe.Pointer
|
||||
|
||||
// TODO: Change these fields into methods.
|
||||
|
||||
// Width is the width of the texture image for the base level mipmap, in pixels.
|
||||
Width int
|
||||
|
||||
// Height is the height of the texture image for the base level mipmap, in pixels.
|
||||
Height int
|
||||
}
|
||||
|
||||
// NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer.
|
||||
func NewTexture(texture unsafe.Pointer) Texture {
|
||||
return Texture{texture: texture}
|
||||
}
|
||||
|
||||
// resource implements the Resource interface.
|
||||
func (t Texture) resource() unsafe.Pointer { return t.texture }
|
||||
|
||||
// ReplaceRegion copies a block of pixels into a section of texture slice 0.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion.
|
||||
func (t Texture) ReplaceRegion(region Region, level int, pixelBytes *byte, bytesPerRow uintptr) {
|
||||
r := C.struct_Region{
|
||||
Origin: C.struct_Origin{
|
||||
X: C.uint_t(region.Origin.X),
|
||||
Y: C.uint_t(region.Origin.Y),
|
||||
Z: C.uint_t(region.Origin.Z),
|
||||
},
|
||||
Size: C.struct_Size{
|
||||
Width: C.uint_t(region.Size.Width),
|
||||
Height: C.uint_t(region.Size.Height),
|
||||
Depth: C.uint_t(region.Size.Depth),
|
||||
},
|
||||
}
|
||||
C.Texture_ReplaceRegion(t.texture, r, C.uint_t(level), unsafe.Pointer(pixelBytes), C.size_t(bytesPerRow))
|
||||
}
|
||||
|
||||
// GetBytes copies a block of pixels from the storage allocation of texture
|
||||
// slice zero into system memory at a specified address.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes.
|
||||
func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, level int) {
|
||||
r := C.struct_Region{
|
||||
Origin: C.struct_Origin{
|
||||
X: C.uint_t(region.Origin.X),
|
||||
Y: C.uint_t(region.Origin.Y),
|
||||
Z: C.uint_t(region.Origin.Z),
|
||||
},
|
||||
Size: C.struct_Size{
|
||||
Width: C.uint_t(region.Size.Width),
|
||||
Height: C.uint_t(region.Size.Height),
|
||||
Depth: C.uint_t(region.Size.Depth),
|
||||
},
|
||||
}
|
||||
C.Texture_GetBytes(t.texture, unsafe.Pointer(pixelBytes), C.size_t(bytesPerRow), r, C.uint_t(level))
|
||||
}
|
||||
|
||||
// Buffer is a memory allocation for storing unformatted data
|
||||
// that is accessible to the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer.
|
||||
type Buffer struct {
|
||||
buffer unsafe.Pointer
|
||||
}
|
||||
|
||||
// Function represents a programmable graphics or compute function executed by the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlfunction.
|
||||
type Function struct {
|
||||
function unsafe.Pointer
|
||||
}
|
||||
|
||||
// RenderPipelineState contains the graphics functions
|
||||
// and configuration state used in a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate.
|
||||
type RenderPipelineState struct {
|
||||
renderPipelineState unsafe.Pointer
|
||||
}
|
||||
|
||||
// Region is a rectangular block of pixels in an image or texture,
|
||||
// defined by its upper-left corner and its size.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlregion.
|
||||
type Region struct {
|
||||
Origin Origin // The location of the upper-left corner of the block.
|
||||
Size Size // The size of the block.
|
||||
}
|
||||
|
||||
// Origin represents the location of a pixel in an image or texture relative
|
||||
// to the upper-left corner, whose coordinates are (0, 0).
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
|
||||
type Origin struct{ X, Y, Z int }
|
||||
|
||||
// Size represents the set of dimensions that declare the size of an object,
|
||||
// such as an image, texture, threadgroup, or grid.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
|
||||
type Size struct{ Width, Height, Depth int }
|
||||
|
||||
// RegionMake2D returns a 2D, rectangular region for image or texture data.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
|
||||
func RegionMake2D(x, y, width, height int) Region {
|
||||
return Region{
|
||||
Origin: Origin{x, y, 0},
|
||||
Size: Size{width, height, 1},
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
// +build darwin
|
||||
|
||||
typedef signed char BOOL;
|
||||
typedef unsigned long uint_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
|
||||
struct Device {
|
||||
void * Device;
|
||||
BOOL Headless;
|
||||
BOOL LowPower;
|
||||
BOOL Removable;
|
||||
uint64_t RegistryID;
|
||||
const char * Name;
|
||||
};
|
||||
|
||||
struct Devices {
|
||||
struct Device * Devices;
|
||||
int Length;
|
||||
};
|
||||
|
||||
struct Library {
|
||||
void * Library;
|
||||
const char * Error;
|
||||
};
|
||||
|
||||
struct RenderPipelineDescriptor {
|
||||
void * VertexFunction;
|
||||
void * FragmentFunction;
|
||||
uint16_t ColorAttachment0PixelFormat;
|
||||
};
|
||||
|
||||
struct RenderPipelineState {
|
||||
void * RenderPipelineState;
|
||||
const char * Error;
|
||||
};
|
||||
|
||||
struct ClearColor {
|
||||
double Red;
|
||||
double Green;
|
||||
double Blue;
|
||||
double Alpha;
|
||||
};
|
||||
|
||||
struct RenderPassDescriptor {
|
||||
uint8_t ColorAttachment0LoadAction;
|
||||
uint8_t ColorAttachment0StoreAction;
|
||||
struct ClearColor ColorAttachment0ClearColor;
|
||||
void * ColorAttachment0Texture;
|
||||
};
|
||||
|
||||
struct TextureDescriptor {
|
||||
uint16_t PixelFormat;
|
||||
uint_t Width;
|
||||
uint_t Height;
|
||||
uint8_t StorageMode;
|
||||
};
|
||||
|
||||
struct Origin {
|
||||
uint_t X;
|
||||
uint_t Y;
|
||||
uint_t Z;
|
||||
};
|
||||
|
||||
struct Size {
|
||||
uint_t Width;
|
||||
uint_t Height;
|
||||
uint_t Depth;
|
||||
};
|
||||
|
||||
struct Region {
|
||||
struct Origin Origin;
|
||||
struct Size Size;
|
||||
};
|
||||
|
||||
struct Device CreateSystemDefaultDevice();
|
||||
struct Devices CopyAllDevices();
|
||||
|
||||
BOOL Device_SupportsFeatureSet(void * device, uint16_t featureSet);
|
||||
void * Device_MakeCommandQueue(void * device);
|
||||
struct Library Device_MakeLibrary(void * device, const char * source, size_t sourceLength);
|
||||
struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct RenderPipelineDescriptor descriptor);
|
||||
void * Device_MakeBuffer(void * device, const void * bytes, size_t length, uint16_t options);
|
||||
void * Device_MakeTexture(void * device, struct TextureDescriptor descriptor);
|
||||
|
||||
void * CommandQueue_MakeCommandBuffer(void * commandQueue);
|
||||
|
||||
void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable);
|
||||
void CommandBuffer_Commit(void * commandBuffer);
|
||||
void CommandBuffer_WaitUntilCompleted(void * commandBuffer);
|
||||
void * CommandBuffer_MakeRenderCommandEncoder(void * commandBuffer, struct RenderPassDescriptor descriptor);
|
||||
void * CommandBuffer_MakeBlitCommandEncoder(void * commandBuffer);
|
||||
|
||||
void CommandEncoder_EndEncoding(void * commandEncoder);
|
||||
|
||||
void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, void * renderPipelineState);
|
||||
void RenderCommandEncoder_SetVertexBuffer(void * renderCommandEncoder, void * buffer, uint_t offset, uint_t index);
|
||||
void RenderCommandEncoder_SetVertexBytes(void * renderCommandEncoder, const void * bytes, size_t length, uint_t index);
|
||||
void RenderCommandEncoder_DrawPrimitives(void * renderCommandEncoder, uint8_t primitiveType, uint_t vertexStart, uint_t vertexCount);
|
||||
|
||||
void BlitCommandEncoder_CopyFromTexture(void * blitCommandEncoder,
|
||||
void * srcTexture, uint_t srcSlice, uint_t srcLevel, struct Origin srcOrigin, struct Size srcSize,
|
||||
void * dstTexture, uint_t dstSlice, uint_t dstLevel, struct Origin dstOrigin);
|
||||
void BlitCommandEncoder_Synchronize(void * blitCommandEncoder, void * resource);
|
||||
|
||||
void * Library_MakeFunction(void * library, const char * name);
|
||||
|
||||
void Texture_ReplaceRegion(void * texture, struct Region region, uint_t level, void * pixelBytes, size_t bytesPerRow);
|
||||
void Texture_GetBytes(void * texture, void * pixelBytes, size_t bytesPerRow, struct Region region, uint_t level);
|
@ -0,0 +1,193 @@
|
||||
// +build darwin
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#include <stdlib.h>
|
||||
#include "mtl.h"
|
||||
|
||||
struct Device CreateSystemDefaultDevice() {
|
||||
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||
if (!device) {
|
||||
struct Device d;
|
||||
d.Device = NULL;
|
||||
return d;
|
||||
}
|
||||
|
||||
struct Device d;
|
||||
d.Device = device;
|
||||
d.Headless = device.headless;
|
||||
d.LowPower = device.lowPower;
|
||||
d.Removable = device.removable;
|
||||
d.RegistryID = device.registryID;
|
||||
d.Name = device.name.UTF8String;
|
||||
return d;
|
||||
}
|
||||
|
||||
// Caller must call free(d.devices).
|
||||
struct Devices CopyAllDevices() {
|
||||
NSArray<id<MTLDevice>> * devices = MTLCopyAllDevices();
|
||||
|
||||
struct Devices d;
|
||||
d.Devices = malloc(devices.count * sizeof(struct Device));
|
||||
for (int i = 0; i < devices.count; i++) {
|
||||
d.Devices[i].Device = devices[i];
|
||||
d.Devices[i].Headless = devices[i].headless;
|
||||
d.Devices[i].LowPower = devices[i].lowPower;
|
||||
d.Devices[i].Removable = devices[i].removable;
|
||||
d.Devices[i].RegistryID = devices[i].registryID;
|
||||
d.Devices[i].Name = devices[i].name.UTF8String;
|
||||
}
|
||||
d.Length = devices.count;
|
||||
return d;
|
||||
}
|
||||
|
||||
BOOL Device_SupportsFeatureSet(void * device, uint16_t featureSet) {
|
||||
return [(id<MTLDevice>)device supportsFeatureSet:featureSet];
|
||||
}
|
||||
|
||||
void * Device_MakeCommandQueue(void * device) {
|
||||
return [(id<MTLDevice>)device newCommandQueue];
|
||||
}
|
||||
|
||||
struct Library Device_MakeLibrary(void * device, const char * source, size_t sourceLength) {
|
||||
NSError * error;
|
||||
id<MTLLibrary> library = [(id<MTLDevice>)device
|
||||
newLibraryWithSource:[[NSString alloc] initWithBytes:source length:sourceLength encoding:NSUTF8StringEncoding]
|
||||
options:NULL // TODO.
|
||||
error:&error];
|
||||
|
||||
struct Library l;
|
||||
l.Library = library;
|
||||
if (!library) {
|
||||
l.Error = error.localizedDescription.UTF8String;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct RenderPipelineDescriptor descriptor) {
|
||||
MTLRenderPipelineDescriptor * renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
renderPipelineDescriptor.vertexFunction = descriptor.VertexFunction;
|
||||
renderPipelineDescriptor.fragmentFunction = descriptor.FragmentFunction;
|
||||
renderPipelineDescriptor.colorAttachments[0].pixelFormat = descriptor.ColorAttachment0PixelFormat;
|
||||
|
||||
NSError * error;
|
||||
id<MTLRenderPipelineState> renderPipelineState = [(id<MTLDevice>)device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor
|
||||
error:&error];
|
||||
|
||||
struct RenderPipelineState rps;
|
||||
rps.RenderPipelineState = renderPipelineState;
|
||||
if (!renderPipelineState) {
|
||||
rps.Error = error.localizedDescription.UTF8String;
|
||||
}
|
||||
return rps;
|
||||
}
|
||||
|
||||
void * Device_MakeBuffer(void * device, const void * bytes, size_t length, uint16_t options) {
|
||||
return [(id<MTLDevice>)device newBufferWithBytes:(const void *)bytes
|
||||
length:(NSUInteger)length
|
||||
options:(MTLResourceOptions)options];
|
||||
}
|
||||
|
||||
void * Device_MakeTexture(void * device, struct TextureDescriptor descriptor) {
|
||||
MTLTextureDescriptor * textureDescriptor = [[MTLTextureDescriptor alloc] init];
|
||||
textureDescriptor.pixelFormat = descriptor.PixelFormat;
|
||||
textureDescriptor.width = descriptor.Width;
|
||||
textureDescriptor.height = descriptor.Height;
|
||||
textureDescriptor.storageMode = descriptor.StorageMode;
|
||||
return [(id<MTLDevice>)device newTextureWithDescriptor:textureDescriptor];
|
||||
}
|
||||
|
||||
void * CommandQueue_MakeCommandBuffer(void * commandQueue) {
|
||||
return [(id<MTLCommandQueue>)commandQueue commandBuffer];
|
||||
}
|
||||
|
||||
void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable) {
|
||||
[(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<MTLDrawable>)drawable];
|
||||
}
|
||||
|
||||
void CommandBuffer_Commit(void * commandBuffer) {
|
||||
[(id<MTLCommandBuffer>)commandBuffer commit];
|
||||
}
|
||||
|
||||
void CommandBuffer_WaitUntilCompleted(void * commandBuffer) {
|
||||
[(id<MTLCommandBuffer>)commandBuffer waitUntilCompleted];
|
||||
}
|
||||
|
||||
void * CommandBuffer_MakeRenderCommandEncoder(void * commandBuffer, struct RenderPassDescriptor descriptor) {
|
||||
MTLRenderPassDescriptor * renderPassDescriptor = [[MTLRenderPassDescriptor alloc] init];
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = descriptor.ColorAttachment0LoadAction;
|
||||
renderPassDescriptor.colorAttachments[0].storeAction = descriptor.ColorAttachment0StoreAction;
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(
|
||||
descriptor.ColorAttachment0ClearColor.Red,
|
||||
descriptor.ColorAttachment0ClearColor.Green,
|
||||
descriptor.ColorAttachment0ClearColor.Blue,
|
||||
descriptor.ColorAttachment0ClearColor.Alpha
|
||||
);
|
||||
renderPassDescriptor.colorAttachments[0].texture = (id<MTLTexture>)descriptor.ColorAttachment0Texture;
|
||||
return [(id<MTLCommandBuffer>)commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
}
|
||||
|
||||
void * CommandBuffer_MakeBlitCommandEncoder(void * commandBuffer) {
|
||||
return [(id<MTLCommandBuffer>)commandBuffer blitCommandEncoder];
|
||||
}
|
||||
|
||||
void CommandEncoder_EndEncoding(void * commandEncoder) {
|
||||
[(id<MTLCommandEncoder>)commandEncoder endEncoding];
|
||||
}
|
||||
|
||||
void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, void * renderPipelineState) {
|
||||
[(id<MTLRenderCommandEncoder>)renderCommandEncoder setRenderPipelineState:(id<MTLRenderPipelineState>)renderPipelineState];
|
||||
}
|
||||
|
||||
void RenderCommandEncoder_SetVertexBuffer(void * renderCommandEncoder, void * buffer, uint_t offset, uint_t index) {
|
||||
[(id<MTLRenderCommandEncoder>)renderCommandEncoder setVertexBuffer:(id<MTLBuffer>)buffer
|
||||
offset:(NSUInteger)offset
|
||||
atIndex:(NSUInteger)index];
|
||||
}
|
||||
|
||||
void RenderCommandEncoder_SetVertexBytes(void * renderCommandEncoder, const void * bytes, size_t length, uint_t index) {
|
||||
[(id<MTLRenderCommandEncoder>)renderCommandEncoder setVertexBytes:bytes
|
||||
length:(NSUInteger)length
|
||||
atIndex:(NSUInteger)index];
|
||||
}
|
||||
|
||||
void RenderCommandEncoder_DrawPrimitives(void * renderCommandEncoder, uint8_t primitiveType, uint_t vertexStart, uint_t vertexCount) {
|
||||
[(id<MTLRenderCommandEncoder>)renderCommandEncoder drawPrimitives:(MTLPrimitiveType)primitiveType
|
||||
vertexStart:(NSUInteger)vertexStart
|
||||
vertexCount:(NSUInteger)vertexCount];
|
||||
}
|
||||
|
||||
void BlitCommandEncoder_CopyFromTexture(void * blitCommandEncoder,
|
||||
void * srcTexture, uint_t srcSlice, uint_t srcLevel, struct Origin srcOrigin, struct Size srcSize,
|
||||
void * dstTexture, uint_t dstSlice, uint_t dstLevel, struct Origin dstOrigin) {
|
||||
[(id<MTLBlitCommandEncoder>)blitCommandEncoder copyFromTexture:(id<MTLTexture>)srcTexture
|
||||
sourceSlice:(NSUInteger)srcSlice
|
||||
sourceLevel:(NSUInteger)srcLevel
|
||||
sourceOrigin:(MTLOrigin){srcOrigin.X, srcOrigin.Y, srcOrigin.Z}
|
||||
sourceSize:(MTLSize){srcSize.Width, srcSize.Height, srcSize.Depth}
|
||||
toTexture:(id<MTLTexture>)dstTexture
|
||||
destinationSlice:(NSUInteger)dstSlice
|
||||
destinationLevel:(NSUInteger)dstLevel
|
||||
destinationOrigin:(MTLOrigin){dstOrigin.X, dstOrigin.Y, dstOrigin.Z}];
|
||||
}
|
||||
|
||||
void BlitCommandEncoder_Synchronize(void * blitCommandEncoder, void * resource) {
|
||||
[(id<MTLBlitCommandEncoder>)blitCommandEncoder synchronizeResource:(id<MTLResource>)resource];
|
||||
}
|
||||
|
||||
void * Library_MakeFunction(void * library, const char * name) {
|
||||
return [(id<MTLLibrary>)library newFunctionWithName:[NSString stringWithUTF8String:name]];
|
||||
}
|
||||
|
||||
void Texture_ReplaceRegion(void * texture, struct Region region, uint_t level, void * pixelBytes, size_t bytesPerRow) {
|
||||
[(id<MTLTexture>)texture replaceRegion:(MTLRegion){{region.Origin.X, region.Origin.Y, region.Origin.Z}, {region.Size.Width, region.Size.Height, region.Size.Depth}}
|
||||
mipmapLevel:(NSUInteger)level
|
||||
withBytes:(void *)pixelBytes
|
||||
bytesPerRow:(NSUInteger)bytesPerRow];
|
||||
}
|
||||
|
||||
void Texture_GetBytes(void * texture, void * pixelBytes, size_t bytesPerRow, struct Region region, uint_t level) {
|
||||
[(id<MTLTexture>)texture getBytes:(void *)pixelBytes
|
||||
bytesPerRow:(NSUInteger)bytesPerRow
|
||||
fromRegion:(MTLRegion){{region.Origin.X, region.Origin.Y, region.Origin.Z}, {region.Size.Width, region.Size.Height, region.Size.Depth}}
|
||||
mipmapLevel:(NSUInteger)level];
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
This project is dual-licensed under the UNLICENSE or
|
||||
the MIT license with the SPDX identifier:
|
||||
|
||||
SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
You may use the project under the terms of either license.
|
||||
|
||||
Both licenses are reproduced below.
|
||||
|
||||
----
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 The Gio authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
The UNLICENSE
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
---
|
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gioui.org/app/internal/window"
|
||||
)
|
||||
|
||||
// extraArgs contains extra arguments to append to
|
||||
// os.Args. The arguments are separated with |.
|
||||
// Useful for running programs on mobiles where the
|
||||
// command line is not available.
|
||||
// Set with the go linker flag -X.
|
||||
var extraArgs string
|
||||
|
||||
func init() {
|
||||
if extraArgs != "" {
|
||||
args := strings.Split(extraArgs, "|")
|
||||
os.Args = append(os.Args, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// DataDir returns a path to use for application-specific
|
||||
// configuration data.
|
||||
// On desktop systems, DataDir use os.UserConfigDir.
|
||||
// On iOS NSDocumentDirectory is queried.
|
||||
// For Android Context.getFilesDir is used.
|
||||
//
|
||||
// BUG: DataDir blocks on Android until init functions
|
||||
// have completed.
|
||||
func DataDir() (string, error) {
|
||||
return dataDir()
|
||||
}
|
||||
|
||||
// Main must be called last from the program main function.
|
||||
// On most platforms Main blocks forever, for Android and
|
||||
// iOS it returns immediately to give control of the main
|
||||
// thread back to the system.
|
||||
//
|
||||
// Calling Main is necessary because some operating systems
|
||||
// require control of the main thread of the program for
|
||||
// running windows.
|
||||
func Main() {
|
||||
window.Main()
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"gioui.org/app/internal/window"
|
||||
)
|
||||
|
||||
// JavaVM returns the global JNI JavaVM.
|
||||
func JavaVM() uintptr {
|
||||
return window.JavaVM()
|
||||
}
|
||||
|
||||
// AppContext returns the global Application context as a JNI
|
||||
// jobject.
|
||||
func AppContext() uintptr {
|
||||
return window.AppContext()
|
||||
}
|
||||
|
||||
// Do invokes the function with a JNI jobject handle to the underlying
|
||||
// Android View. The function is invoked on the main thread, and the
|
||||
// handle is invalidated after the function returns.
|
||||
//
|
||||
// Note: Do may deadlock if called from the same goroutine that receives from
|
||||
// Events.
|
||||
func (w *Window) Do(f func(view uintptr)) {
|
||||
type androidDriver interface {
|
||||
Do(f func(view uintptr)) bool
|
||||
}
|
||||
success := make(chan bool)
|
||||
for {
|
||||
driver := make(chan androidDriver, 1)
|
||||
// two-stage process: first wait for a valid driver...
|
||||
w.driverDo(func() {
|
||||
driver <- w.driver.(androidDriver)
|
||||
})
|
||||
// .. then run the function on the main thread using the
|
||||
// driver. The driver Do method returns false if the
|
||||
// view was invalidated while switching to the main thread.
|
||||
window.RunOnMain(func() {
|
||||
d := <-driver
|
||||
success <- d.Do(f)
|
||||
})
|
||||
if <-success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build !android
|
||||
|
||||
package app
|
||||
|
||||
import "os"
|
||||
|
||||
func dataDir() (string, error) {
|
||||
return os.UserConfigDir()
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build android
|
||||
|
||||
package app
|
||||
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"gioui.org/app/internal/window"
|
||||
)
|
||||
|
||||
var (
|
||||
dataDirOnce sync.Once
|
||||
dataPath string
|
||||
)
|
||||
|
||||
func dataDir() (string, error) {
|
||||
dataDirOnce.Do(func() {
|
||||
dataPath = window.GetDataDir()
|
||||
// Set XDG_CACHE_HOME to make os.UserCacheDir work.
|
||||
if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
|
||||
cachePath := filepath.Join(dataPath, "cache")
|
||||
os.Setenv("XDG_CACHE_HOME", cachePath)
|
||||
}
|
||||
// Set XDG_CONFIG_HOME to make os.UserConfigDir work.
|
||||
if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
|
||||
cfgPath := filepath.Join(dataPath, "config")
|
||||
os.Setenv("XDG_CONFIG_HOME", cfgPath)
|
||||
}
|
||||
// Set HOME to make os.UserHomeDir work.
|
||||
if _, exists := os.LookupEnv("HOME"); !exists {
|
||||
os.Setenv("HOME", dataPath)
|
||||
}
|
||||
})
|
||||
return dataPath, nil
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package app provides a platform-independent interface to operating system
|
||||
functionality for running graphical user interfaces.
|
||||
|
||||
See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
Windows
|
||||
|
||||
Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
||||
is embedded in another project, NewWindow merely connects with a previously
|
||||
created window.
|
||||
|
||||
A Window is run by receiving events from its Events channel. The most
|
||||
important event is FrameEvent that prompts an update of the window
|
||||
contents and state.
|
||||
|
||||
For example:
|
||||
|
||||
import "gioui.org/unit"
|
||||
|
||||
w := app.NewWindow()
|
||||
for e := range w.Events() {
|
||||
if e, ok := e.(system.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
...
|
||||
// Completely replace the window contents and state.
|
||||
e.Frame(ops)
|
||||
}
|
||||
}
|
||||
|
||||
A program must keep receiving events from the event channel until
|
||||
DestroyEvent is received.
|
||||
|
||||
Main
|
||||
|
||||
The Main function must be called from a program's main function, to hand over
|
||||
control of the main thread to operating systems that need it.
|
||||
|
||||
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
|
||||
|
||||
For example, to display a blank but otherwise functional window:
|
||||
|
||||
package main
|
||||
|
||||
import "gioui.org/app"
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
w := app.NewWindow()
|
||||
for range w.Events() {
|
||||
}
|
||||
}()
|
||||
app.Main()
|
||||
}
|
||||
|
||||
|
||||
Event queue
|
||||
|
||||
A FrameEvent's Queue method returns an event.Queue implementation that distributes
|
||||
incoming events to the event handlers declared in the last frame.
|
||||
See the gioui.org/io/event package for more information about event handlers.
|
||||
|
||||
Permissions
|
||||
|
||||
The packages under gioui.org/app/permission should be imported
|
||||
by a Gio program or by one of its dependencies to indicate that specific
|
||||
operating-system permissions are required. Please see documentation for
|
||||
package gioui.org/app/permission for more information.
|
||||
*/
|
||||
package app
|
@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package cocoainit initializes support for multithreaded
|
||||
// programs in Cocoa.
|
||||
package cocoainit
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -xobjective-c -fmodules -fobjc-arc
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
static inline void activate_cocoa_multithreading() {
|
||||
[[NSThread new] start];
|
||||
}
|
||||
#pragma GCC visibility push(hidden)
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
C.activate_cocoa_multithreading()
|
||||
}
|
@ -0,0 +1,856 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package d3d11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu/backend"
|
||||
gunsafe "gioui.org/internal/unsafe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
type Device struct {
|
||||
dev *_ID3D11Device
|
||||
ctx *_ID3D11DeviceContext
|
||||
featLvl uint32
|
||||
floatFormat uint32
|
||||
depthStates map[depthState]*_ID3D11DepthStencilState
|
||||
blendStates map[blendState]*_ID3D11BlendState
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
// Temporary storage to avoid garbage.
|
||||
clearColor [4]float32
|
||||
viewport _D3D11_VIEWPORT
|
||||
depthState depthState
|
||||
blendState blendState
|
||||
prog *Program
|
||||
|
||||
dev *Device
|
||||
caps backend.Caps
|
||||
|
||||
// fbo is the currently bound fbo.
|
||||
fbo *Framebuffer
|
||||
}
|
||||
|
||||
type blendState struct {
|
||||
enable bool
|
||||
sfactor backend.BlendFactor
|
||||
dfactor backend.BlendFactor
|
||||
}
|
||||
|
||||
type depthState struct {
|
||||
enable bool
|
||||
mask bool
|
||||
fn backend.DepthFunc
|
||||
}
|
||||
|
||||
type Texture struct {
|
||||
backend *Backend
|
||||
format uint32
|
||||
bindings backend.BufferBinding
|
||||
tex *_ID3D11Texture2D
|
||||
sampler *_ID3D11SamplerState
|
||||
resView *_ID3D11ShaderResourceView
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
backend *Backend
|
||||
|
||||
vert struct {
|
||||
shader *_ID3D11VertexShader
|
||||
uniforms *Buffer
|
||||
}
|
||||
frag struct {
|
||||
shader *_ID3D11PixelShader
|
||||
uniforms *Buffer
|
||||
}
|
||||
}
|
||||
|
||||
type Framebuffer struct {
|
||||
dev *Device
|
||||
format uint32
|
||||
resource *_ID3D11Resource
|
||||
renderTarget *_ID3D11RenderTargetView
|
||||
depthView *_ID3D11DepthStencilView
|
||||
foreign bool
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
backend *Backend
|
||||
bind uint32
|
||||
buf *_ID3D11Buffer
|
||||
immutable bool
|
||||
}
|
||||
|
||||
type InputLayout struct {
|
||||
dev *Device
|
||||
layout *_ID3D11InputLayout
|
||||
}
|
||||
|
||||
type SwapChain struct {
|
||||
swchain *_IDXGISwapChain
|
||||
fbo *Framebuffer
|
||||
}
|
||||
|
||||
func NewDevice() (*Device, error) {
|
||||
var flags uint32
|
||||
if debug {
|
||||
flags |= _D3D11_CREATE_DEVICE_DEBUG
|
||||
}
|
||||
d3ddev, d3dctx, featLvl, err := _D3D11CreateDevice(
|
||||
_D3D_DRIVER_TYPE_HARDWARE,
|
||||
flags,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
dev := &Device{dev: d3ddev, ctx: d3dctx, featLvl: featLvl}
|
||||
if featLvl < _D3D_FEATURE_LEVEL_9_1 {
|
||||
_IUnknownRelease(unsafe.Pointer(d3ddev), d3ddev.vtbl.Release)
|
||||
_IUnknownRelease(unsafe.Pointer(d3dctx), d3dctx.vtbl.Release)
|
||||
return nil, fmt.Errorf("d3d11: feature level too low: %d", featLvl)
|
||||
}
|
||||
floatFormat, ok := detectFloatFormat(d3ddev)
|
||||
if !ok {
|
||||
_IUnknownRelease(unsafe.Pointer(d3ddev), d3ddev.vtbl.Release)
|
||||
_IUnknownRelease(unsafe.Pointer(d3dctx), d3dctx.vtbl.Release)
|
||||
return nil, fmt.Errorf("d3d11: no available floating point formats")
|
||||
}
|
||||
dev.floatFormat = floatFormat
|
||||
dev.depthStates = make(map[depthState]*_ID3D11DepthStencilState)
|
||||
dev.blendStates = make(map[blendState]*_ID3D11BlendState)
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func detectFloatFormat(dev *_ID3D11Device) (uint32, bool) {
|
||||
formats := []uint32{
|
||||
_DXGI_FORMAT_R16_FLOAT,
|
||||
_DXGI_FORMAT_R32_FLOAT,
|
||||
_DXGI_FORMAT_R16G16_FLOAT,
|
||||
_DXGI_FORMAT_R32G32_FLOAT,
|
||||
// These last two are really wasteful, but c'est la vie.
|
||||
_DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
_DXGI_FORMAT_R32G32B32A32_FLOAT,
|
||||
}
|
||||
for _, format := range formats {
|
||||
need := uint32(_D3D11_FORMAT_SUPPORT_TEXTURE2D | _D3D11_FORMAT_SUPPORT_RENDER_TARGET)
|
||||
if support, _ := dev.CheckFormatSupport(format); support&need == need {
|
||||
return format, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (d *Device) CreateSwapChain(hwnd windows.Handle) (*SwapChain, error) {
|
||||
dxgiDev, err := _IUnknownQueryInterface(unsafe.Pointer(d.dev), d.dev.vtbl.QueryInterface, &_IID_IDXGIDevice)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
adapter, err := (*_IDXGIDevice)(unsafe.Pointer(dxgiDev)).GetAdapter()
|
||||
_IUnknownRelease(unsafe.Pointer(dxgiDev), dxgiDev.vtbl.Release)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
dxgiFactory, err := (*_IDXGIObject)(unsafe.Pointer(adapter)).GetParent(&_IID_IDXGIFactory)
|
||||
_IUnknownRelease(unsafe.Pointer(adapter), adapter.vtbl.Release)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
d3dswchain, err := (*_IDXGIFactory)(unsafe.Pointer(dxgiFactory)).CreateSwapChain(
|
||||
(*_IUnknown)(unsafe.Pointer(d.dev)),
|
||||
&_DXGI_SWAP_CHAIN_DESC{
|
||||
BufferDesc: _DXGI_MODE_DESC{
|
||||
Format: _DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
},
|
||||
SampleDesc: _DXGI_SAMPLE_DESC{
|
||||
Count: 1,
|
||||
},
|
||||
BufferUsage: _DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||
BufferCount: 1,
|
||||
OutputWindow: hwnd,
|
||||
Windowed: 1,
|
||||
SwapEffect: _DXGI_SWAP_EFFECT_DISCARD,
|
||||
},
|
||||
)
|
||||
_IUnknownRelease(unsafe.Pointer(dxgiFactory), dxgiFactory.vtbl.Release)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewContext: %v", err)
|
||||
}
|
||||
return &SwapChain{swchain: d3dswchain, fbo: &Framebuffer{}}, nil
|
||||
}
|
||||
|
||||
func (s *SwapChain) Framebuffer(d *Device) (*Framebuffer, error) {
|
||||
if s.fbo.renderTarget != nil {
|
||||
return s.fbo, nil
|
||||
}
|
||||
desc, err := s.swchain.GetDesc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backBuffer, err := s.swchain.GetBuffer(0, &_IID_ID3D11Texture2D)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
texture := (*_ID3D11Resource)(unsafe.Pointer(backBuffer))
|
||||
renderTarget, err := d.dev.CreateRenderTargetView(texture)
|
||||
_IUnknownRelease(unsafe.Pointer(backBuffer), backBuffer.vtbl.Release)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depthView, err := createDepthView(d.dev, int(desc.BufferDesc.Width), int(desc.BufferDesc.Height), 24)
|
||||
if err != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(renderTarget), renderTarget.vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
s.fbo.renderTarget = renderTarget
|
||||
s.fbo.depthView = depthView
|
||||
s.fbo.dev = d
|
||||
return s.fbo, nil
|
||||
}
|
||||
|
||||
func (d *Device) Release() {
|
||||
_IUnknownRelease(unsafe.Pointer(d.ctx), d.ctx.vtbl.Release)
|
||||
_IUnknownRelease(unsafe.Pointer(d.dev), d.dev.vtbl.Release)
|
||||
d.ctx = nil
|
||||
d.dev = nil
|
||||
for _, state := range d.depthStates {
|
||||
_IUnknownRelease(unsafe.Pointer(state), state.vtbl.Release)
|
||||
}
|
||||
d.depthStates = nil
|
||||
for _, state := range d.blendStates {
|
||||
_IUnknownRelease(unsafe.Pointer(state), state.vtbl.Release)
|
||||
}
|
||||
d.blendStates = nil
|
||||
}
|
||||
|
||||
func (s *SwapChain) Resize() error {
|
||||
if s.fbo.renderTarget != nil {
|
||||
s.fbo.Release()
|
||||
}
|
||||
return s.swchain.ResizeBuffers(0, 0, 0, _DXGI_FORMAT_UNKNOWN, 0)
|
||||
}
|
||||
|
||||
func (s *SwapChain) Release() {
|
||||
_IUnknownRelease(unsafe.Pointer(s.swchain), s.swchain.vtbl.Release)
|
||||
}
|
||||
|
||||
func (s *SwapChain) Present() error {
|
||||
return s.swchain.Present(1, 0)
|
||||
}
|
||||
|
||||
func NewBackend(d *Device) (*Backend, error) {
|
||||
caps := backend.Caps{
|
||||
MaxTextureSize: 2048, // 9.1 maximum
|
||||
}
|
||||
switch {
|
||||
case d.featLvl >= _D3D_FEATURE_LEVEL_11_0:
|
||||
caps.MaxTextureSize = 16384
|
||||
case d.featLvl >= _D3D_FEATURE_LEVEL_9_3:
|
||||
caps.MaxTextureSize = 4096
|
||||
}
|
||||
b := &Backend{dev: d, caps: caps}
|
||||
// Disable backface culling to match OpenGL.
|
||||
state, err := b.dev.dev.CreateRasterizerState(&_D3D11_RASTERIZER_DESC{
|
||||
CullMode: _D3D11_CULL_NONE,
|
||||
FillMode: _D3D11_FILL_SOLID,
|
||||
DepthClipEnable: 1,
|
||||
})
|
||||
// Enable depth mask to match OpenGL.
|
||||
b.depthState.mask = true
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.dev.ctx.RSSetState(state)
|
||||
_IUnknownRelease(unsafe.Pointer(state), state.vtbl.Release)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *Backend) BeginFrame() {
|
||||
}
|
||||
|
||||
func (b *Backend) EndFrame() {
|
||||
}
|
||||
|
||||
func (b *Backend) Caps() backend.Caps {
|
||||
return b.caps
|
||||
}
|
||||
|
||||
func (b *Backend) NewTimer() backend.Timer {
|
||||
panic("timers not supported")
|
||||
}
|
||||
|
||||
func (b *Backend) IsTimeContinuous() bool {
|
||||
panic("timers not supported")
|
||||
}
|
||||
|
||||
func (b *Backend) NewTexture(format backend.TextureFormat, width, height int, minFilter, magFilter backend.TextureFilter, bindings backend.BufferBinding) (backend.Texture, error) {
|
||||
var d3dfmt uint32
|
||||
switch format {
|
||||
case backend.TextureFormatFloat:
|
||||
d3dfmt = b.dev.floatFormat
|
||||
case backend.TextureFormatSRGB:
|
||||
d3dfmt = _DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported texture format %d", format)
|
||||
}
|
||||
tex, err := b.dev.dev.CreateTexture2D(&_D3D11_TEXTURE2D_DESC{
|
||||
Width: uint32(width),
|
||||
Height: uint32(height),
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: d3dfmt,
|
||||
SampleDesc: _DXGI_SAMPLE_DESC{
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
BindFlags: convBufferBinding(bindings),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
sampler *_ID3D11SamplerState
|
||||
resView *_ID3D11ShaderResourceView
|
||||
)
|
||||
if bindings&backend.BufferBindingTexture != 0 {
|
||||
var filter uint32
|
||||
switch {
|
||||
case minFilter == backend.FilterNearest && magFilter == backend.FilterNearest:
|
||||
filter = _D3D11_FILTER_MIN_MAG_MIP_POINT
|
||||
case minFilter == backend.FilterLinear && magFilter == backend.FilterLinear:
|
||||
filter = _D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT
|
||||
default:
|
||||
_IUnknownRelease(unsafe.Pointer(tex), tex.vtbl.Release)
|
||||
return nil, fmt.Errorf("unsupported texture filter combination %d, %d", minFilter, magFilter)
|
||||
}
|
||||
var err error
|
||||
sampler, err = b.dev.dev.CreateSamplerState(&_D3D11_SAMPLER_DESC{
|
||||
Filter: filter,
|
||||
AddressU: _D3D11_TEXTURE_ADDRESS_CLAMP,
|
||||
AddressV: _D3D11_TEXTURE_ADDRESS_CLAMP,
|
||||
AddressW: _D3D11_TEXTURE_ADDRESS_CLAMP,
|
||||
MaxAnisotropy: 1,
|
||||
MinLOD: -math.MaxFloat32,
|
||||
MaxLOD: math.MaxFloat32,
|
||||
})
|
||||
if err != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(tex), tex.vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
resView, err = b.dev.dev.CreateShaderResourceViewTEX2D(
|
||||
(*_ID3D11Resource)(unsafe.Pointer(tex)),
|
||||
&_D3D11_SHADER_RESOURCE_VIEW_DESC_TEX2D{
|
||||
_D3D11_SHADER_RESOURCE_VIEW_DESC: _D3D11_SHADER_RESOURCE_VIEW_DESC{
|
||||
Format: d3dfmt,
|
||||
ViewDimension: _D3D11_SRV_DIMENSION_TEXTURE2D,
|
||||
},
|
||||
Texture2D: _D3D11_TEX2D_SRV{
|
||||
MostDetailedMip: 0,
|
||||
MipLevels: ^uint32(0),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(tex), tex.vtbl.Release)
|
||||
_IUnknownRelease(unsafe.Pointer(sampler), sampler.vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, bindings: bindings, width: width, height: height}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) CurrentFramebuffer() backend.Framebuffer {
|
||||
renderTarget := b.dev.ctx.OMGetRenderTargets()
|
||||
if renderTarget != nil {
|
||||
// Assume someone else is holding on to it.
|
||||
_IUnknownRelease(unsafe.Pointer(renderTarget), renderTarget.vtbl.Release)
|
||||
}
|
||||
if renderTarget == b.fbo.renderTarget {
|
||||
return b.fbo
|
||||
}
|
||||
return &Framebuffer{dev: b.dev, renderTarget: renderTarget, foreign: true}
|
||||
}
|
||||
|
||||
func (b *Backend) NewFramebuffer(tex backend.Texture, depthBits int) (backend.Framebuffer, error) {
|
||||
d3dtex := tex.(*Texture)
|
||||
if d3dtex.bindings&backend.BufferBindingFramebuffer == 0 {
|
||||
return nil, errors.New("the texture was created without BufferBindingFramebuffer binding")
|
||||
}
|
||||
resource := (*_ID3D11Resource)(unsafe.Pointer(d3dtex.tex))
|
||||
renderTarget, err := b.dev.dev.CreateRenderTargetView(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fbo := &Framebuffer{dev: b.dev, format: d3dtex.format, resource: resource, renderTarget: renderTarget}
|
||||
if depthBits > 0 {
|
||||
depthView, err := createDepthView(b.dev.dev, d3dtex.width, d3dtex.height, depthBits)
|
||||
if err != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(renderTarget), renderTarget.vtbl.Release)
|
||||
return nil, err
|
||||
}
|
||||
fbo.depthView = depthView
|
||||
}
|
||||
return fbo, nil
|
||||
}
|
||||
|
||||
func createDepthView(d *_ID3D11Device, width, height, depthBits int) (*_ID3D11DepthStencilView, error) {
|
||||
depthTex, err := d.CreateTexture2D(&_D3D11_TEXTURE2D_DESC{
|
||||
Width: uint32(width),
|
||||
Height: uint32(height),
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: _DXGI_FORMAT_D24_UNORM_S8_UINT,
|
||||
SampleDesc: _DXGI_SAMPLE_DESC{
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
BindFlags: _D3D11_BIND_DEPTH_STENCIL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depthView, err := d.CreateDepthStencilViewTEX2D(
|
||||
(*_ID3D11Resource)(unsafe.Pointer(depthTex)),
|
||||
&_D3D11_DEPTH_STENCIL_VIEW_DESC_TEX2D{
|
||||
Format: _DXGI_FORMAT_D24_UNORM_S8_UINT,
|
||||
ViewDimension: _D3D11_DSV_DIMENSION_TEXTURE2D,
|
||||
},
|
||||
)
|
||||
_IUnknownRelease(unsafe.Pointer(depthTex), depthTex.vtbl.Release)
|
||||
return depthView, err
|
||||
}
|
||||
|
||||
func (b *Backend) NewInputLayout(vertexShader backend.ShaderSources, layout []backend.InputDesc) (backend.InputLayout, error) {
|
||||
if len(vertexShader.Inputs) != len(layout) {
|
||||
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vertexShader.Inputs))
|
||||
}
|
||||
descs := make([]_D3D11_INPUT_ELEMENT_DESC, len(layout))
|
||||
for i, l := range layout {
|
||||
inp := vertexShader.Inputs[i]
|
||||
cname, err := windows.BytePtrFromString(inp.Semantic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var format uint32
|
||||
switch l.Type {
|
||||
case backend.DataTypeFloat:
|
||||
switch l.Size {
|
||||
case 1:
|
||||
format = _DXGI_FORMAT_R32_FLOAT
|
||||
case 2:
|
||||
format = _DXGI_FORMAT_R32G32_FLOAT
|
||||
case 3:
|
||||
format = _DXGI_FORMAT_R32G32B32_FLOAT
|
||||
case 4:
|
||||
format = _DXGI_FORMAT_R32G32B32A32_FLOAT
|
||||
default:
|
||||
panic("unsupported float data size")
|
||||
}
|
||||
case backend.DataTypeShort:
|
||||
switch l.Size {
|
||||
case 1:
|
||||
format = _DXGI_FORMAT_R16_SINT
|
||||
case 2:
|
||||
format = _DXGI_FORMAT_R16G16_SINT
|
||||
default:
|
||||
panic("unsupported float data size")
|
||||
}
|
||||
default:
|
||||
panic("unsupported data type")
|
||||
}
|
||||
descs[i] = _D3D11_INPUT_ELEMENT_DESC{
|
||||
SemanticName: cname,
|
||||
SemanticIndex: uint32(inp.SemanticIndex),
|
||||
Format: format,
|
||||
AlignedByteOffset: uint32(l.Offset),
|
||||
}
|
||||
}
|
||||
l, err := b.dev.dev.CreateInputLayout(descs, vertexShader.HLSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &InputLayout{dev: b.dev, layout: l}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewBuffer(typ backend.BufferBinding, size int) (backend.Buffer, error) {
|
||||
if typ&backend.BufferBindingUniforms != 0 {
|
||||
if typ != backend.BufferBindingUniforms {
|
||||
return nil, errors.New("uniform buffers cannot have other bindings")
|
||||
}
|
||||
if size%16 != 0 {
|
||||
return nil, fmt.Errorf("constant buffer size is %d, expected a multiple of 16", size)
|
||||
}
|
||||
}
|
||||
bind := convBufferBinding(typ)
|
||||
buf, err := b.dev.dev.CreateBuffer(&_D3D11_BUFFER_DESC{
|
||||
ByteWidth: uint32(size),
|
||||
BindFlags: bind,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Buffer{backend: b, buf: buf, bind: bind}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewImmutableBuffer(typ backend.BufferBinding, data []byte) (backend.Buffer, error) {
|
||||
if typ&backend.BufferBindingUniforms != 0 {
|
||||
if typ != backend.BufferBindingUniforms {
|
||||
return nil, errors.New("uniform buffers cannot have other bindings")
|
||||
}
|
||||
if len(data)%16 != 0 {
|
||||
return nil, fmt.Errorf("constant buffer size is %d, expected a multiple of 16", len(data))
|
||||
}
|
||||
}
|
||||
bind := convBufferBinding(typ)
|
||||
buf, err := b.dev.dev.CreateBuffer(&_D3D11_BUFFER_DESC{
|
||||
ByteWidth: uint32(len(data)),
|
||||
Usage: _D3D11_USAGE_IMMUTABLE,
|
||||
BindFlags: bind,
|
||||
}, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Buffer{backend: b, buf: buf, bind: bind, immutable: true}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewProgram(vertexShader, fragmentShader backend.ShaderSources) (backend.Program, error) {
|
||||
vs, err := b.dev.dev.CreateVertexShader(vertexShader.HLSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps, err := b.dev.dev.CreatePixelShader(fragmentShader.HLSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &Program{backend: b}
|
||||
p.vert.shader = vs
|
||||
p.frag.shader = ps
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (b *Backend) Clear(colr, colg, colb, cola float32) {
|
||||
b.clearColor = [4]float32{colr, colg, colb, cola}
|
||||
b.dev.ctx.ClearRenderTargetView(b.fbo.renderTarget, &b.clearColor)
|
||||
}
|
||||
|
||||
func (b *Backend) ClearDepth(depth float32) {
|
||||
if b.fbo.depthView != nil {
|
||||
b.dev.ctx.ClearDepthStencilView(b.fbo.depthView, _D3D11_CLEAR_DEPTH|_D3D11_CLEAR_STENCIL, depth, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) Viewport(x, y, width, height int) {
|
||||
b.viewport = _D3D11_VIEWPORT{
|
||||
TopLeftX: float32(x),
|
||||
TopLeftY: float32(y),
|
||||
Width: float32(width),
|
||||
Height: float32(height),
|
||||
MinDepth: 0.0,
|
||||
MaxDepth: 1.0,
|
||||
}
|
||||
b.dev.ctx.RSSetViewports(&b.viewport)
|
||||
}
|
||||
|
||||
func (b *Backend) DrawArrays(mode backend.DrawMode, off, count int) {
|
||||
b.prepareDraw(mode)
|
||||
b.dev.ctx.Draw(uint32(count), uint32(off))
|
||||
}
|
||||
|
||||
func (b *Backend) DrawElements(mode backend.DrawMode, off, count int) {
|
||||
b.prepareDraw(mode)
|
||||
b.dev.ctx.DrawIndexed(uint32(count), uint32(off), 0)
|
||||
}
|
||||
|
||||
func (b *Backend) prepareDraw(mode backend.DrawMode) {
|
||||
if p := b.prog; p != nil {
|
||||
b.dev.ctx.VSSetShader(p.vert.shader)
|
||||
b.dev.ctx.PSSetShader(p.frag.shader)
|
||||
if buf := p.vert.uniforms; buf != nil {
|
||||
b.dev.ctx.VSSetConstantBuffers(buf.buf)
|
||||
}
|
||||
if buf := p.frag.uniforms; buf != nil {
|
||||
b.dev.ctx.PSSetConstantBuffers(buf.buf)
|
||||
}
|
||||
}
|
||||
var topology uint32
|
||||
switch mode {
|
||||
case backend.DrawModeTriangles:
|
||||
topology = _D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
|
||||
case backend.DrawModeTriangleStrip:
|
||||
topology = _D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
|
||||
default:
|
||||
panic("unsupported draw mode")
|
||||
}
|
||||
b.dev.ctx.IASetPrimitiveTopology(topology)
|
||||
|
||||
depthState, ok := b.dev.depthStates[b.depthState]
|
||||
if !ok {
|
||||
var desc _D3D11_DEPTH_STENCIL_DESC
|
||||
if b.depthState.enable {
|
||||
desc.DepthEnable = 1
|
||||
}
|
||||
if b.depthState.mask {
|
||||
desc.DepthWriteMask = _D3D11_DEPTH_WRITE_MASK_ALL
|
||||
}
|
||||
switch b.depthState.fn {
|
||||
case backend.DepthFuncGreater:
|
||||
desc.DepthFunc = _D3D11_COMPARISON_GREATER
|
||||
case backend.DepthFuncGreaterEqual:
|
||||
desc.DepthFunc = _D3D11_COMPARISON_GREATER_EQUAL
|
||||
default:
|
||||
panic("unsupported depth func")
|
||||
}
|
||||
var err error
|
||||
depthState, err = b.dev.dev.CreateDepthStencilState(&desc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.dev.depthStates[b.depthState] = depthState
|
||||
}
|
||||
b.dev.ctx.OMSetDepthStencilState(depthState, 0)
|
||||
|
||||
blendState, ok := b.dev.blendStates[b.blendState]
|
||||
if !ok {
|
||||
var desc _D3D11_BLEND_DESC
|
||||
t0 := &desc.RenderTarget[0]
|
||||
t0.RenderTargetWriteMask = _D3D11_COLOR_WRITE_ENABLE_ALL
|
||||
t0.BlendOp = _D3D11_BLEND_OP_ADD
|
||||
t0.BlendOpAlpha = _D3D11_BLEND_OP_ADD
|
||||
if b.blendState.enable {
|
||||
t0.BlendEnable = 1
|
||||
}
|
||||
scol, salpha := toBlendFactor(b.blendState.sfactor)
|
||||
dcol, dalpha := toBlendFactor(b.blendState.dfactor)
|
||||
t0.SrcBlend = scol
|
||||
t0.SrcBlendAlpha = salpha
|
||||
t0.DestBlend = dcol
|
||||
t0.DestBlendAlpha = dalpha
|
||||
var err error
|
||||
blendState, err = b.dev.dev.CreateBlendState(&desc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.dev.blendStates[b.blendState] = blendState
|
||||
}
|
||||
b.dev.ctx.OMSetBlendState(blendState, nil, 0xffffffff)
|
||||
}
|
||||
|
||||
func (b *Backend) DepthFunc(f backend.DepthFunc) {
|
||||
b.depthState.fn = f
|
||||
}
|
||||
|
||||
func (b *Backend) SetBlend(enable bool) {
|
||||
b.blendState.enable = enable
|
||||
}
|
||||
|
||||
func (b *Backend) SetDepthTest(enable bool) {
|
||||
b.depthState.enable = enable
|
||||
}
|
||||
|
||||
func (b *Backend) DepthMask(mask bool) {
|
||||
b.depthState.mask = mask
|
||||
}
|
||||
|
||||
func (b *Backend) BlendFunc(sfactor, dfactor backend.BlendFactor) {
|
||||
b.blendState.sfactor = sfactor
|
||||
b.blendState.dfactor = dfactor
|
||||
}
|
||||
|
||||
func (t *Texture) Upload(img *image.RGBA) {
|
||||
b := img.Bounds()
|
||||
w := b.Dx()
|
||||
if img.Stride != w*4 {
|
||||
panic("unsupported stride")
|
||||
}
|
||||
start := (b.Min.X + b.Min.Y*w) * 4
|
||||
end := (b.Max.X + (b.Max.Y-1)*w) * 4
|
||||
pixels := img.Pix[start:end]
|
||||
res := (*_ID3D11Resource)(unsafe.Pointer(t.tex))
|
||||
t.backend.dev.ctx.UpdateSubresource(res, uint32(img.Stride), uint32(len(pixels)), pixels)
|
||||
}
|
||||
|
||||
func (t *Texture) Release() {
|
||||
_IUnknownRelease(unsafe.Pointer(t.tex), t.tex.vtbl.Release)
|
||||
t.tex = nil
|
||||
if t.sampler != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(t.sampler), t.sampler.vtbl.Release)
|
||||
t.sampler = nil
|
||||
}
|
||||
if t.resView != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(t.resView), t.resView.vtbl.Release)
|
||||
t.resView = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindTexture(unit int, tex backend.Texture) {
|
||||
t := tex.(*Texture)
|
||||
b.dev.ctx.PSSetSamplers(uint32(unit), t.sampler)
|
||||
b.dev.ctx.PSSetShaderResources(uint32(unit), t.resView)
|
||||
}
|
||||
|
||||
func (b *Backend) BindProgram(prog backend.Program) {
|
||||
b.prog = prog.(*Program)
|
||||
}
|
||||
|
||||
func (p *Program) Release() {
|
||||
_IUnknownRelease(unsafe.Pointer(p.vert.shader), p.vert.shader.vtbl.Release)
|
||||
_IUnknownRelease(unsafe.Pointer(p.frag.shader), p.frag.shader.vtbl.Release)
|
||||
p.vert.shader = nil
|
||||
p.frag.shader = nil
|
||||
}
|
||||
|
||||
func (p *Program) SetVertexUniforms(buf backend.Buffer) {
|
||||
p.vert.uniforms = buf.(*Buffer)
|
||||
}
|
||||
|
||||
func (p *Program) SetFragmentUniforms(buf backend.Buffer) {
|
||||
p.frag.uniforms = buf.(*Buffer)
|
||||
}
|
||||
|
||||
func (b *Backend) BindVertexBuffer(buf backend.Buffer, stride, offset int) {
|
||||
b.dev.ctx.IASetVertexBuffers(buf.(*Buffer).buf, uint32(stride), uint32(offset))
|
||||
}
|
||||
|
||||
func (b *Backend) BindIndexBuffer(buf backend.Buffer) {
|
||||
b.dev.ctx.IASetIndexBuffer(buf.(*Buffer).buf, _DXGI_FORMAT_R16_UINT, 0)
|
||||
}
|
||||
|
||||
func (b *Buffer) Upload(data []byte) {
|
||||
b.backend.dev.ctx.UpdateSubresource((*_ID3D11Resource)(unsafe.Pointer(b.buf)), 0, 0, data)
|
||||
}
|
||||
|
||||
func (b *Buffer) Release() {
|
||||
_IUnknownRelease(unsafe.Pointer(b.buf), b.buf.vtbl.Release)
|
||||
b.buf = nil
|
||||
}
|
||||
|
||||
func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
|
||||
if f.resource == nil {
|
||||
return errors.New("framebuffer does not support ReadPixels")
|
||||
}
|
||||
w, h := src.Dx(), src.Dy()
|
||||
tex, err := f.dev.dev.CreateTexture2D(&_D3D11_TEXTURE2D_DESC{
|
||||
Width: uint32(w),
|
||||
Height: uint32(h),
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: f.format,
|
||||
SampleDesc: _DXGI_SAMPLE_DESC{
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
Usage: _D3D11_USAGE_STAGING,
|
||||
CPUAccessFlags: _D3D11_CPU_ACCESS_READ,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadPixels: %v", err)
|
||||
}
|
||||
defer _IUnknownRelease(unsafe.Pointer(tex), tex.vtbl.Release)
|
||||
res := (*_ID3D11Resource)(unsafe.Pointer(tex))
|
||||
f.dev.ctx.CopySubresourceRegion(
|
||||
res,
|
||||
0, // Destination subresource.
|
||||
0, 0, 0, // Destination coordinates (x, y, z).
|
||||
f.resource,
|
||||
0, // Source subresource.
|
||||
&_D3D11_BOX{
|
||||
left: uint32(src.Min.X),
|
||||
top: uint32(src.Min.Y),
|
||||
right: uint32(src.Max.X),
|
||||
bottom: uint32(src.Max.Y),
|
||||
front: 0,
|
||||
back: 1,
|
||||
},
|
||||
)
|
||||
resMap, err := f.dev.ctx.Map(res, 0, _D3D11_MAP_READ, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadPixels: %v", err)
|
||||
}
|
||||
defer f.dev.ctx.Unmap(res, 0)
|
||||
srcPitch := w * 4
|
||||
dstPitch := int(resMap.RowPitch)
|
||||
mapSize := dstPitch * h
|
||||
data := gunsafe.SliceOf(resMap.pData)[:mapSize:mapSize]
|
||||
width := w * 4
|
||||
for r := 0; r < h; r++ {
|
||||
pixels := pixels[r*srcPitch:]
|
||||
copy(pixels[:width], data[r*dstPitch:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) BindFramebuffer(fbo backend.Framebuffer) {
|
||||
b.fbo = fbo.(*Framebuffer)
|
||||
b.dev.ctx.OMSetRenderTargets(b.fbo.renderTarget, b.fbo.depthView)
|
||||
}
|
||||
|
||||
func (f *Framebuffer) Invalidate() {
|
||||
}
|
||||
|
||||
func (f *Framebuffer) Release() {
|
||||
if f.foreign {
|
||||
panic("cannot release Framebuffer from CurrentFramebuffer")
|
||||
}
|
||||
if f.renderTarget != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(f.renderTarget), f.renderTarget.vtbl.Release)
|
||||
f.renderTarget = nil
|
||||
}
|
||||
if f.depthView != nil {
|
||||
_IUnknownRelease(unsafe.Pointer(f.depthView), f.depthView.vtbl.Release)
|
||||
f.depthView = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindInputLayout(layout backend.InputLayout) {
|
||||
b.dev.ctx.IASetInputLayout(layout.(*InputLayout).layout)
|
||||
}
|
||||
|
||||
func (l *InputLayout) Release() {
|
||||
_IUnknownRelease(unsafe.Pointer(l.layout), l.layout.vtbl.Release)
|
||||
l.layout = nil
|
||||
}
|
||||
|
||||
func convBufferBinding(typ backend.BufferBinding) uint32 {
|
||||
var bindings uint32
|
||||
if typ&backend.BufferBindingVertices != 0 {
|
||||
bindings |= _D3D11_BIND_VERTEX_BUFFER
|
||||
}
|
||||
if typ&backend.BufferBindingIndices != 0 {
|
||||
bindings |= _D3D11_BIND_INDEX_BUFFER
|
||||
}
|
||||
if typ&backend.BufferBindingUniforms != 0 {
|
||||
bindings |= _D3D11_BIND_CONSTANT_BUFFER
|
||||
}
|
||||
if typ&backend.BufferBindingTexture != 0 {
|
||||
bindings |= _D3D11_BIND_SHADER_RESOURCE
|
||||
}
|
||||
if typ&backend.BufferBindingFramebuffer != 0 {
|
||||
bindings |= _D3D11_BIND_RENDER_TARGET
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
|
||||
func toBlendFactor(f backend.BlendFactor) (uint32, uint32) {
|
||||
switch f {
|
||||
case backend.BlendFactorOne:
|
||||
return _D3D11_BLEND_ONE, _D3D11_BLEND_ONE
|
||||
case backend.BlendFactorOneMinusSrcAlpha:
|
||||
return _D3D11_BLEND_INV_SRC_ALPHA, _D3D11_BLEND_INV_SRC_ALPHA
|
||||
case backend.BlendFactorZero:
|
||||
return _D3D11_BLEND_ZERO, _D3D11_BLEND_ZERO
|
||||
case backend.BlendFactorDstColor:
|
||||
return _D3D11_BLEND_DEST_COLOR, _D3D11_BLEND_DEST_ALPHA
|
||||
default:
|
||||
panic("unsupported blend source factor")
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,279 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux windows freebsd openbsd
|
||||
|
||||
package egl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gioui.org/app/internal/glimpl"
|
||||
"gioui.org/app/internal/srgb"
|
||||
"gioui.org/gpu/backend"
|
||||
"gioui.org/gpu/gl"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
c *glimpl.Functions
|
||||
disp _EGLDisplay
|
||||
eglCtx *eglContext
|
||||
eglSurf _EGLSurface
|
||||
width, height int
|
||||
refreshFBO bool
|
||||
// For sRGB emulation.
|
||||
srgbFBO *srgb.FBO
|
||||
}
|
||||
|
||||
type eglContext struct {
|
||||
config _EGLConfig
|
||||
ctx _EGLContext
|
||||
visualID int
|
||||
srgb bool
|
||||
surfaceless bool
|
||||
}
|
||||
|
||||
var (
|
||||
nilEGLDisplay _EGLDisplay
|
||||
nilEGLSurface _EGLSurface
|
||||
nilEGLContext _EGLContext
|
||||
nilEGLConfig _EGLConfig
|
||||
EGL_DEFAULT_DISPLAY NativeDisplayType
|
||||
)
|
||||
|
||||
const (
|
||||
_EGL_ALPHA_SIZE = 0x3021
|
||||
_EGL_BLUE_SIZE = 0x3022
|
||||
_EGL_CONFIG_CAVEAT = 0x3027
|
||||
_EGL_CONTEXT_CLIENT_VERSION = 0x3098
|
||||
_EGL_DEPTH_SIZE = 0x3025
|
||||
_EGL_GL_COLORSPACE_KHR = 0x309d
|
||||
_EGL_GL_COLORSPACE_SRGB_KHR = 0x3089
|
||||
_EGL_GREEN_SIZE = 0x3023
|
||||
_EGL_EXTENSIONS = 0x3055
|
||||
_EGL_NATIVE_VISUAL_ID = 0x302e
|
||||
_EGL_NONE = 0x3038
|
||||
_EGL_OPENGL_ES2_BIT = 0x4
|
||||
_EGL_RED_SIZE = 0x3024
|
||||
_EGL_RENDERABLE_TYPE = 0x3040
|
||||
_EGL_SURFACE_TYPE = 0x3033
|
||||
_EGL_WINDOW_BIT = 0x4
|
||||
)
|
||||
|
||||
func (c *Context) Release() {
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.Release()
|
||||
c.srgbFBO = nil
|
||||
}
|
||||
c.ReleaseSurface()
|
||||
if c.eglCtx != nil {
|
||||
eglDestroyContext(c.disp, c.eglCtx.ctx)
|
||||
eglTerminate(c.disp)
|
||||
eglReleaseThread()
|
||||
c.eglCtx = nil
|
||||
}
|
||||
c.disp = nilEGLDisplay
|
||||
}
|
||||
|
||||
func (c *Context) Present() error {
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.Blit()
|
||||
}
|
||||
if !eglSwapBuffers(c.disp, c.eglSurf) {
|
||||
return fmt.Errorf("eglSwapBuffers failed (%x)", eglGetError())
|
||||
}
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.AfterPresent()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewContext(disp NativeDisplayType) (*Context, error) {
|
||||
if err := loadEGL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eglDisp := eglGetDisplay(disp)
|
||||
// eglGetDisplay can return EGL_NO_DISPLAY yet no error
|
||||
// (EGL_SUCCESS), in which case a default EGL display might be
|
||||
// available.
|
||||
if eglDisp == nilEGLDisplay {
|
||||
eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY)
|
||||
}
|
||||
if eglDisp == nilEGLDisplay {
|
||||
return nil, fmt.Errorf("eglGetDisplay failed: 0x%x", eglGetError())
|
||||
}
|
||||
eglCtx, err := createContext(eglDisp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Context{
|
||||
disp: eglDisp,
|
||||
eglCtx: eglCtx,
|
||||
c: new(glimpl.Functions),
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Context) Functions() *glimpl.Functions {
|
||||
return c.c
|
||||
}
|
||||
|
||||
func (c *Context) Backend() (backend.Device, error) {
|
||||
return gl.NewBackend(c.c)
|
||||
}
|
||||
|
||||
func (c *Context) ReleaseSurface() {
|
||||
if c.eglSurf == nilEGLSurface {
|
||||
return
|
||||
}
|
||||
// Make sure any in-flight GL commands are complete.
|
||||
c.c.Finish()
|
||||
c.ReleaseCurrent()
|
||||
eglDestroySurface(c.disp, c.eglSurf)
|
||||
c.eglSurf = nilEGLSurface
|
||||
}
|
||||
|
||||
func (c *Context) VisualID() int {
|
||||
return c.eglCtx.visualID
|
||||
}
|
||||
|
||||
func (c *Context) CreateSurface(win NativeWindowType, width, height int) error {
|
||||
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
||||
c.eglSurf = eglSurf
|
||||
c.width = width
|
||||
c.height = height
|
||||
c.refreshFBO = true
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Context) ReleaseCurrent() {
|
||||
if c.disp != nilEGLDisplay {
|
||||
eglMakeCurrent(c.disp, nilEGLSurface, nilEGLSurface, nilEGLContext)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) MakeCurrent() error {
|
||||
if c.eglSurf == nilEGLSurface && !c.eglCtx.surfaceless {
|
||||
return errors.New("no surface created yet EGL_KHR_surfaceless_context is not supported")
|
||||
}
|
||||
if !eglMakeCurrent(c.disp, c.eglSurf, c.eglSurf, c.eglCtx.ctx) {
|
||||
return fmt.Errorf("eglMakeCurrent error 0x%x", eglGetError())
|
||||
}
|
||||
if c.eglCtx.srgb || c.eglSurf == nilEGLSurface {
|
||||
return nil
|
||||
}
|
||||
if c.srgbFBO == nil {
|
||||
var err error
|
||||
c.srgbFBO, err = srgb.New(c.c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.refreshFBO {
|
||||
c.refreshFBO = false
|
||||
return c.srgbFBO.Refresh(c.width, c.height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) EnableVSync(enable bool) {
|
||||
if enable {
|
||||
eglSwapInterval(c.disp, 1)
|
||||
} else {
|
||||
eglSwapInterval(c.disp, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func createContext(disp _EGLDisplay) (*eglContext, error) {
|
||||
major, minor, ret := eglInitialize(disp)
|
||||
if !ret {
|
||||
return nil, fmt.Errorf("eglInitialize failed: 0x%x", eglGetError())
|
||||
}
|
||||
// sRGB framebuffer support on EGL 1.5 or if EGL_KHR_gl_colorspace is supported.
|
||||
exts := strings.Split(eglQueryString(disp, _EGL_EXTENSIONS), " ")
|
||||
srgb := major > 1 || minor >= 5 || hasExtension(exts, "EGL_KHR_gl_colorspace")
|
||||
attribs := []_EGLint{
|
||||
_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
|
||||
_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
|
||||
_EGL_BLUE_SIZE, 8,
|
||||
_EGL_GREEN_SIZE, 8,
|
||||
_EGL_RED_SIZE, 8,
|
||||
_EGL_CONFIG_CAVEAT, _EGL_NONE,
|
||||
}
|
||||
if srgb {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
// Some Mesa drivers crash if an sRGB framebuffer is requested without alpha.
|
||||
// https://bugs.freedesktop.org/show_bug.cgi?id=107782.
|
||||
//
|
||||
// Also, some Android devices (Samsung S9) needs alpha for sRGB to work.
|
||||
attribs = append(attribs, _EGL_ALPHA_SIZE, 1)
|
||||
}
|
||||
// Only request a depth buffer if we're going to render directly to the framebuffer.
|
||||
attribs = append(attribs, _EGL_DEPTH_SIZE, 16)
|
||||
}
|
||||
attribs = append(attribs, _EGL_NONE)
|
||||
eglCfg, ret := eglChooseConfig(disp, attribs)
|
||||
if !ret {
|
||||
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", eglGetError())
|
||||
}
|
||||
if eglCfg == nilEGLConfig {
|
||||
return nil, errors.New("eglChooseConfig returned 0 configs")
|
||||
}
|
||||
visID, ret := eglGetConfigAttrib(disp, eglCfg, _EGL_NATIVE_VISUAL_ID)
|
||||
if !ret {
|
||||
return nil, errors.New("newContext: eglGetConfigAttrib for _EGL_NATIVE_VISUAL_ID failed")
|
||||
}
|
||||
ctxAttribs := []_EGLint{
|
||||
_EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
_EGL_NONE,
|
||||
}
|
||||
eglCtx := eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs)
|
||||
if eglCtx == nilEGLContext {
|
||||
// Fall back to OpenGL ES 2 and rely on extensions.
|
||||
ctxAttribs := []_EGLint{
|
||||
_EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
_EGL_NONE,
|
||||
}
|
||||
eglCtx = eglCreateContext(disp, eglCfg, nilEGLContext, ctxAttribs)
|
||||
if eglCtx == nilEGLContext {
|
||||
return nil, fmt.Errorf("eglCreateContext failed: 0x%x", eglGetError())
|
||||
}
|
||||
}
|
||||
return &eglContext{
|
||||
config: _EGLConfig(eglCfg),
|
||||
ctx: _EGLContext(eglCtx),
|
||||
visualID: int(visID),
|
||||
srgb: srgb,
|
||||
surfaceless: hasExtension(exts, "EGL_KHR_surfaceless_context"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createSurface(disp _EGLDisplay, eglCtx *eglContext, win NativeWindowType) (_EGLSurface, error) {
|
||||
var surfAttribs []_EGLint
|
||||
if eglCtx.srgb {
|
||||
surfAttribs = append(surfAttribs, _EGL_GL_COLORSPACE_KHR, _EGL_GL_COLORSPACE_SRGB_KHR)
|
||||
}
|
||||
surfAttribs = append(surfAttribs, _EGL_NONE)
|
||||
eglSurf := eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs)
|
||||
if eglSurf == nilEGLSurface && eglCtx.srgb {
|
||||
// Try again without sRGB
|
||||
eglCtx.srgb = false
|
||||
surfAttribs = []_EGLint{_EGL_NONE}
|
||||
eglSurf = eglCreateWindowSurface(disp, eglCtx.config, win, surfAttribs)
|
||||
}
|
||||
if eglSurf == nilEGLSurface {
|
||||
return nilEGLSurface, fmt.Errorf("newContext: eglCreateWindowSurface failed 0x%x (sRGB=%v)", eglGetError(), eglCtx.srgb)
|
||||
}
|
||||
return eglSurf, nil
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package egl
|
||||
|
||||
/*
|
||||
#cgo linux,!android pkg-config: egl
|
||||
#cgo freebsd openbsd android LDFLAGS: -lEGL
|
||||
#cgo freebsd CFLAGS: -I/usr/local/include
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/lib
|
||||
#cgo openbsd CFLAGS: -I/usr/X11R6/include
|
||||
#cgo openbsd LDFLAGS: -L/usr/X11R6/lib
|
||||
#cgo CFLAGS: -DMESA_EGL_NO_X11_HEADERS
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type (
|
||||
_EGLint = C.EGLint
|
||||
_EGLDisplay = C.EGLDisplay
|
||||
_EGLConfig = C.EGLConfig
|
||||
_EGLContext = C.EGLContext
|
||||
_EGLSurface = C.EGLSurface
|
||||
NativeDisplayType = C.EGLNativeDisplayType
|
||||
NativeWindowType = C.EGLNativeWindowType
|
||||
)
|
||||
|
||||
func loadEGL() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
|
||||
var cfg C.EGLConfig
|
||||
var ncfg C.EGLint
|
||||
if C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &ncfg) != C.EGL_TRUE {
|
||||
return nilEGLConfig, false
|
||||
}
|
||||
return _EGLConfig(cfg), true
|
||||
}
|
||||
|
||||
func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
|
||||
ctx := C.eglCreateContext(disp, cfg, shareCtx, &attribs[0])
|
||||
return _EGLContext(ctx)
|
||||
}
|
||||
|
||||
func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
|
||||
return C.eglDestroySurface(disp, surf) == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
|
||||
return C.eglDestroyContext(disp, ctx) == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
|
||||
var val _EGLint
|
||||
ret := C.eglGetConfigAttrib(disp, cfg, attr, &val)
|
||||
return val, ret == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglGetError() _EGLint {
|
||||
return C.eglGetError()
|
||||
}
|
||||
|
||||
func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
|
||||
var maj, min _EGLint
|
||||
ret := C.eglInitialize(disp, &maj, &min)
|
||||
return maj, min, ret == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
|
||||
return C.eglMakeCurrent(disp, draw, read, ctx) == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglReleaseThread() bool {
|
||||
return C.eglReleaseThread() == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
|
||||
return C.eglSwapBuffers(disp, surf) == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
|
||||
return C.eglSwapInterval(disp, interval) == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglTerminate(disp _EGLDisplay) bool {
|
||||
return C.eglTerminate(disp) == C.EGL_TRUE
|
||||
}
|
||||
|
||||
func eglQueryString(disp _EGLDisplay, name _EGLint) string {
|
||||
return C.GoString(C.eglQueryString(disp, name))
|
||||
}
|
||||
|
||||
func eglGetDisplay(disp NativeDisplayType) _EGLDisplay {
|
||||
return C.eglGetDisplay(disp)
|
||||
}
|
||||
|
||||
func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win NativeWindowType, attribs []_EGLint) _EGLSurface {
|
||||
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
|
||||
return eglSurf
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package egl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/glimpl"
|
||||
gunsafe "gioui.org/internal/unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
_EGLint int32
|
||||
_EGLDisplay uintptr
|
||||
_EGLConfig uintptr
|
||||
_EGLContext uintptr
|
||||
_EGLSurface uintptr
|
||||
NativeDisplayType uintptr
|
||||
NativeWindowType uintptr
|
||||
)
|
||||
|
||||
var (
|
||||
libEGL = syscall.NewLazyDLL("libEGL.dll")
|
||||
_eglChooseConfig = libEGL.NewProc("eglChooseConfig")
|
||||
_eglCreateContext = libEGL.NewProc("eglCreateContext")
|
||||
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
|
||||
_eglDestroyContext = libEGL.NewProc("eglDestroyContext")
|
||||
_eglDestroySurface = libEGL.NewProc("eglDestroySurface")
|
||||
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
|
||||
_eglGetDisplay = libEGL.NewProc("eglGetDisplay")
|
||||
_eglGetError = libEGL.NewProc("eglGetError")
|
||||
_eglInitialize = libEGL.NewProc("eglInitialize")
|
||||
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
|
||||
_eglReleaseThread = libEGL.NewProc("eglReleaseThread")
|
||||
_eglSwapInterval = libEGL.NewProc("eglSwapInterval")
|
||||
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
|
||||
_eglTerminate = libEGL.NewProc("eglTerminate")
|
||||
_eglQueryString = libEGL.NewProc("eglQueryString")
|
||||
)
|
||||
|
||||
var loadOnce sync.Once
|
||||
|
||||
func loadEGL() error {
|
||||
var err error
|
||||
loadOnce.Do(func() {
|
||||
err = loadDLLs()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func loadDLLs() error {
|
||||
if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := loadDLL(glimpl.LibGLESv2, "libGLESv2.dll"); err != nil {
|
||||
return err
|
||||
}
|
||||
// d3dcompiler_47.dll is needed internally for shader compilation to function.
|
||||
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
|
||||
}
|
||||
|
||||
func loadDLL(dll *syscall.LazyDLL, name string) error {
|
||||
err := dll.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("egl: failed to load %s: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
|
||||
var cfg _EGLConfig
|
||||
var ncfg _EGLint
|
||||
a := &attribs[0]
|
||||
r, _, _ := _eglChooseConfig.Call(uintptr(disp), uintptr(unsafe.Pointer(a)), uintptr(unsafe.Pointer(&cfg)), 1, uintptr(unsafe.Pointer(&ncfg)))
|
||||
issue34474KeepAlive(a)
|
||||
return cfg, r != 0
|
||||
}
|
||||
|
||||
func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
|
||||
a := &attribs[0]
|
||||
c, _, _ := _eglCreateContext.Call(uintptr(disp), uintptr(cfg), uintptr(shareCtx), uintptr(unsafe.Pointer(a)))
|
||||
issue34474KeepAlive(a)
|
||||
return _EGLContext(c)
|
||||
}
|
||||
|
||||
func eglCreateWindowSurface(disp _EGLDisplay, cfg _EGLConfig, win NativeWindowType, attribs []_EGLint) _EGLSurface {
|
||||
a := &attribs[0]
|
||||
s, _, _ := _eglCreateWindowSurface.Call(uintptr(disp), uintptr(cfg), uintptr(win), uintptr(unsafe.Pointer(a)))
|
||||
issue34474KeepAlive(a)
|
||||
return _EGLSurface(s)
|
||||
}
|
||||
|
||||
func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
|
||||
r, _, _ := _eglDestroySurface.Call(uintptr(disp), uintptr(surf))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
|
||||
r, _, _ := _eglDestroyContext.Call(uintptr(disp), uintptr(ctx))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
|
||||
var val uintptr
|
||||
r, _, _ := _eglGetConfigAttrib.Call(uintptr(disp), uintptr(cfg), uintptr(attr), uintptr(unsafe.Pointer(&val)))
|
||||
return _EGLint(val), r != 0
|
||||
}
|
||||
|
||||
func eglGetDisplay(disp NativeDisplayType) _EGLDisplay {
|
||||
d, _, _ := _eglGetDisplay.Call(uintptr(disp))
|
||||
return _EGLDisplay(d)
|
||||
}
|
||||
|
||||
func eglGetError() _EGLint {
|
||||
e, _, _ := _eglGetError.Call()
|
||||
return _EGLint(e)
|
||||
}
|
||||
|
||||
func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
|
||||
var maj, min uintptr
|
||||
r, _, _ := _eglInitialize.Call(uintptr(disp), uintptr(unsafe.Pointer(&maj)), uintptr(unsafe.Pointer(&min)))
|
||||
return _EGLint(maj), _EGLint(min), r != 0
|
||||
}
|
||||
|
||||
func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
|
||||
r, _, _ := _eglMakeCurrent.Call(uintptr(disp), uintptr(draw), uintptr(read), uintptr(ctx))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglReleaseThread() bool {
|
||||
r, _, _ := _eglReleaseThread.Call()
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
|
||||
r, _, _ := _eglSwapInterval.Call(uintptr(disp), uintptr(interval))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
|
||||
r, _, _ := _eglSwapBuffers.Call(uintptr(disp), uintptr(surf))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglTerminate(disp _EGLDisplay) bool {
|
||||
r, _, _ := _eglTerminate.Call(uintptr(disp))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func eglQueryString(disp _EGLDisplay, name _EGLint) string {
|
||||
r, _, _ := _eglQueryString.Call(uintptr(disp), uintptr(name))
|
||||
return gunsafe.GoString(gunsafe.SliceOf(r))
|
||||
}
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
@ -0,0 +1,541 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin linux freebsd openbsd
|
||||
|
||||
package glimpl
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror
|
||||
#cgo linux,!android pkg-config: glesv2
|
||||
#cgo linux freebsd LDFLAGS: -ldl
|
||||
#cgo freebsd openbsd android LDFLAGS: -lGLESv2
|
||||
#cgo freebsd CFLAGS: -I/usr/local/include
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/lib
|
||||
#cgo openbsd CFLAGS: -I/usr/X11R6/include
|
||||
#cgo openbsd LDFLAGS: -L/usr/X11R6/lib
|
||||
#cgo darwin,!ios CFLAGS: -DGL_SILENCE_DEPRECATION
|
||||
#cgo darwin,!ios LDFLAGS: -framework OpenGL
|
||||
#cgo darwin,ios CFLAGS: -DGLES_SILENCE_DEPRECATION
|
||||
#cgo darwin,ios LDFLAGS: -framework OpenGLES
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "TargetConditionals.h"
|
||||
#if TARGET_OS_IPHONE
|
||||
#include <OpenGLES/ES3/gl.h>
|
||||
#else
|
||||
#include <OpenGL/gl3.h>
|
||||
#endif
|
||||
#else
|
||||
#define __USE_GNU
|
||||
#include <dlfcn.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#endif
|
||||
|
||||
static void (*_glBindBufferBase)(GLenum target, GLuint index, GLuint buffer);
|
||||
static GLuint (*_glGetUniformBlockIndex)(GLuint program, const GLchar *uniformBlockName);
|
||||
static void (*_glUniformBlockBinding)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
||||
static void (*_glInvalidateFramebuffer)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
|
||||
|
||||
static void (*_glBeginQuery)(GLenum target, GLuint id);
|
||||
static void (*_glDeleteQueries)(GLsizei n, const GLuint *ids);
|
||||
static void (*_glEndQuery)(GLenum target);
|
||||
static void (*_glGenQueries)(GLsizei n, GLuint *ids);
|
||||
static void (*_glGetQueryObjectuiv)(GLuint id, GLenum pname, GLuint *params);
|
||||
static const GLubyte* (*_glGetStringi)(GLenum name, GLuint index);
|
||||
|
||||
// The pointer-free version of glVertexAttribPointer, to avoid the Cgo pointer checks.
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uintptr_t offset) {
|
||||
glVertexAttribPointer(index, size, type, normalized, stride, (const GLvoid *)offset);
|
||||
}
|
||||
|
||||
// The pointer-free version of glDrawElements, to avoid the Cgo pointer checks.
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glDrawElements(GLenum mode, GLsizei count, GLenum type, const uintptr_t offset) {
|
||||
glDrawElements(mode, count, type, (const GLvoid *)offset);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glBindBufferBase(GLenum target, GLuint index, GLuint buffer) {
|
||||
_glBindBufferBase(target, index, buffer);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding) {
|
||||
_glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) GLuint gio_glGetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName) {
|
||||
return _glGetUniformBlockIndex(program, uniformBlockName);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glInvalidateFramebuffer(GLenum target, GLenum attachment) {
|
||||
// gl.Framebuffer invalidation is just a hint and can safely be ignored.
|
||||
if (_glInvalidateFramebuffer != NULL) {
|
||||
_glInvalidateFramebuffer(target, 1, &attachment);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glBeginQuery(GLenum target, GLenum attachment) {
|
||||
_glBeginQuery(target, attachment);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glDeleteQueries(GLsizei n, const GLuint *ids) {
|
||||
_glDeleteQueries(n, ids);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glEndQuery(GLenum target) {
|
||||
_glEndQuery(target);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) const GLubyte* gio_glGetStringi(GLenum name, GLuint index) {
|
||||
if (_glGetStringi == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return _glGetStringi(name, index);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glGenQueries(GLsizei n, GLuint *ids) {
|
||||
_glGenQueries(n, ids);
|
||||
}
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params) {
|
||||
_glGetQueryObjectuiv(id, pname, params);
|
||||
}
|
||||
|
||||
__attribute__((constructor)) static void gio_loadGLFunctions() {
|
||||
#ifdef __APPLE__
|
||||
#if TARGET_OS_IPHONE
|
||||
_glInvalidateFramebuffer = glInvalidateFramebuffer;
|
||||
_glBeginQuery = glBeginQuery;
|
||||
_glDeleteQueries = glDeleteQueries;
|
||||
_glEndQuery = glEndQuery;
|
||||
_glGenQueries = glGenQueries;
|
||||
_glGetQueryObjectuiv = glGetQueryObjectuiv;
|
||||
#endif
|
||||
_glBindBufferBase = glBindBufferBase;
|
||||
_glGetUniformBlockIndex = glGetUniformBlockIndex;
|
||||
_glUniformBlockBinding = glUniformBlockBinding;
|
||||
_glGetStringi = glGetStringi;
|
||||
#else
|
||||
// Load libGLESv3 if available.
|
||||
dlopen("libGLESv3.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
_glBindBufferBase = dlsym(RTLD_DEFAULT, "glBindBufferBase");
|
||||
_glGetUniformBlockIndex = dlsym(RTLD_DEFAULT, "glGetUniformBlockIndex");
|
||||
_glUniformBlockBinding = dlsym(RTLD_DEFAULT, "glUniformBlockBinding");
|
||||
_glInvalidateFramebuffer = dlsym(RTLD_DEFAULT, "glInvalidateFramebuffer");
|
||||
_glGetStringi = dlsym(RTLD_DEFAULT, "glGetStringi");
|
||||
// Fall back to EXT_invalidate_framebuffer if available.
|
||||
if (_glInvalidateFramebuffer == NULL) {
|
||||
_glInvalidateFramebuffer = dlsym(RTLD_DEFAULT, "glDiscardFramebufferEXT");
|
||||
}
|
||||
|
||||
_glBeginQuery = dlsym(RTLD_DEFAULT, "glBeginQuery");
|
||||
if (_glBeginQuery == NULL)
|
||||
_glBeginQuery = dlsym(RTLD_DEFAULT, "glBeginQueryEXT");
|
||||
_glDeleteQueries = dlsym(RTLD_DEFAULT, "glDeleteQueries");
|
||||
if (_glDeleteQueries == NULL)
|
||||
_glDeleteQueries = dlsym(RTLD_DEFAULT, "glDeleteQueriesEXT");
|
||||
_glEndQuery = dlsym(RTLD_DEFAULT, "glEndQuery");
|
||||
if (_glEndQuery == NULL)
|
||||
_glEndQuery = dlsym(RTLD_DEFAULT, "glEndQueryEXT");
|
||||
_glGenQueries = dlsym(RTLD_DEFAULT, "glGenQueries");
|
||||
if (_glGenQueries == NULL)
|
||||
_glGenQueries = dlsym(RTLD_DEFAULT, "glGenQueriesEXT");
|
||||
_glGetQueryObjectuiv = dlsym(RTLD_DEFAULT, "glGetQueryObjectuiv");
|
||||
if (_glGetQueryObjectuiv == NULL)
|
||||
_glGetQueryObjectuiv = dlsym(RTLD_DEFAULT, "glGetQueryObjectuivEXT");
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Functions struct {
|
||||
// gl.Query caches.
|
||||
uints [100]C.GLuint
|
||||
ints [100]C.GLint
|
||||
}
|
||||
|
||||
func (f *Functions) ActiveTexture(texture gl.Enum) {
|
||||
C.glActiveTexture(C.GLenum(texture))
|
||||
}
|
||||
|
||||
func (f *Functions) AttachShader(p gl.Program, s gl.Shader) {
|
||||
C.glAttachShader(C.GLuint(p.V), C.GLuint(s.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BeginQuery(target gl.Enum, query gl.Query) {
|
||||
C.gio_glBeginQuery(C.GLenum(target), C.GLenum(query.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BindAttribLocation(p gl.Program, a gl.Attrib, name string) {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
C.glBindAttribLocation(C.GLuint(p.V), C.GLuint(a), cname)
|
||||
}
|
||||
|
||||
func (f *Functions) BindBufferBase(target gl.Enum, index int, b gl.Buffer) {
|
||||
C.gio_glBindBufferBase(C.GLenum(target), C.GLuint(index), C.GLuint(b.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BindBuffer(target gl.Enum, b gl.Buffer) {
|
||||
C.glBindBuffer(C.GLenum(target), C.GLuint(b.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BindFramebuffer(target gl.Enum, fb gl.Framebuffer) {
|
||||
C.glBindFramebuffer(C.GLenum(target), C.GLuint(fb.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BindRenderbuffer(target gl.Enum, fb gl.Renderbuffer) {
|
||||
C.glBindRenderbuffer(C.GLenum(target), C.GLuint(fb.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BindTexture(target gl.Enum, t gl.Texture) {
|
||||
C.glBindTexture(C.GLenum(target), C.GLuint(t.V))
|
||||
}
|
||||
|
||||
func (f *Functions) BlendEquation(mode gl.Enum) {
|
||||
C.glBlendEquation(C.GLenum(mode))
|
||||
}
|
||||
|
||||
func (f *Functions) BlendFunc(sfactor, dfactor gl.Enum) {
|
||||
C.glBlendFunc(C.GLenum(sfactor), C.GLenum(dfactor))
|
||||
}
|
||||
|
||||
func (f *Functions) BufferData(target gl.Enum, src []byte, usage gl.Enum) {
|
||||
var p unsafe.Pointer
|
||||
if len(src) > 0 {
|
||||
p = unsafe.Pointer(&src[0])
|
||||
}
|
||||
C.glBufferData(C.GLenum(target), C.GLsizeiptr(len(src)), p, C.GLenum(usage))
|
||||
}
|
||||
|
||||
func (f *Functions) CheckFramebufferStatus(target gl.Enum) gl.Enum {
|
||||
return gl.Enum(C.glCheckFramebufferStatus(C.GLenum(target)))
|
||||
}
|
||||
|
||||
func (f *Functions) Clear(mask gl.Enum) {
|
||||
C.glClear(C.GLbitfield(mask))
|
||||
}
|
||||
|
||||
func (f *Functions) ClearColor(red float32, green float32, blue float32, alpha float32) {
|
||||
C.glClearColor(C.GLfloat(red), C.GLfloat(green), C.GLfloat(blue), C.GLfloat(alpha))
|
||||
}
|
||||
|
||||
func (f *Functions) ClearDepthf(d float32) {
|
||||
C.glClearDepthf(C.GLfloat(d))
|
||||
}
|
||||
|
||||
func (f *Functions) CompileShader(s gl.Shader) {
|
||||
C.glCompileShader(C.GLuint(s.V))
|
||||
}
|
||||
|
||||
func (f *Functions) CreateBuffer() gl.Buffer {
|
||||
C.glGenBuffers(1, &f.uints[0])
|
||||
return gl.Buffer{uint(f.uints[0])}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateFramebuffer() gl.Framebuffer {
|
||||
C.glGenFramebuffers(1, &f.uints[0])
|
||||
return gl.Framebuffer{uint(f.uints[0])}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateProgram() gl.Program {
|
||||
return gl.Program{uint(C.glCreateProgram())}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateQuery() gl.Query {
|
||||
C.gio_glGenQueries(1, &f.uints[0])
|
||||
return gl.Query{uint(f.uints[0])}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateRenderbuffer() gl.Renderbuffer {
|
||||
C.glGenRenderbuffers(1, &f.uints[0])
|
||||
return gl.Renderbuffer{uint(f.uints[0])}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateShader(ty gl.Enum) gl.Shader {
|
||||
return gl.Shader{uint(C.glCreateShader(C.GLenum(ty)))}
|
||||
}
|
||||
|
||||
func (f *Functions) CreateTexture() gl.Texture {
|
||||
C.glGenTextures(1, &f.uints[0])
|
||||
return gl.Texture{uint(f.uints[0])}
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteBuffer(v gl.Buffer) {
|
||||
f.uints[0] = C.GLuint(v.V)
|
||||
C.glDeleteBuffers(1, &f.uints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteFramebuffer(v gl.Framebuffer) {
|
||||
f.uints[0] = C.GLuint(v.V)
|
||||
C.glDeleteFramebuffers(1, &f.uints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteProgram(p gl.Program) {
|
||||
C.glDeleteProgram(C.GLuint(p.V))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteQuery(query gl.Query) {
|
||||
f.uints[0] = C.GLuint(query.V)
|
||||
C.gio_glDeleteQueries(1, &f.uints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteRenderbuffer(v gl.Renderbuffer) {
|
||||
f.uints[0] = C.GLuint(v.V)
|
||||
C.glDeleteRenderbuffers(1, &f.uints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteShader(s gl.Shader) {
|
||||
C.glDeleteShader(C.GLuint(s.V))
|
||||
}
|
||||
|
||||
func (f *Functions) DeleteTexture(v gl.Texture) {
|
||||
f.uints[0] = C.GLuint(v.V)
|
||||
C.glDeleteTextures(1, &f.uints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) DepthFunc(v gl.Enum) {
|
||||
C.glDepthFunc(C.GLenum(v))
|
||||
}
|
||||
|
||||
func (f *Functions) DepthMask(mask bool) {
|
||||
m := C.GLboolean(C.GL_FALSE)
|
||||
if mask {
|
||||
m = C.GLboolean(C.GL_TRUE)
|
||||
}
|
||||
C.glDepthMask(m)
|
||||
}
|
||||
|
||||
func (f *Functions) DisableVertexAttribArray(a gl.Attrib) {
|
||||
C.glDisableVertexAttribArray(C.GLuint(a))
|
||||
}
|
||||
|
||||
func (f *Functions) Disable(cap gl.Enum) {
|
||||
C.glDisable(C.GLenum(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) DrawArrays(mode gl.Enum, first int, count int) {
|
||||
C.glDrawArrays(C.GLenum(mode), C.GLint(first), C.GLsizei(count))
|
||||
}
|
||||
|
||||
func (f *Functions) DrawElements(mode gl.Enum, count int, ty gl.Enum, offset int) {
|
||||
C.gio_glDrawElements(C.GLenum(mode), C.GLsizei(count), C.GLenum(ty), C.uintptr_t(offset))
|
||||
}
|
||||
|
||||
func (f *Functions) Enable(cap gl.Enum) {
|
||||
C.glEnable(C.GLenum(cap))
|
||||
}
|
||||
|
||||
func (f *Functions) EndQuery(target gl.Enum) {
|
||||
C.gio_glEndQuery(C.GLenum(target))
|
||||
}
|
||||
|
||||
func (f *Functions) EnableVertexAttribArray(a gl.Attrib) {
|
||||
C.glEnableVertexAttribArray(C.GLuint(a))
|
||||
}
|
||||
|
||||
func (f *Functions) Finish() {
|
||||
C.glFinish()
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget gl.Enum, renderbuffer gl.Renderbuffer) {
|
||||
C.glFramebufferRenderbuffer(C.GLenum(target), C.GLenum(attachment), C.GLenum(renderbuffertarget), C.GLuint(renderbuffer.V))
|
||||
}
|
||||
|
||||
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget gl.Enum, t gl.Texture, level int) {
|
||||
C.glFramebufferTexture2D(C.GLenum(target), C.GLenum(attachment), C.GLenum(texTarget), C.GLuint(t.V), C.GLint(level))
|
||||
}
|
||||
|
||||
func (c *Functions) GetBinding(pname gl.Enum) gl.Object {
|
||||
return gl.Object{uint(c.GetInteger(pname))}
|
||||
}
|
||||
|
||||
func (f *Functions) GetError() gl.Enum {
|
||||
return gl.Enum(C.glGetError())
|
||||
}
|
||||
|
||||
func (f *Functions) GetRenderbufferParameteri(target, pname gl.Enum) int {
|
||||
C.glGetRenderbufferParameteriv(C.GLenum(target), C.GLenum(pname), &f.ints[0])
|
||||
return int(f.ints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname gl.Enum) int {
|
||||
C.glGetFramebufferAttachmentParameteriv(C.GLenum(target), C.GLenum(attachment), C.GLenum(pname), &f.ints[0])
|
||||
return int(f.ints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) GetInteger(pname gl.Enum) int {
|
||||
C.glGetIntegerv(C.GLenum(pname), &f.ints[0])
|
||||
return int(f.ints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgrami(p gl.Program, pname gl.Enum) int {
|
||||
C.glGetProgramiv(C.GLuint(p.V), C.GLenum(pname), &f.ints[0])
|
||||
return int(f.ints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) GetProgramInfoLog(p gl.Program) string {
|
||||
n := f.GetProgrami(p, gl.INFO_LOG_LENGTH)
|
||||
buf := make([]byte, n)
|
||||
C.glGetProgramInfoLog(C.GLuint(p.V), C.GLsizei(len(buf)), nil, (*C.GLchar)(unsafe.Pointer(&buf[0])))
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (f *Functions) GetQueryObjectuiv(query gl.Query, pname gl.Enum) uint {
|
||||
C.gio_glGetQueryObjectuiv(C.GLuint(query.V), C.GLenum(pname), &f.uints[0])
|
||||
return uint(f.uints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderi(s gl.Shader, pname gl.Enum) int {
|
||||
C.glGetShaderiv(C.GLuint(s.V), C.GLenum(pname), &f.ints[0])
|
||||
return int(f.ints[0])
|
||||
}
|
||||
|
||||
func (f *Functions) GetShaderInfoLog(s gl.Shader) string {
|
||||
n := f.GetShaderi(s, gl.INFO_LOG_LENGTH)
|
||||
buf := make([]byte, n)
|
||||
C.glGetShaderInfoLog(C.GLuint(s.V), C.GLsizei(len(buf)), nil, (*C.GLchar)(unsafe.Pointer(&buf[0])))
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (f *Functions) GetStringi(pname gl.Enum, index int) string {
|
||||
str := C.gio_glGetStringi(C.GLenum(pname), C.GLuint(index))
|
||||
if str == nil {
|
||||
return ""
|
||||
}
|
||||
return C.GoString((*C.char)(unsafe.Pointer(str)))
|
||||
}
|
||||
|
||||
func (f *Functions) GetString(pname gl.Enum) string {
|
||||
switch {
|
||||
case runtime.GOOS == "darwin" && pname == gl.EXTENSIONS:
|
||||
// macOS OpenGL 3 core profile doesn't support glGetString(GL_EXTENSIONS).
|
||||
// Use glGetStringi(GL_EXTENSIONS, <index>).
|
||||
var exts []string
|
||||
nexts := f.GetInteger(gl.NUM_EXTENSIONS)
|
||||
for i := 0; i < nexts; i++ {
|
||||
ext := f.GetStringi(gl.EXTENSIONS, i)
|
||||
exts = append(exts, ext)
|
||||
}
|
||||
return strings.Join(exts, " ")
|
||||
default:
|
||||
str := C.glGetString(C.GLenum(pname))
|
||||
return C.GoString((*C.char)(unsafe.Pointer(str)))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformBlockIndex(p gl.Program, name string) uint {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return uint(C.gio_glGetUniformBlockIndex(C.GLuint(p.V), cname))
|
||||
}
|
||||
|
||||
func (f *Functions) GetUniformLocation(p gl.Program, name string) gl.Uniform {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return gl.Uniform{int(C.glGetUniformLocation(C.GLuint(p.V), cname))}
|
||||
}
|
||||
|
||||
func (f *Functions) InvalidateFramebuffer(target, attachment gl.Enum) {
|
||||
C.gio_glInvalidateFramebuffer(C.GLenum(target), C.GLenum(attachment))
|
||||
}
|
||||
|
||||
func (f *Functions) LinkProgram(p gl.Program) {
|
||||
C.glLinkProgram(C.GLuint(p.V))
|
||||
}
|
||||
|
||||
func (f *Functions) PixelStorei(pname gl.Enum, param int32) {
|
||||
C.glPixelStorei(C.GLenum(pname), C.GLint(param))
|
||||
}
|
||||
|
||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||
C.glScissor(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
|
||||
}
|
||||
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty gl.Enum, data []byte) {
|
||||
var p unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
p = unsafe.Pointer(&data[0])
|
||||
}
|
||||
C.glReadPixels(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
|
||||
}
|
||||
|
||||
func (f *Functions) RenderbufferStorage(target, internalformat gl.Enum, width, height int) {
|
||||
C.glRenderbufferStorage(C.GLenum(target), C.GLenum(internalformat), C.GLsizei(width), C.GLsizei(height))
|
||||
}
|
||||
|
||||
func (f *Functions) ShaderSource(s gl.Shader, src string) {
|
||||
csrc := C.CString(src)
|
||||
defer C.free(unsafe.Pointer(csrc))
|
||||
strlen := C.GLint(len(src))
|
||||
C.glShaderSource(C.GLuint(s.V), 1, &csrc, &strlen)
|
||||
}
|
||||
|
||||
func (f *Functions) TexImage2D(target gl.Enum, level int, internalFormat int, width int, height int, format gl.Enum, ty gl.Enum, data []byte) {
|
||||
var p unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
p = unsafe.Pointer(&data[0])
|
||||
}
|
||||
C.glTexImage2D(C.GLenum(target), C.GLint(level), C.GLint(internalFormat), C.GLsizei(width), C.GLsizei(height), 0, C.GLenum(format), C.GLenum(ty), p)
|
||||
}
|
||||
|
||||
func (f *Functions) TexSubImage2D(target gl.Enum, level int, x int, y int, width int, height int, format gl.Enum, ty gl.Enum, data []byte) {
|
||||
var p unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
p = unsafe.Pointer(&data[0])
|
||||
}
|
||||
C.glTexSubImage2D(C.GLenum(target), C.GLint(level), C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(ty), p)
|
||||
}
|
||||
|
||||
func (f *Functions) TexParameteri(target, pname gl.Enum, param int) {
|
||||
C.glTexParameteri(C.GLenum(target), C.GLenum(pname), C.GLint(param))
|
||||
}
|
||||
|
||||
func (f *Functions) UniformBlockBinding(p gl.Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
C.gio_glUniformBlockBinding(C.GLuint(p.V), C.GLuint(uniformBlockIndex), C.GLuint(uniformBlockBinding))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1f(dst gl.Uniform, v float32) {
|
||||
C.glUniform1f(C.GLint(dst.V), C.GLfloat(v))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform1i(dst gl.Uniform, v int) {
|
||||
C.glUniform1i(C.GLint(dst.V), C.GLint(v))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform2f(dst gl.Uniform, v0 float32, v1 float32) {
|
||||
C.glUniform2f(C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform3f(dst gl.Uniform, v0 float32, v1 float32, v2 float32) {
|
||||
C.glUniform3f(C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2))
|
||||
}
|
||||
|
||||
func (f *Functions) Uniform4f(dst gl.Uniform, v0 float32, v1 float32, v2 float32, v3 float32) {
|
||||
C.glUniform4f(C.GLint(dst.V), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2), C.GLfloat(v3))
|
||||
}
|
||||
|
||||
func (f *Functions) UseProgram(p gl.Program) {
|
||||
C.glUseProgram(C.GLuint(p.V))
|
||||
}
|
||||
|
||||
func (f *Functions) VertexAttribPointer(dst gl.Attrib, size int, ty gl.Enum, normalized bool, stride int, offset int) {
|
||||
var n C.GLboolean = C.GL_FALSE
|
||||
if normalized {
|
||||
n = C.GL_TRUE
|
||||
}
|
||||
C.gio_glVertexAttribPointer(C.GLuint(dst), C.GLint(size), C.GLenum(ty), n, C.GLsizei(stride), C.uintptr_t(offset))
|
||||
}
|
||||
|
||||
func (f *Functions) Viewport(x int, y int, width int, height int) {
|
||||
C.glViewport(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package glimpl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
|
||||
"gioui.org/gpu/gl"
|
||||
)
|
||||
|
||||
type Functions struct {
|
||||
Ctx js.Value
|
||||
EXT_disjoint_timer_query js.Value
|
||||
EXT_disjoint_timer_query_webgl2 js.Value
|
||||
|
||||
// Cached JS arrays.
|
||||
byteBuf js.Value
|
||||
int32Buf js.Value
|
||||
}
|
||||
|
||||
func (f *Functions) Init(version int) error {
|
||||
if version < 2 {
|
||||
f.EXT_disjoint_timer_query = f.getExtension("EXT_disjoint_timer_query")
|
||||
if f.getExtension("OES_texture_half_float").IsNull() && f.getExtension("OES_texture_float").IsNull() {
|
||||
return errors.New("gl: no support for neither OES_texture_half_float nor OES_texture_float")
|
||||
}
|
||||
if f.getExtension("EXT_sRGB").IsNull() {
|
||||
return errors.New("gl: EXT_sRGB not supported")
|
||||
}
|
||||
} else {
|
||||
// WebGL2 extensions.
|
||||
f.EXT_disjoint_timer_query_webgl2 = f.getExtension("EXT_disjoint_timer_query_webgl2")
|
||||
if f.getExtension("EXT_color_buffer_half_float").IsNull() && f.getExtension("EXT_color_buffer_float").IsNull() {
|
||||
return errors.New("gl: no support for neither EXT_color_buffer_half_float nor EXT_color_buffer_float")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Functions) getExtension(name string) js.Value {
|
||||
return f.Ctx.Call("getExtension", name)
|
||||
}
|
||||
|
||||
func (f *Functions) ActiveTexture(t gl.Enum) {
|
||||
f.Ctx.Call("activeTexture", int(t))
|
||||
}
|
||||
func (f *Functions) AttachShader(p gl.Program, s gl.Shader) {
|
||||
f.Ctx.Call("attachShader", js.Value(p), js.Value(s))
|
||||
}
|
||||
func (f *Functions) BeginQuery(target gl.Enum, query gl.Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f.Ctx.Call("beginQuery", int(target), js.Value(query))
|
||||
} else {
|
||||
f.EXT_disjoint_timer_query.Call("beginQueryEXT", int(target), js.Value(query))
|
||||
}
|
||||
}
|
||||
func (f *Functions) BindAttribLocation(p gl.Program, a gl.Attrib, name string) {
|
||||
f.Ctx.Call("bindAttribLocation", js.Value(p), int(a), name)
|
||||
}
|
||||
func (f *Functions) BindBuffer(target gl.Enum, b gl.Buffer) {
|
||||
f.Ctx.Call("bindBuffer", int(target), js.Value(b))
|
||||
}
|
||||
func (f *Functions) BindBufferBase(target gl.Enum, index int, b gl.Buffer) {
|
||||
f.Ctx.Call("bindBufferBase", int(target), index, js.Value(b))
|
||||
}
|
||||
func (f *Functions) BindFramebuffer(target gl.Enum, fb gl.Framebuffer) {
|
||||
f.Ctx.Call("bindFramebuffer", int(target), js.Value(fb))
|
||||
}
|
||||
func (f *Functions) BindRenderbuffer(target gl.Enum, rb gl.Renderbuffer) {
|
||||
f.Ctx.Call("bindRenderbuffer", int(target), js.Value(rb))
|
||||
}
|
||||
func (f *Functions) BindTexture(target gl.Enum, t gl.Texture) {
|
||||
f.Ctx.Call("bindTexture", int(target), js.Value(t))
|
||||
}
|
||||
func (f *Functions) BlendEquation(mode gl.Enum) {
|
||||
f.Ctx.Call("blendEquation", int(mode))
|
||||
}
|
||||
func (f *Functions) BlendFunc(sfactor, dfactor gl.Enum) {
|
||||
f.Ctx.Call("blendFunc", int(sfactor), int(dfactor))
|
||||
}
|
||||
func (f *Functions) BufferData(target gl.Enum, src []byte, usage gl.Enum) {
|
||||
f.Ctx.Call("bufferData", int(target), f.byteArrayOf(src), int(usage))
|
||||
}
|
||||
func (f *Functions) CheckFramebufferStatus(target gl.Enum) gl.Enum {
|
||||
return gl.Enum(f.Ctx.Call("checkFramebufferStatus", int(target)).Int())
|
||||
}
|
||||
func (f *Functions) Clear(mask gl.Enum) {
|
||||
f.Ctx.Call("clear", int(mask))
|
||||
}
|
||||
func (f *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||
f.Ctx.Call("clearColor", red, green, blue, alpha)
|
||||
}
|
||||
func (f *Functions) ClearDepthf(d float32) {
|
||||
f.Ctx.Call("clearDepth", d)
|
||||
}
|
||||
func (f *Functions) CompileShader(s gl.Shader) {
|
||||
f.Ctx.Call("compileShader", js.Value(s))
|
||||
}
|
||||
func (f *Functions) CreateBuffer() gl.Buffer {
|
||||
return gl.Buffer(f.Ctx.Call("createBuffer"))
|
||||
}
|
||||
func (f *Functions) CreateFramebuffer() gl.Framebuffer {
|
||||
return gl.Framebuffer(f.Ctx.Call("createFramebuffer"))
|
||||
}
|
||||
func (f *Functions) CreateProgram() gl.Program {
|
||||
return gl.Program(f.Ctx.Call("createProgram"))
|
||||
}
|
||||
func (f *Functions) CreateQuery() gl.Query {
|
||||
return gl.Query(f.Ctx.Call("createQuery"))
|
||||
}
|
||||
func (f *Functions) CreateRenderbuffer() gl.Renderbuffer {
|
||||
return gl.Renderbuffer(f.Ctx.Call("createRenderbuffer"))
|
||||
}
|
||||
func (f *Functions) CreateShader(ty gl.Enum) gl.Shader {
|
||||
return gl.Shader(f.Ctx.Call("createShader", int(ty)))
|
||||
}
|
||||
func (f *Functions) CreateTexture() gl.Texture {
|
||||
return gl.Texture(f.Ctx.Call("createTexture"))
|
||||
}
|
||||
func (f *Functions) DeleteBuffer(v gl.Buffer) {
|
||||
f.Ctx.Call("deleteBuffer", js.Value(v))
|
||||
}
|
||||
func (f *Functions) DeleteFramebuffer(v gl.Framebuffer) {
|
||||
f.Ctx.Call("deleteFramebuffer", js.Value(v))
|
||||
}
|
||||
func (f *Functions) DeleteProgram(p gl.Program) {
|
||||
f.Ctx.Call("deleteProgram", js.Value(p))
|
||||
}
|
||||
func (f *Functions) DeleteQuery(query gl.Query) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f.Ctx.Call("deleteQuery", js.Value(query))
|
||||
} else {
|
||||
f.EXT_disjoint_timer_query.Call("deleteQueryEXT", js.Value(query))
|
||||
}
|
||||
}
|
||||
func (f *Functions) DeleteShader(s gl.Shader) {
|
||||
f.Ctx.Call("deleteShader", js.Value(s))
|
||||
}
|
||||
func (f *Functions) DeleteRenderbuffer(v gl.Renderbuffer) {
|
||||
f.Ctx.Call("deleteRenderbuffer", js.Value(v))
|
||||
}
|
||||
func (f *Functions) DeleteTexture(v gl.Texture) {
|
||||
f.Ctx.Call("deleteTexture", js.Value(v))
|
||||
}
|
||||
func (f *Functions) DepthFunc(fn gl.Enum) {
|
||||
f.Ctx.Call("depthFunc", int(fn))
|
||||
}
|
||||
func (f *Functions) DepthMask(mask bool) {
|
||||
f.Ctx.Call("depthMask", mask)
|
||||
}
|
||||
func (f *Functions) DisableVertexAttribArray(a gl.Attrib) {
|
||||
f.Ctx.Call("disableVertexAttribArray", int(a))
|
||||
}
|
||||
func (f *Functions) Disable(cap gl.Enum) {
|
||||
f.Ctx.Call("disable", int(cap))
|
||||
}
|
||||
func (f *Functions) DrawArrays(mode gl.Enum, first, count int) {
|
||||
f.Ctx.Call("drawArrays", int(mode), first, count)
|
||||
}
|
||||
func (f *Functions) DrawElements(mode gl.Enum, count int, ty gl.Enum, offset int) {
|
||||
f.Ctx.Call("drawElements", int(mode), count, int(ty), offset)
|
||||
}
|
||||
func (f *Functions) Enable(cap gl.Enum) {
|
||||
f.Ctx.Call("enable", int(cap))
|
||||
}
|
||||
func (f *Functions) EnableVertexAttribArray(a gl.Attrib) {
|
||||
f.Ctx.Call("enableVertexAttribArray", int(a))
|
||||
}
|
||||
func (f *Functions) EndQuery(target gl.Enum) {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
f.Ctx.Call("endQuery", int(target))
|
||||
} else {
|
||||
f.EXT_disjoint_timer_query.Call("endQueryEXT", int(target))
|
||||
}
|
||||
}
|
||||
func (f *Functions) Finish() {
|
||||
f.Ctx.Call("finish")
|
||||
}
|
||||
func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget gl.Enum, renderbuffer gl.Renderbuffer) {
|
||||
f.Ctx.Call("framebufferRenderbuffer", int(target), int(attachment), int(renderbuffertarget), js.Value(renderbuffer))
|
||||
}
|
||||
func (f *Functions) FramebufferTexture2D(target, attachment, texTarget gl.Enum, t gl.Texture, level int) {
|
||||
f.Ctx.Call("framebufferTexture2D", int(target), int(attachment), int(texTarget), js.Value(t), level)
|
||||
}
|
||||
func (f *Functions) GetError() gl.Enum {
|
||||
return gl.Enum(f.Ctx.Call("getError").Int())
|
||||
}
|
||||
func (f *Functions) GetRenderbufferParameteri(target, pname gl.Enum) int {
|
||||
return paramVal(f.Ctx.Call("getRenderbufferParameteri", int(pname)))
|
||||
}
|
||||
func (f *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname gl.Enum) int {
|
||||
return paramVal(f.Ctx.Call("getFramebufferAttachmentParameter", int(target), int(attachment), int(pname)))
|
||||
}
|
||||
func (f *Functions) GetBinding(pname gl.Enum) gl.Object {
|
||||
return gl.Object(f.Ctx.Call("getParameter", int(pname)))
|
||||
}
|
||||
func (f *Functions) GetInteger(pname gl.Enum) int {
|
||||
return paramVal(f.Ctx.Call("getParameter", int(pname)))
|
||||
}
|
||||
func (f *Functions) GetProgrami(p gl.Program, pname gl.Enum) int {
|
||||
return paramVal(f.Ctx.Call("getProgramParameter", js.Value(p), int(pname)))
|
||||
}
|
||||
func (f *Functions) GetProgramInfoLog(p gl.Program) string {
|
||||
return f.Ctx.Call("getProgramInfoLog", js.Value(p)).String()
|
||||
}
|
||||
func (f *Functions) GetQueryObjectuiv(query gl.Query, pname gl.Enum) uint {
|
||||
if !f.EXT_disjoint_timer_query_webgl2.IsNull() {
|
||||
return uint(paramVal(f.Ctx.Call("getQueryParameter", js.Value(query), int(pname))))
|
||||
} else {
|
||||
return uint(paramVal(f.EXT_disjoint_timer_query.Call("getQueryObjectEXT", js.Value(query), int(pname))))
|
||||
}
|
||||
}
|
||||
func (f *Functions) GetShaderi(s gl.Shader, pname gl.Enum) int {
|
||||
return paramVal(f.Ctx.Call("getShaderParameter", js.Value(s), int(pname)))
|
||||
}
|
||||
func (f *Functions) GetShaderInfoLog(s gl.Shader) string {
|
||||
return f.Ctx.Call("getShaderInfoLog", js.Value(s)).String()
|
||||
}
|
||||
func (f *Functions) GetString(pname gl.Enum) string {
|
||||
switch pname {
|
||||
case gl.EXTENSIONS:
|
||||
extsjs := f.Ctx.Call("getSupportedExtensions")
|
||||
var exts []string
|
||||
for i := 0; i < extsjs.Length(); i++ {
|
||||
exts = append(exts, "GL_"+extsjs.Index(i).String())
|
||||
}
|
||||
return strings.Join(exts, " ")
|
||||
default:
|
||||
return f.Ctx.Call("getParameter", int(pname)).String()
|
||||
}
|
||||
}
|
||||
func (f *Functions) GetUniformBlockIndex(p gl.Program, name string) uint {
|
||||
return uint(paramVal(f.Ctx.Call("getUniformBlockIndex", js.Value(p), name)))
|
||||
}
|
||||
func (f *Functions) GetUniformLocation(p gl.Program, name string) gl.Uniform {
|
||||
return gl.Uniform(f.Ctx.Call("getUniformLocation", js.Value(p), name))
|
||||
}
|
||||
func (f *Functions) InvalidateFramebuffer(target, attachment gl.Enum) {
|
||||
fn := f.Ctx.Get("invalidateFramebuffer")
|
||||
if !fn.IsUndefined() {
|
||||
if f.int32Buf.IsUndefined() {
|
||||
f.int32Buf = js.Global().Get("Int32Array").New(1)
|
||||
}
|
||||
f.int32Buf.SetIndex(0, int32(attachment))
|
||||
f.Ctx.Call("invalidateFramebuffer", int(target), f.int32Buf)
|
||||
}
|
||||
}
|
||||
func (f *Functions) LinkProgram(p gl.Program) {
|
||||
f.Ctx.Call("linkProgram", js.Value(p))
|
||||
}
|
||||
func (f *Functions) PixelStorei(pname gl.Enum, param int32) {
|
||||
f.Ctx.Call("pixelStorei", int(pname), param)
|
||||
}
|
||||
func (f *Functions) RenderbufferStorage(target, internalformat gl.Enum, width, height int) {
|
||||
f.Ctx.Call("renderbufferStorage", int(target), int(internalformat), width, height)
|
||||
}
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty gl.Enum, data []byte) {
|
||||
f.resizeByteBuffer(len(data))
|
||||
f.Ctx.Call("readPixels", x, y, width, height, int(format), int(ty), f.byteBuf)
|
||||
js.CopyBytesToGo(data, f.byteBuf)
|
||||
}
|
||||
func (f *Functions) Scissor(x, y, width, height int32) {
|
||||
f.Ctx.Call("scissor", x, y, width, height)
|
||||
}
|
||||
func (f *Functions) ShaderSource(s gl.Shader, src string) {
|
||||
f.Ctx.Call("shaderSource", js.Value(s), src)
|
||||
}
|
||||
func (f *Functions) TexImage2D(target gl.Enum, level int, internalFormat int, width, height int, format, ty gl.Enum, data []byte) {
|
||||
f.Ctx.Call("texImage2D", int(target), int(level), int(internalFormat), int(width), int(height), 0, int(format), int(ty), f.byteArrayOf(data))
|
||||
}
|
||||
func (f *Functions) TexSubImage2D(target gl.Enum, level int, x, y, width, height int, format, ty gl.Enum, data []byte) {
|
||||
f.Ctx.Call("texSubImage2D", int(target), level, x, y, width, height, int(format), int(ty), f.byteArrayOf(data))
|
||||
}
|
||||
func (f *Functions) TexParameteri(target, pname gl.Enum, param int) {
|
||||
f.Ctx.Call("texParameteri", int(target), int(pname), int(param))
|
||||
}
|
||||
func (f *Functions) UniformBlockBinding(p gl.Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
f.Ctx.Call("uniformBlockBinding", js.Value(p), int(uniformBlockIndex), int(uniformBlockBinding))
|
||||
}
|
||||
func (f *Functions) Uniform1f(dst gl.Uniform, v float32) {
|
||||
f.Ctx.Call("uniform1f", js.Value(dst), v)
|
||||
}
|
||||
func (f *Functions) Uniform1i(dst gl.Uniform, v int) {
|
||||
f.Ctx.Call("uniform1i", js.Value(dst), v)
|
||||
}
|
||||
func (f *Functions) Uniform2f(dst gl.Uniform, v0, v1 float32) {
|
||||
f.Ctx.Call("uniform2f", js.Value(dst), v0, v1)
|
||||
}
|
||||
func (f *Functions) Uniform3f(dst gl.Uniform, v0, v1, v2 float32) {
|
||||
f.Ctx.Call("uniform3f", js.Value(dst), v0, v1, v2)
|
||||
}
|
||||
func (f *Functions) Uniform4f(dst gl.Uniform, v0, v1, v2, v3 float32) {
|
||||
f.Ctx.Call("uniform4f", js.Value(dst), v0, v1, v2, v3)
|
||||
}
|
||||
func (f *Functions) UseProgram(p gl.Program) {
|
||||
f.Ctx.Call("useProgram", js.Value(p))
|
||||
}
|
||||
func (f *Functions) VertexAttribPointer(dst gl.Attrib, size int, ty gl.Enum, normalized bool, stride, offset int) {
|
||||
f.Ctx.Call("vertexAttribPointer", int(dst), size, int(ty), normalized, stride, offset)
|
||||
}
|
||||
func (f *Functions) Viewport(x, y, width, height int) {
|
||||
f.Ctx.Call("viewport", x, y, width, height)
|
||||
}
|
||||
|
||||
func (f *Functions) byteArrayOf(data []byte) js.Value {
|
||||
if len(data) == 0 {
|
||||
return js.Null()
|
||||
}
|
||||
f.resizeByteBuffer(len(data))
|
||||
js.CopyBytesToJS(f.byteBuf, data)
|
||||
return f.byteBuf
|
||||
}
|
||||
|
||||
func (f *Functions) resizeByteBuffer(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
if !f.byteBuf.IsUndefined() && f.byteBuf.Length() >= n {
|
||||
return
|
||||
}
|
||||
f.byteBuf = js.Global().Get("Uint8Array").New(n)
|
||||
}
|
||||
|
||||
func paramVal(v js.Value) int {
|
||||
switch v.Type() {
|
||||
case js.TypeBoolean:
|
||||
if b := v.Bool(); b {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
case js.TypeNumber:
|
||||
return v.Int()
|
||||
default:
|
||||
panic("unknown parameter type")
|
||||
}
|
||||
}
|
@ -0,0 +1,406 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package glimpl
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/gpu/gl"
|
||||
gunsafe "gioui.org/internal/unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
|
||||
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
|
||||
_glAttachShader = LibGLESv2.NewProc("glAttachShader")
|
||||
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
|
||||
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
|
||||
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
|
||||
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
|
||||
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
|
||||
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
|
||||
_glBindTexture = LibGLESv2.NewProc("glBindTexture")
|
||||
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
|
||||
_glBlendFunc = LibGLESv2.NewProc("glBlendFunc")
|
||||
_glBufferData = LibGLESv2.NewProc("glBufferData")
|
||||
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
|
||||
_glClear = LibGLESv2.NewProc("glClear")
|
||||
_glClearColor = LibGLESv2.NewProc("glClearColor")
|
||||
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
|
||||
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
|
||||
_glCompileShader = LibGLESv2.NewProc("glCompileShader")
|
||||
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
|
||||
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
|
||||
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
|
||||
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
|
||||
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
|
||||
_glCreateShader = LibGLESv2.NewProc("glCreateShader")
|
||||
_glGenTextures = LibGLESv2.NewProc("glGenTextures")
|
||||
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
|
||||
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
|
||||
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
|
||||
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
|
||||
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
|
||||
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
|
||||
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
|
||||
_glDepthMask = LibGLESv2.NewProc("glDepthMask")
|
||||
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
|
||||
_glDisable = LibGLESv2.NewProc("glDisable")
|
||||
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
|
||||
_glDrawElements = LibGLESv2.NewProc("glDrawElements")
|
||||
_glEnable = LibGLESv2.NewProc("glEnable")
|
||||
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
|
||||
_glEndQuery = LibGLESv2.NewProc("glEndQuery")
|
||||
_glFinish = LibGLESv2.NewProc("glFinish")
|
||||
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
|
||||
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
|
||||
_glGenQueries = LibGLESv2.NewProc("glGenQueries")
|
||||
_glGetError = LibGLESv2.NewProc("glGetError")
|
||||
_glGetRenderbufferParameteri = LibGLESv2.NewProc("glGetRenderbufferParameteri")
|
||||
_glGetFramebufferAttachmentParameteri = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteri")
|
||||
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
|
||||
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
|
||||
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
|
||||
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
|
||||
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
|
||||
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
|
||||
_glGetString = LibGLESv2.NewProc("glGetString")
|
||||
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
|
||||
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
|
||||
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
|
||||
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
|
||||
_glReadPixels = LibGLESv2.NewProc("glReadPixels")
|
||||
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
|
||||
_glScissor = LibGLESv2.NewProc("glScissor")
|
||||
_glShaderSource = LibGLESv2.NewProc("glShaderSource")
|
||||
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
|
||||
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
|
||||
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
|
||||
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
|
||||
_glUniform1f = LibGLESv2.NewProc("glUniform1f")
|
||||
_glUniform1i = LibGLESv2.NewProc("glUniform1i")
|
||||
_glUniform2f = LibGLESv2.NewProc("glUniform2f")
|
||||
_glUniform3f = LibGLESv2.NewProc("glUniform3f")
|
||||
_glUniform4f = LibGLESv2.NewProc("glUniform4f")
|
||||
_glUseProgram = LibGLESv2.NewProc("glUseProgram")
|
||||
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
|
||||
_glViewport = LibGLESv2.NewProc("glViewport")
|
||||
)
|
||||
|
||||
type Functions struct {
|
||||
// gl.Query caches.
|
||||
int32s [100]int32
|
||||
}
|
||||
|
||||
func (c *Functions) ActiveTexture(t gl.Enum) {
|
||||
syscall.Syscall(_glActiveTexture.Addr(), 1, uintptr(t), 0, 0)
|
||||
}
|
||||
func (c *Functions) AttachShader(p gl.Program, s gl.Shader) {
|
||||
syscall.Syscall(_glAttachShader.Addr(), 2, uintptr(p.V), uintptr(s.V), 0)
|
||||
}
|
||||
func (f *Functions) BeginQuery(target gl.Enum, query gl.Query) {
|
||||
syscall.Syscall(_glBeginQuery.Addr(), 2, uintptr(target), uintptr(query.V), 0)
|
||||
}
|
||||
func (c *Functions) BindAttribLocation(p gl.Program, a gl.Attrib, name string) {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
syscall.Syscall(_glBindAttribLocation.Addr(), 3, uintptr(p.V), uintptr(a), uintptr(unsafe.Pointer(c0)))
|
||||
issue34474KeepAlive(c)
|
||||
}
|
||||
func (c *Functions) BindBuffer(target gl.Enum, b gl.Buffer) {
|
||||
syscall.Syscall(_glBindBuffer.Addr(), 2, uintptr(target), uintptr(b.V), 0)
|
||||
}
|
||||
func (c *Functions) BindBufferBase(target gl.Enum, index int, b gl.Buffer) {
|
||||
syscall.Syscall(_glBindBufferBase.Addr(), 3, uintptr(target), uintptr(index), uintptr(b.V))
|
||||
}
|
||||
func (c *Functions) BindFramebuffer(target gl.Enum, fb gl.Framebuffer) {
|
||||
syscall.Syscall(_glBindFramebuffer.Addr(), 2, uintptr(target), uintptr(fb.V), 0)
|
||||
}
|
||||
func (c *Functions) BindRenderbuffer(target gl.Enum, rb gl.Renderbuffer) {
|
||||
syscall.Syscall(_glBindRenderbuffer.Addr(), 2, uintptr(target), uintptr(rb.V), 0)
|
||||
}
|
||||
func (c *Functions) BindTexture(target gl.Enum, t gl.Texture) {
|
||||
syscall.Syscall(_glBindTexture.Addr(), 2, uintptr(target), uintptr(t.V), 0)
|
||||
}
|
||||
func (c *Functions) BlendEquation(mode gl.Enum) {
|
||||
syscall.Syscall(_glBlendEquation.Addr(), 1, uintptr(mode), 0, 0)
|
||||
}
|
||||
func (c *Functions) BlendFunc(sfactor, dfactor gl.Enum) {
|
||||
syscall.Syscall(_glBlendFunc.Addr(), 2, uintptr(sfactor), uintptr(dfactor), 0)
|
||||
}
|
||||
func (c *Functions) BufferData(target gl.Enum, src []byte, usage gl.Enum) {
|
||||
if n := len(src); n == 0 {
|
||||
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), 0, 0, uintptr(usage), 0, 0)
|
||||
} else {
|
||||
s0 := &src[0]
|
||||
syscall.Syscall6(_glBufferData.Addr(), 4, uintptr(target), uintptr(n), uintptr(unsafe.Pointer(s0)), uintptr(usage), 0, 0)
|
||||
issue34474KeepAlive(s0)
|
||||
}
|
||||
}
|
||||
func (c *Functions) CheckFramebufferStatus(target gl.Enum) gl.Enum {
|
||||
s, _, _ := syscall.Syscall(_glCheckFramebufferStatus.Addr(), 1, uintptr(target), 0, 0)
|
||||
return gl.Enum(s)
|
||||
}
|
||||
func (c *Functions) Clear(mask gl.Enum) {
|
||||
syscall.Syscall(_glClear.Addr(), 1, uintptr(mask), 0, 0)
|
||||
}
|
||||
func (c *Functions) ClearColor(red, green, blue, alpha float32) {
|
||||
syscall.Syscall6(_glClearColor.Addr(), 4, uintptr(math.Float32bits(red)), uintptr(math.Float32bits(green)), uintptr(math.Float32bits(blue)), uintptr(math.Float32bits(alpha)), 0, 0)
|
||||
}
|
||||
func (c *Functions) ClearDepthf(d float32) {
|
||||
syscall.Syscall(_glClearDepthf.Addr(), 1, uintptr(math.Float32bits(d)), 0, 0)
|
||||
}
|
||||
func (c *Functions) CompileShader(s gl.Shader) {
|
||||
syscall.Syscall(_glCompileShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
func (c *Functions) CreateBuffer() gl.Buffer {
|
||||
var buf uintptr
|
||||
syscall.Syscall(_glGenBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&buf)), 0)
|
||||
return gl.Buffer{uint(buf)}
|
||||
}
|
||||
func (c *Functions) CreateFramebuffer() gl.Framebuffer {
|
||||
var fb uintptr
|
||||
syscall.Syscall(_glGenFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&fb)), 0)
|
||||
return gl.Framebuffer{uint(fb)}
|
||||
}
|
||||
func (c *Functions) CreateProgram() gl.Program {
|
||||
p, _, _ := syscall.Syscall(_glCreateProgram.Addr(), 0, 0, 0, 0)
|
||||
return gl.Program{uint(p)}
|
||||
}
|
||||
func (f *Functions) CreateQuery() gl.Query {
|
||||
var q uintptr
|
||||
syscall.Syscall(_glGenQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&q)), 0)
|
||||
return gl.Query{uint(q)}
|
||||
}
|
||||
func (c *Functions) CreateRenderbuffer() gl.Renderbuffer {
|
||||
var rb uintptr
|
||||
syscall.Syscall(_glGenRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&rb)), 0)
|
||||
return gl.Renderbuffer{uint(rb)}
|
||||
}
|
||||
func (c *Functions) CreateShader(ty gl.Enum) gl.Shader {
|
||||
s, _, _ := syscall.Syscall(_glCreateShader.Addr(), 1, uintptr(ty), 0, 0)
|
||||
return gl.Shader{uint(s)}
|
||||
}
|
||||
func (c *Functions) CreateTexture() gl.Texture {
|
||||
var t uintptr
|
||||
syscall.Syscall(_glGenTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&t)), 0)
|
||||
return gl.Texture{uint(t)}
|
||||
}
|
||||
func (c *Functions) DeleteBuffer(v gl.Buffer) {
|
||||
syscall.Syscall(_glDeleteBuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v)), 0)
|
||||
}
|
||||
func (c *Functions) DeleteFramebuffer(v gl.Framebuffer) {
|
||||
syscall.Syscall(_glDeleteFramebuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
func (c *Functions) DeleteProgram(p gl.Program) {
|
||||
syscall.Syscall(_glDeleteProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
func (f *Functions) DeleteQuery(query gl.Query) {
|
||||
syscall.Syscall(_glDeleteQueries.Addr(), 2, 1, uintptr(unsafe.Pointer(&query.V)), 0)
|
||||
}
|
||||
func (c *Functions) DeleteShader(s gl.Shader) {
|
||||
syscall.Syscall(_glDeleteShader.Addr(), 1, uintptr(s.V), 0, 0)
|
||||
}
|
||||
func (c *Functions) DeleteRenderbuffer(v gl.Renderbuffer) {
|
||||
syscall.Syscall(_glDeleteRenderbuffers.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
func (c *Functions) DeleteTexture(v gl.Texture) {
|
||||
syscall.Syscall(_glDeleteTextures.Addr(), 2, 1, uintptr(unsafe.Pointer(&v.V)), 0)
|
||||
}
|
||||
func (c *Functions) DepthFunc(f gl.Enum) {
|
||||
syscall.Syscall(_glDepthFunc.Addr(), 1, uintptr(f), 0, 0)
|
||||
}
|
||||
func (c *Functions) DepthMask(mask bool) {
|
||||
var m uintptr
|
||||
if mask {
|
||||
m = 1
|
||||
}
|
||||
syscall.Syscall(_glDepthMask.Addr(), 1, m, 0, 0)
|
||||
}
|
||||
func (c *Functions) DisableVertexAttribArray(a gl.Attrib) {
|
||||
syscall.Syscall(_glDisableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
func (c *Functions) Disable(cap gl.Enum) {
|
||||
syscall.Syscall(_glDisable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
func (c *Functions) DrawArrays(mode gl.Enum, first, count int) {
|
||||
syscall.Syscall(_glDrawArrays.Addr(), 3, uintptr(mode), uintptr(first), uintptr(count))
|
||||
}
|
||||
func (c *Functions) DrawElements(mode gl.Enum, count int, ty gl.Enum, offset int) {
|
||||
syscall.Syscall6(_glDrawElements.Addr(), 4, uintptr(mode), uintptr(count), uintptr(ty), uintptr(offset), 0, 0)
|
||||
}
|
||||
func (c *Functions) Enable(cap gl.Enum) {
|
||||
syscall.Syscall(_glEnable.Addr(), 1, uintptr(cap), 0, 0)
|
||||
}
|
||||
func (c *Functions) EnableVertexAttribArray(a gl.Attrib) {
|
||||
syscall.Syscall(_glEnableVertexAttribArray.Addr(), 1, uintptr(a), 0, 0)
|
||||
}
|
||||
func (f *Functions) EndQuery(target gl.Enum) {
|
||||
syscall.Syscall(_glEndQuery.Addr(), 1, uintptr(target), 0, 0)
|
||||
}
|
||||
func (c *Functions) Finish() {
|
||||
syscall.Syscall(_glFinish.Addr(), 0, 0, 0, 0)
|
||||
}
|
||||
func (c *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget gl.Enum, renderbuffer gl.Renderbuffer) {
|
||||
syscall.Syscall6(_glFramebufferRenderbuffer.Addr(), 4, uintptr(target), uintptr(attachment), uintptr(renderbuffertarget), uintptr(renderbuffer.V), 0, 0)
|
||||
}
|
||||
func (c *Functions) FramebufferTexture2D(target, attachment, texTarget gl.Enum, t gl.Texture, level int) {
|
||||
syscall.Syscall6(_glFramebufferTexture2D.Addr(), 5, uintptr(target), uintptr(attachment), uintptr(texTarget), uintptr(t.V), uintptr(level), 0)
|
||||
}
|
||||
func (f *Functions) GetUniformBlockIndex(p gl.Program, name string) uint {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
u, _, _ := syscall.Syscall(_glGetUniformBlockIndex.Addr(), 2, uintptr(p.V), uintptr(unsafe.Pointer(c0)), 0)
|
||||
issue34474KeepAlive(c0)
|
||||
return uint(u)
|
||||
}
|
||||
func (c *Functions) GetBinding(pname gl.Enum) gl.Object {
|
||||
return gl.Object{uint(c.GetInteger(pname))}
|
||||
}
|
||||
func (c *Functions) GetError() gl.Enum {
|
||||
e, _, _ := syscall.Syscall(_glGetError.Addr(), 0, 0, 0, 0)
|
||||
return gl.Enum(e)
|
||||
}
|
||||
func (c *Functions) GetRenderbufferParameteri(target, pname gl.Enum) int {
|
||||
p, _, _ := syscall.Syscall(_glGetRenderbufferParameteri.Addr(), 2, uintptr(target), uintptr(pname), 0)
|
||||
return int(p)
|
||||
}
|
||||
func (c *Functions) GetFramebufferAttachmentParameteri(target, attachment, pname gl.Enum) int {
|
||||
p, _, _ := syscall.Syscall(_glGetFramebufferAttachmentParameteri.Addr(), 3, uintptr(target), uintptr(attachment), uintptr(pname))
|
||||
return int(p)
|
||||
}
|
||||
func (c *Functions) GetInteger(pname gl.Enum) int {
|
||||
syscall.Syscall(_glGetIntegerv.Addr(), 2, uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])), 0)
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
func (c *Functions) GetProgrami(p gl.Program, pname gl.Enum) int {
|
||||
syscall.Syscall(_glGetProgramiv.Addr(), 3, uintptr(p.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
func (c *Functions) GetProgramInfoLog(p gl.Program) string {
|
||||
n := c.GetProgrami(p, gl.INFO_LOG_LENGTH)
|
||||
buf := make([]byte, n)
|
||||
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||
return string(buf)
|
||||
}
|
||||
func (c *Functions) GetQueryObjectuiv(query gl.Query, pname gl.Enum) uint {
|
||||
syscall.Syscall(_glGetQueryObjectuiv.Addr(), 3, uintptr(query.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return uint(c.int32s[0])
|
||||
}
|
||||
func (c *Functions) GetShaderi(s gl.Shader, pname gl.Enum) int {
|
||||
syscall.Syscall(_glGetShaderiv.Addr(), 3, uintptr(s.V), uintptr(pname), uintptr(unsafe.Pointer(&c.int32s[0])))
|
||||
return int(c.int32s[0])
|
||||
}
|
||||
func (c *Functions) GetShaderInfoLog(s gl.Shader) string {
|
||||
n := c.GetShaderi(s, gl.INFO_LOG_LENGTH)
|
||||
buf := make([]byte, n)
|
||||
syscall.Syscall6(_glGetShaderInfoLog.Addr(), 4, uintptr(s.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||
return string(buf)
|
||||
}
|
||||
func (c *Functions) GetString(pname gl.Enum) string {
|
||||
s, _, _ := syscall.Syscall(_glGetString.Addr(), 1, uintptr(pname), 0, 0)
|
||||
return gunsafe.GoString(gunsafe.SliceOf(s))
|
||||
}
|
||||
func (c *Functions) GetUniformLocation(p gl.Program, name string) gl.Uniform {
|
||||
cname := cString(name)
|
||||
c0 := &cname[0]
|
||||
u, _, _ := syscall.Syscall(_glGetUniformLocation.Addr(), 2, uintptr(p.V), uintptr(unsafe.Pointer(c0)), 0)
|
||||
issue34474KeepAlive(c0)
|
||||
return gl.Uniform{int(u)}
|
||||
}
|
||||
func (c *Functions) InvalidateFramebuffer(target, attachment gl.Enum) {
|
||||
addr := _glInvalidateFramebuffer.Addr()
|
||||
if addr == 0 {
|
||||
// InvalidateFramebuffer is just a hint. Skip it if not supported.
|
||||
return
|
||||
}
|
||||
syscall.Syscall(addr, 3, uintptr(target), 1, uintptr(unsafe.Pointer(&attachment)))
|
||||
}
|
||||
func (c *Functions) LinkProgram(p gl.Program) {
|
||||
syscall.Syscall(_glLinkProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
func (c *Functions) PixelStorei(pname gl.Enum, param int32) {
|
||||
syscall.Syscall(_glPixelStorei.Addr(), 2, uintptr(pname), uintptr(param), 0)
|
||||
}
|
||||
func (f *Functions) ReadPixels(x, y, width, height int, format, ty gl.Enum, data []byte) {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glReadPixels.Addr(), 7, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)), 0, 0)
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
func (c *Functions) RenderbufferStorage(target, internalformat gl.Enum, width, height int) {
|
||||
syscall.Syscall6(_glRenderbufferStorage.Addr(), 4, uintptr(target), uintptr(internalformat), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
func (c *Functions) Scissor(x, y, width, height int32) {
|
||||
syscall.Syscall6(_glScissor.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
func (c *Functions) ShaderSource(s gl.Shader, src string) {
|
||||
var n uintptr = uintptr(len(src))
|
||||
psrc := &src
|
||||
syscall.Syscall6(_glShaderSource.Addr(), 4, uintptr(s.V), 1, uintptr(unsafe.Pointer(psrc)), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||
issue34474KeepAlive(psrc)
|
||||
}
|
||||
func (c *Functions) TexImage2D(target gl.Enum, level int, internalFormat int, width, height int, format, ty gl.Enum, data []byte) {
|
||||
if len(data) == 0 {
|
||||
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), 0)
|
||||
} else {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glTexImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(internalFormat), uintptr(width), uintptr(height), 0, uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
}
|
||||
func (c *Functions) TexSubImage2D(target gl.Enum, level int, x, y, width, height int, format, ty gl.Enum, data []byte) {
|
||||
d0 := &data[0]
|
||||
syscall.Syscall9(_glTexSubImage2D.Addr(), 9, uintptr(target), uintptr(level), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(format), uintptr(ty), uintptr(unsafe.Pointer(d0)))
|
||||
issue34474KeepAlive(d0)
|
||||
}
|
||||
func (c *Functions) TexParameteri(target, pname gl.Enum, param int) {
|
||||
syscall.Syscall(_glTexParameteri.Addr(), 3, uintptr(target), uintptr(pname), uintptr(param))
|
||||
}
|
||||
func (f *Functions) UniformBlockBinding(p gl.Program, uniformBlockIndex uint, uniformBlockBinding uint) {
|
||||
syscall.Syscall(_glUniformBlockBinding.Addr(), 3, uintptr(p.V), uintptr(uniformBlockIndex), uintptr(uniformBlockBinding))
|
||||
}
|
||||
func (c *Functions) Uniform1f(dst gl.Uniform, v float32) {
|
||||
syscall.Syscall(_glUniform1f.Addr(), 2, uintptr(dst.V), uintptr(math.Float32bits(v)), 0)
|
||||
}
|
||||
func (c *Functions) Uniform1i(dst gl.Uniform, v int) {
|
||||
syscall.Syscall(_glUniform1i.Addr(), 2, uintptr(dst.V), uintptr(v), 0)
|
||||
}
|
||||
func (c *Functions) Uniform2f(dst gl.Uniform, v0, v1 float32) {
|
||||
syscall.Syscall(_glUniform2f.Addr(), 3, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)))
|
||||
}
|
||||
func (c *Functions) Uniform3f(dst gl.Uniform, v0, v1, v2 float32) {
|
||||
syscall.Syscall6(_glUniform3f.Addr(), 4, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), 0, 0)
|
||||
}
|
||||
func (c *Functions) Uniform4f(dst gl.Uniform, v0, v1, v2, v3 float32) {
|
||||
syscall.Syscall6(_glUniform4f.Addr(), 5, uintptr(dst.V), uintptr(math.Float32bits(v0)), uintptr(math.Float32bits(v1)), uintptr(math.Float32bits(v2)), uintptr(math.Float32bits(v3)), 0)
|
||||
}
|
||||
func (c *Functions) UseProgram(p gl.Program) {
|
||||
syscall.Syscall(_glUseProgram.Addr(), 1, uintptr(p.V), 0, 0)
|
||||
}
|
||||
func (c *Functions) VertexAttribPointer(dst gl.Attrib, size int, ty gl.Enum, normalized bool, stride, offset int) {
|
||||
var norm uintptr
|
||||
if normalized {
|
||||
norm = 1
|
||||
}
|
||||
syscall.Syscall6(_glVertexAttribPointer.Addr(), 6, uintptr(dst), uintptr(size), uintptr(ty), norm, uintptr(stride), uintptr(offset))
|
||||
}
|
||||
func (c *Functions) Viewport(x, y, width, height int) {
|
||||
syscall.Syscall6(_glViewport.Addr(), 4, uintptr(x), uintptr(y), uintptr(width), uintptr(height), 0, 0)
|
||||
}
|
||||
|
||||
func cString(s string) []byte {
|
||||
b := make([]byte, len(s)+1)
|
||||
copy(b, s)
|
||||
return b
|
||||
}
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package points standard output, standard error and the standard
|
||||
// library package log to the platform logger.
|
||||
package log
|
||||
|
||||
var appID = "gio"
|
@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <android/log.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(appID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(new(androidLogWriter))
|
||||
|
||||
// Redirect stdout and stderr to the Android logger.
|
||||
logFd(os.Stdout.Fd())
|
||||
logFd(os.Stderr.Fd())
|
||||
}
|
||||
|
||||
type androidLogWriter struct {
|
||||
// buf has room for the maximum log line, plus a terminating '\0'.
|
||||
buf [logLineLimit + 1]byte
|
||||
}
|
||||
|
||||
func (w *androidLogWriter) Write(data []byte) (int, error) {
|
||||
// Truncate the buffer, leaving space for the '\0'.
|
||||
if max := len(w.buf) - 1; len(data) > max {
|
||||
data = data[:max]
|
||||
}
|
||||
buf := w.buf[:len(data)+1]
|
||||
copy(buf, data)
|
||||
// Terminating '\0'.
|
||||
buf[len(data)] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, logTag, (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
func logFd(fd uintptr) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := syscall.Dup3(int(w.Fd()), int(fd), syscall.O_CLOEXEC); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
lineBuf := bufio.NewReaderSize(r, logLineLimit)
|
||||
// The buffer to pass to C, including the terminating '\0'.
|
||||
buf := make([]byte, lineBuf.Size()+1)
|
||||
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
for {
|
||||
line, _, err := lineBuf.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
copy(buf, line)
|
||||
buf[len(line)] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, logTag, cbuf)
|
||||
}
|
||||
// The garbage collector doesn't know that w's fd was dup'ed.
|
||||
// Avoid finalizing w, and thereby avoid its finalizer closing its fd.
|
||||
runtime.KeepAlive(w)
|
||||
}()
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
package log
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void nslog(char *str);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
_ "gioui.org/app/internal/cocoainit"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// macOS Console already includes timestamps.
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(newNSLogWriter())
|
||||
}
|
||||
|
||||
func newNSLogWriter() io.Writer {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
// 1024 is an arbitrary truncation limit, taken from Android's
|
||||
// log buffer size.
|
||||
lineBuf := bufio.NewReaderSize(r, 1024)
|
||||
// The buffer to pass to C, including the terminating '\0'.
|
||||
buf := make([]byte, lineBuf.Size()+1)
|
||||
cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
for {
|
||||
line, _, err := lineBuf.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
copy(buf, line)
|
||||
buf[len(line)] = 0
|
||||
C.nslog(cbuf)
|
||||
}
|
||||
}()
|
||||
return w
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
@import Foundation;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void nslog(char *str) {
|
||||
NSLog(@"%@", @(str));
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||
debugView *logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Windows DebugView already includes timestamps.
|
||||
if syscall.Stderr == 0 {
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
log.SetOutput(debugView)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Write(buf []byte) (int, error) {
|
||||
p, err := syscall.UTF16PtrFromString(string(buf))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
outputDebugStringW.Call(uintptr(unsafe.Pointer(p)))
|
||||
return len(buf), nil
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package srgb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gioui.org/app/internal/glimpl"
|
||||
"gioui.org/gpu/gl"
|
||||
"gioui.org/internal/unsafe"
|
||||
)
|
||||
|
||||
// FBO implements an intermediate sRGB FBO
|
||||
// for gamma-correct rendering on platforms without
|
||||
// sRGB enabled native framebuffers.
|
||||
type FBO struct {
|
||||
c *glimpl.Functions
|
||||
width, height int
|
||||
frameBuffer gl.Framebuffer
|
||||
depthBuffer gl.Renderbuffer
|
||||
colorTex gl.Texture
|
||||
blitted bool
|
||||
quad gl.Buffer
|
||||
prog gl.Program
|
||||
gl3 bool
|
||||
}
|
||||
|
||||
func New(f *glimpl.Functions) (*FBO, error) {
|
||||
var gl3 bool
|
||||
glVer := f.GetString(gl.VERSION)
|
||||
ver, _, err := gl.ParseGLVersion(glVer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ver[0] >= 3 {
|
||||
gl3 = true
|
||||
} else {
|
||||
exts := f.GetString(gl.EXTENSIONS)
|
||||
if !strings.Contains(exts, "EXT_sRGB") {
|
||||
return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB")
|
||||
}
|
||||
}
|
||||
s := &FBO{
|
||||
c: f,
|
||||
gl3: gl3,
|
||||
frameBuffer: f.CreateFramebuffer(),
|
||||
colorTex: f.CreateTexture(),
|
||||
depthBuffer: f.CreateRenderbuffer(),
|
||||
}
|
||||
f.BindTexture(gl.TEXTURE_2D, s.colorTex)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *FBO) Blit() {
|
||||
if !s.blitted {
|
||||
prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.prog = prog
|
||||
s.c.UseProgram(prog)
|
||||
s.c.Uniform1i(gl.GetUniformLocation(s.c, prog, "tex"), 0)
|
||||
s.quad = s.c.CreateBuffer()
|
||||
s.c.BindBuffer(gl.ARRAY_BUFFER, s.quad)
|
||||
s.c.BufferData(gl.ARRAY_BUFFER,
|
||||
unsafe.BytesView([]float32{
|
||||
-1, +1, 0, 1,
|
||||
+1, +1, 1, 1,
|
||||
-1, -1, 0, 0,
|
||||
+1, -1, 1, 0,
|
||||
}),
|
||||
gl.STATIC_DRAW)
|
||||
s.blitted = true
|
||||
}
|
||||
s.c.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{})
|
||||
s.c.UseProgram(s.prog)
|
||||
s.c.BindTexture(gl.TEXTURE_2D, s.colorTex)
|
||||
s.c.BindBuffer(gl.ARRAY_BUFFER, s.quad)
|
||||
s.c.VertexAttribPointer(0 /* pos */, 2, gl.FLOAT, false, 4*4, 0)
|
||||
s.c.VertexAttribPointer(1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2)
|
||||
s.c.EnableVertexAttribArray(0)
|
||||
s.c.EnableVertexAttribArray(1)
|
||||
s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
s.c.BindTexture(gl.TEXTURE_2D, gl.Texture{})
|
||||
s.c.DisableVertexAttribArray(0)
|
||||
s.c.DisableVertexAttribArray(1)
|
||||
s.c.BindFramebuffer(gl.FRAMEBUFFER, s.frameBuffer)
|
||||
s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
|
||||
s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT)
|
||||
// The Android emulator requires framebuffer 0 bound at eglSwapBuffer time.
|
||||
// Bind the sRGB framebuffer again in afterPresent.
|
||||
s.c.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{})
|
||||
}
|
||||
|
||||
func (s *FBO) AfterPresent() {
|
||||
s.c.BindFramebuffer(gl.FRAMEBUFFER, s.frameBuffer)
|
||||
}
|
||||
|
||||
func (s *FBO) Refresh(w, h int) error {
|
||||
s.width, s.height = w, h
|
||||
if w == 0 || h == 0 {
|
||||
return nil
|
||||
}
|
||||
s.c.BindTexture(gl.TEXTURE_2D, s.colorTex)
|
||||
if s.gl3 {
|
||||
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, w, h, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
} else /* EXT_sRGB */ {
|
||||
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB_ALPHA_EXT, w, h, gl.SRGB_ALPHA_EXT, gl.UNSIGNED_BYTE, nil)
|
||||
}
|
||||
currentRB := gl.Renderbuffer(s.c.GetBinding(gl.RENDERBUFFER_BINDING))
|
||||
s.c.BindRenderbuffer(gl.RENDERBUFFER, s.depthBuffer)
|
||||
s.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h)
|
||||
s.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
|
||||
s.c.BindFramebuffer(gl.FRAMEBUFFER, s.frameBuffer)
|
||||
s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.colorTex, 0)
|
||||
s.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, s.depthBuffer)
|
||||
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
|
||||
}
|
||||
|
||||
if runtime.GOOS == "js" {
|
||||
// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
|
||||
// texture result in twice gamma corrected colors. Using a plain RGBA
|
||||
// texture seems to work.
|
||||
s.c.ClearColor(.5, .5, .5, 1.0)
|
||||
s.c.Clear(gl.COLOR_BUFFER_BIT)
|
||||
var pixel [4]byte
|
||||
s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:])
|
||||
if pixel[0] == 128 { // Correct sRGB color value is ~188
|
||||
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FBO) Release() {
|
||||
s.c.DeleteFramebuffer(s.frameBuffer)
|
||||
s.c.DeleteTexture(s.colorTex)
|
||||
s.c.DeleteRenderbuffer(s.depthBuffer)
|
||||
if s.blitted {
|
||||
s.c.DeleteBuffer(s.quad)
|
||||
s.c.DeleteProgram(s.prog)
|
||||
}
|
||||
s.c = nil
|
||||
}
|
||||
|
||||
const (
|
||||
blitVSrc = `
|
||||
#version 100
|
||||
|
||||
precision highp float;
|
||||
|
||||
attribute vec2 pos;
|
||||
attribute vec2 uv;
|
||||
|
||||
varying vec2 vUV;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(pos, 0, 1);
|
||||
vUV = uv;
|
||||
}
|
||||
`
|
||||
blitFSrc = `
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D tex;
|
||||
varying vec2 vUV;
|
||||
|
||||
vec3 gamma(vec3 rgb) {
|
||||
vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
|
||||
vec3 lin = rgb * vec3(12.92);
|
||||
bvec3 cut = lessThan(rgb, vec3(0.0031308));
|
||||
return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 col = texture2D(tex, vUV);
|
||||
vec3 rgb = col.rgb;
|
||||
rgb = gamma(rgb);
|
||||
gl_FragColor = vec4(rgb, col.a);
|
||||
}
|
||||
`
|
||||
)
|
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class Gio {
|
||||
private static final Object initLock = new Object();
|
||||
private static boolean jniLoaded;
|
||||
private static final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* init loads and initializes the Go native library and runs
|
||||
* the Go main function.
|
||||
*
|
||||
* It is exported for use by Android apps that need to run Go code
|
||||
* outside the lifecycle of the Gio activity.
|
||||
*/
|
||||
public static synchronized void init(Context appCtx) {
|
||||
synchronized (initLock) {
|
||||
if (jniLoaded) {
|
||||
return;
|
||||
}
|
||||
String dataDir = appCtx.getFilesDir().getAbsolutePath();
|
||||
byte[] dataDirUTF8;
|
||||
try {
|
||||
dataDirUTF8 = dataDir.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.loadLibrary("gio");
|
||||
runGoMain(dataDirUTF8, appCtx);
|
||||
jniLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
static private native void runGoMain(byte[] dataDir, Context context);
|
||||
|
||||
static void writeClipboard(Context ctx, String s) {
|
||||
ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
m.setPrimaryClip(ClipData.newPlainText(null, s));
|
||||
}
|
||||
|
||||
static String readClipboard(Context ctx) {
|
||||
ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData c = m.getPrimaryClip();
|
||||
if (c == null || c.getItemCount() < 1) {
|
||||
return null;
|
||||
}
|
||||
return c.getItemAt(0).coerceToText(ctx).toString();
|
||||
}
|
||||
|
||||
static void wakeupMainThread() {
|
||||
handler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
scheduleMainFuncs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static private native void scheduleMainFuncs();
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public final class GioActivity extends Activity {
|
||||
private GioView view;
|
||||
|
||||
@Override public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Window w = getWindow();
|
||||
|
||||
this.view = new GioView(this);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
this.view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
this.view.setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
|
||||
setContentView(view);
|
||||
}
|
||||
|
||||
@Override public void onDestroy() {
|
||||
view.destroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override public void onStart() {
|
||||
super.onStart();
|
||||
view.start();
|
||||
}
|
||||
|
||||
@Override public void onStop() {
|
||||
view.stop();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override public void onConfigurationChanged(Configuration c) {
|
||||
super.onConfigurationChanged(c);
|
||||
view.configurationChanged();
|
||||
}
|
||||
|
||||
@Override public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
view.lowMemory();
|
||||
}
|
||||
|
||||
@Override public void onBackPressed() {
|
||||
if (!view.backPressed())
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
@ -0,0 +1,255 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.IllegalAccessException;
|
||||
import java.lang.InstantiationException;
|
||||
import java.lang.ExceptionInInitializerError;
|
||||
import java.lang.SecurityException;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Choreographer;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
|
||||
private final static Object initLock = new Object();
|
||||
private static boolean jniLoaded;
|
||||
|
||||
private final SurfaceHolder.Callback surfCallbacks;
|
||||
private final View.OnFocusChangeListener focusCallback;
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
public GioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
// Late initialization of the Go runtime to wait for a valid context.
|
||||
Gio.init(context.getApplicationContext());
|
||||
|
||||
ViewConfiguration conf = ViewConfiguration.get(context);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
scrollXScale = conf.getScaledHorizontalScrollFactor();
|
||||
scrollYScale = conf.getScaledVerticalScrollFactor();
|
||||
} else {
|
||||
float listItemHeight = 48; // dp
|
||||
float px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
listItemHeight,
|
||||
getResources().getDisplayMetrics()
|
||||
);
|
||||
scrollXScale = px;
|
||||
scrollYScale = px;
|
||||
}
|
||||
|
||||
nhandle = onCreateView(this);
|
||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
focusCallback = new View.OnFocusChangeListener() {
|
||||
@Override public void onFocusChange(View v, boolean focus) {
|
||||
GioView.this.onFocusChange(nhandle, focus);
|
||||
}
|
||||
};
|
||||
setOnFocusChangeListener(focusCallback);
|
||||
surfCallbacks = new SurfaceHolder.Callback() {
|
||||
@Override public void surfaceCreated(SurfaceHolder holder) {
|
||||
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
|
||||
}
|
||||
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
onSurfaceChanged(nhandle, getHolder().getSurface());
|
||||
}
|
||||
@Override public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
onSurfaceDestroyed(nhandle);
|
||||
}
|
||||
};
|
||||
getHolder().addCallback(surfCallbacks);
|
||||
}
|
||||
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean onTouchEvent(MotionEvent event) {
|
||||
// Ask for unbuffered events. Flutter and Chrome does it
|
||||
// so I assume its good for us as well.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
requestUnbufferedDispatch(event);
|
||||
}
|
||||
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void dispatchMotionEvent(MotionEvent event) {
|
||||
for (int j = 0; j < event.getHistorySize(); j++) {
|
||||
long time = event.getHistoricalEventTime(j);
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
event.ACTION_MOVE,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getHistoricalX(i, j),
|
||||
event.getHistoricalY(i, j),
|
||||
scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
|
||||
scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
|
||||
event.getButtonState(),
|
||||
time);
|
||||
}
|
||||
}
|
||||
int act = event.getActionMasked();
|
||||
int idx = event.getActionIndex();
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
int pact = event.ACTION_MOVE;
|
||||
if (i == idx) {
|
||||
pact = act;
|
||||
}
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
pact,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getX(i), event.getY(i),
|
||||
scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
|
||||
scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
|
||||
event.getButtonState(),
|
||||
event.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||
return new InputConnection(this);
|
||||
}
|
||||
|
||||
void showTextInput() {
|
||||
post(new Runnable() {
|
||||
@Override public void run() {
|
||||
GioView.this.requestFocus();
|
||||
imm.showSoftInput(GioView.this, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void hideTextInput() {
|
||||
post(new Runnable() {
|
||||
@Override public void run() {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override protected boolean fitSystemWindows(Rect insets) {
|
||||
onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
|
||||
return true;
|
||||
}
|
||||
|
||||
void postFrameCallback() {
|
||||
Choreographer.getInstance().removeFrameCallback(this);
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
|
||||
@Override public void doFrame(long nanos) {
|
||||
onFrameCallback(nhandle, nanos);
|
||||
}
|
||||
|
||||
int getDensity() {
|
||||
return getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
float getFontScale() {
|
||||
return getResources().getConfiguration().fontScale;
|
||||
}
|
||||
|
||||
void start() {
|
||||
onStartView(nhandle);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
onStopView(nhandle);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
setOnFocusChangeListener(null);
|
||||
getHolder().removeCallback(surfCallbacks);
|
||||
onDestroyView(nhandle);
|
||||
nhandle = 0;
|
||||
}
|
||||
|
||||
void configurationChanged() {
|
||||
onConfigurationChanged(nhandle);
|
||||
}
|
||||
|
||||
void lowMemory() {
|
||||
onLowMemory();
|
||||
}
|
||||
|
||||
boolean backPressed() {
|
||||
return onBack(nhandle);
|
||||
}
|
||||
|
||||
static private native long onCreateView(GioView view);
|
||||
static private native void onDestroyView(long handle);
|
||||
static private native void onStartView(long handle);
|
||||
static private native void onStopView(long handle);
|
||||
static private native void onSurfaceDestroyed(long handle);
|
||||
static private native void onSurfaceChanged(long handle, Surface surface);
|
||||
static private native void onConfigurationChanged(long handle);
|
||||
static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
|
||||
static private native void onLowMemory();
|
||||
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
|
||||
static private native void onKeyEvent(long handle, int code, int character, long time);
|
||||
static private native void onFrameCallback(long handle, long nanos);
|
||||
static private native boolean onBack(long handle);
|
||||
static private native void onFocusChange(long handle, boolean focus);
|
||||
|
||||
private static class InputConnection extends BaseInputConnection {
|
||||
private final Editable editable;
|
||||
|
||||
InputConnection(View view) {
|
||||
// Passing false enables "dummy mode", where the BaseInputConnection
|
||||
// attempts to convert IME operations to key events.
|
||||
super(view, false);
|
||||
editable = Editable.Factory.getInstance().newEditable("");
|
||||
}
|
||||
|
||||
@Override public Editable getEditable() {
|
||||
return editable;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"gioui.org/app/internal/d3d11"
|
||||
"gioui.org/gpu/backend"
|
||||
)
|
||||
|
||||
type d3d11Context struct {
|
||||
win *window
|
||||
swchain *d3d11.SwapChain
|
||||
fbo *d3d11.Framebuffer
|
||||
backend backend.Device
|
||||
*d3d11.Device
|
||||
width, height int
|
||||
}
|
||||
|
||||
func init() {
|
||||
backends = append(backends, gpuAPI{
|
||||
priority: 1,
|
||||
initializer: func(w *window) (Context, error) {
|
||||
hwnd, _, _ := w.HWND()
|
||||
dev, err := d3d11.NewDevice()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
swchain, err := dev.CreateSwapChain(hwnd)
|
||||
if err != nil {
|
||||
dev.Release()
|
||||
return nil, err
|
||||
}
|
||||
return &d3d11Context{win: w, Device: dev, swchain: swchain}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Backend() (backend.Device, error) {
|
||||
backend, err := d3d11.NewBackend(c.Device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.backend = backend
|
||||
c.backend.BindFramebuffer(c.fbo)
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Present() error {
|
||||
if err := c.swchain.Present(); err != nil {
|
||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||
switch err.Code {
|
||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||
// Ignore
|
||||
return nil
|
||||
case d3d11.DXGI_ERROR_DEVICE_RESET, d3d11.DXGI_ERROR_DEVICE_REMOVED, d3d11.D3DDDIERR_DEVICEREMOVED:
|
||||
return ErrDeviceLost
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) MakeCurrent() error {
|
||||
_, width, height := c.win.HWND()
|
||||
if c.fbo != nil && width == c.width && height == c.height {
|
||||
c.backend.BindFramebuffer(c.fbo)
|
||||
return nil
|
||||
}
|
||||
if c.fbo != nil {
|
||||
c.fbo.Release()
|
||||
c.fbo = nil
|
||||
}
|
||||
if err := c.swchain.Resize(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.width = width
|
||||
c.height = height
|
||||
fbo, err := c.swchain.Framebuffer(c.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.fbo = fbo
|
||||
if c.backend != nil {
|
||||
c.backend.BindFramebuffer(c.fbo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Lock() {}
|
||||
|
||||
func (c *d3d11Context) Unlock() {}
|
||||
|
||||
func (c *d3d11Context) Release() {
|
||||
if c.fbo != nil {
|
||||
c.fbo.Release()
|
||||
}
|
||||
c.swchain.Release()
|
||||
c.Device.Release()
|
||||
c.fbo = nil
|
||||
c.swchain = nil
|
||||
c.Device = nil
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
/*
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/app/internal/egl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
ctx, err := egl.NewContext(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &context{win: w, Context: ctx}, nil
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.nativeWindow(c.Context.VisualID())
|
||||
if win == nil {
|
||||
return nil
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(unsafe.Pointer(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/app/internal/egl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: egl wayland-egl
|
||||
#cgo freebsd openbsd LDFLAGS: -lwayland-egl
|
||||
#cgo CFLAGS: -DMESA_EGL_NO_X11_HEADERS
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type context struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &context{Context: ctx, win: w}, nil
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
c.eglWin = eglWin
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"gioui.org/app/internal/egl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
backends = append(backends, gpuAPI{
|
||||
priority: 2,
|
||||
initializer: func(w *window) (Context, error) {
|
||||
disp := egl.NativeDisplayType(w.HDC())
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() {}
|
||||
|
||||
func (c *glContext) Unlock() {}
|
@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nox11 freebsd openbsd
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/app/internal/egl"
|
||||
)
|
||||
|
||||
type x11Context struct {
|
||||
win *x11Window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *x11Window) NewContext() (Context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Lock() {}
|
||||
|
||||
func (c *x11Context) Unlock() {}
|
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
@interface GioViewController : UIViewController
|
||||
@end
|
@ -0,0 +1,133 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
package window
|
||||
|
||||
/*
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGLES/ES2/gl.h>
|
||||
#include <OpenGLES/ES2/glext.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_renderbufferStorage(CFTypeRef ctx, CFTypeRef layer, GLenum buffer);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_presentRenderbuffer(CFTypeRef ctx, GLenum buffer);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_makeCurrent(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLLayer(void);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gioui.org/app/internal/glimpl"
|
||||
"gioui.org/gpu/backend"
|
||||
"gioui.org/gpu/gl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
owner *window
|
||||
c *glimpl.Functions
|
||||
ctx C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
init bool
|
||||
frameBuffer gl.Framebuffer
|
||||
colorBuffer, depthBuffer gl.Renderbuffer
|
||||
}
|
||||
|
||||
func init() {
|
||||
layerFactory = func() uintptr {
|
||||
return uintptr(C.gio_createGLLayer())
|
||||
}
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
ctx := C.gio_createContext()
|
||||
if ctx == 0 {
|
||||
return nil, fmt.Errorf("failed to create EAGLContext")
|
||||
}
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
owner: w,
|
||||
layer: C.CFTypeRef(w.contextLayer()),
|
||||
c: new(glimpl.Functions),
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) Backend() (backend.Device, error) {
|
||||
return gl.NewBackend(c.c)
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.ctx == 0 {
|
||||
return
|
||||
}
|
||||
C.gio_renderbufferStorage(c.ctx, 0, C.GLenum(gl.RENDERBUFFER))
|
||||
c.c.DeleteFramebuffer(c.frameBuffer)
|
||||
c.c.DeleteRenderbuffer(c.colorBuffer)
|
||||
c.c.DeleteRenderbuffer(c.depthBuffer)
|
||||
C.gio_makeCurrent(0)
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
if c.layer == 0 {
|
||||
panic("context is not active")
|
||||
}
|
||||
// Discard depth buffer as recommended in
|
||||
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html
|
||||
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
|
||||
c.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT)
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
|
||||
if C.gio_presentRenderbuffer(c.ctx, C.GLenum(gl.RENDERBUFFER)) == 0 {
|
||||
return errors.New("presentRenderBuffer failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
if !c.init {
|
||||
c.init = true
|
||||
c.frameBuffer = c.c.CreateFramebuffer()
|
||||
c.colorBuffer = c.c.CreateRenderbuffer()
|
||||
c.depthBuffer = c.c.CreateRenderbuffer()
|
||||
}
|
||||
if !c.owner.isVisible() {
|
||||
// Make sure any in-flight GL commands are complete.
|
||||
c.c.Finish()
|
||||
return nil
|
||||
}
|
||||
currentRB := gl.Renderbuffer{uint(c.c.GetInteger(gl.RENDERBUFFER_BINDING))}
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
|
||||
if C.gio_renderbufferStorage(c.ctx, c.layer, C.GLenum(gl.RENDERBUFFER)) == 0 {
|
||||
return errors.New("renderbufferStorage failed")
|
||||
}
|
||||
w := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)
|
||||
h := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT)
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.depthBuffer)
|
||||
c.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h)
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
|
||||
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
|
||||
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c.colorBuffer)
|
||||
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, c.depthBuffer)
|
||||
if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
@import UIKit;
|
||||
@import OpenGLES;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
|
||||
return (int)[ctx renderbufferStorage:buffer fromDrawable:layer];
|
||||
}
|
||||
|
||||
int gio_presentRenderbuffer(CFTypeRef ctxRef, GLenum buffer) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
return (int)[ctx presentRenderbuffer:buffer];
|
||||
}
|
||||
|
||||
int gio_makeCurrent(CFTypeRef ctxRef) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
return (int)[EAGLContext setCurrentContext:ctx];
|
||||
}
|
||||
|
||||
CFTypeRef gio_createContext(void) {
|
||||
EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
||||
if (ctx == nil) {
|
||||
return nil;
|
||||
}
|
||||
return CFBridgingRetain(ctx);
|
||||
}
|
||||
|
||||
CFTypeRef gio_createGLLayer(void) {
|
||||
CAEAGLLayer *layer = [[CAEAGLLayer layer] init];
|
||||
if (layer == nil) {
|
||||
return nil;
|
||||
}
|
||||
layer.drawableProperties = @{kEAGLDrawablePropertyColorFormat: kEAGLColorFormatSRGBA8};
|
||||
layer.opaque = YES;
|
||||
layer.anchorPoint = CGPointMake(0, 0);
|
||||
return CFBridgingRetain(layer);
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"gioui.org/app/internal/glimpl"
|
||||
"gioui.org/app/internal/srgb"
|
||||
"gioui.org/gpu/backend"
|
||||
"gioui.org/gpu/gl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
ctx js.Value
|
||||
cnv js.Value
|
||||
f *glimpl.Functions
|
||||
srgbFBO *srgb.FBO
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
args := map[string]interface{}{
|
||||
// Enable low latency rendering.
|
||||
// See https://developers.google.com/web/updates/2019/05/desynchronized.
|
||||
"desynchronized": true,
|
||||
"preserveDrawingBuffer": true,
|
||||
}
|
||||
version := 2
|
||||
ctx := w.cnv.Call("getContext", "webgl2", args)
|
||||
if ctx.IsNull() {
|
||||
version = 1
|
||||
ctx = w.cnv.Call("getContext", "webgl", args)
|
||||
}
|
||||
if ctx.IsNull() {
|
||||
return nil, errors.New("app: webgl is not supported")
|
||||
}
|
||||
f := &glimpl.Functions{Ctx: ctx}
|
||||
if err := f.Init(version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
cnv: w.cnv,
|
||||
f: f,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) Backend() (backend.Device, error) {
|
||||
return gl.NewBackend(c.f)
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.Release()
|
||||
c.srgbFBO = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.Blit()
|
||||
}
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.AfterPresent()
|
||||
}
|
||||
if c.ctx.Call("isContextLost").Bool() {
|
||||
return errors.New("context lost")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
if c.srgbFBO == nil {
|
||||
var err error
|
||||
c.srgbFBO, err = srgb.New(c.f)
|
||||
if err != nil {
|
||||
c.Release()
|
||||
c.srgbFBO = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
w, h := c.cnv.Get("width").Int(), c.cnv.Get("height").Int()
|
||||
if err := c.srgbFBO.Refresh(w, h); err != nil {
|
||||
c.Release()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"gioui.org/app/internal/glimpl"
|
||||
"gioui.org/gpu/backend"
|
||||
"gioui.org/gpu/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <OpenGL/gl3.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLView(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_contextForView(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_lockContext(CFTypeRef ctxRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type context struct {
|
||||
c *glimpl.Functions
|
||||
ctx C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
}
|
||||
|
||||
func init() {
|
||||
viewFactory = func() C.CFTypeRef {
|
||||
return C.gio_createGLView()
|
||||
}
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
view := w.contextView()
|
||||
ctx := C.gio_contextForView(view)
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
c: new(glimpl.Functions),
|
||||
view: view,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) Backend() (backend.Device, error) {
|
||||
return gl.NewBackend(c.c)
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_clearCurrentContext()
|
||||
// We could release the context with [view clearGLContext]
|
||||
// and rely on [view openGLContext] auto-creating a new context.
|
||||
// However that second context is not properly set up by
|
||||
// OpenGLContextView, so we'll stay on the safe side and keep
|
||||
// the first context around.
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
// Assume the caller already locked the context.
|
||||
C.glFlush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {
|
||||
C.gio_lockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
C.gio_unlockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_makeCurrentContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
@import AppKit;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl3.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
dx *= 10;
|
||||
dy *= 10;
|
||||
}
|
||||
gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@interface GioView : NSOpenGLView
|
||||
@end
|
||||
|
||||
@implementation GioView
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
pixelFormat:(NSOpenGLPixelFormat *)format {
|
||||
return [super initWithFrame:frameRect pixelFormat:format];
|
||||
}
|
||||
- (void)prepareOpenGL {
|
||||
[super prepareOpenGL];
|
||||
// Bind a default VBA to emulate OpenGL ES 2.
|
||||
GLuint defVBA;
|
||||
glGenVertexArrays(1, &defVBA);
|
||||
glBindVertexArray(defVBA);
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
- (BOOL)isFlipped {
|
||||
return YES;
|
||||
}
|
||||
- (void)update {
|
||||
[super update];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)middleMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)middletMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
CGFloat dx = -event.scrollingDeltaX;
|
||||
CGFloat dy = -event.scrollingDeltaY;
|
||||
handleMouse(self, event, GIO_MOUSE_SCROLL, dx, dy);
|
||||
}
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags]);
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
const char *utf8 = [string UTF8String];
|
||||
gio_onText((__bridge CFTypeRef)self, (char *)utf8);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)sel {
|
||||
// Don't pass commands up the responder chain.
|
||||
// They will end up in a beep.
|
||||
}
|
||||
@end
|
||||
|
||||
CFTypeRef gio_createGLView(void) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLPixelFormatAttribute attr[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFADepthSize, 16,
|
||||
NSOpenGLPFAAccelerated,
|
||||
// Opt-in to automatic GPU switching. CGL-only property.
|
||||
kCGLPFASupportsAutomaticGraphicsSwitching,
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
||||
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame pixelFormat:pixFormat];
|
||||
|
||||
[view setWantsBestResolutionOpenGLSurface:YES];
|
||||
[view setWantsLayer:YES]; // The default in Mojave.
|
||||
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setNeedsDisplay(CFTypeRef viewRef) {
|
||||
NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
CFTypeRef gio_contextForView(CFTypeRef viewRef) {
|
||||
NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.openGLContext;
|
||||
}
|
||||
|
||||
void gio_clearCurrentContext(void) {
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
}
|
||||
|
||||
void gio_makeCurrentContext(CFTypeRef ctxRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx makeCurrentContext];
|
||||
}
|
||||
|
||||
void gio_lockContext(CFTypeRef ctxRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLLockContext([ctx CGLContextObj]);
|
||||
}
|
||||
|
||||
void gio_unlockContext(CFTypeRef ctxRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLUnlockContext([ctx CGLContextObj]);
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#include <jni.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
|
||||
return (*vm)->GetEnv(vm, (void **)env, version);
|
||||
}
|
||||
|
||||
jint gio_jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) {
|
||||
return (*env)->GetJavaVM(env, jvm);
|
||||
}
|
||||
|
||||
jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
|
||||
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
|
||||
}
|
||||
|
||||
jint gio_jni_DetachCurrentThread(JavaVM *vm) {
|
||||
return (*vm)->DetachCurrentThread(vm);
|
||||
}
|
||||
|
||||
jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj) {
|
||||
return (*env)->NewGlobalRef(env, obj);
|
||||
}
|
||||
|
||||
void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
|
||||
(*env)->DeleteGlobalRef(env, obj);
|
||||
}
|
||||
|
||||
jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj) {
|
||||
return (*env)->GetObjectClass(env, obj);
|
||||
}
|
||||
|
||||
jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
return (*env)->GetMethodID(env, clazz, name, sig);
|
||||
}
|
||||
|
||||
jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
return (*env)->GetStaticMethodID(env, clazz, name, sig);
|
||||
}
|
||||
|
||||
jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
|
||||
return (*env)->CallFloatMethod(env, obj, methodID);
|
||||
}
|
||||
|
||||
jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
|
||||
return (*env)->CallIntMethod(env, obj, methodID);
|
||||
}
|
||||
|
||||
void gio_jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) {
|
||||
(*env)->CallStaticVoidMethodA(env, cls, methodID, args);
|
||||
}
|
||||
|
||||
void gio_jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
|
||||
(*env)->CallVoidMethodA(env, obj, methodID, args);
|
||||
}
|
||||
|
||||
jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
|
||||
return (*env)->GetByteArrayElements(env, arr, NULL);
|
||||
}
|
||||
|
||||
void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
|
||||
(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
|
||||
}
|
||||
|
||||
jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
|
||||
return (*env)->GetArrayLength(env, arr);
|
||||
}
|
||||
|
||||
jstring gio_jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
|
||||
return (*env)->NewString(env, unicodeChars, len);
|
||||
}
|
||||
|
||||
jsize gio_jni_GetStringLength(JNIEnv *env, jstring str) {
|
||||
return (*env)->GetStringLength(env, str);
|
||||
}
|
||||
|
||||
const jchar *gio_jni_GetStringChars(JNIEnv *env, jstring str) {
|
||||
return (*env)->GetStringChars(env, str, NULL);
|
||||
}
|
||||
|
||||
jthrowable gio_jni_ExceptionOccurred(JNIEnv *env) {
|
||||
return (*env)->ExceptionOccurred(env);
|
||||
}
|
||||
|
||||
void gio_jni_ExceptionClear(JNIEnv *env) {
|
||||
(*env)->ExceptionClear(env);
|
||||
}
|
||||
|
||||
jobject gio_jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
|
||||
return (*env)->CallObjectMethodA(env, obj, method, args);
|
||||
}
|
||||
|
||||
jobject gio_jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
|
||||
return (*env)->CallStaticObjectMethodA(env, cls, method, args);
|
||||
}
|
@ -0,0 +1,684 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror
|
||||
#cgo LDFLAGS: -landroid
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/configuration.h>
|
||||
#include <android/keycodes.h>
|
||||
#include <android/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetJavaVM(JNIEnv *env, JavaVM **jvm);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_DetachCurrentThread(JavaVM *vm);
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj);
|
||||
__attribute__ ((visibility ("hidden"))) jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj);
|
||||
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
|
||||
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
|
||||
__attribute__ ((visibility ("hidden"))) jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
|
||||
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
|
||||
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
|
||||
__attribute__ ((visibility ("hidden"))) jstring gio_jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);
|
||||
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetStringLength(JNIEnv *env, jstring str);
|
||||
__attribute__ ((visibility ("hidden"))) const jchar *gio_jni_GetStringChars(JNIEnv *env, jstring str);
|
||||
__attribute__ ((visibility ("hidden"))) jthrowable gio_jni_ExceptionOccurred(JNIEnv *env);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_ExceptionClear(JNIEnv *env);
|
||||
__attribute__ ((visibility ("hidden"))) jobject gio_jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args);
|
||||
__attribute__ ((visibility ("hidden"))) jobject gio_jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
callbacks Callbacks
|
||||
|
||||
view C.jobject
|
||||
|
||||
dpi int
|
||||
fontScale float32
|
||||
insets system.Insets
|
||||
|
||||
stage system.Stage
|
||||
started bool
|
||||
|
||||
mu sync.Mutex
|
||||
win *C.ANativeWindow
|
||||
animating bool
|
||||
|
||||
// Cached Java methods.
|
||||
mgetDensity C.jmethodID
|
||||
mgetFontScale C.jmethodID
|
||||
mshowTextInput C.jmethodID
|
||||
mhideTextInput C.jmethodID
|
||||
mpostFrameCallback C.jmethodID
|
||||
}
|
||||
|
||||
type jvalue uint64 // The largest JNI type fits in 64 bits.
|
||||
|
||||
var dataDirChan = make(chan string, 1)
|
||||
|
||||
var android struct {
|
||||
// mu protects all fields of this structure. However, once a
|
||||
// non-nil jvm is returned from javaVM, all the other fields may
|
||||
// be accessed unlocked.
|
||||
mu sync.Mutex
|
||||
jvm *C.JavaVM
|
||||
|
||||
// appCtx is the global Android App context.
|
||||
appCtx C.jobject
|
||||
// gioCls is the class of the Gio class.
|
||||
gioCls C.jclass
|
||||
|
||||
mwriteClipboard C.jmethodID
|
||||
mreadClipboard C.jmethodID
|
||||
mwakeupMainThread C.jmethodID
|
||||
}
|
||||
|
||||
var views = make(map[C.jlong]*window)
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
|
||||
|
||||
func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
|
||||
m := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(m))
|
||||
s := C.CString(sig)
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
jm := C.gio_jni_GetMethodID(env, class, m, s)
|
||||
if err := exception(env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jm
|
||||
}
|
||||
|
||||
func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
|
||||
m := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(m))
|
||||
s := C.CString(sig)
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
jm := C.gio_jni_GetStaticMethodID(env, class, m, s)
|
||||
if err := exception(env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jm
|
||||
}
|
||||
|
||||
//export Java_org_gioui_Gio_runGoMain
|
||||
func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
|
||||
initJVM(env, class, context)
|
||||
dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
|
||||
if dirBytes == nil {
|
||||
panic("runGoMain: GetByteArrayElements failed")
|
||||
}
|
||||
n := C.gio_jni_GetArrayLength(env, jdataDir)
|
||||
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
|
||||
dataDirChan <- dataDir
|
||||
C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
|
||||
|
||||
runMain()
|
||||
}
|
||||
|
||||
func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
if res := C.gio_jni_GetJavaVM(env, &android.jvm); res != 0 {
|
||||
panic("gio: GetJavaVM failed")
|
||||
}
|
||||
android.appCtx = C.gio_jni_NewGlobalRef(env, ctx)
|
||||
android.gioCls = C.jclass(C.gio_jni_NewGlobalRef(env, C.jobject(gio)))
|
||||
android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
|
||||
android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
|
||||
android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
|
||||
}
|
||||
|
||||
func JavaVM() uintptr {
|
||||
jvm := javaVM()
|
||||
return uintptr(unsafe.Pointer(jvm))
|
||||
}
|
||||
|
||||
func javaVM() *C.JavaVM {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
return android.jvm
|
||||
}
|
||||
|
||||
func AppContext() uintptr {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
return uintptr(android.appCtx)
|
||||
}
|
||||
|
||||
func GetDataDir() string {
|
||||
return <-dataDirChan
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onCreateView
|
||||
func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
|
||||
view = C.gio_jni_NewGlobalRef(env, view)
|
||||
w := &window{
|
||||
view: view,
|
||||
mgetDensity: getMethodID(env, class, "getDensity", "()I"),
|
||||
mgetFontScale: getMethodID(env, class, "getFontScale", "()F"),
|
||||
mshowTextInput: getMethodID(env, class, "showTextInput", "()V"),
|
||||
mhideTextInput: getMethodID(env, class, "hideTextInput", "()V"),
|
||||
mpostFrameCallback: getMethodID(env, class, "postFrameCallback", "()V"),
|
||||
}
|
||||
wopts := <-mainWindow.out
|
||||
w.callbacks = wopts.window
|
||||
w.callbacks.SetDriver(w)
|
||||
handle := C.jlong(view)
|
||||
views[handle] = w
|
||||
w.loadConfig(env, class)
|
||||
w.setStage(system.StagePaused)
|
||||
return handle
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onDestroyView
|
||||
func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.callbacks.SetDriver(nil)
|
||||
delete(views, handle)
|
||||
C.gio_jni_DeleteGlobalRef(env, w.view)
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStopView
|
||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = false
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStartView
|
||||
func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = true
|
||||
if w.aNativeWindow() != nil {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceDestroyed
|
||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.mu.Lock()
|
||||
w.win = nil
|
||||
w.mu.Unlock()
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||
func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
|
||||
w := views[handle]
|
||||
w.mu.Lock()
|
||||
w.win = C.ANativeWindow_fromSurface(env, surf)
|
||||
w.mu.Unlock()
|
||||
if w.started {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onLowMemory
|
||||
func Java_org_gioui_GioView_onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onConfigurationChanged
|
||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||
w := views[view]
|
||||
w.loadConfig(env, class)
|
||||
if w.stage >= system.StageRunning {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFrameCallback
|
||||
func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
|
||||
w, exist := views[view]
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if w.stage < system.StageRunning {
|
||||
return
|
||||
}
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, w.mpostFrameCallback)
|
||||
})
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onBack
|
||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||
w := views[view]
|
||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
||||
w.callbacks.Event(ev)
|
||||
if ev.Cancel {
|
||||
return C.JNI_TRUE
|
||||
}
|
||||
return C.JNI_FALSE
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFocusChange
|
||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||
w := views[view]
|
||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onWindowInsets
|
||||
func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
|
||||
w := views[view]
|
||||
w.insets = system.Insets{
|
||||
Top: unit.Px(float32(top)),
|
||||
Right: unit.Px(float32(right)),
|
||||
Bottom: unit.Px(float32(bottom)),
|
||||
Left: unit.Px(float32(left)),
|
||||
}
|
||||
if w.stage >= system.StageRunning {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setVisible() {
|
||||
win := w.aNativeWindow()
|
||||
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.setStage(system.StageRunning)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.callbacks.Event(system.StageEvent{stage})
|
||||
}
|
||||
|
||||
func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
|
||||
win := w.aNativeWindow()
|
||||
var width, height int
|
||||
if win != nil {
|
||||
if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
|
||||
}
|
||||
w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
width, height = int(w), int(h)
|
||||
}
|
||||
return win, width, height
|
||||
}
|
||||
|
||||
func (w *window) aNativeWindow() *C.ANativeWindow {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.win
|
||||
}
|
||||
|
||||
func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
|
||||
dpi := int(C.gio_jni_CallIntMethod(env, w.view, w.mgetDensity))
|
||||
w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, w.mgetFontScale))
|
||||
switch dpi {
|
||||
case C.ACONFIGURATION_DENSITY_NONE,
|
||||
C.ACONFIGURATION_DENSITY_DEFAULT,
|
||||
C.ACONFIGURATION_DENSITY_ANY:
|
||||
// Assume standard density.
|
||||
w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
|
||||
default:
|
||||
w.dpi = int(dpi)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
if w.view == 0 {
|
||||
// View was destroyed while switching to main thread.
|
||||
return
|
||||
}
|
||||
callVoidMethod(env, w.view, w.mpostFrameCallback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
win := w.aNativeWindow()
|
||||
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
const inchPrDp = 1.0 / 160
|
||||
ppdp := float32(w.dpi) * inchPrDp
|
||||
w.callbacks.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(width),
|
||||
Y: int(height),
|
||||
},
|
||||
Insets: w.insets,
|
||||
Metric: unit.Metric{
|
||||
PxPerDp: ppdp,
|
||||
PxPerSp: w.fontScale * ppdp,
|
||||
},
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
type keyMapper func(devId, keyCode C.int32_t) rune
|
||||
|
||||
func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
||||
if jvm == nil {
|
||||
panic("nil JVM")
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
var env *C.JNIEnv
|
||||
if res := C.gio_jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
|
||||
if res != C.JNI_EDETACHED {
|
||||
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
|
||||
}
|
||||
if C.gio_jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
|
||||
panic(errors.New("runInJVM: AttachCurrentThread failed"))
|
||||
}
|
||||
defer C.gio_jni_DetachCurrentThread(jvm)
|
||||
}
|
||||
|
||||
f(env)
|
||||
}
|
||||
|
||||
func convertKeyCode(code C.jint) (string, bool) {
|
||||
var n string
|
||||
switch code {
|
||||
case C.AKEYCODE_DPAD_UP:
|
||||
n = key.NameUpArrow
|
||||
case C.AKEYCODE_DPAD_DOWN:
|
||||
n = key.NameDownArrow
|
||||
case C.AKEYCODE_DPAD_LEFT:
|
||||
n = key.NameLeftArrow
|
||||
case C.AKEYCODE_DPAD_RIGHT:
|
||||
n = key.NameRightArrow
|
||||
case C.AKEYCODE_FORWARD_DEL:
|
||||
n = key.NameDeleteForward
|
||||
case C.AKEYCODE_DEL:
|
||||
n = key.NameDeleteBackward
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onKeyEvent
|
||||
func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
|
||||
w := views[handle]
|
||||
if n, ok := convertKeyCode(keyCode); ok {
|
||||
w.callbacks.Event(key.Event{Name: n})
|
||||
}
|
||||
if r != 0 {
|
||||
w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onTouchEvent
|
||||
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
|
||||
w := views[handle]
|
||||
var typ pointer.Type
|
||||
switch action {
|
||||
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
typ = pointer.Press
|
||||
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
typ = pointer.Release
|
||||
case C.AMOTION_EVENT_ACTION_CANCEL:
|
||||
typ = pointer.Cancel
|
||||
case C.AMOTION_EVENT_ACTION_MOVE:
|
||||
typ = pointer.Move
|
||||
case C.AMOTION_EVENT_ACTION_SCROLL:
|
||||
typ = pointer.Scroll
|
||||
default:
|
||||
return
|
||||
}
|
||||
var src pointer.Source
|
||||
var btns pointer.Buttons
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
|
||||
btns |= pointer.ButtonLeft
|
||||
}
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
|
||||
btns |= pointer.ButtonRight
|
||||
}
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
|
||||
btns |= pointer.ButtonMiddle
|
||||
}
|
||||
switch tool {
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
|
||||
src = pointer.Touch
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
|
||||
src = pointer.Mouse
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
|
||||
// For example, triggered via 'adb shell input tap'.
|
||||
// Instead of discarding it, treat it as a touch event.
|
||||
src = pointer.Touch
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.callbacks.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: src,
|
||||
Buttons: btns,
|
||||
PointerID: pointer.ID(pointerID),
|
||||
Time: time.Duration(t) * time.Millisecond,
|
||||
Position: f32.Point{X: float32(x), Y: float32(y)},
|
||||
Scroll: f32.Pt(float32(scrollX), float32(scrollY)),
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
if w.view == 0 {
|
||||
return
|
||||
}
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
if show {
|
||||
callVoidMethod(env, w.view, w.mshowTextInput)
|
||||
} else {
|
||||
callVoidMethod(env, w.view, w.mhideTextInput)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func javaString(env *C.JNIEnv, str string) C.jstring {
|
||||
if str == "" {
|
||||
return 0
|
||||
}
|
||||
utf16Chars := utf16.Encode([]rune(str))
|
||||
return C.gio_jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
|
||||
}
|
||||
|
||||
// Do invokes the function with a global JNI handle to the view. If
|
||||
// the view is destroyed, Do returns false and does not invoke the
|
||||
// function.
|
||||
//
|
||||
// NOTE: Do must be invoked on the Android main thread.
|
||||
func (w *window) Do(f func(view uintptr)) bool {
|
||||
if w.view == 0 {
|
||||
return false
|
||||
}
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
view := C.gio_jni_NewGlobalRef(env, w.view)
|
||||
defer C.gio_jni_DeleteGlobalRef(env, view)
|
||||
f(uintptr(view))
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
func varArgs(args []jvalue) *C.jvalue {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return (*C.jvalue)(unsafe.Pointer(&args[0]))
|
||||
}
|
||||
|
||||
func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
|
||||
C.gio_jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
|
||||
return exception(env)
|
||||
}
|
||||
|
||||
func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
|
||||
res := C.gio_jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
|
||||
return res, exception(env)
|
||||
}
|
||||
|
||||
func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
|
||||
C.gio_jni_CallVoidMethodA(env, obj, method, varArgs(args))
|
||||
return exception(env)
|
||||
}
|
||||
|
||||
func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
|
||||
res := C.gio_jni_CallObjectMethodA(env, obj, method, varArgs(args))
|
||||
return res, exception(env)
|
||||
}
|
||||
|
||||
// exception returns an error corresponding to the pending
|
||||
// exception, or nil if no exception is pending. The pending
|
||||
// exception is cleared.
|
||||
func exception(env *C.JNIEnv) error {
|
||||
thr := C.gio_jni_ExceptionOccurred(env)
|
||||
if thr == 0 {
|
||||
return nil
|
||||
}
|
||||
C.gio_jni_ExceptionClear(env)
|
||||
cls := getObjectClass(env, C.jobject(thr))
|
||||
toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
|
||||
msg, err := callObjectMethod(env, C.jobject(thr), toString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(goString(env, C.jstring(msg)))
|
||||
}
|
||||
|
||||
func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
|
||||
if obj == 0 {
|
||||
panic("null object")
|
||||
}
|
||||
cls := C.gio_jni_GetObjectClass(env, C.jobject(obj))
|
||||
if err := exception(env); err != nil {
|
||||
// GetObjectClass should never fail.
|
||||
panic(err)
|
||||
}
|
||||
return cls
|
||||
}
|
||||
|
||||
// goString converts the JVM jstring to a Go string.
|
||||
func goString(env *C.JNIEnv, str C.jstring) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
strlen := C.gio_jni_GetStringLength(env, C.jstring(str))
|
||||
chars := C.gio_jni_GetStringChars(env, C.jstring(str))
|
||||
var utf16Chars []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
|
||||
hdr.Data = uintptr(unsafe.Pointer(chars))
|
||||
hdr.Cap = int(strlen)
|
||||
hdr.Len = int(strlen)
|
||||
utf8 := utf16.Decode(utf16Chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
func Main() {
|
||||
}
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
mainWindow.in <- windowAndOptions{window, opts}
|
||||
return <-mainWindow.errs
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
jstr := javaString(env, s)
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||
jvalue(android.appCtx), jvalue(jstr))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
|
||||
jvalue(android.appCtx))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
w.callbacks.Event(system.ClipboardEvent{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for Android.
|
||||
func (w *window) Close() {}
|
||||
|
||||
// RunOnMain is the exported version of runOnMain without a JNI
|
||||
// environement.
|
||||
func RunOnMain(f func()) {
|
||||
runOnMain(func(_ *C.JNIEnv) {
|
||||
f()
|
||||
})
|
||||
}
|
||||
|
||||
// runOnMain runs a function on the Java main thread.
|
||||
func runOnMain(f func(env *C.JNIEnv)) {
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
//export Java_org_gioui_Gio_scheduleMainFuncs
|
||||
func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f(env)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
||||
__attribute__ ((visibility ("hidden"))) NSUInteger gio_nsstringLength(CFTypeRef str);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_nsstringGetCharacters(CFTypeRef str, unichar *chars, NSUInteger loc, NSUInteger length);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// displayLink is the state for a display link (CVDisplayLinkRef on macOS,
|
||||
// CADisplayLink on iOS). It runs a state-machine goroutine that keeps the
|
||||
// display link running for a while after being stopped to avoid the thread
|
||||
// start/stop overhead and because the CVDisplayLink sometimes fails to
|
||||
// start, stop and start again within a short duration.
|
||||
type displayLink struct {
|
||||
callback func()
|
||||
// states is for starting or stopping the display link.
|
||||
states chan bool
|
||||
// done is closed when the display link is destroyed.
|
||||
done chan struct{}
|
||||
// dids receives the display id when the callback owner is moved
|
||||
// to a different screen.
|
||||
dids chan uint64
|
||||
// running tracks the desired state of the link. running is accessed
|
||||
// with atomic.
|
||||
running uint32
|
||||
}
|
||||
|
||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||
var displayLinks sync.Map
|
||||
|
||||
var mainFuncs = make(chan func(), 1)
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
C.gio_wakeupMainThread()
|
||||
}()
|
||||
}
|
||||
|
||||
//export gio_dispatchMainFuncs
|
||||
func gio_dispatchMainFuncs() {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string, and
|
||||
// releases the original string.
|
||||
func nsstringToString(str C.CFTypeRef) string {
|
||||
defer C.CFRelease(str)
|
||||
n := C.gio_nsstringLength(str)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
chars := make([]uint16, n)
|
||||
C.gio_nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
|
||||
utf8 := utf16.Decode(chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
func NewDisplayLink(callback func()) (*displayLink, error) {
|
||||
d := &displayLink{
|
||||
callback: callback,
|
||||
done: make(chan struct{}),
|
||||
states: make(chan bool),
|
||||
dids: make(chan uint64),
|
||||
}
|
||||
dl := C.gio_createDisplayLink()
|
||||
if dl == 0 {
|
||||
return nil, errors.New("app: failed to create display link")
|
||||
}
|
||||
go d.run(dl)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *displayLink) run(dl C.CFTypeRef) {
|
||||
defer C.gio_releaseDisplayLink(dl)
|
||||
displayLinks.Store(dl, d)
|
||||
defer displayLinks.Delete(dl)
|
||||
var stopTimer *time.Timer
|
||||
var tchan <-chan time.Time
|
||||
started := false
|
||||
for {
|
||||
select {
|
||||
case <-tchan:
|
||||
tchan = nil
|
||||
started = false
|
||||
C.gio_stopDisplayLink(dl)
|
||||
case start := <-d.states:
|
||||
switch {
|
||||
case !start && tchan == nil:
|
||||
// stopTimeout is the delay before stopping the display link to
|
||||
// avoid the overhead of frequently starting and stopping the
|
||||
// link thread.
|
||||
const stopTimeout = 500 * time.Millisecond
|
||||
if stopTimer == nil {
|
||||
stopTimer = time.NewTimer(stopTimeout)
|
||||
} else {
|
||||
// stopTimer is always drained when tchan == nil.
|
||||
stopTimer.Reset(stopTimeout)
|
||||
}
|
||||
tchan = stopTimer.C
|
||||
atomic.StoreUint32(&d.running, 0)
|
||||
case start:
|
||||
if tchan != nil && !stopTimer.Stop() {
|
||||
<-tchan
|
||||
}
|
||||
tchan = nil
|
||||
atomic.StoreUint32(&d.running, 1)
|
||||
if !started {
|
||||
started = true
|
||||
C.gio_startDisplayLink(dl)
|
||||
}
|
||||
}
|
||||
case did := <-d.dids:
|
||||
C.gio_setDisplayLinkDisplay(dl, C.uint64_t(did))
|
||||
case <-d.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *displayLink) Start() {
|
||||
d.states <- true
|
||||
}
|
||||
|
||||
func (d *displayLink) Stop() {
|
||||
d.states <- false
|
||||
}
|
||||
|
||||
func (d *displayLink) Close() {
|
||||
close(d.done)
|
||||
}
|
||||
|
||||
func (d *displayLink) SetDisplayID(did uint64) {
|
||||
d.dids <- did
|
||||
}
|
||||
|
||||
//export gio_onFrameCallback
|
||||
func gio_onFrameCallback(dl C.CFTypeRef) {
|
||||
if d, exists := displayLinks.Load(dl); exists {
|
||||
d := d.(*displayLink)
|
||||
if atomic.LoadUint32(&d.running) != 0 {
|
||||
d.callback()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
@import Dispatch;
|
||||
@import Foundation;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_wakeupMainThread(void) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_dispatchMainFuncs();
|
||||
});
|
||||
}
|
||||
|
||||
NSUInteger gio_nsstringLength(CFTypeRef cstr) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
return [str length];
|
||||
}
|
||||
|
||||
void gio_nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
[str getCharacters:chars range:NSMakeRange(loc, length)];
|
||||
}
|
@ -0,0 +1,316 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
package window
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
CGFloat top, right, bottom, left;
|
||||
};
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_showTextInput(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_hideTextInput(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_removeLayer(CFTypeRef layerRef);
|
||||
__attribute__ ((visibility ("hidden"))) struct drawParams gio_viewDrawParams(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w Callbacks
|
||||
displayLink *displayLink
|
||||
|
||||
layer C.CFTypeRef
|
||||
visible atomic.Value
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var layerFactory func() uintptr
|
||||
|
||||
var views = make(map[C.CFTypeRef]*window)
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view C.CFTypeRef) {
|
||||
w := &window{
|
||||
view: view,
|
||||
}
|
||||
dl, err := NewDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.displayLink = dl
|
||||
wopts := <-mainWindow.out
|
||||
w.w = wopts.window
|
||||
w.w.SetDriver(w)
|
||||
w.visible.Store(false)
|
||||
w.layer = C.CFTypeRef(layerFactory())
|
||||
C.gio_addLayerToView(view, w.layer)
|
||||
views[view] = w
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
params := C.gio_viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
wasVisible := w.isVisible()
|
||||
w.visible.Store(true)
|
||||
C.gio_updateView(w.view, w.layer)
|
||||
if !wasVisible {
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: system.Insets{
|
||||
Top: unit.Px(float32(params.top)),
|
||||
Right: unit.Px(float32(params.right)),
|
||||
Bottom: unit.Px(float32(params.bottom)),
|
||||
Left: unit.Px(float32(params.left)),
|
||||
},
|
||||
Metric: unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
},
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.visible.Store(false)
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
delete(views, view)
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
C.gio_removeLayer(w.layer)
|
||||
C.CFRelease(w.layer)
|
||||
w.layer = 0
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(view C.CFTypeRef, focus int) {
|
||||
w := views[view]
|
||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(view C.CFTypeRef, str *C.char) {
|
||||
w := views[view]
|
||||
w.w.Event(key.EditEvent{
|
||||
Text: C.GoString(str),
|
||||
})
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var typ pointer.Type
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
typ = pointer.Press
|
||||
case C.UITouchPhaseMoved:
|
||||
typ = pointer.Move
|
||||
case C.UITouchPhaseEnded:
|
||||
typ = pointer.Release
|
||||
case C.UITouchPhaseCancelled:
|
||||
typ = pointer.Cancel
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := views[view]
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
Position: p,
|
||||
Time: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func() {
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
w.w.Event(system.ClipboardEvent{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
runOnMain(func() {
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name string) {
|
||||
w.w.Event(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
// lookupTouch maps an UITouch pointer value to an index. If
|
||||
// last is set, the map is cleared.
|
||||
func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
|
||||
id := -1
|
||||
for i, ref := range w.pointerMap {
|
||||
if ref == touch {
|
||||
id = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
id = len(w.pointerMap)
|
||||
w.pointerMap = append(w.pointerMap, touch)
|
||||
}
|
||||
if last {
|
||||
w.pointerMap = w.pointerMap[:0]
|
||||
}
|
||||
return pointer.ID(id)
|
||||
}
|
||||
|
||||
func (w *window) contextLayer() uintptr {
|
||||
return uintptr(w.layer)
|
||||
}
|
||||
|
||||
func (w *window) isVisible() bool {
|
||||
return w.visible.Load().(bool)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
C.CFRetain(v)
|
||||
runOnMain(func() {
|
||||
defer C.CFRelease(v)
|
||||
if show {
|
||||
C.gio_showTextInput(w.view)
|
||||
} else {
|
||||
C.gio_hideTextInput(w.view)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for iOS.
|
||||
func (w *window) Close() {}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
mainWindow.in <- windowAndOptions{win, opts}
|
||||
return <-mainWindow.errs
|
||||
}
|
||||
|
||||
func Main() {
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
runMain()
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
@import UIKit;
|
||||
|
||||
#include <stdint.h>
|
||||
#include "_cgo_export.h"
|
||||
#include "framework_ios.h"
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
|
||||
CGFloat _keyboardHeight;
|
||||
|
||||
- (void)loadView {
|
||||
gio_runMain();
|
||||
|
||||
CGRect zeroFrame = CGRectMake(0, 0, 0, 0);
|
||||
self.view = [[UIView alloc] initWithFrame:zeroFrame];
|
||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||
[self.view addSubview: drawView];
|
||||
#ifndef TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView);
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillChangeFrameNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillHide:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationDidEnterBackground:)
|
||||
name: UIApplicationDidEnterBackgroundNotification
|
||||
object: nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationWillEnterForeground:)
|
||||
name: UIApplicationWillEnterForegroundNotification
|
||||
object: nil];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
onStop((__bridge CFTypeRef)drawView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||
onDestroy(viewRef);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
UIView *view = self.view.subviews[0];
|
||||
CGRect frame = self.view.bounds;
|
||||
// Adjust view bounds to make room for the keyboard.
|
||||
frame.size.height -= _keyboardHeight;
|
||||
view.frame = frame;
|
||||
gio_onDraw((__bridge CFTypeRef)view);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
onLowMemory();
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
- (void)keyboardWillChange:(NSNotification *)note {
|
||||
NSDictionary *userInfo = note.userInfo;
|
||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
_keyboardHeight = f.size.height;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)note {
|
||||
_keyboardHeight = 0.0;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||
for (UITouch *touch in touches) {
|
||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||
i++;
|
||||
NSArray<UITouch *> *coalescedTouches = [event coalescedTouchesForTouch:touch];
|
||||
NSUInteger j = 0;
|
||||
NSUInteger m = [coalescedTouches count];
|
||||
for (UITouch *coalescedTouch in [event coalescedTouchesForTouch:touch]) {
|
||||
CGPoint loc = [coalescedTouch locationInView:view];
|
||||
j++;
|
||||
int lastTouch = last && i == n && j == m;
|
||||
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioView
|
||||
NSArray<UIKeyCommand *> *_keyCommands;
|
||||
+ (void)onFrameCallback:(CADisplayLink *)link {
|
||||
gio_onFrameCallback((__bridge CFTypeRef)link);
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidBecomeKeyNotification
|
||||
object:self.window];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIWindowDidResignKeyNotification
|
||||
object:self.window];
|
||||
}
|
||||
self.contentScaleFactor = newWindow.screen.nativeScale;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onWindowDidBecomeKey:)
|
||||
name:UIWindowDidBecomeKeyNotification
|
||||
object:newWindow];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onWindowDidResignKey:)
|
||||
name:UIWindowDidResignKeyNotification
|
||||
object:newWindow];
|
||||
}
|
||||
|
||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(0, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(0, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(1, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(1, self, touches, event);
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
onText((__bridge CFTypeRef)self, (char *)text.UTF8String);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)hasText {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow((__bridge CFTypeRef)self);
|
||||
}
|
||||
|
||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||
if (_keyCommands == nil) {
|
||||
_keyCommands = @[
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onUpArrow)],
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onDownArrow)],
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onLeftArrow)],
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow
|
||||
modifierFlags:0
|
||||
action:@selector(onRightArrow)]
|
||||
];
|
||||
}
|
||||
return _keyCommands;
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_writeClipboard(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_readClipboard(void) {
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
}
|
||||
|
||||
void gio_showTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view becomeFirstResponder];
|
||||
}
|
||||
|
||||
void gio_hideTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view resignFirstResponder];
|
||||
}
|
||||
|
||||
void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CALayer *layer = (__bridge CALayer *)layerRef;
|
||||
[view.layer addSublayer:layer];
|
||||
}
|
||||
|
||||
void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
|
||||
layer.contentsScale = view.contentScaleFactor;
|
||||
layer.bounds = view.bounds;
|
||||
}
|
||||
|
||||
void gio_removeLayer(CFTypeRef layerRef) {
|
||||
CALayer *layer = (__bridge CALayer *)layerRef;
|
||||
[layer removeFromSuperlayer];
|
||||
}
|
||||
|
||||
struct drawParams gio_viewDrawParams(CFTypeRef viewRef) {
|
||||
UIView *v = (__bridge UIView *)viewRef;
|
||||
struct drawParams params;
|
||||
CGFloat scale = v.layer.contentsScale;
|
||||
// Use 163 as the standard ppi on iOS.
|
||||
params.dpi = 163*scale;
|
||||
params.sdpi = params.dpi;
|
||||
UIEdgeInsets insets = v.layoutMargins;
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
|
||||
params.sdpi = [metrics scaledValueForValue:params.sdpi];
|
||||
insets = v.safeAreaInsets;
|
||||
}
|
||||
params.width = v.bounds.size.width*scale;
|
||||
params.height = v.bounds.size.height*scale;
|
||||
params.top = insets.top*scale;
|
||||
params.right = insets.right*scale;
|
||||
params.bottom = insets.bottom*scale;
|
||||
params.left = insets.left*scale;
|
||||
return params;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
|
||||
dl.paused = YES;
|
||||
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
||||
[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
|
||||
return (__bridge_retained CFTypeRef)dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
dl.paused = NO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
dl.paused = YES;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dlref) {
|
||||
CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
|
||||
[dl invalidate];
|
||||
CFRelease(dlref);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
// Nothing to do on iOS.
|
||||
}
|
@ -0,0 +1,494 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"image"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
window js.Value
|
||||
clipboard js.Value
|
||||
cnv js.Value
|
||||
tarea js.Value
|
||||
w Callbacks
|
||||
redraw js.Func
|
||||
clipboardCallback js.Func
|
||||
requestAnimationFrame js.Value
|
||||
cleanfuncs []func()
|
||||
touches []js.Value
|
||||
composing bool
|
||||
|
||||
mu sync.Mutex
|
||||
scale float32
|
||||
animating bool
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
doc := js.Global().Get("document")
|
||||
cont := getContainer(doc)
|
||||
cnv := createCanvas(doc)
|
||||
cont.Call("appendChild", cnv)
|
||||
tarea := createTextArea(doc)
|
||||
cont.Call("appendChild", tarea)
|
||||
w := &window{
|
||||
cnv: cnv,
|
||||
tarea: tarea,
|
||||
window: js.Global().Get("window"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
}
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
w.animCallback()
|
||||
return nil
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
win.Event(system.ClipboardEvent{Text: content})
|
||||
return nil
|
||||
})
|
||||
w.addEventListeners()
|
||||
w.w = win
|
||||
go func() {
|
||||
w.w.SetDriver(w)
|
||||
w.focus()
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
w.draw(true)
|
||||
select {}
|
||||
w.cleanup()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainer(doc js.Value) js.Value {
|
||||
cont := doc.Call("getElementById", "giowindow")
|
||||
if !cont.IsNull() {
|
||||
return cont
|
||||
}
|
||||
cont = doc.Call("createElement", "DIV")
|
||||
doc.Get("body").Call("appendChild", cont)
|
||||
return cont
|
||||
}
|
||||
|
||||
func createTextArea(doc js.Value) js.Value {
|
||||
tarea := doc.Call("createElement", "input")
|
||||
style := tarea.Get("style")
|
||||
style.Set("width", "1px")
|
||||
style.Set("height", "1px")
|
||||
style.Set("opacity", "0")
|
||||
style.Set("border", "0")
|
||||
style.Set("padding", "0")
|
||||
tarea.Set("autocomplete", "off")
|
||||
tarea.Set("autocorrect", "off")
|
||||
tarea.Set("autocapitalize", "off")
|
||||
tarea.Set("spellcheck", false)
|
||||
return tarea
|
||||
}
|
||||
|
||||
func createCanvas(doc js.Value) js.Value {
|
||||
cnv := doc.Call("createElement", "canvas")
|
||||
style := cnv.Get("style")
|
||||
style.Set("position", "fixed")
|
||||
style.Set("width", "100%")
|
||||
style.Set("height", "100%")
|
||||
return cnv
|
||||
}
|
||||
|
||||
func (w *window) cleanup() {
|
||||
// Cleanup in the opposite order of
|
||||
// construction.
|
||||
for i := len(w.cleanfuncs) - 1; i >= 0; i-- {
|
||||
w.cleanfuncs[i]()
|
||||
}
|
||||
w.cleanfuncs = nil
|
||||
}
|
||||
|
||||
func (w *window) addEventListeners() {
|
||||
w.addEventListener(w.window, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mousedown", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Press, 0, 0, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mouseup", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Release, 0, 0, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
|
||||
e := args[0]
|
||||
dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
|
||||
mode := e.Get("deltaMode").Int()
|
||||
switch mode {
|
||||
case 0x01: // DOM_DELTA_LINE
|
||||
dx *= 10
|
||||
dy *= 10
|
||||
case 0x02: // DOM_DELTA_PAGE
|
||||
dx *= 120
|
||||
dy *= 120
|
||||
}
|
||||
w.pointerEvent(pointer.Scroll, float32(dx), float32(dy), e)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchstart", func(this js.Value, args []js.Value) interface{} {
|
||||
w.touchEvent(pointer.Press, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchend", func(this js.Value, args []js.Value) interface{} {
|
||||
w.touchEvent(pointer.Release, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchmove", func(this js.Value, args []js.Value) interface{} {
|
||||
w.touchEvent(pointer.Move, args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "touchcancel", func(this js.Value, args []js.Value) interface{} {
|
||||
// Cancel all touches even if only one touch was cancelled.
|
||||
for i := range w.touches {
|
||||
w.touches[i] = js.Null()
|
||||
}
|
||||
w.touches = w.touches[:0]
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "keydown", func(this js.Value, args []js.Value) interface{} {
|
||||
w.keyEvent(args[0])
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
|
||||
w.composing = true
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
|
||||
w.composing = false
|
||||
w.flushInput()
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
|
||||
if w.composing {
|
||||
return nil
|
||||
}
|
||||
w.flushInput()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) flushInput() {
|
||||
val := w.tarea.Get("value").String()
|
||||
w.tarea.Set("value", "")
|
||||
w.w.Event(key.EditEvent{Text: string(val)})
|
||||
}
|
||||
|
||||
func (w *window) blur() {
|
||||
w.tarea.Call("blur")
|
||||
}
|
||||
|
||||
func (w *window) focus() {
|
||||
w.tarea.Call("focus")
|
||||
}
|
||||
|
||||
func (w *window) keyEvent(e js.Value) {
|
||||
k := e.Get("key").String()
|
||||
if n, ok := translateKey(k); ok {
|
||||
cmd := key.Event{
|
||||
Name: n,
|
||||
Modifiers: modifiersFor(e),
|
||||
}
|
||||
w.w.Event(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||
// KeyEvent.
|
||||
func modifiersFor(e js.Value) key.Modifiers {
|
||||
var mods key.Modifiers
|
||||
if e.Call("getModifierState", "Alt").Bool() {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if e.Call("getModifierState", "Control").Bool() {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
if e.Call("getModifierState", "Shift").Bool() {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
||||
e.Call("preventDefault")
|
||||
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
||||
changedTouches := e.Get("changedTouches")
|
||||
n := changedTouches.Length()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
w.mu.Lock()
|
||||
scale := w.scale
|
||||
w.mu.Unlock()
|
||||
var mods key.Modifiers
|
||||
if e.Get("shiftKey").Bool() {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
if e.Get("altKey").Bool() {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if e.Get("ctrlKey").Bool() {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
touch := changedTouches.Index(i)
|
||||
pid := w.touchIDFor(touch)
|
||||
x, y := touch.Get("clientX").Float(), touch.Get("clientY").Float()
|
||||
x -= rect.Get("left").Float()
|
||||
y -= rect.Get("top").Float()
|
||||
pos := f32.Point{
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Touch,
|
||||
Position: pos,
|
||||
PointerID: pid,
|
||||
Time: t,
|
||||
Modifiers: modifiersFor(e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) touchIDFor(touch js.Value) pointer.ID {
|
||||
id := touch.Get("identifier")
|
||||
for i, id2 := range w.touches {
|
||||
if id2.Equal(id) {
|
||||
return pointer.ID(i)
|
||||
}
|
||||
}
|
||||
pid := pointer.ID(len(w.touches))
|
||||
w.touches = append(w.touches, id)
|
||||
return pid
|
||||
}
|
||||
|
||||
func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
||||
e.Call("preventDefault")
|
||||
x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
x -= rect.Get("left").Float()
|
||||
y -= rect.Get("top").Float()
|
||||
w.mu.Lock()
|
||||
scale := w.scale
|
||||
w.mu.Unlock()
|
||||
pos := f32.Point{
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
scroll := f32.Point{
|
||||
X: dx * scale,
|
||||
Y: dy * scale,
|
||||
}
|
||||
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
||||
jbtns := e.Get("buttons").Int()
|
||||
var btns pointer.Buttons
|
||||
if jbtns&1 != 0 {
|
||||
btns |= pointer.ButtonLeft
|
||||
}
|
||||
if jbtns&2 != 0 {
|
||||
btns |= pointer.ButtonRight
|
||||
}
|
||||
if jbtns&4 != 0 {
|
||||
btns |= pointer.ButtonMiddle
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: btns,
|
||||
Position: pos,
|
||||
Scroll: scroll,
|
||||
Time: t,
|
||||
Modifiers: modifiersFor(e),
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) addEventListener(this js.Value, event string, f func(this js.Value, args []js.Value) interface{}) {
|
||||
jsf := w.funcOf(f)
|
||||
this.Call("addEventListener", event, jsf)
|
||||
w.cleanfuncs = append(w.cleanfuncs, func() {
|
||||
this.Call("removeEventListener", event, jsf)
|
||||
})
|
||||
}
|
||||
|
||||
// funcOf is like js.FuncOf but adds the js.Func to a list of
|
||||
// functions to be released up.
|
||||
func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.Func {
|
||||
jsf := js.FuncOf(f)
|
||||
w.cleanfuncs = append(w.cleanfuncs, jsf.Release)
|
||||
return jsf
|
||||
}
|
||||
|
||||
func (w *window) animCallback() {
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if anim && !w.animating {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("readText").IsUndefined() {
|
||||
return
|
||||
}
|
||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("writeText").IsUndefined() {
|
||||
return
|
||||
}
|
||||
w.clipboard.Call("writeText", s)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
go func() {
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for js.
|
||||
func (w *window) Close() {}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
width, height, scale, cfg := w.config()
|
||||
if cfg == (unit.Metric{}) || width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.scale = float32(scale)
|
||||
w.mu.Unlock()
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: width,
|
||||
Y: height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) config() (int, int, float32, unit.Metric) {
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
width, height := rect.Get("width").Float(), rect.Get("height").Float()
|
||||
scale := w.window.Get("devicePixelRatio").Float()
|
||||
width *= scale
|
||||
height *= scale
|
||||
iw, ih := int(width+.5), int(height+.5)
|
||||
// Adjust internal size of canvas if necessary.
|
||||
if cw, ch := w.cnv.Get("width").Int(), w.cnv.Get("height").Int(); iw != cw || ih != ch {
|
||||
w.cnv.Set("width", iw)
|
||||
w.cnv.Set("height", ih)
|
||||
}
|
||||
return iw, ih, float32(scale), unit.Metric{
|
||||
PxPerDp: float32(scale),
|
||||
PxPerSp: float32(scale),
|
||||
}
|
||||
}
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func translateKey(k string) (string, bool) {
|
||||
var n string
|
||||
switch k {
|
||||
case "ArrowUp":
|
||||
n = key.NameUpArrow
|
||||
case "ArrowDown":
|
||||
n = key.NameDownArrow
|
||||
case "ArrowLeft":
|
||||
n = key.NameLeftArrow
|
||||
case "ArrowRight":
|
||||
n = key.NameRightArrow
|
||||
case "Escape":
|
||||
n = key.NameEscape
|
||||
case "Enter":
|
||||
n = key.NameReturn
|
||||
case "Backspace":
|
||||
n = key.NameDeleteBackward
|
||||
case "Delete":
|
||||
n = key.NameDeleteForward
|
||||
case "Home":
|
||||
n = key.NameHome
|
||||
case "End":
|
||||
n = key.NameEnd
|
||||
case "PageUp":
|
||||
n = key.NamePageUp
|
||||
case "PageDown":
|
||||
n = key.NamePageDown
|
||||
case "Tab":
|
||||
n = key.NameTab
|
||||
case " ":
|
||||
n = "Space"
|
||||
case "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12":
|
||||
n = k
|
||||
default:
|
||||
r, s := utf8.DecodeRuneInString(k)
|
||||
// If there is exactly one printable character, return that.
|
||||
if s == len(k) && unicode.IsPrint(r) {
|
||||
return strings.ToUpper(k), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
@ -0,0 +1,460 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
|
||||
_ "gioui.org/app/internal/cocoainit"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
#define GIO_MOUSE_MOVE 1
|
||||
#define GIO_MOUSE_UP 2
|
||||
#define GIO_MOUSE_DOWN 3
|
||||
#define GIO_MOUSE_SCROLL 4
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewWidth(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_getViewBackingScale(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_getScreenBackingScale(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef);
|
||||
__attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_close(CFTypeRef windowRef);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
// Darwin requires that UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
window C.CFTypeRef
|
||||
w Callbacks
|
||||
stage system.Stage
|
||||
displayLink *displayLink
|
||||
|
||||
scale float32
|
||||
}
|
||||
|
||||
// viewMap is the mapping from Cocoa NSViews to Go windows.
|
||||
var viewMap = make(map[C.CFTypeRef]*window)
|
||||
|
||||
var viewFactory func() C.CFTypeRef
|
||||
|
||||
// launched is closed when applicationDidFinishLaunching is called.
|
||||
var launched = make(chan struct{})
|
||||
|
||||
// nextTopLeft is the offset to use for the next window's call to
|
||||
// cascadeTopLeftFromPoint.
|
||||
var nextTopLeft C.NSPoint
|
||||
|
||||
// mustView is like lookoupView, except that it panics
|
||||
// if the view isn't mapped.
|
||||
func mustView(view C.CFTypeRef) *window {
|
||||
w, ok := lookupView(view)
|
||||
if !ok {
|
||||
panic("no window for view")
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func lookupView(view C.CFTypeRef) (*window, bool) {
|
||||
w, exists := viewMap[view]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
return w, true
|
||||
}
|
||||
|
||||
func deleteView(view C.CFTypeRef) {
|
||||
delete(viewMap, view)
|
||||
}
|
||||
|
||||
func insertView(view C.CFTypeRef, w *window) {
|
||||
viewMap[view] = w
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
return w.view
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func() {
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
w.w.Event(system.ClipboardEvent{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
runOnMain(func() {
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
runOnMain(func() {
|
||||
// Make sure the view is still valid. The window might've been closed
|
||||
// during the switch to the main thread.
|
||||
if w.view != 0 {
|
||||
C.gio_close(w.window)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.w.Event(system.StageEvent{Stage: stage})
|
||||
}
|
||||
|
||||
//export gio_onKeys
|
||||
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
|
||||
str := C.GoString(cstr)
|
||||
kmods := convertMods(mods)
|
||||
w := mustView(view)
|
||||
for _, k := range str {
|
||||
if n, ok := convertKey(k); ok {
|
||||
w.w.Event(key.Event{
|
||||
Name: n,
|
||||
Modifiers: kmods,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onText
|
||||
func gio_onText(view C.CFTypeRef, cstr *C.char) {
|
||||
str := C.GoString(cstr)
|
||||
w := mustView(view)
|
||||
w.w.Event(key.EditEvent{Text: str})
|
||||
}
|
||||
|
||||
//export gio_onMouse
|
||||
func gio_onMouse(view C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
||||
var typ pointer.Type
|
||||
switch cdir {
|
||||
case C.GIO_MOUSE_MOVE:
|
||||
typ = pointer.Move
|
||||
case C.GIO_MOUSE_UP:
|
||||
typ = pointer.Release
|
||||
case C.GIO_MOUSE_DOWN:
|
||||
typ = pointer.Press
|
||||
case C.GIO_MOUSE_SCROLL:
|
||||
typ = pointer.Scroll
|
||||
default:
|
||||
panic("invalid direction")
|
||||
}
|
||||
var btns pointer.Buttons
|
||||
if cbtns&(1<<0) != 0 {
|
||||
btns |= pointer.ButtonLeft
|
||||
}
|
||||
if cbtns&(1<<1) != 0 {
|
||||
btns |= pointer.ButtonRight
|
||||
}
|
||||
if cbtns&(1<<2) != 0 {
|
||||
btns |= pointer.ButtonMiddle
|
||||
}
|
||||
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
||||
w := mustView(view)
|
||||
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
||||
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Time: t,
|
||||
Buttons: btns,
|
||||
Position: f32.Point{X: xf, Y: yf},
|
||||
Scroll: f32.Point{X: dxf, Y: dyf},
|
||||
Modifiers: convertMods(mods),
|
||||
})
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFocus
|
||||
func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
|
||||
w := mustView(view)
|
||||
w.w.Event(key.FocusEvent{Focus: focus == C.YES})
|
||||
}
|
||||
|
||||
//export gio_onChangeScreen
|
||||
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
|
||||
w := mustView(view)
|
||||
w.displayLink.SetDisplayID(did)
|
||||
}
|
||||
|
||||
func (w *window) draw() {
|
||||
w.scale = float32(C.gio_getViewBackingScale(w.view))
|
||||
wf, hf := float32(C.gio_viewWidth(w.view)), float32(C.gio_viewHeight(w.view))
|
||||
if wf == 0 || hf == 0 {
|
||||
return
|
||||
}
|
||||
width := int(wf*w.scale + .5)
|
||||
height := int(hf*w.scale + .5)
|
||||
cfg := configFor(w.scale)
|
||||
w.setStage(system.StageRunning)
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: width,
|
||||
Y: height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: true,
|
||||
})
|
||||
}
|
||||
|
||||
func configFor(scale float32) unit.Metric {
|
||||
return unit.Metric{
|
||||
PxPerDp: scale,
|
||||
PxPerSp: scale,
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onClose
|
||||
func gio_onClose(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.displayLink.Close()
|
||||
deleteView(view)
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
C.CFRelease(w.view)
|
||||
w.view = 0
|
||||
C.CFRelease(w.window)
|
||||
w.window = 0
|
||||
}
|
||||
|
||||
//export gio_onHide
|
||||
func gio_onHide(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export gio_onShow
|
||||
func gio_onShow(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
|
||||
//export gio_onAppHide
|
||||
func gio_onAppHide() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onAppShow
|
||||
func gio_onAppShow() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onFinishLaunching
|
||||
func gio_onFinishLaunching() {
|
||||
close(launched)
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
<-launched
|
||||
errch := make(chan error)
|
||||
runOnMain(func() {
|
||||
w, err := newWindow(opts)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
screenScale := float32(C.gio_getScreenBackingScale())
|
||||
cfg := configFor(screenScale)
|
||||
width := cfg.Px(opts.Width)
|
||||
height := cfg.Px(opts.Height)
|
||||
// Window sizes is in unscaled screen coordinates, not device pixels.
|
||||
width = int(float32(width) / screenScale)
|
||||
height = int(float32(height) / screenScale)
|
||||
minWidth := cfg.Px(opts.MinWidth)
|
||||
minHeight := cfg.Px(opts.MinHeight)
|
||||
minWidth = int(float32(minWidth) / screenScale)
|
||||
minHeight = int(float32(minHeight) / screenScale)
|
||||
maxWidth := cfg.Px(opts.MaxWidth)
|
||||
maxHeight := cfg.Px(opts.MaxHeight)
|
||||
maxWidth = int(float32(maxWidth) / screenScale)
|
||||
maxHeight = int(float32(maxHeight) / screenScale)
|
||||
title := C.CString(opts.Title)
|
||||
defer C.free(unsafe.Pointer(title))
|
||||
errch <- nil
|
||||
win.SetDriver(w)
|
||||
w.w = win
|
||||
w.window = C.gio_createWindow(w.view, title, C.CGFloat(width), C.CGFloat(height),
|
||||
C.CGFloat(minWidth), C.CGFloat(minHeight), C.CGFloat(maxWidth), C.CGFloat(maxHeight))
|
||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||
// and just returns the offset we need for the first window.
|
||||
nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft)
|
||||
}
|
||||
nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft)
|
||||
C.gio_makeKeyAndOrderFront(w.window)
|
||||
})
|
||||
return <-errch
|
||||
}
|
||||
|
||||
func newWindow(opts *Options) (*window, error) {
|
||||
view := viewFactory()
|
||||
if view == 0 {
|
||||
return nil, errors.New("CreateWindow: failed to create view")
|
||||
}
|
||||
scale := float32(C.gio_getViewBackingScale(view))
|
||||
w := &window{
|
||||
view: view,
|
||||
scale: scale,
|
||||
}
|
||||
dl, err := NewDisplayLink(func() {
|
||||
runOnMain(func() {
|
||||
if w.view != 0 {
|
||||
C.gio_setNeedsDisplay(w.view)
|
||||
}
|
||||
})
|
||||
})
|
||||
w.displayLink = dl
|
||||
if err != nil {
|
||||
C.CFRelease(view)
|
||||
return nil, err
|
||||
}
|
||||
insertView(view, w)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func Main() {
|
||||
C.gio_main()
|
||||
}
|
||||
|
||||
func convertKey(k rune) (string, bool) {
|
||||
var n string
|
||||
switch k {
|
||||
case 0x1b:
|
||||
n = key.NameEscape
|
||||
case C.NSLeftArrowFunctionKey:
|
||||
n = key.NameLeftArrow
|
||||
case C.NSRightArrowFunctionKey:
|
||||
n = key.NameRightArrow
|
||||
case C.NSUpArrowFunctionKey:
|
||||
n = key.NameUpArrow
|
||||
case C.NSDownArrowFunctionKey:
|
||||
n = key.NameDownArrow
|
||||
case 0xd:
|
||||
n = key.NameReturn
|
||||
case 0x3:
|
||||
n = key.NameEnter
|
||||
case C.NSHomeFunctionKey:
|
||||
n = key.NameHome
|
||||
case C.NSEndFunctionKey:
|
||||
n = key.NameEnd
|
||||
case 0x7f:
|
||||
n = key.NameDeleteBackward
|
||||
case C.NSDeleteFunctionKey:
|
||||
n = key.NameDeleteForward
|
||||
case C.NSPageUpFunctionKey:
|
||||
n = key.NamePageUp
|
||||
case C.NSPageDownFunctionKey:
|
||||
n = key.NamePageDown
|
||||
case C.NSF1FunctionKey:
|
||||
n = "F1"
|
||||
case C.NSF2FunctionKey:
|
||||
n = "F2"
|
||||
case C.NSF3FunctionKey:
|
||||
n = "F3"
|
||||
case C.NSF4FunctionKey:
|
||||
n = "F4"
|
||||
case C.NSF5FunctionKey:
|
||||
n = "F5"
|
||||
case C.NSF6FunctionKey:
|
||||
n = "F6"
|
||||
case C.NSF7FunctionKey:
|
||||
n = "F7"
|
||||
case C.NSF8FunctionKey:
|
||||
n = "F8"
|
||||
case C.NSF9FunctionKey:
|
||||
n = "F9"
|
||||
case C.NSF10FunctionKey:
|
||||
n = "F10"
|
||||
case C.NSF11FunctionKey:
|
||||
n = "F11"
|
||||
case C.NSF12FunctionKey:
|
||||
n = "F12"
|
||||
case 0x09, 0x19:
|
||||
n = key.NameTab
|
||||
case 0x20:
|
||||
n = "Space"
|
||||
default:
|
||||
k = unicode.ToUpper(k)
|
||||
if !unicode.IsPrint(k) {
|
||||
return "", false
|
||||
}
|
||||
n = string(k)
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func convertMods(mods C.NSUInteger) key.Modifiers {
|
||||
var kmods key.Modifiers
|
||||
if mods&C.NSAlternateKeyMask != 0 {
|
||||
kmods |= key.ModAlt
|
||||
}
|
||||
if mods&C.NSControlKeyMask != 0 {
|
||||
kmods |= key.ModCtrl
|
||||
}
|
||||
if mods&C.NSCommandKeyMask != 0 {
|
||||
kmods |= key.ModCommand
|
||||
}
|
||||
if mods&C.NSShiftKeyMask != 0 {
|
||||
kmods |= key.ModShift
|
||||
}
|
||||
return kmods
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
@import AppKit;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||
gio_onChangeScreen(view, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, YES);
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, NO);
|
||||
}
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
window.delegate = nil;
|
||||
gio_onClose((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
@end
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
// else holds a strong reference to our window delegate, so
|
||||
// keep a single global reference instead.
|
||||
static GioWindowDelegate *globalWindowDel;
|
||||
|
||||
void gio_writeClipboard(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
NSPasteboard *p = NSPasteboard.generalPasteboard;
|
||||
[p declareTypes:@[NSPasteboardTypeString] owner:nil];
|
||||
[p setString:s forType:NSPasteboardTypeString];
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_readClipboard(void) {
|
||||
@autoreleasepool {
|
||||
NSPasteboard *p = NSPasteboard.generalPasteboard;
|
||||
NSString *content = [p stringForType:NSPasteboardTypeString];
|
||||
return (__bridge_retained CFTypeRef)content;
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat gio_viewHeight(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
}
|
||||
|
||||
CGFloat gio_viewWidth(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
}
|
||||
|
||||
CGFloat gio_getScreenBackingScale(void) {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
}
|
||||
|
||||
CGFloat gio_getViewBackingScale(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
}
|
||||
|
||||
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
|
||||
gio_onFrameCallback(dl);
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CVDisplayLinkRef dl;
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
||||
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
|
||||
return dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStart((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStop((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dl) {
|
||||
CVDisplayLinkRelease((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
|
||||
}
|
||||
|
||||
NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
}
|
||||
|
||||
void gio_makeKeyAndOrderFront(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
NSResizableWindowMask |
|
||||
NSMiniaturizableWindowMask |
|
||||
NSClosableWindowMask;
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
if (minWidth > 0 || minHeight > 0) {
|
||||
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
||||
}
|
||||
if (maxWidth > 0 || maxHeight > 0) {
|
||||
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
||||
}
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
window.title = [NSString stringWithUTF8String: title];
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
[window makeFirstResponder:view];
|
||||
window.releasedWhenClosed = NO;
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
void gio_close(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||
gio_onAppHide();
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onAppShow();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
@autoreleasepool {
|
||||
[NSApplication sharedApplication];
|
||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||
[NSApp setDelegate:del];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
NSMenuItem *mainMenu = [NSMenuItem new];
|
||||
|
||||
NSMenu *menu = [NSMenu new];
|
||||
NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
[menu addItem:hideMenuItem];
|
||||
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
[menu addItem:quitMenuItem];
|
||||
[mainMenu setSubmenu:menu];
|
||||
NSMenu *menuBar = [NSMenu new];
|
||||
[menuBar addItem:mainMenu];
|
||||
[NSApp setMainMenu:menuBar];
|
||||
|
||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||
|
||||
[NSApp run];
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
// instead of creating files with build tags for each combination of wayland +/- x11
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver func(Callbacks, *Options) error
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
var errFirst, err error
|
||||
if wlDriver != nil {
|
||||
if err = wlDriver(window, opts); err == nil {
|
||||
return nil
|
||||
}
|
||||
errFirst = err
|
||||
}
|
||||
if x11Driver != nil {
|
||||
if err = x11Driver(window, opts); err == nil {
|
||||
return nil
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst != nil {
|
||||
return errFirst
|
||||
}
|
||||
return errors.New("app: no window driver available")
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "wayland_xdg_shell.h"
|
||||
#include "wayland_text_input.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
const struct wl_registry_listener gio_registry_listener = {
|
||||
// Cast away const parameter.
|
||||
.global = (void (*)(void *, struct wl_registry *, uint32_t, const char *, uint32_t))gio_onRegistryGlobal,
|
||||
.global_remove = gio_onRegistryGlobalRemove
|
||||
};
|
||||
|
||||
const struct wl_surface_listener gio_surface_listener = {
|
||||
.enter = gio_onSurfaceEnter,
|
||||
.leave = gio_onSurfaceLeave,
|
||||
};
|
||||
|
||||
const struct xdg_surface_listener gio_xdg_surface_listener = {
|
||||
.configure = gio_onXdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
|
||||
.configure = gio_onToplevelConfigure,
|
||||
.close = gio_onToplevelClose,
|
||||
};
|
||||
|
||||
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
|
||||
xdg_wm_base_pong(wm, serial);
|
||||
}
|
||||
|
||||
const struct xdg_wm_base_listener gio_xdg_wm_base_listener = {
|
||||
.ping = xdg_wm_base_handle_ping,
|
||||
};
|
||||
|
||||
const struct wl_callback_listener gio_callback_listener = {
|
||||
.done = gio_onFrameDone,
|
||||
};
|
||||
|
||||
const struct wl_output_listener gio_output_listener = {
|
||||
// Cast away const parameter.
|
||||
.geometry = (void (*)(void *, struct wl_output *, int32_t, int32_t, int32_t, int32_t, int32_t, const char *, const char *, int32_t))gio_onOutputGeometry,
|
||||
.mode = gio_onOutputMode,
|
||||
.done = gio_onOutputDone,
|
||||
.scale = gio_onOutputScale,
|
||||
};
|
||||
|
||||
const struct wl_seat_listener gio_seat_listener = {
|
||||
.capabilities = gio_onSeatCapabilities,
|
||||
// Cast away const parameter.
|
||||
.name = (void (*)(void *, struct wl_seat *, const char *))gio_onSeatName,
|
||||
};
|
||||
|
||||
const struct wl_pointer_listener gio_pointer_listener = {
|
||||
.enter = gio_onPointerEnter,
|
||||
.leave = gio_onPointerLeave,
|
||||
.motion = gio_onPointerMotion,
|
||||
.button = gio_onPointerButton,
|
||||
.axis = gio_onPointerAxis,
|
||||
.frame = gio_onPointerFrame,
|
||||
.axis_source = gio_onPointerAxisSource,
|
||||
.axis_stop = gio_onPointerAxisStop,
|
||||
.axis_discrete = gio_onPointerAxisDiscrete,
|
||||
};
|
||||
|
||||
const struct wl_touch_listener gio_touch_listener = {
|
||||
.down = gio_onTouchDown,
|
||||
.up = gio_onTouchUp,
|
||||
.motion = gio_onTouchMotion,
|
||||
.frame = gio_onTouchFrame,
|
||||
.cancel = gio_onTouchCancel,
|
||||
};
|
||||
|
||||
const struct wl_keyboard_listener gio_keyboard_listener = {
|
||||
.keymap = gio_onKeyboardKeymap,
|
||||
.enter = gio_onKeyboardEnter,
|
||||
.leave = gio_onKeyboardLeave,
|
||||
.key = gio_onKeyboardKey,
|
||||
.modifiers = gio_onKeyboardModifiers,
|
||||
.repeat_info = gio_onKeyboardRepeatInfo
|
||||
};
|
||||
|
||||
const struct zwp_text_input_v3_listener gio_zwp_text_input_v3_listener = {
|
||||
.enter = gio_onTextInputEnter,
|
||||
.leave = gio_onTextInputLeave,
|
||||
// Cast away const parameter.
|
||||
.preedit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *, int32_t, int32_t))gio_onTextInputPreeditString,
|
||||
.commit_string = (void (*)(void *, struct zwp_text_input_v3 *, const char *))gio_onTextInputCommitString,
|
||||
.delete_surrounding_text = gio_onTextInputDeleteSurroundingText,
|
||||
.done = gio_onTextInputDone
|
||||
};
|
||||
|
||||
const struct wl_data_device_listener gio_data_device_listener = {
|
||||
.data_offer = gio_onDataDeviceOffer,
|
||||
.enter = gio_onDataDeviceEnter,
|
||||
.leave = gio_onDataDeviceLeave,
|
||||
.motion = gio_onDataDeviceMotion,
|
||||
.drop = gio_onDataDeviceDrop,
|
||||
.selection = gio_onDataDeviceSelection,
|
||||
};
|
||||
|
||||
const struct wl_data_offer_listener gio_data_offer_listener = {
|
||||
.offer = (void (*)(void *, struct wl_data_offer *, const char *))gio_onDataOfferOffer,
|
||||
.source_actions = gio_onDataOfferSourceActions,
|
||||
.action = gio_onDataOfferAction,
|
||||
};
|
||||
|
||||
const struct wl_data_source_listener gio_data_source_listener = {
|
||||
.target = (void (*)(void *, struct wl_data_source *, const char *))gio_onDataSourceTarget,
|
||||
.send = (void (*)(void *, struct wl_data_source *, const char *, int32_t))gio_onDataSourceSend,
|
||||
.cancelled = gio_onDataSourceCancelled,
|
||||
.dnd_drop_performed = gio_onDataSourceDNDDropPerformed,
|
||||
.dnd_finished = gio_onDataSourceDNDFinished,
|
||||
.action = gio_onDataSourceAction,
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,642 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package window
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/windows"
|
||||
"gioui.org/unit"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
)
|
||||
|
||||
type winConstraints struct {
|
||||
minWidth, minHeight int32
|
||||
maxWidth, maxHeight int32
|
||||
}
|
||||
|
||||
type winDeltas struct {
|
||||
width int32
|
||||
height int32
|
||||
}
|
||||
|
||||
type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w Callbacks
|
||||
width int
|
||||
height int
|
||||
stage system.Stage
|
||||
dead bool
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
mu sync.Mutex
|
||||
animating bool
|
||||
|
||||
minmax winConstraints
|
||||
deltas winDeltas
|
||||
opts *Options
|
||||
}
|
||||
|
||||
const _WM_REDRAW = windows.WM_USER + 0
|
||||
|
||||
type gpuAPI struct {
|
||||
priority int
|
||||
initializer func(w *window) (Context, error)
|
||||
}
|
||||
|
||||
// backends is the list of potential Context
|
||||
// implementations.
|
||||
var backends []gpuAPI
|
||||
|
||||
// winMap maps win32 HWNDs to *windows.
|
||||
var winMap sync.Map
|
||||
|
||||
var resources struct {
|
||||
once sync.Once
|
||||
// handle is the module handle from GetModuleHandle.
|
||||
handle syscall.Handle
|
||||
// class is the Gio window class from RegisterClassEx.
|
||||
class uint16
|
||||
// cursor is the arrow cursor resource
|
||||
cursor syscall.Handle
|
||||
}
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
cerr := make(chan error)
|
||||
go func() {
|
||||
// Call win32 API from a single OS thread.
|
||||
runtime.LockOSThread()
|
||||
w, err := createNativeWindow(opts)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
defer w.destroy()
|
||||
cerr <- nil
|
||||
winMap.Store(w.hwnd, w)
|
||||
defer winMap.Delete(w.hwnd)
|
||||
w.w = window
|
||||
w.w.SetDriver(w)
|
||||
defer w.w.Event(system.DestroyEvent{})
|
||||
windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT)
|
||||
windows.SetForegroundWindow(w.hwnd)
|
||||
windows.SetFocus(w.hwnd)
|
||||
if err := w.loop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
return <-cerr
|
||||
}
|
||||
|
||||
// initResources initializes the resources global.
|
||||
func initResources() error {
|
||||
windows.SetProcessDPIAware()
|
||||
hInst, err := windows.GetModuleHandle()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.handle = hInst
|
||||
curs, err := windows.LoadCursor(windows.IDC_ARROW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.cursor = curs
|
||||
wcls := windows.WndClassEx{
|
||||
CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
|
||||
Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
|
||||
LpfnWndProc: syscall.NewCallback(windowProc),
|
||||
HInstance: hInst,
|
||||
HCursor: curs,
|
||||
LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
|
||||
}
|
||||
cls, err := windows.RegisterClassEx(&wcls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.class = cls
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWindowConstraints(cfg unit.Metric, opts *Options, d winDeltas) winConstraints {
|
||||
var minmax winConstraints
|
||||
minmax.minWidth = int32(cfg.Px(opts.MinWidth))
|
||||
minmax.minHeight = int32(cfg.Px(opts.MinHeight))
|
||||
minmax.maxWidth = int32(cfg.Px(opts.MaxWidth))
|
||||
minmax.maxHeight = int32(cfg.Px(opts.MaxHeight))
|
||||
return minmax
|
||||
}
|
||||
|
||||
func createNativeWindow(opts *Options) (*window, error) {
|
||||
var resErr error
|
||||
resources.once.Do(func() {
|
||||
resErr = initResources()
|
||||
})
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
cfg := configForDC()
|
||||
wr := windows.Rect{
|
||||
Right: int32(cfg.Px(opts.Width)),
|
||||
Bottom: int32(cfg.Px(opts.Height)),
|
||||
}
|
||||
dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
|
||||
dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
|
||||
deltas := winDeltas{
|
||||
width: wr.Right,
|
||||
height: wr.Bottom,
|
||||
}
|
||||
windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
|
||||
deltas.width = wr.Right - wr.Left - deltas.width
|
||||
deltas.height = wr.Bottom - wr.Top - deltas.height
|
||||
|
||||
hwnd, err := windows.CreateWindowEx(dwExStyle,
|
||||
resources.class,
|
||||
opts.Title,
|
||||
dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
|
||||
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
||||
wr.Right-wr.Left,
|
||||
wr.Bottom-wr.Top,
|
||||
0,
|
||||
0,
|
||||
resources.handle,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &window{
|
||||
hwnd: hwnd,
|
||||
minmax: getWindowConstraints(cfg, opts, deltas),
|
||||
deltas: deltas,
|
||||
opts: opts,
|
||||
}
|
||||
w.hdc, err = windows.GetDC(hwnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
win, exists := winMap.Load(hwnd)
|
||||
if !exists {
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
w := win.(*window)
|
||||
|
||||
switch msg {
|
||||
case windows.WM_UNICHAR:
|
||||
if wParam == windows.UNICODE_NOCHAR {
|
||||
// Tell the system that we accept WM_UNICHAR messages.
|
||||
return 1
|
||||
}
|
||||
fallthrough
|
||||
case windows.WM_CHAR:
|
||||
if r := rune(wParam); unicode.IsPrint(r) {
|
||||
w.w.Event(key.EditEvent{Text: string(r)})
|
||||
}
|
||||
// The message is processed.
|
||||
return 1
|
||||
case windows.WM_DPICHANGED:
|
||||
// Let Windows know we're prepared for runtime DPI changes.
|
||||
return 1
|
||||
case windows.WM_ERASEBKGND:
|
||||
// Avoid flickering between GPU content and background color.
|
||||
return 1
|
||||
case windows.WM_KEYDOWN, windows.WM_SYSKEYDOWN:
|
||||
if n, ok := convertKeyCode(wParam); ok {
|
||||
w.w.Event(key.Event{Name: n, Modifiers: getModifiers()})
|
||||
}
|
||||
case windows.WM_LBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonLeft, true, lParam, getModifiers())
|
||||
case windows.WM_LBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonLeft, false, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonRight, true, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonRight, false, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonMiddle, true, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonMiddle, false, lParam, getModifiers())
|
||||
case windows.WM_CANCELMODE:
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Cancel,
|
||||
})
|
||||
case windows.WM_SETFOCUS:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
case windows.WM_KILLFOCUS:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case windows.WM_MOUSEMOVE:
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
})
|
||||
case windows.WM_MOUSEWHEEL:
|
||||
w.scrollEvent(wParam, lParam)
|
||||
case windows.WM_DESTROY:
|
||||
w.dead = true
|
||||
case windows.WM_PAINT:
|
||||
w.draw(true)
|
||||
case windows.WM_SIZE:
|
||||
switch wParam {
|
||||
case windows.SIZE_MINIMIZED:
|
||||
w.setStage(system.StagePaused)
|
||||
case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED:
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
case windows.WM_GETMINMAXINFO:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||
if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 {
|
||||
mm.PtMinTrackSize = windows.Point{
|
||||
w.minmax.minWidth+w.deltas.width,
|
||||
w.minmax.minHeight+w.deltas.height,
|
||||
}
|
||||
}
|
||||
if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 {
|
||||
mm.PtMaxTrackSize = windows.Point{
|
||||
w.minmax.maxWidth+w.deltas.width,
|
||||
w.minmax.maxHeight+w.deltas.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
func getModifiers() key.Modifiers {
|
||||
var kmods key.Modifiers
|
||||
if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
|
||||
kmods |= key.ModSuper
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
|
||||
kmods |= key.ModAlt
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
|
||||
kmods |= key.ModCtrl
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
|
||||
kmods |= key.ModShift
|
||||
}
|
||||
return kmods
|
||||
}
|
||||
|
||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||
var typ pointer.Type
|
||||
if press {
|
||||
typ = pointer.Press
|
||||
if w.pointerBtns == 0 {
|
||||
windows.SetCapture(w.hwnd)
|
||||
}
|
||||
w.pointerBtns |= btn
|
||||
} else {
|
||||
typ = pointer.Release
|
||||
w.pointerBtns &^= btn
|
||||
if w.pointerBtns == 0 {
|
||||
windows.ReleaseCapture()
|
||||
}
|
||||
}
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
Modifiers: kmods,
|
||||
})
|
||||
}
|
||||
|
||||
func coordsFromlParam(lParam uintptr) (int, int) {
|
||||
x := int(int16(lParam & 0xffff))
|
||||
y := int(int16((lParam >> 16) & 0xffff))
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (w *window) scrollEvent(wParam, lParam uintptr) {
|
||||
x, y := coordsFromlParam(lParam)
|
||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||
// to other mouse events.
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
||||
dist := float32(int16(wParam >> 16))
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Scroll: f32.Point{Y: -dist},
|
||||
Time: windows.GetMessageTime(),
|
||||
})
|
||||
}
|
||||
|
||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||
func (w *window) loop() error {
|
||||
msg := new(windows.Msg)
|
||||
for !w.dead {
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.mu.Unlock()
|
||||
if anim && !windows.PeekMessage(msg, w.hwnd, 0, 0, windows.PM_NOREMOVE) {
|
||||
w.draw(false)
|
||||
continue
|
||||
}
|
||||
windows.GetMessage(msg, w.hwnd, 0, 0)
|
||||
if msg.Message == windows.WM_QUIT {
|
||||
windows.PostQuitMessage(msg.WParam)
|
||||
break
|
||||
}
|
||||
windows.TranslateMessage(msg)
|
||||
windows.DispatchMessage(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.postRedraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) postRedraw() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setStage(s system.Stage) {
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
var r windows.Rect
|
||||
windows.GetClientRect(w.hwnd, &r)
|
||||
w.width = int(r.Right - r.Left)
|
||||
w.height = int(r.Bottom - r.Top)
|
||||
if w.width == 0 || w.height == 0 {
|
||||
return
|
||||
}
|
||||
cfg := configForDC()
|
||||
w.minmax = getWindowConstraints(cfg, w.opts, w.deltas)
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: w.width,
|
||||
Y: w.height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) destroy() {
|
||||
if w.hdc != 0 {
|
||||
windows.ReleaseDC(w.hdc)
|
||||
w.hdc = 0
|
||||
}
|
||||
if w.hwnd != 0 {
|
||||
windows.DestroyWindow(w.hwnd)
|
||||
w.hwnd = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
sort.Slice(backends, func(i, j int) bool {
|
||||
return backends[i].priority < backends[j].priority
|
||||
})
|
||||
var errs []string
|
||||
for _, b := range backends {
|
||||
ctx, err := b.initializer(w)
|
||||
if err == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
|
||||
}
|
||||
return nil, errors.New("NewContext: no available backends")
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
w.readClipboard()
|
||||
}
|
||||
|
||||
func (w *window) readClipboard() error {
|
||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseClipboard()
|
||||
mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr, err := windows.GlobalLock(mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.GlobalUnlock(mem)
|
||||
// Look for terminating null character.
|
||||
n := 0
|
||||
for {
|
||||
ch := *(*uint16)(unsafe.Pointer(ptr + uintptr(n)*2))
|
||||
if ch == 0 {
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
var u16 []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16))
|
||||
hdr.Data = ptr
|
||||
hdr.Cap = n
|
||||
hdr.Len = n
|
||||
content := string(utf16.Decode(u16))
|
||||
go func() {
|
||||
w.w.Event(system.ClipboardEvent{Text: content})
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
w.writeClipboard(s)
|
||||
}
|
||||
|
||||
func (w *window) writeClipboard(s string) error {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
// Data must be null terminated.
|
||||
u16 = append(u16, 0)
|
||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseClipboard()
|
||||
if err := windows.EmptyClipboard(); err != nil {
|
||||
return err
|
||||
}
|
||||
n := len(u16) * int(unsafe.Sizeof(u16[0]))
|
||||
mem, err := windows.GlobalAlloc(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr, err := windows.GlobalLock(mem)
|
||||
if err != nil {
|
||||
windows.GlobalFree(mem)
|
||||
return err
|
||||
}
|
||||
var u16v []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v))
|
||||
hdr.Data = ptr
|
||||
hdr.Cap = len(u16)
|
||||
hdr.Len = len(u16)
|
||||
copy(u16v, u16)
|
||||
windows.GlobalUnlock(mem)
|
||||
if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
|
||||
windows.GlobalFree(mem)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *window) HDC() syscall.Handle {
|
||||
return w.hdc
|
||||
}
|
||||
|
||||
func (w *window) HWND() (syscall.Handle, int, int) {
|
||||
return w.hwnd, w.width, w.height
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
|
||||
}
|
||||
|
||||
func convertKeyCode(code uintptr) (string, bool) {
|
||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
||||
return string(rune(code)), true
|
||||
}
|
||||
var r string
|
||||
switch code {
|
||||
case windows.VK_ESCAPE:
|
||||
r = key.NameEscape
|
||||
case windows.VK_LEFT:
|
||||
r = key.NameLeftArrow
|
||||
case windows.VK_RIGHT:
|
||||
r = key.NameRightArrow
|
||||
case windows.VK_RETURN:
|
||||
r = key.NameReturn
|
||||
case windows.VK_UP:
|
||||
r = key.NameUpArrow
|
||||
case windows.VK_DOWN:
|
||||
r = key.NameDownArrow
|
||||
case windows.VK_HOME:
|
||||
r = key.NameHome
|
||||
case windows.VK_END:
|
||||
r = key.NameEnd
|
||||
case windows.VK_BACK:
|
||||
r = key.NameDeleteBackward
|
||||
case windows.VK_DELETE:
|
||||
r = key.NameDeleteForward
|
||||
case windows.VK_PRIOR:
|
||||
r = key.NamePageUp
|
||||
case windows.VK_NEXT:
|
||||
r = key.NamePageDown
|
||||
case windows.VK_F1:
|
||||
r = "F1"
|
||||
case windows.VK_F2:
|
||||
r = "F2"
|
||||
case windows.VK_F3:
|
||||
r = "F3"
|
||||
case windows.VK_F4:
|
||||
r = "F4"
|
||||
case windows.VK_F5:
|
||||
r = "F5"
|
||||
case windows.VK_F6:
|
||||
r = "F6"
|
||||
case windows.VK_F7:
|
||||
r = "F7"
|
||||
case windows.VK_F8:
|
||||
r = "F8"
|
||||
case windows.VK_F9:
|
||||
r = "F9"
|
||||
case windows.VK_F10:
|
||||
r = "F10"
|
||||
case windows.VK_F11:
|
||||
r = "F11"
|
||||
case windows.VK_F12:
|
||||
r = "F12"
|
||||
case windows.VK_TAB:
|
||||
r = key.NameTab
|
||||
case windows.VK_SPACE:
|
||||
r = "Space"
|
||||
case windows.VK_OEM_1:
|
||||
r = ";"
|
||||
case windows.VK_OEM_PLUS:
|
||||
r = "+"
|
||||
case windows.VK_OEM_COMMA:
|
||||
r = ","
|
||||
case windows.VK_OEM_MINUS:
|
||||
r = "-"
|
||||
case windows.VK_OEM_PERIOD:
|
||||
r = "."
|
||||
case windows.VK_OEM_2:
|
||||
r = "/"
|
||||
case windows.VK_OEM_3:
|
||||
r = "`"
|
||||
case windows.VK_OEM_4:
|
||||
r = "["
|
||||
case windows.VK_OEM_5, windows.VK_OEM_102:
|
||||
r = "\\"
|
||||
case windows.VK_OEM_6:
|
||||
r = "]"
|
||||
case windows.VK_OEM_7:
|
||||
r = "'"
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
|
||||
func configForDC() unit.Metric {
|
||||
dpi := windows.GetSystemDPI()
|
||||
const inchPrDp = 1.0 / 96.0
|
||||
ppdp := float32(dpi) * inchPrDp
|
||||
return unit.Metric{
|
||||
PxPerDp: ppdp,
|
||||
PxPerSp: ppdp,
|
||||
}
|
||||
}
|
@ -0,0 +1,647 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nox11 freebsd openbsd
|
||||
|
||||
package window
|
||||
|
||||
/*
|
||||
#cgo openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
|
||||
#cgo openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib
|
||||
#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb
|
||||
#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xresource.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/Xlib-xcb.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
|
||||
"gioui.org/app/internal/xkb"
|
||||
syscall "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type x11Window struct {
|
||||
w Callbacks
|
||||
x *C.Display
|
||||
xkb *xkb.Context
|
||||
xkbEventBase C.int
|
||||
xw C.Window
|
||||
|
||||
atoms struct {
|
||||
// "UTF8_STRING".
|
||||
utf8string C.Atom
|
||||
// "TARGETS"
|
||||
targets C.Atom
|
||||
// "CLIPBOARD".
|
||||
clipboard C.Atom
|
||||
// "CLIPBOARD_CONTENT", the clipboard destination property.
|
||||
clipboardContent C.Atom
|
||||
// "WM_DELETE_WINDOW"
|
||||
evDelWindow C.Atom
|
||||
// "ATOM"
|
||||
atom C.Atom
|
||||
}
|
||||
stage system.Stage
|
||||
cfg unit.Metric
|
||||
width int
|
||||
height int
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
dead bool
|
||||
|
||||
mu sync.Mutex
|
||||
animating bool
|
||||
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
clipboard struct {
|
||||
read bool
|
||||
write *string
|
||||
content []byte
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.wakeup()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) ReadClipboard() {
|
||||
w.mu.Lock()
|
||||
w.clipboard.read = true
|
||||
w.mu.Unlock()
|
||||
w.wakeup()
|
||||
}
|
||||
|
||||
func (w *x11Window) WriteClipboard(s string) {
|
||||
w.mu.Lock()
|
||||
w.clipboard.write = &s
|
||||
w.mu.Unlock()
|
||||
w.wakeup()
|
||||
}
|
||||
|
||||
func (w *x11Window) ShowTextInput(show bool) {}
|
||||
|
||||
// Close the window.
|
||||
func (w *x11Window) Close() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
_type: C.ClientMessage,
|
||||
display: w.x,
|
||||
window: w.xw,
|
||||
message_type: w.atom("WM_PROTOCOLS", true),
|
||||
format: 32,
|
||||
}
|
||||
arr := (*[5]C.long)(unsafe.Pointer(&ev.data))
|
||||
arr[0] = C.long(w.atoms.evDelWindow)
|
||||
arr[1] = C.CurrentTime
|
||||
C.XSendEvent(w.x, w.xw, C.False, C.NoEventMask, &xev)
|
||||
}
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) wakeup() {
|
||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) display() *C.Display {
|
||||
return w.x
|
||||
}
|
||||
|
||||
func (w *x11Window) window() (C.Window, int, int) {
|
||||
return w.xw, w.width, w.height
|
||||
}
|
||||
|
||||
func (w *x11Window) setStage(s system.Stage) {
|
||||
if s == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{s})
|
||||
}
|
||||
|
||||
func (w *x11Window) loop() {
|
||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
xfd := C.XConnectionNumber(w.x)
|
||||
|
||||
// Poll for events and notifications.
|
||||
pollfds := []syscall.PollFd{
|
||||
{Fd: int32(xfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
{Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
}
|
||||
xEvents := &pollfds[0].Revents
|
||||
// Plenty of room for a backlog of notifications.
|
||||
buf := make([]byte, 100)
|
||||
|
||||
loop:
|
||||
for !w.dead {
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
if syn = h.handleEvents(); !syn {
|
||||
w.mu.Lock()
|
||||
anim = w.animating
|
||||
w.mu.Unlock()
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = h.handleEvents()
|
||||
if w.dead {
|
||||
break loop
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, buf)
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if anim || syn {
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: w.width,
|
||||
Y: w.height,
|
||||
},
|
||||
Metric: w.cfg,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
w.mu.Lock()
|
||||
readClipboard := w.clipboard.read
|
||||
writeClipboard := w.clipboard.write
|
||||
w.clipboard.read = false
|
||||
w.clipboard.write = nil
|
||||
w.mu.Unlock()
|
||||
if readClipboard {
|
||||
C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
|
||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||
}
|
||||
if writeClipboard != nil {
|
||||
w.clipboard.content = []byte(*writeClipboard)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
}
|
||||
}
|
||||
w.w.Event(system.DestroyEvent{Err: nil})
|
||||
}
|
||||
|
||||
func (w *x11Window) destroy() {
|
||||
if w.notify.write != 0 {
|
||||
syscall.Close(w.notify.write)
|
||||
w.notify.write = 0
|
||||
}
|
||||
if w.notify.read != 0 {
|
||||
syscall.Close(w.notify.read)
|
||||
w.notify.read = 0
|
||||
}
|
||||
if w.xkb != nil {
|
||||
w.xkb.Destroy()
|
||||
w.xkb = nil
|
||||
}
|
||||
C.XDestroyWindow(w.x, w.xw)
|
||||
C.XCloseDisplay(w.x)
|
||||
}
|
||||
|
||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||
// in order to limit round-trips to the X server.
|
||||
//
|
||||
func (w *x11Window) atom(name string, onlyIfExists bool) C.Atom {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
flag := C.Bool(C.False)
|
||||
if onlyIfExists {
|
||||
flag = C.True
|
||||
}
|
||||
return C.XInternAtom(w.x, cname, flag)
|
||||
}
|
||||
|
||||
// x11EventHandler wraps static variables for the main event loop.
|
||||
// Its sole purpose is to prevent heap allocation and reduce clutter
|
||||
// in x11window.loop.
|
||||
//
|
||||
type x11EventHandler struct {
|
||||
w *x11Window
|
||||
text []byte
|
||||
xev *C.XEvent
|
||||
}
|
||||
|
||||
// handleEvents returns true if the window needs to be redrawn.
|
||||
//
|
||||
func (h *x11EventHandler) handleEvents() bool {
|
||||
w := h.w
|
||||
xev := h.xev
|
||||
redraw := false
|
||||
for C.XPending(w.x) != 0 {
|
||||
C.XNextEvent(w.x, xev)
|
||||
if C.XFilterEvent(xev, C.None) == C.True {
|
||||
continue
|
||||
}
|
||||
switch _type := (*C.XAnyEvent)(unsafe.Pointer(xev))._type; _type {
|
||||
case h.w.xkbEventBase:
|
||||
xkbEvent := (*C.XkbAnyEvent)(unsafe.Pointer(xev))
|
||||
switch xkbEvent.xkb_type {
|
||||
case C.XkbNewKeyboardNotify, C.XkbMapNotify:
|
||||
if err := h.w.updateXkbKeymap(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case C.XkbStateNotify:
|
||||
state := (*C.XkbStateNotifyEvent)(unsafe.Pointer(xev))
|
||||
h.w.xkb.UpdateMask(uint32(state.base_mods), uint32(state.latched_mods), uint32(state.locked_mods),
|
||||
uint32(state.base_group), uint32(state.latched_group), uint32(state.locked_group))
|
||||
}
|
||||
case C.KeyPress:
|
||||
kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
|
||||
for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode)) {
|
||||
w.w.Event(e)
|
||||
}
|
||||
case C.KeyRelease:
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
bevt := (*C.XButtonEvent)(unsafe.Pointer(xev))
|
||||
ev := pointer.Event{
|
||||
Type: pointer.Press,
|
||||
Source: pointer.Mouse,
|
||||
Position: f32.Point{
|
||||
X: float32(bevt.x),
|
||||
Y: float32(bevt.y),
|
||||
},
|
||||
Time: time.Duration(bevt.time) * time.Millisecond,
|
||||
Modifiers: w.xkb.Modifiers(),
|
||||
}
|
||||
if bevt._type == C.ButtonRelease {
|
||||
ev.Type = pointer.Release
|
||||
}
|
||||
var btn pointer.Buttons
|
||||
const scrollScale = 10
|
||||
switch bevt.button {
|
||||
case C.Button1:
|
||||
btn = pointer.ButtonLeft
|
||||
case C.Button2:
|
||||
btn = pointer.ButtonMiddle
|
||||
case C.Button3:
|
||||
btn = pointer.ButtonRight
|
||||
case C.Button4:
|
||||
// scroll up
|
||||
ev.Type = pointer.Scroll
|
||||
ev.Scroll.Y = -scrollScale
|
||||
case C.Button5:
|
||||
// scroll down
|
||||
ev.Type = pointer.Scroll
|
||||
ev.Scroll.Y = +scrollScale
|
||||
default:
|
||||
continue
|
||||
}
|
||||
switch _type {
|
||||
case C.ButtonPress:
|
||||
w.pointerBtns |= btn
|
||||
case C.ButtonRelease:
|
||||
w.pointerBtns &^= btn
|
||||
}
|
||||
ev.Buttons = w.pointerBtns
|
||||
w.w.Event(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
Position: f32.Point{
|
||||
X: float32(mevt.x),
|
||||
Y: float32(mevt.y),
|
||||
},
|
||||
Time: time.Duration(mevt.time) * time.Millisecond,
|
||||
Modifiers: w.xkb.Modifiers(),
|
||||
})
|
||||
case C.Expose: // update
|
||||
// redraw only on the last expose event
|
||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||
case C.FocusIn:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
case C.FocusOut:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case C.ConfigureNotify: // window configuration change
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
w.width = int(cevt.width)
|
||||
w.height = int(cevt.height)
|
||||
// redraw will be done by a later expose event
|
||||
case C.SelectionNotify:
|
||||
cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))
|
||||
prop := w.atoms.clipboardContent
|
||||
if cevt.property != prop {
|
||||
break
|
||||
}
|
||||
if cevt.selection != w.atoms.clipboard {
|
||||
break
|
||||
}
|
||||
var text C.XTextProperty
|
||||
if st := C.XGetTextProperty(w.x, w.xw, &text, prop); st == 0 {
|
||||
// Failed; ignore.
|
||||
break
|
||||
}
|
||||
if text.format != 8 || text.encoding != w.atoms.utf8string {
|
||||
// Ignore non-utf-8 encoded strings.
|
||||
break
|
||||
}
|
||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||
w.w.Event(system.ClipboardEvent{Text: str})
|
||||
case C.SelectionRequest:
|
||||
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
|
||||
if cevt.selection != w.atoms.clipboard || cevt.property == C.None {
|
||||
// Unsupported clipboard or obsolete requestor.
|
||||
break
|
||||
}
|
||||
notify := func() {
|
||||
var xev C.XEvent
|
||||
ev := (*C.XSelectionEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XSelectionEvent{
|
||||
_type: C.SelectionNotify,
|
||||
display: cevt.display,
|
||||
requestor: cevt.requestor,
|
||||
selection: cevt.selection,
|
||||
target: cevt.target,
|
||||
property: cevt.property,
|
||||
time: cevt.time,
|
||||
}
|
||||
C.XSendEvent(w.x, cevt.requestor, 0, 0, &xev)
|
||||
}
|
||||
switch cevt.target {
|
||||
case w.atoms.targets:
|
||||
// The requestor wants the supported clipboard
|
||||
// formats. First write the formats...
|
||||
formats := []uint32{uint32(w.atoms.utf8string)}
|
||||
C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.atom,
|
||||
32 /* bitwidth of formats */, C.PropModeReplace,
|
||||
(*C.uchar)(unsafe.Pointer(&formats[0])), C.int(len(formats)),
|
||||
)
|
||||
// ...then notify the requestor.
|
||||
notify()
|
||||
case w.atoms.utf8string:
|
||||
content := w.clipboard.content
|
||||
var ptr *C.uchar
|
||||
if len(content) > 0 {
|
||||
ptr = (*C.uchar)(unsafe.Pointer(&content[0]))
|
||||
}
|
||||
C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.utf8string,
|
||||
8 /* bitwidth */, C.PropModeReplace,
|
||||
ptr, C.int(len(content)),
|
||||
)
|
||||
notify()
|
||||
}
|
||||
case C.ClientMessage: // extensions
|
||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||
case C.long(w.atoms.evDelWindow):
|
||||
w.dead = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return redraw
|
||||
}
|
||||
|
||||
var (
|
||||
x11Threads sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
x11Driver = newX11Window
|
||||
}
|
||||
|
||||
func newX11Window(gioWin Callbacks, opts *Options) error {
|
||||
var err error
|
||||
|
||||
pipe := make([]int, 2)
|
||||
if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
|
||||
return fmt.Errorf("NewX11Window: failed to create pipe: %w", err)
|
||||
}
|
||||
|
||||
x11Threads.Do(func() {
|
||||
if C.XInitThreads() == 0 {
|
||||
err = errors.New("x11: threads init failed")
|
||||
}
|
||||
C.XrmInitialize()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dpy := C.XOpenDisplay(nil)
|
||||
if dpy == nil {
|
||||
return errors.New("x11: cannot connect to the X server")
|
||||
}
|
||||
var major, minor C.int = C.XkbMajorVersion, C.XkbMinorVersion
|
||||
var xkbEventBase C.int
|
||||
if C.XkbQueryExtension(dpy, nil, &xkbEventBase, nil, &major, &minor) != C.True {
|
||||
C.XCloseDisplay(dpy)
|
||||
return errors.New("x11: XkbQueryExtension failed")
|
||||
}
|
||||
const bits = C.uint(C.XkbNewKeyboardNotifyMask | C.XkbMapNotifyMask | C.XkbStateNotifyMask)
|
||||
if C.XkbSelectEvents(dpy, C.XkbUseCoreKbd, bits, bits) != C.True {
|
||||
C.XCloseDisplay(dpy)
|
||||
return errors.New("x11: XkbSelectEvents failed")
|
||||
}
|
||||
xkb, err := xkb.New()
|
||||
if err != nil {
|
||||
C.XCloseDisplay(dpy)
|
||||
return fmt.Errorf("x11: %v", err)
|
||||
}
|
||||
|
||||
ppsp := x11DetectUIScale(dpy)
|
||||
cfg := unit.Metric{PxPerDp: ppsp, PxPerSp: ppsp}
|
||||
swa := C.XSetWindowAttributes{
|
||||
event_mask: C.ExposureMask | C.FocusChangeMask | // update
|
||||
C.KeyPressMask | C.KeyReleaseMask | // keyboard
|
||||
C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks
|
||||
C.PointerMotionMask | // mouse movement
|
||||
C.StructureNotifyMask, // resize
|
||||
background_pixmap: C.None,
|
||||
override_redirect: C.False,
|
||||
}
|
||||
win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
|
||||
0, 0, C.uint(cfg.Px(opts.Width)), C.uint(cfg.Px(opts.Height)),
|
||||
0, C.CopyFromParent, C.InputOutput, nil,
|
||||
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
|
||||
|
||||
w := &x11Window{
|
||||
w: gioWin, x: dpy, xw: win,
|
||||
width: cfg.Px(opts.Width),
|
||||
height: cfg.Px(opts.Height),
|
||||
cfg: cfg,
|
||||
xkb: xkb,
|
||||
xkbEventBase: xkbEventBase,
|
||||
}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
|
||||
if err := w.updateXkbKeymap(); err != nil {
|
||||
w.destroy()
|
||||
return err
|
||||
}
|
||||
|
||||
var hints C.XWMHints
|
||||
hints.input = C.True
|
||||
hints.flags = C.InputHint
|
||||
C.XSetWMHints(dpy, win, &hints)
|
||||
|
||||
var shints C.XSizeHints
|
||||
if opts.MinWidth.V != 0 || opts.MinHeight.V != 0 {
|
||||
shints.min_width = C.int(cfg.Px(opts.MinWidth))
|
||||
shints.min_height = C.int(cfg.Px(opts.MinHeight))
|
||||
shints.flags = C.PMinSize
|
||||
}
|
||||
if opts.MaxWidth.V != 0 || opts.MaxHeight.V != 0 {
|
||||
shints.max_width = C.int(cfg.Px(opts.MaxWidth))
|
||||
shints.max_height = C.int(cfg.Px(opts.MaxHeight))
|
||||
shints.flags = shints.flags | C.PMaxSize
|
||||
}
|
||||
if shints.flags != 0 {
|
||||
C.XSetWMNormalHints(dpy, win, &shints)
|
||||
}
|
||||
|
||||
name := C.CString(filepath.Base(os.Args[0]))
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
wmhints := C.XClassHint{name, name}
|
||||
C.XSetClassHint(dpy, win, &wmhints)
|
||||
|
||||
w.atoms.utf8string = w.atom("UTF8_STRING", false)
|
||||
w.atoms.evDelWindow = w.atom("WM_DELETE_WINDOW", false)
|
||||
w.atoms.clipboard = w.atom("CLIPBOARD", false)
|
||||
w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
|
||||
w.atoms.atom = w.atom("ATOM", false)
|
||||
w.atoms.targets = w.atom("TARGETS", false)
|
||||
|
||||
// set the name
|
||||
ctitle := C.CString(opts.Title)
|
||||
defer C.free(unsafe.Pointer(ctitle))
|
||||
C.XStoreName(dpy, win, ctitle)
|
||||
// set _NET_WM_NAME as well for UTF-8 support in window title.
|
||||
C.XSetTextProperty(dpy, win,
|
||||
&C.XTextProperty{
|
||||
value: (*C.uchar)(unsafe.Pointer(ctitle)),
|
||||
encoding: w.atoms.utf8string,
|
||||
format: 8,
|
||||
nitems: C.ulong(len(opts.Title)),
|
||||
},
|
||||
w.atom("_NET_WM_NAME", false))
|
||||
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
|
||||
go func() {
|
||||
w.w.SetDriver(w)
|
||||
w.setStage(system.StageRunning)
|
||||
w.loop()
|
||||
w.destroy()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectUIScale reports the system UI scale, or 1.0 if it fails.
|
||||
func x11DetectUIScale(dpy *C.Display) float32 {
|
||||
// default fixed DPI value used in most desktop UI toolkits
|
||||
const defaultDesktopDPI = 96
|
||||
var scale float32 = 1.0
|
||||
|
||||
// Get actual DPI from X resource Xft.dpi (set by GTK and Qt).
|
||||
// This value is entirely based on user preferences and conflates both
|
||||
// screen (UI) scaling and font scale.
|
||||
rms := C.XResourceManagerString(dpy)
|
||||
if rms != nil {
|
||||
db := C.XrmGetStringDatabase(rms)
|
||||
if db != nil {
|
||||
var (
|
||||
t *C.char
|
||||
v C.XrmValue
|
||||
)
|
||||
if C.XrmGetResource(db, (*C.char)(unsafe.Pointer(&[]byte("Xft.dpi\x00")[0])),
|
||||
(*C.char)(unsafe.Pointer(&[]byte("Xft.Dpi\x00")[0])), &t, &v) != C.False {
|
||||
if t != nil && C.GoString(t) == "String" {
|
||||
f, err := strconv.ParseFloat(C.GoString(v.addr), 32)
|
||||
if err == nil {
|
||||
scale = float32(f) / defaultDesktopDPI
|
||||
}
|
||||
}
|
||||
}
|
||||
C.XrmDestroyDatabase(db)
|
||||
}
|
||||
}
|
||||
|
||||
return scale
|
||||
}
|
||||
|
||||
func (w *x11Window) updateXkbKeymap() error {
|
||||
w.xkb.DestroyKeymapState()
|
||||
ctx := (*C.struct_xkb_context)(unsafe.Pointer(w.xkb.Ctx))
|
||||
xcb := C.XGetXCBConnection(w.x)
|
||||
if xcb == nil {
|
||||
return errors.New("x11: XGetXCBConnection failed")
|
||||
}
|
||||
xkbDevID := C.xkb_x11_get_core_keyboard_device_id(xcb)
|
||||
if xkbDevID == -1 {
|
||||
return errors.New("x11: xkb_x11_get_core_keyboard_device_id failed")
|
||||
}
|
||||
keymap := C.xkb_x11_keymap_new_from_device(ctx, xcb, xkbDevID, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
|
||||
if keymap == nil {
|
||||
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
|
||||
}
|
||||
state := C.xkb_x11_state_new_from_device(keymap, xcb, xkbDevID)
|
||||
if state == nil {
|
||||
C.xkb_keymap_unref(keymap)
|
||||
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
|
||||
}
|
||||
w.xkb.SetKeymap(unsafe.Pointer(keymap), unsafe.Pointer(state))
|
||||
return nil
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build android darwin,ios
|
||||
|
||||
package window
|
||||
|
||||
// Android only supports non-Java programs as c-shared libraries.
|
||||
// Unfortunately, Go does not run a program's main function in
|
||||
// library mode. To make Gio programs simpler and uniform, we'll
|
||||
// link to the main function here and call it from Java.
|
||||
|
||||
import (
|
||||
"sync"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
//go:linkname mainMain main.main
|
||||
func mainMain()
|
||||
|
||||
var runMainOnce sync.Once
|
||||
|
||||
func runMain() {
|
||||
runMainOnce.Do(func() {
|
||||
// Indirect call, since the linker does not know the address of main when
|
||||
// laying down this package.
|
||||
fn := mainMain
|
||||
fn()
|
||||
})
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
/* Generated by wayland-scanner 1.17.0 */
|
||||
|
||||
/*
|
||||
* Copyright © 2012, 2013 Intel Corporation
|
||||
* Copyright © 2015, 2016 Jan Arne Petersen
|
||||
* Copyright © 2017, 2018 Red Hat, Inc.
|
||||
* Copyright © 2018 Purism SPC
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this
|
||||
* software and its documentation for any purpose is hereby granted
|
||||
* without fee, provided that the above copyright notice appear in
|
||||
* all copies and that both that copyright notice and this permission
|
||||
* notice appear in supporting documentation, and that the name of
|
||||
* the copyright holders not be used in advertising or publicity
|
||||
* pertaining to distribution of the software without specific,
|
||||
* written prior permission. The copyright holders make no
|
||||
* representations about the suitability of this software for any
|
||||
* purpose. It is provided "as is" without express or implied
|
||||
* warranty.
|
||||
*
|
||||
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
* THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "wayland-util.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
||||
#endif
|
||||
|
||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define WL_PRIVATE
|
||||
#endif
|
||||
|
||||
extern const struct wl_interface wl_seat_interface;
|
||||
extern const struct wl_interface wl_surface_interface;
|
||||
extern const struct wl_interface zwp_text_input_v3_interface;
|
||||
|
||||
static const struct wl_interface *types[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&wl_surface_interface,
|
||||
&wl_surface_interface,
|
||||
&zwp_text_input_v3_interface,
|
||||
&wl_seat_interface,
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_v3_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "enable", "", types + 0 },
|
||||
{ "disable", "", types + 0 },
|
||||
{ "set_surrounding_text", "sii", types + 0 },
|
||||
{ "set_text_change_cause", "u", types + 0 },
|
||||
{ "set_content_type", "uu", types + 0 },
|
||||
{ "set_cursor_rectangle", "iiii", types + 0 },
|
||||
{ "commit", "", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_v3_events[] = {
|
||||
{ "enter", "o", types + 4 },
|
||||
{ "leave", "o", types + 5 },
|
||||
{ "preedit_string", "?sii", types + 0 },
|
||||
{ "commit_string", "?s", types + 0 },
|
||||
{ "delete_surrounding_text", "uu", types + 0 },
|
||||
{ "done", "u", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
|
||||
"zwp_text_input_v3", 1,
|
||||
8, zwp_text_input_v3_requests,
|
||||
6, zwp_text_input_v3_events,
|
||||
};
|
||||
|
||||
static const struct wl_message zwp_text_input_manager_v3_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "get_text_input", "no", types + 6 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
|
||||
"zwp_text_input_manager_v3", 1,
|
||||
2, zwp_text_input_manager_v3_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
@ -0,0 +1,819 @@
|
||||
/* Generated by wayland-scanner 1.17.0 */
|
||||
|
||||
#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
|
||||
#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol
|
||||
* Protocol for composing text
|
||||
*
|
||||
* @section page_desc_text_input_unstable_v3 Description
|
||||
*
|
||||
* This protocol allows compositors to act as input methods and to send text
|
||||
* to applications. A text input object is used to manage state of what are
|
||||
* typically text entry fields in the application.
|
||||
*
|
||||
* This document adheres to the RFC 2119 when using words like "must",
|
||||
* "should", "may", etc.
|
||||
*
|
||||
* Warning! The protocol described in this file is experimental and
|
||||
* backward incompatible changes may be made. Backward compatible changes
|
||||
* may be added together with the corresponding interface version bump.
|
||||
* Backward incompatible changes are done by bumping the version number in
|
||||
* the protocol and interface names and resetting the interface version.
|
||||
* Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
* version number in the protocol and interface names are removed and the
|
||||
* interface version number is reset.
|
||||
*
|
||||
* @section page_ifaces_text_input_unstable_v3 Interfaces
|
||||
* - @subpage page_iface_zwp_text_input_v3 - text input
|
||||
* - @subpage page_iface_zwp_text_input_manager_v3 - text input manager
|
||||
* @section page_copyright_text_input_unstable_v3 Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright © 2012, 2013 Intel Corporation
|
||||
* Copyright © 2015, 2016 Jan Arne Petersen
|
||||
* Copyright © 2017, 2018 Red Hat, Inc.
|
||||
* Copyright © 2018 Purism SPC
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this
|
||||
* software and its documentation for any purpose is hereby granted
|
||||
* without fee, provided that the above copyright notice appear in
|
||||
* all copies and that both that copyright notice and this permission
|
||||
* notice appear in supporting documentation, and that the name of
|
||||
* the copyright holders not be used in advertising or publicity
|
||||
* pertaining to distribution of the software without specific,
|
||||
* written prior permission. The copyright holders make no
|
||||
* representations about the suitability of this software for any
|
||||
* purpose. It is provided "as is" without express or implied
|
||||
* warranty.
|
||||
*
|
||||
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
* THIS SOFTWARE.
|
||||
* </pre>
|
||||
*/
|
||||
struct wl_seat;
|
||||
struct wl_surface;
|
||||
struct zwp_text_input_manager_v3;
|
||||
struct zwp_text_input_v3;
|
||||
|
||||
/**
|
||||
* @page page_iface_zwp_text_input_v3 zwp_text_input_v3
|
||||
* @section page_iface_zwp_text_input_v3_desc Description
|
||||
*
|
||||
* The zwp_text_input_v3 interface represents text input and input methods
|
||||
* associated with a seat. It provides enter/leave events to follow the
|
||||
* text input focus for a seat.
|
||||
*
|
||||
* Requests are used to enable/disable the text-input object and set
|
||||
* state information like surrounding and selected text or the content type.
|
||||
* The information about the entered text is sent to the text-input object
|
||||
* via the preedit_string and commit_string events.
|
||||
*
|
||||
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
|
||||
* must not point to middle bytes inside a code point: they must either
|
||||
* point to the first byte of a code point or to the end of the buffer.
|
||||
* Lengths must be measured between two valid indices.
|
||||
*
|
||||
* Focus moving throughout surfaces will result in the emission of
|
||||
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
|
||||
* surface must commit zwp_text_input_v3.enable and
|
||||
* zwp_text_input_v3.disable requests as the keyboard focus moves across
|
||||
* editable and non-editable elements of the UI. Those two requests are not
|
||||
* expected to be paired with each other, the compositor must be able to
|
||||
* handle consecutive series of the same request.
|
||||
*
|
||||
* State is sent by the state requests (set_surrounding_text,
|
||||
* set_content_type and set_cursor_rectangle) and a commit request. After an
|
||||
* enter event or disable request all state information is invalidated and
|
||||
* needs to be resent by the client.
|
||||
* @section page_iface_zwp_text_input_v3_api API
|
||||
* See @ref iface_zwp_text_input_v3.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface
|
||||
*
|
||||
* The zwp_text_input_v3 interface represents text input and input methods
|
||||
* associated with a seat. It provides enter/leave events to follow the
|
||||
* text input focus for a seat.
|
||||
*
|
||||
* Requests are used to enable/disable the text-input object and set
|
||||
* state information like surrounding and selected text or the content type.
|
||||
* The information about the entered text is sent to the text-input object
|
||||
* via the preedit_string and commit_string events.
|
||||
*
|
||||
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
|
||||
* must not point to middle bytes inside a code point: they must either
|
||||
* point to the first byte of a code point or to the end of the buffer.
|
||||
* Lengths must be measured between two valid indices.
|
||||
*
|
||||
* Focus moving throughout surfaces will result in the emission of
|
||||
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
|
||||
* surface must commit zwp_text_input_v3.enable and
|
||||
* zwp_text_input_v3.disable requests as the keyboard focus moves across
|
||||
* editable and non-editable elements of the UI. Those two requests are not
|
||||
* expected to be paired with each other, the compositor must be able to
|
||||
* handle consecutive series of the same request.
|
||||
*
|
||||
* State is sent by the state requests (set_surrounding_text,
|
||||
* set_content_type and set_cursor_rectangle) and a commit request. After an
|
||||
* enter event or disable request all state information is invalidated and
|
||||
* needs to be resent by the client.
|
||||
*/
|
||||
extern const struct wl_interface zwp_text_input_v3_interface;
|
||||
/**
|
||||
* @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3
|
||||
* @section page_iface_zwp_text_input_manager_v3_desc Description
|
||||
*
|
||||
* A factory for text-input objects. This object is a global singleton.
|
||||
* @section page_iface_zwp_text_input_manager_v3_api API
|
||||
* See @ref iface_zwp_text_input_manager_v3.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface
|
||||
*
|
||||
* A factory for text-input objects. This object is a global singleton.
|
||||
*/
|
||||
extern const struct wl_interface zwp_text_input_manager_v3_interface;
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
|
||||
#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* text change reason
|
||||
*
|
||||
* Reason for the change of surrounding text or cursor posision.
|
||||
*/
|
||||
enum zwp_text_input_v3_change_cause {
|
||||
/**
|
||||
* input method caused the change
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0,
|
||||
/**
|
||||
* something else than the input method caused the change
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1,
|
||||
};
|
||||
#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
|
||||
#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* content hint
|
||||
*
|
||||
* Content hint is a bitmask to allow to modify the behavior of the text
|
||||
* input.
|
||||
*/
|
||||
enum zwp_text_input_v3_content_hint {
|
||||
/**
|
||||
* no special behavior
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0,
|
||||
/**
|
||||
* suggest word completions
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1,
|
||||
/**
|
||||
* suggest word corrections
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2,
|
||||
/**
|
||||
* switch to uppercase letters at the start of a sentence
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4,
|
||||
/**
|
||||
* prefer lowercase letters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8,
|
||||
/**
|
||||
* prefer uppercase letters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10,
|
||||
/**
|
||||
* prefer casing for titles and headings (can be language dependent)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20,
|
||||
/**
|
||||
* characters should be hidden
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40,
|
||||
/**
|
||||
* typed text should not be stored
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80,
|
||||
/**
|
||||
* just Latin characters should be entered
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100,
|
||||
/**
|
||||
* the text input is multiline
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200,
|
||||
};
|
||||
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */
|
||||
|
||||
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
|
||||
#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* content purpose
|
||||
*
|
||||
* The content purpose allows to specify the primary purpose of a text
|
||||
* input.
|
||||
*
|
||||
* This allows an input method to show special purpose input panels with
|
||||
* extra characters or to disallow some characters.
|
||||
*/
|
||||
enum zwp_text_input_v3_content_purpose {
|
||||
/**
|
||||
* default input, allowing all characters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0,
|
||||
/**
|
||||
* allow only alphabetic characters
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1,
|
||||
/**
|
||||
* allow only digits
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2,
|
||||
/**
|
||||
* input a number (including decimal separator and sign)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3,
|
||||
/**
|
||||
* input a phone number
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4,
|
||||
/**
|
||||
* input an URL
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5,
|
||||
/**
|
||||
* input an email address
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6,
|
||||
/**
|
||||
* input a name of a person
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7,
|
||||
/**
|
||||
* input a password (combine with sensitive_data hint)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8,
|
||||
/**
|
||||
* input is a numeric password (combine with sensitive_data hint)
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9,
|
||||
/**
|
||||
* input a date
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10,
|
||||
/**
|
||||
* input a time
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11,
|
||||
/**
|
||||
* input a date and time
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12,
|
||||
/**
|
||||
* input for a terminal
|
||||
*/
|
||||
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13,
|
||||
};
|
||||
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
* @struct zwp_text_input_v3_listener
|
||||
*/
|
||||
struct zwp_text_input_v3_listener {
|
||||
/**
|
||||
* enter event
|
||||
*
|
||||
* Notification that this seat's text-input focus is on a certain
|
||||
* surface.
|
||||
*
|
||||
* When the seat has the keyboard capability the text-input focus
|
||||
* follows the keyboard focus. This event sets the current surface
|
||||
* for the text-input object.
|
||||
*/
|
||||
void (*enter)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
struct wl_surface *surface);
|
||||
/**
|
||||
* leave event
|
||||
*
|
||||
* Notification that this seat's text-input focus is no longer on
|
||||
* a certain surface. The client should reset any preedit string
|
||||
* previously set.
|
||||
*
|
||||
* The leave notification clears the current surface. It is sent
|
||||
* before the enter notification for the new focus.
|
||||
*
|
||||
* When the seat has the keyboard capability the text-input focus
|
||||
* follows the keyboard focus.
|
||||
*/
|
||||
void (*leave)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
struct wl_surface *surface);
|
||||
/**
|
||||
* pre-edit
|
||||
*
|
||||
* Notify when a new composing text (pre-edit) should be set at
|
||||
* the current cursor position. Any previously set composing text
|
||||
* must be removed. Any previously existing selected text must be
|
||||
* removed.
|
||||
*
|
||||
* The argument text contains the pre-edit string buffer.
|
||||
*
|
||||
* The parameters cursor_begin and cursor_end are counted in bytes
|
||||
* relative to the beginning of the submitted text buffer. Cursor
|
||||
* should be hidden when both are equal to -1.
|
||||
*
|
||||
* They could be represented by the client as a line if both values
|
||||
* are the same, or as a text highlight otherwise.
|
||||
*
|
||||
* Values set with this event are double-buffered. They must be
|
||||
* applied and reset to initial on the next zwp_text_input_v3.done
|
||||
* event.
|
||||
*
|
||||
* The initial value of text is an empty string, and cursor_begin,
|
||||
* cursor_end and cursor_hidden are all 0.
|
||||
*/
|
||||
void (*preedit_string)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
const char *text,
|
||||
int32_t cursor_begin,
|
||||
int32_t cursor_end);
|
||||
/**
|
||||
* text commit
|
||||
*
|
||||
* Notify when text should be inserted into the editor widget.
|
||||
* The text to commit could be either just a single character after
|
||||
* a key press or the result of some composing (pre-edit).
|
||||
*
|
||||
* Values set with this event are double-buffered. They must be
|
||||
* applied and reset to initial on the next zwp_text_input_v3.done
|
||||
* event.
|
||||
*
|
||||
* The initial value of text is an empty string.
|
||||
*/
|
||||
void (*commit_string)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
const char *text);
|
||||
/**
|
||||
* delete surrounding text
|
||||
*
|
||||
* Notify when the text around the current cursor position should
|
||||
* be deleted.
|
||||
*
|
||||
* Before_length and after_length are the number of bytes before
|
||||
* and after the current cursor index (excluding the selection) to
|
||||
* delete.
|
||||
*
|
||||
* If a preedit text is present, in effect before_length is counted
|
||||
* from the beginning of it, and after_length from its end (see
|
||||
* done event sequence).
|
||||
*
|
||||
* Values set with this event are double-buffered. They must be
|
||||
* applied and reset to initial on the next zwp_text_input_v3.done
|
||||
* event.
|
||||
*
|
||||
* The initial values of both before_length and after_length are 0.
|
||||
* @param before_length length of text before current cursor position
|
||||
* @param after_length length of text after current cursor position
|
||||
*/
|
||||
void (*delete_surrounding_text)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
uint32_t before_length,
|
||||
uint32_t after_length);
|
||||
/**
|
||||
* apply changes
|
||||
*
|
||||
* Instruct the application to apply changes to state requested
|
||||
* by the preedit_string, commit_string and delete_surrounding_text
|
||||
* events. The state relating to these events is double-buffered,
|
||||
* and each one modifies the pending state. This event replaces the
|
||||
* current state with the pending state.
|
||||
*
|
||||
* The application must proceed by evaluating the changes in the
|
||||
* following order:
|
||||
*
|
||||
* 1. Replace existing preedit string with the cursor. 2. Delete
|
||||
* requested surrounding text. 3. Insert commit string with the
|
||||
* cursor at its end. 4. Calculate surrounding text to send. 5.
|
||||
* Insert new preedit text in cursor position. 6. Place cursor
|
||||
* inside preedit text.
|
||||
*
|
||||
* The serial number reflects the last state of the
|
||||
* zwp_text_input_v3 object known to the compositor. The value of
|
||||
* the serial argument must be equal to the number of commit
|
||||
* requests already issued on that object. When the client receives
|
||||
* a done event with a serial different than the number of past
|
||||
* commit requests, it must proceed as normal, except it should not
|
||||
* change the current state of the zwp_text_input_v3 object.
|
||||
*/
|
||||
void (*done)(void *data,
|
||||
struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
uint32_t serial);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
static inline int
|
||||
zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3,
|
||||
const struct zwp_text_input_v3_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define ZWP_TEXT_INPUT_V3_DESTROY 0
|
||||
#define ZWP_TEXT_INPUT_V3_ENABLE 1
|
||||
#define ZWP_TEXT_INPUT_V3_DISABLE 2
|
||||
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3
|
||||
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6
|
||||
#define ZWP_TEXT_INPUT_V3_COMMIT 7
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zwp_text_input_v3 */
|
||||
static inline void
|
||||
zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zwp_text_input_v3 */
|
||||
static inline void *
|
||||
zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Destroy the wp_text_input object. Also disables all surfaces enabled
|
||||
* through this wp_text_input object.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Requests text input on the surface previously obtained from the enter
|
||||
* event.
|
||||
*
|
||||
* This request must be issued every time the active text input changes
|
||||
* to a new one, including within the current surface. Use
|
||||
* zwp_text_input_v3.disable when there is no longer any input focus on
|
||||
* the current surface.
|
||||
*
|
||||
* This request resets all state associated with previous enable, disable,
|
||||
* set_surrounding_text, set_text_change_cause, set_content_type, and
|
||||
* set_cursor_rectangle requests, as well as the state associated with
|
||||
* preedit_string, commit_string, and delete_surrounding_text events.
|
||||
*
|
||||
* The set_surrounding_text, set_content_type and set_cursor_rectangle
|
||||
* requests must follow if the text input supports the necessary
|
||||
* functionality.
|
||||
*
|
||||
* State set with this request is double-buffered. It will get applied on
|
||||
* the next zwp_text_input_v3.commit request, and stay valid until the
|
||||
* next committed enable or disable request.
|
||||
*
|
||||
* The changes must be applied by the compositor after issuing a
|
||||
* zwp_text_input_v3.commit request.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_ENABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Explicitly disable text input on the current surface (typically when
|
||||
* there is no focus on any text entry inside the surface).
|
||||
*
|
||||
* State set with this request is double-buffered. It will get applied on
|
||||
* the next zwp_text_input_v3.commit request.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_DISABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Sets the surrounding plain text around the input, excluding the preedit
|
||||
* text.
|
||||
*
|
||||
* The client should notify the compositor of any changes in any of the
|
||||
* values carried with this request, including changes caused by handling
|
||||
* incoming text-input events as well as changes caused by other
|
||||
* mechanisms like keyboard typing.
|
||||
*
|
||||
* If the client is unaware of the text around the cursor, it should not
|
||||
* issue this request, to signify lack of support to the compositor.
|
||||
*
|
||||
* Text is UTF-8 encoded, and should include the cursor position, the
|
||||
* complete selection and additional characters before and after them.
|
||||
* There is a maximum length of wayland messages, so text can not be
|
||||
* longer than 4000 bytes.
|
||||
*
|
||||
* Cursor is the byte offset of the cursor within text buffer.
|
||||
*
|
||||
* Anchor is the byte offset of the selection anchor within text buffer.
|
||||
* If there is no selected text, anchor is the same as cursor.
|
||||
*
|
||||
* If any preedit text is present, it is replaced with a cursor for the
|
||||
* purpose of this event.
|
||||
*
|
||||
* Values set with this request are double-buffered. They will get applied
|
||||
* on the next zwp_text_input_v3.commit request, and stay valid until the
|
||||
* next committed enable or disable request.
|
||||
*
|
||||
* The initial state for affected fields is empty, meaning that the text
|
||||
* input does not support sending surrounding text. If the empty values
|
||||
* get applied, subsequent attempts to change them may have no effect.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, text, cursor, anchor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Tells the compositor why the text surrounding the cursor changed.
|
||||
*
|
||||
* Whenever the client detects an external change in text, cursor, or
|
||||
* anchor posision, it must issue this request to the compositor. This
|
||||
* request is intended to give the input method a chance to update the
|
||||
* preedit text in an appropriate way, e.g. by removing it when the user
|
||||
* starts typing with a keyboard.
|
||||
*
|
||||
* cause describes the source of the change.
|
||||
*
|
||||
* The value set with this request is double-buffered. It must be applied
|
||||
* and reset to initial at the next zwp_text_input_v3.commit request.
|
||||
*
|
||||
* The initial value of cause is input_method.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Sets the content purpose and content hint. While the purpose is the
|
||||
* basic purpose of an input field, the hint flags allow to modify some of
|
||||
* the behavior.
|
||||
*
|
||||
* Values set with this request are double-buffered. They will get applied
|
||||
* on the next zwp_text_input_v3.commit request.
|
||||
* Subsequent attempts to update them may have no effect. The values
|
||||
* remain valid until the next committed enable or disable request.
|
||||
*
|
||||
* The initial value for hint is none, and the initial value for purpose
|
||||
* is normal.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, hint, purpose);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Marks an area around the cursor as a x, y, width, height rectangle in
|
||||
* surface local coordinates.
|
||||
*
|
||||
* Allows the compositor to put a window with word suggestions near the
|
||||
* cursor, without obstructing the text being input.
|
||||
*
|
||||
* If the client is unaware of the position of edited text, it should not
|
||||
* issue this request, to signify lack of support to the compositor.
|
||||
*
|
||||
* Values set with this request are double-buffered. They will get applied
|
||||
* on the next zwp_text_input_v3.commit request, and stay valid until the
|
||||
* next committed enable or disable request.
|
||||
*
|
||||
* The initial values describing a cursor rectangle are empty. That means
|
||||
* the text input does not support describing the cursor area. If the
|
||||
* empty values get applied, subsequent attempts to change them may have
|
||||
* no effect.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, x, y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_v3
|
||||
*
|
||||
* Atomically applies state changes recently sent to the compositor.
|
||||
*
|
||||
* The commit request establishes and updates the state of the client, and
|
||||
* must be issued after any changes to apply them.
|
||||
*
|
||||
* Text input state (enabled status, content purpose, content hint,
|
||||
* surrounding text and change cause, cursor rectangle) is conceptually
|
||||
* double-buffered within the context of a text input, i.e. between a
|
||||
* committed enable request and the following committed enable or disable
|
||||
* request.
|
||||
*
|
||||
* Protocol requests modify the pending state, as opposed to the current
|
||||
* state in use by the input method. A commit request atomically applies
|
||||
* all pending state, replacing the current state. After commit, the new
|
||||
* pending state is as documented for each related request.
|
||||
*
|
||||
* Requests are applied in the order of arrival.
|
||||
*
|
||||
* Neither current nor pending state are modified unless noted otherwise.
|
||||
*
|
||||
* The compositor must count the number of commit requests coming from
|
||||
* each zwp_text_input_v3 object and use the count as the serial in done
|
||||
* events.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_v3,
|
||||
ZWP_TEXT_INPUT_V3_COMMIT);
|
||||
}
|
||||
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*/
|
||||
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zwp_text_input_manager_v3 */
|
||||
static inline void
|
||||
zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zwp_text_input_manager_v3 */
|
||||
static inline void *
|
||||
zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*
|
||||
* Destroy the wp_text_input_manager object.
|
||||
*/
|
||||
static inline void
|
||||
zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zwp_text_input_manager_v3,
|
||||
ZWP_TEXT_INPUT_MANAGER_V3_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zwp_text_input_manager_v3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zwp_text_input_manager_v3
|
||||
*
|
||||
* Creates a new text-input object for a given seat.
|
||||
*/
|
||||
static inline struct zwp_text_input_v3 *
|
||||
zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_text_input_manager_v3,
|
||||
ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, NULL, seat);
|
||||
|
||||
return (struct zwp_text_input_v3 *) id;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -0,0 +1,77 @@
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
/* Generated by wayland-scanner 1.17.0 */
|
||||
|
||||
/*
|
||||
* Copyright © 2018 Simon Ser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "wayland-util.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
||||
#endif
|
||||
|
||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define WL_PRIVATE
|
||||
#endif
|
||||
|
||||
extern const struct wl_interface xdg_toplevel_interface;
|
||||
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
|
||||
|
||||
static const struct wl_interface *types[] = {
|
||||
NULL,
|
||||
&zxdg_toplevel_decoration_v1_interface,
|
||||
&xdg_toplevel_interface,
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "get_toplevel_decoration", "no", types + 1 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
|
||||
"zxdg_decoration_manager_v1", 1,
|
||||
2, zxdg_decoration_manager_v1_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "set_mode", "u", types + 0 },
|
||||
{ "unset_mode", "", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
|
||||
{ "configure", "u", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
|
||||
"zxdg_toplevel_decoration_v1", 1,
|
||||
3, zxdg_toplevel_decoration_v1_requests,
|
||||
1, zxdg_toplevel_decoration_v1_events,
|
||||
};
|
||||
|
@ -0,0 +1,376 @@
|
||||
/* Generated by wayland-scanner 1.17.0 */
|
||||
|
||||
#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
|
||||
#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
|
||||
* @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
|
||||
* - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
|
||||
* - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
|
||||
* @section page_copyright_xdg_decoration_unstable_v1 Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright © 2018 Simon Ser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
* </pre>
|
||||
*/
|
||||
struct xdg_toplevel;
|
||||
struct zxdg_decoration_manager_v1;
|
||||
struct zxdg_toplevel_decoration_v1;
|
||||
|
||||
/**
|
||||
* @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
|
||||
* @section page_iface_zxdg_decoration_manager_v1_desc Description
|
||||
*
|
||||
* This interface allows a compositor to announce support for server-side
|
||||
* decorations.
|
||||
*
|
||||
* A window decoration is a set of window controls as deemed appropriate by
|
||||
* the party managing them, such as user interface components used to move,
|
||||
* resize and change a window's state.
|
||||
*
|
||||
* A client can use this protocol to request being decorated by a supporting
|
||||
* compositor.
|
||||
*
|
||||
* If compositor and client do not negotiate the use of a server-side
|
||||
* decoration using this protocol, clients continue to self-decorate as they
|
||||
* see fit.
|
||||
*
|
||||
* Warning! The protocol described in this file is experimental and
|
||||
* backward incompatible changes may be made. Backward compatible changes
|
||||
* may be added together with the corresponding interface version bump.
|
||||
* Backward incompatible changes are done by bumping the version number in
|
||||
* the protocol and interface names and resetting the interface version.
|
||||
* Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
* version number in the protocol and interface names are removed and the
|
||||
* interface version number is reset.
|
||||
* @section page_iface_zxdg_decoration_manager_v1_api API
|
||||
* See @ref iface_zxdg_decoration_manager_v1.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
|
||||
*
|
||||
* This interface allows a compositor to announce support for server-side
|
||||
* decorations.
|
||||
*
|
||||
* A window decoration is a set of window controls as deemed appropriate by
|
||||
* the party managing them, such as user interface components used to move,
|
||||
* resize and change a window's state.
|
||||
*
|
||||
* A client can use this protocol to request being decorated by a supporting
|
||||
* compositor.
|
||||
*
|
||||
* If compositor and client do not negotiate the use of a server-side
|
||||
* decoration using this protocol, clients continue to self-decorate as they
|
||||
* see fit.
|
||||
*
|
||||
* Warning! The protocol described in this file is experimental and
|
||||
* backward incompatible changes may be made. Backward compatible changes
|
||||
* may be added together with the corresponding interface version bump.
|
||||
* Backward incompatible changes are done by bumping the version number in
|
||||
* the protocol and interface names and resetting the interface version.
|
||||
* Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
* version number in the protocol and interface names are removed and the
|
||||
* interface version number is reset.
|
||||
*/
|
||||
extern const struct wl_interface zxdg_decoration_manager_v1_interface;
|
||||
/**
|
||||
* @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
|
||||
* @section page_iface_zxdg_toplevel_decoration_v1_desc Description
|
||||
*
|
||||
* The decoration object allows the compositor to toggle server-side window
|
||||
* decorations for a toplevel surface. The client can request to switch to
|
||||
* another mode.
|
||||
*
|
||||
* The xdg_toplevel_decoration object must be destroyed before its
|
||||
* xdg_toplevel.
|
||||
* @section page_iface_zxdg_toplevel_decoration_v1_api API
|
||||
* See @ref iface_zxdg_toplevel_decoration_v1.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
|
||||
*
|
||||
* The decoration object allows the compositor to toggle server-side window
|
||||
* decorations for a toplevel surface. The client can request to switch to
|
||||
* another mode.
|
||||
*
|
||||
* The xdg_toplevel_decoration object must be destroyed before its
|
||||
* xdg_toplevel.
|
||||
*/
|
||||
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
|
||||
|
||||
#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
|
||||
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*/
|
||||
#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*/
|
||||
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zxdg_decoration_manager_v1 */
|
||||
static inline void
|
||||
zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zxdg_decoration_manager_v1 */
|
||||
static inline void *
|
||||
zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*
|
||||
* Destroy the decoration manager. This doesn't destroy objects created
|
||||
* with the manager.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_decoration_manager_v1,
|
||||
ZXDG_DECORATION_MANAGER_V1_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zxdg_decoration_manager_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_decoration_manager_v1
|
||||
*
|
||||
* Create a new decoration object associated with the given toplevel.
|
||||
*
|
||||
* Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
|
||||
* buffer attached or committed is a client error, and any attempts by a
|
||||
* client to attach or manipulate a buffer prior to the first
|
||||
* xdg_toplevel_decoration.configure event must also be treated as
|
||||
* errors.
|
||||
*/
|
||||
static inline struct zxdg_toplevel_decoration_v1 *
|
||||
zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_constructor((struct wl_proxy *) zxdg_decoration_manager_v1,
|
||||
ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, NULL, toplevel);
|
||||
|
||||
return (struct zxdg_toplevel_decoration_v1 *) id;
|
||||
}
|
||||
|
||||
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
|
||||
enum zxdg_toplevel_decoration_v1_error {
|
||||
/**
|
||||
* xdg_toplevel has a buffer attached before configure
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
|
||||
/**
|
||||
* xdg_toplevel already has a decoration object
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
|
||||
/**
|
||||
* xdg_toplevel destroyed before the decoration object
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
|
||||
};
|
||||
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
|
||||
|
||||
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
* window decoration modes
|
||||
*
|
||||
* These values describe window decoration modes.
|
||||
*/
|
||||
enum zxdg_toplevel_decoration_v1_mode {
|
||||
/**
|
||||
* no server-side window decoration
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
|
||||
/**
|
||||
* server-side window decoration
|
||||
*/
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
|
||||
};
|
||||
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
* @struct zxdg_toplevel_decoration_v1_listener
|
||||
*/
|
||||
struct zxdg_toplevel_decoration_v1_listener {
|
||||
/**
|
||||
* suggest a surface change
|
||||
*
|
||||
* The configure event asks the client to change its decoration
|
||||
* mode. The configured state should not be applied immediately.
|
||||
* Clients must send an ack_configure in response to this event.
|
||||
* See xdg_surface.configure and xdg_surface.ack_configure for
|
||||
* details.
|
||||
*
|
||||
* A configure event can be sent at any time. The specified mode
|
||||
* must be obeyed by the client.
|
||||
* @param mode the decoration mode
|
||||
*/
|
||||
void (*configure)(void *data,
|
||||
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
||||
uint32_t mode);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
static inline int
|
||||
zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
||||
const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*/
|
||||
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
|
||||
static inline void *
|
||||
zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*
|
||||
* Switch back to a mode without any server-side decorations at the next
|
||||
* commit.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_DESTROY);
|
||||
|
||||
wl_proxy_destroy((struct wl_proxy *) zxdg_toplevel_decoration_v1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*
|
||||
* Set the toplevel surface decoration mode. This informs the compositor
|
||||
* that the client prefers the provided decoration mode.
|
||||
*
|
||||
* After requesting a decoration mode, the compositor will respond by
|
||||
* emitting a xdg_surface.configure event. The client should then update
|
||||
* its content, drawing it without decorations if the received mode is
|
||||
* server-side decorations. The client must also acknowledge the configure
|
||||
* when committing the new content (see xdg_surface.ack_configure).
|
||||
*
|
||||
* The compositor can decide not to use the client's mode and enforce a
|
||||
* different mode instead.
|
||||
*
|
||||
* Clients whose decoration mode depend on the xdg_toplevel state may send
|
||||
* a set_mode request in response to a xdg_surface.configure event and wait
|
||||
* for the next xdg_surface.configure event to prevent unwanted state.
|
||||
* Such clients are responsible for preventing configure loops and must
|
||||
* make sure not to send multiple successive set_mode requests with the
|
||||
* same decoration mode.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_zxdg_toplevel_decoration_v1
|
||||
*
|
||||
* Unset the toplevel surface decoration mode. This informs the compositor
|
||||
* that the client doesn't prefer a particular decoration mode.
|
||||
*
|
||||
* This request has the same semantics as set_mode.
|
||||
*/
|
||||
static inline void
|
||||
zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
|
||||
{
|
||||
wl_proxy_marshal((struct wl_proxy *) zxdg_toplevel_decoration_v1,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -0,0 +1,176 @@
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
/* Generated by wayland-scanner 1.17.0 */
|
||||
|
||||
/*
|
||||
* Copyright © 2008-2013 Kristian Høgsberg
|
||||
* Copyright © 2013 Rafael Antognolli
|
||||
* Copyright © 2013 Jasper St. Pierre
|
||||
* Copyright © 2010-2013 Intel Corporation
|
||||
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
|
||||
* Copyright © 2015-2017 Red Hat Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "wayland-util.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
||||
#endif
|
||||
|
||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
||||
#else
|
||||
#define WL_PRIVATE
|
||||
#endif
|
||||
|
||||
extern const struct wl_interface wl_output_interface;
|
||||
extern const struct wl_interface wl_seat_interface;
|
||||
extern const struct wl_interface wl_surface_interface;
|
||||
extern const struct wl_interface xdg_popup_interface;
|
||||
extern const struct wl_interface xdg_positioner_interface;
|
||||
extern const struct wl_interface xdg_surface_interface;
|
||||
extern const struct wl_interface xdg_toplevel_interface;
|
||||
|
||||
static const struct wl_interface *types[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&xdg_positioner_interface,
|
||||
&xdg_surface_interface,
|
||||
&wl_surface_interface,
|
||||
&xdg_toplevel_interface,
|
||||
&xdg_popup_interface,
|
||||
&xdg_surface_interface,
|
||||
&xdg_positioner_interface,
|
||||
&xdg_toplevel_interface,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
NULL,
|
||||
&wl_output_interface,
|
||||
&wl_seat_interface,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "create_positioner", "n", types + 4 },
|
||||
{ "get_xdg_surface", "no", types + 5 },
|
||||
{ "pong", "u", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_wm_base_events[] = {
|
||||
{ "ping", "u", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
|
||||
"xdg_wm_base", 2,
|
||||
4, xdg_wm_base_requests,
|
||||
1, xdg_wm_base_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_positioner_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "set_size", "ii", types + 0 },
|
||||
{ "set_anchor_rect", "iiii", types + 0 },
|
||||
{ "set_anchor", "u", types + 0 },
|
||||
{ "set_gravity", "u", types + 0 },
|
||||
{ "set_constraint_adjustment", "u", types + 0 },
|
||||
{ "set_offset", "ii", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
|
||||
"xdg_positioner", 2,
|
||||
7, xdg_positioner_requests,
|
||||
0, NULL,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "get_toplevel", "n", types + 7 },
|
||||
{ "get_popup", "n?oo", types + 8 },
|
||||
{ "set_window_geometry", "iiii", types + 0 },
|
||||
{ "ack_configure", "u", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_surface_events[] = {
|
||||
{ "configure", "u", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
|
||||
"xdg_surface", 2,
|
||||
5, xdg_surface_requests,
|
||||
1, xdg_surface_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "set_parent", "?o", types + 11 },
|
||||
{ "set_title", "s", types + 0 },
|
||||
{ "set_app_id", "s", types + 0 },
|
||||
{ "show_window_menu", "ouii", types + 12 },
|
||||
{ "move", "ou", types + 16 },
|
||||
{ "resize", "ouu", types + 18 },
|
||||
{ "set_max_size", "ii", types + 0 },
|
||||
{ "set_min_size", "ii", types + 0 },
|
||||
{ "set_maximized", "", types + 0 },
|
||||
{ "unset_maximized", "", types + 0 },
|
||||
{ "set_fullscreen", "?o", types + 21 },
|
||||
{ "unset_fullscreen", "", types + 0 },
|
||||
{ "set_minimized", "", types + 0 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_toplevel_events[] = {
|
||||
{ "configure", "iia", types + 0 },
|
||||
{ "close", "", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
|
||||
"xdg_toplevel", 2,
|
||||
14, xdg_toplevel_requests,
|
||||
2, xdg_toplevel_events,
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_requests[] = {
|
||||
{ "destroy", "", types + 0 },
|
||||
{ "grab", "ou", types + 22 },
|
||||
};
|
||||
|
||||
static const struct wl_message xdg_popup_events[] = {
|
||||
{ "configure", "iiii", types + 0 },
|
||||
{ "popup_done", "", types + 0 },
|
||||
};
|
||||
|
||||
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
|
||||
"xdg_popup", 2,
|
||||
2, xdg_popup_requests,
|
||||
2, xdg_popup_events,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package window implements platform specific windows
|
||||
// and GPU contexts.
|
||||
package window
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gioui.org/gpu/backend"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Width, Height unit.Value
|
||||
MinWidth, MinHeight unit.Value
|
||||
MaxWidth, MaxHeight unit.Value
|
||||
Title string
|
||||
}
|
||||
|
||||
type FrameEvent struct {
|
||||
system.FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
|
||||
type Callbacks interface {
|
||||
SetDriver(d Driver)
|
||||
Event(e event.Event)
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
Backend() (backend.Device, error)
|
||||
Present() error
|
||||
MakeCurrent() error
|
||||
Release()
|
||||
Lock()
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// ErrDeviceLost is returned from Context.Present when
|
||||
// the underlying GPU device is gone and should be
|
||||
// recreated.
|
||||
var ErrDeviceLost = errors.New("GPU device lost")
|
||||
|
||||
// Driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type Driver interface {
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
// ShowTextInput updates the virtual keyboard state.
|
||||
ShowTextInput(show bool)
|
||||
NewContext() (Context, error)
|
||||
|
||||
// ReadClipboard requests the clipboard content.
|
||||
ReadClipboard()
|
||||
// WriteClipboard requests a clipboard write.
|
||||
WriteClipboard(s string)
|
||||
|
||||
// Close the window.
|
||||
Close()
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndOptions
|
||||
out chan windowAndOptions
|
||||
errs chan error
|
||||
}
|
||||
|
||||
type windowAndOptions struct {
|
||||
window Callbacks
|
||||
opts *Options
|
||||
}
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndOptions),
|
||||
out: make(chan windowAndOptions),
|
||||
errs: make(chan error),
|
||||
}
|
||||
go func() {
|
||||
var main windowAndOptions
|
||||
var out chan windowAndOptions
|
||||
for {
|
||||
select {
|
||||
case w := <-wr.in:
|
||||
var err error
|
||||
if main.window != nil {
|
||||
err = errors.New("multiple windows are not supported")
|
||||
}
|
||||
wr.errs <- err
|
||||
main = w
|
||||
out = wr.out
|
||||
case out <- main:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
@ -0,0 +1,537 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type Rect struct {
|
||||
Left, Top, Right, Bottom int32
|
||||
}
|
||||
|
||||
type WndClassEx struct {
|
||||
CbSize uint32
|
||||
Style uint32
|
||||
LpfnWndProc uintptr
|
||||
CnClsExtra int32
|
||||
CbWndExtra int32
|
||||
HInstance syscall.Handle
|
||||
HIcon syscall.Handle
|
||||
HCursor syscall.Handle
|
||||
HbrBackground syscall.Handle
|
||||
LpszMenuName *uint16
|
||||
LpszClassName *uint16
|
||||
HIconSm syscall.Handle
|
||||
}
|
||||
|
||||
type Msg struct {
|
||||
Hwnd syscall.Handle
|
||||
Message uint32
|
||||
WParam uintptr
|
||||
LParam uintptr
|
||||
Time uint32
|
||||
Pt Point
|
||||
LPrivate uint32
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
X, Y int32
|
||||
}
|
||||
|
||||
type MinMaxInfo struct {
|
||||
PtReserved Point
|
||||
PtMaxSize Point
|
||||
PtMaxPosition Point
|
||||
PtMinTrackSize Point
|
||||
PtMaxTrackSize Point
|
||||
}
|
||||
|
||||
const (
|
||||
CS_HREDRAW = 0x0002
|
||||
CS_VREDRAW = 0x0001
|
||||
CS_OWNDC = 0x0020
|
||||
|
||||
CW_USEDEFAULT = -2147483648
|
||||
|
||||
IDC_ARROW = 32512
|
||||
|
||||
INFINITE = 0xFFFFFFFF
|
||||
|
||||
LOGPIXELSX = 88
|
||||
|
||||
MDT_EFFECTIVE_DPI = 0
|
||||
|
||||
MONITOR_DEFAULTTOPRIMARY = 1
|
||||
|
||||
SIZE_MAXIMIZED = 2
|
||||
SIZE_MINIMIZED = 1
|
||||
SIZE_RESTORED = 0
|
||||
|
||||
SW_SHOWDEFAULT = 10
|
||||
|
||||
USER_TIMER_MINIMUM = 0x0000000A
|
||||
|
||||
VK_CONTROL = 0x11
|
||||
VK_LWIN = 0x5B
|
||||
VK_MENU = 0x12
|
||||
VK_RWIN = 0x5C
|
||||
VK_SHIFT = 0x10
|
||||
|
||||
VK_BACK = 0x08
|
||||
VK_DELETE = 0x2e
|
||||
VK_DOWN = 0x28
|
||||
VK_END = 0x23
|
||||
VK_ESCAPE = 0x1b
|
||||
VK_HOME = 0x24
|
||||
VK_LEFT = 0x25
|
||||
VK_NEXT = 0x22
|
||||
VK_PRIOR = 0x21
|
||||
VK_RIGHT = 0x27
|
||||
VK_RETURN = 0x0d
|
||||
VK_SPACE = 0x20
|
||||
VK_TAB = 0x09
|
||||
VK_UP = 0x26
|
||||
|
||||
VK_F1 = 0x70
|
||||
VK_F2 = 0x71
|
||||
VK_F3 = 0x72
|
||||
VK_F4 = 0x73
|
||||
VK_F5 = 0x74
|
||||
VK_F6 = 0x75
|
||||
VK_F7 = 0x76
|
||||
VK_F8 = 0x77
|
||||
VK_F9 = 0x78
|
||||
VK_F10 = 0x79
|
||||
VK_F11 = 0x7A
|
||||
VK_F12 = 0x7B
|
||||
|
||||
VK_OEM_1 = 0xba
|
||||
VK_OEM_PLUS = 0xbb
|
||||
VK_OEM_COMMA = 0xbc
|
||||
VK_OEM_MINUS = 0xbd
|
||||
VK_OEM_PERIOD = 0xbe
|
||||
VK_OEM_2 = 0xbf
|
||||
VK_OEM_3 = 0xc0
|
||||
VK_OEM_4 = 0xdb
|
||||
VK_OEM_5 = 0xdc
|
||||
VK_OEM_6 = 0xdd
|
||||
VK_OEM_7 = 0xde
|
||||
VK_OEM_102 = 0xe2
|
||||
|
||||
UNICODE_NOCHAR = 65535
|
||||
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_PAINT = 0x000F
|
||||
WM_CLOSE = 0x0010
|
||||
WM_QUIT = 0x0012
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
|
||||
WS_CLIPCHILDREN = 0x00010000
|
||||
WS_CLIPSIBLINGS = 0x04000000
|
||||
WS_VISIBLE = 0x10000000
|
||||
WS_OVERLAPPED = 0x00000000
|
||||
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
|
||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
||||
WS_CAPTION = 0x00C00000
|
||||
WS_SYSMENU = 0x00080000
|
||||
WS_THICKFRAME = 0x00040000
|
||||
WS_MINIMIZEBOX = 0x00020000
|
||||
WS_MAXIMIZEBOX = 0x00010000
|
||||
|
||||
WS_EX_APPWINDOW = 0x00040000
|
||||
WS_EX_WINDOWEDGE = 0x00000100
|
||||
|
||||
QS_ALLINPUT = 0x04FF
|
||||
|
||||
MWMO_WAITALL = 0x0001
|
||||
MWMO_INPUTAVAILABLE = 0x0004
|
||||
|
||||
WAIT_OBJECT_0 = 0
|
||||
|
||||
PM_REMOVE = 0x0001
|
||||
PM_NOREMOVE = 0x0000
|
||||
|
||||
GHND = 0x0042
|
||||
|
||||
CF_UNICODETEXT = 13
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32.dll")
|
||||
_GetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||
_GlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
_GlobalFree = kernel32.NewProc("GlobalFree")
|
||||
_GlobalLock = kernel32.NewProc("GlobalLock")
|
||||
_GlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
|
||||
user32 = syscall.NewLazySystemDLL("user32.dll")
|
||||
_AdjustWindowRectEx = user32.NewProc("AdjustWindowRectEx")
|
||||
_CallMsgFilter = user32.NewProc("CallMsgFilterW")
|
||||
_CloseClipboard = user32.NewProc("CloseClipboard")
|
||||
_CreateWindowEx = user32.NewProc("CreateWindowExW")
|
||||
_DefWindowProc = user32.NewProc("DefWindowProcW")
|
||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||
_GetClientRect = user32.NewProc("GetClientRect")
|
||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||
_GetDC = user32.NewProc("GetDC")
|
||||
_GetKeyState = user32.NewProc("GetKeyState")
|
||||
_GetMessage = user32.NewProc("GetMessageW")
|
||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||
_KillTimer = user32.NewProc("KillTimer")
|
||||
_LoadCursor = user32.NewProc("LoadCursorW")
|
||||
_MonitorFromPoint = user32.NewProc("MonitorFromPoint")
|
||||
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
|
||||
_OpenClipboard = user32.NewProc("OpenClipboard")
|
||||
_PeekMessage = user32.NewProc("PeekMessageW")
|
||||
_PostMessage = user32.NewProc("PostMessageW")
|
||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||
_ShowWindow = user32.NewProc("ShowWindow")
|
||||
_SetCapture = user32.NewProc("SetCapture")
|
||||
_SetClipboardData = user32.NewProc("SetClipboardData")
|
||||
_SetForegroundWindow = user32.NewProc("SetForegroundWindow")
|
||||
_SetFocus = user32.NewProc("SetFocus")
|
||||
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
|
||||
_SetTimer = user32.NewProc("SetTimer")
|
||||
_TranslateMessage = user32.NewProc("TranslateMessage")
|
||||
_UnregisterClass = user32.NewProc("UnregisterClassW")
|
||||
_UpdateWindow = user32.NewProc("UpdateWindow")
|
||||
|
||||
shcore = syscall.NewLazySystemDLL("shcore")
|
||||
_GetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
|
||||
|
||||
gdi32 = syscall.NewLazySystemDLL("gdi32")
|
||||
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
|
||||
)
|
||||
|
||||
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
||||
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
|
||||
issue34474KeepAlive(r)
|
||||
}
|
||||
|
||||
func CallMsgFilter(m *Msg, nCode uintptr) bool {
|
||||
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
|
||||
issue34474KeepAlive(m)
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func CloseClipboard() error {
|
||||
r, _, err := _CloseClipboard.Call()
|
||||
if r == 0 {
|
||||
return fmt.Errorf("CloseClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) {
|
||||
wname := syscall.StringToUTF16Ptr(lpWindowName)
|
||||
hwnd, _, err := _CreateWindowEx.Call(
|
||||
uintptr(dwExStyle),
|
||||
uintptr(lpClassName),
|
||||
uintptr(unsafe.Pointer(wname)),
|
||||
uintptr(dwStyle),
|
||||
uintptr(x), uintptr(y),
|
||||
uintptr(w), uintptr(h),
|
||||
uintptr(hWndParent),
|
||||
uintptr(hMenu),
|
||||
uintptr(hInstance),
|
||||
uintptr(lpParam))
|
||||
issue34474KeepAlive(wname)
|
||||
if hwnd == 0 {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||
return r
|
||||
}
|
||||
|
||||
func DestroyWindow(hwnd syscall.Handle) {
|
||||
_DestroyWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func DispatchMessage(m *Msg) {
|
||||
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
issue34474KeepAlive(m)
|
||||
}
|
||||
|
||||
func EmptyClipboard() error {
|
||||
r, _, err := _EmptyClipboard.Call()
|
||||
if r == 0 {
|
||||
return fmt.Errorf("EmptyClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetClientRect(hwnd syscall.Handle, r *Rect) {
|
||||
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(r)))
|
||||
issue34474KeepAlive(r)
|
||||
}
|
||||
|
||||
func GetClipboardData(format uint32) (syscall.Handle, error) {
|
||||
r, _, err := _GetClipboardData.Call(uintptr(format))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GetClipboardData: %v", err)
|
||||
}
|
||||
return syscall.Handle(r), nil
|
||||
}
|
||||
|
||||
func GetDC(hwnd syscall.Handle) (syscall.Handle, error) {
|
||||
hdc, _, err := _GetDC.Call(uintptr(hwnd))
|
||||
if hdc == 0 {
|
||||
return 0, fmt.Errorf("GetDC failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hdc), nil
|
||||
}
|
||||
|
||||
func GetModuleHandle() (syscall.Handle, error) {
|
||||
h, _, err := _GetModuleHandleW.Call(uintptr(0))
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("GetModuleHandleW failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(h), nil
|
||||
}
|
||||
|
||||
func getDeviceCaps(hdc syscall.Handle, index int32) int {
|
||||
c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index))
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func getDpiForMonitor(hmonitor syscall.Handle, dpiType uint32) int {
|
||||
var dpiX, dpiY uintptr
|
||||
_GetDpiForMonitor.Call(uintptr(hmonitor), uintptr(dpiType), uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY)))
|
||||
return int(dpiX)
|
||||
}
|
||||
|
||||
// GetSystemDPI returns the effective DPI of the system.
|
||||
func GetSystemDPI() int {
|
||||
// Check for getDpiForMonitor, introduced in Windows 8.1.
|
||||
if _GetDpiForMonitor.Find() == nil {
|
||||
hmon := monitorFromPoint(Point{}, MONITOR_DEFAULTTOPRIMARY)
|
||||
return getDpiForMonitor(hmon, MDT_EFFECTIVE_DPI)
|
||||
} else {
|
||||
// Fall back to the physical device DPI.
|
||||
screenDC, err := GetDC(0)
|
||||
if err != nil {
|
||||
return 96
|
||||
}
|
||||
defer ReleaseDC(screenDC)
|
||||
return getDeviceCaps(screenDC, LOGPIXELSX)
|
||||
}
|
||||
}
|
||||
|
||||
func GetKeyState(nVirtKey int32) int16 {
|
||||
c, _, _ := _GetKeyState.Call(uintptr(nVirtKey))
|
||||
return int16(c)
|
||||
}
|
||||
|
||||
func GetMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax uint32) int32 {
|
||||
r, _, _ := _GetMessage.Call(uintptr(unsafe.Pointer(m)),
|
||||
uintptr(hwnd),
|
||||
uintptr(wMsgFilterMin),
|
||||
uintptr(wMsgFilterMax))
|
||||
issue34474KeepAlive(m)
|
||||
return int32(r)
|
||||
}
|
||||
|
||||
func GetMessageTime() time.Duration {
|
||||
r, _, _ := _GetMessageTime.Call()
|
||||
return time.Duration(r) * time.Millisecond
|
||||
}
|
||||
|
||||
func GlobalAlloc(size int) (syscall.Handle, error) {
|
||||
r, _, err := _GlobalAlloc.Call(GHND, uintptr(size))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GlobalAlloc: %v", err)
|
||||
}
|
||||
return syscall.Handle(r), nil
|
||||
}
|
||||
|
||||
func GlobalFree(h syscall.Handle) {
|
||||
_GlobalFree.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func GlobalLock(h syscall.Handle) (uintptr, error) {
|
||||
r, _, err := _GlobalLock.Call(uintptr(h))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GlobalLock: %v", err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func GlobalUnlock(h syscall.Handle) {
|
||||
_GlobalUnlock.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func KillTimer(hwnd syscall.Handle, nIDEvent uintptr) error {
|
||||
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), 0, 0)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("KillTimer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadCursor(curID uint16) (syscall.Handle, error) {
|
||||
h, _, err := _LoadCursor.Call(0, uintptr(curID))
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("LoadCursorW failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(h), nil
|
||||
}
|
||||
|
||||
func monitorFromPoint(pt Point, flags uint32) syscall.Handle {
|
||||
r, _, _ := _MonitorFromPoint.Call(uintptr(pt.X), uintptr(pt.Y), uintptr(flags))
|
||||
return syscall.Handle(r)
|
||||
}
|
||||
|
||||
func MsgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, flags uint32) (uint32, error) {
|
||||
r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags))
|
||||
res := uint32(r)
|
||||
if res == 0xFFFFFFFF {
|
||||
return 0, fmt.Errorf("MsgWaitForMultipleObjectsEx failed: %v", err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func OpenClipboard(hwnd syscall.Handle) error {
|
||||
r, _, err := _OpenClipboard.Call(uintptr(hwnd))
|
||||
if r == 0 {
|
||||
return fmt.Errorf("OpenClipboard: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PeekMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool {
|
||||
r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg))
|
||||
issue34474KeepAlive(m)
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func PostQuitMessage(exitCode uintptr) {
|
||||
_PostQuitMessage.Call(exitCode)
|
||||
}
|
||||
|
||||
func PostMessage(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) error {
|
||||
r, _, err := _PostMessage.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("PostMessage failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReleaseCapture() bool {
|
||||
r, _, _ := _ReleaseCapture.Call()
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func RegisterClassEx(cls *WndClassEx) (uint16, error) {
|
||||
a, _, err := _RegisterClassExW.Call(uintptr(unsafe.Pointer(cls)))
|
||||
issue34474KeepAlive(cls)
|
||||
if a == 0 {
|
||||
return 0, fmt.Errorf("RegisterClassExW failed: %v", err)
|
||||
}
|
||||
return uint16(a), nil
|
||||
}
|
||||
|
||||
func ReleaseDC(hdc syscall.Handle) {
|
||||
_ReleaseDC.Call(uintptr(hdc))
|
||||
}
|
||||
|
||||
func SetForegroundWindow(hwnd syscall.Handle) {
|
||||
_SetForegroundWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func SetFocus(hwnd syscall.Handle) {
|
||||
_SetFocus.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
func SetProcessDPIAware() {
|
||||
_SetProcessDPIAware.Call()
|
||||
}
|
||||
|
||||
func SetCapture(hwnd syscall.Handle) syscall.Handle {
|
||||
r, _, _ := _SetCapture.Call(uintptr(hwnd))
|
||||
return syscall.Handle(r)
|
||||
}
|
||||
|
||||
func SetClipboardData(format uint32, mem syscall.Handle) error {
|
||||
r, _, err := _SetClipboardData.Call(uintptr(format), uintptr(mem))
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SetClipboardData: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error {
|
||||
r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc)
|
||||
if r == 0 {
|
||||
return fmt.Errorf("SetTimer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ScreenToClient(hwnd syscall.Handle, p *Point) {
|
||||
_ScreenToClient.Call(uintptr(hwnd), uintptr(unsafe.Pointer(p)))
|
||||
issue34474KeepAlive(p)
|
||||
}
|
||||
|
||||
func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
|
||||
_ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
|
||||
}
|
||||
|
||||
func TranslateMessage(m *Msg) {
|
||||
_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
issue34474KeepAlive(m)
|
||||
}
|
||||
|
||||
func UnregisterClass(cls uint16, hInst syscall.Handle) {
|
||||
_UnregisterClass.Call(uintptr(cls), uintptr(hInst))
|
||||
}
|
||||
|
||||
func UpdateWindow(hwnd syscall.Handle) {
|
||||
_UpdateWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
runtime.KeepAlive(v)
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
// Package xkb implements a Go interface for the X Keyboard Extension library.
|
||||
package xkb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: xkbcommon
|
||||
#cgo freebsd openbsd CFLAGS: -I/usr/local/include
|
||||
#cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Context struct {
|
||||
Ctx *C.struct_xkb_context
|
||||
keyMap *C.struct_xkb_keymap
|
||||
state *C.struct_xkb_state
|
||||
compTable *C.struct_xkb_compose_table
|
||||
compState *C.struct_xkb_compose_state
|
||||
utf8Buf []byte
|
||||
}
|
||||
|
||||
var (
|
||||
_XKB_MOD_NAME_CTRL = []byte("Control\x00")
|
||||
_XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
|
||||
_XKB_MOD_NAME_ALT = []byte("Mod1\x00")
|
||||
_XKB_MOD_NAME_LOGO = []byte("Mod4\x00")
|
||||
)
|
||||
|
||||
func (x *Context) Destroy() {
|
||||
if x.compState != nil {
|
||||
C.xkb_compose_state_unref(x.compState)
|
||||
x.compState = nil
|
||||
}
|
||||
if x.compTable != nil {
|
||||
C.xkb_compose_table_unref(x.compTable)
|
||||
x.compTable = nil
|
||||
}
|
||||
x.DestroyKeymapState()
|
||||
if x.Ctx != nil {
|
||||
C.xkb_context_unref(x.Ctx)
|
||||
x.Ctx = nil
|
||||
}
|
||||
}
|
||||
|
||||
func New() (*Context, error) {
|
||||
ctx := &Context{
|
||||
Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
|
||||
}
|
||||
if ctx.Ctx == nil {
|
||||
return nil, errors.New("newXKB: xkb_context_new failed")
|
||||
}
|
||||
locale := os.Getenv("LC_ALL")
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LC_CTYPE")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LANG")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = "C"
|
||||
}
|
||||
cloc := C.CString(locale)
|
||||
defer C.free(unsafe.Pointer(cloc))
|
||||
ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
|
||||
if ctx.compTable == nil {
|
||||
ctx.Destroy()
|
||||
return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
|
||||
}
|
||||
ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
|
||||
if ctx.compState == nil {
|
||||
ctx.Destroy()
|
||||
return nil, errors.New("newXKB: xkb_compose_state_new failed")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (x *Context) DestroyKeymapState() {
|
||||
if x.state != nil {
|
||||
C.xkb_state_unref(x.state)
|
||||
x.state = nil
|
||||
}
|
||||
if x.keyMap != nil {
|
||||
C.xkb_keymap_unref(x.keyMap)
|
||||
x.keyMap = nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeymap sets the keymap and state. The context takes ownership of the
|
||||
// keymap and state and frees them in Destroy.
|
||||
func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) {
|
||||
x.DestroyKeymapState()
|
||||
x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap)
|
||||
x.state = (*C.struct_xkb_state)(xkbState)
|
||||
}
|
||||
|
||||
func (x *Context) LoadKeymap(format int, fd int, size int) error {
|
||||
x.DestroyKeymapState()
|
||||
mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
|
||||
if err != nil {
|
||||
return fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
|
||||
}
|
||||
defer syscall.Munmap(mapData)
|
||||
keyMap := C.xkb_keymap_new_from_buffer(x.Ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
|
||||
if keyMap == nil {
|
||||
return errors.New("newXKB: xkb_keymap_new_from_buffer failed")
|
||||
}
|
||||
state := C.xkb_state_new(keyMap)
|
||||
if state == nil {
|
||||
C.xkb_keymap_unref(keyMap)
|
||||
return errors.New("newXKB: xkb_state_new failed")
|
||||
}
|
||||
x.keyMap = keyMap
|
||||
x.state = state
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Context) Modifiers() key.Modifiers {
|
||||
var mods key.Modifiers
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModShift
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_ALT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModAlt
|
||||
}
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_LOGO[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModSuper
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
func (x *Context) DispatchKey(keyCode uint32) (events []event.Event) {
|
||||
if x.state == nil {
|
||||
return
|
||||
}
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
if len(x.utf8Buf) == 0 {
|
||||
x.utf8Buf = make([]byte, 1)
|
||||
}
|
||||
sym := C.xkb_state_key_get_one_sym(x.state, kc)
|
||||
if name, ok := convertKeysym(sym); ok {
|
||||
cmd := key.Event{
|
||||
Name: name,
|
||||
Modifiers: x.Modifiers(),
|
||||
}
|
||||
// Ensure that a physical backtab key is translated to
|
||||
// Shift-Tab.
|
||||
if sym == C.XKB_KEY_ISO_Left_Tab {
|
||||
cmd.Modifiers |= key.ModShift
|
||||
}
|
||||
events = append(events, cmd)
|
||||
}
|
||||
C.xkb_compose_state_feed(x.compState, sym)
|
||||
var str []byte
|
||||
switch C.xkb_compose_state_get_status(x.compState) {
|
||||
case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
|
||||
return
|
||||
case C.XKB_COMPOSE_COMPOSED:
|
||||
size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
if int(size) >= len(x.utf8Buf) {
|
||||
x.utf8Buf = make([]byte, size+1)
|
||||
size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
}
|
||||
C.xkb_compose_state_reset(x.compState)
|
||||
str = x.utf8Buf[:size]
|
||||
case C.XKB_COMPOSE_NOTHING:
|
||||
str = x.charsForKeycode(kc)
|
||||
}
|
||||
// Report only printable runes.
|
||||
var n int
|
||||
for n < len(str) {
|
||||
r, s := utf8.DecodeRune(str)
|
||||
if unicode.IsPrint(r) {
|
||||
n += s
|
||||
} else {
|
||||
copy(str[n:], str[n+s:])
|
||||
str = str[:len(str)-s]
|
||||
}
|
||||
}
|
||||
if len(str) > 0 {
|
||||
events = append(events, key.EditEvent{Text: string(str)})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
|
||||
size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
if int(size) >= len(x.utf8Buf) {
|
||||
x.utf8Buf = make([]byte, size+1)
|
||||
size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
|
||||
}
|
||||
return x.utf8Buf[:size]
|
||||
}
|
||||
|
||||
func (x *Context) IsRepeatKey(keyCode uint32) bool {
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
|
||||
}
|
||||
|
||||
func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
|
||||
if x.state == nil {
|
||||
return
|
||||
}
|
||||
C.xkb_state_update_mask(x.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked),
|
||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||
}
|
||||
|
||||
func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
if 'a' <= s && s <= 'z' {
|
||||
return string(rune(s - 'a' + 'A')), true
|
||||
}
|
||||
if ' ' <= s && s <= '~' {
|
||||
return string(rune(s)), true
|
||||
}
|
||||
var n string
|
||||
switch s {
|
||||
case C.XKB_KEY_Escape:
|
||||
n = key.NameEscape
|
||||
case C.XKB_KEY_Left:
|
||||
n = key.NameLeftArrow
|
||||
case C.XKB_KEY_Right:
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_Return:
|
||||
n = key.NameReturn
|
||||
case C.XKB_KEY_KP_Enter:
|
||||
n = key.NameEnter
|
||||
case C.XKB_KEY_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_Down:
|
||||
n = key.NameDownArrow
|
||||
case C.XKB_KEY_Home:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_End:
|
||||
n = key.NameEnd
|
||||
case C.XKB_KEY_BackSpace:
|
||||
n = key.NameDeleteBackward
|
||||
case C.XKB_KEY_Delete:
|
||||
n = key.NameDeleteForward
|
||||
case C.XKB_KEY_Page_Up:
|
||||
n = key.NamePageUp
|
||||
case C.XKB_KEY_Page_Down:
|
||||
n = key.NamePageDown
|
||||
case C.XKB_KEY_F1:
|
||||
n = "F1"
|
||||
case C.XKB_KEY_F2:
|
||||
n = "F2"
|
||||
case C.XKB_KEY_F3:
|
||||
n = "F3"
|
||||
case C.XKB_KEY_F4:
|
||||
n = "F4"
|
||||
case C.XKB_KEY_F5:
|
||||
n = "F5"
|
||||
case C.XKB_KEY_F6:
|
||||
n = "F6"
|
||||
case C.XKB_KEY_F7:
|
||||
n = "F7"
|
||||
case C.XKB_KEY_F8:
|
||||
n = "F8"
|
||||
case C.XKB_KEY_F9:
|
||||
n = "F9"
|
||||
case C.XKB_KEY_F10:
|
||||
n = "F10"
|
||||
case C.XKB_KEY_F11:
|
||||
n = "F11"
|
||||
case C.XKB_KEY_F12:
|
||||
n = "F12"
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
n = key.NameTab
|
||||
case 0x20, C.XKB_KEY_KP_Space:
|
||||
n = "Space"
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"image"
|
||||
"runtime"
|
||||
|
||||
"gioui.org/app/internal/window"
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type renderLoop struct {
|
||||
summary string
|
||||
drawing bool
|
||||
err error
|
||||
|
||||
frames chan frame
|
||||
results chan frameResult
|
||||
refresh chan struct{}
|
||||
refreshErr chan error
|
||||
ack chan struct{}
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
type frame struct {
|
||||
viewport image.Point
|
||||
ops *op.Ops
|
||||
}
|
||||
|
||||
type frameResult struct {
|
||||
profile string
|
||||
err error
|
||||
}
|
||||
|
||||
func newLoop(ctx window.Context) (*renderLoop, error) {
|
||||
l := &renderLoop{
|
||||
frames: make(chan frame),
|
||||
results: make(chan frameResult),
|
||||
refresh: make(chan struct{}),
|
||||
refreshErr: make(chan error),
|
||||
// Ack is buffered so GPU commands can be issued after
|
||||
// ack'ing the frame.
|
||||
ack: make(chan struct{}, 1),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
if err := l.renderLoop(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *renderLoop) renderLoop(ctx window.Context) error {
|
||||
// GL Operations must happen on a single OS thread, so
|
||||
// pass initialization result through a channel.
|
||||
initErr := make(chan error)
|
||||
go func() {
|
||||
defer close(l.stopped)
|
||||
runtime.LockOSThread()
|
||||
// Don't UnlockOSThread to avoid reuse by the Go runtime.
|
||||
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
initErr <- err
|
||||
return
|
||||
}
|
||||
b, err := ctx.Backend()
|
||||
if err != nil {
|
||||
initErr <- err
|
||||
return
|
||||
}
|
||||
g, err := gpu.New(b)
|
||||
if err != nil {
|
||||
initErr <- err
|
||||
return
|
||||
}
|
||||
defer ctx.Release()
|
||||
initErr <- nil
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-l.refresh:
|
||||
l.refreshErr <- ctx.MakeCurrent()
|
||||
case frame := <-l.frames:
|
||||
ctx.Lock()
|
||||
g.Collect(frame.viewport, frame.ops)
|
||||
// Signal that we're done with the frame ops.
|
||||
l.ack <- struct{}{}
|
||||
g.BeginFrame()
|
||||
var res frameResult
|
||||
res.err = ctx.Present()
|
||||
g.EndFrame()
|
||||
res.profile = g.Profile()
|
||||
ctx.Unlock()
|
||||
l.results <- res
|
||||
case <-l.stop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}()
|
||||
return <-initErr
|
||||
}
|
||||
|
||||
func (l *renderLoop) Release() {
|
||||
// Flush error.
|
||||
l.Flush()
|
||||
close(l.stop)
|
||||
<-l.stopped
|
||||
l.stop = nil
|
||||
}
|
||||
|
||||
func (l *renderLoop) Flush() error {
|
||||
if l.drawing {
|
||||
st := <-l.results
|
||||
l.setErr(st.err)
|
||||
if st.profile != "" {
|
||||
l.summary = st.profile
|
||||
}
|
||||
l.drawing = false
|
||||
}
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *renderLoop) Summary() string {
|
||||
return l.summary
|
||||
}
|
||||
|
||||
func (l *renderLoop) Refresh() {
|
||||
if l.err != nil {
|
||||
return
|
||||
}
|
||||
// Make sure any pending frame is complete.
|
||||
l.Flush()
|
||||
l.refresh <- struct{}{}
|
||||
l.setErr(<-l.refreshErr)
|
||||
}
|
||||
|
||||
// Draw initiates a draw of a frame. It returns a channel
|
||||
// than signals when the frame is no longer being accessed.
|
||||
func (l *renderLoop) Draw(viewport image.Point, frameOps *op.Ops) <-chan struct{} {
|
||||
if l.err != nil {
|
||||
l.ack <- struct{}{}
|
||||
return l.ack
|
||||
}
|
||||
l.Flush()
|
||||
l.frames <- frame{viewport, frameOps}
|
||||
l.drawing = true
|
||||
return l.ack
|
||||
}
|
||||
|
||||
func (l *renderLoop) setErr(err error) {
|
||||
if l.err == nil {
|
||||
l.err = err
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build !go1.14
|
||||
|
||||
// Work around golang.org/issue/33384, fixed in CL 191785,
|
||||
// to be released in Go 1.14.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
signal.Notify(make(chan os.Signal), syscall.SIGPIPE)
|
||||
}
|
@ -0,0 +1,458 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"gioui.org/app/internal/window"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/profile"
|
||||
"gioui.org/io/router"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
_ "gioui.org/app/internal/log"
|
||||
)
|
||||
|
||||
// WindowOption configures a Window.
|
||||
type Option func(opts *window.Options)
|
||||
|
||||
// Window represents an operating system window.
|
||||
type Window struct {
|
||||
driver window.Driver
|
||||
loop *renderLoop
|
||||
|
||||
// driverFuncs is a channel of functions to run when
|
||||
// the Window has a valid driver.
|
||||
driverFuncs chan func()
|
||||
|
||||
out chan event.Event
|
||||
in chan event.Event
|
||||
ack chan struct{}
|
||||
invalidates chan struct{}
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// dead is closed when the window is destroyed.
|
||||
dead chan struct{}
|
||||
|
||||
stage system.Stage
|
||||
animating bool
|
||||
hasNextFrame bool
|
||||
nextFrame time.Time
|
||||
delayedDraw *time.Timer
|
||||
|
||||
queue queue
|
||||
|
||||
callbacks callbacks
|
||||
}
|
||||
|
||||
type callbacks struct {
|
||||
w *Window
|
||||
}
|
||||
|
||||
// queue is an event.Queue implementation that distributes system events
|
||||
// to the input handlers declared in the most recent frame.
|
||||
type queue struct {
|
||||
q router.Router
|
||||
}
|
||||
|
||||
// driverEvent is sent when a new native driver
|
||||
// is available for the Window.
|
||||
type driverEvent struct {
|
||||
driver window.Driver
|
||||
}
|
||||
|
||||
// Pre-allocate the ack event to avoid garbage.
|
||||
var ackEvent event.Event
|
||||
|
||||
// NewWindow creates a new window for a set of window
|
||||
// options. The options are hints; the platform is free to
|
||||
// ignore or adjust them.
|
||||
//
|
||||
// If the current program is running on iOS and Android,
|
||||
// NewWindow returns the window previously created by the
|
||||
// platform.
|
||||
//
|
||||
// Calling NewWindow more than once is not supported on
|
||||
// iOS, Android, WebAssembly.
|
||||
func NewWindow(options ...Option) *Window {
|
||||
opts := &window.Options{
|
||||
Width: unit.Dp(800),
|
||||
Height: unit.Dp(600),
|
||||
Title: "Gio",
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
w := &Window{
|
||||
in: make(chan event.Event),
|
||||
out: make(chan event.Event),
|
||||
ack: make(chan struct{}),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func()),
|
||||
dead: make(chan struct{}),
|
||||
}
|
||||
w.callbacks.w = w
|
||||
go w.run(opts)
|
||||
return w
|
||||
}
|
||||
|
||||
// Events returns the channel where events are delivered.
|
||||
func (w *Window) Events() <-chan event.Event {
|
||||
return w.out
|
||||
}
|
||||
|
||||
// update updates the Window. Paint operations updates the
|
||||
// window contents, input operations declare input handlers,
|
||||
// and so on. The supplied operations list completely replaces
|
||||
// the window state from previous calls.
|
||||
func (w *Window) update(frame *op.Ops) {
|
||||
w.frames <- frame
|
||||
<-w.frameAck
|
||||
}
|
||||
|
||||
func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error {
|
||||
for {
|
||||
if w.loop != nil {
|
||||
if err := w.loop.Flush(); err != nil {
|
||||
w.destroyGPU()
|
||||
if err == window.ErrDeviceLost {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.loop == nil {
|
||||
var ctx window.Context
|
||||
ctx, err := w.driver.NewContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.loop, err = newLoop(ctx)
|
||||
if err != nil {
|
||||
ctx.Release()
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.processFrame(frameStart, size, frame)
|
||||
if sync {
|
||||
if err := w.loop.Flush(); err != nil {
|
||||
w.destroyGPU()
|
||||
if err == window.ErrDeviceLost {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) processFrame(frameStart time.Time, size image.Point, frame *op.Ops) {
|
||||
sync := w.loop.Draw(size, frame)
|
||||
w.queue.q.Frame(frame)
|
||||
switch w.queue.q.TextInputState() {
|
||||
case router.TextInputOpen:
|
||||
w.driver.ShowTextInput(true)
|
||||
case router.TextInputClose:
|
||||
w.driver.ShowTextInput(false)
|
||||
}
|
||||
if w.queue.q.Profiling() {
|
||||
frameDur := time.Since(frameStart)
|
||||
frameDur = frameDur.Truncate(100 * time.Microsecond)
|
||||
q := 100 * time.Microsecond
|
||||
timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(q), w.loop.Summary())
|
||||
w.queue.q.Add(profile.Event{Timings: timings})
|
||||
}
|
||||
if t, ok := w.queue.q.WakeupTime(); ok {
|
||||
w.setNextFrame(t)
|
||||
}
|
||||
w.updateAnimation()
|
||||
// Wait for the GPU goroutine to finish processing frame.
|
||||
<-sync
|
||||
}
|
||||
|
||||
// Invalidate the window such that a FrameEvent will be generated
|
||||
// immediately. If the window is inactive, the event is sent when the
|
||||
// window becomes active.
|
||||
// Invalidate is safe for concurrent use.
|
||||
func (w *Window) Invalidate() {
|
||||
select {
|
||||
case w.invalidates <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// ReadClipboard initiates a read of the clipboard in the form
|
||||
// of a system.ClipboardEvent. Multiple reads may be coalescedd
|
||||
// to a single event.
|
||||
func (w *Window) ReadClipboard() {
|
||||
w.driverDo(func() {
|
||||
w.driver.ReadClipboard()
|
||||
})
|
||||
}
|
||||
|
||||
// WriteClipboard writes a string to the clipboard.
|
||||
func (w *Window) WriteClipboard(s string) {
|
||||
w.driverDo(func() {
|
||||
w.driver.WriteClipboard(s)
|
||||
})
|
||||
}
|
||||
|
||||
// Close the window. The window's event loop should exit when it receives
|
||||
// system.DestroyEvent.
|
||||
//
|
||||
// Currently, only macOS, Windows and X11 drivers implement this functionality,
|
||||
// all others are stubbed.
|
||||
func (w *Window) Close() {
|
||||
w.driverDo(func() {
|
||||
w.driver.Close()
|
||||
})
|
||||
}
|
||||
|
||||
// driverDo calls f as soon as the window has a valid driver attached,
|
||||
// or does nothing if the window is destroyed while waiting.
|
||||
func (w *Window) driverDo(f func()) {
|
||||
go func() {
|
||||
select {
|
||||
case w.driverFuncs <- f:
|
||||
case <-w.dead:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *Window) updateAnimation() {
|
||||
animate := false
|
||||
if w.delayedDraw != nil {
|
||||
w.delayedDraw.Stop()
|
||||
w.delayedDraw = nil
|
||||
}
|
||||
if w.stage >= system.StageRunning && w.hasNextFrame {
|
||||
if dt := time.Until(w.nextFrame); dt <= 0 {
|
||||
animate = true
|
||||
} else {
|
||||
w.delayedDraw = time.NewTimer(dt)
|
||||
}
|
||||
}
|
||||
if animate != w.animating {
|
||||
w.animating = animate
|
||||
w.driver.SetAnimating(animate)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) setNextFrame(at time.Time) {
|
||||
if !w.hasNextFrame || at.Before(w.nextFrame) {
|
||||
w.hasNextFrame = true
|
||||
w.nextFrame = at
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) SetDriver(d window.Driver) {
|
||||
c.Event(driverEvent{d})
|
||||
}
|
||||
|
||||
func (c *callbacks) Event(e event.Event) {
|
||||
select {
|
||||
case c.w.in <- e:
|
||||
<-c.w.ack
|
||||
case <-c.w.dead:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) waitAck() {
|
||||
// Send a dummy event; when it gets through we
|
||||
// know the application has processed the previous event.
|
||||
w.out <- ackEvent
|
||||
}
|
||||
|
||||
// Prematurely destroy the window and wait for the native window
|
||||
// destroy event.
|
||||
func (w *Window) destroy(err error) {
|
||||
w.destroyGPU()
|
||||
// Ack the current event.
|
||||
w.ack <- struct{}{}
|
||||
w.out <- system.DestroyEvent{Err: err}
|
||||
close(w.dead)
|
||||
for e := range w.in {
|
||||
w.ack <- struct{}{}
|
||||
if _, ok := e.(system.DestroyEvent); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) destroyGPU() {
|
||||
if w.loop != nil {
|
||||
w.loop.Release()
|
||||
w.loop = nil
|
||||
}
|
||||
}
|
||||
|
||||
// waitFrame waits for the client to either call FrameEvent.Frame
|
||||
// or to continue event handling. It returns whether the client
|
||||
// called Frame or not.
|
||||
func (w *Window) waitFrame() (*op.Ops, bool) {
|
||||
select {
|
||||
case frame := <-w.frames:
|
||||
// The client called FrameEvent.Frame.
|
||||
return frame, true
|
||||
case w.out <- ackEvent:
|
||||
// The client ignored FrameEvent and continued processing
|
||||
// events.
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) run(opts *window.Options) {
|
||||
defer close(w.in)
|
||||
defer close(w.out)
|
||||
if err := window.NewWindow(&w.callbacks, opts); err != nil {
|
||||
w.out <- system.DestroyEvent{Err: err}
|
||||
return
|
||||
}
|
||||
for {
|
||||
var driverFuncs chan func()
|
||||
if w.driver != nil {
|
||||
driverFuncs = w.driverFuncs
|
||||
}
|
||||
var timer <-chan time.Time
|
||||
if w.delayedDraw != nil {
|
||||
timer = w.delayedDraw.C
|
||||
}
|
||||
select {
|
||||
case <-timer:
|
||||
w.setNextFrame(time.Time{})
|
||||
w.updateAnimation()
|
||||
case <-w.invalidates:
|
||||
w.setNextFrame(time.Time{})
|
||||
w.updateAnimation()
|
||||
case f := <-driverFuncs:
|
||||
f()
|
||||
case e := <-w.in:
|
||||
switch e2 := e.(type) {
|
||||
case system.StageEvent:
|
||||
if w.loop != nil {
|
||||
if e2.Stage < system.StageRunning {
|
||||
w.destroyGPU()
|
||||
} else {
|
||||
w.loop.Refresh()
|
||||
}
|
||||
}
|
||||
w.stage = e2.Stage
|
||||
w.updateAnimation()
|
||||
w.out <- e
|
||||
w.waitAck()
|
||||
case window.FrameEvent:
|
||||
if e2.Size == (image.Point{}) {
|
||||
panic(errors.New("internal error: zero-sized Draw"))
|
||||
}
|
||||
if w.stage < system.StageRunning {
|
||||
// No drawing if not visible.
|
||||
break
|
||||
}
|
||||
frameStart := time.Now()
|
||||
w.hasNextFrame = false
|
||||
e2.Frame = w.update
|
||||
e2.Queue = &w.queue
|
||||
w.out <- e2.FrameEvent
|
||||
if w.loop != nil {
|
||||
if e2.Sync {
|
||||
w.loop.Refresh()
|
||||
}
|
||||
}
|
||||
frame, gotFrame := w.waitFrame()
|
||||
err := w.validateAndProcess(frameStart, e2.Size, e2.Sync, frame)
|
||||
if gotFrame {
|
||||
// We're done with frame, let the client continue.
|
||||
w.frameAck <- struct{}{}
|
||||
}
|
||||
if err != nil {
|
||||
w.destroyGPU()
|
||||
w.destroy(err)
|
||||
return
|
||||
}
|
||||
case *system.CommandEvent:
|
||||
w.out <- e
|
||||
w.waitAck()
|
||||
case driverEvent:
|
||||
w.driver = e2.driver
|
||||
case system.DestroyEvent:
|
||||
w.destroyGPU()
|
||||
w.out <- e2
|
||||
w.ack <- struct{}{}
|
||||
return
|
||||
case event.Event:
|
||||
if w.queue.q.Add(e2) {
|
||||
w.setNextFrame(time.Time{})
|
||||
w.updateAnimation()
|
||||
}
|
||||
w.out <- e
|
||||
}
|
||||
w.ack <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queue) Events(k event.Tag) []event.Event {
|
||||
return q.q.Events(k)
|
||||
}
|
||||
|
||||
// Title sets the title of the window.
|
||||
func Title(t string) Option {
|
||||
return func(opts *window.Options) {
|
||||
opts.Title = t
|
||||
}
|
||||
}
|
||||
|
||||
// Size sets the size of the window.
|
||||
func Size(w, h unit.Value) Option {
|
||||
if w.V <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h.V <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(opts *window.Options) {
|
||||
opts.Width = w
|
||||
opts.Height = h
|
||||
}
|
||||
}
|
||||
|
||||
// MaxSize sets the maximum size of the window.
|
||||
func MaxSize(w, h unit.Value) Option {
|
||||
if w.V <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h.V <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(opts *window.Options) {
|
||||
opts.MaxWidth = w
|
||||
opts.MaxHeight = h
|
||||
}
|
||||
}
|
||||
|
||||
// MinSize sets the minimum size of the window.
|
||||
func MinSize(w, h unit.Value) Option {
|
||||
if w.V <= 0 {
|
||||
panic("width must be larger than or equal to 0")
|
||||
}
|
||||
if h.V <= 0 {
|
||||
panic("height must be larger than or equal to 0")
|
||||
}
|
||||
return func(opts *window.Options) {
|
||||
opts.MinWidth = w
|
||||
opts.MinHeight = h
|
||||
}
|
||||
}
|
||||
|
||||
func (driverEvent) ImplementsEvent() {}
|
@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package f32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Affine2D represents an affine 2D transformation. The zero value if Affine2D
|
||||
// represents the identity transform.
|
||||
type Affine2D struct {
|
||||
// in order to make the zero value of Affine2D represent the identity
|
||||
// transform we store it with the identity matrix subtracted, that is
|
||||
// if the actual transformaiton matrix is:
|
||||
// [sx, hx, ox]
|
||||
// [hy, sy, oy]
|
||||
// [ 0, 0, 1]
|
||||
// we store a = sx-1 and e = sy-1
|
||||
a, b, c float32
|
||||
d, e, f float32
|
||||
}
|
||||
|
||||
// NewAffine2D creates a new Affine2D transform from the matrix elements
|
||||
// in row major order. The rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
|
||||
func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
|
||||
return Affine2D{
|
||||
a: sx, b: hx, c: ox,
|
||||
d: hy, e: sy, f: oy,
|
||||
}.encode()
|
||||
}
|
||||
|
||||
// Offset the transformation.
|
||||
func (a Affine2D) Offset(offset Point) Affine2D {
|
||||
return Affine2D{
|
||||
a.a, a.b, a.c + offset.X,
|
||||
a.d, a.e, a.f + offset.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Scale the transformation around the given origin.
|
||||
func (a Affine2D) Scale(origin, factor Point) Affine2D {
|
||||
if origin == (Point{}) {
|
||||
return a.scale(factor)
|
||||
}
|
||||
a = a.Offset(origin.Mul(-1))
|
||||
a = a.scale(factor)
|
||||
return a.Offset(origin)
|
||||
}
|
||||
|
||||
// Rotate the transformation by the given angle (in radians) counter clockwise around the given origin.
|
||||
func (a Affine2D) Rotate(origin Point, radians float32) Affine2D {
|
||||
if origin == (Point{}) {
|
||||
return a.rotate(radians)
|
||||
}
|
||||
a = a.Offset(origin.Mul(-1))
|
||||
a = a.rotate(radians)
|
||||
return a.Offset(origin)
|
||||
}
|
||||
|
||||
// Shear the transformation by the given angle (in radians) around the given origin.
|
||||
func (a Affine2D) Shear(origin Point, radiansX, radiansY float32) Affine2D {
|
||||
if origin == (Point{}) {
|
||||
return a.shear(radiansX, radiansY)
|
||||
}
|
||||
a = a.Offset(origin.Mul(-1))
|
||||
a = a.shear(radiansX, radiansY)
|
||||
return a.Offset(origin)
|
||||
}
|
||||
|
||||
// Mul returns A*B.
|
||||
func (A Affine2D) Mul(B Affine2D) (r Affine2D) {
|
||||
A, B = A.decode(), B.decode()
|
||||
r.a = A.a*B.a + A.b*B.d
|
||||
r.b = A.a*B.b + A.b*B.e
|
||||
r.c = A.a*B.c + A.b*B.f + A.c
|
||||
r.d = A.d*B.a + A.e*B.d
|
||||
r.e = A.d*B.b + A.e*B.e
|
||||
r.f = A.d*B.c + A.e*B.f + A.f
|
||||
return r.encode()
|
||||
}
|
||||
|
||||
// Invert the transformation. Note that if the matrix is close to singular
|
||||
// numerical errors may become large or infinity.
|
||||
func (a Affine2D) Invert() Affine2D {
|
||||
if a.a == 0 && a.b == 0 && a.d == 0 && a.e == 0 {
|
||||
return Affine2D{a: 0, b: 0, c: -a.c, d: 0, e: 0, f: -a.f}
|
||||
}
|
||||
a = a.decode()
|
||||
det := a.a*a.e - a.b*a.d
|
||||
a.a, a.e = a.e/det, a.a/det
|
||||
a.b, a.d = -a.b/det, -a.d/det
|
||||
temp := a.c
|
||||
a.c = -a.a*a.c - a.b*a.f
|
||||
a.f = -a.d*temp - a.e*a.f
|
||||
return a.encode()
|
||||
}
|
||||
|
||||
// Transform p by returning a*p.
|
||||
func (a Affine2D) Transform(p Point) Point {
|
||||
a = a.decode()
|
||||
return Point{
|
||||
X: p.X*a.a + p.Y*a.b + a.c,
|
||||
Y: p.X*a.d + p.Y*a.e + a.f,
|
||||
}
|
||||
}
|
||||
|
||||
// Elems returns the matrix elements of the transform in row-major order. The
|
||||
// rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
|
||||
func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
|
||||
a = a.decode()
|
||||
return a.a, a.b, a.c, a.d, a.e, a.f
|
||||
}
|
||||
|
||||
func (a Affine2D) encode() Affine2D {
|
||||
// since we store with identity matrix subtracted
|
||||
a.a -= 1
|
||||
a.e -= 1
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Affine2D) decode() Affine2D {
|
||||
// since we store with identity matrix subtracted
|
||||
a.a += 1
|
||||
a.e += 1
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Affine2D) scale(factor Point) Affine2D {
|
||||
a = a.decode()
|
||||
return Affine2D{
|
||||
a.a * factor.X, a.b * factor.X, a.c * factor.X,
|
||||
a.d * factor.Y, a.e * factor.Y, a.f * factor.Y,
|
||||
}.encode()
|
||||
}
|
||||
|
||||
func (a Affine2D) rotate(radians float32) Affine2D {
|
||||
sin, cos := math.Sincos(float64(radians))
|
||||
s, c := float32(sin), float32(cos)
|
||||
a = a.decode()
|
||||
return Affine2D{
|
||||
a.a*c - a.d*s, a.b*c - a.e*s, a.c*c - a.f*s,
|
||||
a.a*s + a.d*c, a.b*s + a.e*c, a.c*s + a.f*c,
|
||||
}.encode()
|
||||
}
|
||||
|
||||
func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
|
||||
tx := float32(math.Tan(float64(radiansX)))
|
||||
ty := float32(math.Tan(float64(radiansY)))
|
||||
a = a.decode()
|
||||
return Affine2D{
|
||||
a.a + a.d*tx, a.b + a.e*tx, a.c + a.f*tx,
|
||||
a.a*ty + a.d, a.b*ty + a.e, a.f*ty + a.f,
|
||||
}.encode()
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package f32 is a float32 implementation of package image's
|
||||
Point and Rectangle.
|
||||
|
||||
The coordinate space has the origin in the top left
|
||||
corner with the axes extending right and down.
|
||||
*/
|
||||
package f32
|
||||
|
||||
import "strconv"
|
||||
|
||||
// A Point is a two dimensional point.
|
||||
type Point struct {
|
||||
X, Y float32
|
||||
}
|
||||
|
||||
// String return a string representation of p.
|
||||
func (p Point) String() string {
|
||||
return "(" + strconv.FormatFloat(float64(p.X), 'f', -1, 32) +
|
||||
"," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")"
|
||||
}
|
||||
|
||||
// A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
|
||||
// Min.Y <= Y < Max.Y.
|
||||
type Rectangle struct {
|
||||
Min, Max Point
|
||||
}
|
||||
|
||||
// String return a string representation of r.
|
||||
func (r Rectangle) String() string {
|
||||
return r.Min.String() + "-" + r.Max.String()
|
||||
}
|
||||
|
||||
// Rect is a shorthand for Rectangle{Point{x0, y0}, Point{x1, y1}}.
|
||||
// The returned Rectangle has x0 and y0 swapped if necessary so that
|
||||
// it's correctly formed.
|
||||
func Rect(x0, y0, x1, y1 float32) Rectangle {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
return Rectangle{Point{x0, y0}, Point{x1, y1}}
|
||||
}
|
||||
|
||||
// Pt is shorthand for Point{X: x, Y: y}.
|
||||
func Pt(x, y float32) Point {
|
||||
return Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
// Add return the point p+p2.
|
||||
func (p Point) Add(p2 Point) Point {
|
||||
return Point{X: p.X + p2.X, Y: p.Y + p2.Y}
|
||||
}
|
||||
|
||||
// Sub returns the vector p-p2.
|
||||
func (p Point) Sub(p2 Point) Point {
|
||||
return Point{X: p.X - p2.X, Y: p.Y - p2.Y}
|
||||
}
|
||||
|
||||
// Mul returns p scaled by s.
|
||||
func (p Point) Mul(s float32) Point {
|
||||
return Point{X: p.X * s, Y: p.Y * s}
|
||||
}
|
||||
|
||||
// Size returns r's width and height.
|
||||
func (r Rectangle) Size() Point {
|
||||
return Point{X: r.Dx(), Y: r.Dy()}
|
||||
}
|
||||
|
||||
// Dx returns r's width.
|
||||
func (r Rectangle) Dx() float32 {
|
||||
return r.Max.X - r.Min.X
|
||||
}
|
||||
|
||||
// Dy returns r's Height.
|
||||
func (r Rectangle) Dy() float32 {
|
||||
return r.Max.Y - r.Min.Y
|
||||
}
|
||||
|
||||
// Intersect returns the intersection of r and s.
|
||||
func (r Rectangle) Intersect(s Rectangle) Rectangle {
|
||||
if r.Min.X < s.Min.X {
|
||||
r.Min.X = s.Min.X
|
||||
}
|
||||
if r.Min.Y < s.Min.Y {
|
||||
r.Min.Y = s.Min.Y
|
||||
}
|
||||
if r.Max.X > s.Max.X {
|
||||
r.Max.X = s.Max.X
|
||||
}
|
||||
if r.Max.Y > s.Max.Y {
|
||||
r.Max.Y = s.Max.Y
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Union returns the union of r and s.
|
||||
func (r Rectangle) Union(s Rectangle) Rectangle {
|
||||
if r.Min.X > s.Min.X {
|
||||
r.Min.X = s.Min.X
|
||||
}
|
||||
if r.Min.Y > s.Min.Y {
|
||||
r.Min.Y = s.Min.Y
|
||||
}
|
||||
if r.Max.X < s.Max.X {
|
||||
r.Max.X = s.Max.X
|
||||
}
|
||||
if r.Max.Y < s.Max.Y {
|
||||
r.Max.Y = s.Max.Y
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Canon returns the canonical version of r, where Min is to
|
||||
// the upper left of Max.
|
||||
func (r Rectangle) Canon() Rectangle {
|
||||
if r.Max.X < r.Min.X {
|
||||
r.Min.X, r.Max.X = r.Max.X, r.Min.X
|
||||
}
|
||||
if r.Max.Y < r.Min.Y {
|
||||
r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Empty reports whether r represents the empty area.
|
||||
func (r Rectangle) Empty() bool {
|
||||
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
|
||||
}
|
||||
|
||||
// Add offsets r with the vector p.
|
||||
func (r Rectangle) Add(p Point) Rectangle {
|
||||
return Rectangle{
|
||||
Point{r.Min.X + p.X, r.Min.Y + p.Y},
|
||||
Point{r.Max.X + p.X, r.Max.Y + p.Y},
|
||||
}
|
||||
}
|
||||
|
||||
// Sub offsets r with the vector -p.
|
||||
func (r Rectangle) Sub(p Point) Rectangle {
|
||||
return Rectangle{
|
||||
Point{r.Min.X - p.X, r.Min.Y - p.Y},
|
||||
Point{r.Max.X - p.X, r.Max.Y - p.Y},
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package opentype implements text layout and shaping for OpenType
|
||||
// files.
|
||||
package opentype
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/text"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// Font implements text.Face.
|
||||
type Font struct {
|
||||
font *sfnt.Font
|
||||
buf sfnt.Buffer
|
||||
}
|
||||
|
||||
// Collection is a collection of one or more fonts.
|
||||
type Collection struct {
|
||||
coll *sfnt.Collection
|
||||
}
|
||||
|
||||
type opentype struct {
|
||||
Font *sfnt.Font
|
||||
Hinting font.Hinting
|
||||
}
|
||||
|
||||
// NewFont parses an SFNT font, such as TTF or OTF data, from a []byte
|
||||
// data source.
|
||||
func Parse(src []byte) (*Font, error) {
|
||||
fnt, err := sfnt.Parse(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Font{font: fnt}, nil
|
||||
}
|
||||
|
||||
// ParseCollection parses an SFNT font collection, such as TTC or OTC data,
|
||||
// from a []byte data source.
|
||||
//
|
||||
// If passed data for a single font, a TTF or OTF instead of a TTC or OTC,
|
||||
// it will return a collection containing 1 font.
|
||||
func ParseCollection(src []byte) (*Collection, error) {
|
||||
c, err := sfnt.ParseCollection(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Collection{c}, nil
|
||||
}
|
||||
|
||||
// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
|
||||
// from an io.ReaderAt data source.
|
||||
//
|
||||
// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
|
||||
// will return a collection containing 1 font.
|
||||
func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
|
||||
c, err := sfnt.ParseCollectionReaderAt(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Collection{c}, nil
|
||||
}
|
||||
|
||||
// NumFonts returns the number of fonts in the collection.
|
||||
func (c *Collection) NumFonts() int {
|
||||
return c.coll.NumFonts()
|
||||
}
|
||||
|
||||
// Font returns the i'th font in the collection.
|
||||
func (c *Collection) Font(i int) (*Font, error) {
|
||||
fnt, err := c.coll.Font(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Font{font: fnt}, nil
|
||||
}
|
||||
|
||||
func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
|
||||
glyphs, err := readGlyphs(txt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return layoutText(&f.buf, ppem, maxWidth, &opentype{Font: f.font, Hinting: font.HintingFull}, glyphs)
|
||||
}
|
||||
|
||||
func (f *Font) Shape(ppem fixed.Int26_6, str []text.Glyph) op.CallOp {
|
||||
return textPath(&f.buf, ppem, &opentype{Font: f.font, Hinting: font.HintingFull}, str)
|
||||
}
|
||||
|
||||
func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
|
||||
o := &opentype{Font: f.font, Hinting: font.HintingFull}
|
||||
return o.Metrics(&f.buf, ppem)
|
||||
}
|
||||
|
||||
func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, f *opentype, glyphs []text.Glyph) ([]text.Line, error) {
|
||||
m := f.Metrics(sbuf, ppem)
|
||||
lineTmpl := text.Line{
|
||||
Ascent: m.Ascent,
|
||||
// m.Height is equal to m.Ascent + m.Descent + linegap.
|
||||
// Compute the descent including the linegap.
|
||||
Descent: m.Height - m.Ascent,
|
||||
Bounds: f.Bounds(sbuf, ppem),
|
||||
}
|
||||
var lines []text.Line
|
||||
maxDotX := fixed.I(maxWidth)
|
||||
type state struct {
|
||||
r rune
|
||||
adv fixed.Int26_6
|
||||
x fixed.Int26_6
|
||||
idx int
|
||||
len int
|
||||
valid bool
|
||||
}
|
||||
var prev, word state
|
||||
endLine := func() {
|
||||
line := lineTmpl
|
||||
line.Layout = glyphs[:prev.idx:prev.idx]
|
||||
line.Len = prev.len
|
||||
line.Width = prev.x + prev.adv
|
||||
line.Bounds.Max.X += prev.x
|
||||
lines = append(lines, line)
|
||||
glyphs = glyphs[prev.idx:]
|
||||
prev = state{}
|
||||
word = state{}
|
||||
}
|
||||
for prev.idx < len(glyphs) {
|
||||
g := &glyphs[prev.idx]
|
||||
a, valid := f.GlyphAdvance(sbuf, ppem, g.Rune)
|
||||
next := state{
|
||||
r: g.Rune,
|
||||
idx: prev.idx + 1,
|
||||
len: prev.len + utf8.RuneLen(g.Rune),
|
||||
x: prev.x + prev.adv,
|
||||
adv: a,
|
||||
valid: valid,
|
||||
}
|
||||
if g.Rune == '\n' {
|
||||
// The newline is zero width; use the previous
|
||||
// character for line measurements.
|
||||
prev.idx = next.idx
|
||||
prev.len = next.len
|
||||
endLine()
|
||||
continue
|
||||
}
|
||||
var k fixed.Int26_6
|
||||
if prev.valid {
|
||||
k = f.Kern(sbuf, ppem, prev.r, next.r)
|
||||
}
|
||||
// Break the line if we're out of space.
|
||||
if prev.idx > 0 && next.x+next.adv+k > maxDotX {
|
||||
// If the line contains no word breaks, break off the last rune.
|
||||
if word.idx == 0 {
|
||||
word = prev
|
||||
}
|
||||
next.x -= word.x + word.adv
|
||||
next.idx -= word.idx
|
||||
next.len -= word.len
|
||||
prev = word
|
||||
endLine()
|
||||
} else if k != 0 {
|
||||
glyphs[prev.idx-1].Advance += k
|
||||
next.x += k
|
||||
}
|
||||
g.Advance = next.adv
|
||||
if unicode.IsSpace(g.Rune) {
|
||||
word = next
|
||||
}
|
||||
prev = next
|
||||
}
|
||||
endLine()
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, f *opentype, str []text.Glyph) op.CallOp {
|
||||
var lastPos f32.Point
|
||||
var builder clip.Path
|
||||
ops := new(op.Ops)
|
||||
m := op.Record(ops)
|
||||
var x fixed.Int26_6
|
||||
builder.Begin(ops)
|
||||
for _, g := range str {
|
||||
if !unicode.IsSpace(g.Rune) {
|
||||
segs, ok := f.LoadGlyph(buf, ppem, g.Rune)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Move to glyph position.
|
||||
pos := f32.Point{
|
||||
X: float32(x) / 64,
|
||||
}
|
||||
builder.Move(pos.Sub(lastPos))
|
||||
lastPos = pos
|
||||
var lastArg f32.Point
|
||||
// Convert sfnt.Segments to relative segments.
|
||||
for _, fseg := range segs {
|
||||
nargs := 1
|
||||
switch fseg.Op {
|
||||
case sfnt.SegmentOpQuadTo:
|
||||
nargs = 2
|
||||
case sfnt.SegmentOpCubeTo:
|
||||
nargs = 3
|
||||
}
|
||||
var args [3]f32.Point
|
||||
for i := 0; i < nargs; i++ {
|
||||
a := f32.Point{
|
||||
X: float32(fseg.Args[i].X) / 64,
|
||||
Y: float32(fseg.Args[i].Y) / 64,
|
||||
}
|
||||
args[i] = a.Sub(lastArg)
|
||||
if i == nargs-1 {
|
||||
lastArg = a
|
||||
}
|
||||
}
|
||||
switch fseg.Op {
|
||||
case sfnt.SegmentOpMoveTo:
|
||||
builder.Move(args[0])
|
||||
case sfnt.SegmentOpLineTo:
|
||||
builder.Line(args[0])
|
||||
case sfnt.SegmentOpQuadTo:
|
||||
builder.Quad(args[0], args[1])
|
||||
case sfnt.SegmentOpCubeTo:
|
||||
builder.Cube(args[0], args[1], args[2])
|
||||
default:
|
||||
panic("unsupported segment op")
|
||||
}
|
||||
}
|
||||
lastPos = lastPos.Add(lastArg)
|
||||
}
|
||||
x += g.Advance
|
||||
}
|
||||
builder.End().Add(ops)
|
||||
return m.Stop()
|
||||
}
|
||||
|
||||
func readGlyphs(r io.Reader) ([]text.Glyph, error) {
|
||||
var glyphs []text.Glyph
|
||||
buf := make([]byte, 0, 1024)
|
||||
for {
|
||||
n, err := r.Read(buf[len(buf):cap(buf)])
|
||||
buf = buf[:len(buf)+n]
|
||||
lim := len(buf)
|
||||
// Read full runes if possible.
|
||||
if err != io.EOF {
|
||||
lim -= utf8.UTFMax - 1
|
||||
}
|
||||
i := 0
|
||||
for i < lim {
|
||||
c, s := utf8.DecodeRune(buf[i:])
|
||||
i += s
|
||||
glyphs = append(glyphs, text.Glyph{Rune: c})
|
||||
}
|
||||
n = copy(buf, buf[i:])
|
||||
buf = buf[:n]
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return glyphs, nil
|
||||
}
|
||||
|
||||
func (f *opentype) GlyphAdvance(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
|
||||
g, err := f.Font.GlyphIndex(buf, r)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
adv, err := f.Font.GlyphAdvance(buf, g, ppem, f.Hinting)
|
||||
return adv, err == nil
|
||||
}
|
||||
|
||||
func (f *opentype) Kern(buf *sfnt.Buffer, ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 {
|
||||
g0, err := f.Font.GlyphIndex(buf, r0)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
g1, err := f.Font.GlyphIndex(buf, r1)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
adv, err := f.Font.Kern(buf, g0, g1, ppem, f.Hinting)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return adv
|
||||
}
|
||||
|
||||
func (f *opentype) Metrics(buf *sfnt.Buffer, ppem fixed.Int26_6) font.Metrics {
|
||||
m, _ := f.Font.Metrics(buf, ppem, f.Hinting)
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *opentype) Bounds(buf *sfnt.Buffer, ppem fixed.Int26_6) fixed.Rectangle26_6 {
|
||||
r, _ := f.Font.Bounds(buf, ppem, f.Hinting)
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *opentype) LoadGlyph(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) {
|
||||
g, err := f.Font.GlyphIndex(buf, r)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
segs, err := f.Font.LoadGlyph(buf, g, ppem, nil)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return segs, true
|
||||
}
|
@ -0,0 +1,408 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package gesture implements common pointer gestures.
|
||||
|
||||
Gestures accept low level pointer Events from an event
|
||||
Queue and detect higher level actions such as clicks
|
||||
and scrolling.
|
||||
*/
|
||||
package gesture
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
// The duration is somewhat arbitrary.
|
||||
const doubleClickDuration = 200 * time.Millisecond
|
||||
|
||||
// Click detects click gestures in the form
|
||||
// of ClickEvents.
|
||||
type Click struct {
|
||||
// state tracks the gesture state.
|
||||
state ClickState
|
||||
// clickedAt is the timestamp at which
|
||||
// the last click occurred.
|
||||
clickedAt time.Duration
|
||||
// clicks is incremented if successive clicks
|
||||
// are performed within a fixed duration.
|
||||
clicks int
|
||||
// pressed tracks whether the pointer is pressed.
|
||||
pressed bool
|
||||
// entered tracks whether the pointer is inside the gesture.
|
||||
entered bool
|
||||
// pid is the pointer.ID.
|
||||
pid pointer.ID
|
||||
}
|
||||
|
||||
type ClickState uint8
|
||||
|
||||
// ClickEvent represent a click action, either a
|
||||
// TypePress for the beginning of a click or a
|
||||
// TypeClick for a completed click.
|
||||
type ClickEvent struct {
|
||||
Type ClickType
|
||||
Position f32.Point
|
||||
Source pointer.Source
|
||||
Modifiers key.Modifiers
|
||||
// NumClicks records successive clicks occurring
|
||||
// within a short duration of each other.
|
||||
NumClicks int
|
||||
}
|
||||
|
||||
type ClickType uint8
|
||||
|
||||
// Drag detects drag gestures in the form of pointer.Drag events.
|
||||
type Drag struct {
|
||||
dragging bool
|
||||
pid pointer.ID
|
||||
start f32.Point
|
||||
grab bool
|
||||
}
|
||||
|
||||
// Scroll detects scroll gestures and reduces them to
|
||||
// scroll distances. Scroll recognizes mouse wheel
|
||||
// movements as well as drag and fling touch gestures.
|
||||
type Scroll struct {
|
||||
dragging bool
|
||||
axis Axis
|
||||
estimator fling.Extrapolation
|
||||
flinger fling.Animation
|
||||
pid pointer.ID
|
||||
grab bool
|
||||
last int
|
||||
// Leftover scroll.
|
||||
scroll float32
|
||||
}
|
||||
|
||||
type ScrollState uint8
|
||||
|
||||
type Axis uint8
|
||||
|
||||
const (
|
||||
Horizontal Axis = iota
|
||||
Vertical
|
||||
)
|
||||
|
||||
const (
|
||||
// TypePress is reported for the first pointer
|
||||
// press.
|
||||
TypePress ClickType = iota
|
||||
// TypeClick is reported when a click action
|
||||
// is complete.
|
||||
TypeClick
|
||||
// TypeCancel is reported when the gesture is
|
||||
// cancelled.
|
||||
TypeCancel
|
||||
)
|
||||
|
||||
const (
|
||||
// StateIdle is the default scroll state.
|
||||
StateIdle ScrollState = iota
|
||||
// StateDrag is reported during drag gestures.
|
||||
StateDragging
|
||||
// StateFlinging is reported when a fling is
|
||||
// in progress.
|
||||
StateFlinging
|
||||
)
|
||||
|
||||
var touchSlop = unit.Dp(3)
|
||||
|
||||
// Add the handler to the operation list to receive click events.
|
||||
func (c *Click) Add(ops *op.Ops) {
|
||||
op := pointer.InputOp{
|
||||
Tag: c,
|
||||
Types: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
|
||||
}
|
||||
op.Add(ops)
|
||||
}
|
||||
|
||||
// Events returns the next click event, if any.
|
||||
func (c *Click) Events(q event.Queue) []ClickEvent {
|
||||
var events []ClickEvent
|
||||
for _, evt := range q.Events(c) {
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Type {
|
||||
case pointer.Release:
|
||||
if !c.pressed || c.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
c.pressed = false
|
||||
if c.entered {
|
||||
if e.Time-c.clickedAt < doubleClickDuration {
|
||||
c.clicks++
|
||||
} else {
|
||||
c.clicks = 1
|
||||
}
|
||||
c.clickedAt = e.Time
|
||||
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
|
||||
} else {
|
||||
events = append(events, ClickEvent{Type: TypeCancel})
|
||||
}
|
||||
case pointer.Cancel:
|
||||
wasPressed := c.pressed
|
||||
c.pressed = false
|
||||
c.entered = false
|
||||
if wasPressed {
|
||||
events = append(events, ClickEvent{Type: TypeCancel})
|
||||
}
|
||||
case pointer.Press:
|
||||
if c.pressed {
|
||||
break
|
||||
}
|
||||
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonLeft {
|
||||
break
|
||||
}
|
||||
if !c.entered {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
c.pressed = true
|
||||
events = append(events, ClickEvent{Type: TypePress, Position: e.Position, Source: e.Source, Modifiers: e.Modifiers})
|
||||
case pointer.Leave:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid == e.PointerID {
|
||||
c.entered = false
|
||||
}
|
||||
case pointer.Enter:
|
||||
if !c.pressed {
|
||||
c.pid = e.PointerID
|
||||
}
|
||||
if c.pid == e.PointerID {
|
||||
c.entered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// Add the handler to the operation list to receive scroll events.
|
||||
func (s *Scroll) Add(ops *op.Ops) {
|
||||
oph := pointer.InputOp{
|
||||
Tag: s,
|
||||
Grab: s.grab,
|
||||
Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
|
||||
}
|
||||
oph.Add(ops)
|
||||
if s.flinger.Active() {
|
||||
op.InvalidateOp{}.Add(ops)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop any remaining fling movement.
|
||||
func (s *Scroll) Stop() {
|
||||
s.flinger = fling.Animation{}
|
||||
}
|
||||
|
||||
// Scroll detects the scrolling distance from the available events and
|
||||
// ongoing fling gestures.
|
||||
func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis) int {
|
||||
if s.axis != axis {
|
||||
s.axis = axis
|
||||
return 0
|
||||
}
|
||||
total := 0
|
||||
for _, evt := range q.Events(s) {
|
||||
e, ok := evt.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch e.Type {
|
||||
case pointer.Press:
|
||||
if s.dragging {
|
||||
break
|
||||
}
|
||||
// Only scroll on touch drags, or on Android where mice
|
||||
// drags also scroll by convention.
|
||||
if e.Source != pointer.Touch && runtime.GOOS != "android" {
|
||||
break
|
||||
}
|
||||
s.Stop()
|
||||
s.estimator = fling.Extrapolation{}
|
||||
v := s.val(e.Position)
|
||||
s.last = int(math.Round(float64(v)))
|
||||
s.estimator.Sample(e.Time, v)
|
||||
s.dragging = true
|
||||
s.pid = e.PointerID
|
||||
case pointer.Release:
|
||||
if s.pid != e.PointerID {
|
||||
break
|
||||
}
|
||||
fling := s.estimator.Estimate()
|
||||
if slop, d := float32(cfg.Px(touchSlop)), fling.Distance; d < -slop || d > slop {
|
||||
s.flinger.Start(cfg, t, fling.Velocity)
|
||||
}
|
||||
fallthrough
|
||||
case pointer.Cancel:
|
||||
s.dragging = false
|
||||
s.grab = false
|
||||
case pointer.Scroll:
|
||||
if e.Priority < pointer.Foremost {
|
||||
continue
|
||||
}
|
||||
switch s.axis {
|
||||
case Horizontal:
|
||||
s.scroll += e.Scroll.X
|
||||
case Vertical:
|
||||
s.scroll += e.Scroll.Y
|
||||
}
|
||||
iscroll := int(s.scroll)
|
||||
s.scroll -= float32(iscroll)
|
||||
total += iscroll
|
||||
case pointer.Drag:
|
||||
if !s.dragging || s.pid != e.PointerID {
|
||||
continue
|
||||
}
|
||||
val := s.val(e.Position)
|
||||
s.estimator.Sample(e.Time, val)
|
||||
v := int(math.Round(float64(val)))
|
||||
dist := s.last - v
|
||||
if e.Priority < pointer.Grabbed {
|
||||
slop := cfg.Px(touchSlop)
|
||||
if dist := dist; dist >= slop || -slop >= dist {
|
||||
s.grab = true
|
||||
}
|
||||
} else {
|
||||
s.last = v
|
||||
total += dist
|
||||
}
|
||||
}
|
||||
}
|
||||
total += s.flinger.Tick(t)
|
||||
return total
|
||||
}
|
||||
|
||||
func (s *Scroll) val(p f32.Point) float32 {
|
||||
if s.axis == Horizontal {
|
||||
return p.X
|
||||
} else {
|
||||
return p.Y
|
||||
}
|
||||
}
|
||||
|
||||
// State reports the scroll state.
|
||||
func (s *Scroll) State() ScrollState {
|
||||
switch {
|
||||
case s.flinger.Active():
|
||||
return StateFlinging
|
||||
case s.dragging:
|
||||
return StateDragging
|
||||
default:
|
||||
return StateIdle
|
||||
}
|
||||
}
|
||||
|
||||
// Add the handler to the operation list to receive drag events.
|
||||
func (d *Drag) Add(ops *op.Ops) {
|
||||
op := pointer.InputOp{
|
||||
Tag: d,
|
||||
Grab: d.grab,
|
||||
Types: pointer.Press | pointer.Drag | pointer.Release,
|
||||
}
|
||||
op.Add(ops)
|
||||
}
|
||||
|
||||
// Events returns the next drag events, if any.
|
||||
func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
|
||||
var events []pointer.Event
|
||||
for _, e := range q.Events(d) {
|
||||
e, ok := e.(pointer.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch e.Type {
|
||||
case pointer.Press:
|
||||
if !(e.Buttons == pointer.ButtonLeft || e.Source == pointer.Touch) {
|
||||
continue
|
||||
}
|
||||
if d.dragging {
|
||||
continue
|
||||
}
|
||||
d.dragging = true
|
||||
d.pid = e.PointerID
|
||||
d.start = e.Position
|
||||
case pointer.Drag:
|
||||
if !d.dragging || e.PointerID != d.pid {
|
||||
continue
|
||||
}
|
||||
switch axis {
|
||||
case Horizontal:
|
||||
e.Position.Y = d.start.Y
|
||||
case Vertical:
|
||||
e.Position.X = d.start.X
|
||||
}
|
||||
if e.Priority < pointer.Grabbed {
|
||||
diff := e.Position.Sub(d.start)
|
||||
slop := cfg.Px(touchSlop)
|
||||
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
|
||||
d.grab = true
|
||||
}
|
||||
}
|
||||
case pointer.Release, pointer.Cancel:
|
||||
if !d.dragging || e.PointerID != d.pid {
|
||||
continue
|
||||
}
|
||||
d.dragging = false
|
||||
d.grab = false
|
||||
}
|
||||
|
||||
events = append(events, e)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
func (a Axis) String() string {
|
||||
switch a {
|
||||
case Horizontal:
|
||||
return "Horizontal"
|
||||
case Vertical:
|
||||
return "Vertical"
|
||||
default:
|
||||
panic("invalid Axis")
|
||||
}
|
||||
}
|
||||
|
||||
func (ct ClickType) String() string {
|
||||
switch ct {
|
||||
case TypePress:
|
||||
return "TypePress"
|
||||
case TypeClick:
|
||||
return "TypeClick"
|
||||
case TypeCancel:
|
||||
return "TypeCancel"
|
||||
default:
|
||||
panic("invalid ClickType")
|
||||
}
|
||||
}
|
||||
|
||||
func (s ScrollState) String() string {
|
||||
switch s {
|
||||
case StateIdle:
|
||||
return "StateIdle"
|
||||
case StateDragging:
|
||||
return "StateDragging"
|
||||
case StateFlinging:
|
||||
return "StateFlinging"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Device represents the abstraction of underlying GPU
|
||||
// APIs such as OpenGL, Direct3D useful for rendering Gio
|
||||
// operations.
|
||||
type Device interface {
|
||||
BeginFrame()
|
||||
EndFrame()
|
||||
Caps() Caps
|
||||
NewTimer() Timer
|
||||
// IsContinuousTime reports whether all timer measurements
|
||||
// are valid at the point of call.
|
||||
IsTimeContinuous() bool
|
||||
NewTexture(format TextureFormat, width, height int, minFilter, magFilter TextureFilter, bindings BufferBinding) (Texture, error)
|
||||
CurrentFramebuffer() Framebuffer
|
||||
NewFramebuffer(tex Texture, depthBits int) (Framebuffer, error)
|
||||
NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error)
|
||||
NewBuffer(typ BufferBinding, size int) (Buffer, error)
|
||||
NewProgram(vertexShader, fragmentShader ShaderSources) (Program, error)
|
||||
NewInputLayout(vertexShader ShaderSources, layout []InputDesc) (InputLayout, error)
|
||||
|
||||
DepthFunc(f DepthFunc)
|
||||
ClearDepth(d float32)
|
||||
Clear(r, g, b, a float32)
|
||||
Viewport(x, y, width, height int)
|
||||
DrawArrays(mode DrawMode, off, count int)
|
||||
DrawElements(mode DrawMode, off, count int)
|
||||
SetBlend(enable bool)
|
||||
SetDepthTest(enable bool)
|
||||
DepthMask(mask bool)
|
||||
BlendFunc(sfactor, dfactor BlendFactor)
|
||||
|
||||
BindInputLayout(i InputLayout)
|
||||
BindProgram(p Program)
|
||||
BindFramebuffer(f Framebuffer)
|
||||
BindTexture(unit int, t Texture)
|
||||
BindVertexBuffer(b Buffer, stride, offset int)
|
||||
BindIndexBuffer(b Buffer)
|
||||
}
|
||||
|
||||
type ShaderSources struct {
|
||||
GLSL100ES string
|
||||
GLSL300ES string
|
||||
GLSL130 string
|
||||
GLSL150 string
|
||||
HLSL []byte
|
||||
Uniforms UniformsReflection
|
||||
Inputs []InputLocation
|
||||
Textures []TextureBinding
|
||||
}
|
||||
|
||||
type UniformsReflection struct {
|
||||
Blocks []UniformBlock
|
||||
Locations []UniformLocation
|
||||
Size int
|
||||
}
|
||||
|
||||
type TextureBinding struct {
|
||||
Name string
|
||||
Binding int
|
||||
}
|
||||
|
||||
type UniformBlock struct {
|
||||
Name string
|
||||
Binding int
|
||||
}
|
||||
|
||||
type UniformLocation struct {
|
||||
Name string
|
||||
Type DataType
|
||||
Size int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type InputLocation struct {
|
||||
// For GLSL.
|
||||
Name string
|
||||
Location int
|
||||
// For HLSL.
|
||||
Semantic string
|
||||
SemanticIndex int
|
||||
|
||||
Type DataType
|
||||
Size int
|
||||
}
|
||||
|
||||
// InputDesc describes a vertex attribute as laid out in a Buffer.
|
||||
type InputDesc struct {
|
||||
Type DataType
|
||||
Size int
|
||||
|
||||
Offset int
|
||||
}
|
||||
|
||||
// InputLayout is the backend specific representation of the mapping
|
||||
// between Buffers and shader attributes.
|
||||
type InputLayout interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type BlendFactor uint8
|
||||
|
||||
type DrawMode uint8
|
||||
|
||||
type TextureFilter uint8
|
||||
type TextureFormat uint8
|
||||
|
||||
type BufferBinding uint8
|
||||
|
||||
type DataType uint8
|
||||
|
||||
type DepthFunc uint8
|
||||
|
||||
type Features uint
|
||||
|
||||
type Caps struct {
|
||||
Features Features
|
||||
MaxTextureSize int
|
||||
}
|
||||
|
||||
type Program interface {
|
||||
Release()
|
||||
SetVertexUniforms(buf Buffer)
|
||||
SetFragmentUniforms(buf Buffer)
|
||||
}
|
||||
|
||||
type Buffer interface {
|
||||
Release()
|
||||
Upload(data []byte)
|
||||
}
|
||||
|
||||
type Framebuffer interface {
|
||||
Invalidate()
|
||||
Release()
|
||||
ReadPixels(src image.Rectangle, pixels []byte) error
|
||||
}
|
||||
|
||||
type Timer interface {
|
||||
Begin()
|
||||
End()
|
||||
Duration() (time.Duration, bool)
|
||||
Release()
|
||||
}
|
||||
|
||||
type Texture interface {
|
||||
Upload(img *image.RGBA)
|
||||
Release()
|
||||
}
|
||||
|
||||
const (
|
||||
DepthFuncGreater DepthFunc = iota
|
||||
DepthFuncGreaterEqual
|
||||
)
|
||||
|
||||
const (
|
||||
DataTypeFloat DataType = iota
|
||||
DataTypeInt
|
||||
DataTypeShort
|
||||
)
|
||||
|
||||
const (
|
||||
BufferBindingIndices BufferBinding = 1 << iota
|
||||
BufferBindingVertices
|
||||
BufferBindingUniforms
|
||||
BufferBindingTexture
|
||||
BufferBindingFramebuffer
|
||||
)
|
||||
|
||||
const (
|
||||
TextureFormatSRGB TextureFormat = iota
|
||||
TextureFormatFloat
|
||||
)
|
||||
|
||||
const (
|
||||
FilterNearest TextureFilter = iota
|
||||
FilterLinear
|
||||
)
|
||||
|
||||
const (
|
||||
FeatureTimers Features = iota
|
||||
)
|
||||
|
||||
const (
|
||||
DrawModeTriangleStrip DrawMode = iota
|
||||
DrawModeTriangles
|
||||
)
|
||||
|
||||
const (
|
||||
BlendFactorOne BlendFactor = iota
|
||||
BlendFactorOneMinusSrcAlpha
|
||||
BlendFactorZero
|
||||
BlendFactorDstColor
|
||||
)
|
||||
|
||||
func (f Features) Has(feats Features) bool {
|
||||
return f&feats == feats
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/ops"
|
||||
)
|
||||
|
||||
type resourceCache struct {
|
||||
res map[interface{}]resource
|
||||
newRes map[interface{}]resource
|
||||
}
|
||||
|
||||
// opCache is like a resourceCache but using concrete types and a
|
||||
// freelist instead of two maps to avoid runtime.mapaccess2 calls
|
||||
// since benchmarking showed them as a bottleneck.
|
||||
type opCache struct {
|
||||
// store the index + 1 in cache this key is stored in
|
||||
index map[ops.Key]int
|
||||
// list of indexes in cache that are free and can be used
|
||||
freelist []int
|
||||
cache []opCacheValue
|
||||
}
|
||||
|
||||
type opCacheValue struct {
|
||||
data pathData
|
||||
bounds f32.Rectangle
|
||||
// the fields below are handled by opCache
|
||||
key ops.Key
|
||||
keep bool
|
||||
}
|
||||
|
||||
func newResourceCache() *resourceCache {
|
||||
return &resourceCache{
|
||||
res: make(map[interface{}]resource),
|
||||
newRes: make(map[interface{}]resource),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resourceCache) get(key interface{}) (resource, bool) {
|
||||
v, exists := r.res[key]
|
||||
if exists {
|
||||
r.newRes[key] = v
|
||||
}
|
||||
return v, exists
|
||||
}
|
||||
|
||||
func (r *resourceCache) put(key interface{}, val resource) {
|
||||
if _, exists := r.newRes[key]; exists {
|
||||
panic(fmt.Errorf("key exists, %p", key))
|
||||
}
|
||||
r.res[key] = val
|
||||
r.newRes[key] = val
|
||||
}
|
||||
|
||||
func (r *resourceCache) frame() {
|
||||
for k, v := range r.res {
|
||||
if _, exists := r.newRes[k]; !exists {
|
||||
delete(r.res, k)
|
||||
v.release()
|
||||
}
|
||||
}
|
||||
for k, v := range r.newRes {
|
||||
delete(r.newRes, k)
|
||||
r.res[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resourceCache) release() {
|
||||
for _, v := range r.newRes {
|
||||
v.release()
|
||||
}
|
||||
r.newRes = nil
|
||||
r.res = nil
|
||||
}
|
||||
|
||||
func newOpCache() *opCache {
|
||||
return &opCache{
|
||||
index: make(map[ops.Key]int),
|
||||
freelist: make([]int, 0),
|
||||
cache: make([]opCacheValue, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *opCache) get(key ops.Key) (o opCacheValue, exist bool) {
|
||||
v := r.index[key]
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
r.cache[v-1].keep = true
|
||||
return r.cache[v-1], true
|
||||
}
|
||||
|
||||
func (r *opCache) put(key ops.Key, val opCacheValue) {
|
||||
v := r.index[key]
|
||||
val.keep = true
|
||||
val.key = key
|
||||
if v == 0 {
|
||||
// not in cache
|
||||
i := len(r.cache)
|
||||
if len(r.freelist) > 0 {
|
||||
i = r.freelist[len(r.freelist)-1]
|
||||
r.freelist = r.freelist[:len(r.freelist)-1]
|
||||
r.cache[i] = val
|
||||
} else {
|
||||
r.cache = append(r.cache, val)
|
||||
}
|
||||
r.index[key] = i + 1
|
||||
} else {
|
||||
r.cache[v-1] = val
|
||||
}
|
||||
}
|
||||
|
||||
func (r *opCache) frame() {
|
||||
r.freelist = r.freelist[:0]
|
||||
for i, v := range r.cache {
|
||||
r.cache[i].keep = false
|
||||
if v.keep {
|
||||
continue
|
||||
}
|
||||
if v.data.data != nil {
|
||||
v.data.release()
|
||||
r.cache[i].data.data = nil
|
||||
}
|
||||
delete(r.index, v.key)
|
||||
r.freelist = append(r.freelist, i)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *opCache) release() {
|
||||
for i := range r.cache {
|
||||
r.cache[i].keep = false
|
||||
}
|
||||
r.frame()
|
||||
r.index = nil
|
||||
r.freelist = nil
|
||||
r.cache = nil
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/ops"
|
||||
)
|
||||
|
||||
type quadSplitter struct {
|
||||
verts []byte
|
||||
bounds f32.Rectangle
|
||||
contour uint32
|
||||
d *drawOps
|
||||
}
|
||||
|
||||
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
|
||||
// NW.
|
||||
encodeVertex(data, meta, -1, 1, from, ctrl, to)
|
||||
// NE.
|
||||
encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
|
||||
// SW.
|
||||
encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
|
||||
// SE.
|
||||
encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
|
||||
}
|
||||
|
||||
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
|
||||
var corner float32
|
||||
if cornerx == 1 {
|
||||
corner += .5
|
||||
}
|
||||
if cornery == 1 {
|
||||
corner += .25
|
||||
}
|
||||
v := vertex{
|
||||
Corner: corner,
|
||||
FromX: from.X,
|
||||
FromY: from.Y,
|
||||
CtrlX: ctrl.X,
|
||||
CtrlY: ctrl.Y,
|
||||
ToX: to.X,
|
||||
ToY: to.Y,
|
||||
}
|
||||
v.encode(data, meta)
|
||||
}
|
||||
|
||||
func (qs *quadSplitter) encodeQuadTo(from, ctrl, to f32.Point) {
|
||||
data := qs.d.writeVertCache(vertStride * 4)
|
||||
encodeQuadTo(data, qs.contour, from, ctrl, to)
|
||||
}
|
||||
|
||||
func (qs *quadSplitter) splitAndEncode(quad ops.Quad) {
|
||||
cbnd := f32.Rectangle{
|
||||
Min: quad.From,
|
||||
Max: quad.To,
|
||||
}.Canon()
|
||||
from, ctrl, to := quad.From, quad.Ctrl, quad.To
|
||||
|
||||
// If the curve contain areas where a vertical line
|
||||
// intersects it twice, split the curve in two x monotone
|
||||
// lower and upper curves. The stencil fragment program
|
||||
// expects only one intersection per curve.
|
||||
|
||||
// Find the t where the derivative in x is 0.
|
||||
v0 := ctrl.Sub(from)
|
||||
v1 := to.Sub(ctrl)
|
||||
d := v0.X - v1.X
|
||||
// t = v0 / d. Split if t is in ]0;1[.
|
||||
if v0.X > 0 && d > v0.X || v0.X < 0 && d < v0.X {
|
||||
t := v0.X / d
|
||||
ctrl0 := from.Mul(1 - t).Add(ctrl.Mul(t))
|
||||
ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
|
||||
mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
|
||||
qs.encodeQuadTo(from, ctrl0, mid)
|
||||
qs.encodeQuadTo(mid, ctrl1, to)
|
||||
if mid.X > cbnd.Max.X {
|
||||
cbnd.Max.X = mid.X
|
||||
}
|
||||
if mid.X < cbnd.Min.X {
|
||||
cbnd.Min.X = mid.X
|
||||
}
|
||||
} else {
|
||||
qs.encodeQuadTo(from, ctrl, to)
|
||||
}
|
||||
// Find the y extremum, if any.
|
||||
d = v0.Y - v1.Y
|
||||
if v0.Y > 0 && d > v0.Y || v0.Y < 0 && d < v0.Y {
|
||||
t := v0.Y / d
|
||||
y := (1-t)*(1-t)*from.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
|
||||
if y > cbnd.Max.Y {
|
||||
cbnd.Max.Y = y
|
||||
}
|
||||
if y < cbnd.Min.Y {
|
||||
cbnd.Min.Y = y
|
||||
}
|
||||
}
|
||||
|
||||
qs.bounds = qs.bounds.Union(cbnd)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
//go:generate go run ../internal/cmd/convertshaders -package gpu
|
@ -0,0 +1,835 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/gpu/backend"
|
||||
)
|
||||
|
||||
// Backend implements backend.Device.
|
||||
type Backend struct {
|
||||
funcs Functions
|
||||
|
||||
state glstate
|
||||
|
||||
glver [2]int
|
||||
gles bool
|
||||
ubo bool
|
||||
feats backend.Caps
|
||||
// floatTriple holds the settings for floating point
|
||||
// textures.
|
||||
floatTriple textureTriple
|
||||
// Single channel alpha textures.
|
||||
alphaTriple textureTriple
|
||||
srgbaTriple textureTriple
|
||||
}
|
||||
|
||||
// State tracking.
|
||||
type glstate struct {
|
||||
// nattr is the current number of enabled vertex arrays.
|
||||
nattr int
|
||||
prog *gpuProgram
|
||||
texUnits [2]*gpuTexture
|
||||
layout *gpuInputLayout
|
||||
buffer bufferBinding
|
||||
}
|
||||
|
||||
type bufferBinding struct {
|
||||
buf *gpuBuffer
|
||||
offset int
|
||||
stride int
|
||||
}
|
||||
|
||||
type gpuTimer struct {
|
||||
funcs Functions
|
||||
obj Query
|
||||
}
|
||||
|
||||
type gpuTexture struct {
|
||||
backend *Backend
|
||||
obj Texture
|
||||
triple textureTriple
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
type gpuFramebuffer struct {
|
||||
backend *Backend
|
||||
obj Framebuffer
|
||||
hasDepth bool
|
||||
depthBuf Renderbuffer
|
||||
foreign bool
|
||||
}
|
||||
|
||||
type gpuBuffer struct {
|
||||
backend *Backend
|
||||
hasBuffer bool
|
||||
obj Buffer
|
||||
typ backend.BufferBinding
|
||||
size int
|
||||
immutable bool
|
||||
version int
|
||||
// For emulation of uniform buffers.
|
||||
data []byte
|
||||
}
|
||||
|
||||
type gpuProgram struct {
|
||||
backend *Backend
|
||||
obj Program
|
||||
nattr int
|
||||
vertUniforms uniformsTracker
|
||||
fragUniforms uniformsTracker
|
||||
}
|
||||
|
||||
type uniformsTracker struct {
|
||||
locs []uniformLocation
|
||||
size int
|
||||
buf *gpuBuffer
|
||||
version int
|
||||
}
|
||||
|
||||
type uniformLocation struct {
|
||||
uniform Uniform
|
||||
offset int
|
||||
typ backend.DataType
|
||||
size int
|
||||
}
|
||||
|
||||
type gpuInputLayout struct {
|
||||
inputs []backend.InputLocation
|
||||
layout []backend.InputDesc
|
||||
}
|
||||
|
||||
// textureTriple holds the type settings for
|
||||
// a TexImage2D call.
|
||||
type textureTriple struct {
|
||||
internalFormat int
|
||||
format Enum
|
||||
typ Enum
|
||||
}
|
||||
|
||||
func NewBackend(f Functions) (*Backend, error) {
|
||||
exts := strings.Split(f.GetString(EXTENSIONS), " ")
|
||||
glVer := f.GetString(VERSION)
|
||||
ver, gles, err := ParseGLVersion(glVer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
floatTriple, err := floatTripleFor(f, ver, exts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srgbaTriple, err := srgbaTripleFor(ver, exts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ubo := ver[0] >= 3 && gles
|
||||
b := &Backend{
|
||||
glver: ver,
|
||||
gles: gles,
|
||||
ubo: ubo,
|
||||
funcs: f,
|
||||
floatTriple: floatTriple,
|
||||
alphaTriple: alphaTripleFor(ver),
|
||||
srgbaTriple: srgbaTriple,
|
||||
}
|
||||
if hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") {
|
||||
b.feats.Features |= backend.FeatureTimers
|
||||
}
|
||||
b.feats.MaxTextureSize = f.GetInteger(MAX_TEXTURE_SIZE)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *Backend) BeginFrame() {
|
||||
// Assume GL state is reset between frames.
|
||||
b.state = glstate{}
|
||||
}
|
||||
|
||||
func (b *Backend) EndFrame() {
|
||||
b.funcs.ActiveTexture(TEXTURE0)
|
||||
}
|
||||
|
||||
func (b *Backend) Caps() backend.Caps {
|
||||
return b.feats
|
||||
}
|
||||
|
||||
func (b *Backend) NewTimer() backend.Timer {
|
||||
return &gpuTimer{
|
||||
funcs: b.funcs,
|
||||
obj: b.funcs.CreateQuery(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) IsTimeContinuous() bool {
|
||||
return b.funcs.GetInteger(GPU_DISJOINT_EXT) == FALSE
|
||||
}
|
||||
|
||||
func (b *Backend) NewFramebuffer(tex backend.Texture, depthBits int) (backend.Framebuffer, error) {
|
||||
glErr(b.funcs)
|
||||
gltex := tex.(*gpuTexture)
|
||||
fb := b.funcs.CreateFramebuffer()
|
||||
fbo := &gpuFramebuffer{backend: b, obj: fb}
|
||||
b.BindFramebuffer(fbo)
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
fbo.Release()
|
||||
return nil, err
|
||||
}
|
||||
b.funcs.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, gltex.obj, 0)
|
||||
if depthBits > 0 {
|
||||
size := Enum(DEPTH_COMPONENT16)
|
||||
switch {
|
||||
case depthBits > 24:
|
||||
size = DEPTH_COMPONENT32F
|
||||
case depthBits > 16:
|
||||
size = DEPTH_COMPONENT24
|
||||
}
|
||||
depthBuf := b.funcs.CreateRenderbuffer()
|
||||
b.funcs.BindRenderbuffer(RENDERBUFFER, depthBuf)
|
||||
b.funcs.RenderbufferStorage(RENDERBUFFER, size, gltex.width, gltex.height)
|
||||
b.funcs.FramebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, depthBuf)
|
||||
fbo.depthBuf = depthBuf
|
||||
fbo.hasDepth = true
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
fbo.Release()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if st := b.funcs.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
|
||||
fbo.Release()
|
||||
return nil, fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError())
|
||||
}
|
||||
return fbo, nil
|
||||
}
|
||||
|
||||
func (b *Backend) CurrentFramebuffer() backend.Framebuffer {
|
||||
fboID := Framebuffer(b.funcs.GetBinding(FRAMEBUFFER_BINDING))
|
||||
return &gpuFramebuffer{backend: b, obj: fboID, foreign: true}
|
||||
}
|
||||
|
||||
func (b *Backend) NewTexture(format backend.TextureFormat, width, height int, minFilter, magFilter backend.TextureFilter, binding backend.BufferBinding) (backend.Texture, error) {
|
||||
glErr(b.funcs)
|
||||
tex := &gpuTexture{backend: b, obj: b.funcs.CreateTexture(), width: width, height: height}
|
||||
switch format {
|
||||
case backend.TextureFormatFloat:
|
||||
tex.triple = b.floatTriple
|
||||
case backend.TextureFormatSRGB:
|
||||
tex.triple = b.srgbaTriple
|
||||
default:
|
||||
return nil, errors.New("unsupported texture format")
|
||||
}
|
||||
b.BindTexture(0, tex)
|
||||
b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, toTexFilter(magFilter))
|
||||
b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, toTexFilter(minFilter))
|
||||
b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE)
|
||||
b.funcs.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE)
|
||||
b.funcs.TexImage2D(TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ, nil)
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
tex.Release()
|
||||
return nil, err
|
||||
}
|
||||
return tex, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewBuffer(typ backend.BufferBinding, size int) (backend.Buffer, error) {
|
||||
glErr(b.funcs)
|
||||
buf := &gpuBuffer{backend: b, typ: typ, size: size}
|
||||
if typ&backend.BufferBindingUniforms != 0 {
|
||||
if typ != backend.BufferBindingUniforms {
|
||||
return nil, errors.New("uniforms buffers cannot be bound as anything else")
|
||||
}
|
||||
if !b.ubo {
|
||||
// GLES 2 doesn't support uniform buffers.
|
||||
buf.data = make([]byte, size)
|
||||
}
|
||||
}
|
||||
if typ&^backend.BufferBindingUniforms != 0 || b.ubo {
|
||||
buf.hasBuffer = true
|
||||
buf.obj = b.funcs.CreateBuffer()
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
buf.Release()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewImmutableBuffer(typ backend.BufferBinding, data []byte) (backend.Buffer, error) {
|
||||
glErr(b.funcs)
|
||||
obj := b.funcs.CreateBuffer()
|
||||
buf := &gpuBuffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
|
||||
buf.Upload(data)
|
||||
buf.immutable = true
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
buf.Release()
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func glErr(f Functions) error {
|
||||
if st := f.GetError(); st != NO_ERROR {
|
||||
return fmt.Errorf("glGetError: %#x", st)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) bindTexture(unit int, t *gpuTexture) {
|
||||
if b.state.texUnits[unit] != t {
|
||||
b.funcs.ActiveTexture(TEXTURE0 + Enum(unit))
|
||||
b.funcs.BindTexture(TEXTURE_2D, t.obj)
|
||||
b.state.texUnits[unit] = t
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) useProgram(p *gpuProgram) {
|
||||
if b.state.prog != p {
|
||||
p.backend.funcs.UseProgram(p.obj)
|
||||
b.state.prog = p
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) enableVertexArrays(n int) {
|
||||
// Enable needed arrays.
|
||||
for i := b.state.nattr; i < n; i++ {
|
||||
b.funcs.EnableVertexAttribArray(Attrib(i))
|
||||
}
|
||||
// Disable extra arrays.
|
||||
for i := n; i < b.state.nattr; i++ {
|
||||
b.funcs.DisableVertexAttribArray(Attrib(i))
|
||||
}
|
||||
b.state.nattr = n
|
||||
}
|
||||
|
||||
func (b *Backend) SetDepthTest(enable bool) {
|
||||
if enable {
|
||||
b.funcs.Enable(DEPTH_TEST)
|
||||
} else {
|
||||
b.funcs.Disable(DEPTH_TEST)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BlendFunc(sfactor, dfactor backend.BlendFactor) {
|
||||
b.funcs.BlendFunc(toGLBlendFactor(sfactor), toGLBlendFactor(dfactor))
|
||||
}
|
||||
|
||||
func toGLBlendFactor(f backend.BlendFactor) Enum {
|
||||
switch f {
|
||||
case backend.BlendFactorOne:
|
||||
return ONE
|
||||
case backend.BlendFactorOneMinusSrcAlpha:
|
||||
return ONE_MINUS_SRC_ALPHA
|
||||
case backend.BlendFactorZero:
|
||||
return ZERO
|
||||
case backend.BlendFactorDstColor:
|
||||
return DST_COLOR
|
||||
default:
|
||||
panic("unsupported blend factor")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) DepthMask(mask bool) {
|
||||
b.funcs.DepthMask(mask)
|
||||
}
|
||||
|
||||
func (b *Backend) SetBlend(enable bool) {
|
||||
if enable {
|
||||
b.funcs.Enable(BLEND)
|
||||
} else {
|
||||
b.funcs.Disable(BLEND)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) DrawElements(mode backend.DrawMode, off, count int) {
|
||||
b.prepareDraw()
|
||||
// off is in 16-bit indices, but DrawElements take a byte offset.
|
||||
byteOff := off * 2
|
||||
b.funcs.DrawElements(toGLDrawMode(mode), count, UNSIGNED_SHORT, byteOff)
|
||||
}
|
||||
|
||||
func (b *Backend) DrawArrays(mode backend.DrawMode, off, count int) {
|
||||
b.prepareDraw()
|
||||
b.funcs.DrawArrays(toGLDrawMode(mode), off, count)
|
||||
}
|
||||
|
||||
func (b *Backend) prepareDraw() {
|
||||
nattr := b.state.prog.nattr
|
||||
b.enableVertexArrays(nattr)
|
||||
if nattr > 0 {
|
||||
b.setupVertexArrays()
|
||||
}
|
||||
if p := b.state.prog; p != nil {
|
||||
p.updateUniforms()
|
||||
}
|
||||
}
|
||||
|
||||
func toGLDrawMode(mode backend.DrawMode) Enum {
|
||||
switch mode {
|
||||
case backend.DrawModeTriangleStrip:
|
||||
return TRIANGLE_STRIP
|
||||
case backend.DrawModeTriangles:
|
||||
return TRIANGLES
|
||||
default:
|
||||
panic("unsupported draw mode")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) Viewport(x, y, width, height int) {
|
||||
b.funcs.Viewport(x, y, width, height)
|
||||
}
|
||||
|
||||
func (b *Backend) Clear(colR, colG, colB, colA float32) {
|
||||
b.funcs.ClearColor(colR, colG, colB, colA)
|
||||
b.funcs.Clear(COLOR_BUFFER_BIT)
|
||||
}
|
||||
|
||||
func (b *Backend) ClearDepth(d float32) {
|
||||
b.funcs.ClearDepthf(d)
|
||||
b.funcs.Clear(DEPTH_BUFFER_BIT)
|
||||
}
|
||||
|
||||
func (b *Backend) DepthFunc(f backend.DepthFunc) {
|
||||
var glfunc Enum
|
||||
switch f {
|
||||
case backend.DepthFuncGreater:
|
||||
glfunc = GREATER
|
||||
case backend.DepthFuncGreaterEqual:
|
||||
glfunc = GEQUAL
|
||||
default:
|
||||
panic("unsupported depth func")
|
||||
}
|
||||
b.funcs.DepthFunc(glfunc)
|
||||
}
|
||||
|
||||
func (b *Backend) NewInputLayout(vs backend.ShaderSources, layout []backend.InputDesc) (backend.InputLayout, error) {
|
||||
if len(vs.Inputs) != len(layout) {
|
||||
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vs.Inputs))
|
||||
}
|
||||
for i, inp := range vs.Inputs {
|
||||
if exp, got := inp.Size, layout[i].Size; exp != got {
|
||||
return nil, fmt.Errorf("NewInputLayout: data size mismatch for %q: got %d expected %d", inp.Name, got, exp)
|
||||
}
|
||||
}
|
||||
return &gpuInputLayout{
|
||||
inputs: vs.Inputs,
|
||||
layout: layout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewProgram(vertShader, fragShader backend.ShaderSources) (backend.Program, error) {
|
||||
attr := make([]string, len(vertShader.Inputs))
|
||||
for _, inp := range vertShader.Inputs {
|
||||
attr[inp.Location] = inp.Name
|
||||
}
|
||||
vsrc, fsrc := vertShader.GLSL100ES, fragShader.GLSL100ES
|
||||
if b.glver[0] >= 3 {
|
||||
// OpenGL (ES) 3.0.
|
||||
switch {
|
||||
case b.gles:
|
||||
vsrc, fsrc = vertShader.GLSL300ES, fragShader.GLSL300ES
|
||||
case b.glver[0] >= 4 || b.glver[1] >= 2:
|
||||
// OpenGL 3.2 Core only accepts glsl 1.50 or newer.
|
||||
vsrc, fsrc = vertShader.GLSL150, fragShader.GLSL150
|
||||
default:
|
||||
vsrc, fsrc = vertShader.GLSL130, fragShader.GLSL130
|
||||
}
|
||||
}
|
||||
p, err := CreateProgram(b.funcs, vsrc, fsrc, attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gpuProg := &gpuProgram{
|
||||
backend: b,
|
||||
obj: p,
|
||||
nattr: len(attr),
|
||||
}
|
||||
b.BindProgram(gpuProg)
|
||||
// Bind texture uniforms.
|
||||
for _, tex := range vertShader.Textures {
|
||||
u := b.funcs.GetUniformLocation(p, tex.Name)
|
||||
if u.valid() {
|
||||
b.funcs.Uniform1i(u, tex.Binding)
|
||||
}
|
||||
}
|
||||
for _, tex := range fragShader.Textures {
|
||||
u := b.funcs.GetUniformLocation(p, tex.Name)
|
||||
if u.valid() {
|
||||
b.funcs.Uniform1i(u, tex.Binding)
|
||||
}
|
||||
}
|
||||
if b.ubo {
|
||||
for _, block := range vertShader.Uniforms.Blocks {
|
||||
blockIdx := b.funcs.GetUniformBlockIndex(p, block.Name)
|
||||
if blockIdx != INVALID_INDEX {
|
||||
b.funcs.UniformBlockBinding(p, blockIdx, uint(block.Binding))
|
||||
}
|
||||
}
|
||||
// To match Direct3D 11 with separate vertex and fragment
|
||||
// shader uniform buffers, offset all fragment blocks to be
|
||||
// located after the vertex blocks.
|
||||
off := len(vertShader.Uniforms.Blocks)
|
||||
for _, block := range fragShader.Uniforms.Blocks {
|
||||
blockIdx := b.funcs.GetUniformBlockIndex(p, block.Name)
|
||||
if blockIdx != INVALID_INDEX {
|
||||
b.funcs.UniformBlockBinding(p, blockIdx, uint(block.Binding+off))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gpuProg.vertUniforms.setup(b.funcs, p, vertShader.Uniforms.Size, vertShader.Uniforms.Locations)
|
||||
gpuProg.fragUniforms.setup(b.funcs, p, fragShader.Uniforms.Size, fragShader.Uniforms.Locations)
|
||||
}
|
||||
return gpuProg, nil
|
||||
}
|
||||
|
||||
func lookupUniform(funcs Functions, p Program, loc backend.UniformLocation) uniformLocation {
|
||||
u := GetUniformLocation(funcs, p, loc.Name)
|
||||
return uniformLocation{uniform: u, offset: loc.Offset, typ: loc.Type, size: loc.Size}
|
||||
}
|
||||
|
||||
func (p *gpuProgram) SetVertexUniforms(buffer backend.Buffer) {
|
||||
p.vertUniforms.setBuffer(buffer)
|
||||
}
|
||||
|
||||
func (p *gpuProgram) SetFragmentUniforms(buffer backend.Buffer) {
|
||||
p.fragUniforms.setBuffer(buffer)
|
||||
}
|
||||
|
||||
func (p *gpuProgram) updateUniforms() {
|
||||
f := p.backend.funcs
|
||||
if p.backend.ubo {
|
||||
if b := p.vertUniforms.buf; b != nil {
|
||||
f.BindBufferBase(UNIFORM_BUFFER, 0, b.obj)
|
||||
}
|
||||
if b := p.fragUniforms.buf; b != nil {
|
||||
f.BindBufferBase(UNIFORM_BUFFER, 1, b.obj)
|
||||
}
|
||||
} else {
|
||||
p.vertUniforms.update(f)
|
||||
p.fragUniforms.update(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindProgram(prog backend.Program) {
|
||||
p := prog.(*gpuProgram)
|
||||
b.useProgram(p)
|
||||
}
|
||||
|
||||
func (p *gpuProgram) Release() {
|
||||
p.backend.funcs.DeleteProgram(p.obj)
|
||||
}
|
||||
|
||||
func (u *uniformsTracker) setup(funcs Functions, p Program, uniformSize int, uniforms []backend.UniformLocation) {
|
||||
u.locs = make([]uniformLocation, len(uniforms))
|
||||
for i, uniform := range uniforms {
|
||||
u.locs[i] = lookupUniform(funcs, p, uniform)
|
||||
}
|
||||
u.size = uniformSize
|
||||
}
|
||||
|
||||
func (u *uniformsTracker) setBuffer(buffer backend.Buffer) {
|
||||
buf := buffer.(*gpuBuffer)
|
||||
if buf.typ&backend.BufferBindingUniforms == 0 {
|
||||
panic("not a uniform buffer")
|
||||
}
|
||||
if buf.size < u.size {
|
||||
panic(fmt.Errorf("uniform buffer too small, got %d need %d", buf.size, u.size))
|
||||
}
|
||||
u.buf = buf
|
||||
// Force update.
|
||||
u.version = buf.version - 1
|
||||
}
|
||||
|
||||
func (p *uniformsTracker) update(funcs Functions) {
|
||||
b := p.buf
|
||||
if b == nil || b.version == p.version {
|
||||
return
|
||||
}
|
||||
p.version = b.version
|
||||
data := b.data
|
||||
for _, u := range p.locs {
|
||||
data := data[u.offset:]
|
||||
switch {
|
||||
case u.typ == backend.DataTypeFloat && u.size == 1:
|
||||
data := data[:4]
|
||||
v := *(*[1]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform1f(u.uniform, v[0])
|
||||
case u.typ == backend.DataTypeFloat && u.size == 2:
|
||||
data := data[:8]
|
||||
v := *(*[2]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform2f(u.uniform, v[0], v[1])
|
||||
case u.typ == backend.DataTypeFloat && u.size == 3:
|
||||
data := data[:12]
|
||||
v := *(*[3]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform3f(u.uniform, v[0], v[1], v[2])
|
||||
case u.typ == backend.DataTypeFloat && u.size == 4:
|
||||
data := data[:16]
|
||||
v := *(*[4]float32)(unsafe.Pointer(&data[0]))
|
||||
funcs.Uniform4f(u.uniform, v[0], v[1], v[2], v[3])
|
||||
default:
|
||||
panic("unsupported uniform data type or size")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *gpuBuffer) Upload(data []byte) {
|
||||
if b.immutable {
|
||||
panic("immutable buffer")
|
||||
}
|
||||
if len(data) > b.size {
|
||||
panic("buffer size overflow")
|
||||
}
|
||||
b.version++
|
||||
copy(b.data, data)
|
||||
if b.hasBuffer {
|
||||
firstBinding := firstBufferType(b.typ)
|
||||
b.backend.funcs.BindBuffer(firstBinding, b.obj)
|
||||
b.backend.funcs.BufferData(firstBinding, data, STATIC_DRAW)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *gpuBuffer) Release() {
|
||||
if b.hasBuffer {
|
||||
b.backend.funcs.DeleteBuffer(b.obj)
|
||||
b.hasBuffer = false
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindVertexBuffer(buf backend.Buffer, stride, offset int) {
|
||||
gbuf := buf.(*gpuBuffer)
|
||||
if gbuf.typ&backend.BufferBindingVertices == 0 {
|
||||
panic("not a vertex buffer")
|
||||
}
|
||||
b.state.buffer = bufferBinding{buf: gbuf, stride: stride, offset: offset}
|
||||
}
|
||||
|
||||
func (b *Backend) setupVertexArrays() {
|
||||
layout := b.state.layout
|
||||
if layout == nil {
|
||||
return
|
||||
}
|
||||
buf := b.state.buffer
|
||||
b.funcs.BindBuffer(ARRAY_BUFFER, buf.buf.obj)
|
||||
for i, inp := range layout.inputs {
|
||||
l := layout.layout[i]
|
||||
var gltyp Enum
|
||||
switch l.Type {
|
||||
case backend.DataTypeFloat:
|
||||
gltyp = FLOAT
|
||||
case backend.DataTypeShort:
|
||||
gltyp = SHORT
|
||||
default:
|
||||
panic("unsupported data type")
|
||||
}
|
||||
b.funcs.VertexAttribPointer(Attrib(inp.Location), l.Size, gltyp, false, buf.stride, buf.offset+l.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindIndexBuffer(buf backend.Buffer) {
|
||||
gbuf := buf.(*gpuBuffer)
|
||||
if gbuf.typ&backend.BufferBindingIndices == 0 {
|
||||
panic("not an index buffer")
|
||||
}
|
||||
b.funcs.BindBuffer(ELEMENT_ARRAY_BUFFER, gbuf.obj)
|
||||
}
|
||||
|
||||
func (f *gpuFramebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
|
||||
glErr(f.backend.funcs)
|
||||
f.backend.BindFramebuffer(f)
|
||||
if len(pixels) < src.Dx()*src.Dy() {
|
||||
return errors.New("unexpected RGBA size")
|
||||
}
|
||||
f.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, src.Dx(), src.Dy(), RGBA, UNSIGNED_BYTE, pixels)
|
||||
// OpenGL origin is in the lower-left corner. Flip the image to
|
||||
// match.
|
||||
flipImageY(src.Dx()*4, src.Dy(), pixels)
|
||||
return glErr(f.backend.funcs)
|
||||
}
|
||||
|
||||
func flipImageY(stride int, height int, pixels []byte) {
|
||||
// Flip image in y-direction. OpenGL's origin is in the lower
|
||||
// left corner.
|
||||
row := make([]uint8, stride)
|
||||
for y := 0; y < height/2; y++ {
|
||||
y1 := height - y - 1
|
||||
dest := y1 * stride
|
||||
src := y * stride
|
||||
copy(row, pixels[dest:])
|
||||
copy(pixels[dest:], pixels[src:src+len(row)])
|
||||
copy(pixels[src:], row)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindFramebuffer(fbo backend.Framebuffer) {
|
||||
b.funcs.BindFramebuffer(FRAMEBUFFER, fbo.(*gpuFramebuffer).obj)
|
||||
}
|
||||
|
||||
func (f *gpuFramebuffer) Invalidate() {
|
||||
f.backend.BindFramebuffer(f)
|
||||
f.backend.funcs.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
|
||||
}
|
||||
|
||||
func (f *gpuFramebuffer) Release() {
|
||||
if f.foreign {
|
||||
panic("cannot release framebuffer created by CurrentFramebuffer")
|
||||
}
|
||||
f.backend.funcs.DeleteFramebuffer(f.obj)
|
||||
if f.hasDepth {
|
||||
f.backend.funcs.DeleteRenderbuffer(f.depthBuf)
|
||||
}
|
||||
}
|
||||
|
||||
func toTexFilter(f backend.TextureFilter) int {
|
||||
switch f {
|
||||
case backend.FilterNearest:
|
||||
return NEAREST
|
||||
case backend.FilterLinear:
|
||||
return LINEAR
|
||||
default:
|
||||
panic("unsupported texture filter")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backend) BindTexture(unit int, t backend.Texture) {
|
||||
b.bindTexture(unit, t.(*gpuTexture))
|
||||
}
|
||||
|
||||
func (t *gpuTexture) Release() {
|
||||
t.backend.funcs.DeleteTexture(t.obj)
|
||||
}
|
||||
|
||||
func (t *gpuTexture) Upload(img *image.RGBA) {
|
||||
t.backend.BindTexture(0, t)
|
||||
var pixels []byte
|
||||
b := img.Bounds()
|
||||
w, h := b.Dx(), b.Dy()
|
||||
if img.Stride != w*4 {
|
||||
panic("unsupported stride")
|
||||
}
|
||||
start := (b.Min.X + b.Min.Y*w) * 4
|
||||
end := (b.Max.X + (b.Max.Y-1)*w) * 4
|
||||
pixels = img.Pix[start:end]
|
||||
t.backend.funcs.TexImage2D(TEXTURE_2D, 0, t.triple.internalFormat, w, h, t.triple.format, t.triple.typ, pixels)
|
||||
}
|
||||
|
||||
func (t *gpuTimer) Begin() {
|
||||
t.funcs.BeginQuery(TIME_ELAPSED_EXT, t.obj)
|
||||
}
|
||||
|
||||
func (t *gpuTimer) End() {
|
||||
t.funcs.EndQuery(TIME_ELAPSED_EXT)
|
||||
}
|
||||
|
||||
func (t *gpuTimer) ready() bool {
|
||||
return t.funcs.GetQueryObjectuiv(t.obj, QUERY_RESULT_AVAILABLE) == TRUE
|
||||
}
|
||||
|
||||
func (t *gpuTimer) Release() {
|
||||
t.funcs.DeleteQuery(t.obj)
|
||||
}
|
||||
|
||||
func (t *gpuTimer) Duration() (time.Duration, bool) {
|
||||
if !t.ready() {
|
||||
return 0, false
|
||||
}
|
||||
nanos := t.funcs.GetQueryObjectuiv(t.obj, QUERY_RESULT)
|
||||
return time.Duration(nanos), true
|
||||
}
|
||||
|
||||
func (b *Backend) BindInputLayout(l backend.InputLayout) {
|
||||
b.state.layout = l.(*gpuInputLayout)
|
||||
}
|
||||
|
||||
func (l *gpuInputLayout) Release() {}
|
||||
|
||||
// floatTripleFor determines the best texture triple for floating point FBOs.
|
||||
func floatTripleFor(f Functions, ver [2]int, exts []string) (textureTriple, error) {
|
||||
var triples []textureTriple
|
||||
if ver[0] >= 3 {
|
||||
triples = append(triples, textureTriple{R16F, Enum(RED), Enum(HALF_FLOAT)})
|
||||
}
|
||||
// According to the OES_texture_half_float specification, EXT_color_buffer_half_float is needed to
|
||||
// render to FBOs. However, the Safari WebGL1 implementation does support half-float FBOs but does not
|
||||
// report EXT_color_buffer_half_float support. The triples are verified below, so it doesn't matter if we're
|
||||
// wrong.
|
||||
if hasExtension(exts, "GL_OES_texture_half_float") || hasExtension(exts, "GL_EXT_color_buffer_half_float") {
|
||||
// Try single channel.
|
||||
triples = append(triples, textureTriple{LUMINANCE, Enum(LUMINANCE), Enum(HALF_FLOAT_OES)})
|
||||
// Fallback to 4 channels.
|
||||
triples = append(triples, textureTriple{RGBA, Enum(RGBA), Enum(HALF_FLOAT_OES)})
|
||||
}
|
||||
if hasExtension(exts, "GL_OES_texture_float") || hasExtension(exts, "GL_EXT_color_buffer_float") {
|
||||
triples = append(triples, textureTriple{RGBA, Enum(RGBA), Enum(FLOAT)})
|
||||
}
|
||||
tex := f.CreateTexture()
|
||||
defer f.DeleteTexture(tex)
|
||||
f.BindTexture(TEXTURE_2D, tex)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST)
|
||||
fbo := f.CreateFramebuffer()
|
||||
defer f.DeleteFramebuffer(fbo)
|
||||
defFBO := Framebuffer(f.GetBinding(FRAMEBUFFER_BINDING))
|
||||
f.BindFramebuffer(FRAMEBUFFER, fbo)
|
||||
defer f.BindFramebuffer(FRAMEBUFFER, defFBO)
|
||||
var attempts []string
|
||||
for _, tt := range triples {
|
||||
const size = 256
|
||||
f.TexImage2D(TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ, nil)
|
||||
f.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, tex, 0)
|
||||
st := f.CheckFramebufferStatus(FRAMEBUFFER)
|
||||
if st == FRAMEBUFFER_COMPLETE {
|
||||
return tt, nil
|
||||
}
|
||||
attempts = append(attempts, fmt.Sprintf("(0x%x, 0x%x, 0x%x): 0x%x", tt.internalFormat, tt.format, tt.typ, st))
|
||||
}
|
||||
return textureTriple{}, fmt.Errorf("floating point fbos not supported (attempted %s)", attempts)
|
||||
}
|
||||
|
||||
func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) {
|
||||
switch {
|
||||
case ver[0] >= 3:
|
||||
return textureTriple{SRGB8_ALPHA8, Enum(RGBA), Enum(UNSIGNED_BYTE)}, nil
|
||||
case hasExtension(exts, "GL_EXT_sRGB"):
|
||||
return textureTriple{SRGB_ALPHA_EXT, Enum(SRGB_ALPHA_EXT), Enum(UNSIGNED_BYTE)}, nil
|
||||
default:
|
||||
return textureTriple{}, errors.New("no sRGB texture formats found")
|
||||
}
|
||||
}
|
||||
|
||||
func alphaTripleFor(ver [2]int) textureTriple {
|
||||
intf, f := R8, Enum(RED)
|
||||
if ver[0] < 3 {
|
||||
// R8, RED not supported on OpenGL ES 2.0.
|
||||
intf, f = LUMINANCE, Enum(LUMINANCE)
|
||||
}
|
||||
return textureTriple{intf, f, UNSIGNED_BYTE}
|
||||
}
|
||||
|
||||
func hasExtension(exts []string, ext string) bool {
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func firstBufferType(typ backend.BufferBinding) Enum {
|
||||
switch {
|
||||
case typ&backend.BufferBindingIndices != 0:
|
||||
return ELEMENT_ARRAY_BUFFER
|
||||
case typ&backend.BufferBindingVertices != 0:
|
||||
return ARRAY_BUFFER
|
||||
case typ&backend.BufferBindingUniforms != 0:
|
||||
return UNIFORM_BUFFER
|
||||
default:
|
||||
panic("unsupported buffer type")
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
type (
|
||||
Attrib uint
|
||||
Enum uint
|
||||
)
|
||||
|
||||
const (
|
||||
ARRAY_BUFFER = 0x8892
|
||||
BLEND = 0xbe2
|
||||
CLAMP_TO_EDGE = 0x812f
|
||||
COLOR_ATTACHMENT0 = 0x8ce0
|
||||
COLOR_BUFFER_BIT = 0x4000
|
||||
COMPILE_STATUS = 0x8b81
|
||||
DEPTH_BUFFER_BIT = 0x100
|
||||
DEPTH_ATTACHMENT = 0x8d00
|
||||
DEPTH_COMPONENT16 = 0x81a5
|
||||
DEPTH_COMPONENT24 = 0x81A6
|
||||
DEPTH_COMPONENT32F = 0x8CAC
|
||||
DEPTH_TEST = 0xb71
|
||||
DST_COLOR = 0x306
|
||||
ELEMENT_ARRAY_BUFFER = 0x8893
|
||||
EXTENSIONS = 0x1f03
|
||||
FALSE = 0
|
||||
FLOAT = 0x1406
|
||||
FRAGMENT_SHADER = 0x8b30
|
||||
FRAMEBUFFER = 0x8d40
|
||||
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
|
||||
FRAMEBUFFER_BINDING = 0x8ca6
|
||||
FRAMEBUFFER_COMPLETE = 0x8cd5
|
||||
HALF_FLOAT = 0x140b
|
||||
HALF_FLOAT_OES = 0x8d61
|
||||
INFO_LOG_LENGTH = 0x8B84
|
||||
INVALID_INDEX = ^uint(0)
|
||||
GREATER = 0x204
|
||||
GEQUAL = 0x206
|
||||
LINEAR = 0x2601
|
||||
LINK_STATUS = 0x8b82
|
||||
LUMINANCE = 0x1909
|
||||
MAX_TEXTURE_SIZE = 0xd33
|
||||
NEAREST = 0x2600
|
||||
NO_ERROR = 0x0
|
||||
NUM_EXTENSIONS = 0x821D
|
||||
ONE = 0x1
|
||||
ONE_MINUS_SRC_ALPHA = 0x303
|
||||
QUERY_RESULT = 0x8866
|
||||
QUERY_RESULT_AVAILABLE = 0x8867
|
||||
R16F = 0x822d
|
||||
R8 = 0x8229
|
||||
READ_FRAMEBUFFER = 0x8ca8
|
||||
RED = 0x1903
|
||||
RENDERER = 0x1F01
|
||||
RENDERBUFFER = 0x8d41
|
||||
RENDERBUFFER_BINDING = 0x8ca7
|
||||
RENDERBUFFER_HEIGHT = 0x8d43
|
||||
RENDERBUFFER_WIDTH = 0x8d42
|
||||
RGB = 0x1907
|
||||
RGBA = 0x1908
|
||||
RGBA8 = 0x8058
|
||||
SHORT = 0x1402
|
||||
SRGB = 0x8c40
|
||||
SRGB_ALPHA_EXT = 0x8c42
|
||||
SRGB8 = 0x8c41
|
||||
SRGB8_ALPHA8 = 0x8c43
|
||||
STATIC_DRAW = 0x88e4
|
||||
TEXTURE_2D = 0xde1
|
||||
TEXTURE_MAG_FILTER = 0x2800
|
||||
TEXTURE_MIN_FILTER = 0x2801
|
||||
TEXTURE_WRAP_S = 0x2802
|
||||
TEXTURE_WRAP_T = 0x2803
|
||||
TEXTURE0 = 0x84c0
|
||||
TEXTURE1 = 0x84c1
|
||||
TRIANGLE_STRIP = 0x5
|
||||
TRIANGLES = 0x4
|
||||
TRUE = 1
|
||||
UNIFORM_BUFFER = 0x8A11
|
||||
UNPACK_ALIGNMENT = 0xcf5
|
||||
UNSIGNED_BYTE = 0x1401
|
||||
UNSIGNED_SHORT = 0x1403
|
||||
VERSION = 0x1f02
|
||||
VERTEX_SHADER = 0x8b31
|
||||
ZERO = 0x0
|
||||
|
||||
// EXT_disjoint_timer_query
|
||||
TIME_ELAPSED_EXT = 0x88BF
|
||||
GPU_DISJOINT_EXT = 0x8FBB
|
||||
)
|
||||
|
||||
type Functions interface {
|
||||
ActiveTexture(texture Enum)
|
||||
AttachShader(p Program, s Shader)
|
||||
BeginQuery(target Enum, query Query)
|
||||
BindAttribLocation(p Program, a Attrib, name string)
|
||||
BindBuffer(target Enum, b Buffer)
|
||||
BindBufferBase(target Enum, index int, buffer Buffer)
|
||||
BindFramebuffer(target Enum, fb Framebuffer)
|
||||
BindRenderbuffer(target Enum, fb Renderbuffer)
|
||||
BindTexture(target Enum, t Texture)
|
||||
BlendEquation(mode Enum)
|
||||
BlendFunc(sfactor, dfactor Enum)
|
||||
BufferData(target Enum, src []byte, usage Enum)
|
||||
CheckFramebufferStatus(target Enum) Enum
|
||||
Clear(mask Enum)
|
||||
ClearColor(red, green, blue, alpha float32)
|
||||
ClearDepthf(d float32)
|
||||
CompileShader(s Shader)
|
||||
CreateBuffer() Buffer
|
||||
CreateFramebuffer() Framebuffer
|
||||
CreateProgram() Program
|
||||
CreateQuery() Query
|
||||
CreateRenderbuffer() Renderbuffer
|
||||
CreateShader(ty Enum) Shader
|
||||
CreateTexture() Texture
|
||||
DeleteBuffer(v Buffer)
|
||||
DeleteFramebuffer(v Framebuffer)
|
||||
DeleteProgram(p Program)
|
||||
DeleteQuery(query Query)
|
||||
DeleteRenderbuffer(r Renderbuffer)
|
||||
DeleteShader(s Shader)
|
||||
DeleteTexture(v Texture)
|
||||
DepthFunc(f Enum)
|
||||
DepthMask(mask bool)
|
||||
DisableVertexAttribArray(a Attrib)
|
||||
Disable(cap Enum)
|
||||
DrawArrays(mode Enum, first, count int)
|
||||
DrawElements(mode Enum, count int, ty Enum, offset int)
|
||||
Enable(cap Enum)
|
||||
EnableVertexAttribArray(a Attrib)
|
||||
EndQuery(target Enum)
|
||||
FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int)
|
||||
FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer)
|
||||
GetBinding(pname Enum) Object
|
||||
GetError() Enum
|
||||
GetInteger(pname Enum) int
|
||||
GetProgrami(p Program, pname Enum) int
|
||||
GetProgramInfoLog(p Program) string
|
||||
GetQueryObjectuiv(query Query, pname Enum) uint
|
||||
GetShaderi(s Shader, pname Enum) int
|
||||
GetShaderInfoLog(s Shader) string
|
||||
GetString(pname Enum) string
|
||||
GetUniformBlockIndex(p Program, name string) uint
|
||||
GetUniformLocation(p Program, name string) Uniform
|
||||
InvalidateFramebuffer(target, attachment Enum)
|
||||
LinkProgram(p Program)
|
||||
ReadPixels(x, y, width, height int, format, ty Enum, data []byte)
|
||||
RenderbufferStorage(target, internalformat Enum, width, height int)
|
||||
ShaderSource(s Shader, src string)
|
||||
TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte)
|
||||
TexParameteri(target, pname Enum, param int)
|
||||
UniformBlockBinding(p Program, uniformBlockIndex uint, uniformBlockBinding uint)
|
||||
Uniform1f(dst Uniform, v float32)
|
||||
Uniform1i(dst Uniform, v int)
|
||||
Uniform2f(dst Uniform, v0, v1 float32)
|
||||
Uniform3f(dst Uniform, v0, v1, v2 float32)
|
||||
Uniform4f(dst Uniform, v0, v1, v2, v3 float32)
|
||||
UseProgram(p Program)
|
||||
VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int)
|
||||
Viewport(x, y, width, height int)
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// +build !js
|
||||
|
||||
package gl
|
||||
|
||||
type (
|
||||
Buffer struct{ V uint }
|
||||
Framebuffer struct{ V uint }
|
||||
Program struct{ V uint }
|
||||
Renderbuffer struct{ V uint }
|
||||
Shader struct{ V uint }
|
||||
Texture struct{ V uint }
|
||||
Query struct{ V uint }
|
||||
Uniform struct{ V int }
|
||||
Object struct{ V uint }
|
||||
)
|
||||
|
||||
func (u Uniform) valid() bool {
|
||||
return u.V != -1
|
||||
}
|
||||
|
||||
func (p Program) valid() bool {
|
||||
return p.V != 0
|
||||
}
|
||||
|
||||
func (s Shader) valid() bool {
|
||||
return s.V != 0
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
type (
|
||||
Buffer js.Value
|
||||
Framebuffer js.Value
|
||||
Program js.Value
|
||||
Renderbuffer js.Value
|
||||
Shader js.Value
|
||||
Texture js.Value
|
||||
Query js.Value
|
||||
Uniform js.Value
|
||||
Object js.Value
|
||||
)
|
||||
|
||||
func (p Program) valid() bool {
|
||||
return !js.Value(p).IsUndefined() && !js.Value(p).IsNull()
|
||||
}
|
||||
|
||||
func (s Shader) valid() bool {
|
||||
return !js.Value(s).IsUndefined() && !js.Value(s).IsNull()
|
||||
}
|
||||
|
||||
func (u Uniform) valid() bool {
|
||||
return !js.Value(u).IsUndefined() && !js.Value(u).IsNull()
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CreateProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
|
||||
vs, err := createShader(ctx, VERTEX_SHADER, vsSrc)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
defer ctx.DeleteShader(vs)
|
||||
fs, err := createShader(ctx, FRAGMENT_SHADER, fsSrc)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
defer ctx.DeleteShader(fs)
|
||||
prog := ctx.CreateProgram()
|
||||
if !prog.valid() {
|
||||
return Program{}, errors.New("glCreateProgram failed")
|
||||
}
|
||||
ctx.AttachShader(prog, vs)
|
||||
ctx.AttachShader(prog, fs)
|
||||
for i, a := range attribs {
|
||||
ctx.BindAttribLocation(prog, Attrib(i), a)
|
||||
}
|
||||
ctx.LinkProgram(prog)
|
||||
if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
|
||||
log := ctx.GetProgramInfoLog(prog)
|
||||
ctx.DeleteProgram(prog)
|
||||
return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
|
||||
}
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
func GetUniformLocation(ctx Functions, prog Program, name string) Uniform {
|
||||
loc := ctx.GetUniformLocation(prog, name)
|
||||
if !loc.valid() {
|
||||
panic(fmt.Errorf("uniform %s not found", name))
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
func createShader(ctx Functions, typ Enum, src string) (Shader, error) {
|
||||
sh := ctx.CreateShader(typ)
|
||||
if !sh.valid() {
|
||||
return Shader{}, errors.New("glCreateShader failed")
|
||||
}
|
||||
ctx.ShaderSource(sh, src)
|
||||
ctx.CompileShader(sh)
|
||||
if ctx.GetShaderi(sh, COMPILE_STATUS) == 0 {
|
||||
log := ctx.GetShaderInfoLog(sh)
|
||||
ctx.DeleteShader(sh)
|
||||
return Shader{}, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
|
||||
}
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
func ParseGLVersion(glVer string) (version [2]int, gles bool, err error) {
|
||||
var ver [2]int
|
||||
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
|
||||
return ver, true, nil
|
||||
} else if _, err := fmt.Sscanf(glVer, "WebGL %d.%d", &ver[0], &ver[1]); err == nil {
|
||||
// WebGL major version v corresponds to OpenGL ES version v + 1
|
||||
ver[0]++
|
||||
return ver, true, nil
|
||||
} else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
|
||||
return ver, false, nil
|
||||
}
|
||||
return ver, false, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
// packer packs a set of many smaller rectangles into
|
||||
// much fewer larger atlases.
|
||||
type packer struct {
|
||||
maxDim int
|
||||
spaces []image.Rectangle
|
||||
|
||||
sizes []image.Point
|
||||
pos image.Point
|
||||
}
|
||||
|
||||
type placement struct {
|
||||
Idx int
|
||||
Pos image.Point
|
||||
}
|
||||
|
||||
// add adds the given rectangle to the atlases and
|
||||
// return the allocated position.
|
||||
func (p *packer) add(s image.Point) (placement, bool) {
|
||||
if place, ok := p.tryAdd(s); ok {
|
||||
return place, true
|
||||
}
|
||||
p.newPage()
|
||||
return p.tryAdd(s)
|
||||
}
|
||||
|
||||
func (p *packer) clear() {
|
||||
p.sizes = p.sizes[:0]
|
||||
p.spaces = p.spaces[:0]
|
||||
}
|
||||
|
||||
func (p *packer) newPage() {
|
||||
p.pos = image.Point{}
|
||||
p.sizes = append(p.sizes, image.Point{})
|
||||
p.spaces = p.spaces[:0]
|
||||
p.spaces = append(p.spaces, image.Rectangle{
|
||||
Max: image.Point{X: p.maxDim, Y: p.maxDim},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *packer) tryAdd(s image.Point) (placement, bool) {
|
||||
// Go backwards to prioritize smaller spaces first.
|
||||
for i := len(p.spaces) - 1; i >= 0; i-- {
|
||||
space := p.spaces[i]
|
||||
rightSpace := space.Dx() - s.X
|
||||
bottomSpace := space.Dy() - s.Y
|
||||
if rightSpace >= 0 && bottomSpace >= 0 {
|
||||
// Remove space.
|
||||
p.spaces[i] = p.spaces[len(p.spaces)-1]
|
||||
p.spaces = p.spaces[:len(p.spaces)-1]
|
||||
// Put s in the top left corner and add the (at most)
|
||||
// two smaller spaces.
|
||||
pos := space.Min
|
||||
if bottomSpace > 0 {
|
||||
p.spaces = append(p.spaces, image.Rectangle{
|
||||
Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
|
||||
Max: image.Point{X: space.Max.X, Y: space.Max.Y},
|
||||
})
|
||||
}
|
||||
if rightSpace > 0 {
|
||||
p.spaces = append(p.spaces, image.Rectangle{
|
||||
Min: image.Point{X: pos.X + s.X, Y: pos.Y},
|
||||
Max: image.Point{X: space.Max.X, Y: pos.Y + s.Y},
|
||||
})
|
||||
}
|
||||
idx := len(p.sizes) - 1
|
||||
size := &p.sizes[idx]
|
||||
if x := pos.X + s.X; x > size.X {
|
||||
size.X = x
|
||||
}
|
||||
if y := pos.Y + s.Y; y > size.Y {
|
||||
size.Y = y
|
||||
}
|
||||
return placement{Idx: idx, Pos: pos}, true
|
||||
}
|
||||
}
|
||||
return placement{}, false
|
||||
}
|
@ -0,0 +1,396 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
// GPU accelerated path drawing using the algorithms from
|
||||
// Pathfinder (https://github.com/servo/pathfinder).
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/gpu/backend"
|
||||
"gioui.org/internal/f32color"
|
||||
gunsafe "gioui.org/internal/unsafe"
|
||||
)
|
||||
|
||||
type pather struct {
|
||||
ctx backend.Device
|
||||
|
||||
viewport image.Point
|
||||
|
||||
stenciler *stenciler
|
||||
coverer *coverer
|
||||
}
|
||||
|
||||
type coverer struct {
|
||||
ctx backend.Device
|
||||
prog [2]*program
|
||||
texUniforms *coverTexUniforms
|
||||
colUniforms *coverColUniforms
|
||||
layout backend.InputLayout
|
||||
}
|
||||
|
||||
type coverTexUniforms struct {
|
||||
vert struct {
|
||||
coverUniforms
|
||||
_ [12]byte // Padding to multiple of 16.
|
||||
}
|
||||
}
|
||||
|
||||
type coverColUniforms struct {
|
||||
vert struct {
|
||||
coverUniforms
|
||||
_ [12]byte // Padding to multiple of 16.
|
||||
}
|
||||
frag struct {
|
||||
colorUniforms
|
||||
}
|
||||
}
|
||||
|
||||
type coverUniforms struct {
|
||||
transform [4]float32
|
||||
uvCoverTransform [4]float32
|
||||
uvTransformR1 [4]float32
|
||||
uvTransformR2 [4]float32
|
||||
z float32
|
||||
}
|
||||
|
||||
type stenciler struct {
|
||||
ctx backend.Device
|
||||
prog struct {
|
||||
prog *program
|
||||
uniforms *stencilUniforms
|
||||
layout backend.InputLayout
|
||||
}
|
||||
iprog struct {
|
||||
prog *program
|
||||
uniforms *intersectUniforms
|
||||
layout backend.InputLayout
|
||||
}
|
||||
fbos fboSet
|
||||
intersections fboSet
|
||||
indexBuf backend.Buffer
|
||||
}
|
||||
|
||||
type stencilUniforms struct {
|
||||
vert struct {
|
||||
transform [4]float32
|
||||
pathOffset [2]float32
|
||||
_ [8]byte // Padding to multiple of 16.
|
||||
}
|
||||
}
|
||||
|
||||
type intersectUniforms struct {
|
||||
vert struct {
|
||||
uvTransform [4]float32
|
||||
subUVTransform [4]float32
|
||||
}
|
||||
}
|
||||
|
||||
type fboSet struct {
|
||||
fbos []stencilFBO
|
||||
}
|
||||
|
||||
type stencilFBO struct {
|
||||
size image.Point
|
||||
fbo backend.Framebuffer
|
||||
tex backend.Texture
|
||||
}
|
||||
|
||||
type pathData struct {
|
||||
ncurves int
|
||||
data backend.Buffer
|
||||
}
|
||||
|
||||
// vertex data suitable for passing to vertex programs.
|
||||
type vertex struct {
|
||||
// Corner encodes the corner: +0.5 for south, +.25 for east.
|
||||
Corner float32
|
||||
MaxY float32
|
||||
FromX, FromY float32
|
||||
CtrlX, CtrlY float32
|
||||
ToX, ToY float32
|
||||
}
|
||||
|
||||
func (v vertex) encode(d []byte, maxy uint32) {
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(d[0:], math.Float32bits(v.Corner))
|
||||
bo.PutUint32(d[4:], maxy)
|
||||
bo.PutUint32(d[8:], math.Float32bits(v.FromX))
|
||||
bo.PutUint32(d[12:], math.Float32bits(v.FromY))
|
||||
bo.PutUint32(d[16:], math.Float32bits(v.CtrlX))
|
||||
bo.PutUint32(d[20:], math.Float32bits(v.CtrlY))
|
||||
bo.PutUint32(d[24:], math.Float32bits(v.ToX))
|
||||
bo.PutUint32(d[28:], math.Float32bits(v.ToY))
|
||||
}
|
||||
|
||||
const (
|
||||
// Number of path quads per draw batch.
|
||||
pathBatchSize = 10000
|
||||
// Size of a vertex as sent to gpu
|
||||
vertStride = 7*4 + 2*2
|
||||
)
|
||||
|
||||
func newPather(ctx backend.Device) *pather {
|
||||
return &pather{
|
||||
ctx: ctx,
|
||||
stenciler: newStenciler(ctx),
|
||||
coverer: newCoverer(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func newCoverer(ctx backend.Device) *coverer {
|
||||
c := &coverer{
|
||||
ctx: ctx,
|
||||
}
|
||||
c.colUniforms = new(coverColUniforms)
|
||||
c.texUniforms = new(coverTexUniforms)
|
||||
prog, layout, err := createColorPrograms(ctx, shader_cover_vert, shader_cover_frag,
|
||||
[2]interface{}{&c.colUniforms.vert, &c.texUniforms.vert},
|
||||
[2]interface{}{&c.colUniforms.frag, nil},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.prog = prog
|
||||
c.layout = layout
|
||||
return c
|
||||
}
|
||||
|
||||
func newStenciler(ctx backend.Device) *stenciler {
|
||||
// Allocate a suitably large index buffer for drawing paths.
|
||||
indices := make([]uint16, pathBatchSize*6)
|
||||
for i := 0; i < pathBatchSize; i++ {
|
||||
i := uint16(i)
|
||||
indices[i*6+0] = i*4 + 0
|
||||
indices[i*6+1] = i*4 + 1
|
||||
indices[i*6+2] = i*4 + 2
|
||||
indices[i*6+3] = i*4 + 2
|
||||
indices[i*6+4] = i*4 + 1
|
||||
indices[i*6+5] = i*4 + 3
|
||||
}
|
||||
indexBuf, err := ctx.NewImmutableBuffer(backend.BufferBindingIndices, gunsafe.BytesView(indices))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
progLayout, err := ctx.NewInputLayout(shader_stencil_vert, []backend.InputDesc{
|
||||
{Type: backend.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
|
||||
{Type: backend.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
|
||||
{Type: backend.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
|
||||
{Type: backend.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
|
||||
{Type: backend.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
iprogLayout, err := ctx.NewInputLayout(shader_intersect_vert, []backend.InputDesc{
|
||||
{Type: backend.DataTypeFloat, Size: 2, Offset: 0},
|
||||
{Type: backend.DataTypeFloat, Size: 2, Offset: 4 * 2},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
st := &stenciler{
|
||||
ctx: ctx,
|
||||
indexBuf: indexBuf,
|
||||
}
|
||||
prog, err := ctx.NewProgram(shader_stencil_vert, shader_stencil_frag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
st.prog.uniforms = new(stencilUniforms)
|
||||
vertUniforms := newUniformBuffer(ctx, &st.prog.uniforms.vert)
|
||||
st.prog.prog = newProgram(prog, vertUniforms, nil)
|
||||
st.prog.layout = progLayout
|
||||
iprog, err := ctx.NewProgram(shader_intersect_vert, shader_intersect_frag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
st.iprog.uniforms = new(intersectUniforms)
|
||||
vertUniforms = newUniformBuffer(ctx, &st.iprog.uniforms.vert)
|
||||
st.iprog.prog = newProgram(iprog, vertUniforms, nil)
|
||||
st.iprog.layout = iprogLayout
|
||||
return st
|
||||
}
|
||||
|
||||
func (s *fboSet) resize(ctx backend.Device, sizes []image.Point) {
|
||||
// Add fbos.
|
||||
for i := len(s.fbos); i < len(sizes); i++ {
|
||||
s.fbos = append(s.fbos, stencilFBO{})
|
||||
}
|
||||
// Resize fbos.
|
||||
for i, sz := range sizes {
|
||||
f := &s.fbos[i]
|
||||
// Resizing or recreating FBOs can introduce rendering stalls.
|
||||
// Avoid if the space waste is not too high.
|
||||
resize := sz.X > f.size.X || sz.Y > f.size.Y
|
||||
waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
|
||||
resize = resize || waste > 1.2
|
||||
if resize {
|
||||
if f.fbo != nil {
|
||||
f.fbo.Release()
|
||||
f.tex.Release()
|
||||
}
|
||||
tex, err := ctx.NewTexture(backend.TextureFormatFloat, sz.X, sz.Y, backend.FilterNearest, backend.FilterNearest,
|
||||
backend.BufferBindingTexture|backend.BufferBindingFramebuffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fbo, err := ctx.NewFramebuffer(tex, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.size = sz
|
||||
f.tex = tex
|
||||
f.fbo = fbo
|
||||
}
|
||||
}
|
||||
// Delete extra fbos.
|
||||
s.delete(ctx, len(sizes))
|
||||
}
|
||||
|
||||
func (s *fboSet) invalidate(ctx backend.Device) {
|
||||
for _, f := range s.fbos {
|
||||
f.fbo.Invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fboSet) delete(ctx backend.Device, idx int) {
|
||||
for i := idx; i < len(s.fbos); i++ {
|
||||
f := s.fbos[i]
|
||||
f.fbo.Release()
|
||||
f.tex.Release()
|
||||
}
|
||||
s.fbos = s.fbos[:idx]
|
||||
}
|
||||
|
||||
func (s *stenciler) release() {
|
||||
s.fbos.delete(s.ctx, 0)
|
||||
s.prog.layout.Release()
|
||||
s.prog.prog.Release()
|
||||
s.iprog.layout.Release()
|
||||
s.iprog.prog.Release()
|
||||
s.indexBuf.Release()
|
||||
}
|
||||
|
||||
func (p *pather) release() {
|
||||
p.stenciler.release()
|
||||
p.coverer.release()
|
||||
}
|
||||
|
||||
func (c *coverer) release() {
|
||||
for _, p := range c.prog {
|
||||
p.Release()
|
||||
}
|
||||
c.layout.Release()
|
||||
}
|
||||
|
||||
func buildPath(ctx backend.Device, p []byte) pathData {
|
||||
buf, err := ctx.NewImmutableBuffer(backend.BufferBindingVertices, p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pathData{
|
||||
ncurves: len(p) / vertStride,
|
||||
data: buf,
|
||||
}
|
||||
}
|
||||
|
||||
func (p pathData) release() {
|
||||
p.data.Release()
|
||||
}
|
||||
|
||||
func (p *pather) begin(sizes []image.Point) {
|
||||
p.stenciler.begin(sizes)
|
||||
}
|
||||
|
||||
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||
p.stenciler.stencilPath(bounds, offset, uv, data)
|
||||
}
|
||||
|
||||
func (s *stenciler) beginIntersect(sizes []image.Point) {
|
||||
s.ctx.BlendFunc(backend.BlendFactorDstColor, backend.BlendFactorZero)
|
||||
// 8 bit coverage is enough, but OpenGL ES only supports single channel
|
||||
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
|
||||
// no floating point support is available.
|
||||
s.intersections.resize(s.ctx, sizes)
|
||||
s.ctx.BindProgram(s.iprog.prog.prog)
|
||||
}
|
||||
|
||||
func (s *stenciler) invalidateFBO() {
|
||||
s.intersections.invalidate(s.ctx)
|
||||
s.fbos.invalidate(s.ctx)
|
||||
}
|
||||
|
||||
func (s *stenciler) cover(idx int) stencilFBO {
|
||||
return s.fbos.fbos[idx]
|
||||
}
|
||||
|
||||
func (s *stenciler) begin(sizes []image.Point) {
|
||||
s.ctx.BlendFunc(backend.BlendFactorOne, backend.BlendFactorOne)
|
||||
s.fbos.resize(s.ctx, sizes)
|
||||
s.ctx.BindProgram(s.prog.prog.prog)
|
||||
s.ctx.BindInputLayout(s.prog.layout)
|
||||
s.ctx.BindIndexBuffer(s.indexBuf)
|
||||
}
|
||||
|
||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
|
||||
// Transform UI coordinates to OpenGL coordinates.
|
||||
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
|
||||
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
|
||||
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
|
||||
s.prog.uniforms.vert.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
|
||||
s.prog.uniforms.vert.pathOffset = [2]float32{offset.X, offset.Y}
|
||||
s.prog.prog.UploadUniforms()
|
||||
// Draw in batches that fit in uint16 indices.
|
||||
start := 0
|
||||
nquads := data.ncurves / 4
|
||||
for start < nquads {
|
||||
batch := nquads - start
|
||||
if max := pathBatchSize; batch > max {
|
||||
batch = max
|
||||
}
|
||||
off := vertStride * start * 4
|
||||
s.ctx.BindVertexBuffer(data.data, vertStride, off)
|
||||
s.ctx.DrawElements(backend.DrawModeTriangles, 0, batch*6)
|
||||
start += batch
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
p.coverer.cover(z, mat, col, scale, off, uvTrans, coverScale, coverOff)
|
||||
}
|
||||
|
||||
func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
p := c.prog[mat]
|
||||
c.ctx.BindProgram(p.prog)
|
||||
var uniforms *coverUniforms
|
||||
switch mat {
|
||||
case materialColor:
|
||||
c.colUniforms.frag.color = col
|
||||
uniforms = &c.colUniforms.vert.coverUniforms
|
||||
case materialTexture:
|
||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||
c.texUniforms.vert.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
c.texUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
uniforms = &c.texUniforms.vert.coverUniforms
|
||||
}
|
||||
uniforms.z = z
|
||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||
p.UploadUniforms()
|
||||
c.ctx.DrawArrays(backend.DrawModeTriangleStrip, 0, 4)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Check that struct vertex has the expected size and
|
||||
// that it contains no padding.
|
||||
if unsafe.Sizeof(*(*vertex)(nil)) != vertStride {
|
||||
panic("unexpected struct size")
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gioui.org/gpu/backend"
|
||||
)
|
||||
|
||||
type timers struct {
|
||||
backend backend.Device
|
||||
timers []*timer
|
||||
}
|
||||
|
||||
type timer struct {
|
||||
Elapsed time.Duration
|
||||
backend backend.Device
|
||||
timer backend.Timer
|
||||
state timerState
|
||||
}
|
||||
|
||||
type timerState uint8
|
||||
|
||||
const (
|
||||
timerIdle timerState = iota
|
||||
timerRunning
|
||||
timerWaiting
|
||||
)
|
||||
|
||||
func newTimers(b backend.Device) *timers {
|
||||
return &timers{
|
||||
backend: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *timers) newTimer() *timer {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
tt := &timer{
|
||||
backend: t.backend,
|
||||
timer: t.backend.NewTimer(),
|
||||
}
|
||||
t.timers = append(t.timers, tt)
|
||||
return tt
|
||||
}
|
||||
|
||||
func (t *timer) begin() {
|
||||
if t == nil || t.state != timerIdle {
|
||||
return
|
||||
}
|
||||
t.timer.Begin()
|
||||
t.state = timerRunning
|
||||
}
|
||||
|
||||
func (t *timer) end() {
|
||||
if t == nil || t.state != timerRunning {
|
||||
return
|
||||
}
|
||||
t.timer.End()
|
||||
t.state = timerWaiting
|
||||
}
|
||||
|
||||
func (t *timers) ready() bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
for _, tt := range t.timers {
|
||||
switch tt.state {
|
||||
case timerIdle:
|
||||
continue
|
||||
case timerRunning:
|
||||
return false
|
||||
}
|
||||
d, ok := tt.timer.Duration()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
tt.state = timerIdle
|
||||
tt.Elapsed = d
|
||||
}
|
||||
return t.backend.IsTimeContinuous()
|
||||
}
|
||||
|
||||
func (t *timers) release() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
for _, tt := range t.timers {
|
||||
tt.timer.Release()
|
||||
}
|
||||
t.timers = nil
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package f32color
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// RGBA is a 32 bit floating point linear space color.
|
||||
type RGBA struct {
|
||||
R, G, B, A float32
|
||||
}
|
||||
|
||||
// Array returns rgba values in a [4]float32 array.
|
||||
func (rgba RGBA) Array() [4]float32 {
|
||||
return [4]float32{rgba.R, rgba.G, rgba.B, rgba.A}
|
||||
}
|
||||
|
||||
// Float32 returns r, g, b, a values.
|
||||
func (col RGBA) Float32() (r, g, b, a float32) {
|
||||
return col.R, col.G, col.B, col.A
|
||||
}
|
||||
|
||||
// SRGBA converts from linear to sRGB color space.
|
||||
func (col RGBA) SRGB() color.RGBA {
|
||||
return color.RGBA{
|
||||
R: uint8(linearTosRGB(col.R)*255 + .5),
|
||||
G: uint8(linearTosRGB(col.G)*255 + .5),
|
||||
B: uint8(linearTosRGB(col.B)*255 + .5),
|
||||
A: uint8(col.A*255 + .5),
|
||||
}
|
||||
}
|
||||
|
||||
// Opaque returns the color without alpha component.
|
||||
func (col RGBA) Opaque() RGBA {
|
||||
col.A = 1.0
|
||||
return col
|
||||
}
|
||||
|
||||
// RGBAFromSRGB converts from SRGBA to RGBA.
|
||||
func RGBAFromSRGB(col color.RGBA) RGBA {
|
||||
r, g, b, a := col.RGBA()
|
||||
return RGBA{
|
||||
R: sRGBToLinear(float32(r) / 0xffff),
|
||||
G: sRGBToLinear(float32(g) / 0xffff),
|
||||
B: sRGBToLinear(float32(b) / 0xffff),
|
||||
A: float32(a) / 0xFFFF,
|
||||
}
|
||||
}
|
||||
|
||||
// linearTosRGB transforms color value from linear to sRGB.
|
||||
func linearTosRGB(c float32) float32 {
|
||||
// Formula from EXT_sRGB.
|
||||
switch {
|
||||
case c <= 0:
|
||||
return 0
|
||||
case 0 < c && c < 0.0031308:
|
||||
return 12.92 * c
|
||||
case 0.0031308 <= c && c < 1:
|
||||
return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// sRGBToLinear transforms color value from sRGB to linear.
|
||||
func sRGBToLinear(c float32) float32 {
|
||||
// Formula from EXT_sRGB.
|
||||
if c <= 0.04045 {
|
||||
return c / 12.92
|
||||
} else {
|
||||
return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package fling
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type Animation struct {
|
||||
// Current offset in pixels.
|
||||
x float32
|
||||
// Initial time.
|
||||
t0 time.Time
|
||||
// Initial velocity in pixels pr second.
|
||||
v0 float32
|
||||
}
|
||||
|
||||
var (
|
||||
// Pixels/second.
|
||||
minFlingVelocity = unit.Dp(50)
|
||||
maxFlingVelocity = unit.Dp(8000)
|
||||
)
|
||||
|
||||
const (
|
||||
thresholdVelocity = 1
|
||||
)
|
||||
|
||||
// Start a fling given a starting velocity. Returns whether a
|
||||
// fling was started.
|
||||
func (f *Animation) Start(c unit.Metric, now time.Time, velocity float32) bool {
|
||||
min := float32(c.Px(minFlingVelocity))
|
||||
v := velocity
|
||||
if -min <= v && v <= min {
|
||||
return false
|
||||
}
|
||||
max := float32(c.Px(maxFlingVelocity))
|
||||
if v > max {
|
||||
v = max
|
||||
} else if v < -max {
|
||||
v = -max
|
||||
}
|
||||
f.init(now, v)
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Animation) init(now time.Time, v0 float32) {
|
||||
f.t0 = now
|
||||
f.v0 = v0
|
||||
f.x = 0
|
||||
}
|
||||
|
||||
func (f *Animation) Active() bool {
|
||||
return f.v0 != 0
|
||||
}
|
||||
|
||||
// Tick computes and returns a fling distance since
|
||||
// the last time Tick was called.
|
||||
func (f *Animation) Tick(now time.Time) int {
|
||||
if !f.Active() {
|
||||
return 0
|
||||
}
|
||||
var k float32
|
||||
if runtime.GOOS == "darwin" {
|
||||
k = -2 // iOS
|
||||
} else {
|
||||
k = -4.2 // Android and default
|
||||
}
|
||||
t := now.Sub(f.t0)
|
||||
// The acceleration x''(t) of a point mass with a drag
|
||||
// force, f, proportional with velocity, x'(t), is
|
||||
// governed by the equation
|
||||
//
|
||||
// x''(t) = kx'(t)
|
||||
//
|
||||
// Given the starting position x(0) = 0, the starting
|
||||
// velocity x'(0) = v0, the position is then
|
||||
// given by
|
||||
//
|
||||
// x(t) = v0*e^(k*t)/k - v0/k
|
||||
//
|
||||
ekt := float32(math.Exp(float64(k) * t.Seconds()))
|
||||
x := f.v0*ekt/k - f.v0/k
|
||||
dist := x - f.x
|
||||
idist := int(dist)
|
||||
f.x += float32(idist)
|
||||
// Solving for the velocity x'(t) gives us
|
||||
//
|
||||
// x'(t) = v0*e^(k*t)
|
||||
v := f.v0 * ekt
|
||||
if -thresholdVelocity < v && v < thresholdVelocity {
|
||||
f.v0 = 0
|
||||
}
|
||||
return idist
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package fling
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Extrapolation computes a 1-dimensional velocity estimate
|
||||
// for a set of timestamped points using the least squares
|
||||
// fit of a 2nd order polynomial. The same method is used
|
||||
// by Android.
|
||||
type Extrapolation struct {
|
||||
// Index into points.
|
||||
idx int
|
||||
// Circular buffer of samples.
|
||||
samples []sample
|
||||
lastValue float32
|
||||
// Pre-allocated cache for samples.
|
||||
cache [historySize]sample
|
||||
|
||||
// Filtered values and times
|
||||
values [historySize]float32
|
||||
times [historySize]float32
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
t time.Duration
|
||||
v float32
|
||||
}
|
||||
|
||||
type matrix struct {
|
||||
rows, cols int
|
||||
data []float32
|
||||
}
|
||||
|
||||
type Estimate struct {
|
||||
Velocity float32
|
||||
Distance float32
|
||||
}
|
||||
|
||||
type coefficients [degree + 1]float32
|
||||
|
||||
const (
|
||||
degree = 2
|
||||
historySize = 20
|
||||
maxAge = 100 * time.Millisecond
|
||||
maxSampleGap = 40 * time.Millisecond
|
||||
)
|
||||
|
||||
// SampleDelta adds a relative sample to the estimation.
|
||||
func (e *Extrapolation) SampleDelta(t time.Duration, delta float32) {
|
||||
val := delta + e.lastValue
|
||||
e.Sample(t, val)
|
||||
}
|
||||
|
||||
// Sample adds an absolute sample to the estimation.
|
||||
func (e *Extrapolation) Sample(t time.Duration, val float32) {
|
||||
e.lastValue = val
|
||||
if e.samples == nil {
|
||||
e.samples = e.cache[:0]
|
||||
}
|
||||
s := sample{
|
||||
t: t,
|
||||
v: val,
|
||||
}
|
||||
if e.idx == len(e.samples) && e.idx < cap(e.samples) {
|
||||
e.samples = append(e.samples, s)
|
||||
} else {
|
||||
e.samples[e.idx] = s
|
||||
}
|
||||
e.idx++
|
||||
if e.idx == cap(e.samples) {
|
||||
e.idx = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Velocity returns an estimate of the implied velocity and
|
||||
// distance for the points sampled, or zero if the estimation method
|
||||
// failed.
|
||||
func (e *Extrapolation) Estimate() Estimate {
|
||||
if len(e.samples) == 0 {
|
||||
return Estimate{}
|
||||
}
|
||||
values := e.values[:0]
|
||||
times := e.times[:0]
|
||||
first := e.get(0)
|
||||
t := first.t
|
||||
// Walk backwards collecting samples.
|
||||
for i := 0; i < len(e.samples); i++ {
|
||||
p := e.get(-i)
|
||||
age := first.t - p.t
|
||||
if age >= maxAge || t-p.t >= maxSampleGap {
|
||||
// If the samples are too old or
|
||||
// too much time passed between samples
|
||||
// assume they're not part of the fling.
|
||||
break
|
||||
}
|
||||
t = p.t
|
||||
values = append(values, first.v-p.v)
|
||||
times = append(times, float32((-age).Seconds()))
|
||||
}
|
||||
coef, ok := polyFit(times, values)
|
||||
if !ok {
|
||||
return Estimate{}
|
||||
}
|
||||
dist := values[len(values)-1] - values[0]
|
||||
return Estimate{
|
||||
Velocity: coef[1],
|
||||
Distance: dist,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Extrapolation) get(i int) sample {
|
||||
idx := (e.idx + i - 1 + len(e.samples)) % len(e.samples)
|
||||
return e.samples[idx]
|
||||
}
|
||||
|
||||
// fit computes the least squares polynomial fit for
|
||||
// the set of points in X, Y. If the fitting fails
|
||||
// because of contradicting or insufficient data,
|
||||
// fit returns false.
|
||||
func polyFit(X, Y []float32) (coefficients, bool) {
|
||||
if len(X) != len(Y) {
|
||||
panic("X and Y lengths differ")
|
||||
}
|
||||
if len(X) <= degree {
|
||||
// Not enough points to fit a curve.
|
||||
return coefficients{}, false
|
||||
}
|
||||
|
||||
// Use a method similar to Android's VelocityTracker.cpp:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/56a2301/libs/androidfw/VelocityTracker.cpp
|
||||
// where all weights are 1.
|
||||
|
||||
// First, expand the X vector to the matrix A in column-major order.
|
||||
A := newMatrix(degree+1, len(X))
|
||||
for i, x := range X {
|
||||
A.set(0, i, 1)
|
||||
for j := 1; j < A.rows; j++ {
|
||||
A.set(j, i, A.get(j-1, i)*x)
|
||||
}
|
||||
}
|
||||
|
||||
Q, Rt, ok := decomposeQR(A)
|
||||
if !ok {
|
||||
return coefficients{}, false
|
||||
}
|
||||
// Solve R*B = Qt*Y for B, which is then the polynomial coefficients.
|
||||
// Since R is upper triangular, we can proceed from bottom right to
|
||||
// upper left.
|
||||
// https://en.wikipedia.org/wiki/Non-linear_least_squares
|
||||
var B coefficients
|
||||
for i := Q.rows - 1; i >= 0; i-- {
|
||||
B[i] = dot(Q.col(i), Y)
|
||||
for j := Q.rows - 1; j > i; j-- {
|
||||
B[i] -= Rt.get(i, j) * B[j]
|
||||
}
|
||||
B[i] /= Rt.get(i, i)
|
||||
}
|
||||
return B, true
|
||||
}
|
||||
|
||||
// decomposeQR computes and returns Q, Rt where Q*transpose(Rt) = A, if
|
||||
// possible. R is guaranteed to be upper triangular and only the square
|
||||
// part of Rt is returned.
|
||||
func decomposeQR(A *matrix) (*matrix, *matrix, bool) {
|
||||
// Gram-Schmidt QR decompose A where Q*R = A.
|
||||
// https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
||||
Q := newMatrix(A.rows, A.cols) // Column-major.
|
||||
Rt := newMatrix(A.rows, A.rows) // R transposed, row-major.
|
||||
for i := 0; i < Q.rows; i++ {
|
||||
// Copy A column.
|
||||
for j := 0; j < Q.cols; j++ {
|
||||
Q.set(i, j, A.get(i, j))
|
||||
}
|
||||
// Subtract projections. Note that int the projection
|
||||
//
|
||||
// proju a = <u, a>/<u, u> u
|
||||
//
|
||||
// the normalized column e replaces u, where <e, e> = 1:
|
||||
//
|
||||
// proje a = <e, a>/<e, e> e = <e, a> e
|
||||
for j := 0; j < i; j++ {
|
||||
d := dot(Q.col(j), Q.col(i))
|
||||
for k := 0; k < Q.cols; k++ {
|
||||
Q.set(i, k, Q.get(i, k)-d*Q.get(j, k))
|
||||
}
|
||||
}
|
||||
// Normalize Q columns.
|
||||
n := norm(Q.col(i))
|
||||
if n < 0.000001 {
|
||||
// Degenerate data, no solution.
|
||||
return nil, nil, false
|
||||
}
|
||||
invNorm := 1 / n
|
||||
for j := 0; j < Q.cols; j++ {
|
||||
Q.set(i, j, Q.get(i, j)*invNorm)
|
||||
}
|
||||
// Update Rt.
|
||||
for j := i; j < Rt.cols; j++ {
|
||||
Rt.set(i, j, dot(Q.col(i), A.col(j)))
|
||||
}
|
||||
}
|
||||
return Q, Rt, true
|
||||
}
|
||||
|
||||
func norm(V []float32) float32 {
|
||||
var n float32
|
||||
for _, v := range V {
|
||||
n += v * v
|
||||
}
|
||||
return float32(math.Sqrt(float64(n)))
|
||||
}
|
||||
|
||||
func dot(V1, V2 []float32) float32 {
|
||||
var d float32
|
||||
for i, v1 := range V1 {
|
||||
d += v1 * V2[i]
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func newMatrix(rows, cols int) *matrix {
|
||||
return &matrix{
|
||||
rows: rows,
|
||||
cols: cols,
|
||||
data: make([]float32, rows*cols),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *matrix) set(row, col int, v float32) {
|
||||
if row < 0 || row >= m.rows {
|
||||
panic("row out of range")
|
||||
}
|
||||
if col < 0 || col >= m.cols {
|
||||
panic("col out of range")
|
||||
}
|
||||
m.data[row*m.cols+col] = v
|
||||
}
|
||||
|
||||
func (m *matrix) get(row, col int) float32 {
|
||||
if row < 0 || row >= m.rows {
|
||||
panic("row out of range")
|
||||
}
|
||||
if col < 0 || col >= m.cols {
|
||||
panic("col out of range")
|
||||
}
|
||||
return m.data[row*m.cols+col]
|
||||
}
|
||||
|
||||
func (m *matrix) col(c int) []float32 {
|
||||
return m.data[c*m.cols : (c+1)*m.cols]
|
||||
}
|
||||
|
||||
func (m *matrix) approxEqual(m2 *matrix) bool {
|
||||
if m.rows != m2.rows || m.cols != m2.cols {
|
||||
return false
|
||||
}
|
||||
const epsilon = 0.00001
|
||||
for row := 0; row < m.rows; row++ {
|
||||
for col := 0; col < m.cols; col++ {
|
||||
d := m2.get(row, col) - m.get(row, col)
|
||||
if d < -epsilon || d > epsilon {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *matrix) transpose() *matrix {
|
||||
t := &matrix{
|
||||
rows: m.cols,
|
||||
cols: m.rows,
|
||||
data: make([]float32, len(m.data)),
|
||||
}
|
||||
for i := 0; i < m.rows; i++ {
|
||||
for j := 0; j < m.cols; j++ {
|
||||
t.set(j, i, m.get(i, j))
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (m *matrix) mul(m2 *matrix) *matrix {
|
||||
if m.rows != m2.cols {
|
||||
panic("mismatched matrices")
|
||||
}
|
||||
mm := &matrix{
|
||||
rows: m.rows,
|
||||
cols: m2.cols,
|
||||
data: make([]float32, m.rows*m2.cols),
|
||||
}
|
||||
for i := 0; i < mm.rows; i++ {
|
||||
for j := 0; j < mm.cols; j++ {
|
||||
var v float32
|
||||
for k := 0; k < m.rows; k++ {
|
||||
v += m.get(k, j) * m2.get(i, k)
|
||||
}
|
||||
mm.set(i, j, v)
|
||||
}
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
func (m *matrix) String() string {
|
||||
var b strings.Builder
|
||||
for i := 0; i < m.rows; i++ {
|
||||
for j := 0; j < m.cols; j++ {
|
||||
v := m.get(i, j)
|
||||
b.WriteString(strconv.FormatFloat(float64(v), 'g', -1, 32))
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c coefficients) approxEqual(c2 coefficients) bool {
|
||||
const epsilon = 0.00001
|
||||
for i, v := range c {
|
||||
d := v - c2[i]
|
||||
if d < -epsilon || d > epsilon {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package opconst
|
||||
|
||||
type OpType byte
|
||||
|
||||
// Start at a high number for easier debugging.
|
||||
const firstOpIndex = 200
|
||||
|
||||
const (
|
||||
TypeMacro OpType = iota + firstOpIndex
|
||||
TypeCall
|
||||
TypeTransform
|
||||
TypeLayer
|
||||
TypeInvalidate
|
||||
TypeImage
|
||||
TypePaint
|
||||
TypeColor
|
||||
TypeArea
|
||||
TypePointerInput
|
||||
TypePass
|
||||
TypeKeyInput
|
||||
TypeHideInput
|
||||
TypePush
|
||||
TypePop
|
||||
TypeAux
|
||||
TypeClip
|
||||
TypeProfile
|
||||
)
|
||||
|
||||
const (
|
||||
TypeMacroLen = 1 + 4 + 4
|
||||
TypeCallLen = 1 + 4 + 4
|
||||
TypeTransformLen = 1 + 4*6
|
||||
TypeLayerLen = 1
|
||||
TypeRedrawLen = 1 + 8
|
||||
TypeImageLen = 1 + 4*4
|
||||
TypePaintLen = 1 + 4*4
|
||||
TypeColorLen = 1 + 4
|
||||
TypeAreaLen = 1 + 1 + 4*4
|
||||
TypePointerInputLen = 1 + 1 + 1
|
||||
TypePassLen = 1 + 1
|
||||
TypeKeyInputLen = 1 + 1
|
||||
TypeHideInputLen = 1
|
||||
TypePushLen = 1
|
||||
TypePopLen = 1
|
||||
TypeAuxLen = 1
|
||||
TypeClipLen = 1 + 4*4
|
||||
TypeProfileLen = 1
|
||||
)
|
||||
|
||||
func (t OpType) Size() int {
|
||||
return [...]int{
|
||||
TypeMacroLen,
|
||||
TypeCallLen,
|
||||
TypeTransformLen,
|
||||
TypeLayerLen,
|
||||
TypeRedrawLen,
|
||||
TypeImageLen,
|
||||
TypePaintLen,
|
||||
TypeColorLen,
|
||||
TypeAreaLen,
|
||||
TypePointerInputLen,
|
||||
TypePassLen,
|
||||
TypeKeyInputLen,
|
||||
TypeHideInputLen,
|
||||
TypePushLen,
|
||||
TypePopLen,
|
||||
TypeAuxLen,
|
||||
TypeClipLen,
|
||||
TypeProfileLen,
|
||||
}[t-firstOpIndex]
|
||||
}
|
||||
|
||||
func (t OpType) NumRefs() int {
|
||||
switch t {
|
||||
case TypeKeyInput, TypePointerInput, TypeProfile, TypeCall:
|
||||
return 1
|
||||
case TypeImage:
|
||||
return 2
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package ops
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/opconst"
|
||||
)
|
||||
|
||||
const QuadSize = 4 * 2 * 3
|
||||
|
||||
type Quad struct {
|
||||
From, Ctrl, To f32.Point
|
||||
}
|
||||
|
||||
func (q Quad) Transform(t f32.Affine2D) Quad {
|
||||
q.From = t.Transform(q.From)
|
||||
q.Ctrl = t.Transform(q.Ctrl)
|
||||
q.To = t.Transform(q.To)
|
||||
return q
|
||||
}
|
||||
|
||||
func EncodeQuad(d []byte, q Quad) {
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(d[0:], math.Float32bits(q.From.X))
|
||||
bo.PutUint32(d[4:], math.Float32bits(q.From.Y))
|
||||
bo.PutUint32(d[8:], math.Float32bits(q.Ctrl.X))
|
||||
bo.PutUint32(d[12:], math.Float32bits(q.Ctrl.Y))
|
||||
bo.PutUint32(d[16:], math.Float32bits(q.To.X))
|
||||
bo.PutUint32(d[20:], math.Float32bits(q.To.Y))
|
||||
}
|
||||
|
||||
func DecodeQuad(d []byte) (q Quad) {
|
||||
bo := binary.LittleEndian
|
||||
q.From.X = math.Float32frombits(bo.Uint32(d[0:]))
|
||||
q.From.Y = math.Float32frombits(bo.Uint32(d[4:]))
|
||||
q.Ctrl.X = math.Float32frombits(bo.Uint32(d[8:]))
|
||||
q.Ctrl.Y = math.Float32frombits(bo.Uint32(d[12:]))
|
||||
q.To.X = math.Float32frombits(bo.Uint32(d[16:]))
|
||||
q.To.Y = math.Float32frombits(bo.Uint32(d[20:]))
|
||||
return
|
||||
}
|
||||
|
||||
func DecodeTransform(d []byte) (t f32.Affine2D) {
|
||||
if opconst.OpType(d[0]) != opconst.TypeTransform {
|
||||
panic("invalid op")
|
||||
}
|
||||
if len(d) < 1+6*4 {
|
||||
panic("too short buffer")
|
||||
}
|
||||
return decodeAffine2D(d[1:])
|
||||
}
|
||||
|
||||
func decodeAffine2D(data []byte) f32.Affine2D {
|
||||
bo := binary.LittleEndian
|
||||
a := math.Float32frombits(bo.Uint32(data))
|
||||
b := math.Float32frombits(bo.Uint32(data[4*1:]))
|
||||
c := math.Float32frombits(bo.Uint32(data[4*2:]))
|
||||
d := math.Float32frombits(bo.Uint32(data[4*3:]))
|
||||
e := math.Float32frombits(bo.Uint32(data[4*4:]))
|
||||
f := math.Float32frombits(bo.Uint32(data[4*5:]))
|
||||
return f32.NewAffine2D(a, b, c, d, e, f)
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package ops
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Reader parses an ops list.
|
||||
type Reader struct {
|
||||
pc pc
|
||||
stack []macro
|
||||
ops *op.Ops
|
||||
}
|
||||
|
||||
// EncodedOp represents an encoded op returned by
|
||||
// Reader.
|
||||
type EncodedOp struct {
|
||||
Key Key
|
||||
Data []byte
|
||||
Refs []interface{}
|
||||
}
|
||||
|
||||
// Key is a unique key for a given op.
|
||||
type Key struct {
|
||||
ops *op.Ops
|
||||
pc int
|
||||
version int
|
||||
sx, hx, sy, hy float32
|
||||
}
|
||||
|
||||
// Shadow of op.MacroOp.
|
||||
type macroOp struct {
|
||||
ops *op.Ops
|
||||
pc pc
|
||||
}
|
||||
|
||||
type pc struct {
|
||||
data int
|
||||
refs int
|
||||
}
|
||||
|
||||
type macro struct {
|
||||
ops *op.Ops
|
||||
retPC pc
|
||||
endPC pc
|
||||
}
|
||||
|
||||
type opMacroDef struct {
|
||||
endpc pc
|
||||
}
|
||||
|
||||
// Reset start reading from the op list.
|
||||
func (r *Reader) Reset(ops *op.Ops) {
|
||||
r.stack = r.stack[:0]
|
||||
r.pc = pc{}
|
||||
r.ops = ops
|
||||
}
|
||||
|
||||
func (k Key) SetTransform(t f32.Affine2D) Key {
|
||||
sx, hx, _, hy, sy, _ := t.Elems()
|
||||
k.sx = sx
|
||||
k.hx = hx
|
||||
k.hy = hy
|
||||
k.sy = sy
|
||||
return k
|
||||
}
|
||||
|
||||
func (r *Reader) Decode() (EncodedOp, bool) {
|
||||
if r.ops == nil {
|
||||
return EncodedOp{}, false
|
||||
}
|
||||
for {
|
||||
if len(r.stack) > 0 {
|
||||
b := r.stack[len(r.stack)-1]
|
||||
if r.pc == b.endPC {
|
||||
r.ops = b.ops
|
||||
r.pc = b.retPC
|
||||
r.stack = r.stack[:len(r.stack)-1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
data := r.ops.Data()
|
||||
data = data[r.pc.data:]
|
||||
if len(data) == 0 {
|
||||
return EncodedOp{}, false
|
||||
}
|
||||
key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.Version()}
|
||||
t := opconst.OpType(data[0])
|
||||
n := t.Size()
|
||||
nrefs := t.NumRefs()
|
||||
data = data[:n]
|
||||
refs := r.ops.Refs()
|
||||
refs = refs[r.pc.refs:]
|
||||
refs = refs[:nrefs]
|
||||
switch t {
|
||||
case opconst.TypeAux:
|
||||
// An Aux operations is always wrapped in a macro, and
|
||||
// its length is the remaining space.
|
||||
block := r.stack[len(r.stack)-1]
|
||||
n += block.endPC.data - r.pc.data - opconst.TypeAuxLen
|
||||
data = data[:n]
|
||||
case opconst.TypeCall:
|
||||
var op macroOp
|
||||
op.decode(data, refs)
|
||||
macroData := op.ops.Data()[op.pc.data:]
|
||||
if opconst.OpType(macroData[0]) != opconst.TypeMacro {
|
||||
panic("invalid macro reference")
|
||||
}
|
||||
var opDef opMacroDef
|
||||
opDef.decode(macroData[:opconst.TypeMacro.Size()])
|
||||
retPC := r.pc
|
||||
retPC.data += n
|
||||
retPC.refs += nrefs
|
||||
r.stack = append(r.stack, macro{
|
||||
ops: r.ops,
|
||||
retPC: retPC,
|
||||
endPC: opDef.endpc,
|
||||
})
|
||||
r.ops = op.ops
|
||||
r.pc = op.pc
|
||||
r.pc.data += opconst.TypeMacro.Size()
|
||||
r.pc.refs += opconst.TypeMacro.NumRefs()
|
||||
continue
|
||||
case opconst.TypeMacro:
|
||||
var op opMacroDef
|
||||
op.decode(data)
|
||||
r.pc = op.endpc
|
||||
continue
|
||||
}
|
||||
r.pc.data += n
|
||||
r.pc.refs += nrefs
|
||||
return EncodedOp{Key: key, Data: data, Refs: refs}, true
|
||||
}
|
||||
}
|
||||
|
||||
func (op *opMacroDef) decode(data []byte) {
|
||||
if opconst.OpType(data[0]) != opconst.TypeMacro {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
dataIdx := int(int32(bo.Uint32(data[1:])))
|
||||
refsIdx := int(int32(bo.Uint32(data[5:])))
|
||||
*op = opMacroDef{
|
||||
endpc: pc{
|
||||
data: dataIdx,
|
||||
refs: refsIdx,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *macroOp) decode(data []byte, refs []interface{}) {
|
||||
if opconst.OpType(data[0]) != opconst.TypeCall {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
dataIdx := int(int32(bo.Uint32(data[1:])))
|
||||
refsIdx := int(int32(bo.Uint32(data[5:])))
|
||||
*m = macroOp{
|
||||
ops: refs[0].(*op.Ops),
|
||||
pc: pc{
|
||||
data: dataIdx,
|
||||
refs: refsIdx,
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package unsafe
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// BytesView returns a byte slice view of a slice.
|
||||
func BytesView(s interface{}) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
first := v.Index(0)
|
||||
sz := int(first.Type().Size())
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))),
|
||||
Len: v.Len() * sz,
|
||||
Cap: v.Cap() * sz,
|
||||
}))
|
||||
}
|
||||
|
||||
// SliceOf returns a slice from a (native) pointer.
|
||||
func SliceOf(s uintptr) []byte {
|
||||
if s == 0 {
|
||||
return nil
|
||||
}
|
||||
sh := reflect.SliceHeader{
|
||||
Data: s,
|
||||
Len: 1 << 30,
|
||||
Cap: 1 << 30,
|
||||
}
|
||||
return *(*[]byte)(unsafe.Pointer(&sh))
|
||||
}
|
||||
|
||||
// GoString convert a NUL-terminated C string
|
||||
// to a Go string.
|
||||
func GoString(s []byte) string {
|
||||
i := 0
|
||||
for {
|
||||
if s[i] == 0 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return string(s[:i])
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package event contains the types for event handling.
|
||||
|
||||
The Queue interface is the protocol for receiving external events.
|
||||
|
||||
For example:
|
||||
|
||||
var queue event.Queue = ...
|
||||
|
||||
for _, e := range queue.Events(h) {
|
||||
switch e.(type) {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
In general, handlers must be declared before events become
|
||||
available. Other packages such as pointer and key provide
|
||||
the means for declaring handlers for specific event types.
|
||||
|
||||
The following example declares a handler ready for key input:
|
||||
|
||||
import gioui.org/io/key
|
||||
|
||||
ops := new(op.Ops)
|
||||
var h *Handler = ...
|
||||
key.InputOp{Tag: h}.Add(ops)
|
||||
|
||||
*/
|
||||
package event
|
||||
|
||||
// Queue maps an event handler key to the events
|
||||
// available to the handler.
|
||||
type Queue interface {
|
||||
// Events returns the available events for an
|
||||
// event handler tag.
|
||||
Events(t Tag) []Event
|
||||
}
|
||||
|
||||
// Tag is the stable identifier for an event handler.
|
||||
// For a handler h, the tag is typically &h.
|
||||
type Tag interface{}
|
||||
|
||||
// Event is the marker interface for events.
|
||||
type Event interface {
|
||||
ImplementsEvent()
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package key implements key and text events and operations.
|
||||
|
||||
The InputOp operations is used for declaring key input handlers. Use
|
||||
an implementation of the Queue interface from package ui to receive
|
||||
events.
|
||||
*/
|
||||
package key
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// InputOp declares a handler ready for key events.
|
||||
// Key events are in general only delivered to the
|
||||
// focused key handler. Set the Focus flag to request
|
||||
// the focus.
|
||||
type InputOp struct {
|
||||
Tag event.Tag
|
||||
Focus bool
|
||||
}
|
||||
|
||||
// HideInputOp request that any on screen text input
|
||||
// be hidden.
|
||||
type HideInputOp struct{}
|
||||
|
||||
// A FocusEvent is generated when a handler gains or loses
|
||||
// focus.
|
||||
type FocusEvent struct {
|
||||
Focus bool
|
||||
}
|
||||
|
||||
// An Event is generated when a key is pressed. For text input
|
||||
// use EditEvent.
|
||||
type Event struct {
|
||||
// Name of the key. For letters, the upper case form is used, via
|
||||
// unicode.ToUpper. The shift modifier is taken into account, all other
|
||||
// modifiers are ignored. For example, the "shift-1" and "ctrl-shift-1"
|
||||
// combinations both give the Name "!" with the US keyboard layout.
|
||||
Name string
|
||||
// Modifiers is the set of active modifiers when the key was pressed.
|
||||
Modifiers Modifiers
|
||||
}
|
||||
|
||||
// An EditEvent is generated when text is input.
|
||||
type EditEvent struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
type Modifiers uint32
|
||||
|
||||
const (
|
||||
// ModCtrl is the ctrl modifier key.
|
||||
ModCtrl Modifiers = 1 << iota
|
||||
// ModCommand is the command modifier key
|
||||
// found on Apple keyboards.
|
||||
ModCommand
|
||||
// ModShift is the shift modifier key.
|
||||
ModShift
|
||||
// ModAlt is the alt modifier key, or the option
|
||||
// key on Apple keyboards.
|
||||
ModAlt
|
||||
// ModSuper is the "logo" modifier key, often
|
||||
// represented by a Windows logo.
|
||||
ModSuper
|
||||
)
|
||||
|
||||
const (
|
||||
// Names for special keys.
|
||||
NameLeftArrow = "←"
|
||||
NameRightArrow = "→"
|
||||
NameUpArrow = "↑"
|
||||
NameDownArrow = "↓"
|
||||
NameReturn = "⏎"
|
||||
NameEnter = "⌤"
|
||||
NameEscape = "⎋"
|
||||
NameHome = "⇱"
|
||||
NameEnd = "⇲"
|
||||
NameDeleteBackward = "⌫"
|
||||
NameDeleteForward = "⌦"
|
||||
NamePageUp = "⇞"
|
||||
NamePageDown = "⇟"
|
||||
NameTab = "⇥"
|
||||
)
|
||||
|
||||
// Contain reports whether m contains all modifiers
|
||||
// in m2.
|
||||
func (m Modifiers) Contain(m2 Modifiers) bool {
|
||||
return m&m2 == m2
|
||||
}
|
||||
|
||||
func (h InputOp) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypeKeyInputLen, h.Tag)
|
||||
data[0] = byte(opconst.TypeKeyInput)
|
||||
if h.Focus {
|
||||
data[1] = 1
|
||||
}
|
||||
}
|
||||
|
||||
func (h HideInputOp) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypeHideInputLen)
|
||||
data[0] = byte(opconst.TypeHideInput)
|
||||
}
|
||||
|
||||
func (EditEvent) ImplementsEvent() {}
|
||||
func (Event) ImplementsEvent() {}
|
||||
func (FocusEvent) ImplementsEvent() {}
|
||||
|
||||
func (e Event) String() string {
|
||||
return "{" + string(e.Name) + " " + e.Modifiers.String() + "}"
|
||||
}
|
||||
|
||||
func (m Modifiers) String() string {
|
||||
var strs []string
|
||||
if m.Contain(ModCtrl) {
|
||||
strs = append(strs, "ModCtrl")
|
||||
}
|
||||
if m.Contain(ModCommand) {
|
||||
strs = append(strs, "ModCommand")
|
||||
}
|
||||
if m.Contain(ModShift) {
|
||||
strs = append(strs, "ModShift")
|
||||
}
|
||||
if m.Contain(ModAlt) {
|
||||
strs = append(strs, "ModAlt")
|
||||
}
|
||||
if m.Contain(ModSuper) {
|
||||
strs = append(strs, "ModSuper")
|
||||
}
|
||||
return strings.Join(strs, "|")
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build !darwin
|
||||
|
||||
package key
|
||||
|
||||
// ModShortcut is the platform's shortcut modifier, usually the Ctrl
|
||||
// key. On Apple platforms it is the Cmd key.
|
||||
const ModShortcut = ModCtrl
|
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package key
|
||||
|
||||
// ModShortcut is the platform's shortcut modifier, usually the Ctrl
|
||||
// key. On Apple platforms it is the Cmd key.
|
||||
const ModShortcut = ModCommand
|
@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
/*
|
||||
Package pointer implements pointer events and operations.
|
||||
A pointer is either a mouse controlled cursor or a touch
|
||||
object such as a finger.
|
||||
|
||||
The InputOp operation is used to declare a handler ready for pointer
|
||||
events. Use an event.Queue to receive events.
|
||||
|
||||
Types
|
||||
|
||||
Only events that match a specified list of types are delivered to a handler.
|
||||
|
||||
For example, to receive Press, Drag, and Release events (but not Move, Enter,
|
||||
Leave, or Scroll):
|
||||
|
||||
var ops op.Ops
|
||||
var h *Handler = ...
|
||||
|
||||
pointer.InputOp{
|
||||
Tag: h,
|
||||
Types: pointer.Press | pointer.Drag | pointer.Release,
|
||||
}.Add(ops)
|
||||
|
||||
Cancel events are always delivered.
|
||||
|
||||
Areas
|
||||
|
||||
The area operations are used for specifying the area where
|
||||
subsequent InputOp are active.
|
||||
|
||||
For example, to set up a rectangular hit area:
|
||||
|
||||
r := image.Rectangle{...}
|
||||
pointer.Rect(r).Add(ops)
|
||||
pointer.InputOp{Tag: h}.Add(ops)
|
||||
|
||||
Note that areas compound: the effective area of multiple area
|
||||
operations is the intersection of the areas.
|
||||
|
||||
Matching events
|
||||
|
||||
StackOp operations and input handlers form an implicit tree.
|
||||
Each stack operation is a node, and each input handler is associated
|
||||
with the most recent node.
|
||||
|
||||
For example:
|
||||
|
||||
ops := new(op.Ops)
|
||||
var stack op.StackOp
|
||||
var h1, h2 *Handler
|
||||
|
||||
stack := op.Push(ops)
|
||||
pointer.InputOp{Tag: h1}.Add(Ops)
|
||||
stack.Pop()
|
||||
|
||||
stack = op.Push(ops)
|
||||
pointer.InputOp{Tag: h2}.Add(ops)
|
||||
stack.Pop()
|
||||
|
||||
implies a tree of two inner nodes, each with one pointer handler.
|
||||
|
||||
When determining which handlers match an Event, only handlers whose
|
||||
areas contain the event position are considered. The matching
|
||||
proceeds as follows.
|
||||
|
||||
First, the foremost matching handler is included. If the handler
|
||||
has pass-through enabled, this step is repeated.
|
||||
|
||||
Then, all matching handlers from the current node and all parent
|
||||
nodes are included.
|
||||
|
||||
In the example above, all events will go to h2 only even though both
|
||||
handlers have the same area (the entire screen).
|
||||
|
||||
Pass-through
|
||||
|
||||
The PassOp operations controls the pass-through setting. A handler's
|
||||
pass-through setting is recorded along with the InputOp.
|
||||
|
||||
Pass-through handlers are useful for overlay widgets such as a hidden
|
||||
side drawer. When the user touches the side, both the (transparent)
|
||||
drawer handle and the interface below should receive pointer events.
|
||||
|
||||
Disambiguation
|
||||
|
||||
When more than one handler matches a pointer event, the event queue
|
||||
follows a set of rules for distributing the event.
|
||||
|
||||
As long as the pointer has not received a Press event, all
|
||||
matching handlers receive all events.
|
||||
|
||||
When a pointer is pressed, the set of matching handlers is
|
||||
recorded. The set is not updated according to the pointer position
|
||||
and hit areas. Rather, handlers stay in the matching set until they
|
||||
no longer appear in a InputOp or when another handler in the set
|
||||
grabs the pointer.
|
||||
|
||||
A handler can exclude all other handler from its matching sets
|
||||
by setting the Grab flag in its InputOp. The Grab flag is sticky
|
||||
and stays in effect until the handler no longer appears in any
|
||||
matching sets.
|
||||
|
||||
The losing handlers are notified by a Cancel event.
|
||||
|
||||
For multiple grabbing handlers, the foremost handler wins.
|
||||
|
||||
Priorities
|
||||
|
||||
Handlers know their position in a matching set of a pointer through
|
||||
event priorities. The Shared priority is for matching sets with
|
||||
multiple handlers; the Grabbed priority indicate exclusive access.
|
||||
|
||||
Priorities are useful for deferred gesture matching.
|
||||
|
||||
Consider a scrollable list of clickable elements. When the user touches an
|
||||
element, it is unknown whether the gesture is a click on the element
|
||||
or a drag (scroll) of the list. While the click handler might light up
|
||||
the element in anticipation of a click, the scrolling handler does not
|
||||
scroll on finger movements with lower than Grabbed priority.
|
||||
|
||||
Should the user release the finger, the click handler registers a click.
|
||||
|
||||
However, if the finger moves beyond a threshold, the scrolling handler
|
||||
determines that the gesture is a drag and sets its Grab flag. The
|
||||
click handler receives a Cancel (removing the highlight) and further
|
||||
movements for the scroll handler has priority Grabbed, scrolling the
|
||||
list.
|
||||
*/
|
||||
package pointer
|
@ -0,0 +1,245 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Event is a pointer event.
|
||||
type Event struct {
|
||||
Type Type
|
||||
Source Source
|
||||
// PointerID is the id for the pointer and can be used
|
||||
// to track a particular pointer from Press to
|
||||
// Release or Cancel.
|
||||
PointerID ID
|
||||
// Priority is the priority of the receiving handler
|
||||
// for this event.
|
||||
Priority Priority
|
||||
// Time is when the event was received. The
|
||||
// timestamp is relative to an undefined base.
|
||||
Time time.Duration
|
||||
// Buttons are the set of pressed mouse buttons for this event.
|
||||
Buttons Buttons
|
||||
// Position is the position of the event, relative to
|
||||
// the current transformation, as set by op.TransformOp.
|
||||
Position f32.Point
|
||||
// Scroll is the scroll amount, if any.
|
||||
Scroll f32.Point
|
||||
// Modifiers is the set of active modifiers when
|
||||
// the mouse button was pressed.
|
||||
Modifiers key.Modifiers
|
||||
}
|
||||
|
||||
// AreaOp updates the hit area to the intersection of the current
|
||||
// hit area and the area. The area is transformed before applying
|
||||
// it.
|
||||
type AreaOp struct {
|
||||
kind areaKind
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
// InputOp declares an input handler ready for pointer
|
||||
// events.
|
||||
type InputOp struct {
|
||||
Tag event.Tag
|
||||
// Grab, if set, request that the handler get
|
||||
// Grabbed priority.
|
||||
Grab bool
|
||||
// Types is a bitwise-or of event types to receive.
|
||||
Types Type
|
||||
}
|
||||
|
||||
// PassOp sets the pass-through mode.
|
||||
type PassOp struct {
|
||||
Pass bool
|
||||
}
|
||||
|
||||
type ID uint16
|
||||
|
||||
// Type of an Event.
|
||||
type Type uint8
|
||||
|
||||
// Priority of an Event.
|
||||
type Priority uint8
|
||||
|
||||
// Source of an Event.
|
||||
type Source uint8
|
||||
|
||||
// Buttons is a set of mouse buttons
|
||||
type Buttons uint8
|
||||
|
||||
// Must match app/internal/input.areaKind
|
||||
type areaKind uint8
|
||||
|
||||
const (
|
||||
// A Cancel event is generated when the current gesture is
|
||||
// interrupted by other handlers or the system.
|
||||
Cancel Type = (1 << iota) >> 1
|
||||
// Press of a pointer.
|
||||
Press
|
||||
// Release of a pointer.
|
||||
Release
|
||||
// Move of a pointer.
|
||||
Move
|
||||
// Drag of a pointer.
|
||||
Drag
|
||||
// Pointer enters an area watching for pointer input
|
||||
Enter
|
||||
// Pointer leaves an area watching for pointer input
|
||||
Leave
|
||||
// Scroll of a pointer.
|
||||
Scroll
|
||||
)
|
||||
|
||||
const (
|
||||
// Mouse generated event.
|
||||
Mouse Source = iota
|
||||
// Touch generated event.
|
||||
Touch
|
||||
)
|
||||
|
||||
const (
|
||||
// Shared priority is for handlers that
|
||||
// are part of a matching set larger than 1.
|
||||
Shared Priority = iota
|
||||
// Foremost priority is like Shared, but the
|
||||
// handler is the foremost of the matching set.
|
||||
Foremost
|
||||
// Grabbed is used for matching sets of size 1.
|
||||
Grabbed
|
||||
)
|
||||
|
||||
const (
|
||||
ButtonLeft Buttons = 1 << iota
|
||||
ButtonRight
|
||||
ButtonMiddle
|
||||
)
|
||||
|
||||
const (
|
||||
areaRect areaKind = iota
|
||||
areaEllipse
|
||||
)
|
||||
|
||||
// Rect constructs a rectangular hit area.
|
||||
func Rect(size image.Rectangle) AreaOp {
|
||||
return AreaOp{
|
||||
kind: areaRect,
|
||||
rect: size,
|
||||
}
|
||||
}
|
||||
|
||||
// Ellipse constructs an ellipsoid hit area.
|
||||
func Ellipse(size image.Rectangle) AreaOp {
|
||||
return AreaOp{
|
||||
kind: areaEllipse,
|
||||
rect: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (op AreaOp) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypeAreaLen)
|
||||
data[0] = byte(opconst.TypeArea)
|
||||
data[1] = byte(op.kind)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[2:], uint32(op.rect.Min.X))
|
||||
bo.PutUint32(data[6:], uint32(op.rect.Min.Y))
|
||||
bo.PutUint32(data[10:], uint32(op.rect.Max.X))
|
||||
bo.PutUint32(data[14:], uint32(op.rect.Max.Y))
|
||||
}
|
||||
|
||||
func (h InputOp) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypePointerInputLen, h.Tag)
|
||||
data[0] = byte(opconst.TypePointerInput)
|
||||
if h.Grab {
|
||||
data[1] = 1
|
||||
}
|
||||
data[2] = byte(h.Types)
|
||||
}
|
||||
|
||||
func (op PassOp) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypePassLen)
|
||||
data[0] = byte(opconst.TypePass)
|
||||
if op.Pass {
|
||||
data[1] = 1
|
||||
}
|
||||
}
|
||||
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case Press:
|
||||
return "Press"
|
||||
case Release:
|
||||
return "Release"
|
||||
case Cancel:
|
||||
return "Cancel"
|
||||
case Move:
|
||||
return "Move"
|
||||
case Drag:
|
||||
return "Drag"
|
||||
case Enter:
|
||||
return "Enter"
|
||||
case Leave:
|
||||
return "Leave"
|
||||
case Scroll:
|
||||
return "Scroll"
|
||||
default:
|
||||
panic("unknown Type")
|
||||
}
|
||||
}
|
||||
|
||||
func (p Priority) String() string {
|
||||
switch p {
|
||||
case Shared:
|
||||
return "Shared"
|
||||
case Foremost:
|
||||
return "Foremost"
|
||||
case Grabbed:
|
||||
return "Grabbed"
|
||||
default:
|
||||
panic("unknown priority")
|
||||
}
|
||||
}
|
||||
|
||||
func (s Source) String() string {
|
||||
switch s {
|
||||
case Mouse:
|
||||
return "Mouse"
|
||||
case Touch:
|
||||
return "Touch"
|
||||
default:
|
||||
panic("unknown source")
|
||||
}
|
||||
}
|
||||
|
||||
// Contain reports whether the set b contains
|
||||
// all of the buttons.
|
||||
func (b Buttons) Contain(buttons Buttons) bool {
|
||||
return b&buttons == buttons
|
||||
}
|
||||
|
||||
func (b Buttons) String() string {
|
||||
var strs []string
|
||||
if b.Contain(ButtonLeft) {
|
||||
strs = append(strs, "ButtonLeft")
|
||||
}
|
||||
if b.Contain(ButtonRight) {
|
||||
strs = append(strs, "ButtonRight")
|
||||
}
|
||||
if b.Contain(ButtonMiddle) {
|
||||
strs = append(strs, "ButtonMiddle")
|
||||
}
|
||||
return strings.Join(strs, "|")
|
||||
}
|
||||
|
||||
func (Event) ImplementsEvent() {}
|
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package profiles provides access to rendering
|
||||
// profiles.
|
||||
package profile
|
||||
|
||||
import (
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
// Op registers a handler for receiving
|
||||
// Events.
|
||||
type Op struct {
|
||||
Tag event.Tag
|
||||
}
|
||||
|
||||
// Event contains profile data from a single
|
||||
// rendered frame.
|
||||
type Event struct {
|
||||
// Timings. Very likely to change.
|
||||
Timings string
|
||||
}
|
||||
|
||||
func (p Op) Add(o *op.Ops) {
|
||||
data := o.Write(opconst.TypeProfileLen, p.Tag)
|
||||
data[0] = byte(opconst.TypeProfile)
|
||||
}
|
||||
|
||||
func (p Event) ImplementsEvent() {}
|
@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type TextInputState uint8
|
||||
|
||||
type keyQueue struct {
|
||||
focus event.Tag
|
||||
handlers map[event.Tag]*keyHandler
|
||||
reader ops.Reader
|
||||
state TextInputState
|
||||
}
|
||||
|
||||
type keyHandler struct {
|
||||
active bool
|
||||
}
|
||||
|
||||
type listenerPriority uint8
|
||||
|
||||
const (
|
||||
priNone listenerPriority = iota
|
||||
priDefault
|
||||
priCurrentFocus
|
||||
priNewFocus
|
||||
)
|
||||
|
||||
const (
|
||||
TextInputKeep TextInputState = iota
|
||||
TextInputClose
|
||||
TextInputOpen
|
||||
)
|
||||
|
||||
// InputState returns the last text input state as
|
||||
// determined in Frame.
|
||||
func (q *keyQueue) InputState() TextInputState {
|
||||
return q.state
|
||||
}
|
||||
|
||||
func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) {
|
||||
if q.handlers == nil {
|
||||
q.handlers = make(map[event.Tag]*keyHandler)
|
||||
}
|
||||
for _, h := range q.handlers {
|
||||
h.active = false
|
||||
}
|
||||
q.reader.Reset(root)
|
||||
focus, pri, hide := q.resolveFocus(events)
|
||||
for k, h := range q.handlers {
|
||||
if !h.active {
|
||||
delete(q.handlers, k)
|
||||
if q.focus == k {
|
||||
q.focus = nil
|
||||
hide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if focus != q.focus {
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, key.FocusEvent{Focus: false})
|
||||
}
|
||||
q.focus = focus
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, key.FocusEvent{Focus: true})
|
||||
} else {
|
||||
hide = true
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case pri == priNewFocus:
|
||||
q.state = TextInputOpen
|
||||
case hide:
|
||||
q.state = TextInputClose
|
||||
default:
|
||||
q.state = TextInputKeep
|
||||
}
|
||||
}
|
||||
|
||||
func (q *keyQueue) Push(e event.Event, events *handlerEvents) {
|
||||
if q.focus != nil {
|
||||
events.Add(q.focus, e)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *keyQueue) resolveFocus(events *handlerEvents) (event.Tag, listenerPriority, bool) {
|
||||
var k event.Tag
|
||||
var pri listenerPriority
|
||||
var hide bool
|
||||
loop:
|
||||
for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypeKeyInput:
|
||||
op := decodeKeyInputOp(encOp.Data, encOp.Refs)
|
||||
var newPri listenerPriority
|
||||
switch {
|
||||
case op.Focus:
|
||||
newPri = priNewFocus
|
||||
case op.Tag == q.focus:
|
||||
newPri = priCurrentFocus
|
||||
default:
|
||||
newPri = priDefault
|
||||
}
|
||||
// Switch focus if higher priority or if focus requested.
|
||||
if newPri.replaces(pri) {
|
||||
k, pri = op.Tag, newPri
|
||||
}
|
||||
h, ok := q.handlers[op.Tag]
|
||||
if !ok {
|
||||
h = new(keyHandler)
|
||||
q.handlers[op.Tag] = h
|
||||
// Reset the handler on (each) first appearance.
|
||||
events.Add(op.Tag, key.FocusEvent{Focus: false})
|
||||
}
|
||||
h.active = true
|
||||
case opconst.TypeHideInput:
|
||||
hide = true
|
||||
case opconst.TypePush:
|
||||
newK, newPri, h := q.resolveFocus(events)
|
||||
hide = hide || h
|
||||
if newPri.replaces(pri) {
|
||||
k, pri = newK, newPri
|
||||
}
|
||||
case opconst.TypePop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return k, pri, hide
|
||||
}
|
||||
|
||||
func (p listenerPriority) replaces(p2 listenerPriority) bool {
|
||||
// Favor earliest default focus or latest requested focus.
|
||||
return p > p2 || p == p2 && p == priNewFocus
|
||||
}
|
||||
|
||||
func decodeKeyInputOp(d []byte, refs []interface{}) key.InputOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypeKeyInput {
|
||||
panic("invalid op")
|
||||
}
|
||||
return key.InputOp{
|
||||
Tag: refs[0].(event.Tag),
|
||||
Focus: d[1] != 0,
|
||||
}
|
||||
}
|
@ -0,0 +1,404 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/opconst"
|
||||
"gioui.org/internal/ops"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type pointerQueue struct {
|
||||
hitTree []hitNode
|
||||
areas []areaNode
|
||||
handlers map[event.Tag]*pointerHandler
|
||||
pointers []pointerInfo
|
||||
reader ops.Reader
|
||||
|
||||
scratch []event.Tag
|
||||
}
|
||||
|
||||
type hitNode struct {
|
||||
next int
|
||||
area int
|
||||
// Pass tracks the most recent PassOp mode.
|
||||
pass bool
|
||||
|
||||
// For handler nodes.
|
||||
tag event.Tag
|
||||
}
|
||||
|
||||
type pointerInfo struct {
|
||||
id pointer.ID
|
||||
pressed bool
|
||||
handlers []event.Tag
|
||||
|
||||
// entered tracks the tags that contain the pointer.
|
||||
entered []event.Tag
|
||||
}
|
||||
|
||||
type pointerHandler struct {
|
||||
area int
|
||||
active bool
|
||||
wantsGrab bool
|
||||
types pointer.Type
|
||||
}
|
||||
|
||||
type areaOp struct {
|
||||
kind areaKind
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
type areaNode struct {
|
||||
trans f32.Affine2D
|
||||
next int
|
||||
area areaOp
|
||||
}
|
||||
|
||||
type areaKind uint8
|
||||
|
||||
const (
|
||||
areaRect areaKind = iota
|
||||
areaEllipse
|
||||
)
|
||||
|
||||
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f32.Affine2D, area, node int, pass bool) {
|
||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||
switch opconst.OpType(encOp.Data[0]) {
|
||||
case opconst.TypePush:
|
||||
q.collectHandlers(r, events, t, area, node, pass)
|
||||
case opconst.TypePop:
|
||||
return
|
||||
case opconst.TypePass:
|
||||
op := decodePassOp(encOp.Data)
|
||||
pass = op.Pass
|
||||
case opconst.TypeArea:
|
||||
var op areaOp
|
||||
op.Decode(encOp.Data)
|
||||
q.areas = append(q.areas, areaNode{trans: t, next: area, area: op})
|
||||
area = len(q.areas) - 1
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: node,
|
||||
area: area,
|
||||
pass: pass,
|
||||
})
|
||||
node = len(q.hitTree) - 1
|
||||
case opconst.TypeTransform:
|
||||
dop := ops.DecodeTransform(encOp.Data)
|
||||
t = t.Mul(dop)
|
||||
case opconst.TypePointerInput:
|
||||
op := decodePointerInputOp(encOp.Data, encOp.Refs)
|
||||
q.hitTree = append(q.hitTree, hitNode{
|
||||
next: node,
|
||||
area: area,
|
||||
pass: pass,
|
||||
tag: op.Tag,
|
||||
})
|
||||
node = len(q.hitTree) - 1
|
||||
h, ok := q.handlers[op.Tag]
|
||||
if !ok {
|
||||
h = new(pointerHandler)
|
||||
q.handlers[op.Tag] = h
|
||||
events.Add(op.Tag, pointer.Event{Type: pointer.Cancel})
|
||||
}
|
||||
h.active = true
|
||||
h.area = area
|
||||
h.wantsGrab = h.wantsGrab || op.Grab
|
||||
h.types = h.types | op.Types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
|
||||
// Track whether we're passing through hits.
|
||||
pass := true
|
||||
idx := len(q.hitTree) - 1
|
||||
for idx >= 0 {
|
||||
n := &q.hitTree[idx]
|
||||
if !q.hit(n.area, pos) {
|
||||
idx--
|
||||
continue
|
||||
}
|
||||
pass = pass && n.pass
|
||||
if pass {
|
||||
idx--
|
||||
} else {
|
||||
idx = n.next
|
||||
}
|
||||
if n.tag != nil {
|
||||
if _, exists := q.handlers[n.tag]; exists {
|
||||
*handlers = append(*handlers, n.tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
|
||||
if areaIdx == -1 {
|
||||
return p
|
||||
}
|
||||
return q.areas[areaIdx].trans.Invert().Transform(p)
|
||||
}
|
||||
|
||||
func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
|
||||
for areaIdx != -1 {
|
||||
a := &q.areas[areaIdx]
|
||||
p := a.trans.Invert().Transform(p)
|
||||
if !a.area.Hit(p) {
|
||||
return false
|
||||
}
|
||||
areaIdx = a.next
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *pointerQueue) init() {
|
||||
if q.handlers == nil {
|
||||
q.handlers = make(map[event.Tag]*pointerHandler)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
|
||||
q.init()
|
||||
for _, h := range q.handlers {
|
||||
// Reset handler.
|
||||
h.active = false
|
||||
h.wantsGrab = false
|
||||
h.types = 0
|
||||
}
|
||||
q.hitTree = q.hitTree[:0]
|
||||
q.areas = q.areas[:0]
|
||||
q.reader.Reset(root)
|
||||
q.collectHandlers(&q.reader, events, f32.Affine2D{}, -1, -1, false)
|
||||
for k, h := range q.handlers {
|
||||
if !h.active {
|
||||
q.dropHandlers(events, k)
|
||||
delete(q.handlers, k)
|
||||
}
|
||||
if h.wantsGrab {
|
||||
for _, p := range q.pointers {
|
||||
if !p.pressed {
|
||||
continue
|
||||
}
|
||||
for i, k2 := range p.handlers {
|
||||
if k2 == k {
|
||||
// Drop other handlers that lost their grab.
|
||||
q.dropHandlers(events, p.handlers[i+1:]...)
|
||||
q.dropHandlers(events, p.handlers[:i]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) {
|
||||
for _, k := range tags {
|
||||
events.Add(k, pointer.Event{Type: pointer.Cancel})
|
||||
for i := range q.pointers {
|
||||
p := &q.pointers[i]
|
||||
for i := len(p.handlers) - 1; i >= 0; i-- {
|
||||
if p.handlers[i] == k {
|
||||
p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
|
||||
}
|
||||
}
|
||||
for i := len(p.entered) - 1; i >= 0; i-- {
|
||||
if p.entered[i] == k {
|
||||
p.entered = append(p.entered[:i], p.entered[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
|
||||
q.init()
|
||||
if e.Type == pointer.Cancel {
|
||||
q.pointers = q.pointers[:0]
|
||||
for k := range q.handlers {
|
||||
q.dropHandlers(events, k)
|
||||
}
|
||||
return
|
||||
}
|
||||
pidx := -1
|
||||
for i, p := range q.pointers {
|
||||
if p.id == e.PointerID {
|
||||
pidx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if pidx == -1 {
|
||||
q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
|
||||
pidx = len(q.pointers) - 1
|
||||
}
|
||||
p := &q.pointers[pidx]
|
||||
|
||||
if e.Type == pointer.Move && p.pressed {
|
||||
e.Type = pointer.Drag
|
||||
}
|
||||
|
||||
if e.Type == pointer.Release {
|
||||
q.deliverEvent(p, events, e)
|
||||
p.pressed = false
|
||||
}
|
||||
q.scratch = q.scratch[:0]
|
||||
q.opHit(&q.scratch, e.Position)
|
||||
if p.pressed {
|
||||
// Filter out non-participating handlers.
|
||||
for i := len(q.scratch) - 1; i >= 0; i-- {
|
||||
if _, found := searchTag(p.handlers, q.scratch[i]); !found {
|
||||
q.scratch = append(q.scratch[:i], q.scratch[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
q.deliverEnterLeaveEvents(p, q.scratch, events, e)
|
||||
|
||||
if !p.pressed {
|
||||
p.handlers = append(p.handlers[:0], q.scratch...)
|
||||
}
|
||||
if e.Type == pointer.Press {
|
||||
p.pressed = true
|
||||
}
|
||||
if e.Type != pointer.Release {
|
||||
q.deliverEvent(p, events, e)
|
||||
}
|
||||
if !p.pressed && len(p.entered) == 0 {
|
||||
// No longer need to track pointer.
|
||||
q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
|
||||
foremost := true
|
||||
for _, k := range p.handlers {
|
||||
h := q.handlers[k]
|
||||
e := e
|
||||
if p.pressed && len(p.handlers) == 1 {
|
||||
e.Priority = pointer.Grabbed
|
||||
} else if foremost {
|
||||
e.Priority = pointer.Foremost
|
||||
}
|
||||
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
|
||||
if e.Type&h.types == e.Type {
|
||||
foremost = false
|
||||
events.Add(k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, hits []event.Tag, events *handlerEvents, e pointer.Event) {
|
||||
if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press {
|
||||
// Consider non-mouse pointers leaving when they're released.
|
||||
hits = nil
|
||||
}
|
||||
// Deliver Leave events.
|
||||
for _, k := range p.entered {
|
||||
if _, found := searchTag(hits, k); found {
|
||||
continue
|
||||
}
|
||||
h := q.handlers[k]
|
||||
e.Type = pointer.Leave
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
|
||||
if e.Type&h.types == e.Type {
|
||||
events.Add(k, e)
|
||||
}
|
||||
}
|
||||
// Deliver Enter events.
|
||||
for _, k := range hits {
|
||||
if _, found := searchTag(p.entered, k); found {
|
||||
continue
|
||||
}
|
||||
h := q.handlers[k]
|
||||
e.Type = pointer.Enter
|
||||
e.Position = q.invTransform(h.area, e.Position)
|
||||
|
||||
if e.Type&h.types == e.Type {
|
||||
events.Add(k, e)
|
||||
}
|
||||
}
|
||||
p.entered = append(p.entered[:0], hits...)
|
||||
}
|
||||
|
||||
func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
|
||||
for i, t := range tags {
|
||||
if t == tag {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (op *areaOp) Decode(d []byte) {
|
||||
if opconst.OpType(d[0]) != opconst.TypeArea {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
rect := image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: int(int32(bo.Uint32(d[2:]))),
|
||||
Y: int(int32(bo.Uint32(d[6:]))),
|
||||
},
|
||||
Max: image.Point{
|
||||
X: int(int32(bo.Uint32(d[10:]))),
|
||||
Y: int(int32(bo.Uint32(d[14:]))),
|
||||
},
|
||||
}
|
||||
*op = areaOp{
|
||||
kind: areaKind(d[1]),
|
||||
rect: rect,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *areaOp) Hit(pos f32.Point) bool {
|
||||
min := f32.Point{
|
||||
X: float32(op.rect.Min.X),
|
||||
Y: float32(op.rect.Min.Y),
|
||||
}
|
||||
pos = pos.Sub(min)
|
||||
size := op.rect.Size()
|
||||
switch op.kind {
|
||||
case areaRect:
|
||||
return 0 <= pos.X && pos.X < float32(size.X) &&
|
||||
0 <= pos.Y && pos.Y < float32(size.Y)
|
||||
case areaEllipse:
|
||||
rx := float32(size.X) / 2
|
||||
ry := float32(size.Y) / 2
|
||||
xh := pos.X - rx
|
||||
yk := pos.Y - ry
|
||||
// The ellipse function works in all cases because
|
||||
// 0/0 is not <= 1.
|
||||
return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1
|
||||
default:
|
||||
panic("invalid area kind")
|
||||
}
|
||||
}
|
||||
|
||||
func decodePointerInputOp(d []byte, refs []interface{}) pointer.InputOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypePointerInput {
|
||||
panic("invalid op")
|
||||
}
|
||||
return pointer.InputOp{
|
||||
Tag: refs[0].(event.Tag),
|
||||
Grab: d[1] != 0,
|
||||
Types: pointer.Type(d[2]),
|
||||
}
|
||||
}
|
||||
|
||||
func decodePassOp(d []byte) pointer.PassOp {
|
||||
if opconst.OpType(d[0]) != opconst.TypePass {
|
||||
panic("invalid op")
|
||||
}
|
||||
return pointer.PassOp{
|
||||
Pass: d[1] != 0,
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue