Major update -> v1.5.0

This commit is contained in:
esimov
2025-04-27 10:40:09 +03:00
parent bdceadba96
commit 4b51dc0207
1124 changed files with 1356 additions and 513450 deletions

View File

@@ -12,12 +12,12 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go-version: [~1.19, ~1.20] go-version: [~1.21, ~1.22]
os: [ubuntu-latest, macos-latest] os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
GO111MODULE: "on" GO111MODULE: "on"
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:

View File

@@ -33,7 +33,7 @@ Key features which differentiates this library from the other existing open sour
- [x] Support for both shrinking or enlarging the image - [x] Support for both shrinking or enlarging the image
- [x] Resize image both vertically and horizontally - [x] Resize image both vertically and horizontally
- [x] Face detection to avoid face deformation - [x] Face detection to avoid face deformation
- [x] Support for multiple output image type (jpg, jpeg, png, bmp, gif) - [x] Support for multiple output image type (jpg, jpeg, png, bmp)
- [x] Support for `stdin` and `stdout` pipe commands - [x] Support for `stdin` and `stdout` pipe commands
- [x] Can process whole directories recursively and concurrently - [x] Can process whole directories recursively and concurrently
- [x] Use of sobel threshold for fine tuning - [x] Use of sobel threshold for fine tuning
@@ -48,7 +48,7 @@ Key features which differentiates this library from the other existing open sour
First, install Go, set your `GOPATH`, and make sure `$GOPATH/bin` is on your `PATH`. First, install Go, set your `GOPATH`, and make sure `$GOPATH/bin` is on your `PATH`.
```bash ```bash
$ go install github.com/esimov/caire/cmd/caire@latest $ go install github.com/esimov/caire/cmd/caire@latest
``` ```
## MacOS (Brew) install ## MacOS (Brew) install
@@ -147,7 +147,7 @@ $ caire -in <input_folder> -out <output-folder>
``` ```
### Support for multiple output image type ### Support for multiple output image type
There is no need to define the output file type, just use the correct extension and the library will encode the image to that specific type. You can export the resized image even to a **Gif** file, in which case the generated file shows the resizing process interactively. There is no need to define the output file type, just use the correct extension and the library will encode the image to that specific type.
### Other options ### Other options
In case you wish to scale down the image by a specific percentage, it can be used the **`-perc`** boolean flag. In this case the values provided for the `width` and `height` are expressed in percentage and not pixel values. For example to reduce the image dimension by 20% both horizontally and vertically you can use the following command: In case you wish to scale down the image by a specific percentage, it can be used the **`-perc`** boolean flag. In this case the values provided for the `width` and `height` are expressed in percentage and not pixel values. For example to reduce the image dimension by 20% both horizontally and vertically you can use the following command:

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
VERSION="1.4.6" VERSION="1.5.0"
PROTECTED_MODE="no" PROTECTED_MODE="no"
export GO15VENDOREXPERIMENT=1 export GO15VENDOREXPERIMENT=1
@@ -62,4 +62,4 @@ if [ "$NOCOPY" != "1" ]; then
fi fi
# build and store objects into original directory. # build and store objects into original directory.
go build -ldflags "-X main.Version=$VERSION" -o "$OD/caire" cmd/caire/*.go go build -ldflags "-X main.Version=$VERSION" -o "$OD/caire" cmd/caire/main.go

View File

@@ -11,6 +11,12 @@ import (
pigo "github.com/esimov/pigo/core" pigo "github.com/esimov/pigo/core"
) )
// SeamCarver defines the Carve interface method, which have to be
// implemented by the Processor struct.
type SeamCarver interface {
Carve(*image.NRGBA) (image.Image, error)
}
// maxFaceDetAttempts defines the maximum number of attempts of face detections // maxFaceDetAttempts defines the maximum number of attempts of face detections
const maxFaceDetAttempts = 20 const maxFaceDetAttempts = 20
@@ -41,10 +47,10 @@ type Seam struct {
// NewCarver returns an initialized Carver structure. // NewCarver returns an initialized Carver structure.
func NewCarver(width, height int) *Carver { func NewCarver(width, height int) *Carver {
return &Carver{ return &Carver{
make([]float64, width*height), Points: make([]float64, width*height),
nil, Seams: []Seam{},
width, Width: width,
height, Height: height,
} }
} }
@@ -70,7 +76,7 @@ func (c *Carver) set(x, y int, px float64) {
// with the minimum pixel value of the neighboring pixels from the previous row. // with the minimum pixel value of the neighboring pixels from the previous row.
func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, error) { func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, error) {
var srcImg *image.NRGBA var srcImg *image.NRGBA
p.GuiDebug = image.NewNRGBA(img.Bounds()) p.DebugMask = image.NewNRGBA(img.Bounds())
width, height := img.Bounds().Dx(), img.Bounds().Dy() width, height := img.Bounds().Dx(), img.Bounds().Dy()
sobel = c.SobelDetector(img, float64(p.SobelThreshold)) sobel = c.SobelDetector(img, float64(p.SobelThreshold))
@@ -88,7 +94,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
minSize := float64(utils.Min(width, height)) * ratio / 3 minSize := float64(utils.Min(width, height)) * ratio / 3
// Transform the image to pixel array. // Transform the image to pixel array.
pixels := c.rgbToGrayscale(img) pixels := rgbToGrayscale(img)
cParams := pigo.CascadeParams{ cParams := pigo.CascadeParams{
MinSize: int(minSize), MinSize: int(minSize),
@@ -165,9 +171,9 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
} else { } else {
sobel.Set(x, y, color.Black) sobel.Set(x, y, color.Black)
} }
p.GuiDebug.Set(x, y, color.Black) p.DebugMask.Set(x, y, color.Black)
} else { } else {
p.GuiDebug.Set(x, y, color.Transparent) p.DebugMask.Set(x, y, color.Transparent)
} }
} }
} }
@@ -190,7 +196,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
face.Row+scale, face.Row+scale,
) )
draw.Draw(sobel, rect, &image.Uniform{color.White}, image.Point{}, draw.Src) draw.Draw(sobel, rect, &image.Uniform{color.White}, image.Point{}, draw.Src)
draw.Draw(p.GuiDebug, rect, &image.Uniform{color.White}, image.Point{}, draw.Src) draw.Draw(p.DebugMask, rect, &image.Uniform{color.White}, image.Point{}, draw.Src)
} }
} }

View File

@@ -21,12 +21,17 @@ func Benchmark_Carver(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
img := p.imgToNRGBA(src) img := imgToNRGBA(src)
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c := NewCarver(width, height)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y _, err := c.ComputeSeams(p, img)
c := NewCarver(width, height) if err != nil {
c.ComputeSeams(p, img) b.FailNow()
}
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug) img = c.RemoveSeam(img, seams, p.Debug)
} }

View File

@@ -42,16 +42,19 @@ func TestCarver_EnergySeamShouldNotBeDetected(t *testing.T) {
dx, dy := img.Bounds().Dx(), img.Bounds().Dy() dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
var c = NewCarver(dx, dy) var c = NewCarver(dx, dy)
for x := 0; x < imgWidth; x++ { for range imgWidth {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img)
_, err := c.ComputeSeams(p, img)
assert.NoError(err)
les := c.FindLowestEnergySeams(p) les := c.FindLowestEnergySeams(p)
seams = append(seams, les) seams = append(seams, les)
} }
for i := 0; i < len(seams); i++ { for i := range seams {
for s := 0; s < len(seams[i]); s++ { for s := range seams[i] {
totalEnergySeams += seams[i][s].X totalEnergySeams += seams[i][s].X
} }
} }
@@ -59,8 +62,6 @@ func TestCarver_EnergySeamShouldNotBeDetected(t *testing.T) {
} }
func TestCarver_DetectHorizontalEnergySeam(t *testing.T) { func TestCarver_DetectHorizontalEnergySeam(t *testing.T) {
assert := assert.New(t)
var seams [][]Seam var seams [][]Seam
var totalEnergySeams int var totalEnergySeams int
@@ -82,24 +83,24 @@ func TestCarver_DetectHorizontalEnergySeam(t *testing.T) {
var c = NewCarver(dx, dy) var c = NewCarver(dx, dy)
for x := 0; x < imgWidth; x++ { for x := 0; x < imgWidth; x++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img) _, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
les := c.FindLowestEnergySeams(p) les := c.FindLowestEnergySeams(p)
seams = append(seams, les) seams = append(seams, les)
} }
for i := 0; i < len(seams); i++ { for i := range seams {
for s := 0; s < len(seams[i]); s++ { for s := range seams[i] {
totalEnergySeams += seams[i][s].X totalEnergySeams += seams[i][s].X
} }
} }
assert.Greater(t, totalEnergySeams, 0)
assert.Greater(totalEnergySeams, 0)
} }
func TestCarver_DetectVerticalEnergySeam(t *testing.T) { func TestCarver_DetectVerticalEnergySeam(t *testing.T) {
assert := assert.New(t)
var seams [][]Seam var seams [][]Seam
var totalEnergySeams int var totalEnergySeams int
@@ -119,26 +120,27 @@ func TestCarver_DetectVerticalEnergySeam(t *testing.T) {
} }
var c = NewCarver(dx, dy) var c = NewCarver(dx, dy)
img = c.RotateImage90(img) img = rotateImage90(img)
for x := 0; x < imgHeight; x++ { for x := 0; x < imgHeight; x++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img) _, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
les := c.FindLowestEnergySeams(p) les := c.FindLowestEnergySeams(p)
seams = append(seams, les) seams = append(seams, les)
} }
for i := 0; i < len(seams); i++ { for i := range seams {
for s := 0; s < len(seams[i]); s++ { for s := range seams[i] {
totalEnergySeams += seams[i][s].X totalEnergySeams += seams[i][s].X
} }
} }
assert.Greater(totalEnergySeams, 0) assert.Greater(t, totalEnergySeams, 0)
} }
func TestCarver_RemoveSeam(t *testing.T) { func TestCarver_RemoveSeam(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
bounds := img.Bounds() bounds := img.Bounds()
@@ -154,7 +156,9 @@ func TestCarver_RemoveSeam(t *testing.T) {
} }
c := NewCarver(dx, dy) c := NewCarver(dx, dy)
c.ComputeSeams(p, img) _, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, false) img = c.RemoveSeam(img, seams, false)
@@ -172,12 +176,10 @@ func TestCarver_RemoveSeam(t *testing.T) {
} }
} }
} }
assert.False(isEq) assert.False(t, isEq)
} }
func TestCarver_AddSeam(t *testing.T) { func TestCarver_AddSeam(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
bounds := img.Bounds() bounds := img.Bounds()
@@ -193,7 +195,9 @@ func TestCarver_AddSeam(t *testing.T) {
} }
c := NewCarver(dx, dy) c := NewCarver(dx, dy)
c.ComputeSeams(p, img) _, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, false) img = c.AddSeam(img, seams, false)
@@ -211,12 +215,10 @@ func TestCarver_AddSeam(t *testing.T) {
} }
} }
} }
assert.False(isEq) assert.False(t, isEq)
} }
func TestCarver_ComputeSeams(t *testing.T) { func TestCarver_ComputeSeams(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
// We choose to fill up the background with an uniform white color // We choose to fill up the background with an uniform white color
@@ -231,16 +233,15 @@ func TestCarver_ComputeSeams(t *testing.T) {
} }
c := NewCarver(dx, dy) c := NewCarver(dx, dy)
c.ComputeSeams(p, img) _, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
otherThenZero := findNonZeroValue(c.Points) otherThenZero := findNonZeroValue(c.Points)
assert.True(otherThenZero) assert.True(t, otherThenZero)
} }
func TestCarver_ShouldDetectFace(t *testing.T) { func TestCarver_ShouldDetectFace(t *testing.T) {
assert := assert.New(t)
p.FaceDetect = true p.FaceDetect = true
sampleImg := filepath.Join("./testdata", "sample.jpg") sampleImg := filepath.Join("./testdata", "sample.jpg")
@@ -259,12 +260,11 @@ func TestCarver_ShouldDetectFace(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error decoding image: %v", err) t.Fatalf("error decoding image: %v", err)
} }
img := p.imgToNRGBA(src) img := imgToNRGBA(src)
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
c := NewCarver(dx, dy)
// Transform the image to a pixel array. // Transform the image to a pixel array.
pixels := c.rgbToGrayscale(img) pixels := rgbToGrayscale(img)
cParams := pigo.CascadeParams{ cParams := pigo.CascadeParams{
MinSize: 100, MinSize: 100,
@@ -287,7 +287,7 @@ func TestCarver_ShouldDetectFace(t *testing.T) {
// Calculate the intersection over union (IoU) of two clusters. // Calculate the intersection over union (IoU) of two clusters.
faces = p.FaceDetector.ClusterDetections(faces, 0.2) faces = p.FaceDetector.ClusterDetections(faces, 0.2)
assert.Equal(1, len(faces)) assert.Equal(t, 1, len(faces))
} }
func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) { func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
@@ -310,12 +310,12 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error decoding image: %v", err) t.Fatalf("error decoding image: %v", err)
} }
img := p.imgToNRGBA(src) img := imgToNRGBA(src)
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
c := NewCarver(dx, dy) c := NewCarver(dx, dy)
// Transform the image to a pixel array. // Transform the image to a pixel array.
pixels := c.rgbToGrayscale(img) pixels := rgbToGrayscale(img)
sobel := c.SobelDetector(img, float64(p.SobelThreshold)) sobel := c.SobelDetector(img, float64(p.SobelThreshold))
img = c.StackBlur(sobel, uint32(p.BlurRadius)) img = c.StackBlur(sobel, uint32(p.BlurRadius))
@@ -355,7 +355,9 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
draw.Draw(sobel, rect, &image.Uniform{image.White}, image.Point{}, draw.Src) draw.Draw(sobel, rect, &image.Uniform{image.White}, image.Point{}, draw.Src)
} }
} }
c.ComputeSeams(p, img) _, err = c.ComputeSeams(p, img)
assert.Error(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
for _, seam := range seams { for _, seam := range seams {
@@ -387,12 +389,11 @@ func TestCarver_ShouldNotResizeWithFaceDistorsion(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error decoding image: %v", err) t.Fatalf("error decoding image: %v", err)
} }
img := p.imgToNRGBA(src) img := imgToNRGBA(src)
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
c := NewCarver(dx, dy)
// Transform the image to a pixel array. // Transform the image to a pixel array.
pixels := c.rgbToGrayscale(img) pixels := rgbToGrayscale(img)
cParams := pigo.CascadeParams{ cParams := pigo.CascadeParams{
MinSize: 100, MinSize: 100,
MaxSize: utils.Max(dx, dy), MaxSize: utils.Max(dx, dy),

View File

@@ -71,18 +71,18 @@ func main() {
FaceAngle: *faceAngle, FaceAngle: *faceAngle,
MaskPath: *maskPath, MaskPath: *maskPath,
RMaskPath: *rMaskPath, RMaskPath: *rMaskPath,
ShapeType: *shapeType,
SeamColor: *seamColor, SeamColor: *seamColor,
ShapeType: caire.ShapeType(*shapeType),
} }
if !(*newWidth > 0 || *newHeight > 0 || *percentage || *square) { if !(*newWidth > 0 || *newHeight > 0 || *percentage || *square) {
flag.Usage() flag.Usage()
log.Fatal(fmt.Sprintf("%s%s", log.Fatalf("%s%s",
utils.DecorateText("\nPlease provide a width, height or percentage for image rescaling!", utils.ErrorMessage), utils.DecorateText("\nPlease provide a width, height or percentage for image rescaling!", utils.ErrorMessage),
utils.DefaultColor, utils.DefaultColor,
)) )
} else { } else {
op := &caire.Ops{ op := &caire.Image{
Src: *source, Src: *source,
Dst: *destination, Dst: *destination,
Workers: *workers, Workers: *workers,

59
draw.go
View File

@@ -1,74 +1,49 @@
package caire package caire
import ( import (
"image"
"image/color" "image/color"
"math" "math"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"github.com/esimov/caire/utils" "github.com/esimov/caire/utils"
) )
type ShapeType string
const ( const (
circle = "circle" Circle ShapeType = "circle"
line = "line" Line ShapeType = "line"
) )
// DrawSeam visualizes the seam carver in action when the preview mode is activated. // DrawSeam visualizes the seam carver in action when the preview mode is activated.
// It receives as parameters the shape type, the seam (x,y) coordinates and a dimension. // It receives as parameters the shape type, the seam (x,y) coordinates and it's thickness.
func (g *Gui) DrawSeam(shape string, x, y, dim float32) { func (g *Gui) DrawSeam(shape ShapeType, x, y, thickness float32) {
r := getRatio(g.cfg.window.w, g.cfg.window.h) r := getRatio(g.cfg.window.width, g.cfg.window.height)
switch shape { switch shape {
case circle: case Circle:
g.drawCircle(x*r, y*r, dim) g.drawCircle(x*r, y*r, thickness)
case line: case Line:
g.drawLine(x*r, y*r, dim) g.drawLine(x*r, y*r, thickness)
} }
} }
// EncodeSeamToImg draws the seams into an image widget.
func (g *Gui) EncodeSeamToImg() {
c := utils.HexToRGBA(g.cp.SeamColor)
g.setFillColor(c)
img := image.NewNRGBA(image.Rect(0, 0, int(g.cfg.window.w), int(g.cfg.window.h)))
r := getRatio(g.cfg.window.w, g.cfg.window.h)
for _, s := range g.proc.seams {
x := int(float32(s.X) * r)
y := int(float32(s.Y) * r)
img.Set(x, y, g.getFillColor())
}
src := paint.NewImageOp(img)
src.Add(g.ctx.Ops)
widget.Image{
Src: src,
Scale: 1 / float32(unit.Dp(1)),
Fit: widget.Contain,
}.Layout(g.ctx)
}
// drawCircle draws a circle at the seam (x,y) coordinate with the provided size. // drawCircle draws a circle at the seam (x,y) coordinate with the provided size.
func (g *Gui) drawCircle(x, y, s float32) { func (g *Gui) drawCircle(x, y, radius float32) {
var ( var (
sq float64 sq float64
p1 f32.Point p1 f32.Point
p2 f32.Point p2 f32.Point
orig = g.point(x-s, y) orig = g.point(x-radius, y)
) )
sq = math.Sqrt(float64(s*s) - float64(s*s)) sq = math.Sqrt(float64(radius*radius) - float64(radius*radius))
p1 = g.point(x+float32(sq), y).Sub(orig) p1 = g.point(x+float32(sq), y).Sub(orig)
p2 = g.point(x-float32(sq), y).Sub(orig) p2 = g.point(x-float32(sq), y).Sub(orig)
col := utils.HexToRGBA(g.cp.SeamColor) col := utils.HexToRGBA(g.proc.SeamColor)
g.setFillColor(col) g.setFillColor(col)
var path clip.Path var path clip.Path
@@ -95,7 +70,7 @@ func (g *Gui) drawLine(x, y, thickness float32) {
path.Line(p2.Sub(path.Pos())) path.Line(p2.Sub(path.Pos()))
path.Close() path.Close()
col := utils.HexToRGBA(g.cp.SeamColor) col := utils.HexToRGBA(g.proc.SeamColor)
g.setFillColor(col) g.setFillColor(col)
defer clip.Stroke{Path: path.End(), Width: float32(thickness)}.Op().Push(g.ctx.Ops).Pop() defer clip.Stroke{Path: path.End(), Width: float32(thickness)}.Op().Push(g.ctx.Ops).Pop()
@@ -139,7 +114,7 @@ func getRatio(w, h float32) float32 {
wr := maxScreenX / float32(w) // width ratio wr := maxScreenX / float32(w) // width ratio
hr := maxScreenY / float32(h) // height ratio hr := maxScreenY / float32(h) // height ratio
r = utils.Min(wr, hr) r = utils.Max(wr, hr)
} }
return r return r
} }

114
exec.go
View File

@@ -3,6 +3,7 @@ package caire
import ( import (
"errors" "errors"
"fmt" "fmt"
"image"
"io" "io"
"log" "log"
"os" "os"
@@ -13,13 +14,12 @@ import (
"syscall" "syscall"
"time" "time"
"slices"
"github.com/esimov/caire/utils" "github.com/esimov/caire/utils"
"golang.org/x/term" "golang.org/x/term"
) )
// maxWorkers sets the maximum number of concurrently running workers.
const maxWorkers = 20
var ( var (
// imgFile holds the file being accessed, be it normal file or pipe name. // imgFile holds the file being accessed, be it normal file or pipe name.
imgFile *os.File imgFile *os.File
@@ -28,7 +28,7 @@ var (
fs os.FileInfo fs os.FileInfo
) )
type Ops struct { type Image struct {
Src, Dst, PipeName string Src, Dst, PipeName string
Workers int Workers int
} }
@@ -39,10 +39,14 @@ type result struct {
err error err error
} }
func Resize(s SeamCarver, img *image.NRGBA) (image.Image, error) {
return s.Carve(img)
}
// Execute executes the image resizing process. // Execute executes the image resizing process.
// In case the preview mode is activated it will be invoked in a separate goroutine // In case the preview mode is activated it will be invoked in a separate goroutine
// in order to not block the main OS thread. Otherwise it will be called normally. // in order to avoid blocking the main OS thread. Otherwise it will be called normally.
func (p *Processor) Execute(op *Ops) { func (p *Processor) Execute(img *Image) {
var err error var err error
defaultMsg := fmt.Sprintf("%s %s", defaultMsg := fmt.Sprintf("%s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage), utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
@@ -54,8 +58,8 @@ func (p *Processor) Execute(op *Ops) {
validExtensions := []string{".jpg", ".png", ".jpeg", ".bmp", ".gif"} validExtensions := []string{".jpg", ".png", ".jpeg", ".bmp", ".gif"}
// Check if source path is a local image or URL. // Check if source path is a local image or URL.
if utils.IsValidUrl(op.Src) { if utils.IsValidUrl(img.Src) {
src, err := utils.DownloadImage(op.Src) src, err := utils.DownloadImage(img.Src)
if src != nil { if src != nil {
defer os.Remove(src.Name()) defer os.Remove(src.Name())
} }
@@ -84,10 +88,10 @@ func (p *Processor) Execute(op *Ops) {
imgFile = img imgFile = img
} else { } else {
// Check if the source is a pipe name or a regular file. // Check if the source is a pipe name or a regular file.
if op.Src == op.PipeName { if img.Src == img.PipeName {
fs, err = os.Stdin.Stat() fs, err = os.Stdin.Stat()
} else { } else {
fs, err = os.Stat(op.Src) fs, err = os.Stat(img.Src)
} }
if err != nil { if err != nil {
log.Fatalf( log.Fatalf(
@@ -103,9 +107,9 @@ func (p *Processor) Execute(op *Ops) {
case mode.IsDir(): case mode.IsDir():
var wg sync.WaitGroup var wg sync.WaitGroup
// Read destination file or directory. // Read destination file or directory.
_, err := os.Stat(op.Dst) _, err := os.Stat(img.Dst)
if err != nil { if err != nil {
err = os.Mkdir(op.Dst, 0755) err = os.Mkdir(img.Dst, 0755)
if err != nil { if err != nil {
log.Fatalf( log.Fatalf(
utils.DecorateText("Unable to get dir stats: %v\n", utils.ErrorMessage), utils.DecorateText("Unable to get dir stats: %v\n", utils.ErrorMessage),
@@ -116,22 +120,22 @@ func (p *Processor) Execute(op *Ops) {
p.Preview = false p.Preview = false
// Limit the concurrently running workers to maxWorkers. // Limit the concurrently running workers to maxWorkers.
if op.Workers <= 0 || op.Workers > maxWorkers { if img.Workers <= 0 || img.Workers > runtime.NumCPU() {
op.Workers = runtime.NumCPU() img.Workers = runtime.NumCPU()
} }
// Process recursively the image files from the specified directory concurrently. // Process recursively the image files from the specified directory concurrently.
ch := make(chan result) ch := make(chan result)
done := make(chan interface{}) done := make(chan any)
defer close(done) defer close(done)
paths, errc := walkDir(done, op.Src, validExtensions) paths, errc := walkDir(done, img.Src, validExtensions)
wg.Add(op.Workers) wg.Add(img.Workers)
for i := 0; i < op.Workers; i++ { for range img.Workers {
go func() { go func() {
defer wg.Done() defer wg.Done()
op.consumer(p, op.Dst, ch, done, paths) img.consumer(p, img.Dst, ch, done, paths)
}() }()
} }
@@ -146,7 +150,7 @@ func (p *Processor) Execute(op *Ops) {
if res.err != nil { if res.err != nil {
err = res.err err = res.err
} }
op.printOpStatus(res.path, err) img.printOpStatus(res.path, err)
} }
if err = <-errc; err != nil { if err = <-errc; err != nil {
@@ -154,30 +158,32 @@ func (p *Processor) Execute(op *Ops) {
} }
case mode.IsRegular() || mode&os.ModeNamedPipe != 0: // check for regular files or pipe names case mode.IsRegular() || mode&os.ModeNamedPipe != 0: // check for regular files or pipe names
ext := filepath.Ext(op.Dst) ext := filepath.Ext(img.Dst)
if !isValidExtension(ext, validExtensions) && op.Dst != op.PipeName { if !slices.Contains(validExtensions, ext) && img.Dst != img.PipeName {
log.Fatalf(utils.DecorateText(fmt.Sprintf("%v file type not supported", ext), utils.ErrorMessage)) log.Fatalf(utils.DecorateText(fmt.Sprintf("%v file type not supported", ext), utils.ErrorMessage))
} }
err = op.process(p, op.Src, op.Dst) err = img.process(p, img.Src, img.Dst)
op.printOpStatus(op.Dst, err) img.printOpStatus(img.Dst, err)
} }
if err == nil { if err == nil {
fmt.Fprintf(os.Stderr, "\nExecution time: %s\n", utils.DecorateText(fmt.Sprintf("%s", utils.FormatTime(time.Since(now))), utils.SuccessMessage)) fmt.Fprintf(os.Stderr, "\nExecution time: %s\n", utils.DecorateText(
utils.FormatTime(time.Since(now)), utils.SuccessMessage),
)
} }
} }
// consumer reads the path names from the paths channel and calls the resizing processor against the source image. // consumer reads the path names from the paths channel and calls the resizing processor against the source image.
func (op *Ops) consumer( func (img *Image) consumer(
p *Processor, p *Processor,
dest string, dest string,
res chan<- result, res chan<- result,
done <-chan interface{}, done <-chan any,
paths <-chan string, paths <-chan string,
) { ) {
for src := range paths { for src := range paths {
dst := filepath.Join(dest, filepath.Base(src)) dst := filepath.Join(dest, filepath.Base(src))
err := op.process(p, src, dst) err := img.process(p, src, dst)
select { select {
case <-done: case <-done:
@@ -191,7 +197,7 @@ func (op *Ops) consumer(
} }
// processor calls the resizer method over the source image and returns the error in case exists. // processor calls the resizer method over the source image and returns the error in case exists.
func (op *Ops) process(p *Processor, in, out string) error { func (img *Image) process(p *Processor, in, out string) error {
var ( var (
successMsg string successMsg string
errorMsg string errorMsg string
@@ -211,7 +217,7 @@ func (op *Ops) process(p *Processor, in, out string) error {
utils.DecorateText("✘", utils.ErrorMessage), utils.DecorateText("✘", utils.ErrorMessage),
) )
src, dst, err := op.pathToFile(in, out) src, dst, err := img.pathToFile(in, out)
if err != nil { if err != nil {
p.Spinner.StopMsg = errorMsg p.Spinner.StopMsg = errorMsg
return err return err
@@ -245,6 +251,24 @@ func (op *Ops) process(p *Processor, in, out string) error {
} }
}() }()
if len(p.MaskPath) > 0 {
mask, err := decodeImg(p.MaskPath)
if err != nil {
return fmt.Errorf("cannot decode image: %w", err)
}
p.Mask = dither(imgToNRGBA(mask))
p.DebugMask = p.Mask
}
if len(p.RMaskPath) > 0 {
rmask, err := decodeImg(p.RMaskPath)
if err != nil {
return fmt.Errorf("cannot decode image: %w", err)
}
p.RMask = dither(imgToNRGBA(rmask))
p.DebugMask = p.RMask
}
err = p.Process(src, dst) err = p.Process(src, dst)
if err != nil { if err != nil {
// remove the generated image file in case of an error // remove the generated image file in case of an error
@@ -265,7 +289,7 @@ func (op *Ops) process(p *Processor, in, out string) error {
} }
// pathToFile converts the source and destination paths to readable and writable files. // pathToFile converts the source and destination paths to readable and writable files.
func (op *Ops) pathToFile(in, out string) (io.Reader, io.Writer, error) { func (img *Image) pathToFile(in, out string) (io.Reader, io.Writer, error) {
var ( var (
src io.Reader src io.Reader
dst io.Writer dst io.Writer
@@ -276,7 +300,7 @@ func (op *Ops) pathToFile(in, out string) (io.Reader, io.Writer, error) {
src = imgFile src = imgFile
} else { } else {
// Check if the source is a pipe name or a regular file. // Check if the source is a pipe name or a regular file.
if in == op.PipeName { if in == img.PipeName {
if term.IsTerminal(int(os.Stdin.Fd())) { if term.IsTerminal(int(os.Stdin.Fd())) {
return nil, nil, errors.New("`-` should be used with a pipe for stdin") return nil, nil, errors.New("`-` should be used with a pipe for stdin")
} }
@@ -290,7 +314,7 @@ func (op *Ops) pathToFile(in, out string) (io.Reader, io.Writer, error) {
} }
// Check if the destination is a pipe name or a regular file. // Check if the destination is a pipe name or a regular file.
if out == op.PipeName { if out == img.PipeName {
if term.IsTerminal(int(os.Stdout.Fd())) { if term.IsTerminal(int(os.Stdout.Fd())) {
return nil, nil, errors.New("`-` should be used with a pipe for stdout") return nil, nil, errors.New("`-` should be used with a pipe for stdout")
} }
@@ -301,18 +325,19 @@ func (op *Ops) pathToFile(in, out string) (io.Reader, io.Writer, error) {
return nil, nil, fmt.Errorf("unable to create the destination file: %v", err) return nil, nil, fmt.Errorf("unable to create the destination file: %v", err)
} }
} }
return src, dst, nil return src, dst, nil
} }
// printOpStatus displays the relevant information about the image resizing process. // printOpStatus displays the relevant information about the image resizing process.
func (op *Ops) printOpStatus(fname string, err error) { func (img *Image) printOpStatus(fname string, err error) {
if err != nil { if err != nil {
log.Fatalf( log.Fatalf(
utils.DecorateText("\nError resizing the image: %s", utils.ErrorMessage), utils.DecorateText("\nError resizing the image: %s", utils.ErrorMessage),
utils.DecorateText(fmt.Sprintf("\n\tReason: %v\n", err.Error()), utils.DefaultMessage), utils.DecorateText(fmt.Sprintf("\n\tReason: %v\n", err.Error()), utils.DefaultMessage),
) )
} else { } else {
if fname != op.PipeName { if fname != img.PipeName {
fmt.Fprintf(os.Stderr, "\nThe image has been saved as: %s %s\n\n", fmt.Fprintf(os.Stderr, "\nThe image has been saved as: %s %s\n\n",
utils.DecorateText(filepath.Base(fname), utils.SuccessMessage), utils.DecorateText(filepath.Base(fname), utils.SuccessMessage),
utils.DefaultColor, utils.DefaultColor,
@@ -325,7 +350,7 @@ func (op *Ops) printOpStatus(fname string, err error) {
// in recursive manner and sends the path of each regular file to a new channel. // in recursive manner and sends the path of each regular file to a new channel.
// It finishes in case the done channel is getting closed. // It finishes in case the done channel is getting closed.
func walkDir( func walkDir(
done <-chan interface{}, done <-chan any,
src string, src string,
srcExts []string, srcExts []string,
) (<-chan string, <-chan error) { ) (<-chan string, <-chan error) {
@@ -347,11 +372,8 @@ func walkDir(
// Get the file base name. // Get the file base name.
fx := filepath.Ext(f.Name()) fx := filepath.Ext(f.Name())
for _, ext := range srcExts { if slices.Contains(srcExts, fx) {
if ext == fx { isFileSupported = true
isFileSupported = true
break
}
} }
if isFileSupported { if isFileSupported {
@@ -366,13 +388,3 @@ func walkDir(
}() }()
return pathChan, errChan return pathChan, errChan
} }
// isValidExtension checks for the supported extensions.
func isValidExtension(ext string, extensions []string) bool {
for _, ex := range extensions {
if ex == ext {
return true
}
}
return false
}

15
go.mod
View File

@@ -3,23 +3,22 @@ module github.com/esimov/caire
go 1.22 go 1.22
require ( require (
gioui.org v0.3.1 gioui.org v0.8.0
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/esimov/pigo v1.4.5 github.com/esimov/pigo v1.4.5
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 golang.org/x/exp v0.0.0-20240707233637-46b078467d37
golang.org/x/image v0.5.0 golang.org/x/image v0.18.0
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
) )
require ( require (
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
gioui.org/shader v1.0.8 // indirect gioui.org/shader v1.0.8 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 // indirect github.com/go-text/typesetting v0.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

56
go.sum
View File

@@ -1,10 +1,8 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.3.1 h1:hslYkrkIWvx28Mxe3A87opl+8s9mnWsnWmPDh11+zco= gioui.org v0.8.0 h1:QV5p5JvsmSmGiIXVYOKn6d9YDliTfjtLlVf5J+BZ9Pg=
gioui.org v0.3.1/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q= gioui.org v0.8.0/go.mod h1:vEMmpxMOd/iwJhXvGVIzWEbxMWhnMQ9aByOGQdlQ8rc=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -15,10 +13,10 @@ github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44am
github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg= github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg=
github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w= github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo= github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -29,46 +27,24 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,47 +0,0 @@
package caire
import (
"image"
"image/color"
)
// Grayscale converts the image to grayscale mode.
func (p *Processor) Grayscale(src *image.NRGBA) *image.NRGBA {
dx, dy := src.Bounds().Max.X, src.Bounds().Max.Y
dst := image.NewNRGBA(src.Bounds())
for x := 0; x < dx; x++ {
for y := 0; y < dy; y++ {
r, g, b, _ := src.At(x, y).RGBA()
lum := float32(r)*0.299 + float32(g)*0.587 + float32(b)*0.114
pixel := color.Gray{Y: uint8(lum / 256)}
dst.Set(x, y, pixel)
}
}
return dst
}
// Dither converts an image to black and white image, where the white is fully transparent.
func (p *Processor) Dither(src *image.NRGBA) *image.NRGBA {
var (
bounds = src.Bounds()
dithered = image.NewNRGBA(bounds)
dx = bounds.Dx()
dy = bounds.Dy()
)
for x := 0; x < dx; x++ {
for y := 0; y < dy; y++ {
r, g, b, _ := src.At(x, y).RGBA()
threshold := func() color.Color {
if r > 127 && g > 127 && b > 127 {
return color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
}
return color.NRGBA{A: 0x00}
}
dithered.Set(x, y, threshold())
}
}
return dithered
}

View File

@@ -1,29 +0,0 @@
package caire
import (
"image"
"image/color"
"testing"
)
func TestImage_GrayscaleMode(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
for i := 0; i < img.Bounds().Dx(); i++ {
for j := 0; j < img.Bounds().Dy(); j++ {
img.Set(i, j, color.RGBA{177, 177, 177, 255})
}
}
for i := 0; i < img.Bounds().Dx(); i++ {
for j := 0; j < img.Bounds().Dy(); j++ {
r, g, b, _ := img.At(i, j).RGBA()
r = r >> 8
g = g >> 8
b = b >> 8
if r != g || r != b || g != b {
t.Errorf("R, G, B value expected to be equal. Got %v, %v, %v", r, g, b)
}
}
}
}

287
gui.go
View File

@@ -26,6 +26,13 @@ import (
"github.com/esimov/caire/utils" "github.com/esimov/caire/utils"
) )
type hudControlType int
const (
hudShowSeams hudControlType = iota
hudShowDebugMask
)
const ( const (
// The starting colors for the linear gradient, used when the image is resized both horizontally and vertically. // The starting colors for the linear gradient, used when the image is resized both horizontally and vertically.
// In this case the preview mode is deactivated and a dynamic gradient overlay is shown. // In this case the preview mode is deactivated and a dynamic gradient overlay is shown.
@@ -40,8 +47,8 @@ const (
) )
var ( var (
maxScreenX float32 = 1024 maxScreenX float32 = 1280
maxScreenY float32 = 640 maxScreenY float32 = 720
defaultBkgColor = color.Transparent defaultBkgColor = color.Transparent
defaultFillColor = color.Black defaultFillColor = color.Black
@@ -60,9 +67,9 @@ type Gui struct {
chrot bool chrot bool
angle float32 angle float32
window struct { window struct {
w float32 width float32
h float32 height float32
title string title string
} }
color struct { color struct {
randR uint8 randR uint8
@@ -74,70 +81,71 @@ type Gui struct {
} }
timeStamp time.Time timeStamp time.Time
} }
proc struct { process struct {
isDone bool isDone bool
img image.Image img image.Image
seams []Seam seams []Seam
wrk <-chan worker worker <-chan worker
err chan<- error err chan<- error
} }
cp *Processor proc *Processor
cop *imop.Composite compOp *imop.Composite
bop *imop.Blend blendOp *imop.Blend
th *material.Theme theme *material.Theme
ctx layout.Context ctx layout.Context
huds map[int]*hudCtrl huds map[hudControlType]*hudCtrl
view struct { view struct {
huds layout.List huds layout.List
} }
} }
type hudCtrl struct { type hudCtrl struct {
visible widget.Bool visible widget.Bool
index int hudType hudControlType
title string title string
} }
// NewGUI initializes the Gio interface. // NewGUI initializes the Gio interface.
func NewGUI(w, h int) *Gui { func NewGUI(width, height int) *Gui {
defaultColor := color.NRGBA{R: 0x2d, G: 0x23, B: 0x2e, A: 0xff} defaultColor := color.NRGBA{R: 0x2d, G: 0x23, B: 0x2e, A: 0xff}
gui := &Gui{ gui := &Gui{
ctx: layout.Context{ ctx: layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Constraints{ Constraints: layout.Constraints{
Max: image.Pt(w, h), Max: image.Pt(width, height),
}, },
}, },
cop: imop.InitOp(), compOp: imop.InitOp(),
bop: imop.NewBlend(), blendOp: imop.NewBlend(),
huds: make(map[int]*hudCtrl), theme: material.NewTheme(),
th: material.NewTheme(), huds: make(map[hudControlType]*hudCtrl),
} }
gui.th.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
gui.th.TextSize = unit.Sp(12)
gui.th.Palette.ContrastBg = defaultColor
gui.th.FingerSize = 10
gui.initWindow(w, h) gui.theme.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
gui.theme.TextSize = unit.Sp(16)
gui.theme.Palette.ContrastBg = defaultColor
gui.theme.FingerSize = 10
gui.initWindow(width, height)
return gui return gui
} }
// Add adds a new hud control for debugging. // AddHudControl adds a new hud control for debugging.
func (g *Gui) Add(index int, title string, enabled bool) { func (g *Gui) AddHudControl(hudControlType hudControlType, title string, enabled bool) {
control := &hudCtrl{ control := &hudCtrl{
index: index, hudType: hudControlType,
title: title, title: title,
visible: widget.Bool{}, visible: widget.Bool{},
} }
control.visible.Value = enabled control.visible.Value = enabled
g.huds[index] = control g.huds[hudControlType] = control
} }
// initWindow creates and initializes the GUI window. // initWindow creates and initializes the GUI window.
func (g *Gui) initWindow(w, h int) { func (g *Gui) initWindow(width, height int) {
rand.NewSource(time.Now().UnixNano()) rand.NewSource(time.Now().UnixNano())
g.cfg.angle = 45 g.cfg.angle = 45
@@ -145,22 +153,22 @@ func (g *Gui) initWindow(w, h int) {
g.cfg.color.randG = uint8(random(1, 2)) g.cfg.color.randG = uint8(random(1, 2))
g.cfg.color.randB = uint8(random(1, 2)) g.cfg.color.randB = uint8(random(1, 2))
g.cfg.window.w, g.cfg.window.h = float32(w), float32(h) g.cfg.window.width, g.cfg.window.height = float32(width), float32(height)
g.cfg.x = interval{min: 0, max: float64(w)} g.cfg.x = interval{min: 0, max: float64(width)}
g.cfg.y = interval{min: 0, max: float64(h)} g.cfg.y = interval{min: 0, max: float64(height)}
g.cfg.color.background = defaultBkgColor g.cfg.color.background = defaultBkgColor
g.cfg.color.fill = defaultFillColor g.cfg.color.fill = defaultFillColor
if !resizeXY { if !resizeXY {
g.cfg.window.w, g.cfg.window.h = g.getWindowSize() g.cfg.window.width, g.cfg.window.height = g.getWindowSize()
} }
g.cfg.window.title = "Preview" g.cfg.window.title = "Preview process..."
} }
// getWindowSize returns the resized image dimension. // getWindowSize returns the resized image dimension.
func (g *Gui) getWindowSize() (float32, float32) { func (g *Gui) getWindowSize() (float32, float32) {
w, h := g.cfg.window.w, g.cfg.window.h w, h := g.cfg.window.width, g.cfg.window.height
// Maintain the image aspect ratio in case the image width and height is greater than the predefined window. // Maintain the image aspect ratio in case the image width and height is greater than the predefined window.
r := getRatio(w, h) r := getRatio(w, h)
if w > maxScreenX && h > maxScreenY { if w > maxScreenX && h > maxScreenY {
@@ -181,54 +189,115 @@ func (g *Gui) Run() error {
descRed, descGreen, descBlue bool descRed, descGreen, descBlue bool
) )
w := app.NewWindow(app.Title(g.cfg.window.title), app.Size(
unit.Dp(g.cfg.window.w), width := unit.Dp(g.cfg.window.width)
unit.Dp(g.cfg.window.h), height := unit.Dp(g.cfg.window.height)
))
w := new(app.Window)
w.Option(
app.Title(g.cfg.window.title),
app.Size(width, height),
app.MinSize(width, height),
app.MaxSize(width, height),
)
// Center the window.
w.Perform(system.ActionCenter) w.Perform(system.ActionCenter)
g.cfg.timeStamp = time.Now() g.cfg.timeStamp = time.Now()
if g.cp.Debug { if g.proc.Debug {
g.Add(0, "Show seams", true) g.AddHudControl(hudShowSeams, "Show seams", true)
if len(g.cp.MaskPath) > 0 || len(g.cp.RMaskPath) > 0 || g.cp.FaceDetect { if len(g.proc.MaskPath) > 0 || len(g.proc.RMaskPath) > 0 || g.proc.FaceDetect {
g.Add(1, "Debug mask", false) g.AddHudControl(hudShowDebugMask, "Debug mode", false)
} }
} }
abortFn := func() { abortFn := func() {
var dx, dy int var dx, dy int
if g.proc.img != nil { if g.process.img != nil {
bounds := g.proc.img.Bounds() bounds := g.process.img.Bounds()
dx, dy = bounds.Max.X, bounds.Max.Y dx, dy = bounds.Max.X, bounds.Max.Y
} }
if !g.proc.isDone {
if (g.cp.NewWidth > 0 && g.cp.NewWidth != dx) || if !g.process.isDone {
(g.cp.NewHeight > 0 && g.cp.NewHeight != dy) { if (g.proc.NewWidth > 0 && g.proc.NewWidth != dx) ||
(g.proc.NewHeight > 0 && g.proc.NewHeight != dy) {
errorMsg := fmt.Sprintf("%s %s %s", errorMsg := fmt.Sprintf("%s %s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage), utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("⇢ process aborted by the user...", utils.DefaultMessage), utils.DecorateText("⇢ process aborted by the user...", utils.DefaultMessage),
utils.DecorateText("✘\n", utils.ErrorMessage), utils.DecorateText("✘\n", utils.ErrorMessage),
) )
g.cp.Spinner.StopMsg = errorMsg g.proc.Spinner.StopMsg = errorMsg
g.cp.Spinner.Stop() g.proc.Spinner.Stop()
} }
} }
g.cp.Spinner.RestoreCursor() g.proc.Spinner.RestoreCursor()
} }
for { for {
select { select {
case e := <-w.Events(): case res := <-g.process.worker:
switch e := e.(type) { if res.done {
case system.FrameEvent: w.Option(app.Title("Done!"))
gtx := layout.NewContext(g.ctx.Ops, e) g.process.isDone = true
break
}
if resizeXY {
continue
}
key.InputOp{Tag: w, Keys: key.NameEscape}.Add(gtx.Ops) g.process.img = res.img
for _, ev := range gtx.Queue.Events(w) { g.process.seams = res.seams
if e, ok := ev.(key.Event); ok && e.Name == key.NameEscape {
w.Perform(system.ActionClose) if mask, ok := g.huds[hudShowDebugMask]; ok {
if mask.visible.Value {
bounds := res.img.Bounds()
srcBitmap := imop.NewBitmap(bounds)
dstBitmap := imop.NewBitmap(bounds)
uniformCol := image.NewNRGBA(bounds)
col := color.RGBA{R: 0x2f, G: 0xf3, B: 0xe0, A: 0xff}
draw.Draw(uniformCol, uniformCol.Bounds(), &image.Uniform{col}, image.Point{}, draw.Src)
_ = g.compOp.Set(imop.DstIn)
g.compOp.Draw(srcBitmap, res.mask, uniformCol, nil)
_ = g.blendOp.Set(imop.Screen)
_ = g.compOp.Set(imop.SrcAtop)
g.compOp.Draw(dstBitmap, res.img, srcBitmap.Img, g.blendOp)
g.process.img = dstBitmap.Img
}
}
if g.proc.vRes {
g.process.img = rotateImage270(g.process.img.(*image.NRGBA))
}
w.Invalidate()
default:
switch e := w.Event().(type) {
case app.FrameEvent:
g.ctx = app.NewContext(g.ctx.Ops, e)
for {
event, ok := g.ctx.Event(key.Filter{
Name: key.NameEscape,
})
if !ok {
break
}
switch event := event.(type) {
case key.Event:
switch event.Name {
case key.NameEscape:
w.Perform(system.ActionClose)
abortFn()
return nil
}
} }
} }
@@ -271,47 +340,12 @@ func (g *Gui) Run() error {
descBlue = !descBlue descBlue = !descBlue
} }
} }
g.draw(gtx, color.NRGBA{R: rc, G: gc, B: bc}) g.draw(color.NRGBA{R: rc, G: gc, B: bc})
e.Frame(gtx.Ops) e.Frame(g.ctx.Ops)
case system.DestroyEvent: case app.DestroyEvent:
abortFn() abortFn()
return e.Err return e.Err
} }
case res := <-g.proc.wrk:
if res.done {
g.proc.isDone = true
break
}
if resizeXY {
continue
}
g.proc.img = res.img
g.proc.seams = res.carver.Seams
if mask, ok := g.huds[1]; ok {
if mask.visible.Value {
srcBitmap := imop.NewBitmap(res.img.Bounds())
dstBitmap := imop.NewBitmap(res.img.Bounds())
uniform := image.NewNRGBA(res.img.Bounds())
col := color.RGBA{R: 0x2f, G: 0xf3, B: 0xe0, A: 0xff}
draw.Draw(uniform, uniform.Bounds(), &image.Uniform{col}, image.Point{}, draw.Src)
g.cop.Set(imop.DstIn)
g.cop.Draw(srcBitmap, res.debug, uniform, nil)
g.bop.Set(imop.ColorMode)
g.cop.Set(imop.DstOver)
g.cop.Draw(dstBitmap, res.img, srcBitmap.Img, g.bop)
g.proc.img = dstBitmap.Img
}
}
if g.cp.vRes {
g.proc.img = res.carver.RotateImage270(g.proc.img.(*image.NRGBA))
}
w.Invalidate()
} }
} }
} }
@@ -323,15 +357,14 @@ type (
// draw draws the resized image in the GUI window (obtained from a channel) // draw draws the resized image in the GUI window (obtained from a channel)
// and in case the debug mode is activated it prints out the seams. // and in case the debug mode is activated it prints out the seams.
func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) { func (g *Gui) draw(bgColor color.NRGBA) {
g.ctx = gtx g.ctx.Execute(op.InvalidateCmd{})
op.InvalidateOp{}.Add(gtx.Ops)
c := g.setColor(g.cfg.color.background) c := g.setColor(g.cfg.color.background)
paint.Fill(g.ctx.Ops, c) paint.Fill(g.ctx.Ops, c)
if g.proc.img != nil { if g.process.img != nil {
src := paint.NewImageOp(g.proc.img) src := paint.NewImageOp(g.process.img)
src.Add(g.ctx.Ops) src.Add(g.ctx.Ops)
layout.Stack{}.Layout(g.ctx, layout.Stack{}.Layout(g.ctx,
@@ -347,11 +380,11 @@ func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) {
Fit: widget.Contain, Fit: widget.Contain,
}.Layout(gtx) }.Layout(gtx)
if seam, ok := g.huds[0]; ok { if seam, ok := g.huds[hudShowSeams]; ok {
if seam.visible.Value { if seam.visible.Value {
tr := f32.Affine2D{} tr := f32.Affine2D{}
screen := layout.FPt(g.ctx.Constraints.Max) screen := layout.FPt(g.ctx.Constraints.Max)
width, height := float32(g.proc.img.Bounds().Dx()), float32(g.proc.img.Bounds().Dy()) width, height := float32(g.process.img.Bounds().Dx()), float32(g.process.img.Bounds().Dy())
sw, sh := float32(screen.X), float32(screen.Y) sw, sh := float32(screen.X), float32(screen.Y)
if sw > width { if sw > width {
@@ -362,7 +395,7 @@ func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) {
tr = tr.Scale(f32.Pt(sw/2, sh/2), f32.Pt(ratio, 1)) tr = tr.Scale(f32.Pt(sw/2, sh/2), f32.Pt(ratio, 1))
} }
if g.cp.vRes { if g.proc.vRes {
angle := float32(270 * math.Pi / 180) angle := float32(270 * math.Pi / 180)
half := float32(math.Round(float64(sh*0.5-height*0.5) * 0.5)) half := float32(math.Round(float64(sh*0.5-height*0.5) * 0.5))
@@ -378,14 +411,10 @@ func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) {
} }
op.Affine(tr).Add(gtx.Ops) op.Affine(tr).Add(gtx.Ops)
for _, s := range g.proc.seams { for _, s := range g.process.seams {
dpx := unit.Dp(s.X) dpx := gtx.Dp(unit.Dp(s.X))
dpy := unit.Dp(s.Y) dpy := gtx.Dp(unit.Dp(s.Y))
g.DrawSeam(g.proc.ShapeType, float32(dpx), float32(dpy), 1.0)
// Convert the image coordinates from pixel values to DP units.
dpiy := unit.Dp(float32(g.cfg.window.w) / float32(300))
dpix := unit.Dp(float32(g.cfg.window.h) / float32(300))
g.DrawSeam(g.cp.ShapeType, float32(dpx*dpix), float32(dpy*dpiy), 2.0)
} }
} }
} }
@@ -394,10 +423,10 @@ func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) {
}), }),
) )
} }
if g.cp.Debug { if g.proc.Debug {
layout.Stack{}.Layout(g.ctx, layout.Stack{}.Layout(g.ctx,
layout.Stacked(func(gtx C) D { layout.Stacked(func(gtx C) D {
hudHeight := 40 hudHeight := 30
r := image.Rectangle{ r := image.Rectangle{
Max: image.Point{ Max: image.Point{
X: gtx.Constraints.Max.X, X: gtx.Constraints.Max.X,
@@ -417,14 +446,14 @@ func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) {
Y: gtx.Dp(unit.Dp(0.5)), Y: gtx.Dp(unit.Dp(0.5)),
}, },
} }
paint.FillShape(gtx.Ops, color.NRGBA{R: 0x3B, G: 0x41, B: 0x3C, A: 0xaa}, clip.Rect(border).Op()) paint.FillShape(gtx.Ops, color.NRGBA{R: 0xd0, G: 0xcd, B: 0xd7, A: 0xaa}, clip.Rect(border).Op())
return layout.Dimensions{Size: r.Max} return layout.Dimensions{Size: r.Max}
}), }),
layout.Stacked(func(gtx C) D { layout.Stacked(func(gtx C) D {
return g.view.huds.Layout(gtx, len(g.huds), return g.view.huds.Layout(gtx, len(g.huds),
func(gtx layout.Context, index int) D { func(gtx layout.Context, index int) D {
if hud, ok := g.huds[index]; ok { if hud, ok := g.huds[hudControlType(index)]; ok {
checkbox := material.CheckBox(g.th, &hud.visible, fmt.Sprintf("%v", hud.title)) checkbox := material.CheckBox(g.theme, &hud.visible, fmt.Sprintf("%v", hud.title))
checkbox.Size = 20 checkbox.Size = 20
return checkbox.Layout(gtx) return checkbox.Layout(gtx)
} }
@@ -440,19 +469,19 @@ func (g *Gui) draw(gtx layout.Context, bgCol color.NRGBA) {
if resizeXY { if resizeXY {
var msg string var msg string
if !g.proc.isDone { if !g.process.isDone {
msg = "Preview is not available while the image is resized both horizontally and vertically!" msg = "Preview is not available while the image is resized both horizontally and vertically!"
} else { } else {
msg = "Done, you may close this window!" msg = "Done, you may close this window!"
bgCol = color.NRGBA{R: 45, G: 45, B: 42, A: 0xff} bgColor = color.NRGBA{R: 45, G: 45, B: 42, A: 0xff}
} }
g.displayMessage(g.ctx, bgCol, msg) g.displayMessage(g.ctx, bgColor, msg)
} }
} }
// displayMessage show a static message when the image is resized both horizontally and vertically. // displayMessage show a static message when the image is resized both horizontally and vertically.
func (g *Gui) displayMessage(ctx layout.Context, bgCol color.NRGBA, msg string) { func (g *Gui) displayMessage(ctx layout.Context, bgCol color.NRGBA, msg string) {
g.th.Palette.Fg = color.NRGBA{R: 251, G: 254, B: 249, A: 0xff} g.theme.Palette.Fg = color.NRGBA{R: 251, G: 254, B: 249, A: 0xff}
paint.ColorOp{Color: bgCol}.Add(ctx.Ops) paint.ColorOp{Color: bgCol}.Add(ctx.Ops)
rect := image.Rectangle{ rect := image.Rectangle{
@@ -465,7 +494,7 @@ func (g *Gui) displayMessage(ctx layout.Context, bgCol color.NRGBA, msg string)
layout.Stack{}.Layout(ctx, layout.Stack{}.Layout(ctx,
layout.Stacked(func(gtx C) D { layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(ctx, func(gtx C) D { return layout.UniformInset(unit.Dp(4)).Layout(ctx, func(gtx C) D {
if !g.proc.isDone { if !g.process.isDone {
gtx.Constraints.Min.Y = 0 gtx.Constraints.Min.Y = 0
tr := f32.Affine2D{} tr := f32.Affine2D{}
dr := image.Rectangle{Max: gtx.Constraints.Min} dr := image.Rectangle{Max: gtx.Constraints.Min}
@@ -508,7 +537,7 @@ func (g *Gui) displayMessage(ctx layout.Context, bgCol color.NRGBA, msg string)
layout.Stacked(func(gtx C) D { layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(ctx, func(gtx C) D { return layout.UniformInset(unit.Dp(4)).Layout(ctx, func(gtx C) D {
return layout.Center.Layout(ctx, func(gtx C) D { return layout.Center.Layout(ctx, func(gtx C) D {
m := material.Label(g.th, unit.Sp(40), msg) m := material.Label(g.theme, unit.Sp(40), msg)
m.Alignment = text.Middle m.Alignment = text.Middle
return m.Layout(gtx) return m.Layout(gtx)
@@ -517,13 +546,13 @@ func (g *Gui) displayMessage(ctx layout.Context, bgCol color.NRGBA, msg string)
}), }),
layout.Stacked(func(gtx C) D { layout.Stacked(func(gtx C) D {
info := "(You will be notified once the process is finished.)" info := "(You will be notified once the process is finished.)"
if g.proc.isDone { if g.process.isDone {
return layout.Dimensions{} return layout.Dimensions{}
} }
return layout.Inset{Top: 70}.Layout(ctx, func(gtx C) D { return layout.Inset{Top: 70}.Layout(ctx, func(gtx C) D {
return layout.Center.Layout(ctx, func(gtx C) D { return layout.Center.Layout(ctx, func(gtx C) D {
return material.Label(g.th, unit.Sp(13), info).Layout(gtx) return material.Label(g.theme, unit.Sp(13), info).Layout(gtx)
}) })
}) })
}), }),

185
image.go
View File

@@ -1,28 +1,83 @@
package caire package caire
import ( import (
"errors"
"fmt"
"image" "image"
"image/color" "image/color"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
"github.com/esimov/caire/utils"
"golang.org/x/image/bmp"
) )
// Grayscale converts the source image to grayscale mode. // decodeImg decodes an image file to type image.Image
func (c *Carver) Grayscale(src *image.NRGBA) *image.NRGBA { func decodeImg(src string) (image.Image, error) {
dx, dy := src.Bounds().Max.X, src.Bounds().Max.Y file, err := os.Open(src)
dst := image.NewNRGBA(src.Bounds()) if err != nil {
return nil, fmt.Errorf("could not open the mask file: %v", err)
for x := 0; x < dx; x++ {
for y := 0; y < dy; y++ {
r, g, b, _ := src.At(x, y).RGBA()
lum := float32(r)*0.299 + float32(g)*0.587 + float32(b)*0.114
pixel := color.Gray{Y: uint8(lum / 256)}
dst.Set(x, y, pixel)
}
} }
return dst
ctype, err := utils.DetectContentType(file.Name())
if err != nil {
return nil, err
}
if !strings.Contains(ctype.(string), "image") {
return nil, fmt.Errorf("the mask should be an image file")
}
img, _, err := image.Decode(file)
if err != nil {
return nil, fmt.Errorf("could not decode the mask file: %v", err)
}
return img, nil
} }
// RotateImage90 rotate the image by 90 degree counter clockwise. // encodeImg encodes an image to a destination of type io.Writer.
func (c *Carver) RotateImage90(src *image.NRGBA) *image.NRGBA { func encodeImg(p *Processor, w io.Writer, img *image.NRGBA) error {
switch w := w.(type) {
case *os.File:
ext := filepath.Ext(w.Name())
switch ext {
case "", ".jpg", ".jpeg":
res, err := Resize(p, img)
if err != nil {
return err
}
return jpeg.Encode(w, res, &jpeg.Options{Quality: 100})
case ".png":
res, err := Resize(p, img)
if err != nil {
return err
}
return png.Encode(w, res)
case ".bmp":
res, err := Resize(p, img)
if err != nil {
return err
}
return bmp.Encode(w, res)
default:
return errors.New("unsupported image format")
}
default:
res, err := Resize(p, img)
if err != nil {
return err
}
return jpeg.Encode(w, res, &jpeg.Options{Quality: 100})
}
}
// rotateImage90 rotate the image by 90 degree counter clockwise.
func rotateImage90(src *image.NRGBA) *image.NRGBA {
b := src.Bounds() b := src.Bounds()
dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X)) dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X))
for dstY := 0; dstY < b.Max.X; dstY++ { for dstY := 0; dstY < b.Max.X; dstY++ {
@@ -38,8 +93,8 @@ func (c *Carver) RotateImage90(src *image.NRGBA) *image.NRGBA {
return dst return dst
} }
// RotateImage270 rotate the image by 270 degree counter clockwise. // rotateImage270 rotate the image by 270 degree counter clockwise.
func (c *Carver) RotateImage270(src *image.NRGBA) *image.NRGBA { func rotateImage270(src *image.NRGBA) *image.NRGBA {
b := src.Bounds() b := src.Bounds()
dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X)) dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X))
for dstY := 0; dstY < b.Max.X; dstY++ { for dstY := 0; dstY < b.Max.X; dstY++ {
@@ -52,11 +107,71 @@ func (c *Carver) RotateImage270(src *image.NRGBA) *image.NRGBA {
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
} }
} }
return dst
}
// imgToNRGBA converts any image type to *image.NRGBA with min-point at (0, 0).
func imgToNRGBA(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
if src0, ok := img.(*image.NRGBA); ok {
return src0
}
}
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
dstBounds := srcBounds.Sub(srcBounds.Min)
dstW := dstBounds.Dx()
dstH := dstBounds.Dy()
dst := image.NewNRGBA(dstBounds)
switch src := img.(type) {
case *image.NRGBA:
rowSize := srcBounds.Dx() * 4
for dstY := 0; dstY < dstH; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
}
}
case *image.YCbCr:
for dstY := 0; dstY < dstH; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
srcX := srcMinX + dstX
srcY := srcMinY + dstY
siy := src.YOffset(srcX, srcY)
sic := src.COffset(srcX, srcY)
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
dst.Pix[di+0] = r
dst.Pix[di+1] = g
dst.Pix[di+2] = b
dst.Pix[di+3] = 0xff
di += 4
}
}
default:
for dstY := 0; dstY < dstH; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
dst.Pix[di+0] = c.R
dst.Pix[di+1] = c.G
dst.Pix[di+2] = c.B
dst.Pix[di+3] = c.A
di += 4
}
}
}
return dst return dst
} }
// imgToPix converts an image to a pixel array. // imgToPix converts an image to a pixel array.
func (c *Carver) imgToPix(src *image.NRGBA) []uint8 { func imgToPix(src *image.NRGBA) []uint8 {
bounds := src.Bounds() bounds := src.Bounds()
pixels := make([]uint8, 0, bounds.Max.X*bounds.Max.Y*4) pixels := make([]uint8, 0, bounds.Max.X*bounds.Max.Y*4)
@@ -66,12 +181,13 @@ func (c *Carver) imgToPix(src *image.NRGBA) []uint8 {
pixels = append(pixels, uint8(r>>8), uint8(g>>8), uint8(b>>8), 255) pixels = append(pixels, uint8(r>>8), uint8(g>>8), uint8(b>>8), 255)
} }
} }
return pixels return pixels
} }
// pixToImage converts an array buffer to an image. // pixToImage converts an array buffer to an image.
func (c *Carver) pixToImage(pixels []uint8) image.Image { func pixToImage(pixels []uint8, width, height int) image.Image {
dst := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height)) dst := image.NewNRGBA(image.Rect(0, 0, width, height))
bounds := dst.Bounds() bounds := dst.Bounds()
dx, dy := bounds.Max.X, bounds.Max.Y dx, dy := bounds.Max.X, bounds.Max.Y
col := color.NRGBA{ col := color.NRGBA{
@@ -91,12 +207,13 @@ func (c *Carver) pixToImage(pixels []uint8) image.Image {
dst.SetNRGBA(x, int(y/4), col) dst.SetNRGBA(x, int(y/4), col)
} }
} }
return dst return dst
} }
// rgbToGrayscale converts an image to grayscale mode and // rgbToGrayscale converts an image to grayscale mode and
// returns the pixel values as an one dimensional array. // returns the pixel values as an one dimensional array.
func (c *Carver) rgbToGrayscale(src *image.NRGBA) []uint8 { func rgbToGrayscale(src *image.NRGBA) []uint8 {
width, height := src.Bounds().Dx(), src.Bounds().Dy() width, height := src.Bounds().Dx(), src.Bounds().Dy()
gray := make([]uint8, width*height) gray := make([]uint8, width*height)
@@ -110,5 +227,31 @@ func (c *Carver) rgbToGrayscale(src *image.NRGBA) []uint8 {
) )
} }
} }
return gray return gray
} }
// dither converts an image to black and white image, where the white is fully transparent.
func dither(src *image.NRGBA) *image.NRGBA {
var (
bounds = src.Bounds()
dithered = image.NewNRGBA(bounds)
dx = bounds.Dx()
dy = bounds.Dy()
)
for x := 0; x < dx; x++ {
for y := 0; y < dy; y++ {
r, g, b, _ := src.At(x, y).RGBA()
threshold := func() color.Color {
if r > 127 && g > 127 && b > 127 {
return color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
}
return color.NRGBA{A: 0x00}
}
dithered.Set(x, y, threshold())
}
}
return dithered
}

View File

@@ -5,7 +5,7 @@ import (
"image/color" "image/color"
"image/color/palette" "image/color/palette"
"image/draw" "image/draw"
"io/ioutil" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -14,7 +14,7 @@ import (
func TestImage_ShouldGetSampleImage(t *testing.T) { func TestImage_ShouldGetSampleImage(t *testing.T) {
path := filepath.Join("./testdata", "sample.jpg") path := filepath.Join("./testdata", "sample.jpg")
_, err := ioutil.ReadFile(path) _, err := os.ReadFile(path)
if err != nil { if err != nil {
t.Errorf("Should get the sample image") t.Errorf("Should get the sample image")
} }

View File

@@ -18,31 +18,33 @@ import (
"github.com/esimov/caire/utils" "github.com/esimov/caire/utils"
) )
type BlendType int
const ( const (
Normal = "normal" Normal BlendType = iota
Darken = "darken" Darken
Lighten = "lighten" Lighten
Multiply = "multiply" Multiply
Screen = "screen" Screen
Overlay = "overlay" Overlay
SoftLight = "soft_light" SoftLight
HardLight = "hard_light" HardLight
ColorDodge = "color_dodge" ColorDodge
ColorBurn = "color_burn" ColorBurn
Difference = "difference" Difference
Exclusion = "exclusion" Exclusion
// Non-separable blend modes // Non-separable blend modes
Hue = "hue" Hue
Saturation = "saturation" Saturation
ColorMode = "color" ColorMode
Luminosity = "luminosity" Luminosity
) )
// Blend struct contains the currently active blend mode and all the supported blend modes. // Blend struct contains the currently active blend mode and all the supported blend modes.
type Blend struct { type Blend struct {
Current string CurrentOp BlendType
Modes []string Modes []BlendType
} }
// Color represents the RGB channel of a specific color. // Color represents the RGB channel of a specific color.
@@ -50,42 +52,31 @@ type Color struct {
R, G, B float64 R, G, B float64
} }
// NewBlend initializes a new Blend. // NewBlend intantiates a new Blend.
func NewBlend() *Blend { func NewBlend() *Blend {
return &Blend{ return &Blend{
Modes: []string{ Modes: []BlendType{
Normal, Normal, Darken, Lighten, Multiply,
Darken, Screen, Overlay, SoftLight, HardLight,
Lighten, ColorDodge, ColorBurn, Difference, Exclusion,
Multiply, Hue, Saturation, ColorMode, Luminosity,
Screen,
Overlay,
SoftLight,
HardLight,
ColorDodge,
ColorBurn,
Difference,
Exclusion,
Hue,
Saturation,
ColorMode,
Luminosity,
}, },
} }
} }
// Set activate one of the supported blend modes. // Set activate one of the supported blend modes.
func (bl *Blend) Set(blendType string) error { func (bl *Blend) Set(blendType BlendType) error {
if utils.Contains(bl.Modes, blendType) { if utils.Contains(bl.Modes, blendType) {
bl.Current = blendType bl.CurrentOp = blendType
return nil return nil
} }
return fmt.Errorf("unsupported blend mode") return fmt.Errorf("unsupported blend mode")
} }
// Get returns the active blend mode. // Get returns the active blend mode.
func (bl *Blend) Get() string { func (bl *Blend) Get() BlendType {
return bl.Current return bl.CurrentOp
} }
// Lum gets the luminosity of a color. // Lum gets the luminosity of a color.

View File

@@ -14,8 +14,6 @@ func TestBlend_Basic(t *testing.T) {
op := NewBlend() op := NewBlend()
assert.Empty(op.Get()) assert.Empty(op.Get())
err := op.Set("blend_mode_not_supported")
assert.Error(err)
op.Set(Darken) op.Set(Darken)
assert.Equal(Darken, op.Get()) assert.Equal(Darken, op.Get())
op.Set(Lighten) op.Set(Lighten)

View File

@@ -1,469 +1,472 @@
// Package imop implements the Porter-Duff composition operations // Package imop implements the Porter-Duff composition operations
// used for mixing a graphic element with its backdrop. // used for mixing a graphic element with its backdrop.
// Porter and Duff presented in their paper 12 different composition operation, but the // Porter and Duff presented in their paper 12 different composition operation, but the
// core image/draw core package implements only the source-over-destination and source. // core image/draw core package implements only the source-over-destination and source.
// This package implements all of the 12 composite operation together with some blending modes. // This package implements all of the 12 composite operation together with some blending modes.
package imop package imop
import ( import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"math" "math"
"github.com/esimov/caire/utils" "github.com/esimov/caire/utils"
) )
const ( type CompType int
Clear = "clear"
Copy = "copy" const (
Dst = "dst" Clear CompType = iota
SrcOver = "src_over" Copy
DstOver = "dst_over" Dst
SrcIn = "src_in" SrcOver
DstIn = "dst_in" DstOver
SrcOut = "src_out" SrcIn
DstOut = "dst_out" DstIn
SrcAtop = "src_atop" SrcOut
DstAtop = "dst_atop" DstOut
Xor = "xor" SrcAtop
) DstAtop
Xor
// Bitmap holds an image type as a placeholder for the Porter-Duff composition )
// operations which can be used as a source or destination image.
type Bitmap struct { // Bitmap holds an image type as a placeholder for the Porter-Duff composition
Img *image.NRGBA // operations which can be used as a source or destination image.
} type Bitmap struct {
Img *image.NRGBA
// Composite struct contains the currently active composition operation and all the supported operations. }
type Composite struct {
CurrentOp string // Composite struct contains the currently active composition operation and all the supported operations.
Ops []string type Composite struct {
} CurrentOp CompType
Ops []CompType
// NewBitmap initializes a new Bitmap. }
func NewBitmap(rect image.Rectangle) *Bitmap {
return &Bitmap{ // NewBitmap initializes a new Bitmap.
Img: image.NewNRGBA(rect), func NewBitmap(rect image.Rectangle) *Bitmap {
} return &Bitmap{
} Img: image.NewNRGBA(rect),
}
// InitOp initializes a new composition operation. }
func InitOp() *Composite {
return &Composite{ // InitOp initializes a new composition operation.
CurrentOp: SrcOver, func InitOp() *Composite {
Ops: []string{ return &Composite{
Clear, CurrentOp: SrcOver,
Copy, Ops: []CompType{
Dst, Clear,
SrcOver, Copy,
DstOver, Dst,
SrcIn, SrcOver,
DstIn, DstOver,
SrcOut, SrcIn,
DstOut, DstIn,
SrcAtop, SrcOut,
DstAtop, DstOut,
Xor, SrcAtop,
}, DstAtop,
} Xor,
} },
}
// Set changes the current composition operation. }
func (op *Composite) Set(cop string) error {
if utils.Contains(op.Ops, cop) { // Set changes the current composition operation.
op.CurrentOp = cop func (op *Composite) Set(compType CompType) error {
return nil if utils.Contains(op.Ops, compType) {
} op.CurrentOp = compType
return fmt.Errorf("unsupported composition operation") return nil
} }
// Set changes the current composition operation. return fmt.Errorf("unsupported composition operation")
func (op *Composite) Get() string { }
return op.CurrentOp
} // Set changes the current composition operation.
func (op *Composite) Get() CompType {
// Draw applies the currently active Ported-Duff composition operation formula, return op.CurrentOp
// taking as parameter the source and the destination image and draws the result into the bitmap. }
// If a blend mode is activated it will plug in the alpha blending formula also into the equation.
func (op *Composite) Draw(bitmap *Bitmap, src, dst *image.NRGBA, bl *Blend) { // Draw applies the currently active Ported-Duff composition operation formula,
dx, dy := src.Bounds().Dx(), src.Bounds().Dy() // taking as parameter the source and the destination image and draws the result into the bitmap.
// If a blend mode is activated it will plug in the alpha blending formula also into the equation.
var ( func (op *Composite) Draw(bitmap *Bitmap, src, dst *image.NRGBA, blend *Blend) {
r, g, b, a uint32 dx, dy := src.Bounds().Dx(), src.Bounds().Dy()
rn, gn, bn, an float64
) var (
r, g, b, a uint32
for x := 0; x < dx; x++ { rn, gn, bn, an float64
for y := 0; y < dy; y++ { )
r1, g1, b1, a1 := src.At(x, y).RGBA()
r2, g2, b2, a2 := dst.At(x, y).RGBA() for x := 0; x < dx; x++ {
for y := 0; y < dy; y++ {
rs, gs, bs, as := r1>>8, g1>>8, b1>>8, a1>>8 r1, g1, b1, a1 := src.At(x, y).RGBA()
rb, gb, bb, ab := r2>>8, g2>>8, b2>>8, a2>>8 r2, g2, b2, a2 := dst.At(x, y).RGBA()
// normalize the values. rs, gs, bs, as := r1>>8, g1>>8, b1>>8, a1>>8
rsn := float64(rs) / 255 rb, gb, bb, ab := r2>>8, g2>>8, b2>>8, a2>>8
gsn := float64(gs) / 255
bsn := float64(bs) / 255 // normalize the values.
asn := float64(as) / 255 rsn := float64(rs) / 255
gsn := float64(gs) / 255
rbn := float64(rb) / 255 bsn := float64(bs) / 255
gbn := float64(gb) / 255 asn := float64(as) / 255
bbn := float64(bb) / 255
abn := float64(ab) / 255 rbn := float64(rb) / 255
gbn := float64(gb) / 255
// applying the alpha composition formula bbn := float64(bb) / 255
switch op.CurrentOp { abn := float64(ab) / 255
case Clear:
rn, gn, bn, an = 0, 0, 0, 0 // applying the alpha composition formula
case Copy: switch op.CurrentOp {
rn = asn * rsn case Clear:
gn = asn * gsn rn, gn, bn, an = 0, 0, 0, 0
bn = asn * bsn case Copy:
an = asn * asn rn = asn * rsn
case Dst: gn = asn * gsn
rn = abn * rbn bn = asn * bsn
gn = abn * gbn an = asn * asn
bn = abn * bbn case Dst:
an = abn * abn rn = abn * rbn
case SrcOver: gn = abn * gbn
rn = asn*rsn + abn*rbn*(1-asn) bn = abn * bbn
gn = asn*gsn + abn*gbn*(1-asn) an = abn * abn
bn = asn*bsn + abn*bbn*(1-asn) case SrcOver:
an = asn + abn*(1-asn) rn = asn*rsn + abn*rbn*(1-asn)
case DstOver: gn = asn*gsn + abn*gbn*(1-asn)
rn = asn*rsn*(1-abn) + abn*rbn bn = asn*bsn + abn*bbn*(1-asn)
gn = asn*gsn*(1-abn) + abn*gbn an = asn + abn*(1-asn)
bn = asn*bsn*(1-abn) + abn*bbn case DstOver:
an = asn*(1-abn) + abn rn = asn*rsn*(1-abn) + abn*rbn
case SrcIn: gn = asn*gsn*(1-abn) + abn*gbn
rn = asn * rsn * abn bn = asn*bsn*(1-abn) + abn*bbn
gn = asn * gsn * abn an = asn*(1-abn) + abn
bn = asn * bsn * abn case SrcIn:
an = asn * abn rn = asn * rsn * abn
case DstIn: gn = asn * gsn * abn
rn = abn * rbn * asn bn = asn * bsn * abn
gn = abn * gbn * asn an = asn * abn
bn = abn * bbn * asn case DstIn:
an = abn * asn rn = abn * rbn * asn
case SrcOut: gn = abn * gbn * asn
rn = asn * rsn * (1 - abn) bn = abn * bbn * asn
gn = asn * gsn * (1 - abn) an = abn * asn
bn = asn * bsn * (1 - abn) case SrcOut:
an = asn * (1 - abn) rn = asn * rsn * (1 - abn)
case DstOut: gn = asn * gsn * (1 - abn)
rn = abn * rbn * (1 - asn) bn = asn * bsn * (1 - abn)
gn = abn * gbn * (1 - asn) an = asn * (1 - abn)
bn = abn * bbn * (1 - asn) case DstOut:
an = abn * (1 - asn) rn = abn * rbn * (1 - asn)
case SrcAtop: gn = abn * gbn * (1 - asn)
rn = asn*rsn*abn + (1-asn)*abn*rbn bn = abn * bbn * (1 - asn)
gn = asn*gsn*abn + (1-asn)*abn*gbn an = abn * (1 - asn)
bn = asn*bsn*abn + (1-asn)*abn*bbn case SrcAtop:
an = asn*abn + abn*(1-asn) rn = asn*rsn*abn + (1-asn)*abn*rbn
case DstAtop: gn = asn*gsn*abn + (1-asn)*abn*gbn
rn = asn*rsn*(1-abn) + abn*rbn*asn bn = asn*bsn*abn + (1-asn)*abn*bbn
gn = asn*gsn*(1-abn) + abn*gbn*asn an = asn*abn + abn*(1-asn)
bn = asn*bsn*(1-abn) + abn*bbn*asn case DstAtop:
an = asn*(1-abn) + abn*asn rn = asn*rsn*(1-abn) + abn*rbn*asn
case Xor: gn = asn*gsn*(1-abn) + abn*gbn*asn
rn = asn*rsn*(1-abn) + abn*rbn*(1-asn) bn = asn*bsn*(1-abn) + abn*bbn*asn
gn = asn*gsn*(1-abn) + abn*gbn*(1-asn) an = asn*(1-abn) + abn*asn
bn = asn*bsn*(1-abn) + abn*bbn*(1-asn) case Xor:
an = asn*(1-abn) + abn*(1-asn) rn = asn*rsn*(1-abn) + abn*rbn*(1-asn)
} gn = asn*gsn*(1-abn) + abn*gbn*(1-asn)
bn = asn*bsn*(1-abn) + abn*bbn*(1-asn)
r = uint32(rn * 255) an = asn*(1-abn) + abn*(1-asn)
g = uint32(gn * 255) }
b = uint32(bn * 255)
a = uint32(an * 255) r = uint32(rn * 255)
g = uint32(gn * 255)
bitmap.Img.Set(x, y, color.NRGBA{ b = uint32(bn * 255)
R: uint8(r), a = uint32(an * 255)
G: uint8(g),
B: uint8(b), bitmap.Img.Set(x, y, color.NRGBA{
A: uint8(a), R: uint8(r),
}) G: uint8(g),
B: uint8(b),
// applying the blending mode A: uint8(a),
if bl != nil { })
rn, gn, bn, an = 0, 0, 0, 0 // reset the colors
r1, g1, b1, a1 = src.At(x, y).RGBA() // applying the blending mode
r2, g2, b2, a2 = dst.At(x, y).RGBA() if blend != nil {
rn, gn, bn, an = 0, 0, 0, 0 // reset the colors
rs, gs, bs, as = r1>>8, g1>>8, b1>>8, a1>>8 r1, g1, b1, a1 = src.At(x, y).RGBA()
rb, gb, bb, ab = r2>>8, g2>>8, b2>>8, a2>>8 r2, g2, b2, a2 = dst.At(x, y).RGBA()
rsn = float64(rs) / 255 rs, gs, bs, as = r1>>8, g1>>8, b1>>8, a1>>8
gsn = float64(gs) / 255 rb, gb, bb, ab = r2>>8, g2>>8, b2>>8, a2>>8
bsn = float64(bs) / 255
asn = float64(as) / 255 rsn = float64(rs) / 255
gsn = float64(gs) / 255
rbn = float64(rb) / 255 bsn = float64(bs) / 255
gbn = float64(gb) / 255 asn = float64(as) / 255
bbn = float64(bb) / 255
abn = float64(ab) / 255 rbn = float64(rb) / 255
gbn = float64(gb) / 255
foreground := Color{R: rsn, G: gsn, B: bsn} bbn = float64(bb) / 255
background := Color{R: rbn, G: gbn, B: bbn} abn = float64(ab) / 255
switch bl.Current { foreground := Color{R: rsn, G: gsn, B: bsn}
case Normal: background := Color{R: rbn, G: gbn, B: bbn}
rn, gn, bn, an = rsn, gsn, bsn, asn
case Darken: switch blend.CurrentOp {
rn = utils.Min(rsn, rbn) case Normal:
gn = utils.Min(gsn, gbn) rn, gn, bn, an = rsn, gsn, bsn, asn
bn = utils.Min(bsn, bbn) case Darken:
an = utils.Min(asn, abn) rn = utils.Min(rsn, rbn)
case Lighten: gn = utils.Min(gsn, gbn)
rn = utils.Max(rsn, rbn) bn = utils.Min(bsn, bbn)
gn = utils.Max(gsn, gbn) an = utils.Min(asn, abn)
bn = utils.Max(bsn, bbn) case Lighten:
an = utils.Max(asn, abn) rn = utils.Max(rsn, rbn)
case Screen: gn = utils.Max(gsn, gbn)
rn = 1 - (1-rsn)*(1-rbn) bn = utils.Max(bsn, bbn)
gn = 1 - (1-gsn)*(1-gbn) an = utils.Max(asn, abn)
bn = 1 - (1-bsn)*(1-bbn) case Screen:
an = 1 - (1-asn)*(1-abn) rn = 1 - (1-rsn)*(1-rbn)
case Multiply: gn = 1 - (1-gsn)*(1-gbn)
rn = rsn * rbn bn = 1 - (1-bsn)*(1-bbn)
gn = gsn * gbn an = 1 - (1-asn)*(1-abn)
bn = bsn * bbn case Multiply:
an = asn * abn rn = rsn * rbn
case Overlay: gn = gsn * gbn
if rsn <= 0.5 { bn = bsn * bbn
rn = 2 * rsn * rbn an = asn * abn
} else { case Overlay:
rn = 1 - 2*(1-rsn)*(1-rbn) if rsn <= 0.5 {
} rn = 2 * rsn * rbn
} else {
if gsn <= 0.5 { rn = 1 - 2*(1-rsn)*(1-rbn)
gn = 2 * gsn * gbn }
} else {
gn = 1 - 2*(1-gsn)*(1-gbn) if gsn <= 0.5 {
} gn = 2 * gsn * gbn
} else {
if bsn <= 0.5 { gn = 1 - 2*(1-gsn)*(1-gbn)
bn = 2 * bsn * bbn }
} else {
bn = 1 - 2*(1-bsn)*(1-bbn) if bsn <= 0.5 {
} bn = 2 * bsn * bbn
} else {
if asn <= 0.5 { bn = 1 - 2*(1-bsn)*(1-bbn)
an = 2 * asn * abn }
} else {
an = 1 - 2*(1-asn)*(1-abn) if asn <= 0.5 {
} an = 2 * asn * abn
case SoftLight: } else {
if rbn < 0.5 { an = 1 - 2*(1-asn)*(1-abn)
rn = rsn - (1-2*rbn)*rsn*(1-rsn) }
} else { case SoftLight:
var w3r float64 if rbn < 0.5 {
if rsn < 0.25 { rn = rsn - (1-2*rbn)*rsn*(1-rsn)
w3r = ((16*rsn-12)*rsn + 4) * rsn } else {
} else { var w3r float64
w3r = math.Sqrt(rsn) if rsn < 0.25 {
} w3r = ((16*rsn-12)*rsn + 4) * rsn
rn = rsn + (2*rbn-1)*(w3r-rsn) } else {
} w3r = math.Sqrt(rsn)
}
if gbn < 0.5 { rn = rsn + (2*rbn-1)*(w3r-rsn)
gn = gsn - (1-2*gbn)*gsn*(1-gsn) }
} else {
var w3g float64 if gbn < 0.5 {
if gsn < 0.25 { gn = gsn - (1-2*gbn)*gsn*(1-gsn)
w3g = ((16*gsn-12)*gsn + 4) * gsn } else {
} else { var w3g float64
w3g = math.Sqrt(gsn) if gsn < 0.25 {
} w3g = ((16*gsn-12)*gsn + 4) * gsn
gn = gsn + (2*gbn-1)*(w3g-gsn) } else {
} w3g = math.Sqrt(gsn)
}
if bbn < 0.5 { gn = gsn + (2*gbn-1)*(w3g-gsn)
bn = bsn - (1-2*bbn)*bsn*(1-bsn) }
} else {
var w3b float64 if bbn < 0.5 {
if bsn < 0.25 { bn = bsn - (1-2*bbn)*bsn*(1-bsn)
w3b = ((16*bsn-12)*bsn + 4) * bsn } else {
} else { var w3b float64
w3b = math.Sqrt(bsn) if bsn < 0.25 {
} w3b = ((16*bsn-12)*bsn + 4) * bsn
bn = bsn + (2*bbn-1)*(w3b-bsn) } else {
} w3b = math.Sqrt(bsn)
}
if abn < 0.5 { bn = bsn + (2*bbn-1)*(w3b-bsn)
an = asn - (1-2*abn)*asn*(1-asn) }
} else {
var w3a float64 if abn < 0.5 {
if asn < 0.25 { an = asn - (1-2*abn)*asn*(1-asn)
w3a = ((16*asn-12)*asn + 4) * asn } else {
} else { var w3a float64
w3a = math.Sqrt(asn) if asn < 0.25 {
} w3a = ((16*asn-12)*asn + 4) * asn
an = asn + (2*abn-1)*(w3a-asn) } else {
} w3a = math.Sqrt(asn)
case HardLight: }
if rbn < 0.5 { an = asn + (2*abn-1)*(w3a-asn)
rn = rbn - (1-2*rsn)*rbn*(1-rbn) }
} else { case HardLight:
var w3r float64 if rbn < 0.5 {
if rbn < 0.25 { rn = rbn - (1-2*rsn)*rbn*(1-rbn)
w3r = ((16*rbn-12)*rbn + 4) * rbn } else {
} else { var w3r float64
w3r = math.Sqrt(rbn) if rbn < 0.25 {
} w3r = ((16*rbn-12)*rbn + 4) * rbn
rn = rbn + (2*rsn-1)*(w3r-rbn) } else {
} w3r = math.Sqrt(rbn)
}
if gbn < 0.5 { rn = rbn + (2*rsn-1)*(w3r-rbn)
gn = gbn - (1-2*gsn)*gbn*(1-gbn) }
} else {
var w3g float64 if gbn < 0.5 {
if gbn < 0.25 { gn = gbn - (1-2*gsn)*gbn*(1-gbn)
w3g = ((16*gbn-12)*gbn + 4) * gbn } else {
} else { var w3g float64
w3g = math.Sqrt(gbn) if gbn < 0.25 {
} w3g = ((16*gbn-12)*gbn + 4) * gbn
gn = gbn + (2*gsn-1)*(w3g-gbn) } else {
} w3g = math.Sqrt(gbn)
}
if bbn < 0.5 { gn = gbn + (2*gsn-1)*(w3g-gbn)
bn = bbn - (1-2*bsn)*bbn*(1-bbn) }
} else {
var w3b float64 if bbn < 0.5 {
if bbn < 0.25 { bn = bbn - (1-2*bsn)*bbn*(1-bbn)
w3b = ((16*bbn-12)*bbn + 4) * bbn } else {
} else { var w3b float64
w3b = math.Sqrt(bbn) if bbn < 0.25 {
} w3b = ((16*bbn-12)*bbn + 4) * bbn
bn = bbn + (2*bsn-1)*(w3b-bbn) } else {
} w3b = math.Sqrt(bbn)
}
if abn < 0.5 { bn = bbn + (2*bsn-1)*(w3b-bbn)
an = abn - (1-2*asn)*abn*(1-abn) }
} else {
var w3a float64 if abn < 0.5 {
if abn < 0.25 { an = abn - (1-2*asn)*abn*(1-abn)
w3a = ((16*abn-12)*abn + 4) * abn } else {
} else { var w3a float64
w3a = math.Sqrt(abn) if abn < 0.25 {
} w3a = ((16*abn-12)*abn + 4) * abn
an = abn + (2*asn-1)*(w3a-abn) } else {
} w3a = math.Sqrt(abn)
case ColorDodge: }
if rsn < 1 { an = abn + (2*asn-1)*(w3a-abn)
rn = utils.Min(1, rbn/(1-rsn)) }
} else if rsn == 1 { case ColorDodge:
rn = 1 if rsn < 1 {
} rn = utils.Min(1, rbn/(1-rsn))
} else if rsn == 1 {
if gsn < 1 { rn = 1
gn = utils.Min(1, gbn/(1-gsn)) }
} else if gsn == 1 {
gn = 1 if gsn < 1 {
} gn = utils.Min(1, gbn/(1-gsn))
} else if gsn == 1 {
if bsn < 1 { gn = 1
bn = utils.Min(1, bbn/(1-bsn)) }
} else if bsn == 1 {
bn = 1 if bsn < 1 {
} bn = utils.Min(1, bbn/(1-bsn))
} else if bsn == 1 {
if asn < 1 { bn = 1
an = utils.Min(1, abn/(1-asn)) }
} else if asn == 1 {
an = 1 if asn < 1 {
} an = utils.Min(1, abn/(1-asn))
case ColorBurn: } else if asn == 1 {
if rsn > 0 { an = 1
rn = 1 - utils.Min(1, (1-rbn)/rsn) }
} else if rsn == 0 { case ColorBurn:
rn = 0 if rsn > 0 {
} rn = 1 - utils.Min(1, (1-rbn)/rsn)
} else if rsn == 0 {
if gsn > 0 { rn = 0
gn = 1 - utils.Min(1, (1-gbn)/gsn) }
} else if gsn == 0 {
gn = 0 if gsn > 0 {
} gn = 1 - utils.Min(1, (1-gbn)/gsn)
} else if gsn == 0 {
if bsn > 0 { gn = 0
bn = 1 - utils.Min(1, (1-bbn)/bsn) }
} else if bsn == 0 {
bn = 0 if bsn > 0 {
} bn = 1 - utils.Min(1, (1-bbn)/bsn)
} else if bsn == 0 {
if asn > 0 { bn = 0
an = 1 - utils.Min(1, (1-abn)/asn) }
} else if asn == 0 {
an = 0 if asn > 0 {
} an = 1 - utils.Min(1, (1-abn)/asn)
case Difference: } else if asn == 0 {
rn = utils.Abs(rbn - rsn) an = 0
gn = utils.Abs(gbn - gsn) }
bn = utils.Abs(bbn - bsn) case Difference:
an = 1 rn = utils.Abs(rbn - rsn)
case Exclusion: gn = utils.Abs(gbn - gsn)
rn = rsn + rbn - 2*rsn*rbn bn = utils.Abs(bbn - bsn)
gn = gsn + gbn - 2*gsn*gbn an = 1
bn = bsn + bbn - 2*bsn*bbn case Exclusion:
an = 1 rn = rsn + rbn - 2*rsn*rbn
gn = gsn + gbn - 2*gsn*gbn
// Non-separable blend modes bn = bsn + bbn - 2*bsn*bbn
// https://www.w3.org/TR/compositing-1/#blendingnonseparable an = 1
case Hue:
sat := bl.SetSat(background, bl.Sat(foreground)) // Non-separable blend modes
rgb := bl.SetLum(sat, bl.Lum(foreground)) // https://www.w3.org/TR/compositing-1/#blendingnonseparable
case Hue:
a := asn + abn - asn*abn sat := blend.SetSat(background, blend.Sat(foreground))
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255) rgb := blend.SetLum(sat, blend.Lum(foreground))
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255) a := asn + abn - asn*abn
rn, gn, bn = rn/255, gn/255, bn/255 rn = blend.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
an = a gn = blend.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
case Saturation: bn = blend.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
sat := bl.SetSat(foreground, bl.Sat(background)) rn, gn, bn = rn/255, gn/255, bn/255
rgb := bl.SetLum(sat, bl.Lum(foreground)) an = a
case Saturation:
a := asn + abn - asn*abn sat := blend.SetSat(foreground, blend.Sat(background))
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255) rgb := blend.SetLum(sat, blend.Lum(foreground))
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255) a := asn + abn - asn*abn
rn, gn, bn = rn/255, gn/255, bn/255 rn = blend.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
an = a gn = blend.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
case ColorMode: bn = blend.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
rgb := bl.SetLum(background, bl.Lum(foreground)) rn, gn, bn = rn/255, gn/255, bn/255
an = a
a := asn + abn - asn*abn case ColorMode:
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255) rgb := blend.SetLum(background, blend.Lum(foreground))
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255) a := asn + abn - asn*abn
rn, gn, bn = rn/255, gn/255, bn/255 rn = blend.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
an = a gn = blend.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
case Luminosity: bn = blend.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
rgb := bl.SetLum(foreground, bl.Lum(background)) rn, gn, bn = rn/255, gn/255, bn/255
an = a
a := asn + abn - asn*abn case Luminosity:
rn = bl.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255) rgb := blend.SetLum(foreground, blend.Lum(background))
gn = bl.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
bn = bl.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255) a := asn + abn - asn*abn
rn, gn, bn = rn/255, gn/255, bn/255 rn = blend.AlphaCompose(abn, asn, a, rbn*255, rsn*255, rgb.R*255)
an = a gn = blend.AlphaCompose(abn, asn, a, gbn*255, gsn*255, rgb.G*255)
} bn = blend.AlphaCompose(abn, asn, a, bbn*255, bsn*255, rgb.B*255)
} rn, gn, bn = rn/255, gn/255, bn/255
an = a
r = uint32(rn * 255) }
g = uint32(gn * 255)
b = uint32(bn * 255) r = uint32(rn * 255)
a = uint32(an * 255) g = uint32(gn * 255)
b = uint32(bn * 255)
bitmap.Img.Set(x, y, color.NRGBA{ a = uint32(an * 255)
R: uint8(r),
G: uint8(g), bitmap.Img.Set(x, y, color.NRGBA{
B: uint8(b), R: uint8(r),
A: uint8(a), G: uint8(g),
}) B: uint8(b),
} A: uint8(a),
} })
} }
}
}
}

View File

@@ -1,210 +1,208 @@
package imop package imop
import ( import (
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestComp_Basic(t *testing.T) { func TestComp_Basic(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
op := InitOp() op := InitOp()
err := op.Set("unsupported_composite_operation")
assert.Error(err) op.Set(Clear)
assert.Equal(Clear, op.Get())
op.Set(Clear) assert.NotEqual("unsupported_composite_operation", op.Get())
assert.Equal(Clear, op.Get())
assert.NotEqual("unsupported_composite_operation", op.Get()) op.Set(Dst)
assert.Equal(Dst, op.Get())
op.Set(Dst) }
assert.Equal(Dst, op.Get())
} func TestComp_Ops(t *testing.T) {
assert := assert.New(t)
func TestComp_Ops(t *testing.T) { op := InitOp()
assert := assert.New(t)
op := InitOp() transparent := color.NRGBA{R: 0, G: 0, B: 0, A: 0}
cyan := color.NRGBA{R: 33, G: 150, B: 243, A: 255}
transparent := color.NRGBA{R: 0, G: 0, B: 0, A: 0} magenta := color.NRGBA{R: 233, G: 30, B: 99, A: 255}
cyan := color.NRGBA{R: 33, G: 150, B: 243, A: 255}
magenta := color.NRGBA{R: 233, G: 30, B: 99, A: 255} rect := image.Rect(0, 0, 10, 10)
bmp := NewBitmap(rect)
rect := image.Rect(0, 0, 10, 10) source := image.NewNRGBA(rect)
bmp := NewBitmap(rect) backdrop := image.NewNRGBA(rect)
source := image.NewNRGBA(rect)
backdrop := image.NewNRGBA(rect) // No composition operation applied. The SrcOver is the default one.
draw.Draw(source, image.Rect(0, 4, 6, 10), &image.Uniform{cyan}, image.Point{}, draw.Src)
// No composition operation applied. The SrcOver is the default one. draw.Draw(backdrop, image.Rect(4, 0, 10, 6), &image.Uniform{magenta}, image.Point{}, draw.Src)
draw.Draw(source, image.Rect(0, 4, 6, 10), &image.Uniform{cyan}, image.Point{}, draw.Src) op.Draw(bmp, source, backdrop, nil)
draw.Draw(backdrop, image.Rect(4, 0, 10, 6), &image.Uniform{magenta}, image.Point{}, draw.Src)
op.Draw(bmp, source, backdrop, nil) // Pick three representative points/pixels from the generated image output.
// Depending on the applied composition operation the colors of the
// Pick three representative points/pixels from the generated image output. // selected pixels should be the source color, the destination color or transparent.
// Depending on the applied composition operation the colors of the topRight := bmp.Img.At(9, 0)
// selected pixels should be the source color, the destination color or transparent. bottomLeft := bmp.Img.At(0, 9)
topRight := bmp.Img.At(9, 0) center := bmp.Img.At(5, 5)
bottomLeft := bmp.Img.At(0, 9)
center := bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, cyan)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(center, cyan) // Clear
op.Set(Clear)
// Clear op.Draw(bmp, source, backdrop, nil)
op.Set(Clear)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, transparent)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(center, transparent) // Copy
op.Set(Copy)
// Copy op.Draw(bmp, source, backdrop, nil)
op.Set(Copy)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, cyan)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(center, cyan) // Dst
op.Set(Dst)
// Dst op.Draw(bmp, source, backdrop, nil)
op.Set(Dst)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, magenta)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(center, magenta) // SrcOver
op.Set(SrcOver)
// SrcOver op.Draw(bmp, source, backdrop, nil)
op.Set(SrcOver)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, cyan)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(center, cyan) // DstOver
op.Set(DstOver)
// DstOver op.Draw(bmp, source, backdrop, nil)
op.Set(DstOver)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, magenta)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(center, magenta) // SrcIn
op.Set(SrcIn)
// SrcIn op.Draw(bmp, source, backdrop, nil)
op.Set(SrcIn)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, cyan)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(center, cyan) // DstIn
op.Set(DstIn)
// DstIn op.Draw(bmp, source, backdrop, nil)
op.Set(DstIn)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, magenta)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(center, magenta) // SrcOut
op.Set(SrcOut)
// SrcOut op.Draw(bmp, source, backdrop, nil)
op.Set(SrcOut)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, transparent)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(center, transparent) // DstOut
op.Set(DstOut)
// DstOut op.Draw(bmp, source, backdrop, nil)
op.Set(DstOut)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, transparent)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(center, transparent) // SrcAtop
op.Set(SrcAtop)
// SrcAtop op.Draw(bmp, source, backdrop, nil)
op.Set(SrcAtop)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, cyan)
assert.EqualValues(bottomLeft, transparent)
assert.EqualValues(center, cyan) // DstAtop
op.Set(DstAtop)
// DstAtop op.Draw(bmp, source, backdrop, nil)
op.Set(DstAtop)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, magenta)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(center, magenta) // Xor
op.Set(Xor)
// Xor op.Draw(bmp, source, backdrop, nil)
op.Set(Xor)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, magenta)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, magenta) assert.EqualValues(center, transparent)
assert.EqualValues(bottomLeft, cyan) // DstAtop
assert.EqualValues(center, transparent) op.Set(DstAtop)
// DstAtop op.Draw(bmp, source, backdrop, nil)
op.Set(DstAtop)
op.Draw(bmp, source, backdrop, nil) topRight = bmp.Img.At(9, 0)
bottomLeft = bmp.Img.At(0, 9)
topRight = bmp.Img.At(9, 0) center = bmp.Img.At(5, 5)
bottomLeft = bmp.Img.At(0, 9)
center = bmp.Img.At(5, 5) assert.EqualValues(topRight, transparent)
assert.EqualValues(bottomLeft, cyan)
assert.EqualValues(topRight, transparent) assert.EqualValues(center, magenta)
assert.EqualValues(bottomLeft, cyan) }
assert.EqualValues(center, magenta)
}

View File

@@ -8,14 +8,14 @@ import (
func (p *Processor) showPreview( func (p *Processor) showPreview(
imgWorker <-chan worker, imgWorker <-chan worker,
errChan chan<- error, errChan chan<- error,
guiParams struct { guiWindow struct {
width int width int
height int height int
}, },
) { ) {
var gui = NewGUI(guiParams.width, guiParams.height) var gui = NewGUI(guiWindow.width, guiWindow.height)
gui.cp = p gui.proc = p
gui.proc.wrk = imgWorker gui.process.worker = imgWorker
// Run the Gio GUI app in a separate goroutine // Run the Gio GUI app in a separate goroutine
go func() { go func() {

View File

@@ -5,35 +5,20 @@ import (
"errors" "errors"
"fmt" "fmt"
"image" "image"
"image/color"
"image/color/palette"
"image/draw" "image/draw"
"image/gif"
"image/jpeg"
"image/png"
"io" "io"
"math" "math"
"os"
"path/filepath"
"strings"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/esimov/caire/utils" "github.com/esimov/caire/utils"
pigo "github.com/esimov/pigo/core" pigo "github.com/esimov/pigo/core"
"golang.org/x/image/bmp"
) )
//go:embed data/facefinder //go:embed data/facefinder
var cascadeFile []byte var cascadeFile []byte
var (
g *gif.GIF
rCount int
)
var ( var (
resizeXY = false // the image is resized both vertically and horizontally resizeXY = false // the image is resized both vertically and horizontally
isGif = false
imgWorker = make(chan worker) // channel used to transfer the image to the GUI imgWorker = make(chan worker) // channel used to transfer the image to the GUI
errs = make(chan error) errs = make(chan error)
@@ -41,47 +26,42 @@ var (
// worker struct contains all the information needed for transferring the resized image to the Gio GUI. // worker struct contains all the information needed for transferring the resized image to the Gio GUI.
type worker struct { type worker struct {
carver *Carver img *image.NRGBA
img *image.NRGBA mask *image.NRGBA
debug *image.NRGBA seams []Seam
done bool done bool
} }
// SeamCarver interface defines the Resize method. var _ SeamCarver = (*Processor)(nil)
// This needs to be implemented by every struct which declares a Resize method.
type SeamCarver interface {
Resize(*image.NRGBA) (image.Image, error)
}
// shrinkFn is a generic function used to shrink an image. // shrinkFn is a generic function used to shrink an image.
type shrinkFn func(*Carver, *image.NRGBA) (*image.NRGBA, error) type shrinkFn func(*image.NRGBA) (*image.NRGBA, error)
// enlargeFn is a generic function used to enlarge an image. // enlargeFn is a generic function used to enlarge an image.
type enlargeFn func(*Carver, *image.NRGBA) (*image.NRGBA, error) type enlargeFn func(*image.NRGBA) (*image.NRGBA, error)
// Processor options // Processor options
type Processor struct { type Processor struct {
FaceAngle float64
SeamColor string
MaskPath string
RMaskPath string
ShapeType ShapeType
SobelThreshold int SobelThreshold int
BlurRadius int BlurRadius int
NewWidth int NewWidth int
NewHeight int NewHeight int
FaceDetector *pigo.Pigo
Spinner *utils.Spinner
Mask *image.NRGBA
RMask *image.NRGBA
DebugMask *image.NRGBA
Percentage bool Percentage bool
Square bool Square bool
Debug bool Debug bool
Preview bool Preview bool
FaceDetect bool FaceDetect bool
ShapeType string vRes bool
SeamColor string
MaskPath string
RMaskPath string
Mask *image.NRGBA
RMask *image.NRGBA
GuiDebug *image.NRGBA
FaceAngle float64
FaceDetector *pigo.Pigo
Spinner *utils.Spinner
vRes bool
} }
var ( var (
@@ -91,17 +71,10 @@ var (
enlargeVertFn enlargeFn enlargeVertFn enlargeFn
) )
// resize implements the Resize method of the Carver interface. // Carve is the main entry point for the image resize operation.
// It returns the concrete resize operation method.
func resize(s SeamCarver, img *image.NRGBA) (image.Image, error) {
return s.Resize(img)
}
// Resize is the main entry point for the image resize operation.
// The new image can be resized either horizontally or vertically (or both). // The new image can be resized either horizontally or vertically (or both).
// Depending on the provided options the image can be either reduced or enlarged. // Depending on the provided options the image can be either reduced or enlarged.
func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) { func (p *Processor) Carve(img *image.NRGBA) (image.Image, error) {
var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy())
var ( var (
newImg image.Image newImg image.Image
newWidth int newWidth int
@@ -109,7 +82,7 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
pw, ph int pw, ph int
err error err error
) )
rCount = 0 c := NewCarver(img.Bounds().Dx(), img.Bounds().Dy())
if p.NewWidth > c.Width { if p.NewWidth > c.Width {
newWidth = p.NewWidth - (p.NewWidth - (p.NewWidth - c.Width)) newWidth = p.NewWidth - (p.NewWidth - (p.NewWidth - c.Width))
@@ -133,159 +106,156 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
// shrinkHorizFn calls itself recursively to shrink the image horizontally. // shrinkHorizFn calls itself recursively to shrink the image horizontally.
// If the image is resized on both X and Y axis it calls the shrink and enlarge // If the image is resized on both X and Y axis it calls the shrink and enlarge
// function intermittently up until the desired dimension is reached. // function intermittently up until the desired dimension is reached.
// We are opting for this solution instead of resizing the image sequentially, // I opted for this solution instead of resizing the image sequentially,
// because this way the horizontal and vertical seams are merged together seamlessly. // to avoid resulting visual artefacts, since this way
shrinkHorizFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) { // the horizontal and vertical seams are merged together seamlessly.
shrinkHorizFn = func(img *image.NRGBA) (*image.NRGBA, error) {
p.vRes = false p.vRes = false
dx, dy := img.Bounds().Dx(), img.Bounds().Dy() dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
if dx > p.NewWidth { if dx > p.NewWidth {
img, err = p.shrink(c, img) img, err = p.shrink(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if p.NewHeight > 0 && p.NewHeight != dy { if p.NewHeight > 0 && p.NewHeight != dy {
if p.NewHeight <= dy { if p.NewHeight <= dy {
img, err = shrinkVertFn(c, img) img, err = shrinkVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
img, err = enlargeVertFn(c, img) img, err = enlargeVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} else { } else {
img, err = shrinkHorizFn(c, img) img, err = shrinkHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
rCount++
return img, nil return img, nil
} }
// enlargeHorizFn calls itself recursively to enlarge the image horizontally. // enlargeHorizFn calls itself recursively to enlarge the image horizontally.
enlargeHorizFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) { enlargeHorizFn = func(img *image.NRGBA) (*image.NRGBA, error) {
p.vRes = false p.vRes = false
dx, dy := img.Bounds().Dx(), img.Bounds().Dy() dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
if dx < p.NewWidth { if dx < p.NewWidth {
img, err = p.enlarge(c, img) img, err = p.enlarge(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if p.NewHeight > 0 && p.NewHeight != dy { if p.NewHeight > 0 && p.NewHeight != dy {
if p.NewHeight <= dy { if p.NewHeight <= dy {
img, err = shrinkVertFn(c, img) img, err = shrinkVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
img, err = enlargeVertFn(c, img) img, err = enlargeVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} else { } else {
img, err = enlargeHorizFn(c, img) img, err = enlargeHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
rCount++
return img, nil return img, nil
} }
// shrinkVertFn calls itself recursively to shrink the image vertically. // shrinkVertFn calls itself recursively to shrink the image vertically.
shrinkVertFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) { shrinkVertFn = func(img *image.NRGBA) (*image.NRGBA, error) {
p.vRes = true p.vRes = true
dx, dy := img.Bounds().Dx(), img.Bounds().Dy() dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
// If the image is resized both horizontally and vertically we need // If the image is resized both horizontally and vertically we need
// to rotate the image each time we are invoking the shrink function. // to rotate the image each time when invoking the shrink function.
// Otherwise we rotate the image only once, right before calling this function. // Otherwise we rotate the image only once, right before calling this function.
if resizeXY { if resizeXY {
dx, dy = img.Bounds().Dy(), img.Bounds().Dx() dx, dy = img.Bounds().Dy(), img.Bounds().Dx()
img = c.RotateImage90(img) img = rotateImage90(img)
} }
if dx > p.NewHeight { if dx > p.NewHeight {
img, err = p.shrink(c, img) img, err = p.shrink(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resizeXY { if resizeXY {
img = c.RotateImage270(img) img = rotateImage270(img)
} }
if p.NewWidth > 0 && p.NewWidth != dy { if p.NewWidth > 0 && p.NewWidth != dy {
if p.NewWidth <= dy { if p.NewWidth <= dy {
img, err = shrinkHorizFn(c, img) img, err = shrinkHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
img, err = enlargeHorizFn(c, img) img, err = enlargeHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} else { } else {
img, err = shrinkVertFn(c, img) img, err = shrinkVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} else { } else {
if resizeXY { if resizeXY {
img = c.RotateImage270(img) img = rotateImage270(img)
} }
} }
rCount++
return img, nil return img, nil
} }
// enlargeVertFn calls itself recursively to enlarge the image vertically. // enlargeVertFn calls itself recursively to enlarge the image vertically.
enlargeVertFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) { enlargeVertFn = func(img *image.NRGBA) (*image.NRGBA, error) {
p.vRes = true p.vRes = true
dx, dy := img.Bounds().Dx(), img.Bounds().Dy() dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
if resizeXY { if resizeXY {
dx, dy = img.Bounds().Dy(), img.Bounds().Dx() dx, dy = img.Bounds().Dy(), img.Bounds().Dx()
img = c.RotateImage90(img) img = rotateImage90(img)
} }
if dx < p.NewHeight { if dx < p.NewHeight {
img, err = p.enlarge(c, img) img, err = p.enlarge(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resizeXY { if resizeXY {
img = c.RotateImage270(img) img = rotateImage270(img)
} }
if p.NewWidth > 0 && p.NewWidth != dy { if p.NewWidth > 0 && p.NewWidth != dy {
if p.NewWidth <= dy { if p.NewWidth <= dy {
img, err = shrinkHorizFn(c, img) img, err = shrinkHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
img, err = enlargeHorizFn(c, img) img, err = enlargeHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} else { } else {
img, err = enlargeVertFn(c, img) img, err = enlargeVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} else { } else {
if resizeXY { if resizeXY {
img = c.RotateImage270(img) img = rotateImage270(img)
} }
} }
rCount++
return img, nil return img, nil
} }
@@ -371,12 +341,12 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
// Run the carver function if the desired image width is not identical with the rescaled image width. // Run the carver function if the desired image width is not identical with the rescaled image width.
if newWidth > 0 && p.NewWidth != c.Width { if newWidth > 0 && p.NewWidth != c.Width {
if p.NewWidth > c.Width { if p.NewWidth > c.Width {
img, err = enlargeHorizFn(c, img) img, err = enlargeHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
img, err = shrinkHorizFn(c, img) img, err = shrinkHorizFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -386,43 +356,45 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
// Run the carver function if the desired image height is not identical with the rescaled image height. // Run the carver function if the desired image height is not identical with the rescaled image height.
if newHeight > 0 && p.NewHeight != c.Height { if newHeight > 0 && p.NewHeight != c.Height {
if !resizeXY { if !resizeXY {
img = c.RotateImage90(img) img = rotateImage90(img)
if len(p.MaskPath) > 0 { if p.Mask != nil {
p.Mask = c.RotateImage90(p.Mask) p.Mask = rotateImage90(p.Mask)
} }
if len(p.RMaskPath) > 0 { if p.RMask != nil {
p.RMask = c.RotateImage90(p.RMask) p.RMask = rotateImage90(p.RMask)
} }
} }
if p.NewHeight > c.Height { if p.NewHeight > c.Height {
img, err = enlargeVertFn(c, img) img, err = enlargeVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
img, err = shrinkVertFn(c, img) img, err = shrinkVertFn(img)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if !resizeXY { if !resizeXY {
img = c.RotateImage270(img) img = rotateImage270(img)
if len(p.MaskPath) > 0 { if p.Mask != nil {
p.Mask = c.RotateImage270(p.Mask) p.Mask = rotateImage270(p.Mask)
} }
if len(p.RMaskPath) > 0 { if p.RMask != nil {
p.RMask = c.RotateImage270(p.RMask) p.RMask = rotateImage270(p.RMask)
} }
} }
} }
// Signal that the process is done and no more data is sent through the channel. // Signal that the process is done and no more data is sent through the channel.
go func() { go func() {
imgWorker <- worker{ imgWorker <- worker{
carver: nil, img: nil,
img: nil, mask: nil,
done: true, seams: nil,
done: true,
} }
}() }()
@@ -445,18 +417,18 @@ func (p *Processor) calculateFitness(img *image.NRGBA, c *Carver) *image.NRGBA {
if sw <= sh { if sw <= sh {
newImg = imaging.Resize(img, 0, int(sw), imaging.Lanczos) newImg = imaging.Resize(img, 0, int(sw), imaging.Lanczos)
if len(p.MaskPath) > 0 { if p.Mask != nil {
p.Mask = imaging.Resize(p.Mask, 0, int(sw), imaging.Lanczos) p.Mask = imaging.Resize(p.Mask, 0, int(sw), imaging.Lanczos)
} }
if len(p.RMaskPath) > 0 { if p.RMask != nil {
p.RMask = imaging.Resize(p.RMask, 0, int(sw), imaging.Lanczos) p.RMask = imaging.Resize(p.RMask, 0, int(sw), imaging.Lanczos)
} }
} else { } else {
newImg = imaging.Resize(img, 0, int(sh), imaging.Lanczos) newImg = imaging.Resize(img, 0, int(sh), imaging.Lanczos)
if len(p.MaskPath) > 0 { if p.Mask != nil {
p.Mask = imaging.Resize(p.Mask, 0, int(sh), imaging.Lanczos) p.Mask = imaging.Resize(p.Mask, 0, int(sh), imaging.Lanczos)
} }
if len(p.RMaskPath) > 0 { if p.RMask != nil {
p.RMask = imaging.Resize(p.RMask, 0, int(sh), imaging.Lanczos) p.RMask = imaging.Resize(p.RMask, 0, int(sh), imaging.Lanczos)
} }
} }
@@ -494,57 +466,10 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
src, _, err := image.Decode(r) src, _, err := image.Decode(r)
if err != nil { if err != nil {
fmt.Println("err:", err)
os.Exit(2)
return err return err
} }
img := p.imgToNRGBA(src) img := imgToNRGBA(src)
p.GuiDebug = image.NewNRGBA(img.Bounds())
if len(p.MaskPath) > 0 {
mf, err := os.Open(p.MaskPath)
if err != nil {
return fmt.Errorf("could not open the mask file: %v", err)
}
ctype, err := utils.DetectContentType(mf.Name())
if err != nil {
return err
}
if !strings.Contains(ctype.(string), "image") {
return fmt.Errorf("the mask should be an image file")
}
mask, _, err := image.Decode(mf)
if err != nil {
return fmt.Errorf("could not decode the mask file: %v", err)
}
p.Mask = p.Dither(p.imgToNRGBA(mask))
p.GuiDebug = p.Mask
}
if len(p.RMaskPath) > 0 {
rmf, err := os.Open(p.RMaskPath)
if err != nil {
return fmt.Errorf("could not open the mask file: %v", err)
}
ctype, err := utils.DetectContentType(rmf.Name())
if err != nil {
return err
}
if !strings.Contains(ctype.(string), "image") {
return fmt.Errorf("the mask should be an image file")
}
rmask, _, err := image.Decode(rmf)
if err != nil {
return fmt.Errorf("could not decode the mask file: %v", err)
}
p.RMask = p.Dither(p.imgToNRGBA(rmask))
p.GuiDebug = p.RMask
}
if p.Preview { if p.Preview {
guiWidth := img.Bounds().Max.X guiWidth := img.Bounds().Max.X
@@ -557,11 +482,11 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
guiHeight = p.NewHeight guiHeight = p.NewHeight
} }
if resizeXY { if resizeXY {
guiWidth = 1024 guiWidth = int(maxScreenX)
guiHeight = 640 guiHeight = int(maxScreenY)
} }
guiParams := struct { guiWindow := struct {
width int width int
height int height int
}{ }{
@@ -569,55 +494,16 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
height: guiHeight, height: guiHeight,
} }
// Lunch Gio GUI thread. // Lunch Gio GUI thread.
go p.showPreview(imgWorker, errs, guiParams) go p.showPreview(imgWorker, errs, guiWindow)
} }
switch w := w.(type) { return encodeImg(p, w, img)
case *os.File:
ext := filepath.Ext(w.Name())
switch ext {
case "", ".jpg", ".jpeg":
res, err := resize(p, img)
if err != nil {
return err
}
return jpeg.Encode(w, res, &jpeg.Options{Quality: 100})
case ".png":
res, err := resize(p, img)
if err != nil {
return err
}
return png.Encode(w, res)
case ".bmp":
res, err := resize(p, img)
if err != nil {
return err
}
return bmp.Encode(w, res)
case ".gif":
g = new(gif.GIF)
isGif = true
_, err := resize(p, img)
if err != nil {
return err
}
return writeGifToFile(w.Name(), g)
default:
return errors.New("unsupported image format")
}
default:
res, err := resize(p, img)
if err != nil {
return err
}
return jpeg.Encode(w, res, &jpeg.Options{Quality: 100})
}
} }
// shrink reduces the image dimension either horizontally or vertically. // shrink reduces the image dimension either horizontally or vertically.
func (p *Processor) shrink(c *Carver, img *image.NRGBA) (*image.NRGBA, error) { func (p *Processor) shrink(img *image.NRGBA) (*image.NRGBA, error) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c := NewCarver(width, height)
if _, err := c.ComputeSeams(p, img); err != nil { if _, err := c.ComputeSeams(p, img); err != nil {
return nil, err return nil, err
@@ -625,26 +511,23 @@ func (p *Processor) shrink(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug) img = c.RemoveSeam(img, seams, p.Debug)
if len(p.MaskPath) > 0 { if p.Mask != nil {
p.Mask = c.RemoveSeam(p.Mask, seams, false) p.Mask = c.RemoveSeam(p.Mask, seams, false)
draw.Draw(p.GuiDebug, img.Bounds(), p.Mask, image.Point{}, draw.Over) draw.Draw(p.DebugMask, img.Bounds(), p.Mask, image.Point{}, draw.Over)
}
if len(p.RMaskPath) > 0 {
p.RMask = c.RemoveSeam(p.RMask, seams, false)
draw.Draw(p.GuiDebug, img.Bounds(), p.RMask, image.Point{}, draw.Over)
} }
if isGif { if p.RMask != nil {
p.encodeImgToGif(c, img, g) p.RMask = c.RemoveSeam(p.RMask, seams, false)
draw.Draw(p.DebugMask, img.Bounds(), p.RMask, image.Point{}, draw.Over)
} }
go func() { go func() {
select { select {
case imgWorker <- worker{ case imgWorker <- worker{
carver: c, img: img,
img: img, mask: p.DebugMask,
debug: p.GuiDebug, seams: c.Seams,
done: false, done: false,
}: }:
case <-errs: case <-errs:
return return
@@ -654,9 +537,9 @@ func (p *Processor) shrink(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
} }
// enlarge increases the image dimension either horizontally or vertically. // enlarge increases the image dimension either horizontally or vertically.
func (p *Processor) enlarge(c *Carver, img *image.NRGBA) (*image.NRGBA, error) { func (p *Processor) enlarge(img *image.NRGBA) (*image.NRGBA, error) {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c := NewCarver(width, height)
if _, err := c.ComputeSeams(p, img); err != nil { if _, err := c.ComputeSeams(p, img); err != nil {
return nil, err return nil, err
@@ -664,26 +547,23 @@ func (p *Processor) enlarge(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, p.Debug) img = c.AddSeam(img, seams, p.Debug)
if len(p.MaskPath) > 0 { if p.Mask != nil {
p.Mask = c.AddSeam(p.Mask, seams, false) p.Mask = c.AddSeam(p.Mask, seams, false)
p.GuiDebug = p.Mask p.DebugMask = p.Mask
}
if len(p.RMaskPath) > 0 {
p.RMask = c.AddSeam(p.RMask, seams, false)
p.GuiDebug = p.RMask
} }
if isGif { if p.RMask != nil {
p.encodeImgToGif(c, img, g) p.RMask = c.AddSeam(p.RMask, seams, false)
p.DebugMask = p.RMask
} }
go func() { go func() {
select { select {
case imgWorker <- worker{ case imgWorker <- worker{
carver: c, img: img,
img: img, mask: p.DebugMask,
debug: p.GuiDebug, seams: c.Seams,
done: false, done: false,
}: }:
case <-errs: case <-errs:
return return
@@ -691,102 +571,3 @@ func (p *Processor) enlarge(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
}() }()
return img, nil return img, nil
} }
// imgToNRGBA converts any image type to *image.NRGBA with min-point at (0, 0).
func (p *Processor) imgToNRGBA(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
if src0, ok := img.(*image.NRGBA); ok {
return src0
}
}
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
dstBounds := srcBounds.Sub(srcBounds.Min)
dstW := dstBounds.Dx()
dstH := dstBounds.Dy()
dst := image.NewNRGBA(dstBounds)
switch src := img.(type) {
case *image.NRGBA:
rowSize := srcBounds.Dx() * 4
for dstY := 0; dstY < dstH; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
}
}
case *image.YCbCr:
for dstY := 0; dstY < dstH; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
srcX := srcMinX + dstX
srcY := srcMinY + dstY
siy := src.YOffset(srcX, srcY)
sic := src.COffset(srcX, srcY)
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
dst.Pix[di+0] = r
dst.Pix[di+1] = g
dst.Pix[di+2] = b
dst.Pix[di+3] = 0xff
di += 4
}
}
default:
for dstY := 0; dstY < dstH; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
dst.Pix[di+0] = c.R
dst.Pix[di+1] = c.G
dst.Pix[di+2] = c.B
dst.Pix[di+3] = c.A
di += 4
}
}
}
return dst
}
// encodeImgToGif encodes the provided image to a Gif file.
func (p *Processor) encodeImgToGif(c *Carver, src image.Image, g *gif.GIF) {
dx, dy := src.Bounds().Max.X, src.Bounds().Max.Y
dst := image.NewPaletted(image.Rect(0, 0, dx, dy), palette.Plan9)
if p.NewHeight != 0 {
dst = image.NewPaletted(image.Rect(0, 0, dy, dx), palette.Plan9)
}
if p.NewWidth > dx {
dx += rCount
g.Config.Width = dst.Bounds().Max.X + 1
g.Config.Height = dst.Bounds().Max.Y + 1
} else {
dx -= rCount
}
if p.NewHeight > dx {
dx += rCount
g.Config.Width = dst.Bounds().Max.X + 1
g.Config.Height = dst.Bounds().Max.Y + 1
} else {
dx -= rCount
}
if p.NewHeight != 0 {
src = c.RotateImage270(src.(*image.NRGBA))
}
draw.Draw(dst, src.Bounds(), src, image.Point{}, draw.Src)
g.Image = append(g.Image, dst)
g.Delay = append(g.Delay, 0)
}
// writeGifToFile writes the encoded Gif file to the destination file.
func writeGifToFile(path string, g *gif.GIF) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return gif.EncodeAll(f, g)
}

View File

@@ -8,8 +8,6 @@ import (
) )
func TestResize_ShrinkImageWidth(t *testing.T) { func TestResize_ShrinkImageWidth(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy()) var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy())
newWidth := imgWidth / 2 newWidth := imgWidth / 2
@@ -20,18 +18,19 @@ func TestResize_ShrinkImageWidth(t *testing.T) {
for x := 0; x < newWidth; x++ { for x := 0; x < newWidth; x++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img)
_, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug) img = c.RemoveSeam(img, seams, p.Debug)
} }
imgWidth := img.Bounds().Max.X imgWidth := img.Bounds().Max.X
assert.Equal(imgWidth, newWidth) assert.Equal(t, imgWidth, newWidth)
} }
func TestResize_ShrinkImageHeight(t *testing.T) { func TestResize_ShrinkImageHeight(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy()) var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy())
newHeight := imgHeight / 2 newHeight := imgHeight / 2
@@ -39,23 +38,24 @@ func TestResize_ShrinkImageHeight(t *testing.T) {
p.NewWidth = imgWidth p.NewWidth = imgWidth
p.NewHeight = newHeight p.NewHeight = newHeight
img = c.RotateImage90(img) img = rotateImage90(img)
for x := 0; x < newHeight; x++ { for x := 0; x < newHeight; x++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img)
_, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.RemoveSeam(img, seams, p.Debug) img = c.RemoveSeam(img, seams, p.Debug)
} }
img = c.RotateImage270(img) img = rotateImage270(img)
imgHeight := img.Bounds().Max.Y imgHeight := img.Bounds().Max.Y
assert.Equal(imgHeight, newHeight) assert.Equal(t, imgHeight, newHeight)
} }
func TestResize_EnlargeImageWidth(t *testing.T) { func TestResize_EnlargeImageWidth(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy()) var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy())
newWidth := imgWidth * 2 newWidth := imgWidth * 2
@@ -66,18 +66,19 @@ func TestResize_EnlargeImageWidth(t *testing.T) {
for x := 0; x < newWidth; x++ { for x := 0; x < newWidth; x++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img)
_, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, p.Debug) img = c.AddSeam(img, seams, p.Debug)
} }
imgWidth := img.Bounds().Max.X - img.Bounds().Dx() imgWidth := img.Bounds().Max.X - img.Bounds().Dx()
assert.NotEqual(imgWidth, newWidth) assert.NotEqual(t, imgWidth, newWidth)
} }
func TestResize_EnlargeImageHeight(t *testing.T) { func TestResize_EnlargeImageHeight(t *testing.T) {
assert := assert.New(t)
img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight)) img := image.NewNRGBA(image.Rect(0, 0, imgWidth, imgHeight))
var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy()) var c = NewCarver(img.Bounds().Dx(), img.Bounds().Dy())
newHeight := imgHeight * 2 newHeight := imgHeight * 2
@@ -85,16 +86,19 @@ func TestResize_EnlargeImageHeight(t *testing.T) {
p.NewWidth = imgWidth p.NewWidth = imgWidth
p.NewHeight = newHeight p.NewHeight = newHeight
img = c.RotateImage90(img) img = rotateImage90(img)
for x := 0; x < newHeight; x++ { for x := 0; x < newHeight; x++ {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(p, img)
_, err := c.ComputeSeams(p, img)
assert.NoError(t, err)
seams := c.FindLowestEnergySeams(p) seams := c.FindLowestEnergySeams(p)
img = c.AddSeam(img, seams, p.Debug) img = c.AddSeam(img, seams, p.Debug)
} }
img = c.RotateImage270(img) img = rotateImage270(img)
imgHeight := img.Bounds().Max.Y - img.Bounds().Dy() imgHeight := img.Bounds().Max.Y - img.Bounds().Dy()
assert.NotEqual(imgHeight, newHeight) assert.NotEqual(t, imgHeight, newHeight)
} }

View File

@@ -64,7 +64,7 @@ func IsValidUrl(uri string) bool {
} }
// DetectContentType detects the file type by reading MIME type information of the file content. // DetectContentType detects the file type by reading MIME type information of the file content.
func DetectContentType(fname string) (interface{}, error) { func DetectContentType(fname string) (any, error) {
file, err := os.Open(fname) file, err := os.Open(fname)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -83,7 +83,9 @@ func DetectContentType(fname string) (interface{}, error) {
} }
// Reset the read pointer if necessary. // Reset the read pointer if necessary.
file.Seek(0, 0) if _, err := file.Seek(0, 0); err != nil {
return nil, err
}
// Always returns a valid content-type and "application/octet-stream" if no others seemed to match. // Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
contentType := http.DetectContentType(buffer) contentType := http.DetectContentType(buffer)

View File

@@ -1,6 +1,10 @@
package utils package utils
import "golang.org/x/exp/constraints" import (
"slices"
"golang.org/x/exp/constraints"
)
// Min returns the slowest value of the provided parameters. // Min returns the slowest value of the provided parameters.
func Min[T constraints.Ordered](values ...T) T { func Min[T constraints.Ordered](values ...T) T {
@@ -36,10 +40,5 @@ func Abs[T constraints.Signed | constraints.Float](x T) T {
// Contains returns true if a value is available in the collection. // Contains returns true if a value is available in the collection.
func Contains[T comparable](slice []T, value T) bool { func Contains[T comparable](slice []T, value T) bool {
for _, v := range slice { return slices.Contains(slice, value)
if v == value {
return true
}
}
return false
} }

63
vendor/gioui.org/LICENSE generated vendored
View File

@@ -1,63 +0,0 @@
This project is provided under the terms of the UNLICENSE or
the MIT license denoted by the following 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/>
---

68
vendor/gioui.org/app/Gio.java generated vendored
View File

@@ -1,68 +0,0 @@
// 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();
}

View File

@@ -1,63 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package org.gioui;
import android.app.Activity;
import android.os.Bundle;
import android.content.res.Configuration;
import android.view.ViewGroup;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
public final class GioActivity extends Activity {
private GioView view;
public FrameLayout layer;
@Override public void onCreate(Bundle state) {
super.onCreate(state);
layer = new FrameLayout(this);
view = new GioView(this);
view.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
));
view.setFocusable(true);
view.setFocusableInTouchMode(true);
layer.addView(view);
setContentView(layer);
}
@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();
GioView.onLowMemory();
}
@Override public void onBackPressed() {
if (!view.backPressed())
super.onBackPressed();
}
}

838
vendor/gioui.org/app/GioView.java generated vendored
View File

@@ -1,838 +0,0 @@
// 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.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.text.TextUtils;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Choreographer;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
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.Window;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.SurroundingText;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import java.io.UnsupportedEncodingException;
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
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 int keyboardHint;
private AccessibilityManager accessManager;
private long nhandle;
public GioView(Context context) {
this(context, null);
}
public GioView(Context context, AttributeSet attrs) {
super(context, attrs);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
// Late initialization of the Go runtime to wait for a valid context.
Gio.init(context.getApplicationContext());
// Set background color to transparent to avoid a flickering
// issue on ChromeOS.
setBackgroundColor(Color.argb(0, 0, 0, 0));
ViewConfiguration conf = ViewConfiguration.get(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
scrollXScale = conf.getScaledHorizontalScrollFactor();
scrollYScale = conf.getScaledVerticalScrollFactor();
// The platform focus highlight is not aware of Gio's widgets.
setDefaultFocusHighlightEnabled(false);
} else {
float listItemHeight = 48; // dp
float px = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
listItemHeight,
getResources().getDisplayMetrics()
);
scrollXScale = px;
scrollYScale = px;
}
setHighRefreshRate();
accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
nhandle = onCreateView(this);
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) {
if (nhandle != 0) {
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), true, event.getEventTime());
}
return false;
}
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
if (nhandle != 0) {
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), false, 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 do it
// so assume it's good for us as well.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
requestUnbufferedDispatch(event);
}
dispatchMotionEvent(event);
return true;
}
private void setCursor(int id) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return;
}
PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
setPointerIcon(pointerIcon);
}
private void setOrientation(int id, int fallback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
id = fallback;
}
((Activity) this.getContext()).setRequestedOrientation(id);
}
private void setFullscreen(boolean enabled) {
int flags = this.getSystemUiVisibility();
if (enabled) {
flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
flags |= SYSTEM_UI_FLAG_FULLSCREEN;
flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
} else {
flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
this.setSystemUiVisibility(flags);
}
private enum Bar {
NAVIGATION,
STATUS,
}
private void setBarColor(Bar t, int color, int luminance) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
Window window = ((Activity) this.getContext()).getWindow();
int insetsMask;
int viewMask;
switch (t) {
case STATUS:
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
window.setStatusBarColor(color);
break;
case NAVIGATION:
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
window.setNavigationBarColor(color);
break;
default:
throw new RuntimeException("invalid bar type");
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
int flags = this.getSystemUiVisibility();
if (luminance > 128) {
flags |= viewMask;
} else {
flags &= ~viewMask;
}
this.setSystemUiVisibility(flags);
return;
}
WindowInsetsController insetsController = window.getInsetsController();
if (insetsController == null) {
return;
}
if (luminance > 128) {
insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
} else {
insetsController.setSystemBarsAppearance(0, insetsMask);
}
}
private void setStatusColor(int color, int luminance) {
this.setBarColor(Bar.STATUS, color, luminance);
}
private void setNavigationColor(int color, int luminance) {
this.setBarColor(Bar.NAVIGATION, color, luminance);
}
private void setHighRefreshRate() {
Context context = getContext();
Display display = context.getDisplay();
Display.Mode[] supportedModes = display.getSupportedModes();
if (supportedModes.length <= 1) {
// Nothing to set
return;
}
Display.Mode currentMode = display.getMode();
int currentWidth = currentMode.getPhysicalWidth();
int currentHeight = currentMode.getPhysicalHeight();
float minRefreshRate = -1;
float maxRefreshRate = -1;
float bestRefreshRate = -1;
int bestModeId = -1;
for (Display.Mode mode : supportedModes) {
float refreshRate = mode.getRefreshRate();
float width = mode.getPhysicalWidth();
float height = mode.getPhysicalHeight();
if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
minRefreshRate = refreshRate;
}
if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
maxRefreshRate = refreshRate;
}
boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
int modeId = mode.getModeId();
bestRefreshRate = refreshRate;
bestModeId = modeId;
}
}
if (bestModeId == -1) {
// Not expecting this but just in case
return;
}
if (minRefreshRate == maxRefreshRate) {
// Can't improve the refresh rate
return;
}
Window window = ((Activity) context).getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.preferredDisplayModeId = bestModeId;
window.setAttributes(layoutParams);
}
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
if (!accessManager.isTouchExplorationEnabled()) {
return super.dispatchHoverEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
// Fall through.
case MotionEvent.ACTION_HOVER_MOVE:
onTouchExploration(nhandle, event.getX(), event.getY());
break;
case MotionEvent.ACTION_HOVER_EXIT:
onExitTouchExploration(nhandle);
break;
}
return true;
}
void sendA11yEvent(int eventType, int viewId) {
if (!accessManager.isEnabled()) {
return;
}
AccessibilityEvent event = obtainA11yEvent(eventType, viewId);
getParent().requestSendAccessibilityEvent(this, event);
}
AccessibilityEvent obtainA11yEvent(int eventType, int viewId) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setPackageName(getContext().getPackageName());
event.setSource(this, viewId);
return event;
}
boolean isA11yActive() {
return accessManager.isEnabled();
}
void sendA11yChange(int viewId) {
if (!accessManager.isEnabled()) {
return;
}
AccessibilityEvent event = obtainA11yEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, viewId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
}
getParent().requestSendAccessibilityEvent(this, event);
}
private void dispatchMotionEvent(MotionEvent event) {
if (nhandle == 0) {
return;
}
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 editor) {
Snippet snip = getSnippet();
editor.inputType = this.keyboardHint;
editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
editor.initialSelStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
editor.initialSelEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
int selStart = editor.initialSelStart - snip.offset;
editor.initialCapsMode = TextUtils.getCapsMode(snip.snippet, selStart, this.keyboardHint);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
editor.setInitialSurroundingSubText(snip.snippet, imeToUTF16(nhandle, snip.offset));
}
imeSetComposingRegion(nhandle, -1, -1);
return new GioInputConnection();
}
void setInputHint(int hint) {
if (hint == this.keyboardHint) {
return;
}
this.keyboardHint = hint;
restartInput();
}
void showTextInput() {
GioView.this.requestFocus();
imm.showSoftInput(GioView.this, 0);
}
void hideTextInput() {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
@Override protected boolean fitSystemWindows(Rect insets) {
if (nhandle != 0) {
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) {
if (nhandle != 0) {
onFrameCallback(nhandle);
}
}
int getDensity() {
return getResources().getDisplayMetrics().densityDpi;
}
float getFontScale() {
return getResources().getConfiguration().fontScale;
}
public void start() {
if (nhandle != 0) {
onStartView(nhandle);
}
}
public void stop() {
if (nhandle != 0) {
onStopView(nhandle);
}
}
public void destroy() {
if (nhandle != 0) {
onDestroyView(nhandle);
}
}
protected void unregister() {
setOnFocusChangeListener(null);
getHolder().removeCallback(surfCallbacks);
nhandle = 0;
}
public void configurationChanged() {
if (nhandle != 0) {
onConfigurationChanged(nhandle);
}
}
public boolean backPressed() {
if (nhandle == 0) {
return false;
}
return onBack(nhandle);
}
void restartInput() {
imm.restartInput(this);
}
void updateSelection() {
int selStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
int selEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
int compStart = imeToUTF16(nhandle, imeComposingStart(nhandle));
int compEnd = imeToUTF16(nhandle, imeComposingEnd(nhandle));
imm.updateSelection(this, selStart, selEnd, compStart, compEnd);
}
void updateCaret(float m00, float m01, float m02, float m10, float m11, float m12, float caretX, float caretTop, float caretBase, float caretBottom) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
Matrix m = new Matrix();
m.setValues(new float[]{m00, m01, m02, m10, m11, m12, 0.0f, 0.0f, 1.0f});
m.setConcat(getMatrix(), m);
int selStart = imeSelectionStart(nhandle);
int selEnd = imeSelectionEnd(nhandle);
int compStart = imeComposingStart(nhandle);
int compEnd = imeComposingEnd(nhandle);
Snippet snip = getSnippet();
String composing = "";
if (compStart != -1) {
composing = snip.substringRunes(compStart, compEnd);
}
CursorAnchorInfo inf = new CursorAnchorInfo.Builder()
.setMatrix(m)
.setComposingText(imeToUTF16(nhandle, compStart), composing)
.setSelectionRange(imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd))
.setInsertionMarkerLocation(caretX, caretTop, caretBase, caretBottom, 0)
.build();
imm.updateCursorAnchorInfo(this, inf);
}
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 public 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, boolean pressed, long time);
static private native void onFrameCallback(long handle);
static private native boolean onBack(long handle);
static private native void onFocusChange(long handle, boolean focus);
static private native AccessibilityNodeInfo initializeAccessibilityNodeInfo(long handle, int viewId, int screenX, int screenY, AccessibilityNodeInfo info);
static private native void onTouchExploration(long handle, float x, float y);
static private native void onExitTouchExploration(long handle);
static private native void onA11yFocus(long handle, int viewId);
static private native void onClearA11yFocus(long handle, int viewId);
static private native void imeSetSnippet(long handle, int start, int end);
static private native String imeSnippet(long handle);
static private native int imeSnippetStart(long handle);
static private native int imeSelectionStart(long handle);
static private native int imeSelectionEnd(long handle);
static private native int imeComposingStart(long handle);
static private native int imeComposingEnd(long handle);
static private native int imeReplace(long handle, int start, int end, String text);
static private native int imeSetSelection(long handle, int start, int end);
static private native int imeSetComposingRegion(long handle, int start, int end);
// imeToRunes converts the Java character index into runes (Java code points).
static private native int imeToRunes(long handle, int chars);
// imeToUTF16 converts the rune index into Java characters.
static private native int imeToUTF16(long handle, int runes);
private class GioInputConnection implements InputConnection {
private int batchDepth;
@Override public boolean beginBatchEdit() {
batchDepth++;
return true;
}
@Override public boolean endBatchEdit() {
batchDepth--;
return batchDepth > 0;
}
@Override public boolean clearMetaKeyStates(int states) {
return false;
}
@Override public boolean commitCompletion(CompletionInfo text) {
return false;
}
@Override public boolean commitCorrection(CorrectionInfo info) {
return false;
}
@Override public boolean commitText(CharSequence text, int cursor) {
setComposingText(text, cursor);
return finishComposingText();
}
@Override public boolean deleteSurroundingText(int beforeChars, int afterChars) {
// translate before and after to runes.
int selStart = imeSelectionStart(nhandle);
int selEnd = imeSelectionEnd(nhandle);
int before = selStart - imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - beforeChars);
int after = selEnd - imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) - afterChars);
return deleteSurroundingTextInCodePoints(before, after);
}
@Override public boolean finishComposingText() {
imeSetComposingRegion(nhandle, -1, -1);
return true;
}
@Override public int getCursorCapsMode(int reqModes) {
Snippet snip = getSnippet();
int selStart = imeSelectionStart(nhandle);
int off = imeToUTF16(nhandle, selStart - snip.offset);
if (off < 0 || off > snip.snippet.length()) {
return 0;
}
return TextUtils.getCapsMode(snip.snippet, off, reqModes);
}
@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
return null;
}
@Override public CharSequence getSelectedText(int flags) {
Snippet snip = getSnippet();
int selStart = imeSelectionStart(nhandle);
int selEnd = imeSelectionEnd(nhandle);
String sub = snip.substringRunes(selStart, selEnd);
return sub;
}
@Override public CharSequence getTextAfterCursor(int n, int flags) {
Snippet snip = getSnippet();
int selStart = imeSelectionStart(nhandle);
int selEnd = imeSelectionEnd(nhandle);
// n are in Java characters, but in worst case we'll just ask for more runes
// than wanted.
imeSetSnippet(nhandle, selStart - n, selEnd + n);
int start = selEnd;
int end = imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) + n);
String ret = snip.substringRunes(start, end);
return ret;
}
@Override public CharSequence getTextBeforeCursor(int n, int flags) {
Snippet snip = getSnippet();
int selStart = imeSelectionStart(nhandle);
int selEnd = imeSelectionEnd(nhandle);
// n are in Java characters, but in worst case we'll just ask for more runes
// than wanted.
imeSetSnippet(nhandle, selStart - n, selEnd + n);
int start = imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - n);
int end = selStart;
String ret = snip.substringRunes(start, end);
return ret;
}
@Override public boolean performContextMenuAction(int id) {
return false;
}
@Override public boolean performEditorAction(int editorAction) {
long eventTime = SystemClock.uptimeMillis();
// Translate to enter key.
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', true, eventTime);
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', false, eventTime);
return true;
}
@Override public boolean performPrivateCommand(String action, Bundle data) {
return false;
}
@Override public boolean reportFullscreenMode(boolean enabled) {
return false;
}
@Override public boolean sendKeyEvent(KeyEvent event) {
boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
onKeyEvent(nhandle, event.getKeyCode(), event.getUnicodeChar(), pressed, event.getEventTime());
return true;
}
@Override public boolean setComposingRegion(int startChars, int endChars) {
int compStart = imeToRunes(nhandle, startChars);
int compEnd = imeToRunes(nhandle, endChars);
imeSetComposingRegion(nhandle, compStart, compEnd);
return true;
}
@Override public boolean setComposingText(CharSequence text, int relCursor) {
int start = imeComposingStart(nhandle);
int end = imeComposingEnd(nhandle);
if (start == -1 || end == -1) {
start = imeSelectionStart(nhandle);
end = imeSelectionEnd(nhandle);
}
String str = text.toString();
imeReplace(nhandle, start, end, str);
int cursor = start;
int runes = str.codePointCount(0, str.length());
if (relCursor > 0) {
cursor += runes;
relCursor--;
}
imeSetComposingRegion(nhandle, start, start + runes);
// Move cursor.
Snippet snip = getSnippet();
cursor = imeToRunes(nhandle, imeToUTF16(nhandle, cursor) + relCursor);
imeSetSelection(nhandle, cursor, cursor);
return true;
}
@Override public boolean setSelection(int startChars, int endChars) {
int start = imeToRunes(nhandle, startChars);
int end = imeToRunes(nhandle, endChars);
imeSetSelection(nhandle, start, end);
return true;
}
/*@Override*/ public boolean requestCursorUpdates(int cursorUpdateMode) {
// We always provide cursor updates.
return true;
}
/*@Override*/ public void closeConnection() {
}
/*@Override*/ public Handler getHandler() {
return null;
}
/*@Override*/ public boolean commitContent(InputContentInfo info, int flags, Bundle opts) {
return false;
}
/*@Override*/ public boolean deleteSurroundingTextInCodePoints(int before, int after) {
if (after > 0) {
int selEnd = imeSelectionEnd(nhandle);
imeReplace(nhandle, selEnd, selEnd + after, "");
}
if (before > 0) {
int selStart = imeSelectionStart(nhandle);
imeReplace(nhandle, selStart - before, selStart, "");
}
return true;
}
/*@Override*/ public SurroundingText getSurroundingText(int beforeChars, int afterChars, int flags) {
Snippet snip = getSnippet();
int selStart = imeSelectionStart(nhandle);
int selEnd = imeSelectionEnd(nhandle);
// Expanding in Java characters is ok.
imeSetSnippet(nhandle, selStart - beforeChars, selEnd + afterChars);
return new SurroundingText(snip.snippet, imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd), imeToUTF16(nhandle, snip.offset));
}
}
private Snippet getSnippet() {
Snippet snip = new Snippet();
snip.snippet = imeSnippet(nhandle);
snip.offset = imeSnippetStart(nhandle);
return snip;
}
// Snippet is like android.view.inputmethod.SurroundingText but available for Android < 31.
private static class Snippet {
String snippet;
// offset of snippet into the entire editor content. It is in runes because we won't require
// Gio editors to keep track of UTF-16 offsets. The distinction won't matter in practice because IMEs only
// ever see snippets.
int offset;
// substringRunes returns the substring from start to end in runes. The resuls is
// truncated to the snippet.
String substringRunes(int start, int end) {
start -= this.offset;
end -= this.offset;
int runes = snippet.codePointCount(0, snippet.length());
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
if (start > runes) {
start = runes;
}
if (end > runes) {
end = runes;
}
return snippet.substring(
snippet.offsetByCodePoints(0, start),
snippet.offsetByCodePoints(0, end)
);
}
}
@Override public AccessibilityNodeProvider getAccessibilityNodeProvider() {
return new AccessibilityNodeProvider() {
private final int[] screenOff = new int[2];
@Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int viewId) {
AccessibilityNodeInfo info = null;
if (viewId == View.NO_ID) {
info = AccessibilityNodeInfo.obtain(GioView.this);
GioView.this.onInitializeAccessibilityNodeInfo(info);
} else {
info = AccessibilityNodeInfo.obtain(GioView.this, viewId);
info.setPackageName(getContext().getPackageName());
info.setVisibleToUser(true);
}
GioView.this.getLocationOnScreen(screenOff);
info = GioView.this.initializeAccessibilityNodeInfo(nhandle, viewId, screenOff[0], screenOff[1], info);
return info;
}
@Override public boolean performAction(int viewId, int action, Bundle arguments) {
if (viewId == View.NO_ID) {
return GioView.this.performAccessibilityAction(action, arguments);
}
switch (action) {
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
GioView.this.onA11yFocus(nhandle, viewId);
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, viewId);
return true;
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
GioView.this.onClearA11yFocus(nhandle, viewId);
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, viewId);
return true;
}
return false;
}
};
}
}

65
vendor/gioui.org/app/app.go generated vendored
View File

@@ -1,65 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"os"
"path/filepath"
"strings"
)
// 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
// ID is the app id exposed to the platform.
//
// On Android ID is the package property of AndroidManifest.xml,
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
// on Wayland it is the toplevel app_id,
// on X11 it is the X11 XClassHint
//
// ID is set by the gogio tool or manually with the -X linker flag. For example,
//
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
//
// Note that ID is treated as a constant, and that changing it at runtime
// is not supported. Default value of ID is filepath.Base(os.Args[0]).
var ID = ""
func init() {
if extraArgs != "" {
args := strings.Split(extraArgs, "|")
os.Args = append(os.Args, args...)
}
if ID == "" {
ID = filepath.Base(os.Args[0])
}
}
// 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() {
osMain()
}

135
vendor/gioui.org/app/d3d11_windows.go generated vendored
View File

@@ -1,135 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"fmt"
"unsafe"
"gioui.org/gpu"
"gioui.org/internal/d3d11"
)
type d3d11Context struct {
win *window
dev *d3d11.Device
ctx *d3d11.DeviceContext
swchain *d3d11.IDXGISwapChain
renderTarget *d3d11.RenderTargetView
width, height int
}
const debugDirectX = false
func init() {
drivers = append(drivers, gpuAPI{
priority: 1,
initializer: func(w *window) (context, error) {
hwnd, _, _ := w.HWND()
var flags uint32
if debugDirectX {
flags |= d3d11.CREATE_DEVICE_DEBUG
}
dev, ctx, _, err := d3d11.CreateDevice(
d3d11.DRIVER_TYPE_HARDWARE,
flags,
)
if err != nil {
return nil, fmt.Errorf("NewContext: %v", err)
}
swchain, err := d3d11.CreateSwapChain(dev, hwnd)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
return nil, err
}
return &d3d11Context{win: w, dev: dev, ctx: ctx, swchain: swchain}, nil
},
})
}
func (c *d3d11Context) API() gpu.API {
return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)}
}
func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
return gpu.Direct3D11RenderTarget{
RenderTarget: unsafe.Pointer(c.renderTarget),
}, nil
}
func (c *d3d11Context) Present() error {
err := c.swchain.Present(1, 0)
if err == nil {
return 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 gpu.ErrDeviceLost
}
}
return err
}
func (c *d3d11Context) Refresh() error {
var width, height int
_, width, height = c.win.HWND()
if c.renderTarget != nil && width == c.width && height == c.height {
return nil
}
c.releaseFBO()
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
return err
}
c.width = width
c.height = height
backBuffer, err := c.swchain.GetBuffer(0, &d3d11.IID_Texture2D)
if err != nil {
return err
}
texture := (*d3d11.Resource)(unsafe.Pointer(backBuffer))
renderTarget, err := c.dev.CreateRenderTargetView(texture)
d3d11.IUnknownRelease(unsafe.Pointer(backBuffer), backBuffer.Vtbl.Release)
if err != nil {
return err
}
c.renderTarget = renderTarget
return nil
}
func (c *d3d11Context) Lock() error {
c.ctx.OMSetRenderTargets(c.renderTarget, nil)
return nil
}
func (c *d3d11Context) Unlock() {}
func (c *d3d11Context) Release() {
c.releaseFBO()
if c.swchain != nil {
d3d11.IUnknownRelease(unsafe.Pointer(c.swchain), c.swchain.Vtbl.Release)
}
if c.ctx != nil {
d3d11.IUnknownRelease(unsafe.Pointer(c.ctx), c.ctx.Vtbl.Release)
}
if c.dev != nil {
d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
}
*c = d3d11Context{}
if debugDirectX {
d3d11.ReportLiveObjects()
}
}
func (c *d3d11Context) releaseFBO() {
if c.renderTarget != nil {
d3d11.IUnknownRelease(unsafe.Pointer(c.renderTarget), c.renderTarget.Vtbl.Release)
c.renderTarget = nil
}
}

12
vendor/gioui.org/app/datadir.go generated vendored
View File

@@ -1,12 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !android
// +build !android
package app
import "os"
func dataDir() (string, error) {
return os.UserConfigDir()
}

72
vendor/gioui.org/app/doc.go generated vendored
View File

@@ -1,72 +0,0 @@
// 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

68
vendor/gioui.org/app/egl_android.go generated vendored
View File

@@ -1,68 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !noopengl
package app
/*
#include <android/native_window_jni.h>
#include <EGL/egl.h>
*/
import "C"
import (
"unsafe"
"gioui.org/internal/egl"
)
type androidContext struct {
win *window
eglSurf egl.NativeWindowType
width, height int
*egl.Context
}
func init() {
newAndroidGLESContext = func(w *window) (context, error) {
ctx, err := egl.NewContext(nil)
if err != nil {
return nil, err
}
return &androidContext{win: w, Context: ctx}, nil
}
}
func (c *androidContext) Release() {
if c.Context != nil {
c.Context.Release()
c.Context = nil
}
}
func (c *androidContext) Refresh() error {
c.Context.ReleaseSurface()
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
return err
}
win, width, height := c.win.nativeWindow()
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
c.width, c.height = width, height
return nil
}
func (c *androidContext) Lock() error {
// The Android emulator creates a broken surface if it is not
// created on the same thread as the context is made current.
if c.eglSurf != nil {
if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
return err
}
c.eglSurf = nil
}
return c.Context.MakeCurrent()
}
func (c *androidContext) Unlock() {
c.Context.ReleaseCurrent()
}

81
vendor/gioui.org/app/egl_wayland.go generated vendored
View File

@@ -1,81 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
// +build linux,!android freebsd
// +build !nowayland
// +build !noopengl
package app
import (
"errors"
"unsafe"
"gioui.org/internal/egl"
)
/*
#cgo linux pkg-config: egl wayland-egl
#cgo freebsd openbsd LDFLAGS: -lwayland-egl
#cgo CFLAGS: -DEGL_NO_X11
#include <EGL/egl.h>
#include <wayland-client.h>
#include <wayland-egl.h>
*/
import "C"
type wlContext struct {
win *window
*egl.Context
eglWin *C.struct_wl_egl_window
}
func init() {
newWaylandEGLContext = func(w *window) (context, error) {
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
ctx, err := egl.NewContext(disp)
if err != nil {
return nil, err
}
return &wlContext{Context: ctx, win: w}, nil
}
}
func (c *wlContext) 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 *wlContext) Refresh() 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)))
return c.Context.CreateSurface(eglSurf, width, height)
}
func (c *wlContext) Lock() error {
return c.Context.MakeCurrent()
}
func (c *wlContext) Unlock() {
c.Context.ReleaseCurrent()
}

64
vendor/gioui.org/app/egl_windows.go generated vendored
View File

@@ -1,64 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !noopengl
package app
import (
"golang.org/x/sys/windows"
"gioui.org/internal/egl"
)
type glContext struct {
win *window
*egl.Context
}
func init() {
drivers = append(drivers, 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) Refresh() error {
c.Context.ReleaseSurface()
var (
win windows.Handle
width, height int
)
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)
c.Context.ReleaseCurrent()
return nil
}
func (c *glContext) Lock() error {
return c.Context.MakeCurrent()
}
func (c *glContext) Unlock() {
c.Context.ReleaseCurrent()
}

60
vendor/gioui.org/app/egl_x11.go generated vendored
View File

@@ -1,60 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
// +build linux,!android freebsd openbsd
// +build !nox11
// +build !noopengl
package app
import (
"unsafe"
"gioui.org/internal/egl"
)
type x11Context struct {
win *x11Window
*egl.Context
}
func init() {
newX11EGLContext = func(w *x11Window) (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) Refresh() 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)
c.Context.ReleaseCurrent()
return nil
}
func (c *x11Context) Lock() error {
return c.Context.MakeCurrent()
}
func (c *x11Context) Unlock() {
c.Context.ReleaseCurrent()
}

View File

@@ -1,6 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#include <UIKit/UIKit.h>
@interface GioViewController : UIViewController
@end

148
vendor/gioui.org/app/gl_ios.go generated vendored
View File

@@ -1,148 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && ios && nometal
// +build darwin,ios,nometal
package app
/*
@import UIKit;
#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);
static CFTypeRef getViewLayer(CFTypeRef viewRef) {
@autoreleasepool {
UIView *view = (__bridge UIView *)viewRef;
return CFBridgingRetain(view.layer);
}
}
*/
import "C"
import (
"errors"
"fmt"
"runtime"
"gioui.org/gpu"
"gioui.org/internal/gl"
)
type context struct {
owner *window
c *gl.Functions
ctx C.CFTypeRef
layer C.CFTypeRef
init bool
frameBuffer gl.Framebuffer
colorBuffer gl.Renderbuffer
}
func newContext(w *window) (*context, error) {
ctx := C.gio_createContext()
if ctx == 0 {
return nil, fmt.Errorf("failed to create EAGLContext")
}
api := contextAPI()
f, err := gl.NewFunctions(api.Context, api.ES)
if err != nil {
return nil, err
}
c := &context{
ctx: ctx,
owner: w,
layer: C.getViewLayer(w.contextView()),
c: f,
}
return c, nil
}
func contextAPI() gpu.OpenGL {
return gpu.OpenGL{}
}
func (c *context) RenderTarget() gpu.RenderTarget {
return gpu.OpenGLRenderTarget(c.frameBuffer)
}
func (c *context) API() gpu.API {
return contextAPI()
}
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.gio_makeCurrent(0)
C.CFRelease(c.ctx)
c.ctx = 0
}
func (c *context) Present() error {
if c.layer == 0 {
panic("context is not active")
}
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() error {
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
runtime.LockOSThread()
if C.gio_makeCurrent(c.ctx) == 0 {
return errors.New("[EAGLContext setCurrentContext] failed")
}
return nil
}
func (c *context) Unlock() {
C.gio_makeCurrent(0)
}
func (c *context) Refresh() error {
if C.gio_makeCurrent(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()
}
if !c.owner.visible {
// 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")
}
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)
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)
}

47
vendor/gioui.org/app/gl_ios.m generated vendored
View File

@@ -1,47 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios,nometal
@import UIKit;
@import OpenGLES;
#include "_cgo_export.h"
Class gio_layerClass(void) {
return [CAEAGLLayer class];
}
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);
}

79
vendor/gioui.org/app/gl_js.go generated vendored
View File

@@ -1,79 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"syscall/js"
"gioui.org/gpu"
"gioui.org/internal/gl"
)
type glContext struct {
ctx js.Value
cnv js.Value
w *window
}
func newContext(w *window) (*glContext, error) {
args := map[string]interface{}{
// Enable low latency rendering.
// See https://developers.google.com/web/updates/2019/05/desynchronized.
"desynchronized": true,
"preserveDrawingBuffer": true,
}
ctx := w.cnv.Call("getContext", "webgl2", args)
if ctx.IsNull() {
ctx = w.cnv.Call("getContext", "webgl", args)
}
if ctx.IsNull() {
return nil, errors.New("app: webgl is not supported")
}
c := &glContext{
ctx: ctx,
cnv: w.cnv,
w: w,
}
return c, nil
}
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
if c.w.contextStatus != contextStatusOkay {
return nil, gpu.ErrDeviceLost
}
return gpu.OpenGLRenderTarget{}, nil
}
func (c *glContext) API() gpu.API {
return gpu.OpenGL{Context: gl.Context(c.ctx)}
}
func (c *glContext) Release() {
}
func (c *glContext) Present() error {
return nil
}
func (c *glContext) Lock() error {
return nil
}
func (c *glContext) Unlock() {}
func (c *glContext) Refresh() error {
switch c.w.contextStatus {
case contextStatusLost:
return errOutOfDate
case contextStatusRestored:
c.w.contextStatus = contextStatusOkay
return gpu.ErrDeviceLost
default:
return nil
}
}
func (w *window) NewContext() (context, error) {
return newContext(w)
}

124
vendor/gioui.org/app/gl_macos.go generated vendored
View File

@@ -1,124 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && !ios && nometal
// +build darwin,!ios,nometal
package app
import (
"errors"
"runtime"
"unsafe"
"gioui.org/gpu"
"gioui.org/internal/gl"
)
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -xobjective-c -fobjc-arc
#cgo LDFLAGS: -framework OpenGL
#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>
#include <AppKit/AppKit.h>
#include <dlfcn.h>
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLContext(void);
__attribute__ ((visibility ("hidden"))) void gio_setContextView(CFTypeRef ctx, CFTypeRef view);
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
__attribute__ ((visibility ("hidden"))) void gio_updateContext(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);
typedef void (*PFN_glFlush)(void);
static void glFlush(PFN_glFlush f) {
f();
}
*/
import "C"
type glContext struct {
c *gl.Functions
ctx C.CFTypeRef
view C.CFTypeRef
glFlush C.PFN_glFlush
}
func newContext(w *window) (*glContext, error) {
clib := C.CString("/System/Library/Frameworks/OpenGL.framework/OpenGL")
defer C.free(unsafe.Pointer(clib))
lib, err := C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
if err != nil {
return nil, err
}
csym := C.CString("glFlush")
defer C.free(unsafe.Pointer(csym))
glFlush := C.PFN_glFlush(C.dlsym(lib, csym))
if glFlush == nil {
return nil, errors.New("gl: missing symbol glFlush in the OpenGL framework")
}
view := w.contextView()
ctx := C.gio_createGLContext()
if ctx == 0 {
return nil, errors.New("gl: failed to create NSOpenGLContext")
}
C.gio_setContextView(ctx, view)
c := &glContext{
ctx: ctx,
view: view,
glFlush: glFlush,
}
return c, nil
}
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
return gpu.OpenGLRenderTarget{}, nil
}
func (c *glContext) API() gpu.API {
return gpu.OpenGL{}
}
func (c *glContext) Release() {
if c.ctx != 0 {
C.gio_clearCurrentContext()
C.CFRelease(c.ctx)
c.ctx = 0
}
}
func (c *glContext) Present() error {
// Assume the caller already locked the context.
C.glFlush(c.glFlush)
return nil
}
func (c *glContext) Lock() error {
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
runtime.LockOSThread()
C.gio_lockContext(c.ctx)
C.gio_makeCurrentContext(c.ctx)
return nil
}
func (c *glContext) Unlock() {
C.gio_clearCurrentContext()
C.gio_unlockContext(c.ctx)
}
func (c *glContext) Refresh() error {
c.Lock()
defer c.Unlock()
C.gio_updateContext(c.ctx)
return nil
}
func (w *window) NewContext() (context, error) {
return newContext(w)
}

73
vendor/gioui.org/app/gl_macos.m generated vendored
View File

@@ -1,73 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios,nometal
#import <AppKit/AppKit.h>
#include <CoreFoundation/CoreFoundation.h>
#include <OpenGL/OpenGL.h>
#include "_cgo_export.h"
CALayer *gio_layerFactory(void) {
@autoreleasepool {
return [CALayer layer];
}
}
CFTypeRef gio_createGLContext(void) {
@autoreleasepool {
NSOpenGLPixelFormatAttribute attr[] = {
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
NSOpenGLPFAColorSize, 24,
NSOpenGLPFAAccelerated,
// Opt-in to automatic GPU switching. CGL-only property.
kCGLPFASupportsAutomaticGraphicsSwitching,
NSOpenGLPFAAllowOfflineRenderers,
0
};
NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
NSOpenGLContext *ctx = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext: nil];
return CFBridgingRetain(ctx);
}
}
void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
NSView *view = (__bridge NSView *)viewRef;
[view setWantsBestResolutionOpenGLSurface:YES];
[ctx setView:view];
}
void gio_clearCurrentContext(void) {
@autoreleasepool {
[NSOpenGLContext clearCurrentContext];
}
}
void gio_updateContext(CFTypeRef ctxRef) {
@autoreleasepool {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
[ctx update];
}
}
void gio_makeCurrentContext(CFTypeRef ctxRef) {
@autoreleasepool {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
[ctx makeCurrentContext];
}
}
void gio_lockContext(CFTypeRef ctxRef) {
@autoreleasepool {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
CGLLockContext([ctx CGLContextObj]);
}
}
void gio_unlockContext(CFTypeRef ctxRef) {
@autoreleasepool {
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
CGLUnlockContext([ctx CGLContextObj]);
}
}

View File

@@ -1,7 +0,0 @@
// 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"

View File

@@ -1,87 +0,0 @@
// 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) {
n := 0
for len(data) > 0 {
msg := data
// Truncate the buffer, leaving space for the '\0'.
if max := len(w.buf) - 1; len(msg) > max {
msg = msg[:max]
}
buf := w.buf[:len(msg)+1]
copy(buf, msg)
// Terminating '\0'.
buf[len(msg)] = 0
C.__android_log_write(C.ANDROID_LOG_INFO, logTag, (*C.char)(unsafe.Pointer(&buf[0])))
n += len(msg)
data = data[len(msg):]
}
return n, 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)
}()
}

View File

@@ -1,54 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && ios
// +build darwin,ios
package log
/*
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
@import Foundation;
static void nslog(char *str) {
NSLog(@"%@", @(str));
}
*/
import "C"
import (
"bufio"
"io"
"log"
"unsafe"
_ "gioui.org/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
}

View File

@@ -1,34 +0,0 @@
// 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
}

View File

@@ -1,857 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build windows
// +build windows
package windows
import (
"fmt"
"runtime"
"time"
"unicode/utf16"
"unsafe"
syscall "golang.org/x/sys/windows"
)
type CompositionForm struct {
dwStyle uint32
ptCurrentPos Point
rcArea Rect
}
type CandidateForm struct {
dwIndex uint32
dwStyle uint32
ptCurrentPos Point
rcArea Rect
}
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 Margins struct {
CxLeftWidth int32
CxRightWidth int32
CyTopHeight int32
CyBottomHeight int32
}
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
}
type NCCalcSizeParams struct {
Rgrc [3]Rect
LpPos *WindowPos
}
type WindowPos struct {
HWND syscall.Handle
HWNDInsertAfter syscall.Handle
x int32
y int32
cx int32
cy int32
flags uint32
}
type WindowPlacement struct {
length uint32
flags uint32
showCmd uint32
ptMinPosition Point
ptMaxPosition Point
rcNormalPosition Rect
rcDevice Rect
}
type MonitorInfo struct {
cbSize uint32
Monitor Rect
WorkArea Rect
Flags uint32
}
const (
TRUE = 1
CPS_CANCEL = 0x0004
CS_HREDRAW = 0x0002
CS_INSERTCHAR = 0x2000
CS_NOMOVECARET = 0x4000
CS_VREDRAW = 0x0001
CS_OWNDC = 0x0020
CW_USEDEFAULT = -2147483648
GWL_STYLE = ^(uintptr(16) - 1) // -16
GCS_COMPSTR = 0x0008
GCS_COMPREADSTR = 0x0001
GCS_CURSORPOS = 0x0080
GCS_DELTASTART = 0x0100
GCS_RESULTREADSTR = 0x0200
GCS_RESULTSTR = 0x0800
CFS_POINT = 0x0002
CFS_CANDIDATEPOS = 0x0040
HWND_TOPMOST = ^(uint32(1) - 1) // -1
HTCAPTION = 2
HTCLIENT = 1
HTLEFT = 10
HTRIGHT = 11
HTTOP = 12
HTTOPLEFT = 13
HTTOPRIGHT = 14
HTBOTTOM = 15
HTBOTTOMLEFT = 16
HTBOTTOMRIGHT = 17
IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
IDC_ARROW = 32512 // Standard arrow
IDC_CROSS = 32515 // Crosshair
IDC_HAND = 32649 // Hand
IDC_HELP = 32651 // Arrow and question mark
IDC_IBEAM = 32513 // I-beam
IDC_NO = 32648 // Slashed circle
IDC_SIZEALL = 32646 // Four-pointed arrow pointing north, south, east, and west
IDC_SIZENESW = 32643 // Double-pointed arrow pointing northeast and southwest
IDC_SIZENS = 32645 // Double-pointed arrow pointing north and south
IDC_SIZENWSE = 32642 // Double-pointed arrow pointing northwest and southeast
IDC_SIZEWE = 32644 // Double-pointed arrow pointing west and east
IDC_UPARROW = 32516 // Vertical arrow
IDC_WAIT = 32514 // Hour
INFINITE = 0xFFFFFFFF
LOGPIXELSX = 88
MDT_EFFECTIVE_DPI = 0
MONITOR_DEFAULTTOPRIMARY = 1
NI_COMPOSITIONSTR = 0x0015
SIZE_MAXIMIZED = 2
SIZE_MINIMIZED = 1
SIZE_RESTORED = 0
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
SM_CXSIZEFRAME = 32
SM_CYSIZEFRAME = 33
SW_SHOWDEFAULT = 10
SW_SHOWMINIMIZED = 2
SW_SHOWMAXIMIZED = 3
SW_SHOWNORMAL = 1
SW_SHOW = 5
SWP_FRAMECHANGED = 0x0020
SWP_NOMOVE = 0x0002
SWP_NOOWNERZORDER = 0x0200
SWP_NOSIZE = 0x0001
SWP_NOZORDER = 0x0004
SWP_SHOWWINDOW = 0x0040
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_CLOSE = 0x0010
WM_CREATE = 0x0001
WM_DPICHANGED = 0x02E0
WM_DESTROY = 0x0002
WM_ERASEBKGND = 0x0014
WM_GETMINMAXINFO = 0x0024
WM_IME_COMPOSITION = 0x010F
WM_IME_ENDCOMPOSITION = 0x010E
WM_IME_STARTCOMPOSITION = 0x010D
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_KILLFOCUS = 0x0008
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_NCACTIVATE = 0x0086
WM_NCHITTEST = 0x0084
WM_NCCALCSIZE = 0x0083
WM_PAINT = 0x000F
WM_QUIT = 0x0012
WM_SETCURSOR = 0x0020
WM_SETFOCUS = 0x0007
WM_SHOWWINDOW = 0x0018
WM_SIZE = 0x0005
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_TIMER = 0x0113
WM_UNICHAR = 0x0109
WM_USER = 0x0400
WM_WINDOWPOSCHANGED = 0x0047
WS_CLIPCHILDREN = 0x02000000
WS_CLIPSIBLINGS = 0x04000000
WS_MAXIMIZE = 0x01000000
WS_ICONIC = 0x20000000
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
IMAGE_BITMAP = 0
IMAGE_ICON = 1
IMAGE_CURSOR = 2
LR_CREATEDIBSECTION = 0x00002000
LR_DEFAULTCOLOR = 0x00000000
LR_DEFAULTSIZE = 0x00000040
LR_LOADFROMFILE = 0x00000010
LR_LOADMAP3DCOLORS = 0x00001000
LR_LOADTRANSPARENT = 0x00000020
LR_MONOCHROME = 0x00000001
LR_SHARED = 0x00008000
LR_VGACOLOR = 0x00000080
)
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")
_GetWindowRect = user32.NewProc("GetWindowRect")
_GetClientRect = user32.NewProc("GetClientRect")
_GetClipboardData = user32.NewProc("GetClipboardData")
_GetDC = user32.NewProc("GetDC")
_GetDpiForWindow = user32.NewProc("GetDpiForWindow")
_GetKeyState = user32.NewProc("GetKeyState")
_GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime")
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
_KillTimer = user32.NewProc("KillTimer")
_LoadCursor = user32.NewProc("LoadCursorW")
_LoadImage = user32.NewProc("LoadImageW")
_MonitorFromPoint = user32.NewProc("MonitorFromPoint")
_MonitorFromWindow = user32.NewProc("MonitorFromWindow")
_MoveWindow = user32.NewProc("MoveWindow")
_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")
_SetCursor = user32.NewProc("SetCursor")
_SetClipboardData = user32.NewProc("SetClipboardData")
_SetForegroundWindow = user32.NewProc("SetForegroundWindow")
_SetFocus = user32.NewProc("SetFocus")
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
_SetTimer = user32.NewProc("SetTimer")
_SetWindowLong = user32.NewProc("SetWindowLongPtrW")
_SetWindowLong32 = user32.NewProc("SetWindowLongW")
_SetWindowPlacement = user32.NewProc("SetWindowPlacement")
_SetWindowPos = user32.NewProc("SetWindowPos")
_SetWindowText = user32.NewProc("SetWindowTextW")
_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")
imm32 = syscall.NewLazySystemDLL("imm32")
_ImmGetContext = imm32.NewProc("ImmGetContext")
_ImmGetCompositionString = imm32.NewProc("ImmGetCompositionStringW")
_ImmNotifyIME = imm32.NewProc("ImmNotifyIME")
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
_ImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
dwmapi = syscall.NewLazySystemDLL("dwmapi")
_DwmExtendFrameIntoClientArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
)
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
}
func CallMsgFilter(m *Msg, nCode uintptr) bool {
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
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))
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)))
}
func DwmExtendFrameIntoClientArea(hwnd syscall.Handle, margins Margins) error {
r, _, _ := _DwmExtendFrameIntoClientArea.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&margins)))
if r != 0 {
return fmt.Errorf("DwmExtendFrameIntoClientArea: %#x", r)
}
return nil
}
func EmptyClipboard() error {
r, _, err := _EmptyClipboard.Call()
if r == 0 {
return fmt.Errorf("EmptyClipboard: %v", err)
}
return nil
}
func GetWindowRect(hwnd syscall.Handle) Rect {
var r Rect
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
return r
}
func GetClientRect(hwnd syscall.Handle) Rect {
var r Rect
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
return 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))
return int32(r)
}
func GetMessageTime() time.Duration {
r, _, _ := _GetMessageTime.Call()
return time.Duration(r) * time.Millisecond
}
func GetSystemMetrics(nIndex int) int {
r, _, _ := _GetSystemMetrics.Call(uintptr(nIndex))
return int(r)
}
// GetWindowDPI returns the effective DPI of the window.
func GetWindowDPI(hwnd syscall.Handle) int {
// Check for GetDpiForWindow, introduced in Windows 10.
if _GetDpiForWindow.Find() == nil {
dpi, _, _ := _GetDpiForWindow.Call(uintptr(hwnd))
return int(dpi)
} else {
return GetSystemDPI()
}
}
func GetWindowPlacement(hwnd syscall.Handle) *WindowPlacement {
var wp WindowPlacement
wp.length = uint32(unsafe.Sizeof(wp))
_GetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&wp)))
return &wp
}
func GetMonitorInfo(hwnd syscall.Handle) MonitorInfo {
var mi MonitorInfo
mi.cbSize = uint32(unsafe.Sizeof(mi))
v, _, _ := _MonitorFromWindow.Call(uintptr(hwnd), MONITOR_DEFAULTTOPRIMARY)
_GetMonitorInfo.Call(v, uintptr(unsafe.Pointer(&mi)))
return mi
}
func GetWindowLong(hwnd syscall.Handle, index uintptr) (val uintptr) {
if runtime.GOARCH == "386" {
val, _, _ = _GetWindowLong32.Call(uintptr(hwnd), index)
} else {
val, _, _ = _GetWindowLong.Call(uintptr(hwnd), index)
}
return
}
func ImmGetContext(hwnd syscall.Handle) syscall.Handle {
h, _, _ := _ImmGetContext.Call(uintptr(hwnd))
return syscall.Handle(h)
}
func ImmReleaseContext(hwnd, imc syscall.Handle) {
_ImmReleaseContext.Call(uintptr(hwnd), uintptr(imc))
}
func ImmNotifyIME(imc syscall.Handle, action, index, value int) {
_ImmNotifyIME.Call(uintptr(imc), uintptr(action), uintptr(index), uintptr(value))
}
func ImmGetCompositionString(imc syscall.Handle, key int) string {
size, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
if int32(size) <= 0 {
return ""
}
u16 := make([]uint16, size/unsafe.Sizeof(uint16(0)))
_ImmGetCompositionString.Call(uintptr(imc), uintptr(key), uintptr(unsafe.Pointer(&u16[0])), size)
return string(utf16.Decode(u16))
}
func ImmGetCompositionValue(imc syscall.Handle, key int) int {
val, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
return int(int32(val))
}
func ImmSetCompositionWindow(imc syscall.Handle, x, y int) {
f := CompositionForm{
dwStyle: CFS_POINT,
ptCurrentPos: Point{
X: int32(x), Y: int32(y),
},
}
_ImmSetCompositionWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
}
func ImmSetCandidateWindow(imc syscall.Handle, x, y int) {
f := CandidateForm{
dwStyle: CFS_CANDIDATEPOS,
ptCurrentPos: Point{
X: int32(x), Y: int32(y),
},
}
_ImmSetCandidateWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
}
func SetWindowLong(hwnd syscall.Handle, idx uintptr, style uintptr) {
if runtime.GOARCH == "386" {
_SetWindowLong32.Call(uintptr(hwnd), idx, style)
} else {
_SetWindowLong.Call(uintptr(hwnd), idx, style)
}
}
func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
_SetWindowPlacement.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wp)))
}
func SetWindowPos(hwnd syscall.Handle, hwndInsertAfter uint32, x, y, dx, dy int32, style uintptr) {
_SetWindowPos.Call(uintptr(hwnd), uintptr(hwndInsertAfter),
uintptr(x), uintptr(y),
uintptr(dx), uintptr(dy),
style,
)
}
func SetWindowText(hwnd syscall.Handle, title string) {
wname := syscall.StringToUTF16Ptr(title)
_SetWindowText.Call(uintptr(hwnd), uintptr(unsafe.Pointer(wname)))
}
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) (unsafe.Pointer, error) {
r, _, err := _GlobalLock.Call(uintptr(h))
if r == 0 {
return nil, fmt.Errorf("GlobalLock: %v", err)
}
return unsafe.Pointer(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 LoadImage(hInst syscall.Handle, res uint32, typ uint32, cx, cy int, fuload uint32) (syscall.Handle, error) {
h, _, err := _LoadImage.Call(uintptr(hInst), uintptr(res), uintptr(typ), uintptr(cx), uintptr(cy), uintptr(fuload))
if h == 0 {
return 0, fmt.Errorf("LoadImageW failed: %v", err)
}
return syscall.Handle(h), nil
}
func MoveWindow(hwnd syscall.Handle, x, y, width, height int32, repaint bool) {
var paint uintptr
if repaint {
paint = TRUE
}
_MoveWindow.Call(uintptr(hwnd), uintptr(x), uintptr(y), uintptr(width), uintptr(height), paint)
}
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))
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)))
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 SetCursor(h syscall.Handle) {
_SetCursor.Call(uintptr(h))
}
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)))
}
func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
_ShowWindow.Call(uintptr(hwnd), uintptr(nCmdShow))
}
func TranslateMessage(m *Msg) {
_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
}
func UnregisterClass(cls uint16, hInst syscall.Handle) {
_UnregisterClass.Call(uintptr(cls), uintptr(hInst))
}
func UpdateWindow(hwnd syscall.Handle) {
_UpdateWindow.Call(uintptr(hwnd))
}
func (p WindowPlacement) Rect() Rect {
return p.rcNormalPosition
}
func (p WindowPlacement) IsMinimized() bool {
return p.showCmd == SW_SHOWMINIMIZED
}
func (p WindowPlacement) IsMaximized() bool {
return p.showCmd == SW_SHOWMAXIMIZED
}
func (p *WindowPlacement) Set(Left, Top, Right, Bottom int) {
p.rcNormalPosition.Left = int32(Left)
p.rcNormalPosition.Top = int32(Top)
p.rcNormalPosition.Right = int32(Right)
p.rcNormalPosition.Bottom = int32(Bottom)
}

View File

@@ -1,375 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build (linux && !android) || freebsd || openbsd
// +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 x.state == nil {
return mods
}
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, state key.State) (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(),
State: state,
}
// 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:
mod := x.Modifiers()
if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
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 state == key.Press && 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 {
if x.state == nil {
return false
}
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 C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
return string(rune(s - C.XKB_KEY_KP_0 + '0')), 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_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 = key.NameF1
case C.XKB_KEY_F2:
n = key.NameF2
case C.XKB_KEY_F3:
n = key.NameF3
case C.XKB_KEY_F4:
n = key.NameF4
case C.XKB_KEY_F5:
n = key.NameF5
case C.XKB_KEY_F6:
n = key.NameF6
case C.XKB_KEY_F7:
n = key.NameF7
case C.XKB_KEY_F8:
n = key.NameF8
case C.XKB_KEY_F9:
n = key.NameF9
case C.XKB_KEY_F10:
n = key.NameF10
case C.XKB_KEY_F11:
n = key.NameF11
case C.XKB_KEY_F12:
n = key.NameF12
case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
n = key.NameTab
case 0x20:
n = key.NameSpace
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
n = key.NameCtrl
case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R:
n = key.NameShift
case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R:
n = key.NameAlt
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
n = key.NameSuper
case C.XKB_KEY_KP_Space:
n = key.NameSpace
case C.XKB_KEY_KP_Tab:
n = key.NameTab
case C.XKB_KEY_KP_Enter:
n = key.NameEnter
case C.XKB_KEY_KP_F1:
n = key.NameF1
case C.XKB_KEY_KP_F2:
n = key.NameF2
case C.XKB_KEY_KP_F3:
n = key.NameF3
case C.XKB_KEY_KP_F4:
n = key.NameF4
case C.XKB_KEY_KP_Home:
n = key.NameHome
case C.XKB_KEY_KP_Left:
n = key.NameLeftArrow
case C.XKB_KEY_KP_Up:
n = key.NameUpArrow
case C.XKB_KEY_KP_Right:
n = key.NameRightArrow
case C.XKB_KEY_KP_Down:
n = key.NameDownArrow
case C.XKB_KEY_KP_Prior:
// not supported
return "", false
case C.XKB_KEY_KP_Next:
// not supported
return "", false
case C.XKB_KEY_KP_End:
n = key.NameEnd
case C.XKB_KEY_KP_Begin:
n = key.NameHome
case C.XKB_KEY_KP_Insert:
// not supported
return "", false
case C.XKB_KEY_KP_Delete:
n = key.NameDeleteForward
case C.XKB_KEY_KP_Multiply:
n = "*"
case C.XKB_KEY_KP_Add:
n = "+"
case C.XKB_KEY_KP_Separator:
// not supported
return "", false
case C.XKB_KEY_KP_Subtract:
n = "-"
case C.XKB_KEY_KP_Decimal:
// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
// German, the decimal is a comma, not a period.
n = "."
case C.XKB_KEY_KP_Divide:
n = "/"
case C.XKB_KEY_KP_Equal:
n = "="
default:
return "", false
}
return n, true
}

173
vendor/gioui.org/app/metal_darwin.go generated vendored
View File

@@ -1,173 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !nometal
// +build !nometal
package app
import (
"errors"
"gioui.org/gpu"
)
/*
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
#cgo LDFLAGS: -framework QuartzCore -framework Metal
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h>
static CFTypeRef createMetalDevice(void) {
@autoreleasepool {
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
return CFBridgingRetain(dev);
}
}
static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) {
@autoreleasepool {
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
layer.device = dev;
// Package gpu assumes an sRGB-encoded framebuffer.
layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
if (@available(iOS 11.0, *)) {
// Never let nextDrawable time out and return nil.
layer.allowsNextDrawableTimeout = NO;
}
}
}
static CFTypeRef nextDrawable(CFTypeRef layerRef) {
@autoreleasepool {
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
return CFBridgingRetain([layer nextDrawable]);
}
}
static CFTypeRef drawableTexture(CFTypeRef drawableRef) {
@autoreleasepool {
id<CAMetalDrawable> drawable = (__bridge id<CAMetalDrawable>)drawableRef;
return CFBridgingRetain(drawable.texture);
}
}
static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
@autoreleasepool {
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
[cmdBuffer presentDrawable:drawable];
[cmdBuffer commit];
}
}
static CFTypeRef newCommandQueue(CFTypeRef devRef) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
return CFBridgingRetain([dev newCommandQueue]);
}
}
*/
import "C"
type mtlContext struct {
dev C.CFTypeRef
view C.CFTypeRef
layer C.CFTypeRef
queue C.CFTypeRef
drawable C.CFTypeRef
texture C.CFTypeRef
}
func newMtlContext(w *window) (*mtlContext, error) {
dev := C.createMetalDevice()
if dev == 0 {
return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed")
}
view := w.contextView()
layer := getMetalLayer(view)
if layer == 0 {
C.CFRelease(dev)
return nil, errors.New("metal: CAMetalLayer construction failed")
}
queue := C.newCommandQueue(dev)
if layer == 0 {
C.CFRelease(dev)
C.CFRelease(layer)
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
}
C.setupLayer(layer, dev)
c := &mtlContext{
dev: dev,
view: view,
layer: layer,
queue: queue,
}
return c, nil
}
func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) {
if c.drawable != 0 || c.texture != 0 {
return nil, errors.New("metal:a previous RenderTarget wasn't Presented")
}
c.drawable = C.nextDrawable(c.layer)
if c.drawable == 0 {
return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed")
}
c.texture = C.drawableTexture(c.drawable)
if c.texture == 0 {
return nil, errors.New("metal: CADrawable.texture is nil")
}
return gpu.MetalRenderTarget{
Texture: uintptr(c.texture),
}, nil
}
func (c *mtlContext) API() gpu.API {
return gpu.Metal{
Device: uintptr(c.dev),
Queue: uintptr(c.queue),
PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB),
}
}
func (c *mtlContext) Release() {
C.CFRelease(c.queue)
C.CFRelease(c.dev)
C.CFRelease(c.layer)
if c.drawable != 0 {
C.CFRelease(c.drawable)
}
if c.texture != 0 {
C.CFRelease(c.texture)
}
*c = mtlContext{}
}
func (c *mtlContext) Present() error {
C.CFRelease(c.texture)
c.texture = 0
C.presentDrawable(c.queue, c.drawable)
C.CFRelease(c.drawable)
c.drawable = 0
return nil
}
func (c *mtlContext) Lock() error {
return nil
}
func (c *mtlContext) Unlock() {}
func (c *mtlContext) Refresh() error {
resizeDrawable(c.view, c.layer)
return nil
}
func (w *window) NewContext() (context, error) {
return newMtlContext(w)
}

48
vendor/gioui.org/app/metal_ios.go generated vendored
View File

@@ -1,48 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !nometal
// +build !nometal
package app
/*
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
@import UIKit;
@import QuartzCore.CAMetalLayer;
#include <CoreFoundation/CoreFoundation.h>
Class gio_layerClass(void) {
return [CAMetalLayer class];
}
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
@autoreleasepool {
UIView *view = (__bridge UIView *)viewRef;
return CFBridgingRetain(view.layer);
}
}
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
@autoreleasepool {
UIView *view = (__bridge UIView *)viewRef;
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
layer.contentsScale = view.contentScaleFactor;
CGSize size = layer.bounds.size;
size.width *= layer.contentsScale;
size.height *= layer.contentsScale;
layer.drawableSize = size;
}
}
*/
import "C"
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
return C.getMetalLayer(view)
}
func resizeDrawable(view, layer C.CFTypeRef) {
C.resizeDrawable(view, layer)
}

47
vendor/gioui.org/app/metal_macos.go generated vendored
View File

@@ -1,47 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && !ios && !nometal
// +build darwin,!ios,!nometal
package app
/*
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
#import <AppKit/AppKit.h>
#import <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h>
CALayer *gio_layerFactory(void) {
@autoreleasepool {
return [CAMetalLayer layer];
}
}
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
@autoreleasepool {
NSView *view = (__bridge NSView *)viewRef;
return CFBridgingRetain(view.layer);
}
}
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
@autoreleasepool {
NSView *view = (__bridge NSView *)viewRef;
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
CGSize size = layer.bounds.size;
size.width *= layer.contentsScale;
size.height *= layer.contentsScale;
layer.drawableSize = size;
}
}
*/
import "C"
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
return C.getMetalLayer(view)
}
func resizeDrawable(view, layer C.CFTypeRef) {
C.resizeDrawable(view, layer)
}

223
vendor/gioui.org/app/os.go generated vendored
View File

@@ -1,223 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"image"
"image/color"
"gioui.org/io/key"
"gioui.org/gpu"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/unit"
)
// errOutOfDate is reported when the GPU surface dimensions or properties no
// longer match the window.
var errOutOfDate = errors.New("app: GPU surface out of date")
// Config describes a Window configuration.
type Config struct {
// Size is the window dimensions (Width, Height).
Size image.Point
// MaxSize is the window maximum allowed dimensions.
MaxSize image.Point
// MinSize is the window minimum allowed dimensions.
MinSize image.Point
// Title is the window title displayed in its decoration bar.
Title string
// WindowMode is the window mode.
Mode WindowMode
// StatusColor is the color of the Android status bar.
StatusColor color.NRGBA
// NavigationColor is the color of the navigation bar
// on Android, or the address bar in browsers.
NavigationColor color.NRGBA
// Orientation is the current window orientation.
Orientation Orientation
// CustomRenderer is true when the window content is rendered by the
// client.
CustomRenderer bool
// Decorated reports whether window decorations are provided automatically.
Decorated bool
// decoHeight is the height of the fallback decoration for platforms such
// as Wayland that may need fallback client-side decorations.
decoHeight unit.Dp
}
// ConfigEvent is sent whenever the configuration of a Window changes.
type ConfigEvent struct {
Config Config
}
func (c *Config) apply(m unit.Metric, options []Option) {
for _, o := range options {
o(m, c)
}
}
type wakeupEvent struct{}
// WindowMode is the window mode (WindowMode.Option sets it).
// Note that mode can be changed programatically as well as by the user
// clicking on the minimize/maximize buttons on the window's title bar.
type WindowMode uint8
const (
// Windowed is the normal window mode with OS specific window decorations.
Windowed WindowMode = iota
// Fullscreen is the full screen window mode.
Fullscreen
// Minimized is for systems where the window can be minimized to an icon.
Minimized
// Maximized is for systems where the window can be made to fill the available monitor area.
Maximized
)
// Option changes the mode of a Window.
func (m WindowMode) Option() Option {
return func(_ unit.Metric, cnf *Config) {
cnf.Mode = m
}
}
// String returns the mode name.
func (m WindowMode) String() string {
switch m {
case Windowed:
return "windowed"
case Fullscreen:
return "fullscreen"
case Minimized:
return "minimized"
case Maximized:
return "maximized"
}
return ""
}
// Orientation is the orientation of the app (Orientation.Option sets it).
//
// Supported platforms are Android and JS.
type Orientation uint8
const (
// AnyOrientation allows the window to be freely orientated.
AnyOrientation Orientation = iota
// LandscapeOrientation constrains the window to landscape orientations.
LandscapeOrientation
// PortraitOrientation constrains the window to portrait orientations.
PortraitOrientation
)
func (o Orientation) Option() Option {
return func(_ unit.Metric, cnf *Config) {
cnf.Orientation = o
}
}
func (o Orientation) String() string {
switch o {
case AnyOrientation:
return "any"
case LandscapeOrientation:
return "landscape"
case PortraitOrientation:
return "portrait"
}
return ""
}
type frameEvent struct {
system.FrameEvent
Sync bool
}
type context interface {
API() gpu.API
RenderTarget() (gpu.RenderTarget, error)
Present() error
Refresh() error
Release()
Lock() error
Unlock()
}
// 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)
SetInputHint(mode key.InputHint)
NewContext() (context, error)
// ReadClipboard requests the clipboard content.
ReadClipboard()
// WriteClipboard requests a clipboard write.
WriteClipboard(s string)
// Configure the window.
Configure([]Option)
// SetCursor updates the current cursor to name.
SetCursor(cursor pointer.Cursor)
// Wakeup wakes up the event loop and sends a WakeupEvent.
Wakeup()
// Perform actions on the window.
Perform(system.Action)
// EditorStateChanged notifies the driver that the editor state changed.
EditorStateChanged(old, new editorState)
}
type windowRendezvous struct {
in chan windowAndConfig
out chan windowAndConfig
errs chan error
}
type windowAndConfig struct {
window *callbacks
options []Option
}
func newWindowRendezvous() *windowRendezvous {
wr := &windowRendezvous{
in: make(chan windowAndConfig),
out: make(chan windowAndConfig),
errs: make(chan error),
}
go func() {
var main windowAndConfig
var out chan windowAndConfig
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
}
func (wakeupEvent) ImplementsEvent() {}
func (ConfigEvent) ImplementsEvent() {}
func walkActions(actions system.Action, do func(system.Action)) {
for a := system.Action(1); actions != 0; a <<= 1 {
if actions&a != 0 {
actions &^= a
do(a)
}
}
}

1457
vendor/gioui.org/app/os_android.go generated vendored

File diff suppressed because it is too large Load Diff

267
vendor/gioui.org/app/os_darwin.go generated vendored
View File

@@ -1,267 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
/*
#include <Foundation/Foundation.h>
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
__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);
__attribute__ ((visibility ("hidden"))) void gio_hideCursor();
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
static bool isMainThread() {
return [NSThread isMainThread];
}
static NSUInteger nsstringLength(CFTypeRef cstr) {
NSString *str = (__bridge NSString *)cstr;
return [str length];
}
static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
NSString *str = (__bridge NSString *)cstr;
[str getCharacters:chars range:NSMakeRange(loc, length)];
}
static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
@autoreleasepool {
NSString *s = [NSString string];
if (length > 0) {
s = [NSString stringWithCharacters:chars length:length];
}
return CFBridgingRetain(s);
}
}
*/
import "C"
import (
"errors"
"sync"
"sync/atomic"
"time"
"unicode/utf16"
"unsafe"
"gioui.org/io/pointer"
)
// 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()) {
if C.isMainThread() {
f()
return
}
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.
func nsstringToString(str C.CFTypeRef) string {
if str == 0 {
return ""
}
n := C.nsstringLength(str)
if n == 0 {
return ""
}
chars := make([]uint16, n)
C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
utf8 := utf16.Decode(chars)
return string(utf8)
}
// stringToNSString converts a Go string to a retained NSString.
func stringToNSString(str string) C.CFTypeRef {
u16 := utf16.Encode([]rune(str))
var chars *C.unichar
if len(u16) > 0 {
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
}
return C.newNSString(chars, C.NSUInteger(len(u16)))
}
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(ref C.CFTypeRef) {
d, exists := displayLinks.Load(ref)
if !exists {
return
}
dl := d.(*displayLink)
if atomic.LoadUint32(&dl.running) != 0 {
dl.callback()
}
}
var macosCursorID = [...]byte{
pointer.CursorDefault: 0,
pointer.CursorNone: 1,
pointer.CursorText: 2,
pointer.CursorVerticalText: 3,
pointer.CursorPointer: 4,
pointer.CursorCrosshair: 5,
pointer.CursorAllScroll: 6,
pointer.CursorColResize: 7,
pointer.CursorRowResize: 8,
pointer.CursorGrab: 9,
pointer.CursorGrabbing: 10,
pointer.CursorNotAllowed: 11,
pointer.CursorWait: 12,
pointer.CursorProgress: 13,
pointer.CursorNorthWestResize: 14,
pointer.CursorNorthEastResize: 15,
pointer.CursorSouthWestResize: 16,
pointer.CursorSouthEastResize: 17,
pointer.CursorNorthSouthResize: 18,
pointer.CursorEastWestResize: 19,
pointer.CursorWestResize: 20,
pointer.CursorEastResize: 21,
pointer.CursorNorthResize: 22,
pointer.CursorSouthResize: 23,
pointer.CursorNorthEastSouthWestResize: 24,
pointer.CursorNorthWestSouthEastResize: 25,
}
// windowSetCursor updates the cursor from the current one to a new one
// and returns the new one.
func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
if from == to {
return to
}
if to == pointer.CursorNone {
C.gio_hideCursor()
return to
}
if from == pointer.CursorNone {
C.gio_showCursor()
}
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
return to
}
func (w *window) Wakeup() {
runOnMain(func() {
w.w.Event(wakeupEvent{})
})
}

11
vendor/gioui.org/app/os_darwin.m generated vendored
View File

@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#import <Foundation/Foundation.h>
#include "_cgo_export.h"
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_dispatchMainFuncs();
});
}

359
vendor/gioui.org/app/os_ios.go generated vendored
View File

@@ -1,359 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && ios
// +build darwin,ios
package app
/*
#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;
};
static void 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;
}
}
static CFTypeRef readClipboard(void) {
@autoreleasepool {
UIPasteboard *p = UIPasteboard.generalPasteboard;
return (__bridge_retained CFTypeRef)p.string;
}
}
static void showTextInput(CFTypeRef viewRef) {
UIView *view = (__bridge UIView *)viewRef;
[view becomeFirstResponder];
}
static void hideTextInput(CFTypeRef viewRef) {
UIView *view = (__bridge UIView *)viewRef;
[view resignFirstResponder];
}
static struct drawParams 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;
}
*/
import "C"
import (
"image"
"runtime"
"runtime/debug"
"time"
"unicode/utf16"
"unsafe"
"gioui.org/f32"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/unit"
)
type ViewEvent struct {
// ViewController is a CFTypeRef for the UIViewController backing a Window.
ViewController uintptr
}
type window struct {
view C.CFTypeRef
w *callbacks
displayLink *displayLink
visible bool
cursor pointer.Cursor
config Config
pointerMap []C.CFTypeRef
}
var mainWindow = newWindowRendezvous()
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, controller 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)
views[view] = w
w.Configure(wopts.options)
w.w.Event(system.StageEvent{Stage: system.StagePaused})
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
}
//export gio_onDraw
func gio_onDraw(view C.CFTypeRef) {
w := views[view]
w.draw(true)
}
func (w *window) draw(sync bool) {
params := C.viewDrawParams(w.view)
if params.width == 0 || params.height == 0 {
return
}
wasVisible := w.visible
w.visible = true
if !wasVisible {
w.w.Event(system.StageEvent{Stage: system.StageRunning})
}
const inchPrDp = 1.0 / 163
m := unit.Metric{
PxPerDp: float32(params.dpi) * inchPrDp,
PxPerSp: float32(params.sdpi) * inchPrDp,
}
dppp := unit.Dp(1. / m.PxPerDp)
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.Dp(params.top) * dppp,
Bottom: unit.Dp(params.bottom) * dppp,
Left: unit.Dp(params.left) * dppp,
Right: unit.Dp(params.right) * dppp,
},
Metric: m,
},
Sync: sync,
})
}
//export onStop
func onStop(view C.CFTypeRef) {
w := views[view]
w.visible = 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(ViewEvent{})
w.w.Event(system.DestroyEvent{})
w.displayLink.Close()
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, str C.CFTypeRef) {
w := views[view]
w.w.EditorInsert(nsstringToString(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() {
cstr := C.readClipboard()
defer C.CFRelease(cstr)
content := nsstringToString(cstr)
w.w.Event(clipboard.Event{Text: content})
}
func (w *window) WriteClipboard(s string) {
u16 := utf16.Encode([]rune(s))
var chars *C.unichar
if len(u16) > 0 {
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
}
C.writeClipboard(chars, C.NSUInteger(len(u16)))
}
func (w *window) Configure([]Option) {
// Decorations are never disabled.
w.config.Decorated = true
w.w.Event(ConfigEvent{Config: w.config})
}
func (w *window) EditorStateChanged(old, new editorState) {}
func (w *window) Perform(system.Action) {}
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) SetCursor(cursor pointer.Cursor) {
w.cursor = windowSetCursor(w.cursor, cursor)
}
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) contextView() C.CFTypeRef {
return w.view
}
func (w *window) ShowTextInput(show bool) {
if show {
C.showTextInput(w.view)
} else {
C.hideTextInput(w.view)
}
}
func (w *window) SetInputHint(_ key.InputHint) {}
func newWindow(win *callbacks, options []Option) error {
mainWindow.in <- windowAndConfig{win, options}
return <-mainWindow.errs
}
func osMain() {
}
//export gio_runMain
func gio_runMain() {
runMain()
}
func (_ ViewEvent) ImplementsEvent() {}

273
vendor/gioui.org/app/os_ios.m generated vendored
View File

@@ -1,273 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,ios
@import UIKit;
#include <stdint.h>
#include "_cgo_export.h"
#include "framework_ios.h"
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
@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, (__bridge CFTypeRef)self);
[[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);
}
+ (Class)layerClass {
return gio_layerClass();
}
- (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)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, (__bridge CFTypeRef)text);
}
- (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
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.
}
void gio_hideCursor() {
// Not supported.
}
void gio_showCursor() {
// Not supported.
}
void gio_setCursor(NSUInteger curID) {
// Not supported.
}

824
vendor/gioui.org/app/os_js.go generated vendored
View File

@@ -1,824 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"fmt"
"image"
"image/color"
"strings"
"syscall/js"
"time"
"unicode"
"unicode/utf8"
"gioui.org/internal/f32color"
"gioui.org/f32"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/unit"
)
type ViewEvent struct {
Element js.Value
}
type contextStatus int
const (
contextStatusOkay contextStatus = iota
contextStatusLost
contextStatusRestored
)
type window struct {
window js.Value
document js.Value
head js.Value
clipboard js.Value
cnv js.Value
tarea js.Value
w *callbacks
redraw js.Func
clipboardCallback js.Func
requestAnimationFrame js.Value
browserHistory js.Value
visualViewport js.Value
screenOrientation js.Value
cleanfuncs []func()
touches []js.Value
composing bool
requestFocus bool
chanAnimation chan struct{}
chanRedraw chan struct{}
config Config
inset f32.Point
scale float32
animating bool
// animRequested tracks whether a requestAnimationFrame callback
// is pending.
animRequested bool
wakeups chan struct{}
contextStatus contextStatus
}
func newWindow(win *callbacks, options []Option) 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,
document: doc,
tarea: tarea,
window: js.Global().Get("window"),
head: doc.Get("head"),
clipboard: js.Global().Get("navigator").Get("clipboard"),
wakeups: make(chan struct{}, 1),
}
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
w.browserHistory = w.window.Get("history")
w.visualViewport = w.window.Get("visualViewport")
if w.visualViewport.IsUndefined() {
w.visualViewport = w.window
}
if screen := w.window.Get("screen"); screen.Truthy() {
w.screenOrientation = screen.Get("orientation")
}
w.chanAnimation = make(chan struct{}, 1)
w.chanRedraw = make(chan struct{}, 1)
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
w.chanAnimation <- struct{}{}
return nil
})
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
content := args[0].String()
go win.Event(clipboard.Event{Text: content})
return nil
})
w.addEventListeners()
w.addHistory()
w.w = win
go func() {
defer w.cleanup()
w.w.SetDriver(w)
w.Configure(options)
w.blur()
w.w.Event(ViewEvent{Element: cont})
w.w.Event(system.StageEvent{Stage: system.StageRunning})
w.resize()
w.draw(true)
for {
select {
case <-w.wakeups:
w.w.Event(wakeupEvent{})
case <-w.chanAnimation:
w.animCallback()
case <-w.chanRedraw:
w.draw(true)
}
}
}()
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.cnv, "webglcontextlost", func(this js.Value, args []js.Value) interface{} {
args[0].Call("preventDefault")
w.contextStatus = contextStatusLost
return nil
})
w.addEventListener(w.cnv, "webglcontextrestored", func(this js.Value, args []js.Value) interface{} {
args[0].Call("preventDefault")
w.contextStatus = contextStatusRestored
// Resize is required to force update the canvas content when restored.
w.cnv.Set("width", 0)
w.cnv.Set("height", 0)
w.resize()
w.requestRedraw()
return nil
})
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
w.resize()
w.requestRedraw()
return nil
})
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
args[0].Call("preventDefault")
return nil
})
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
if w.w.Event(key.Event{Name: key.NameBack}) {
return w.browserHistory.Call("forward")
}
return w.browserHistory.Call("back")
})
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
ev := system.StageEvent{}
switch w.document.Get("visibilityState").String() {
case "hidden", "prerender", "unloaded":
ev.Stage = system.StagePaused
default:
ev.Stage = system.StageRunning
}
w.w.Event(ev)
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])
if w.requestFocus {
w.focus()
w.requestFocus = false
}
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()
// horizontal scroll if shift is pressed.
if e.Get("shiftKey").Bool() {
dx, dy = dy, dx
}
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])
if w.requestFocus {
w.focus() // iOS can only focus inside a Touch event.
w.requestFocus = false
}
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})
w.blur()
return nil
})
w.addEventListener(w.tarea, "keydown", func(this js.Value, args []js.Value) interface{} {
w.keyEvent(args[0], key.Press)
return nil
})
w.addEventListener(w.tarea, "keyup", func(this js.Value, args []js.Value) interface{} {
w.keyEvent(args[0], key.Release)
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
})
w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
if w.clipboard.IsUndefined() {
return nil
}
// Prevents duplicated-paste, since "paste" is already handled through Clipboard API.
args[0].Call("preventDefault")
return nil
})
}
func (w *window) addHistory() {
w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
}
func (w *window) flushInput() {
val := w.tarea.Get("value").String()
w.tarea.Set("value", "")
w.w.EditorInsert(string(val))
}
func (w *window) blur() {
w.tarea.Call("blur")
w.requestFocus = false
}
func (w *window) focus() {
w.tarea.Call("focus")
w.requestFocus = true
}
func (w *window) keyboard(hint key.InputHint) {
var m string
switch hint {
case key.HintAny:
m = "text"
case key.HintText:
m = "text"
case key.HintNumeric:
m = "decimal"
case key.HintEmail:
m = "email"
case key.HintURL:
m = "url"
case key.HintTelephone:
m = "tel"
case key.HintPassword:
m = "password"
default:
m = "text"
}
w.tarea.Set("inputMode", m)
}
func (w *window) keyEvent(e js.Value, ks key.State) {
k := e.Get("key").String()
if n, ok := translateKey(k); ok {
cmd := key.Event{
Name: n,
Modifiers: modifiersFor(e),
State: ks,
}
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.Get("getModifierState").IsUndefined() {
// Some browsers doesn't support getModifierState.
return mods
}
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")
scale := w.scale
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: mods,
})
}
}
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()
scale := w.scale
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.ButtonPrimary
}
if jbtns&2 != 0 {
btns |= pointer.ButtonSecondary
}
if jbtns&4 != 0 {
btns |= pointer.ButtonTertiary
}
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 during cleanup.
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() {
anim := w.animating
w.animRequested = anim
if anim {
w.requestAnimationFrame.Invoke(w.redraw)
}
if anim {
w.draw(false)
}
}
func (w *window) EditorStateChanged(old, new editorState) {}
func (w *window) SetAnimating(anim bool) {
w.animating = anim
if anim && !w.animRequested {
w.animRequested = true
w.requestAnimationFrame.Invoke(w.redraw)
}
}
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) Configure(options []Option) {
prev := w.config
cnf := w.config
cnf.apply(unit.Metric{}, options)
// Decorations are never disabled.
cnf.Decorated = true
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
w.document.Set("title", cnf.Title)
}
if prev.Mode != cnf.Mode {
w.windowMode(cnf.Mode)
}
if prev.NavigationColor != cnf.NavigationColor {
w.config.NavigationColor = cnf.NavigationColor
w.navigationColor(cnf.NavigationColor)
}
if prev.Orientation != cnf.Orientation {
w.config.Orientation = cnf.Orientation
w.orientation(cnf.Orientation)
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
w.w.Event(ConfigEvent{Config: w.config})
}
func (w *window) Perform(system.Action) {}
var webCursor = [...]string{
pointer.CursorDefault: "default",
pointer.CursorNone: "none",
pointer.CursorText: "text",
pointer.CursorVerticalText: "vertical-text",
pointer.CursorPointer: "pointer",
pointer.CursorCrosshair: "crosshair",
pointer.CursorAllScroll: "all-scroll",
pointer.CursorColResize: "col-resize",
pointer.CursorRowResize: "row-resize",
pointer.CursorGrab: "grab",
pointer.CursorGrabbing: "grabbing",
pointer.CursorNotAllowed: "not-allowed",
pointer.CursorWait: "wait",
pointer.CursorProgress: "progress",
pointer.CursorNorthWestResize: "nw-resize",
pointer.CursorNorthEastResize: "ne-resize",
pointer.CursorSouthWestResize: "sw-resize",
pointer.CursorSouthEastResize: "se-resize",
pointer.CursorNorthSouthResize: "ns-resize",
pointer.CursorEastWestResize: "ew-resize",
pointer.CursorWestResize: "w-resize",
pointer.CursorEastResize: "e-resize",
pointer.CursorNorthResize: "n-resize",
pointer.CursorSouthResize: "s-resize",
pointer.CursorNorthEastSouthWestResize: "nesw-resize",
pointer.CursorNorthWestSouthEastResize: "nwse-resize",
}
func (w *window) SetCursor(cursor pointer.Cursor) {
style := w.cnv.Get("style")
style.Set("cursor", webCursor[cursor])
}
func (w *window) Wakeup() {
select {
case w.wakeups <- struct{}{}:
default:
}
}
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()
}
}()
}
func (w *window) SetInputHint(mode key.InputHint) {
w.keyboard(mode)
}
func (w *window) resize() {
w.scale = float32(w.window.Get("devicePixelRatio").Float())
rect := w.cnv.Call("getBoundingClientRect")
size := image.Point{
X: int(float32(rect.Get("width").Float()) * w.scale),
Y: int(float32(rect.Get("height").Float()) * w.scale),
}
if size != w.config.Size {
w.config.Size = size
w.w.Event(ConfigEvent{Config: w.config})
}
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
w.inset.X = float32(w.config.Size.X) - float32(vx.Float())*w.scale
w.inset.Y = float32(w.config.Size.Y) - float32(vy.Float())*w.scale
}
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
return
}
w.cnv.Set("width", w.config.Size.X)
w.cnv.Set("height", w.config.Size.Y)
}
func (w *window) draw(sync bool) {
if w.contextStatus == contextStatusLost {
return
}
size, insets, metric := w.getConfig()
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
return
}
w.w.Event(frameEvent{
FrameEvent: system.FrameEvent{
Now: time.Now(),
Size: size,
Insets: insets,
Metric: metric,
},
Sync: sync,
})
}
func (w *window) getConfig() (image.Point, system.Insets, unit.Metric) {
invscale := unit.Dp(1. / w.scale)
return image.Pt(w.config.Size.X, w.config.Size.Y),
system.Insets{
Bottom: unit.Dp(w.inset.Y) * invscale,
Right: unit.Dp(w.inset.X) * invscale,
}, unit.Metric{
PxPerDp: w.scale,
PxPerSp: w.scale,
}
}
func (w *window) windowMode(mode WindowMode) {
switch mode {
case Windowed:
if !w.document.Get("fullscreenElement").Truthy() {
return // Browser is already Windowed.
}
if !w.document.Get("exitFullscreen").Truthy() {
return // Browser doesn't support such feature.
}
w.document.Call("exitFullscreen")
w.config.Mode = Windowed
case Fullscreen:
elem := w.document.Get("documentElement")
if !elem.Get("requestFullscreen").Truthy() {
return // Browser doesn't support such feature.
}
elem.Call("requestFullscreen")
w.config.Mode = Fullscreen
}
}
func (w *window) orientation(mode Orientation) {
if j := w.screenOrientation; !j.Truthy() || !j.Get("unlock").Truthy() || !j.Get("lock").Truthy() {
return // Browser don't support Screen Orientation API.
}
switch mode {
case AnyOrientation:
w.screenOrientation.Call("unlock")
case LandscapeOrientation:
w.screenOrientation.Call("lock", "landscape").Call("then", w.redraw)
case PortraitOrientation:
w.screenOrientation.Call("lock", "portrait").Call("then", w.redraw)
}
}
func (w *window) navigationColor(c color.NRGBA) {
theme := w.head.Call("querySelector", `meta[name="theme-color"]`)
if !theme.Truthy() {
theme = w.document.Call("createElement", "meta")
theme.Set("name", "theme-color")
w.head.Call("appendChild", theme)
}
rgba := f32color.NRGBAToRGBA(c)
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
}
func (w *window) requestRedraw() {
select {
case w.chanRedraw <- struct{}{}:
default:
}
}
func osMain() {
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 = key.NameSpace
case "F1":
n = key.NameF1
case "F2":
n = key.NameF2
case "F3":
n = key.NameF3
case "F4":
n = key.NameF4
case "F5":
n = key.NameF5
case "F6":
n = key.NameF6
case "F7":
n = key.NameF7
case "F8":
n = key.NameF8
case "F9":
n = key.NameF9
case "F10":
n = key.NameF10
case "F11":
n = key.NameF11
case "F12":
n = key.NameF12
case "Control":
n = key.NameCtrl
case "Shift":
n = key.NameShift
case "Alt":
n = key.NameAlt
case "OS":
n = key.NameSuper
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
}
func (_ ViewEvent) ImplementsEvent() {}

994
vendor/gioui.org/app/os_macos.go generated vendored
View File

@@ -1,994 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin && !ios
// +build darwin,!ios
package app
import (
"errors"
"image"
"runtime"
"time"
"unicode"
"unicode/utf8"
"gioui.org/internal/f32"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/unit"
_ "gioui.org/internal/cocoainit"
)
/*
#cgo CFLAGS: -Werror -Wno-deprecated-declarations -fobjc-arc -x objective-c
#cgo LDFLAGS: -framework AppKit -framework QuartzCore
#include <AppKit/AppKit.h>
#define MOUSE_MOVE 1
#define MOUSE_UP 2
#define MOUSE_DOWN 3
#define MOUSE_SCROLL 4
__attribute__ ((visibility ("hidden"))) void gio_main(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
static void writeClipboard(CFTypeRef str) {
@autoreleasepool {
NSString *s = (__bridge NSString *)str;
NSPasteboard *p = NSPasteboard.generalPasteboard;
[p declareTypes:@[NSPasteboardTypeString] owner:nil];
[p setString:s forType:NSPasteboardTypeString];
}
}
static CFTypeRef readClipboard(void) {
@autoreleasepool {
NSPasteboard *p = NSPasteboard.generalPasteboard;
NSString *content = [p stringForType:NSPasteboardTypeString];
return (__bridge_retained CFTypeRef)content;
}
}
static CGFloat viewHeight(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return [view bounds].size.height;
}
static CGFloat viewWidth(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return [view bounds].size.width;
}
static CGFloat getScreenBackingScale(void) {
return [NSScreen.mainScreen backingScaleFactor];
}
static CGFloat getViewBackingScale(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return [view.window backingScaleFactor];
}
static void setNeedsDisplay(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
[view setNeedsDisplay:YES];
}
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
NSWindow *window = (__bridge NSWindow *)windowRef;
return [window cascadeTopLeftFromPoint:topLeft];
}
static void makeKeyAndOrderFront(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil];
}
static void toggleFullScreen(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
[window toggleFullScreen:nil];
}
static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
return [window styleMask];
}
static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
NSWindow *window = (__bridge NSWindow *)windowRef;
window.styleMask = mask;
}
static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
NSWindow *window = (__bridge NSWindow *)windowRef;
window.titleVisibility = state;
}
static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
NSWindow *window = (__bridge NSWindow *)windowRef;
window.titlebarAppearsTransparent = (BOOL)transparent;
}
static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
NSWindow *window = (__bridge NSWindow *)windowRef;
[window standardWindowButton:btn].hidden = (BOOL)hide;
}
static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
NSWindow *window = (__bridge NSWindow *)windowRef;
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
}
static void closeWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef;
[window performClose:nil];
}
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
NSWindow* window = (__bridge NSWindow *)windowRef;
NSSize size = NSMakeSize(width, height);
[window setContentSize:size];
}
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
NSWindow* window = (__bridge NSWindow *)windowRef;
window.contentMinSize = NSMakeSize(width, height);
}
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
NSWindow* window = (__bridge NSWindow *)windowRef;
window.contentMaxSize = NSMakeSize(width, height);
}
static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
NSWindow* window = (__bridge NSWindow *)windowRef;
NSRect r = NSMakeRect(x, y, w, h);
[window setFrame:r display:YES];
}
static void hideWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef;
[window miniaturize:window];
}
static void unhideWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef;
[window deminiaturize:window];
}
static NSRect getScreenFrame(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef;
return [[window screen] frame];
}
static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
window.title = (__bridge NSString *)titleRef;
}
static int isWindowZoomed(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
return window.zoomed ? 1 : 0;
}
static void zoomWindow(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef;
[window zoom:nil];
}
static CFTypeRef layerForView(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return (__bridge CFTypeRef)view.layer;
}
static CFTypeRef windowForView(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return (__bridge CFTypeRef)view.window;
}
static void raiseWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil];
}
static CFTypeRef createInputContext(CFTypeRef clientRef) {
@autoreleasepool {
id<NSTextInputClient> client = (__bridge id<NSTextInputClient>)clientRef;
NSTextInputContext *ctx = [[NSTextInputContext alloc] initWithClient:client];
return CFBridgingRetain(ctx);
}
}
static void discardMarkedText(CFTypeRef viewRef) {
@autoreleasepool {
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
if (view == [ctx client]) {
[ctx discardMarkedText];
}
}
}
static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
@autoreleasepool {
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
if (view == [ctx client]) {
[ctx invalidateCharacterCoordinates];
}
}
}
*/
import "C"
func init() {
// Darwin requires that UI operations happen on the main thread only.
runtime.LockOSThread()
}
// ViewEvent notified the client of changes to the window AppKit handles.
// The handles are retained until another ViewEvent is sent.
type ViewEvent struct {
// View is a CFTypeRef for the NSView for the window.
View uintptr
// Layer is a CFTypeRef of the CALayer of View.
Layer uintptr
}
type window struct {
view C.CFTypeRef
w *callbacks
stage system.Stage
displayLink *displayLink
// redraw is a single entry channel for making sure only one
// display link redraw request is in flight.
redraw chan struct{}
cursor pointer.Cursor
pointerBtns pointer.Buttons
scale float32
config Config
}
// viewMap is the mapping from Cocoa NSViews to Go windows.
var viewMap = make(map[C.CFTypeRef]*window)
// 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 lookupView, 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() {
cstr := C.readClipboard()
defer C.CFRelease(cstr)
content := nsstringToString(cstr)
w.w.Event(clipboard.Event{Text: content})
}
func (w *window) WriteClipboard(s string) {
cstr := stringToNSString(s)
defer C.CFRelease(cstr)
C.writeClipboard(cstr)
}
func (w *window) updateWindowMode() {
style := int(C.getWindowStyleMask(C.windowForView(w.view)))
if style&C.NSWindowStyleMaskFullScreen != 0 {
w.config.Mode = Fullscreen
} else {
w.config.Mode = Windowed
}
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
}
func (w *window) Configure(options []Option) {
screenScale := float32(C.getScreenBackingScale())
cfg := configFor(screenScale)
prev := w.config
w.updateWindowMode()
cnf := w.config
cnf.apply(cfg, options)
window := C.windowForView(w.view)
switch cnf.Mode {
case Fullscreen:
switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window)
fallthrough
default:
w.config.Mode = Fullscreen
C.toggleFullScreen(window)
}
case Minimized:
switch prev.Mode {
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
C.hideWindow(window)
}
case Maximized:
switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window)
fallthrough
default:
w.config.Mode = Maximized
w.setTitle(prev, cnf)
if C.isWindowZoomed(window) == 0 {
C.zoomWindow(window)
}
}
case Windowed:
switch prev.Mode {
case Fullscreen:
C.toggleFullScreen(window)
case Minimized:
C.unhideWindow(window)
case Maximized:
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
}
w.config.Mode = Windowed
w.setTitle(prev, cnf)
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
cnf.Size = cnf.Size.Div(int(screenScale))
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
}
if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
}
if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
}
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
mask := C.getWindowStyleMask(window)
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
style = C.NSWindowStyleMaskFullSizeContentView
mask &^= style
barTrans := C.int(C.NO)
titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible)
if !cnf.Decorated {
mask |= style
barTrans = C.YES
titleVis = C.NSWindowTitleHidden
}
C.setWindowTitlebarAppearsTransparent(window, barTrans)
C.setWindowTitleVisibility(window, titleVis)
C.setWindowStyleMask(window, mask)
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
}
w.w.Event(ConfigEvent{Config: w.config})
}
func (w *window) setTitle(prev, cnf Config) {
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
title := stringToNSString(cnf.Title)
defer C.CFRelease(title)
C.setTitle(C.windowForView(w.view), title)
}
}
func (w *window) Perform(acts system.Action) {
window := C.windowForView(w.view)
walkActions(acts, func(a system.Action) {
switch a {
case system.ActionCenter:
r := C.getScreenFrame(window) // the screen size of the window
screenScale := float32(C.getScreenBackingScale())
sz := w.config.Size.Div(int(screenScale))
x := (int(r.size.width) - sz.X) / 2
y := (int(r.size.height) - sz.Y) / 2
C.setScreenFrame(window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
case system.ActionRaise:
C.raiseWindow(window)
}
})
if acts&system.ActionClose != 0 {
C.closeWindow(window)
}
}
func (w *window) SetCursor(cursor pointer.Cursor) {
w.cursor = windowSetCursor(w.cursor, cursor)
}
func (w *window) EditorStateChanged(old, new editorState) {
if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
C.discardMarkedText(w.view)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
}
if old.Selection.Caret != new.Selection.Caret || old.Selection.Transform != new.Selection.Transform {
C.invalidateCharacterCoordinates(w.view)
}
}
func (w *window) ShowTextInput(show bool) {}
func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) SetAnimating(anim bool) {
if anim {
w.displayLink.Start()
} else {
w.displayLink.Stop()
}
}
func (w *window) runOnMain(f func()) {
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 {
f()
}
})
}
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, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
str := nsstringToString(cstr)
kmods := convertMods(mods)
ks := key.Release
if keyDown {
ks = key.Press
}
w := mustView(view)
for _, k := range str {
if n, ok := convertKey(k); ok {
w.w.Event(key.Event{
Name: n,
Modifiers: kmods,
State: ks,
})
}
}
}
//export gio_onText
func gio_onText(view, cstr C.CFTypeRef) {
str := nsstringToString(cstr)
w := mustView(view)
w.w.EditorInsert(str)
}
//export gio_onMouse
func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
w := mustView(view)
t := time.Duration(float64(ti)*float64(time.Second) + .5)
xf, yf := float32(x)*w.scale, float32(y)*w.scale
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
pos := f32.Point{X: xf, Y: yf}
var btn pointer.Buttons
switch cbtn {
case 0:
btn = pointer.ButtonPrimary
case 1:
btn = pointer.ButtonSecondary
case 2:
btn = pointer.ButtonTertiary
}
var typ pointer.Type
switch cdir {
case C.MOUSE_MOVE:
typ = pointer.Move
case C.MOUSE_UP:
typ = pointer.Release
w.pointerBtns &^= btn
case C.MOUSE_DOWN:
typ = pointer.Press
w.pointerBtns |= btn
act, ok := w.w.ActionAt(pos)
if ok && w.config.Mode != Fullscreen {
switch act {
case system.ActionMove:
C.performWindowDragWithEvent(C.windowForView(w.view), evt)
return
}
}
case C.MOUSE_SCROLL:
typ = pointer.Scroll
default:
panic("invalid direction")
}
w.w.Event(pointer.Event{
Type: typ,
Source: pointer.Mouse,
Time: t,
Buttons: w.pointerBtns,
Position: pos,
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.int) {
w := mustView(view)
w.w.Event(key.FocusEvent{Focus: focus == 1})
if w.stage >= system.StageInactive {
if focus == 0 {
w.setStage(system.StageInactive)
} else {
w.setStage(system.StageRunning)
}
}
w.SetCursor(w.cursor)
}
//export gio_onChangeScreen
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
w := mustView(view)
w.displayLink.SetDisplayID(did)
C.setNeedsDisplay(w.view)
}
//export gio_hasMarkedText
func gio_hasMarkedText(view C.CFTypeRef) C.int {
w := mustView(view)
state := w.w.EditorState()
if state.compose.Start != -1 {
return 1
}
return 0
}
//export gio_markedRange
func gio_markedRange(view C.CFTypeRef) C.NSRange {
w := mustView(view)
state := w.w.EditorState()
rng := state.compose
start, end := rng.Start, rng.End
if start == -1 {
return C.NSMakeRange(C.NSNotFound, 0)
}
u16start := state.UTF16Index(start)
return C.NSMakeRange(
C.NSUInteger(u16start),
C.NSUInteger(state.UTF16Index(end)-u16start),
)
}
//export gio_selectedRange
func gio_selectedRange(view C.CFTypeRef) C.NSRange {
w := mustView(view)
state := w.w.EditorState()
rng := state.Selection
start, end := rng.Start, rng.End
if start > end {
start, end = end, start
}
u16start := state.UTF16Index(start)
return C.NSMakeRange(
C.NSUInteger(u16start),
C.NSUInteger(state.UTF16Index(end)-u16start),
)
}
//export gio_unmarkText
func gio_unmarkText(view C.CFTypeRef) {
w := mustView(view)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
}
//export gio_setMarkedText
func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
w := mustView(view)
str := nsstringToString(cstr)
state := w.w.EditorState()
rng := state.compose
if rng.Start == -1 {
rng = state.Selection.Range
}
if replaceRange.location != C.NSNotFound {
// replaceRange is relative to marked (or selected) text.
offset := state.UTF16Index(rng.Start)
start := state.RunesIndex(int(replaceRange.location) + offset)
end := state.RunesIndex(int(replaceRange.location+replaceRange.length) + offset)
rng = key.Range{
Start: start,
End: end,
}
}
w.w.EditorReplace(rng, str)
comp := key.Range{
Start: rng.Start,
End: rng.Start + utf8.RuneCountInString(str),
}
w.w.SetComposingRegion(comp)
sel := key.Range{Start: comp.End, End: comp.End}
if selRange.location != C.NSNotFound {
// selRange is relative to inserted text.
offset := state.UTF16Index(rng.Start)
start := state.RunesIndex(int(selRange.location) + offset)
end := state.RunesIndex(int(selRange.location+selRange.length) + offset)
sel = key.Range{
Start: start,
End: end,
}
}
w.w.SetEditorSelection(sel)
}
//export gio_substringForProposedRange
func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
w := mustView(view)
state := w.w.EditorState()
start, end := state.Snippet.Start, state.Snippet.End
if start > end {
start, end = end, start
}
rng := key.Range{
Start: state.RunesIndex(int(crng.location)),
End: state.RunesIndex(int(crng.location + crng.length)),
}
if rng.Start < start || end < rng.End {
w.w.SetEditorSnippet(rng)
}
u16start := state.UTF16Index(start)
actual.location = C.NSUInteger(u16start)
actual.length = C.NSUInteger(state.UTF16Index(end) - u16start)
return stringToNSString(state.Snippet.Text)
}
//export gio_insertText
func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
w := mustView(view)
state := w.w.EditorState()
rng := state.compose
if rng.Start == -1 {
rng = state.Selection.Range
}
if crng.location != C.NSNotFound {
rng = key.Range{
Start: state.RunesIndex(int(crng.location)),
End: state.RunesIndex(int(crng.location + crng.length)),
}
}
str := nsstringToString(cstr)
w.w.EditorReplace(rng, str)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
start := rng.Start
if rng.End < start {
start = rng.End
}
pos := start + utf8.RuneCountInString(str)
w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
}
//export gio_characterIndexForPoint
func gio_characterIndexForPoint(view C.CFTypeRef, p C.NSPoint) C.NSUInteger {
return C.NSNotFound
}
//export gio_firstRectForCharacterRange
func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
w := mustView(view)
state := w.w.EditorState()
sel := state.Selection
u16start := state.UTF16Index(sel.Start)
actual.location = C.NSUInteger(u16start)
actual.length = 0
// Transform to NSView local coordinates (lower left origin, undo backing scale).
scale := 1. / float32(C.getViewBackingScale(w.view))
height := float32(C.viewHeight(w.view))
local := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height))
t := local.Mul(sel.Transform)
bounds := f32.Rectangle{
Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))),
Max: t.Transform(sel.Pos.Add(f32.Pt(0, sel.Descent))),
}.Canon()
sz := bounds.Size()
return C.NSMakeRect(
C.CGFloat(bounds.Min.X), C.CGFloat(bounds.Min.Y),
C.CGFloat(sz.X), C.CGFloat(sz.Y),
)
}
func (w *window) draw() {
select {
case <-w.redraw:
default:
}
w.scale = float32(C.getViewBackingScale(w.view))
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
sz := image.Point{
X: int(wf*w.scale + .5),
Y: int(hf*w.scale + .5),
}
if sz != w.config.Size {
w.config.Size = sz
w.w.Event(ConfigEvent{Config: w.config})
}
if sz.X == 0 || sz.Y == 0 {
return
}
cfg := configFor(w.scale)
w.setStage(system.StageRunning)
w.w.Event(frameEvent{
FrameEvent: system.FrameEvent{
Now: time.Now(),
Size: w.config.Size,
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.w.Event(ViewEvent{})
w.w.Event(system.DestroyEvent{})
w.displayLink.Close()
w.displayLink = nil
deleteView(view)
C.CFRelease(w.view)
w.view = 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_onFullscreen
func gio_onFullscreen(view C.CFTypeRef) {
w := mustView(view)
w.config.Mode = Fullscreen
w.w.Event(ConfigEvent{Config: w.config})
}
//export gio_onWindowed
func gio_onWindowed(view C.CFTypeRef) {
w := mustView(view)
w.config.Mode = Windowed
w.w.Event(ConfigEvent{Config: w.config})
}
//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, options []Option) error {
<-launched
errch := make(chan error)
runOnMain(func() {
w, err := newOSWindow()
if err != nil {
errch <- err
return
}
errch <- nil
w.w = win
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
w.updateWindowMode()
win.SetDriver(w)
w.Configure(options)
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.cascadeTopLeftFromPoint(window, nextTopLeft)
}
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
// makeKeyAndOrderFront assumes ownership of our window reference.
C.makeKeyAndOrderFront(window)
layer := C.layerForView(w.view)
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
})
return <-errch
}
func newOSWindow() (*window, error) {
view := C.gio_createView()
if view == 0 {
return nil, errors.New("newOSWindows: failed to create view")
}
scale := float32(C.getViewBackingScale(view))
w := &window{
view: view,
scale: scale,
redraw: make(chan struct{}, 1),
}
dl, err := NewDisplayLink(func() {
select {
case w.redraw <- struct{}{}:
default:
return
}
w.runOnMain(func() {
C.setNeedsDisplay(w.view)
})
})
w.displayLink = dl
if err != nil {
C.CFRelease(view)
return nil, err
}
insertView(view, w)
return w, nil
}
func osMain() {
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 = key.NameF1
case C.NSF2FunctionKey:
n = key.NameF2
case C.NSF3FunctionKey:
n = key.NameF3
case C.NSF4FunctionKey:
n = key.NameF4
case C.NSF5FunctionKey:
n = key.NameF5
case C.NSF6FunctionKey:
n = key.NameF6
case C.NSF7FunctionKey:
n = key.NameF7
case C.NSF8FunctionKey:
n = key.NameF8
case C.NSF9FunctionKey:
n = key.NameF9
case C.NSF10FunctionKey:
n = key.NameF10
case C.NSF11FunctionKey:
n = key.NameF11
case C.NSF12FunctionKey:
n = key.NameF12
case 0x09, 0x19:
n = key.NameTab
case 0x20:
n = key.NameSpace
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
}
func (_ ViewEvent) ImplementsEvent() {}

421
vendor/gioui.org/app/os_macos.m generated vendored
View File

@@ -1,421 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// +build darwin,!ios
#import <AppKit/AppKit.h>
#include "_cgo_export.h"
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
@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)windowWillEnterFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
}
- (void)windowWillExitFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
gio_onWindowed((__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, 1);
}
- (void)windowDidResignKey:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
}
@end
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;
}
// Origin is in the lower left corner. Convert to upper left.
CGFloat height = view.bounds.size.height;
gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
}
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
@end
@implementation GioView
- (void)setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize];
[self setNeedsDisplay:YES];
}
// drawRect is called when OpenGL is used, displayLayer otherwise.
// Don't know why.
- (void)drawRect:(NSRect)r {
gio_onDraw((__bridge CFTypeRef)self);
}
- (void)displayLayer:(CALayer *)layer {
layer.contentsScale = self.window.backingScaleFactor;
gio_onDraw((__bridge CFTypeRef)self);
}
- (CALayer *)makeBackingLayer {
CALayer *layer = gio_layerFactory();
layer.delegate = self;
return layer;
}
- (void)viewDidMoveToWindow {
if (self.window == nil) {
gio_onClose((__bridge CFTypeRef)self);
}
}
- (void)mouseDown:(NSEvent *)event {
handleMouse(self, event, MOUSE_DOWN, 0, 0);
}
- (void)mouseUp:(NSEvent *)event {
handleMouse(self, event, MOUSE_UP, 0, 0);
}
- (void)rightMouseDown:(NSEvent *)event {
handleMouse(self, event, MOUSE_DOWN, 0, 0);
}
- (void)rightMouseUp:(NSEvent *)event {
handleMouse(self, event, MOUSE_UP, 0, 0);
}
- (void)otherMouseDown:(NSEvent *)event {
handleMouse(self, event, MOUSE_DOWN, 0, 0);
}
- (void)otherMouseUp:(NSEvent *)event {
handleMouse(self, event, MOUSE_UP, 0, 0);
}
- (void)mouseMoved:(NSEvent *)event {
handleMouse(self, event, MOUSE_MOVE, 0, 0);
}
- (void)mouseDragged:(NSEvent *)event {
handleMouse(self, event, MOUSE_MOVE, 0, 0);
}
- (void)rightMouseDragged:(NSEvent *)event {
handleMouse(self, event, MOUSE_MOVE, 0, 0);
}
- (void)otherMouseDragged:(NSEvent *)event {
handleMouse(self, event, MOUSE_MOVE, 0, 0);
}
- (void)scrollWheel:(NSEvent *)event {
CGFloat dx = -event.scrollingDeltaX;
CGFloat dy = -event.scrollingDeltaY;
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
}
- (void)keyDown:(NSEvent *)event {
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
}
- (void)keyUp:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
}
- (void)insertText:(id)string {
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
}
- (void)doCommandBySelector:(SEL)sel {
// Don't pass commands up the responder chain.
// They will end up in a beep.
}
- (BOOL)hasMarkedText {
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
return res ? YES : NO;
}
- (NSRange)markedRange {
return gio_markedRange((__bridge CFTypeRef)self);
}
- (NSRange)selectedRange {
return gio_selectedRange((__bridge CFTypeRef)self);
}
- (void)unmarkText {
gio_unmarkText((__bridge CFTypeRef)self);
}
- (void)setMarkedText:(id)string
selectedRange:(NSRange)selRange
replacementRange:(NSRange)replaceRange {
NSString *str;
// string is either an NSAttributedString or an NSString.
if ([string isKindOfClass:[NSAttributedString class]]) {
str = [string string];
} else {
str = string;
}
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
}
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
return nil;
}
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
actualRange:(NSRangePointer)actualRange {
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
return [[NSAttributedString alloc] initWithString:str attributes:nil];
}
- (void)insertText:(id)string
replacementRange:(NSRange)replaceRange {
NSString *str;
// string is either an NSAttributedString or an NSString.
if ([string isKindOfClass:[NSAttributedString class]]) {
str = [string string];
} else {
str = string;
}
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
}
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
}
- (NSRect)firstRectForCharacterRange:(NSRange)rng
actualRange:(NSRangePointer)actual {
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
r = [self convertRect:r toView:nil];
return [[self window] convertRectToScreen:r];
}
@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;
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
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);
}
void gio_hideCursor() {
@autoreleasepool {
[NSCursor hide];
}
}
void gio_showCursor() {
@autoreleasepool {
[NSCursor unhide];
}
}
// some cursors are not public, this tries to use a private cursor
// and uses fallback when the use of private cursor fails.
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
if ([NSCursor respondsToSelector:cursorName]) {
id object = [NSCursor performSelector:cursorName];
if ([object isKindOfClass:[NSCursor class]]) {
[(NSCursor*)object set];
return;
}
}
[fallback set];
}
void gio_setCursor(NSUInteger curID) {
@autoreleasepool {
switch (curID) {
case 0: // pointer.CursorDefault
[NSCursor.arrowCursor set];
break;
// case 1: // pointer.CursorNone
case 2: // pointer.CursorText
[NSCursor.IBeamCursor set];
break;
case 3: // pointer.CursorVerticalText
[NSCursor.IBeamCursorForVerticalLayout set];
break;
case 4: // pointer.CursorPointer
[NSCursor.pointingHandCursor set];
break;
case 5: // pointer.CursorCrosshair
[NSCursor.crosshairCursor set];
break;
case 6: // pointer.CursorAllScroll
// For some reason, using _moveCursor fails on Monterey.
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
[NSCursor.arrowCursor set];
break;
case 7: // pointer.CursorColResize
[NSCursor.resizeLeftRightCursor set];
break;
case 8: // pointer.CursorRowResize
[NSCursor.resizeUpDownCursor set];
break;
case 9: // pointer.CursorGrab
// [NSCursor.openHandCursor set];
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
break;
case 10: // pointer.CursorGrabbing
// [NSCursor.closedHandCursor set];
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
break;
case 11: // pointer.CursorNotAllowed
[NSCursor.operationNotAllowedCursor set];
break;
case 12: // pointer.CursorWait
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
break;
case 13: // pointer.CursorProgress
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
break;
case 14: // pointer.CursorNorthWestResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
break;
case 15: // pointer.CursorNorthEastResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
break;
case 16: // pointer.CursorSouthWestResize
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
break;
case 17: // pointer.CursorSouthEastResize
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
break;
case 18: // pointer.CursorNorthSouthResize
[NSCursor.resizeUpDownCursor set];
break;
case 19: // pointer.CursorEastWestResize
[NSCursor.resizeLeftRightCursor set];
break;
case 20: // pointer.CursorWestResize
[NSCursor.resizeLeftCursor set];
break;
case 21: // pointer.CursorEastResize
[NSCursor.resizeRightCursor set];
break;
case 22: // pointer.CursorNorthResize
[NSCursor.resizeUpCursor set];
break;
case 23: // pointer.CursorSouthResize
[NSCursor.resizeDownCursor set];
break;
case 24: // pointer.CursorNorthEastSouthWestResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
break;
case 25: // pointer.CursorNorthWestSouthEastResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
break;
default:
[NSCursor.arrowCursor set];
break;
}
}
}
CFTypeRef gio_createWindow(CFTypeRef viewRef, 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];
NSView *view = (__bridge NSView *)viewRef;
[window setContentView:view];
[window makeFirstResponder:view];
window.delegate = globalWindowDel;
return (__bridge_retained CFTypeRef)window;
}
}
CFTypeRef gio_createView(void) {
@autoreleasepool {
NSRect frame = NSMakeRect(0, 0, 0, 0);
GioView* view = [[GioView alloc] initWithFrame:frame];
view.wantsLayer = YES;
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
return CFBridgingRetain(view);
}
}
@implementation GioAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
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];
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];
}
}

100
vendor/gioui.org/app/os_unix.go generated vendored
View File

@@ -1,100 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build (linux && !android) || freebsd || openbsd
// +build linux,!android freebsd openbsd
package app
import (
"errors"
"unsafe"
"gioui.org/io/pointer"
)
// ViewEvent provides handles to the underlying window objects for the
// current display protocol.
type ViewEvent interface {
implementsViewEvent()
ImplementsEvent()
}
type X11ViewEvent struct {
// Display is a pointer to the X11 Display created by XOpenDisplay.
Display unsafe.Pointer
// Window is the X11 window ID as returned by XCreateWindow.
Window uintptr
}
func (X11ViewEvent) implementsViewEvent() {}
func (X11ViewEvent) ImplementsEvent() {}
type WaylandViewEvent struct {
// Display is the *wl_display returned by wl_display_connect.
Display unsafe.Pointer
// Surface is the *wl_surface returned by wl_compositor_create_surface.
Surface unsafe.Pointer
}
func (WaylandViewEvent) implementsViewEvent() {}
func (WaylandViewEvent) ImplementsEvent() {}
func osMain() {
select {}
}
type windowDriver func(*callbacks, []Option) error
// 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 windowDriver
func newWindow(window *callbacks, options []Option) error {
var errFirst error
for _, d := range []windowDriver{wlDriver, x11Driver} {
if d == nil {
continue
}
err := d(window, options)
if err == nil {
return nil
}
if errFirst == nil {
errFirst = err
}
}
if errFirst != nil {
return errFirst
}
return errors.New("app: no window driver available")
}
// xCursor contains mapping from pointer.Cursor to XCursor.
var xCursor = [...]string{
pointer.CursorDefault: "left_ptr",
pointer.CursorNone: "",
pointer.CursorText: "xterm",
pointer.CursorVerticalText: "vertical-text",
pointer.CursorPointer: "hand2",
pointer.CursorCrosshair: "crosshair",
pointer.CursorAllScroll: "fleur",
pointer.CursorColResize: "sb_h_double_arrow",
pointer.CursorRowResize: "sb_v_double_arrow",
pointer.CursorGrab: "hand1",
pointer.CursorGrabbing: "move",
pointer.CursorNotAllowed: "crossed_circle",
pointer.CursorWait: "watch",
pointer.CursorProgress: "left_ptr_watch",
pointer.CursorNorthWestResize: "top_left_corner",
pointer.CursorNorthEastResize: "top_right_corner",
pointer.CursorSouthWestResize: "bottom_left_corner",
pointer.CursorSouthEastResize: "bottom_right_corner",
pointer.CursorNorthSouthResize: "sb_v_double_arrow",
pointer.CursorEastWestResize: "sb_h_double_arrow",
pointer.CursorWestResize: "left_side",
pointer.CursorEastResize: "right_side",
pointer.CursorNorthResize: "top_side",
pointer.CursorSouthResize: "bottom_side",
pointer.CursorNorthEastSouthWestResize: "fd_double_arrow",
pointer.CursorNorthWestSouthEastResize: "bd_double_arrow",
}

124
vendor/gioui.org/app/os_wayland.c generated vendored
View File

@@ -1,124 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build ((linux && !android) || freebsd) && !nowayland
// +build linux,!android freebsd
// +build !nowayland
#include <wayland-client.h>
#include "wayland_xdg_shell.h"
#include "wayland_xdg_decoration.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,
};
const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
.configure = gio_onToplevelDecorationConfigure,
};
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,
};

1884
vendor/gioui.org/app/os_wayland.go generated vendored

File diff suppressed because it is too large Load Diff

970
vendor/gioui.org/app/os_windows.go generated vendored
View File

@@ -1,970 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"errors"
"fmt"
"image"
"runtime"
"sort"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
"unsafe"
syscall "golang.org/x/sys/windows"
"gioui.org/app/internal/windows"
"gioui.org/unit"
gowindows "golang.org/x/sys/windows"
"gioui.org/f32"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
)
type ViewEvent struct {
HWND uintptr
}
type window struct {
hwnd syscall.Handle
hdc syscall.Handle
w *callbacks
stage system.Stage
pointerBtns pointer.Buttons
// cursorIn tracks whether the cursor was inside the window according
// to the most recent WM_SETCURSOR.
cursorIn bool
cursor syscall.Handle
// placement saves the previous window position when in full screen mode.
placement *windows.WindowPlacement
animating bool
focused bool
borderSize image.Point
config Config
}
const _WM_WAKEUP = windows.WM_USER + iota
type gpuAPI struct {
priority int
initializer func(w *window) (context, error)
}
// drivers is the list of potential Context implementations.
var drivers []gpuAPI
// winMap maps win32 HWNDs to *windows.
var winMap sync.Map
// iconID is the ID of the icon in the resource file.
const iconID = 1
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 osMain() {
select {}
}
func newWindow(window *callbacks, options []Option) error {
cerr := make(chan error)
go func() {
// GetMessage and PeekMessage can filter on a window HWND, but
// then thread-specific messages such as WM_QUIT are ignored.
// Instead lock the thread so window messages arrive through
// unfiltered GetMessage calls.
runtime.LockOSThread()
w, err := createNativeWindow()
if err != nil {
cerr <- err
return
}
cerr <- nil
winMap.Store(w.hwnd, w)
defer winMap.Delete(w.hwnd)
w.w = window
w.w.SetDriver(w)
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
w.Configure(options)
windows.SetForegroundWindow(w.hwnd)
windows.SetFocus(w.hwnd)
// Since the window class for the cursor is null,
// set it here to show the cursor.
w.SetCursor(pointer.CursorDefault)
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
c, err := windows.LoadCursor(windows.IDC_ARROW)
if err != nil {
return err
}
resources.cursor = c
icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
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,
HIcon: icon,
LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
}
cls, err := windows.RegisterClassEx(&wcls)
if err != nil {
return err
}
resources.class = cls
return nil
}
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
func createNativeWindow() (*window, error) {
var resErr error
resources.once.Do(func() {
resErr = initResources()
})
if resErr != nil {
return nil, resErr
}
const dwStyle = windows.WS_OVERLAPPEDWINDOW
hwnd, err := windows.CreateWindowEx(
dwExStyle,
resources.class,
"",
dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
0,
0,
resources.handle,
0)
if err != nil {
return nil, err
}
w := &window{
hwnd: hwnd,
}
w.hdc, err = windows.GetDC(hwnd)
if err != nil {
return nil, err
}
return w, nil
}
// update() handles changes done by the user, and updates the configuration.
// It reads the window style and size/position and updates w.config.
// If anything has changed it emits a ConfigEvent to notify the application.
func (w *window) update() {
cr := windows.GetClientRect(w.hwnd)
w.config.Size = image.Point{
X: int(cr.Right - cr.Left),
Y: int(cr.Bottom - cr.Top),
}
w.borderSize = image.Pt(
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
)
w.w.Event(ConfigEvent{Config: w.config})
}
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 windows.TRUE
}
fallthrough
case windows.WM_CHAR:
if r := rune(wParam); unicode.IsPrint(r) {
w.w.EditorInsert(string(r))
}
// The message is processed.
return windows.TRUE
case windows.WM_DPICHANGED:
// Let Windows know we're prepared for runtime DPI changes.
return windows.TRUE
case windows.WM_ERASEBKGND:
// Avoid flickering between GPU content and background color.
return windows.TRUE
case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
if n, ok := convertKeyCode(wParam); ok {
e := key.Event{
Name: n,
Modifiers: getModifiers(),
State: key.Press,
}
if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
e.State = key.Release
}
w.w.Event(e)
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
// such as cmd.exe and graphical debuggers also reserve F10.
return 0
}
}
case windows.WM_LBUTTONDOWN:
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
case windows.WM_LBUTTONUP:
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
case windows.WM_RBUTTONDOWN:
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
case windows.WM_RBUTTONUP:
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
case windows.WM_MBUTTONDOWN:
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
case windows.WM_MBUTTONUP:
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
case windows.WM_CANCELMODE:
w.w.Event(pointer.Event{
Type: pointer.Cancel,
})
case windows.WM_SETFOCUS:
w.focused = true
w.w.Event(key.FocusEvent{Focus: true})
case windows.WM_KILLFOCUS:
w.focused = false
w.w.Event(key.FocusEvent{Focus: false})
case windows.WM_NCACTIVATE:
if w.stage >= system.StageInactive {
if wParam == windows.TRUE {
w.setStage(system.StageRunning)
} else {
w.setStage(system.StageInactive)
}
}
case windows.WM_NCHITTEST:
if w.config.Decorated {
// Let the system handle it.
break
}
x, y := coordsFromlParam(lParam)
np := windows.Point{X: int32(x), Y: int32(y)}
windows.ScreenToClient(w.hwnd, &np)
return w.hitTest(int(np.X), int(np.Y))
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(),
Modifiers: getModifiers(),
})
case windows.WM_MOUSEWHEEL:
w.scrollEvent(wParam, lParam, false, getModifiers())
case windows.WM_MOUSEHWHEEL:
w.scrollEvent(wParam, lParam, true, getModifiers())
case windows.WM_DESTROY:
w.w.Event(ViewEvent{})
w.w.Event(system.DestroyEvent{})
if w.hdc != 0 {
windows.ReleaseDC(w.hdc)
w.hdc = 0
}
// The system destroys the HWND for us.
w.hwnd = 0
windows.PostQuitMessage(0)
case windows.WM_NCCALCSIZE:
if w.config.Decorated {
// Let Windows handle decorations.
break
}
// No client areas; we draw decorations ourselves.
if wParam != 1 {
return 0
}
// lParam contains an NCCALCSIZE_PARAMS for us to adjust.
place := windows.GetWindowPlacement(w.hwnd)
if !place.IsMaximized() {
// Nothing do adjust.
return 0
}
// Adjust window position to avoid the extra padding in maximized
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
mi := windows.GetMonitorInfo(w.hwnd)
szp.Rgrc[0] = mi.WorkArea
return 0
case windows.WM_PAINT:
w.draw(true)
case windows.WM_SIZE:
w.update()
switch wParam {
case windows.SIZE_MINIMIZED:
w.config.Mode = Minimized
w.setStage(system.StagePaused)
case windows.SIZE_MAXIMIZED:
w.config.Mode = Maximized
w.setStage(system.StageRunning)
case windows.SIZE_RESTORED:
if w.config.Mode != Fullscreen {
w.config.Mode = Windowed
}
w.setStage(system.StageRunning)
}
case windows.WM_GETMINMAXINFO:
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
var bw, bh int32
if w.config.Decorated {
r := windows.GetWindowRect(w.hwnd)
cr := windows.GetClientRect(w.hwnd)
bw = r.Right - r.Left - (cr.Right - cr.Left)
bh = r.Bottom - r.Top - (cr.Bottom - cr.Top)
}
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
mm.PtMinTrackSize = windows.Point{
X: int32(p.X) + bw,
Y: int32(p.Y) + bh,
}
}
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
mm.PtMaxTrackSize = windows.Point{
X: int32(p.X) + bw,
Y: int32(p.Y) + bh,
}
}
return 0
case windows.WM_SETCURSOR:
w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
if w.cursorIn {
windows.SetCursor(w.cursor)
return windows.TRUE
}
case _WM_WAKEUP:
w.w.Event(wakeupEvent{})
case windows.WM_IME_STARTCOMPOSITION:
imc := windows.ImmGetContext(w.hwnd)
if imc == 0 {
return windows.TRUE
}
defer windows.ImmReleaseContext(w.hwnd, imc)
sel := w.w.EditorState().Selection
caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
case windows.WM_IME_COMPOSITION:
imc := windows.ImmGetContext(w.hwnd)
if imc == 0 {
return windows.TRUE
}
defer windows.ImmReleaseContext(w.hwnd, imc)
state := w.w.EditorState()
rng := state.compose
if rng.Start == -1 {
rng = state.Selection.Range
}
if rng.Start > rng.End {
rng.Start, rng.End = rng.End, rng.Start
}
var replacement string
switch {
case lParam&windows.GCS_RESULTSTR != 0:
replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
case lParam&windows.GCS_COMPSTR != 0:
replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
}
end := rng.Start + utf8.RuneCountInString(replacement)
w.w.EditorReplace(rng, replacement)
state = w.w.EditorState()
comp := key.Range{
Start: rng.Start,
End: end,
}
if lParam&windows.GCS_DELTASTART != 0 {
start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
}
w.w.SetComposingRegion(comp)
pos := end
if lParam&windows.GCS_CURSORPOS != 0 {
rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
}
w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
return windows.TRUE
case windows.WM_IME_ENDCOMPOSITION:
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
return windows.TRUE
}
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
}
// hitTest returns the non-client area hit by the point, needed to
// process WM_NCHITTEST.
func (w *window) hitTest(x, y int) uintptr {
if w.config.Mode == Fullscreen {
return windows.HTCLIENT
}
if w.config.Mode != Windowed {
// Only windowed mode should allow resizing.
return windows.HTCLIENT
}
// Check for resize handle before system actions; otherwise it can be impossible to
// resize a custom-decorations window when the system move area is flush with the
// edge of the window.
top := y <= w.borderSize.Y
bottom := y >= w.config.Size.Y-w.borderSize.Y
left := x <= w.borderSize.X
right := x >= w.config.Size.X-w.borderSize.X
switch {
case top && left:
return windows.HTTOPLEFT
case top && right:
return windows.HTTOPRIGHT
case bottom && left:
return windows.HTBOTTOMLEFT
case bottom && right:
return windows.HTBOTTOMRIGHT
case top:
return windows.HTTOP
case bottom:
return windows.HTBOTTOM
case left:
return windows.HTLEFT
case right:
return windows.HTRIGHT
}
p := f32.Pt(float32(x), float32(y))
if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
return windows.HTCAPTION
}
return windows.HTCLIENT
}
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
if !w.focused {
windows.SetFocus(w.hwnd)
}
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, horizontal bool, kmods key.Modifiers) {
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))
var sp f32.Point
if horizontal {
sp.X = dist
} else {
// support horizontal scroll (shift + mousewheel)
if kmods == key.ModShift {
sp.X = -dist
} else {
sp.Y = -dist
}
}
w.w.Event(pointer.Event{
Type: pointer.Scroll,
Source: pointer.Mouse,
Position: p,
Buttons: w.pointerBtns,
Scroll: sp,
Modifiers: kmods,
Time: windows.GetMessageTime(),
})
}
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
func (w *window) loop() error {
msg := new(windows.Msg)
loop:
for {
anim := w.animating
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
w.draw(false)
continue
}
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
case -1:
return errors.New("GetMessage failed")
case 0:
// WM_QUIT received.
break loop
}
windows.TranslateMessage(msg)
windows.DispatchMessage(msg)
}
return nil
}
func (w *window) EditorStateChanged(old, new editorState) {
imc := windows.ImmGetContext(w.hwnd)
if imc == 0 {
return
}
defer windows.ImmReleaseContext(w.hwnd, imc)
if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
}
}
func (w *window) SetAnimating(anim bool) {
w.animating = anim
}
func (w *window) Wakeup() {
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
panic(err)
}
}
func (w *window) setStage(s system.Stage) {
if s != w.stage {
w.stage = s
w.w.Event(system.StageEvent{Stage: s})
}
}
func (w *window) draw(sync bool) {
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
return
}
dpi := windows.GetWindowDPI(w.hwnd)
cfg := configForDPI(dpi)
w.w.Event(frameEvent{
FrameEvent: system.FrameEvent{
Now: time.Now(),
Size: w.config.Size,
Metric: cfg,
},
Sync: sync,
})
}
func (w *window) NewContext() (context, error) {
sort.Slice(drivers, func(i, j int) bool {
return drivers[i].priority < drivers[j].priority
})
var errs []string
for _, b := range drivers {
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 GPU drivers")
}
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)
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
w.w.Event(clipboard.Event{Text: content})
return nil
}
func (w *window) Configure(options []Option) {
dpi := windows.GetSystemDPI()
metric := configForDPI(dpi)
w.config.apply(metric, options)
windows.SetWindowText(w.hwnd, w.config.Title)
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
var showMode int32
var x, y, width, height int32
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
style &^= winStyle
switch w.config.Mode {
case Minimized:
style |= winStyle
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
showMode = windows.SW_SHOWMINIMIZED
case Maximized:
style |= winStyle
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
showMode = windows.SW_SHOWMAXIMIZED
case Windowed:
style |= winStyle
showMode = windows.SW_SHOWNORMAL
// Get target for client area size.
width = int32(w.config.Size.X)
height = int32(w.config.Size.Y)
// Get the current window size and position.
wr := windows.GetWindowRect(w.hwnd)
x = wr.Left
y = wr.Top
if w.config.Decorated {
// Compute client size and position. Note that the client size is
// equal to the window size when we are in control of decorations.
r := windows.Rect{
Right: width,
Bottom: height,
}
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
width = r.Right - r.Left
height = r.Bottom - r.Top
}
if !w.config.Decorated {
// Enable drop shadows when we draw decorations.
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
}
case Fullscreen:
mi := windows.GetMonitorInfo(w.hwnd)
x, y = mi.Monitor.Left, mi.Monitor.Top
width = mi.Monitor.Right - mi.Monitor.Left
height = mi.Monitor.Bottom - mi.Monitor.Top
showMode = windows.SW_SHOW
}
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
windows.ShowWindow(w.hwnd, showMode)
w.update()
}
func (w *window) WriteClipboard(s string) {
w.writeClipboard(s)
}
func (w *window) writeClipboard(s string) error {
if err := windows.OpenClipboard(w.hwnd); err != nil {
return err
}
defer windows.CloseClipboard()
if err := windows.EmptyClipboard(); err != nil {
return err
}
u16, err := gowindows.UTF16FromString(s)
if 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
}
u16v := unsafe.Slice((*uint16)(ptr), 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) SetCursor(cursor pointer.Cursor) {
c, err := loadCursor(cursor)
if err != nil {
c = resources.cursor
}
w.cursor = c
if w.cursorIn {
windows.SetCursor(w.cursor)
}
}
// windowsCursor contains mapping from pointer.Cursor to an IDC.
var windowsCursor = [...]uint16{
pointer.CursorDefault: windows.IDC_ARROW,
pointer.CursorNone: 0,
pointer.CursorText: windows.IDC_IBEAM,
pointer.CursorVerticalText: windows.IDC_IBEAM,
pointer.CursorPointer: windows.IDC_HAND,
pointer.CursorCrosshair: windows.IDC_CROSS,
pointer.CursorAllScroll: windows.IDC_SIZEALL,
pointer.CursorColResize: windows.IDC_SIZEWE,
pointer.CursorRowResize: windows.IDC_SIZENS,
pointer.CursorGrab: windows.IDC_SIZEALL,
pointer.CursorGrabbing: windows.IDC_SIZEALL,
pointer.CursorNotAllowed: windows.IDC_NO,
pointer.CursorWait: windows.IDC_WAIT,
pointer.CursorProgress: windows.IDC_APPSTARTING,
pointer.CursorNorthWestResize: windows.IDC_SIZENWSE,
pointer.CursorNorthEastResize: windows.IDC_SIZENESW,
pointer.CursorSouthWestResize: windows.IDC_SIZENESW,
pointer.CursorSouthEastResize: windows.IDC_SIZENWSE,
pointer.CursorNorthSouthResize: windows.IDC_SIZENS,
pointer.CursorEastWestResize: windows.IDC_SIZEWE,
pointer.CursorWestResize: windows.IDC_SIZEWE,
pointer.CursorEastResize: windows.IDC_SIZEWE,
pointer.CursorNorthResize: windows.IDC_SIZENS,
pointer.CursorSouthResize: windows.IDC_SIZENS,
pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,
pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,
}
func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
switch cursor {
case pointer.CursorDefault:
return resources.cursor, nil
case pointer.CursorNone:
return 0, nil
default:
return windows.LoadCursor(windowsCursor[cursor])
}
}
func (w *window) ShowTextInput(show bool) {}
func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) HDC() syscall.Handle {
return w.hdc
}
func (w *window) HWND() (syscall.Handle, int, int) {
return w.hwnd, w.config.Size.X, w.config.Size.Y
}
func (w *window) Perform(acts system.Action) {
walkActions(acts, func(a system.Action) {
switch a {
case system.ActionCenter:
if w.config.Mode != Windowed {
break
}
r := windows.GetWindowRect(w.hwnd)
dx := r.Right - r.Left
dy := r.Bottom - r.Top
// Calculate center position on current monitor.
mi := windows.GetMonitorInfo(w.hwnd).Monitor
x := (mi.Right - mi.Left - dx) / 2
y := (mi.Bottom - mi.Top - dy) / 2
windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
case system.ActionRaise:
w.raise()
case system.ActionClose:
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
}
})
}
func (w *window) raise() {
windows.SetForegroundWindow(w.hwnd)
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST, 0, 0, 0, 0,
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
}
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 = key.NameF1
case windows.VK_F2:
r = key.NameF2
case windows.VK_F3:
r = key.NameF3
case windows.VK_F4:
r = key.NameF4
case windows.VK_F5:
r = key.NameF5
case windows.VK_F6:
r = key.NameF6
case windows.VK_F7:
r = key.NameF7
case windows.VK_F8:
r = key.NameF8
case windows.VK_F9:
r = key.NameF9
case windows.VK_F10:
r = key.NameF10
case windows.VK_F11:
r = key.NameF11
case windows.VK_F12:
r = key.NameF12
case windows.VK_TAB:
r = key.NameTab
case windows.VK_SPACE:
r = key.NameSpace
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 = "'"
case windows.VK_CONTROL:
r = key.NameCtrl
case windows.VK_SHIFT:
r = key.NameShift
case windows.VK_MENU:
r = key.NameAlt
case windows.VK_LWIN, windows.VK_RWIN:
r = key.NameSuper
default:
return "", false
}
return r, true
}
func configForDPI(dpi int) unit.Metric {
const inchPrDp = 1.0 / 96.0
ppdp := float32(dpi) * inchPrDp
return unit.Metric{
PxPerDp: ppdp,
PxPerSp: ppdp,
}
}
func (_ ViewEvent) ImplementsEvent() {}

897
vendor/gioui.org/app/os_x11.go generated vendored
View File

@@ -1,897 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build ((linux && !android) || freebsd || openbsd) && !nox11
// +build linux,!android freebsd openbsd
// +build !nox11
package app
/*
#cgo freebsd openbsd CFLAGS: -I/usr/X11R6/include -I/usr/local/include
#cgo freebsd openbsd LDFLAGS: -L/usr/X11R6/lib -L/usr/local/lib
#cgo freebsd openbsd LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb -lXcursor -lXfixes
#cgo linux pkg-config: x11 xkbcommon xkbcommon-x11 x11-xcb xcursor xfixes
#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 <X11/extensions/Xfixes.h>
#include <X11/Xcursor/Xcursor.h>
#include <xkbcommon/xkbcommon-x11.h>
*/
import "C"
import (
"errors"
"fmt"
"image"
"strconv"
"sync"
"time"
"unsafe"
"gioui.org/f32"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/unit"
syscall "golang.org/x/sys/unix"
"gioui.org/app/internal/xkb"
)
const (
_NET_WM_STATE_REMOVE = 0
_NET_WM_STATE_ADD = 1
)
type x11Window struct {
w *callbacks
x *C.Display
xkb *xkb.Context
xkbEventBase C.int
xw C.Window
atoms struct {
// "UTF8_STRING".
utf8string C.Atom
// "text/plain;charset=utf-8".
plaintext C.Atom
// "TARGETS"
targets C.Atom
// "CLIPBOARD".
clipboard C.Atom
// "PRIMARY".
primary C.Atom
// "CLIPBOARD_CONTENT", the clipboard destination property.
clipboardContent C.Atom
// "WM_DELETE_WINDOW"
evDelWindow C.Atom
// "ATOM"
atom C.Atom
// "GTK_TEXT_BUFFER_CONTENTS"
gtk_text_buffer_contents C.Atom
// "_NET_WM_NAME"
wmName C.Atom
// "_NET_WM_STATE"
wmState C.Atom
// "_NET_WM_STATE_FULLSCREEN"
wmStateFullscreen C.Atom
// "_NET_ACTIVE_WINDOW"
wmActiveWindow C.Atom
// _NET_WM_STATE_MAXIMIZED_HORZ
wmStateMaximizedHorz C.Atom
// _NET_WM_STATE_MAXIMIZED_VERT
wmStateMaximizedVert C.Atom
}
stage system.Stage
metric unit.Metric
notify struct {
read, write int
}
dead bool
animating bool
pointerBtns pointer.Buttons
clipboard struct {
content []byte
}
cursor pointer.Cursor
config Config
wakeups chan struct{}
}
var (
newX11EGLContext func(w *x11Window) (context, error)
newX11VulkanContext func(w *x11Window) (context, error)
)
// X11 and Vulkan doesn't work reliably on NVIDIA systems.
// See https://gioui.org/issue/347.
const vulkanBuggy = true
func (w *x11Window) NewContext() (context, error) {
var firstErr error
if f := newX11VulkanContext; f != nil && !vulkanBuggy {
c, err := f(w)
if err == nil {
return c, nil
}
firstErr = err
}
if f := newX11EGLContext; f != nil {
c, err := f(w)
if err == nil {
return c, nil
}
firstErr = err
}
if firstErr != nil {
return nil, firstErr
}
return nil, errors.New("x11: no available GPU backends")
}
func (w *x11Window) SetAnimating(anim bool) {
w.animating = anim
}
func (w *x11Window) 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)
}
func (w *x11Window) WriteClipboard(s string) {
w.clipboard.content = []byte(s)
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
}
func (w *x11Window) Configure(options []Option) {
var shints C.XSizeHints
prev := w.config
cnf := w.config
cnf.apply(w.metric, options)
// Decorations are never disabled.
cnf.Decorated = true
switch cnf.Mode {
case Fullscreen:
switch prev.Mode {
case Fullscreen:
case Minimized:
w.raise()
fallthrough
default:
w.config.Mode = Fullscreen
w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateFullscreen, 0)
}
case Minimized:
switch prev.Mode {
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
screen := C.XDefaultScreen(w.x)
C.XIconifyWindow(w.x, w.xw, screen)
}
case Maximized:
switch prev.Mode {
case Fullscreen:
case Minimized:
w.raise()
fallthrough
default:
w.config.Mode = Maximized
w.sendWMStateEvent(_NET_WM_STATE_ADD, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
w.setTitle(prev, cnf)
}
case Windowed:
switch prev.Mode {
case Fullscreen:
w.config.Mode = Windowed
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateFullscreen, 0)
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
case Minimized:
w.config.Mode = Windowed
w.raise()
case Maximized:
w.config.Mode = Windowed
w.sendWMStateEvent(_NET_WM_STATE_REMOVE, w.atoms.wmStateMaximizedHorz, w.atoms.wmStateMaximizedVert)
}
w.setTitle(prev, cnf)
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
}
if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize
shints.min_width = C.int(cnf.MinSize.X)
shints.min_height = C.int(cnf.MinSize.Y)
shints.flags = C.PMinSize
}
if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize
shints.max_width = C.int(cnf.MaxSize.X)
shints.max_height = C.int(cnf.MaxSize.Y)
shints.flags = shints.flags | C.PMaxSize
}
if shints.flags != 0 {
C.XSetWMNormalHints(w.x, w.xw, &shints)
}
}
if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated
}
w.w.Event(ConfigEvent{Config: w.config})
}
func (w *x11Window) setTitle(prev, cnf Config) {
if prev.Title != cnf.Title {
title := cnf.Title
ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
C.XStoreName(w.x, w.xw, ctitle)
// set _NET_WM_NAME as well for UTF-8 support in window title.
C.XSetTextProperty(w.x, w.xw,
&C.XTextProperty{
value: (*C.uchar)(unsafe.Pointer(ctitle)),
encoding: w.atoms.utf8string,
format: 8,
nitems: C.ulong(len(title)),
},
w.atoms.wmName)
}
}
func (w *x11Window) Perform(acts system.Action) {
walkActions(acts, func(a system.Action) {
switch a {
case system.ActionCenter:
w.center()
case system.ActionRaise:
w.raise()
}
})
if acts&system.ActionClose != 0 {
w.close()
}
}
func (w *x11Window) center() {
screen := C.XDefaultScreen(w.x)
width := C.XDisplayWidth(w.x, screen)
height := C.XDisplayHeight(w.x, screen)
var attrs C.XWindowAttributes
C.XGetWindowAttributes(w.x, w.xw, &attrs)
width -= attrs.border_width
height -= attrs.border_width
sz := w.config.Size
x := (int(width) - sz.X) / 2
y := (int(height) - sz.Y) / 2
C.XMoveResizeWindow(w.x, w.xw, C.int(x), C.int(y), C.uint(sz.X), C.uint(sz.Y))
}
func (w *x11Window) raise() {
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.atoms.wmActiveWindow,
format: 32,
}
C.XSendEvent(
w.x,
C.XDefaultRootWindow(w.x), // MUST be the root window
C.False,
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
&xev,
)
C.XMapRaised(w.display(), w.xw)
}
func (w *x11Window) SetCursor(cursor pointer.Cursor) {
if cursor == pointer.CursorNone {
w.cursor = cursor
C.XFixesHideCursor(w.x, w.xw)
return
}
xcursor := xCursor[cursor]
cname := C.CString(xcursor)
defer C.free(unsafe.Pointer(cname))
c := C.XcursorLibraryLoadCursor(w.x, cname)
if c == 0 {
cursor = pointer.CursorDefault
}
w.cursor = cursor
// If c if null (i.e. cursor was not found),
// XDefineCursor will use the default cursor.
C.XDefineCursor(w.x, w.xw, c)
}
func (w *x11Window) ShowTextInput(show bool) {}
func (w *x11Window) SetInputHint(_ key.InputHint) {}
func (w *x11Window) EditorStateChanged(old, new editorState) {}
// close the window.
func (w *x11Window) close() {
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)
}
// action is one of _NET_WM_STATE_REMOVE, _NET_WM_STATE_ADD.
func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
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.atoms.wmState,
format: 32,
}
data := (*[5]C.long)(unsafe.Pointer(&ev.data))
data[0] = C.long(action)
data[1] = C.long(atom1)
data[2] = C.long(atom2)
data[3] = 1 // application
C.XSendEvent(
w.x,
C.XDefaultRootWindow(w.x), // MUST be the root window
C.False,
C.SubstructureNotifyMask|C.SubstructureRedirectMask,
&xev,
)
}
var x11OneByte = make([]byte, 1)
func (w *x11Window) Wakeup() {
select {
case w.wakeups <- struct{}{}:
default:
}
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.config.Size.X, w.config.Size.Y
}
func (w *x11Window) setStage(s system.Stage) {
if s == w.stage {
return
}
w.stage = s
w.w.Event(system.StageEvent{Stage: 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 {
anim = w.animating
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))
}
}
select {
case <-w.wakeups:
w.w.Event(wakeupEvent{})
default:
}
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
w.w.Event(frameEvent{
FrameEvent: system.FrameEvent{
Now: time.Now(),
Size: w.config.Size,
Metric: w.metric,
},
Sync: syn,
})
}
}
}
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, C.KeyRelease:
ks := key.Press
if _type == C.KeyRelease {
ks = key.Release
}
kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode), ks) {
if ee, ok := e.(key.EditEvent); ok {
// There's no support for IME yet.
w.w.EditorInsert(ee.Text)
} else {
w.w.Event(e)
}
}
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.ButtonPrimary
case C.Button2:
btn = pointer.ButtonTertiary
case C.Button3:
btn = pointer.ButtonSecondary
case C.Button4:
ev.Type = pointer.Scroll
// scroll up or left (if shift is pressed).
if ev.Modifiers == key.ModShift {
ev.Scroll.X = -scrollScale
} else {
ev.Scroll.Y = -scrollScale
}
case C.Button5:
// scroll down or right (if shift is pressed).
ev.Type = pointer.Scroll
if ev.Modifiers == key.ModShift {
ev.Scroll.X = +scrollScale
} else {
ev.Scroll.Y = +scrollScale
}
case 6:
// http://xahlee.info/linux/linux_x11_mouse_button_number.html
// scroll left.
ev.Type = pointer.Scroll
ev.Scroll.X = -scrollScale * 2
case 7:
// scroll right
ev.Type = pointer.Scroll
ev.Scroll.X = +scrollScale * 2
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))
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
w.config.Size = sz
w.w.Event(ConfigEvent{Config: w.config})
}
// 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(clipboard.Event{Text: str})
case C.SelectionRequest:
cevt := (*C.XSelectionRequestEvent)(unsafe.Pointer(xev))
if (cevt.selection != w.atoms.clipboard && cevt.selection != w.atoms.primary) || 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 targets...
formats := [...]C.long{
C.long(w.atoms.targets),
C.long(w.atoms.utf8string),
C.long(w.atoms.plaintext),
// GTK clients need this.
C.long(w.atoms.gtk_text_buffer_contents),
}
C.XChangeProperty(w.x, cevt.requestor, cevt.property, w.atoms.atom,
32 /* bitwidth of formats */, C.PropModeReplace,
(*C.uchar)(unsafe.Pointer(&formats)), C.int(len(formats)),
)
// ...then notify the requestor.
notify()
case w.atoms.plaintext, w.atoms.utf8string, w.atoms.gtk_text_buffer_contents:
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, cevt.target,
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, options []Option) 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}
// Only use cnf for getting the window size.
var cnf Config
cnf.apply(cfg, options)
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(cnf.Size.X), C.uint(cnf.Size.Y),
0, C.CopyFromParent, C.InputOutput, nil,
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
w := &x11Window{
w: gioWin, x: dpy, xw: win,
metric: cfg,
xkb: xkb,
xkbEventBase: xkbEventBase,
wakeups: make(chan struct{}, 1),
config: Config{Size: cnf.Size},
}
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)
name := C.CString(ID)
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.plaintext = w.atom("text/plain;charset=utf-8", false)
w.atoms.gtk_text_buffer_contents = w.atom("GTK_TEXT_BUFFER_CONTENTS", false)
w.atoms.evDelWindow = w.atom("WM_DELETE_WINDOW", false)
w.atoms.clipboard = w.atom("CLIPBOARD", false)
w.atoms.primary = w.atom("PRIMARY", false)
w.atoms.clipboardContent = w.atom("CLIPBOARD_CONTENT", false)
w.atoms.atom = w.atom("ATOM", false)
w.atoms.targets = w.atom("TARGETS", false)
w.atoms.wmName = w.atom("_NET_WM_NAME", false)
w.atoms.wmState = w.atom("_NET_WM_STATE", false)
w.atoms.wmStateFullscreen = w.atom("_NET_WM_STATE_FULLSCREEN", false)
w.atoms.wmActiveWindow = w.atom("_NET_ACTIVE_WINDOW", false)
w.atoms.wmStateMaximizedHorz = w.atom("_NET_WM_STATE_MAXIMIZED_HORZ", false)
w.atoms.wmStateMaximizedVert = w.atom("_NET_WM_STATE_MAXIMIZED_VERT", false)
// extensions
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
go func() {
w.w.SetDriver(w)
// make the window visible on the screen
C.XMapWindow(dpy, win)
w.Configure(options)
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
w.setStage(system.StageRunning)
w.loop()
w.w.Event(X11ViewEvent{})
w.w.Event(system.DestroyEvent{Err: nil})
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
}

30
vendor/gioui.org/app/runmain.go generated vendored
View File

@@ -1,30 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build android || (darwin && ios)
// +build android darwin,ios
package app
// 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()
})
}

218
vendor/gioui.org/app/vulkan.go generated vendored
View File

@@ -1,218 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build (linux || freebsd) && !novulkan
// +build linux freebsd
// +build !novulkan
package app
import (
"errors"
"unsafe"
"gioui.org/gpu"
"gioui.org/internal/vk"
)
type vkContext struct {
physDev vk.PhysicalDevice
inst vk.Instance
dev vk.Device
queueFam int
queue vk.Queue
acquireSem vk.Semaphore
presentSem vk.Semaphore
fence vk.Fence
swchain vk.Swapchain
imgs []vk.Image
views []vk.ImageView
fbos []vk.Framebuffer
format vk.Format
presentIdx int
}
func newVulkanContext(inst vk.Instance, surf vk.Surface) (*vkContext, error) {
physDev, qFam, err := vk.ChoosePhysicalDevice(inst, surf)
if err != nil {
return nil, err
}
dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain")
if err != nil {
return nil, err
}
acquireSem, err := vk.CreateSemaphore(dev)
if err != nil {
vk.DestroyDevice(dev)
return nil, err
}
presentSem, err := vk.CreateSemaphore(dev)
if err != nil {
vk.DestroySemaphore(dev, acquireSem)
vk.DestroyDevice(dev)
return nil, err
}
fence, err := vk.CreateFence(dev, vk.FENCE_CREATE_SIGNALED_BIT)
if err != nil {
vk.DestroySemaphore(dev, presentSem)
vk.DestroySemaphore(dev, acquireSem)
vk.DestroyDevice(dev)
return nil, err
}
c := &vkContext{
physDev: physDev,
inst: inst,
dev: dev,
queueFam: qFam,
queue: vk.GetDeviceQueue(dev, qFam, 0),
acquireSem: acquireSem,
presentSem: presentSem,
fence: fence,
}
return c, nil
}
func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) {
vk.WaitForFences(c.dev, c.fence)
vk.ResetFences(c.dev, c.fence)
imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0)
if err := mapSurfaceErr(err); err != nil {
return nil, err
}
c.presentIdx = imgIdx
return gpu.VulkanRenderTarget{
WaitSem: uint64(c.acquireSem),
SignalSem: uint64(c.presentSem),
Fence: uint64(c.fence),
Framebuffer: uint64(c.fbos[imgIdx]),
Image: uint64(c.imgs[imgIdx]),
}, nil
}
func (c *vkContext) api() gpu.API {
return gpu.Vulkan{
PhysDevice: unsafe.Pointer(c.physDev),
Device: unsafe.Pointer(c.dev),
Format: int(c.format),
QueueFamily: c.queueFam,
QueueIndex: 0,
}
}
func mapErr(err error) error {
var vkErr vk.Error
if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST {
return gpu.ErrDeviceLost
}
return err
}
func mapSurfaceErr(err error) error {
var vkErr vk.Error
if !errors.As(err, &vkErr) {
return err
}
switch {
case vkErr == vk.SUBOPTIMAL_KHR:
// Android reports VK_SUBOPTIMAL_KHR when presenting to a rotated
// swapchain (preTransform != currentTransform). However, we don't
// support transforming the output ourselves, so we'll live with it.
return nil
case vkErr == vk.ERROR_OUT_OF_DATE_KHR:
return errOutOfDate
case vkErr == vk.ERROR_SURFACE_LOST_KHR:
// Treating a lost surface as a lost device isn't accurate, but
// probably not worth optimizing.
return gpu.ErrDeviceLost
}
return mapErr(err)
}
func (c *vkContext) release() {
vk.DeviceWaitIdle(c.dev)
c.destroySwapchain()
vk.DestroyFence(c.dev, c.fence)
vk.DestroySemaphore(c.dev, c.acquireSem)
vk.DestroySemaphore(c.dev, c.presentSem)
vk.DestroyDevice(c.dev)
*c = vkContext{}
}
func (c *vkContext) present() error {
return mapSurfaceErr(vk.PresentQueue(c.queue, c.swchain, c.presentSem, c.presentIdx))
}
func (c *vkContext) destroyImageViews() {
for _, f := range c.fbos {
vk.DestroyFramebuffer(c.dev, f)
}
c.fbos = nil
for _, view := range c.views {
vk.DestroyImageView(c.dev, view)
}
c.views = nil
}
func (c *vkContext) destroySwapchain() {
vk.DeviceWaitIdle(c.dev)
c.destroyImageViews()
if c.swchain != 0 {
vk.DestroySwapchain(c.dev, c.swchain)
c.swchain = 0
}
}
func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
vk.DeviceWaitIdle(c.dev)
c.destroyImageViews()
// Check whether size is valid. That's needed on X11, where ConfigureNotify
// is not always synchronized with the window extent.
caps, err := vk.GetPhysicalDeviceSurfaceCapabilities(c.physDev, surf)
if err != nil {
return err
}
minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
return errOutOfDate
}
swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, surf, width, height, c.swchain)
if c.swchain != 0 {
vk.DestroySwapchain(c.dev, c.swchain)
c.swchain = 0
}
if err := mapSurfaceErr(err); err != nil {
return err
}
c.swchain = swchain
c.imgs = imgs
c.format = format
pass, err := vk.CreateRenderPass(
c.dev,
format,
vk.ATTACHMENT_LOAD_OP_CLEAR,
vk.IMAGE_LAYOUT_UNDEFINED,
vk.IMAGE_LAYOUT_PRESENT_SRC_KHR,
nil,
)
if err := mapErr(err); err != nil {
return err
}
defer vk.DestroyRenderPass(c.dev, pass)
for _, img := range imgs {
view, err := vk.CreateImageView(c.dev, img, format)
if err := mapErr(err); err != nil {
return err
}
c.views = append(c.views, view)
fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height)
if err := mapErr(err); err != nil {
return err
}
c.fbos = append(c.fbos, fbo)
}
return nil
}

View File

@@ -1,90 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !novulkan
// +build !novulkan
package app
import (
"unsafe"
"gioui.org/gpu"
"gioui.org/internal/vk"
)
type wlVkContext struct {
win *window
inst vk.Instance
surf vk.Surface
ctx *vkContext
}
func init() {
newAndroidVulkanContext = func(w *window) (context, error) {
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_android_surface")
if err != nil {
return nil, err
}
window, _, _ := w.nativeWindow()
surf, err := vk.CreateAndroidSurface(inst, unsafe.Pointer(window))
if err != nil {
vk.DestroyInstance(inst)
return nil, err
}
ctx, err := newVulkanContext(inst, surf)
if err != nil {
vk.DestroySurface(inst, surf)
vk.DestroyInstance(inst)
return nil, err
}
c := &wlVkContext{
win: w,
inst: inst,
surf: surf,
ctx: ctx,
}
return c, nil
}
}
func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
return c.ctx.RenderTarget()
}
func (c *wlVkContext) API() gpu.API {
return c.ctx.api()
}
func (c *wlVkContext) Release() {
c.ctx.release()
if c.surf != 0 {
vk.DestroySurface(c.inst, c.surf)
}
vk.DestroyInstance(c.inst)
*c = wlVkContext{}
}
func (c *wlVkContext) Present() error {
return c.ctx.present()
}
func (c *wlVkContext) Lock() error {
return nil
}
func (c *wlVkContext) Unlock() {}
func (c *wlVkContext) Refresh() error {
win, w, h := c.win.nativeWindow()
if c.surf != 0 {
c.ctx.destroySwapchain()
vk.DestroySurface(c.inst, c.surf)
c.surf = 0
}
surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win))
if err != nil {
return err
}
c.surf = surf
return c.ctx.refresh(c.surf, w, h)
}

View File

@@ -1,81 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build ((linux && !android) || freebsd) && !nowayland && !novulkan
// +build linux,!android freebsd
// +build !nowayland
// +build !novulkan
package app
import (
"unsafe"
"gioui.org/gpu"
"gioui.org/internal/vk"
)
type wlVkContext struct {
win *window
inst vk.Instance
surf vk.Surface
ctx *vkContext
}
func init() {
newWaylandVulkanContext = func(w *window) (context, error) {
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_wayland_surface")
if err != nil {
return nil, err
}
disp := w.display()
wlSurf, _, _ := w.surface()
surf, err := vk.CreateWaylandSurface(inst, unsafe.Pointer(disp), unsafe.Pointer(wlSurf))
if err != nil {
vk.DestroyInstance(inst)
return nil, err
}
ctx, err := newVulkanContext(inst, surf)
if err != nil {
vk.DestroySurface(inst, surf)
vk.DestroyInstance(inst)
return nil, err
}
c := &wlVkContext{
win: w,
inst: inst,
surf: surf,
ctx: ctx,
}
return c, nil
}
}
func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
return c.ctx.RenderTarget()
}
func (c *wlVkContext) API() gpu.API {
return c.ctx.api()
}
func (c *wlVkContext) Release() {
c.ctx.release()
vk.DestroySurface(c.inst, c.surf)
vk.DestroyInstance(c.inst)
*c = wlVkContext{}
}
func (c *wlVkContext) Present() error {
return c.ctx.present()
}
func (c *wlVkContext) Lock() error {
return nil
}
func (c *wlVkContext) Unlock() {}
func (c *wlVkContext) Refresh() error {
_, w, h := c.win.surface()
return c.ctx.refresh(c.surf, w, h)
}

81
vendor/gioui.org/app/vulkan_x11.go generated vendored
View File

@@ -1,81 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build ((linux && !android) || freebsd) && !nox11 && !novulkan
// +build linux,!android freebsd
// +build !nox11
// +build !novulkan
package app
import (
"unsafe"
"gioui.org/gpu"
"gioui.org/internal/vk"
)
type x11VkContext struct {
win *x11Window
inst vk.Instance
surf vk.Surface
ctx *vkContext
}
func init() {
newX11VulkanContext = func(w *x11Window) (context, error) {
inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_xlib_surface")
if err != nil {
return nil, err
}
disp := w.display()
window, _, _ := w.window()
surf, err := vk.CreateXlibSurface(inst, unsafe.Pointer(disp), uintptr(window))
if err != nil {
vk.DestroyInstance(inst)
return nil, err
}
ctx, err := newVulkanContext(inst, surf)
if err != nil {
vk.DestroySurface(inst, surf)
vk.DestroyInstance(inst)
return nil, err
}
c := &x11VkContext{
win: w,
inst: inst,
surf: surf,
ctx: ctx,
}
return c, nil
}
}
func (c *x11VkContext) RenderTarget() (gpu.RenderTarget, error) {
return c.ctx.RenderTarget()
}
func (c *x11VkContext) API() gpu.API {
return c.ctx.api()
}
func (c *x11VkContext) Release() {
c.ctx.release()
vk.DestroySurface(c.inst, c.surf)
vk.DestroyInstance(c.inst)
*c = x11VkContext{}
}
func (c *x11VkContext) Present() error {
return c.ctx.present()
}
func (c *x11VkContext) Lock() error {
return nil
}
func (c *x11VkContext) Unlock() {}
func (c *x11VkContext) Refresh() error {
_, w, h := c.win.window()
return c.ctx.refresh(c.surf, w, h)
}

View File

@@ -1,100 +0,0 @@
//go:build ((linux && !android) || freebsd) && !nowayland
// +build linux,!android freebsd
// +build !nowayland
/* Generated by wayland-scanner 1.19.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 *text_input_unstable_v3_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", "", text_input_unstable_v3_types + 0 },
{ "enable", "", text_input_unstable_v3_types + 0 },
{ "disable", "", text_input_unstable_v3_types + 0 },
{ "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
{ "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
{ "set_content_type", "uu", text_input_unstable_v3_types + 0 },
{ "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
{ "commit", "", text_input_unstable_v3_types + 0 },
};
static const struct wl_message zwp_text_input_v3_events[] = {
{ "enter", "o", text_input_unstable_v3_types + 4 },
{ "leave", "o", text_input_unstable_v3_types + 5 },
{ "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
{ "commit_string", "?s", text_input_unstable_v3_types + 0 },
{ "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
{ "done", "u", text_input_unstable_v3_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", "", text_input_unstable_v3_types + 0 },
{ "get_text_input", "no", text_input_unstable_v3_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,
};

View File

@@ -1,836 +0,0 @@
/* Generated by wayland-scanner 1.19.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;
#ifndef ZWP_TEXT_INPUT_V3_INTERFACE
#define ZWP_TEXT_INPUT_V3_INTERFACE
/**
* @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;
#endif
#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
#define ZWP_TEXT_INPUT_MANAGER_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;
#endif
#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.
*
* If client has created multiple text input objects, compositor
* must send this event to all of them.
*
* 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. After leave
* event, compositor must ignore requests from any text input
* instances until next enter event.
*
* 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.
*
* Clients must not enable more than one text input on the single seat
* and should disable the current text input before enabling the new one.
* At most one instance of text input may be in enabled state per instance,
* Requests to enable the another text input when some text input is active
* must be ignored by compositor.
*
* 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

View File

@@ -1,79 +0,0 @@
//go:build ((linux && !android) || freebsd) && !nowayland
// +build linux,!android freebsd
// +build !nowayland
/* Generated by wayland-scanner 1.19.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 *xdg_decoration_unstable_v1_types[] = {
NULL,
&zxdg_toplevel_decoration_v1_interface,
&xdg_toplevel_interface,
};
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
{ "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_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", "", xdg_decoration_unstable_v1_types + 0 },
{ "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
{ "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
};
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
{ "configure", "u", xdg_decoration_unstable_v1_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,
};

View File

@@ -1,382 +0,0 @@
/* Generated by wayland-scanner 1.19.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;
#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE
#define ZXDG_DECORATION_MANAGER_V1_INTERFACE
/**
* @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;
#endif
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
#define ZXDG_TOPLEVEL_DECORATION_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;
#endif
#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 an 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 an 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

View File

@@ -1,185 +0,0 @@
//go:build ((linux && !android) || freebsd) && !nowayland
// +build linux,!android freebsd
// +build !nowayland
/* Generated by wayland-scanner 1.19.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 *xdg_shell_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,
&xdg_positioner_interface,
NULL,
};
static const struct wl_message xdg_wm_base_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "create_positioner", "n", xdg_shell_types + 4 },
{ "get_xdg_surface", "no", xdg_shell_types + 5 },
{ "pong", "u", xdg_shell_types + 0 },
};
static const struct wl_message xdg_wm_base_events[] = {
{ "ping", "u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
"xdg_wm_base", 3,
4, xdg_wm_base_requests,
1, xdg_wm_base_events,
};
static const struct wl_message xdg_positioner_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "set_size", "ii", xdg_shell_types + 0 },
{ "set_anchor_rect", "iiii", xdg_shell_types + 0 },
{ "set_anchor", "u", xdg_shell_types + 0 },
{ "set_gravity", "u", xdg_shell_types + 0 },
{ "set_constraint_adjustment", "u", xdg_shell_types + 0 },
{ "set_offset", "ii", xdg_shell_types + 0 },
{ "set_reactive", "3", xdg_shell_types + 0 },
{ "set_parent_size", "3ii", xdg_shell_types + 0 },
{ "set_parent_configure", "3u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
"xdg_positioner", 3,
10, xdg_positioner_requests,
0, NULL,
};
static const struct wl_message xdg_surface_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "get_toplevel", "n", xdg_shell_types + 7 },
{ "get_popup", "n?oo", xdg_shell_types + 8 },
{ "set_window_geometry", "iiii", xdg_shell_types + 0 },
{ "ack_configure", "u", xdg_shell_types + 0 },
};
static const struct wl_message xdg_surface_events[] = {
{ "configure", "u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
"xdg_surface", 3,
5, xdg_surface_requests,
1, xdg_surface_events,
};
static const struct wl_message xdg_toplevel_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "set_parent", "?o", xdg_shell_types + 11 },
{ "set_title", "s", xdg_shell_types + 0 },
{ "set_app_id", "s", xdg_shell_types + 0 },
{ "show_window_menu", "ouii", xdg_shell_types + 12 },
{ "move", "ou", xdg_shell_types + 16 },
{ "resize", "ouu", xdg_shell_types + 18 },
{ "set_max_size", "ii", xdg_shell_types + 0 },
{ "set_min_size", "ii", xdg_shell_types + 0 },
{ "set_maximized", "", xdg_shell_types + 0 },
{ "unset_maximized", "", xdg_shell_types + 0 },
{ "set_fullscreen", "?o", xdg_shell_types + 21 },
{ "unset_fullscreen", "", xdg_shell_types + 0 },
{ "set_minimized", "", xdg_shell_types + 0 },
};
static const struct wl_message xdg_toplevel_events[] = {
{ "configure", "iia", xdg_shell_types + 0 },
{ "close", "", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
"xdg_toplevel", 3,
14, xdg_toplevel_requests,
2, xdg_toplevel_events,
};
static const struct wl_message xdg_popup_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "grab", "ou", xdg_shell_types + 22 },
{ "reposition", "3ou", xdg_shell_types + 24 },
};
static const struct wl_message xdg_popup_events[] = {
{ "configure", "iiii", xdg_shell_types + 0 },
{ "popup_done", "", xdg_shell_types + 0 },
{ "repositioned", "3u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
"xdg_popup", 3,
3, xdg_popup_requests,
3, xdg_popup_events,
};

File diff suppressed because it is too large Load Diff

1167
vendor/gioui.org/app/window.go generated vendored

File diff suppressed because it is too large Load Diff

63
vendor/gioui.org/cpu/LICENSE generated vendored
View File

@@ -1,63 +0,0 @@
This project is provided under the terms of the UNLICENSE or
the MIT license denoted by the following 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/>
---

25
vendor/gioui.org/cpu/README.md generated vendored
View File

@@ -1,25 +0,0 @@
# Compile and run compute programs on CPU
This projects contains the compiler for turning Vulkan SPIR-V compute shaders
into binaries for arm64, arm or amd64, using
[SwiftShader](https://github.com/eliasnaur/swiftshader) with a few
modifications. A runtime implemented in C and Go is available for running the
resulting binaries.
The primary use is to support a CPU-based rendering fallback for
[Gio](https://gioui.org). In particular, the `gioui.org/shader/piet` package
contains arm, arm64, amd64 binaries for
[piet-gpu](https://github.com/linebender/piet-gpu).
# Compiling and running shaders
The `init.sh` script clones the modifed SwiftShader projects and builds it for
64-bit and 32-bit. SwiftShader is not designed to cross-compile which is why a
32-bit build is needed to compile shaders for arm.
The `example/run.sh` script demonstrates compiling and running a simple compute
program.
## Issues and contributions
See the [Gio contribution guide](https://gioui.org/doc/contribute).

91
vendor/gioui.org/cpu/abi.h generated vendored
View File

@@ -1,91 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#define ALIGN(bytes, type) type __attribute__((aligned(bytes)))
typedef ALIGN(8, uint8_t) byte8[8];
typedef ALIGN(8, uint16_t) word4[4];
typedef ALIGN(4, uint32_t) dword;
typedef ALIGN(16, uint32_t) dword4[4];
typedef ALIGN(8, uint64_t) qword;
typedef ALIGN(16, uint64_t) qword2[2];
typedef ALIGN(16, unsigned int) uint4[4];
typedef ALIGN(8, uint32_t) dword2[2];
typedef ALIGN(8, unsigned short) ushort4[4];
typedef ALIGN(16, float) float4[4];
typedef ALIGN(16, int) int4[4];
typedef unsigned short half;
typedef unsigned char bool;
enum {
MAX_BOUND_DESCRIPTOR_SETS = 4,
MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC = 8,
MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC = 4,
MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC =
MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC +
MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC,
MAX_PUSH_CONSTANT_SIZE = 128,
MIN_STORAGE_BUFFER_OFFSET_ALIGNMENT = 256,
REQUIRED_MEMORY_ALIGNMENT = 16,
SIMD_WIDTH = 4,
};
struct image_descriptor {
ALIGN(16, void *ptr);
int width;
int height;
int depth;
int row_pitch_bytes;
int slice_pitch_bytes;
int sample_pitch_bytes;
int sample_count;
int size_in_bytes;
void *stencil_ptr;
int stencil_row_pitch_bytes;
int stencil_slice_pitch_bytes;
int stencil_sample_pitch_bytes;
// TODO: unused?
void *memoryOwner;
};
struct buffer_descriptor {
ALIGN(16, void *ptr);
int size_in_bytes;
int robustness_size;
};
struct program_data {
uint8_t *descriptor_sets[MAX_BOUND_DESCRIPTOR_SETS];
uint32_t descriptor_dynamic_offsets[MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC];
uint4 num_workgroups;
uint4 workgroup_size;
uint32_t invocations_per_subgroup;
uint32_t subgroups_per_workgroup;
uint32_t invocations_per_workgroup;
unsigned char push_constants[MAX_PUSH_CONSTANT_SIZE];
// Unused.
void *constants;
};
typedef int32_t yield_result;
typedef void * coroutine;
typedef coroutine (*routine_begin)(struct program_data *data,
int32_t workgroupX,
int32_t workgroupY,
int32_t workgroupZ,
void *workgroupMemory,
int32_t firstSubgroup,
int32_t subgroupCount);
typedef bool (*routine_await)(coroutine r, yield_result *res);
typedef void (*routine_destroy)(coroutine r);

86
vendor/gioui.org/cpu/driver.go generated vendored
View File

@@ -1,86 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build linux && (arm64 || arm || amd64)
// +build linux
// +build arm64 arm amd64
package cpu
/*
#cgo CFLAGS: -std=c11 -D_POSIX_C_SOURCE=200112L
#include <stdint.h>
#include <stdlib.h>
#include "abi.h"
#include "runtime.h"
*/
import "C"
import (
"unsafe"
)
type (
BufferDescriptor = C.struct_buffer_descriptor
ImageDescriptor = C.struct_image_descriptor
SamplerDescriptor = C.struct_sampler_descriptor
DispatchContext = C.struct_dispatch_context
ThreadContext = C.struct_thread_context
ProgramInfo = C.struct_program_info
)
const Supported = true
func NewBuffer(size int) BufferDescriptor {
return C.alloc_buffer(C.size_t(size))
}
func (d *BufferDescriptor) Data() []byte {
return (*(*[1 << 30]byte)(d.ptr))[:d.size_in_bytes:d.size_in_bytes]
}
func (d *BufferDescriptor) Free() {
if d.ptr != nil {
C.free(d.ptr)
}
*d = BufferDescriptor{}
}
func NewImageRGBA(width, height int) ImageDescriptor {
return C.alloc_image_rgba(C.int(width), C.int(height))
}
func (d *ImageDescriptor) Data() []byte {
return (*(*[1 << 30]byte)(d.ptr))[:d.size_in_bytes:d.size_in_bytes]
}
func (d *ImageDescriptor) Free() {
if d.ptr != nil {
C.free(d.ptr)
}
*d = ImageDescriptor{}
}
func NewDispatchContext() *DispatchContext {
return C.alloc_dispatch_context()
}
func (c *DispatchContext) Free() {
C.free_dispatch_context(c)
}
func (c *DispatchContext) Prepare(numThreads int, prog *ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
C.prepare_dispatch(c, C.int(numThreads), prog, (*C.uint8_t)(descSet), C.int(x), C.int(y), C.int(z))
}
func (c *DispatchContext) Dispatch(threadIdx int, ctx *ThreadContext) {
C.dispatch_thread(c, C.int(threadIdx), ctx)
}
func NewThreadContext() *ThreadContext {
return C.alloc_thread_context()
}
func (c *ThreadContext) Free() {
C.free_thread_context(c)
}

View File

@@ -1,64 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build !(linux && (arm64 || arm || amd64))
// +build !linux !arm64,!arm,!amd64
package cpu
import "unsafe"
type (
BufferDescriptor struct{}
ImageDescriptor struct{}
SamplerDescriptor struct{}
DispatchContext struct{}
ThreadContext struct{}
ProgramInfo struct{}
)
const Supported = false
func NewBuffer(size int) BufferDescriptor {
panic("unsupported")
}
func (d *BufferDescriptor) Data() []byte {
panic("unsupported")
}
func (d *BufferDescriptor) Free() {
}
func NewImageRGBA(width, height int) ImageDescriptor {
panic("unsupported")
}
func (d *ImageDescriptor) Data() []byte {
panic("unsupported")
}
func (d *ImageDescriptor) Free() {
}
func NewDispatchContext() *DispatchContext {
panic("unsupported")
}
func (c *DispatchContext) Free() {
}
func (c *DispatchContext) Prepare(numThreads int, prog *ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
panic("unsupported")
}
func (c *DispatchContext) Dispatch(threadIdx int, ctx *ThreadContext) {
panic("unsupported")
}
func NewThreadContext() *ThreadContext {
panic("unsupported")
}
func (c *ThreadContext) Free() {
}

11
vendor/gioui.org/cpu/embed.go generated vendored
View File

@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package cpu
import _ "embed"
//go:embed abi.h
var ABIH []byte
//go:embed runtime.h
var RuntimeH []byte

23
vendor/gioui.org/cpu/init.sh generated vendored
View File

@@ -1,23 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: Unlicense OR MIT
set -e
cd ~/.cache
git clone https://github.com/eliasnaur/swiftshader
cd swiftshader
# 32-bit build
cp -a build build.32bit
cd build.32bit
CXX=clang++ CC=clang CFLAGS=-m32 CXXFLAGS=-m32 cmake -DREACTOR_EMIT_ASM_FILE=true -DSWIFTSHADER_BUILD_PVR=false -DSWIFTSHADER_BUILD_TESTS=false -DSWIFTSHADER_BUILD_GLESv2=false -DSWIFTSHADER_BUILD_EGL=false -DSWIFTSHADER_BUILD_ANGLE=false ..
cmake --build . --parallel 4
cd ..
# 64-bit build
cp -a build build.64bit
cd build.64bit
CXX=clang++ CC=clang cmake -DREACTOR_EMIT_ASM_FILE=true -DSWIFTSHADER_BUILD_PVR=false -DSWIFTSHADER_BUILD_TESTS=false -DSWIFTSHADER_BUILD_GLESv2=false -DSWIFTSHADER_BUILD_EGL=false -DSWIFTSHADER_BUILD_ANGLE=false ..
cmake --build . --parallel 4
cd ..

245
vendor/gioui.org/cpu/runtime.c generated vendored
View File

@@ -1,245 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build linux && (arm64 || arm || amd64)
// +build linux
// +build arm64 arm amd64
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include "abi.h"
#include "runtime.h"
#include "_cgo_export.h"
// coroutines is a FIFO queue of coroutines implemented as a circular
// buffer.
struct coroutines {
coroutine *routines;
// start and end indexes into routines.
unsigned int start;
unsigned int end;
// cap is the capacity of routines.
unsigned int cap;
};
struct dispatch_context {
// descriptor_set is the aligned storage for the descriptor set.
void *descriptor_set;
int desc_set_size;
int nthreads;
bool has_cbarriers;
size_t memory_size;
// Program entrypoints.
routine_begin begin;
routine_await await;
routine_destroy destroy;
struct program_data data;
};
struct thread_context {
struct coroutines routines;
size_t memory_size;
uint8_t *memory;
};
static void *malloc_align(size_t alignment, size_t size) {
void *ptr;
int ret = posix_memalign(&ptr, alignment, size);
assert(ret == 0);
return ptr;
}
static void coroutines_dump(struct coroutines *routines) {
fprintf(stderr, "s: %d e: %d c: %d [", routines->start, routines->end, routines->cap);
unsigned int i = routines->start;
while (i != routines->end) {
fprintf(stderr, "%p,", routines->routines[routines->start]);
i = (i + 1)%routines->cap;
}
fprintf(stderr, "]\n");
}
static void coroutines_push(struct coroutines *routines, coroutine r) {
unsigned int next = routines->end + 1;
if (next >= routines->cap) {
next = 0;
}
if (next == routines->start) {
unsigned int newcap = routines->cap*2;
if (newcap < 10) {
newcap = 10;
}
routines->routines = realloc(routines->routines, newcap*sizeof(coroutine));
// Move elements wrapped around the old cap to the newly allocated space.
if (routines->end < routines->start) {
unsigned int nelems = routines->end;
unsigned int max = newcap - routines->cap;
// We doubled the space above, so we can assume enough room.
assert(nelems <= max);
memmove(&routines->routines[routines->cap], &routines->routines[0], nelems*sizeof(coroutine));
routines->end += routines->cap;
}
routines->cap = newcap;
next = (routines->end + 1)%routines->cap;
}
routines->routines[routines->end] = r;
routines->end = next;
}
static bool coroutines_pop(struct coroutines *routines, coroutine *r) {
if (routines->start == routines->end) {
return 0;
}
*r = routines->routines[routines->start];
routines->start = (routines->start + 1)%routines->cap;
return 1;
}
static void coroutines_free(struct coroutines *routines) {
if (routines->routines != NULL) {
free(routines->routines);
}
struct coroutines clr = { 0 }; *routines = clr;
}
struct dispatch_context *alloc_dispatch_context(void) {
struct dispatch_context *c = malloc(sizeof(*c));
assert(c != NULL);
struct dispatch_context clr = { 0 }; *c = clr;
return c;
}
void free_dispatch_context(struct dispatch_context *c) {
if (c->descriptor_set != NULL) {
free(c->descriptor_set);
c->descriptor_set = NULL;
}
}
struct thread_context *alloc_thread_context(void) {
struct thread_context *c = malloc(sizeof(*c));
assert(c != NULL);
struct thread_context clr = { 0 }; *c = clr;
return c;
}
void free_thread_context(struct thread_context *c) {
if (c->memory != NULL) {
free(c->memory);
}
coroutines_free(&c->routines);
struct thread_context clr = { 0 }; *c = clr;
}
struct buffer_descriptor alloc_buffer(size_t size) {
void *buf = malloc_align(MIN_STORAGE_BUFFER_OFFSET_ALIGNMENT, size);
struct buffer_descriptor desc = {
.ptr = buf,
.size_in_bytes = size,
.robustness_size = size,
};
return desc;
}
struct image_descriptor alloc_image_rgba(int width, int height) {
size_t size = width*height*4;
size = (size + 16 - 1)&~(16 - 1);
void *storage = malloc_align(REQUIRED_MEMORY_ALIGNMENT, size);
struct image_descriptor desc = { 0 };
desc.ptr = storage;
desc.width = width;
desc.height = height;
desc.depth = 1;
desc.row_pitch_bytes = width*4;
desc.slice_pitch_bytes = size;
desc.sample_pitch_bytes = size;
desc.sample_count = 1;
desc.size_in_bytes = size;
return desc;
}
void prepare_dispatch(struct dispatch_context *ctx, int nthreads, struct program_info *info, uint8_t *desc_set, int ngroupx, int ngroupy, int ngroupz) {
if (ctx->desc_set_size < info->desc_set_size) {
if (ctx->descriptor_set != NULL) {
free(ctx->descriptor_set);
}
ctx->descriptor_set = malloc_align(16, info->desc_set_size);
ctx->desc_set_size = info->desc_set_size;
}
memcpy(ctx->descriptor_set, desc_set, info->desc_set_size);
int invocations_per_subgroup = SIMD_WIDTH;
int invocations_per_workgroup = info->workgroup_size_x * info->workgroup_size_y * info->workgroup_size_z;
int subgroups_per_workgroup = (invocations_per_workgroup + invocations_per_subgroup - 1) / invocations_per_subgroup;
ctx->has_cbarriers = info->has_cbarriers;
ctx->begin = info->begin;
ctx->await = info->await;
ctx->destroy = info->destroy;
ctx->nthreads = nthreads;
ctx->memory_size = info->min_memory_size;
ctx->data.workgroup_size[0] = info->workgroup_size_x;
ctx->data.workgroup_size[1] = info->workgroup_size_y;
ctx->data.workgroup_size[2] = info->workgroup_size_z;
ctx->data.num_workgroups[0] = ngroupx;
ctx->data.num_workgroups[1] = ngroupy;
ctx->data.num_workgroups[2] = ngroupz;
ctx->data.invocations_per_subgroup = invocations_per_subgroup;
ctx->data.invocations_per_workgroup = invocations_per_workgroup;
ctx->data.subgroups_per_workgroup = subgroups_per_workgroup;
ctx->data.descriptor_sets[0] = ctx->descriptor_set;
}
void dispatch_thread(struct dispatch_context *ctx, int thread_idx, struct thread_context *thread) {
if (thread->memory_size < ctx->memory_size) {
if (thread->memory != NULL) {
free(thread->memory);
}
// SwiftShader doesn't seem to align shared memory. However, better safe
// than subtle errors. Note that the program info generator pads
// memory_size to ensure space for alignment.
thread->memory = malloc_align(16, ctx->memory_size);
thread->memory_size = ctx->memory_size;
}
uint8_t *memory = thread->memory;
struct program_data *data = &ctx->data;
int sx = data->num_workgroups[0];
int sy = data->num_workgroups[1];
int sz = data->num_workgroups[2];
int ngroups = sx * sy * sz;
for (int i = thread_idx; i < ngroups; i += ctx->nthreads) {
int group_id = i;
int z = group_id / (sx * sy);
group_id -= z * sx * sy;
int y = group_id / sx;
group_id -= y * sx;
int x = group_id;
if (ctx->has_cbarriers) {
for (int subgroup = 0; subgroup < data->subgroups_per_workgroup; subgroup++) {
coroutine r = ctx->begin(data, x, y, z, memory, subgroup, 1);
coroutines_push(&thread->routines, r);
}
} else {
coroutine r = ctx->begin(data, x, y, z, memory, 0, data->subgroups_per_workgroup);
coroutines_push(&thread->routines, r);
}
coroutine r;
while (coroutines_pop(&thread->routines, &r)) {
yield_result res;
if (ctx->await(r, &res)) {
coroutines_push(&thread->routines, r);
} else {
ctx->destroy(r);
}
}
}
}

45
vendor/gioui.org/cpu/runtime.h generated vendored
View File

@@ -1,45 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#define ATTR_HIDDEN __attribute__ ((visibility ("hidden")))
// program_info contains constant parameters for a program.
struct program_info {
// MinMemorySize is the minimum size of memory passed to dispatch.
size_t min_memory_size;
// has_cbarriers is 1 when the program contains control barriers.
bool has_cbarriers;
// desc_set_size is the size of the first descriptor set for the program.
size_t desc_set_size;
int workgroup_size_x;
int workgroup_size_y;
int workgroup_size_z;
// Program entrypoints.
routine_begin begin;
routine_await await;
routine_destroy destroy;
};
// dispatch_context contains the information a program dispatch.
struct dispatch_context;
// thread_context contains the working memory of a batch. It may be
// reused, but not concurrently.
struct thread_context;
extern struct buffer_descriptor alloc_buffer(size_t size) ATTR_HIDDEN;
extern struct image_descriptor alloc_image_rgba(int width, int height) ATTR_HIDDEN;
extern struct dispatch_context *alloc_dispatch_context(void) ATTR_HIDDEN;
extern void free_dispatch_context(struct dispatch_context *c) ATTR_HIDDEN;
extern struct thread_context *alloc_thread_context(void) ATTR_HIDDEN;
extern void free_thread_context(struct thread_context *c) ATTR_HIDDEN;
// prepare_dispatch initializes ctx to run a dispatch of a program distributed
// among nthreads threads.
extern void prepare_dispatch(struct dispatch_context *ctx, int nthreads, struct program_info *info, uint8_t *desc_set, int ngroupx, int ngroupy, int ngroupz) ATTR_HIDDEN;
// dispatch_batch executes a dispatch batch.
extern void dispatch_thread(struct dispatch_context *ctx, int thread_idx, struct thread_context *thread) ATTR_HIDDEN;

172
vendor/gioui.org/f32/affine.go generated vendored
View File

@@ -1,172 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package f32
import (
"math"
"strconv"
)
// Affine2D represents an affine 2D transformation. The zero value of 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 transformation 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 - 1, b: hx, c: ox,
d: hy, e: sy - 1, f: oy,
}
}
// 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) {
r.a = (A.a+1)*(B.a+1) + A.b*B.d - 1
r.b = (A.a+1)*B.b + A.b*(B.e+1)
r.c = (A.a+1)*B.c + A.b*B.f + A.c
r.d = A.d*(B.a+1) + (A.e+1)*B.d
r.e = A.d*B.b + (A.e+1)*(B.e+1) - 1
r.f = A.d*B.c + (A.e+1)*B.f + A.f
return r
}
// 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 += 1
a.e += 1
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
a.a -= 1
a.e -= 1
return a
}
// Transform p by returning a*p.
func (a Affine2D) Transform(p Point) Point {
return Point{
X: p.X*(a.a+1) + p.Y*a.b + a.c,
Y: p.X*a.d + p.Y*(a.e+1) + 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) {
return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
}
// Split a transform into two parts, one which is pure offset and the
// other representing the scaling, shearing and rotation part.
func (a *Affine2D) Split() (srs Affine2D, offset Point) {
return Affine2D{
a: a.a, b: a.b, c: 0,
d: a.d, e: a.e, f: 0,
}, Point{X: a.c, Y: a.f}
}
func (a Affine2D) scale(factor Point) Affine2D {
return Affine2D{
(a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
a.d * factor.Y, (a.e+1)*factor.Y - 1, a.f * factor.Y,
}
}
func (a Affine2D) rotate(radians float32) Affine2D {
sin, cos := math.Sincos(float64(radians))
s, c := float32(sin), float32(cos)
return Affine2D{
(a.a+1)*c - a.d*s - 1, a.b*c - (a.e+1)*s, a.c*c - a.f*s,
(a.a+1)*s + a.d*c, a.b*s + (a.e+1)*c - 1, a.c*s + a.f*c,
}
}
func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
tx := float32(math.Tan(float64(radiansX)))
ty := float32(math.Tan(float64(radiansY)))
return Affine2D{
(a.a + 1) + a.d*tx - 1, a.b + (a.e+1)*tx, a.c + a.f*tx,
(a.a+1)*ty + a.d, a.b*ty + (a.e + 1) - 1, a.f*ty + a.f,
}
}
func (a Affine2D) String() string {
sx, hx, ox, hy, sy, oy := a.Elems()
// precision 6, one period, negative sign and space per number
const prec = 6
const charsPerFloat = prec + 2 + 1
s := make([]byte, 0, 6*charsPerFloat+6)
s = append(s, '[', '[')
s = strconv.AppendFloat(s, float64(sx), 'g', prec, 32)
s = append(s, ' ')
s = strconv.AppendFloat(s, float64(hx), 'g', prec, 32)
s = append(s, ' ')
s = strconv.AppendFloat(s, float64(ox), 'g', prec, 32)
s = append(s, ']', ' ', '[')
s = strconv.AppendFloat(s, float64(hy), 'g', prec, 32)
s = append(s, ' ')
s = strconv.AppendFloat(s, float64(sy), 'g', prec, 32)
s = append(s, ' ')
s = strconv.AppendFloat(s, float64(oy), 'g', prec, 32)
s = append(s, ']', ']')
return string(s)
}

60
vendor/gioui.org/f32/f32.go generated vendored
View File

@@ -1,60 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package f32 is a float32 implementation of package image's
Point and affine transformations.
The coordinate space has the origin in the top left
corner with the axes extending right and down.
*/
package f32
import (
"image"
"math"
"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) + ")"
}
// 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}
}
// Div returns the vector p/s.
func (p Point) Div(s float32) Point {
return Point{X: p.X / s, Y: p.Y / s}
}
// Round returns the integer point closest to p.
func (p Point) Round() image.Point {
return image.Point{
X: int(math.Round(float64(p.X))),
Y: int(math.Round(float64(p.Y))),
}
}

126
vendor/gioui.org/font/font.go generated vendored
View File

@@ -1,126 +0,0 @@
/*
Package font provides type describing font faces attributes.
*/
package font
import (
"github.com/go-text/typesetting/font"
)
// A FontFace is a Font and a matching Face.
type FontFace struct {
Font Font
Face Face
}
// Style is the font style.
type Style int
// Weight is a font weight, in CSS units subtracted 400 so the zero value
// is normal text weight.
type Weight int
// Font specify a particular typeface variant, style and weight.
type Font struct {
// Typeface specifies the name(s) of the the font faces to try. See [Typeface]
// for details.
Typeface Typeface
// Style specifies the kind of text style.
Style Style
// Weight is the text weight.
Weight Weight
}
// Face is an opaque handle to a typeface. The concrete implementation depends
// upon the kind of font and shaper in use.
type Face interface {
Face() font.Face
}
// Typeface identifies a list of font families to attempt to use for displaying
// a string. The syntax is a comma-delimited list of family names. In order to
// allow for the remote possibility of needing to express a font family name
// containing a comma, name entries may be quoted using either single or double
// quotes. Within quotes, a literal quotation mark can be expressed by escaping
// it with `\`. A literal backslash may be expressed by escaping it with another
// `\`.
//
// Here's an example Typeface:
//
// Times New Roman, Georgia, serif
//
// This is equivalent to the above:
//
// "Times New Roman", 'Georgia', serif
//
// Here are some valid uses of escape sequences:
//
// "Contains a literal \" doublequote", 'Literal \' Singlequote', "\\ Literal backslash", '\\ another'
//
// This syntax has the happy side effect that most CSS "font-family" rules are
// valid Typefaces (without the trailing semicolon).
//
// Generic CSS font families are supported, and are automatically expanded to lists
// of known font families with a matching style. The supported generic families are:
//
// - fantasy
// - math
// - emoji
// - serif
// - sans-serif
// - cursive
// - monospace
type Typeface string
const (
Regular Style = iota
Italic
)
const (
Thin Weight = -300
ExtraLight Weight = -200
Light Weight = -100
Normal Weight = 0
Medium Weight = 100
SemiBold Weight = 200
Bold Weight = 300
ExtraBold Weight = 400
Black Weight = 500
)
func (s Style) String() string {
switch s {
case Regular:
return "Regular"
case Italic:
return "Italic"
default:
panic("invalid Style")
}
}
func (w Weight) String() string {
switch w {
case Thin:
return "Thin"
case ExtraLight:
return "ExtraLight"
case Light:
return "Light"
case Normal:
return "Normal"
case Medium:
return "Medium"
case SemiBold:
return "SemiBold"
case Bold:
return "Bold"
case ExtraBold:
return "ExtraBold"
case Black:
return "Black"
default:
panic("invalid Weight")
}
}

View File

@@ -1,83 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Package gofont exports the Go fonts as a text.Collection.
//
// See https://blog.golang.org/go-fonts for a description of the
// fonts, and the golang.org/x/image/font/gofont packages for the
// font data.
package gofont
import (
"fmt"
"sync"
"golang.org/x/image/font/gofont/gobold"
"golang.org/x/image/font/gofont/gobolditalic"
"golang.org/x/image/font/gofont/goitalic"
"golang.org/x/image/font/gofont/gomedium"
"golang.org/x/image/font/gofont/gomediumitalic"
"golang.org/x/image/font/gofont/gomono"
"golang.org/x/image/font/gofont/gomonobold"
"golang.org/x/image/font/gofont/gomonobolditalic"
"golang.org/x/image/font/gofont/gomonoitalic"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/font/gofont/gosmallcaps"
"golang.org/x/image/font/gofont/gosmallcapsitalic"
"gioui.org/font"
"gioui.org/font/opentype"
)
var (
regOnce sync.Once
reg []font.FontFace
once sync.Once
collection []font.FontFace
)
func loadRegular() {
regOnce.Do(func() {
faces, err := opentype.ParseCollection(goregular.TTF)
if err != nil {
panic(fmt.Errorf("failed to parse font: %v", err))
}
reg = faces
collection = append(collection, reg[0])
})
}
// Regular returns a collection of only the Go regular font face.
func Regular() []font.FontFace {
loadRegular()
return reg
}
// Regular returns a collection of all available Go font faces.
func Collection() []font.FontFace {
loadRegular()
once.Do(func() {
register(goitalic.TTF)
register(gobold.TTF)
register(gobolditalic.TTF)
register(gomedium.TTF)
register(gomediumitalic.TTF)
register(gomono.TTF)
register(gomonobold.TTF)
register(gomonobolditalic.TTF)
register(gomonoitalic.TTF)
register(gosmallcaps.TTF)
register(gosmallcapsitalic.TTF)
// Ensure that any outside appends will not reuse the backing store.
n := len(collection)
collection = collection[:n:n]
})
return collection
}
func register(ttf []byte) {
faces, err := opentype.ParseCollection(ttf)
if err != nil {
panic(fmt.Errorf("failed to parse font: %v", err))
}
collection = append(collection, faces[0])
}

View File

@@ -1,192 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Package opentype implements text layout and shaping for OpenType
// files.
//
// NOTE: the OpenType specification allows for fonts to include bitmap images
// in a variety of formats. In the interest of small binary sizes, the opentype
// package only automatically imports the PNG image decoder. If you have a font
// with glyphs in JPEG or TIFF formats, register those decoders with the image
// package in order to ensure those glyphs are visible in text.
package opentype
import (
"bytes"
"fmt"
_ "image/png"
giofont "gioui.org/font"
"github.com/go-text/typesetting/font"
fontapi "github.com/go-text/typesetting/opentype/api/font"
"github.com/go-text/typesetting/opentype/api/metadata"
"github.com/go-text/typesetting/opentype/loader"
)
// Face is a thread-safe representation of a loaded font. For efficiency, applications
// should construct a face for any given font file once, reusing it across different
// text shapers.
type Face struct {
face font.Font
font giofont.Font
}
// Parse constructs a Face from source bytes.
func Parse(src []byte) (Face, error) {
ld, err := loader.NewLoader(bytes.NewReader(src))
if err != nil {
return Face{}, err
}
font, md, err := parseLoader(ld)
if err != nil {
return Face{}, fmt.Errorf("failed parsing truetype font: %w", err)
}
return Face{
face: font,
font: md,
}, nil
}
// ParseCollection parse an Opentype font file, with support for collections.
// Single font files are supported, returning a slice with length 1.
// The returned fonts are automatically wrapped in a text.FontFace with
// inferred font metadata.
// BUG(whereswaldon): the only Variant that can be detected automatically is
// "Mono".
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
lds, err := loader.NewLoaders(bytes.NewReader(src))
if err != nil {
return nil, err
}
out := make([]giofont.FontFace, len(lds))
for i, ld := range lds {
face, md, err := parseLoader(ld)
if err != nil {
return nil, fmt.Errorf("reading font %d of collection: %s", i, err)
}
ff := Face{
face: face,
font: md,
}
out[i] = giofont.FontFace{
Face: ff,
Font: ff.Font(),
}
}
return out, nil
}
func DescriptionToFont(md metadata.Description) giofont.Font {
return giofont.Font{
Typeface: giofont.Typeface(md.Family),
Style: gioStyle(md.Aspect.Style),
Weight: gioWeight(md.Aspect.Weight),
}
}
func FontToDescription(font giofont.Font) metadata.Description {
return metadata.Description{
Family: string(font.Typeface),
Aspect: metadata.Aspect{
Style: mdStyle(font.Style),
Weight: mdWeight(font.Weight),
},
}
}
// parseLoader parses the contents of the loader into a face and its metadata.
func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
ft, err := fontapi.NewFont(ld)
if err != nil {
return nil, giofont.Font{}, err
}
data := DescriptionToFont(metadata.Metadata(ld))
return ft, data, nil
}
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
// Face many be invoked any number of times and is safe so long as each return value is
// only used by one goroutine.
func (f Face) Face() font.Face {
return &fontapi.Face{Font: f.face}
}
// FontFace returns a text.Font with populated font metadata for the
// font.
// BUG(whereswaldon): the only Variant that can be detected automatically is
// "Mono".
func (f Face) Font() giofont.Font {
return f.font
}
func gioStyle(s metadata.Style) giofont.Style {
switch s {
case metadata.StyleItalic:
return giofont.Italic
case metadata.StyleNormal:
fallthrough
default:
return giofont.Regular
}
}
func mdStyle(g giofont.Style) metadata.Style {
switch g {
case giofont.Italic:
return metadata.StyleItalic
case giofont.Regular:
fallthrough
default:
return metadata.StyleNormal
}
}
func gioWeight(w metadata.Weight) giofont.Weight {
switch w {
case metadata.WeightThin:
return giofont.Thin
case metadata.WeightExtraLight:
return giofont.ExtraLight
case metadata.WeightLight:
return giofont.Light
case metadata.WeightNormal:
return giofont.Normal
case metadata.WeightMedium:
return giofont.Medium
case metadata.WeightSemibold:
return giofont.SemiBold
case metadata.WeightBold:
return giofont.Bold
case metadata.WeightExtraBold:
return giofont.ExtraBold
case metadata.WeightBlack:
return giofont.Black
default:
return giofont.Normal
}
}
func mdWeight(g giofont.Weight) metadata.Weight {
switch g {
case giofont.Thin:
return metadata.WeightThin
case giofont.ExtraLight:
return metadata.WeightExtraLight
case giofont.Light:
return metadata.WeightLight
case giofont.Normal:
return metadata.WeightNormal
case giofont.Medium:
return metadata.WeightMedium
case giofont.SemiBold:
return metadata.WeightSemibold
case giofont.Bold:
return metadata.WeightBold
case giofont.ExtraBold:
return metadata.WeightExtraBold
case giofont.Black:
return metadata.WeightBlack
default:
return metadata.WeightNormal
}
}

471
vendor/gioui.org/gesture/gesture.go generated vendored
View File

@@ -1,471 +0,0 @@
// 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 (
"image"
"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
// Hover detects the hover gesture for a pointer area.
type Hover struct {
// entered tracks whether the pointer is inside the gesture.
entered bool
// pid is the pointer.ID.
pid pointer.ID
}
// Add the gesture to detect hovering over the current pointer area.
func (h *Hover) Add(ops *op.Ops) {
pointer.InputOp{
Tag: h,
Types: pointer.Enter | pointer.Leave,
}.Add(ops)
}
// Hovered returns whether a pointer is inside the area.
func (h *Hover) Hovered(q event.Queue) bool {
for _, ev := range q.Events(h) {
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Leave, pointer.Cancel:
if h.entered && h.pid == e.PointerID {
h.entered = false
}
case pointer.Enter:
if !h.entered {
h.pid = e.PointerID
}
if h.pid == e.PointerID {
h.entered = true
}
}
}
return h.entered
}
// Click detects click gestures in the form
// of ClickEvents.
type Click struct {
// 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
// hovered tracks whether the pointer is inside the gesture.
hovered bool
// entered tracks whether an Enter event has been received.
entered bool
// pid is the pointer.ID.
pid pointer.ID
}
// 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 image.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
pressed 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
Both
)
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
// StateDragging is reported during drag gestures.
StateDragging
// StateFlinging is reported when a fling is
// in progress.
StateFlinging
)
const touchSlop = unit.Dp(3)
// Add the handler to the operation list to receive click events.
func (c *Click) Add(ops *op.Ops) {
pointer.InputOp{
Tag: c,
Types: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave,
}.Add(ops)
}
// Hovered returns whether a pointer is inside the area.
func (c *Click) Hovered() bool {
return c.hovered
}
// Pressed returns whether a pointer is pressing.
func (c *Click) Pressed() bool {
return c.pressed
}
// Events returns the next click events, 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 || c.hovered {
events = append(events, ClickEvent{Type: TypeClick, Position: e.Position.Round(), 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.hovered = 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.ButtonPrimary {
break
}
if !c.hovered {
c.pid = e.PointerID
}
if c.pid != e.PointerID {
break
}
c.pressed = true
if e.Time-c.clickedAt < doubleClickDuration {
c.clicks++
} else {
c.clicks = 1
}
c.clickedAt = e.Time
events = append(events, ClickEvent{Type: TypePress, Position: e.Position.Round(), Source: e.Source, Modifiers: e.Modifiers, NumClicks: c.clicks})
case pointer.Leave:
if !c.pressed {
c.pid = e.PointerID
}
if c.pid == e.PointerID {
c.hovered = false
}
case pointer.Enter:
if !c.pressed {
c.pid = e.PointerID
}
if c.pid == e.PointerID {
c.hovered = true
c.entered = true
}
}
}
return events
}
func (ClickEvent) ImplementsEvent() {}
// Add the handler to the operation list to receive scroll events.
// The bounds variable refers to the scrolling boundaries
// as defined in io/pointer.InputOp.
func (s *Scroll) Add(ops *op.Ops, bounds image.Rectangle) {
oph := pointer.InputOp{
Tag: s,
Grab: s.grab,
Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
ScrollBounds: bounds,
}
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.Dp(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:
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.Dp(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) {
pointer.InputOp{
Tag: d,
Grab: d.grab,
Types: pointer.Press | pointer.Drag | pointer.Release,
}.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.ButtonPrimary || e.Source == pointer.Touch) {
continue
}
d.pressed = true
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
case Both:
// Do nothing
}
if e.Priority < pointer.Grabbed {
diff := e.Position.Sub(d.start)
slop := cfg.Dp(touchSlop)
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
d.grab = true
}
}
case pointer.Release, pointer.Cancel:
d.pressed = false
if !d.dragging || e.PointerID != d.pid {
continue
}
d.dragging = false
d.grab = false
}
events = append(events, e)
}
return events
}
// Dragging reports whether it is currently in use.
func (d *Drag) Dragging() bool { return d.dragging }
// Pressed returns whether a pointer is pressing.
func (d *Drag) Pressed() bool { return d.pressed }
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")
}
}

40
vendor/gioui.org/gpu/api.go generated vendored
View File

@@ -1,40 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import "gioui.org/gpu/internal/driver"
// An API carries the necessary GPU API specific resources to create a Device.
// There is an API type for each supported GPU API such as OpenGL and Direct3D.
type API = driver.API
// A RenderTarget denotes the destination framebuffer for a frame.
type RenderTarget = driver.RenderTarget
// OpenGLRenderTarget is a render target suitable for the OpenGL backend.
type OpenGLRenderTarget = driver.OpenGLRenderTarget
// Direct3D11RenderTarget is a render target suitable for the Direct3D 11 backend.
type Direct3D11RenderTarget = driver.Direct3D11RenderTarget
// MetalRenderTarget is a render target suitable for the Metal backend.
type MetalRenderTarget = driver.MetalRenderTarget
// VulkanRenderTarget is a render target suitable for the Vulkan backend.
type VulkanRenderTarget = driver.VulkanRenderTarget
// OpenGL denotes the OpenGL or OpenGL ES API.
type OpenGL = driver.OpenGL
// Direct3D11 denotes the Direct3D API.
type Direct3D11 = driver.Direct3D11
// Metal denotes the Apple Metal API.
type Metal = driver.Metal
// Vulkan denotes the Vulkan API.
type Vulkan = driver.Vulkan
// ErrDeviceLost is returned from GPU operations when the underlying GPU device
// is lost and should be recreated.
var ErrDeviceLost = driver.ErrDeviceLost

148
vendor/gioui.org/gpu/caches.go generated vendored
View File

@@ -1,148 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"fmt"
"gioui.org/internal/f32"
)
type resourceCache struct {
res map[interface{}]resourceCacheValue
}
type resourceCacheValue struct {
used bool
resource 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[opKey]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 opKey
keep bool
}
func newResourceCache() *resourceCache {
return &resourceCache{
res: make(map[interface{}]resourceCacheValue),
}
}
func (r *resourceCache) get(key interface{}) (resource, bool) {
v, exists := r.res[key]
if !exists {
return nil, false
}
if !v.used {
v.used = true
r.res[key] = v
}
return v.resource, exists
}
func (r *resourceCache) put(key interface{}, val resource) {
v, exists := r.res[key]
if exists && v.used {
panic(fmt.Errorf("key exists, %p", key))
}
v.used = true
v.resource = val
r.res[key] = v
}
func (r *resourceCache) frame() {
for k, v := range r.res {
if v.used {
v.used = false
r.res[k] = v
} else {
delete(r.res, k)
v.resource.release()
}
}
}
func (r *resourceCache) release() {
for _, v := range r.res {
v.resource.release()
}
r.res = nil
}
func newOpCache() *opCache {
return &opCache{
index: make(map[opKey]int),
freelist: make([]int, 0),
cache: make([]opCacheValue, 0),
}
}
func (r *opCache) get(key opKey) (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 opKey, 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
}

146
vendor/gioui.org/gpu/clip.go generated vendored
View File

@@ -1,146 +0,0 @@
package gpu
import (
"encoding/binary"
"math"
"gioui.org/internal/f32"
"gioui.org/internal/stroke"
)
type quadSplitter struct {
bounds f32.Rectangle
contour uint32
d *drawOps
// scratch space used by calls to stroke.SplitCubic
scratch []stroke.QuadSegment
}
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
// inlined code:
// encodeVertex(data, meta, -1, 1, from, ctrl, to)
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
// encodeVertex(data[vertStride*3:], meta, 1, -1, from, ctrl, to)
// this code needs to stay in sync with `vertex.encode`.
bo := binary.LittleEndian
data = data[:vertStride*4]
// encode the main template
bo.PutUint32(data[4:8], meta)
bo.PutUint32(data[8:12], math.Float32bits(from.X))
bo.PutUint32(data[12:16], math.Float32bits(from.Y))
bo.PutUint32(data[16:20], math.Float32bits(ctrl.X))
bo.PutUint32(data[20:24], math.Float32bits(ctrl.Y))
bo.PutUint32(data[24:28], math.Float32bits(to.X))
bo.PutUint32(data[28:32], math.Float32bits(to.Y))
copy(data[vertStride*1:vertStride*2], data[vertStride*0:vertStride*1])
copy(data[vertStride*2:vertStride*3], data[vertStride*0:vertStride*1])
copy(data[vertStride*3:vertStride*4], data[vertStride*0:vertStride*1])
bo.PutUint32(data[vertStride*0:vertStride*0+4], math.Float32bits(nwCorner))
bo.PutUint32(data[vertStride*1:vertStride*1+4], math.Float32bits(neCorner))
bo.PutUint32(data[vertStride*2:vertStride*2+4], math.Float32bits(swCorner))
bo.PutUint32(data[vertStride*3:vertStride*3+4], math.Float32bits(seCorner))
}
const (
nwCorner = 1*0.25 + 0*0.5
neCorner = 1*0.25 + 1*0.5
swCorner = 0*0.25 + 0*0.5
seCorner = 0*0.25 + 1*0.5
)
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 stroke.QuadSegment) {
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 = unionRect(qs.bounds, cbnd)
}
// Union is like f32.Rectangle.Union but ignores empty rectangles.
func unionRect(r, s f32.Rectangle) f32.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
}

2202
vendor/gioui.org/gpu/compute.go generated vendored

File diff suppressed because it is too large Load Diff

129
vendor/gioui.org/gpu/cpu.go generated vendored
View File

@@ -1,129 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"unsafe"
"gioui.org/cpu"
)
// This file contains code specific to running compute shaders on the CPU.
// dispatcher dispatches CPU compute programs across multiple goroutines.
type dispatcher struct {
// done is notified when a worker completes its work slice.
done chan struct{}
// work receives work slice indices. It is closed when the dispatcher is released.
work chan work
// dispatch receives compute jobs, which is then split among workers.
dispatch chan dispatch
// sync receives notification when a Sync completes.
sync chan struct{}
}
type work struct {
ctx *cpu.DispatchContext
index int
}
type dispatch struct {
_type jobType
program *cpu.ProgramInfo
descSet unsafe.Pointer
x, y, z int
}
type jobType uint8
const (
jobDispatch jobType = iota
jobBarrier
jobSync
)
func newDispatcher(workers int) *dispatcher {
d := &dispatcher{
work: make(chan work, workers),
done: make(chan struct{}, workers),
// Leave some room to avoid blocking calls to Dispatch.
dispatch: make(chan dispatch, 20),
sync: make(chan struct{}),
}
for i := 0; i < workers; i++ {
go d.worker()
}
go d.dispatcher()
return d
}
func (d *dispatcher) dispatcher() {
defer close(d.work)
var free []*cpu.DispatchContext
defer func() {
for _, ctx := range free {
ctx.Free()
}
}()
var used []*cpu.DispatchContext
for job := range d.dispatch {
switch job._type {
case jobDispatch:
if len(free) == 0 {
free = append(free, cpu.NewDispatchContext())
}
ctx := free[len(free)-1]
free = free[:len(free)-1]
used = append(used, ctx)
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
for i := 0; i < cap(d.work); i++ {
d.work <- work{
ctx: ctx,
index: i,
}
}
case jobBarrier:
// Wait for all outstanding dispatches to complete.
for i := 0; i < len(used)*cap(d.work); i++ {
<-d.done
}
free = append(free, used...)
used = used[:0]
case jobSync:
d.sync <- struct{}{}
}
}
}
func (d *dispatcher) worker() {
thread := cpu.NewThreadContext()
defer thread.Free()
for w := range d.work {
w.ctx.Dispatch(w.index, thread)
d.done <- struct{}{}
}
}
func (d *dispatcher) Barrier() {
d.dispatch <- dispatch{_type: jobBarrier}
}
func (d *dispatcher) Sync() {
d.dispatch <- dispatch{_type: jobSync}
<-d.sync
}
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
d.dispatch <- dispatch{
_type: jobDispatch,
program: program,
descSet: descSet,
x: x,
y: y,
z: z,
}
}
func (d *dispatcher) Stop() {
close(d.dispatch)
}

1554
vendor/gioui.org/gpu/gpu.go generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// This file exists so this package builds on non-Windows platforms.
package d3d11

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