mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b66dcc436c | |||
| 526db27c75 | |||
| 27193ae8e8 | |||
| 313c488ec3 | |||
| f30e936d9a | |||
| ae3bd2a1e1 | |||
| ae43d18ced | |||
| b4d93379c4 | |||
| b9654eb4eb |
@@ -76,6 +76,21 @@ type MinMaxInfo struct {
|
||||
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
|
||||
@@ -331,6 +346,7 @@ var (
|
||||
_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")
|
||||
@@ -463,6 +479,12 @@ func GetWindowRect(hwnd syscall.Handle) Rect {
|
||||
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 {
|
||||
|
||||
@@ -242,6 +242,9 @@ 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
|
||||
}
|
||||
@@ -255,8 +258,6 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_Return:
|
||||
n = key.NameReturn
|
||||
case C.XKB_KEY_KP_Enter:
|
||||
n = key.NameEnter
|
||||
case C.XKB_KEY_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_Down:
|
||||
@@ -297,9 +298,9 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
n = key.NameF11
|
||||
case C.XKB_KEY_F12:
|
||||
n = key.NameF12
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
n = key.NameTab
|
||||
case 0x20, C.XKB_KEY_KP_Space:
|
||||
case 0x20:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
|
||||
n = key.NameCtrl
|
||||
@@ -309,6 +310,64 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
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
|
||||
}
|
||||
|
||||
+3
-3
@@ -365,11 +365,11 @@ func (w *window) Configure(options []Option) {
|
||||
case Minimized:
|
||||
C.unhideWindow(window)
|
||||
case Maximized:
|
||||
if C.isWindowZoomed(window) != 0 {
|
||||
C.zoomWindow(window)
|
||||
}
|
||||
}
|
||||
w.config.Mode = Windowed
|
||||
if C.isWindowZoomed(window) != 0 {
|
||||
C.zoomWindow(window)
|
||||
}
|
||||
w.setTitle(prev, cnf)
|
||||
if prev.Size != cnf.Size {
|
||||
w.config.Size = cnf.Size
|
||||
|
||||
+48
-44
@@ -32,11 +32,6 @@ type ViewEvent struct {
|
||||
HWND uintptr
|
||||
}
|
||||
|
||||
type winDeltas struct {
|
||||
width int32
|
||||
height int32
|
||||
}
|
||||
|
||||
type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
@@ -55,7 +50,6 @@ type window struct {
|
||||
animating bool
|
||||
focused bool
|
||||
|
||||
deltas winDeltas
|
||||
borderSize image.Point
|
||||
config Config
|
||||
}
|
||||
@@ -192,22 +186,12 @@ func createNativeWindow() (*window, error) {
|
||||
// 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() {
|
||||
r := windows.GetWindowRect(w.hwnd)
|
||||
size := image.Point{
|
||||
X: int(r.Right - r.Left - w.deltas.width),
|
||||
Y: int(r.Bottom - r.Top - w.deltas.height),
|
||||
cr := windows.GetClientRect(w.hwnd)
|
||||
w.config.Size = image.Point{
|
||||
X: int(cr.Right - cr.Left),
|
||||
Y: int(cr.Bottom - cr.Top),
|
||||
}
|
||||
|
||||
// Check the window mode.
|
||||
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
||||
if style&windows.WS_OVERLAPPEDWINDOW == 0 {
|
||||
size = image.Point{
|
||||
X: int(r.Right - r.Left),
|
||||
Y: int(r.Bottom - r.Top),
|
||||
}
|
||||
}
|
||||
w.config.Size = size
|
||||
|
||||
w.borderSize = image.Pt(
|
||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||
@@ -326,10 +310,27 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
w.hwnd = 0
|
||||
windows.PostQuitMessage(0)
|
||||
case windows.WM_NCCALCSIZE:
|
||||
if !w.config.Decorated {
|
||||
// No client areas; we draw decorations ourselves.
|
||||
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:
|
||||
@@ -349,18 +350,26 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
}
|
||||
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) + w.deltas.width,
|
||||
Y: int32(p.Y) + w.deltas.height,
|
||||
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) + w.deltas.width,
|
||||
Y: int32(p.Y) + w.deltas.height,
|
||||
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 {
|
||||
@@ -686,31 +695,26 @@ func (w *window) Configure(options []Option) {
|
||||
showMode = windows.SW_SHOWMAXIMIZED
|
||||
|
||||
case Windowed:
|
||||
windows.SetWindowText(w.hwnd, w.config.Title)
|
||||
style |= winStyle
|
||||
showMode = windows.SW_SHOWNORMAL
|
||||
// Get target for client areaa size.
|
||||
// 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)
|
||||
// Set desired window size.
|
||||
wr.Right = wr.Left + width
|
||||
wr.Bottom = wr.Top + height
|
||||
// Compute client size and position. Note that the client size is
|
||||
// equal to the window size when we are in control of decorations.
|
||||
r := wr
|
||||
if w.config.Decorated {
|
||||
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
||||
}
|
||||
// Calculate difference between client and full window sizes.
|
||||
w.deltas.width = r.Right - wr.Right + wr.Left - r.Left
|
||||
w.deltas.height = r.Bottom - wr.Bottom + wr.Top - r.Top
|
||||
// Set new window size and position.
|
||||
x = wr.Left
|
||||
y = wr.Top
|
||||
width = r.Right - r.Left
|
||||
height = r.Bottom - r.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})
|
||||
@@ -727,7 +731,7 @@ func (w *window) Configure(options []Option) {
|
||||
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
|
||||
windows.ShowWindow(w.hwnd, showMode)
|
||||
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.update()
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.19
|
||||
require (
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
||||
gioui.org/shader v1.0.6
|
||||
gioui.org/shader v1.0.8
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
||||
|
||||
@@ -3,8 +3,8 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8v
|
||||
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.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
|
||||
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
||||
|
||||
+194
-43
@@ -68,22 +68,40 @@ type renderer struct {
|
||||
pather *pather
|
||||
packer packer
|
||||
intersections packer
|
||||
layers packer
|
||||
layerFBOs fboSet
|
||||
}
|
||||
|
||||
type drawOps struct {
|
||||
profile bool
|
||||
reader ops.Reader
|
||||
states []f32.Affine2D
|
||||
transStack []f32.Affine2D
|
||||
vertCache []byte
|
||||
viewport image.Point
|
||||
clear bool
|
||||
clearColor f32color.RGBA
|
||||
imageOps []imageOp
|
||||
pathOps []*pathOp
|
||||
pathOpCache []pathOp
|
||||
qs quadSplitter
|
||||
pathCache *opCache
|
||||
profile bool
|
||||
reader ops.Reader
|
||||
states []f32.Affine2D
|
||||
transStack []f32.Affine2D
|
||||
layers []opacityLayer
|
||||
opacityStack []int
|
||||
vertCache []byte
|
||||
viewport image.Point
|
||||
clear bool
|
||||
clearColor f32color.RGBA
|
||||
imageOps []imageOp
|
||||
pathOps []*pathOp
|
||||
pathOpCache []pathOp
|
||||
qs quadSplitter
|
||||
pathCache *opCache
|
||||
}
|
||||
|
||||
type opacityLayer struct {
|
||||
opacity float32
|
||||
parent int
|
||||
// depth of the opacity stack. Layers of equal depth are
|
||||
// independent and may be packed into one atlas.
|
||||
depth int
|
||||
// opStart and opEnd denote the range of drawOps.imageOps
|
||||
// that belong to the layer.
|
||||
opStart, opEnd int
|
||||
// clip of the layer operations.
|
||||
clip image.Rectangle
|
||||
place placement
|
||||
}
|
||||
|
||||
type drawState struct {
|
||||
@@ -127,7 +145,12 @@ type imageOp struct {
|
||||
clip image.Rectangle
|
||||
material material
|
||||
clipType clipType
|
||||
place placement
|
||||
// place is either a placement in the path fbos or intersection fbos,
|
||||
// depending on clipType.
|
||||
place placement
|
||||
// layerOps is the number of operations this
|
||||
// operation replaces.
|
||||
layerOps int
|
||||
}
|
||||
|
||||
func decodeStrokeOp(data []byte) float32 {
|
||||
@@ -154,10 +177,12 @@ type material struct {
|
||||
// For materialTypeColor.
|
||||
color f32color.RGBA
|
||||
// For materialTypeLinearGradient.
|
||||
color1 f32color.RGBA
|
||||
color2 f32color.RGBA
|
||||
color1 f32color.RGBA
|
||||
color2 f32color.RGBA
|
||||
opacity float32
|
||||
// For materialTypeTexture.
|
||||
data imageOpData
|
||||
tex driver.Texture
|
||||
uvTrans f32.Affine2D
|
||||
}
|
||||
|
||||
@@ -222,8 +247,6 @@ func decodeLinearGradientOp(data []byte) linearGradientOpData {
|
||||
}
|
||||
}
|
||||
|
||||
type clipType uint8
|
||||
|
||||
type resource interface {
|
||||
release()
|
||||
}
|
||||
@@ -273,6 +296,9 @@ type blitUniforms struct {
|
||||
transform [4]float32
|
||||
uvTransformR1 [4]float32
|
||||
uvTransformR2 [4]float32
|
||||
opacity float32
|
||||
fbo float32
|
||||
_ [2]float32
|
||||
}
|
||||
|
||||
type colorUniforms struct {
|
||||
@@ -284,7 +310,7 @@ type gradientUniforms struct {
|
||||
color2 f32color.RGBA
|
||||
}
|
||||
|
||||
type materialType uint8
|
||||
type clipType uint8
|
||||
|
||||
const (
|
||||
clipTypeNone clipType = iota
|
||||
@@ -292,6 +318,8 @@ const (
|
||||
clipTypeIntersection
|
||||
)
|
||||
|
||||
type materialType uint8
|
||||
|
||||
const (
|
||||
materialColor materialType = iota
|
||||
materialLinearGradient
|
||||
@@ -391,6 +419,8 @@ func (g *gpu) frame(target RenderTarget) error {
|
||||
g.coverTimer.begin()
|
||||
g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
|
||||
g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps)
|
||||
g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
|
||||
g.renderer.drawLayers(g.cache, g.drawOps.layers, g.drawOps.imageOps)
|
||||
d := driver.LoadDesc{
|
||||
ClearColor: g.drawOps.clearColor,
|
||||
}
|
||||
@@ -400,7 +430,7 @@ func (g *gpu) frame(target RenderTarget) error {
|
||||
}
|
||||
g.ctx.BeginRenderPass(defFBO, d)
|
||||
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
|
||||
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
|
||||
g.renderer.drawOps(g.cache, false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
|
||||
g.coverTimer.end()
|
||||
g.ctx.EndRenderPass()
|
||||
g.cleanupTimer.begin()
|
||||
@@ -464,15 +494,18 @@ func newRenderer(ctx driver.Device) *renderer {
|
||||
if cap := 8192; maxDim > cap {
|
||||
maxDim = cap
|
||||
}
|
||||
d := image.Pt(maxDim, maxDim)
|
||||
|
||||
r.packer.maxDims = image.Pt(maxDim, maxDim)
|
||||
r.intersections.maxDims = image.Pt(maxDim, maxDim)
|
||||
r.packer.maxDims = d
|
||||
r.intersections.maxDims = d
|
||||
r.layers.maxDims = d
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *renderer) release() {
|
||||
r.pather.release()
|
||||
r.blitter.release()
|
||||
r.layerFBOs.delete(r.ctx, 0)
|
||||
}
|
||||
|
||||
func newBlitter(ctx driver.Device) *blitter {
|
||||
@@ -747,8 +780,7 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
|
||||
ops = ops[:len(ops)-1]
|
||||
continue
|
||||
}
|
||||
sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()}
|
||||
place, ok := r.packer.add(sz)
|
||||
place, ok := r.packer.add(p.clip.Size())
|
||||
if !ok {
|
||||
// The clip area is at most the entire screen. Hopefully no
|
||||
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
||||
@@ -760,6 +792,83 @@ func (r *renderer) packStencils(pops *[]*pathOp) {
|
||||
*pops = ops
|
||||
}
|
||||
|
||||
func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
|
||||
// Make every layer bounds contain nested layers; cull empty layers.
|
||||
for i := len(layers) - 1; i >= 0; i-- {
|
||||
l := layers[i]
|
||||
if l.parent != -1 {
|
||||
b := layers[l.parent].clip
|
||||
layers[l.parent].clip = b.Union(l.clip)
|
||||
}
|
||||
if l.clip.Empty() {
|
||||
layers = append(layers[:i], layers[i+1:]...)
|
||||
}
|
||||
}
|
||||
// Pack layers.
|
||||
r.layers.clear()
|
||||
depth := 0
|
||||
for i := range layers {
|
||||
l := &layers[i]
|
||||
// Only layers of the same depth may be packed together.
|
||||
if l.depth != depth {
|
||||
r.layers.newPage()
|
||||
}
|
||||
place, ok := r.layers.add(l.clip.Size())
|
||||
if !ok {
|
||||
// The layer area is at most the entire screen. Hopefully no
|
||||
// screen is larger than GL_MAX_TEXTURE_SIZE.
|
||||
panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
|
||||
}
|
||||
l.place = place
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
func (r *renderer) drawLayers(cache *resourceCache, layers []opacityLayer, ops []imageOp) {
|
||||
if len(r.layers.sizes) == 0 {
|
||||
return
|
||||
}
|
||||
fbo := -1
|
||||
r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
|
||||
for i := len(layers) - 1; i >= 0; i-- {
|
||||
l := layers[i]
|
||||
if fbo != l.place.Idx {
|
||||
if fbo != -1 {
|
||||
r.ctx.EndRenderPass()
|
||||
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
|
||||
}
|
||||
fbo = l.place.Idx
|
||||
f := r.layerFBOs.fbos[fbo]
|
||||
r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
|
||||
}
|
||||
v := image.Rectangle{
|
||||
Min: l.place.Pos,
|
||||
Max: l.place.Pos.Add(l.clip.Size()),
|
||||
}
|
||||
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
|
||||
f := r.layerFBOs.fbos[fbo]
|
||||
r.drawOps(cache, true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||
sr := f32.FRect(v)
|
||||
uvScale, uvOffset := texSpaceTransform(sr, f.size)
|
||||
uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
|
||||
// Replace layer ops with one textured op.
|
||||
ops[l.opStart] = imageOp{
|
||||
clip: l.clip,
|
||||
material: material{
|
||||
material: materialTexture,
|
||||
tex: f.tex,
|
||||
uvTrans: uvTrans,
|
||||
opacity: l.opacity,
|
||||
},
|
||||
layerOps: l.opEnd - l.opStart - 1,
|
||||
}
|
||||
}
|
||||
if fbo != -1 {
|
||||
r.ctx.EndRenderPass()
|
||||
r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *drawOps) reset(viewport image.Point) {
|
||||
d.profile = false
|
||||
d.viewport = viewport
|
||||
@@ -768,6 +877,8 @@ func (d *drawOps) reset(viewport image.Point) {
|
||||
d.pathOpCache = d.pathOpCache[:0]
|
||||
d.vertCache = d.vertCache[:0]
|
||||
d.transStack = d.transStack[:0]
|
||||
d.layers = d.layers[:0]
|
||||
d.opacityStack = d.opacityStack[:0]
|
||||
}
|
||||
|
||||
func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
|
||||
@@ -866,6 +977,27 @@ loop:
|
||||
state.t = d.transStack[n-1]
|
||||
d.transStack = d.transStack[:n-1]
|
||||
|
||||
case ops.TypePushOpacity:
|
||||
opacity := ops.DecodeOpacity(encOp.Data)
|
||||
parent := -1
|
||||
depth := len(d.opacityStack)
|
||||
if depth > 0 {
|
||||
parent = d.opacityStack[depth-1]
|
||||
}
|
||||
lidx := len(d.layers)
|
||||
d.layers = append(d.layers, opacityLayer{
|
||||
opacity: opacity,
|
||||
parent: parent,
|
||||
depth: depth,
|
||||
opStart: len(d.imageOps),
|
||||
})
|
||||
d.opacityStack = append(d.opacityStack, lidx)
|
||||
case ops.TypePopOpacity:
|
||||
n := len(d.opacityStack)
|
||||
idx := d.opacityStack[n-1]
|
||||
d.layers[idx].opEnd = len(d.imageOps)
|
||||
d.opacityStack = d.opacityStack[:n-1]
|
||||
|
||||
case ops.TypeStroke:
|
||||
quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
|
||||
|
||||
@@ -958,7 +1090,7 @@ loop:
|
||||
mat := state.materialFor(bnd, off, partialTrans, bounds)
|
||||
|
||||
rect := state.cpath == nil || state.cpath.rect
|
||||
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) {
|
||||
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
|
||||
// The image is a uniform opaque color and takes up the whole screen.
|
||||
// Scrap images up to and including this image and set clear color.
|
||||
d.imageOps = d.imageOps[:0]
|
||||
@@ -971,6 +1103,15 @@ loop:
|
||||
clip: bounds,
|
||||
material: mat,
|
||||
}
|
||||
if n := len(d.opacityStack); n > 0 {
|
||||
idx := d.opacityStack[n-1]
|
||||
lb := d.layers[idx].clip
|
||||
if lb.Empty() {
|
||||
d.layers[idx].clip = img.clip
|
||||
} else {
|
||||
d.layers[idx].clip = lb.Union(img.clip)
|
||||
}
|
||||
}
|
||||
|
||||
d.imageOps = append(d.imageOps, img)
|
||||
if clipData != nil {
|
||||
@@ -1000,7 +1141,9 @@ func expandPathOp(p *pathOp, clip image.Rectangle) {
|
||||
}
|
||||
|
||||
func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
|
||||
var m material
|
||||
m := material{
|
||||
opacity: 1.,
|
||||
}
|
||||
switch d.matType {
|
||||
case materialColor:
|
||||
m.material = materialColor
|
||||
@@ -1040,10 +1183,11 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
|
||||
}
|
||||
|
||||
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
|
||||
for _, img := range ops {
|
||||
for i := range ops {
|
||||
img := &ops[i]
|
||||
m := img.material
|
||||
if m.material == materialTexture {
|
||||
r.texHandle(cache, m.data)
|
||||
img.material.tex = r.texHandle(cache, m.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1053,10 +1197,10 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
||||
m := img.material
|
||||
switch m.material {
|
||||
case materialTexture:
|
||||
r.ctx.PrepareTexture(r.texHandle(cache, m.data))
|
||||
r.ctx.PrepareTexture(m.tex)
|
||||
}
|
||||
|
||||
var fbo stencilFBO
|
||||
var fbo FBO
|
||||
switch img.clipType {
|
||||
case clipTypeNone:
|
||||
continue
|
||||
@@ -1069,24 +1213,26 @@ func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
|
||||
func (r *renderer) drawOps(cache *resourceCache, isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) {
|
||||
var coverTex driver.Texture
|
||||
for _, img := range ops {
|
||||
for i := 0; i < len(ops); i++ {
|
||||
img := ops[i]
|
||||
i += img.layerOps
|
||||
m := img.material
|
||||
switch m.material {
|
||||
case materialTexture:
|
||||
r.ctx.BindTexture(0, r.texHandle(cache, m.data))
|
||||
r.ctx.BindTexture(0, m.tex)
|
||||
}
|
||||
drc := img.clip
|
||||
drc := img.clip.Add(opOff)
|
||||
|
||||
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
|
||||
var fbo stencilFBO
|
||||
scale, off := clipSpaceTransform(drc, viewport)
|
||||
var fbo FBO
|
||||
switch img.clipType {
|
||||
case clipTypeNone:
|
||||
p := r.blitter.pipelines[m.material]
|
||||
r.ctx.BindPipeline(p.pipeline)
|
||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||
r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
|
||||
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
||||
continue
|
||||
case clipTypePath:
|
||||
fbo = r.pather.stenciler.cover(img.place.Idx)
|
||||
@@ -1105,11 +1251,11 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
|
||||
p := r.pather.coverer.pipelines[m.material]
|
||||
r.ctx.BindPipeline(p.pipeline)
|
||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||
r.pather.cover(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
||||
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
|
||||
func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
|
||||
p := b.pipelines[mat]
|
||||
b.ctx.BindPipeline(p.pipeline)
|
||||
var uniforms *blitUniforms
|
||||
@@ -1119,18 +1265,23 @@ func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.
|
||||
uniforms = &b.colUniforms.blitUniforms
|
||||
case materialTexture:
|
||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||
b.texUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
b.texUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
uniforms = &b.texUniforms.blitUniforms
|
||||
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
case materialLinearGradient:
|
||||
b.linearGradientUniforms.color1 = col1
|
||||
b.linearGradientUniforms.color2 = col2
|
||||
|
||||
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
|
||||
b.linearGradientUniforms.blitUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
b.linearGradientUniforms.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
uniforms = &b.linearGradientUniforms.blitUniforms
|
||||
uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
|
||||
uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
}
|
||||
uniforms.fbo = 0
|
||||
if fbo {
|
||||
uniforms.fbo = 1
|
||||
}
|
||||
uniforms.opacity = opacity
|
||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||
p.UploadUniforms(b.ctx)
|
||||
b.ctx.DrawArrays(0, 4)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 334 B |
@@ -413,6 +413,22 @@ func TestGapsInPath(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpacity(t *testing.T) {
|
||||
run(t, func(ops *op.Ops) {
|
||||
opc1 := paint.PushOpacity(ops, .3)
|
||||
// Fill screen to exercize the glClear optimization.
|
||||
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
||||
opc2 := paint.PushOpacity(ops, .6)
|
||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
||||
opc2.Pop()
|
||||
opc1.Pop()
|
||||
opc3 := paint.PushOpacity(ops, .6)
|
||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
|
||||
opc3.Pop()
|
||||
}, func(r result) {
|
||||
})
|
||||
}
|
||||
|
||||
// lerp calculates linear interpolation with color b and p.
|
||||
func lerp(a, b f32color.RGBA, p float32) f32color.RGBA {
|
||||
return f32color.RGBA{
|
||||
|
||||
+16
-12
@@ -58,7 +58,7 @@ type coverUniforms struct {
|
||||
uvCoverTransform [4]float32
|
||||
uvTransformR1 [4]float32
|
||||
uvTransformR2 [4]float32
|
||||
_ float32
|
||||
fbo float32
|
||||
}
|
||||
|
||||
type stenciler struct {
|
||||
@@ -90,10 +90,10 @@ type intersectUniforms struct {
|
||||
}
|
||||
|
||||
type fboSet struct {
|
||||
fbos []stencilFBO
|
||||
fbos []FBO
|
||||
}
|
||||
|
||||
type stencilFBO struct {
|
||||
type FBO struct {
|
||||
size image.Point
|
||||
tex driver.Texture
|
||||
}
|
||||
@@ -247,10 +247,10 @@ func newStenciler(ctx driver.Device) *stenciler {
|
||||
return st
|
||||
}
|
||||
|
||||
func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
|
||||
func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
|
||||
// Add fbos.
|
||||
for i := len(s.fbos); i < len(sizes); i++ {
|
||||
s.fbos = append(s.fbos, stencilFBO{})
|
||||
s.fbos = append(s.fbos, FBO{})
|
||||
}
|
||||
// Resize fbos.
|
||||
for i, sz := range sizes {
|
||||
@@ -273,7 +273,7 @@ func (s *fboSet) resize(ctx driver.Device, sizes []image.Point) {
|
||||
if sz.X > max {
|
||||
sz.X = max
|
||||
}
|
||||
tex, err := ctx.NewTexture(driver.TextureFormatFloat, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
|
||||
tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
|
||||
driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -340,15 +340,15 @@ func (s *stenciler) beginIntersect(sizes []image.Point) {
|
||||
// 8 bit coverage is enough, but OpenGL ES only supports single channel
|
||||
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
|
||||
// no floating point support is available.
|
||||
s.intersections.resize(s.ctx, sizes)
|
||||
s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||
}
|
||||
|
||||
func (s *stenciler) cover(idx int) stencilFBO {
|
||||
func (s *stenciler) cover(idx int) FBO {
|
||||
return s.fbos.fbos[idx]
|
||||
}
|
||||
|
||||
func (s *stenciler) begin(sizes []image.Point) {
|
||||
s.fbos.resize(s.ctx, sizes)
|
||||
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
|
||||
}
|
||||
|
||||
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
|
||||
@@ -375,11 +375,11 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pather) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
p.coverer.cover(mat, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
|
||||
func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
|
||||
}
|
||||
|
||||
func (c *coverer) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
|
||||
var uniforms *coverUniforms
|
||||
switch mat {
|
||||
case materialColor:
|
||||
@@ -399,6 +399,10 @@ func (c *coverer) cover(mat materialType, col f32color.RGBA, col1, col2 f32color
|
||||
c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
|
||||
uniforms = &c.texUniforms.coverUniforms
|
||||
}
|
||||
uniforms.fbo = 0
|
||||
if isFBO {
|
||||
uniforms.fbo = 1
|
||||
}
|
||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||
c.pipelines[mat].UploadUniforms(c.ctx)
|
||||
|
||||
+48
-5
@@ -19,6 +19,17 @@ type Ops struct {
|
||||
data []byte
|
||||
// refs hold external references for operations.
|
||||
refs []interface{}
|
||||
// stringRefs provides space for string references, pointers to which will
|
||||
// be stored in refs. Storing a string directly in refs would cause a heap
|
||||
// allocation, to store the string header in an interface value. The backing
|
||||
// array of stringRefs, on the other hand, gets reused between calls to
|
||||
// reset, making string references free on average.
|
||||
//
|
||||
// Appending to stringRefs might reallocate the backing array, which will
|
||||
// leave pointers to the old array in refs. This temporarily causes a slight
|
||||
// increase in memory usage, but this, too, amortizes away as the capacity
|
||||
// of stringRefs approaches its stable maximum.
|
||||
stringRefs []string
|
||||
// nextStateID is the id allocated for the next
|
||||
// StateOp.
|
||||
nextStateID int
|
||||
@@ -40,9 +51,10 @@ const (
|
||||
TypeMacro OpType = iota + firstOpIndex
|
||||
TypeCall
|
||||
TypeDefer
|
||||
TypePushTransform
|
||||
TypeTransform
|
||||
TypePopTransform
|
||||
TypePushOpacity
|
||||
TypePopOpacity
|
||||
TypeInvalidate
|
||||
TypeImage
|
||||
TypePaint
|
||||
@@ -111,6 +123,7 @@ const (
|
||||
ClipStack StackKind = iota
|
||||
TransStack
|
||||
PassStack
|
||||
OpacityStack
|
||||
_StackKind
|
||||
)
|
||||
|
||||
@@ -124,9 +137,10 @@ const (
|
||||
TypeMacroLen = 1 + 4 + 4
|
||||
TypeCallLen = 1 + 4 + 4 + 4 + 4
|
||||
TypeDeferLen = 1
|
||||
TypePushTransformLen = 1 + 4*6
|
||||
TypeTransformLen = 1 + 1 + 4*6
|
||||
TypePopTransformLen = 1
|
||||
TypePushOpacityLen = 1 + 4
|
||||
TypePopOpacityLen = 1
|
||||
TypeRedrawLen = 1 + 8
|
||||
TypeImageLen = 1
|
||||
TypePaintLen = 1
|
||||
@@ -183,8 +197,12 @@ func Reset(o *Ops) {
|
||||
for i := range o.refs {
|
||||
o.refs[i] = nil
|
||||
}
|
||||
for i := range o.stringRefs {
|
||||
o.stringRefs[i] = ""
|
||||
}
|
||||
o.data = o.data[:0]
|
||||
o.refs = o.refs[:0]
|
||||
o.stringRefs = o.stringRefs[:0]
|
||||
o.nextStateID = 0
|
||||
o.version++
|
||||
}
|
||||
@@ -265,12 +283,26 @@ func Write1(o *Ops, n int, ref1 interface{}) []byte {
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write1String(o *Ops, n int, ref1 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref1)
|
||||
o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref2)
|
||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||
@@ -354,6 +386,14 @@ func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
|
||||
return f32.NewAffine2D(a, b, c, d, e, f), push
|
||||
}
|
||||
|
||||
func DecodeOpacity(data []byte) float32 {
|
||||
if OpType(data[0]) != TypePushOpacity {
|
||||
panic("invalid op")
|
||||
}
|
||||
bo := binary.LittleEndian
|
||||
return math.Float32frombits(bo.Uint32(data[1:]))
|
||||
}
|
||||
|
||||
// DecodeSave decodes the state id of a save op.
|
||||
func DecodeSave(data []byte) int {
|
||||
if OpType(data[0]) != TypeSave {
|
||||
@@ -381,9 +421,10 @@ var opProps = [0x100]opProp{
|
||||
TypeMacro: {Size: TypeMacroLen, NumRefs: 0},
|
||||
TypeCall: {Size: TypeCallLen, NumRefs: 1},
|
||||
TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
|
||||
TypePushTransform: {Size: TypePushTransformLen, NumRefs: 0},
|
||||
TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
|
||||
TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
|
||||
TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
|
||||
TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
|
||||
TypeInvalidate: {Size: TypeRedrawLen, NumRefs: 0},
|
||||
TypeImage: {Size: TypeImageLen, NumRefs: 2},
|
||||
TypePaint: {Size: TypePaintLen, NumRefs: 0},
|
||||
@@ -440,12 +481,14 @@ func (t OpType) String() string {
|
||||
return "Call"
|
||||
case TypeDefer:
|
||||
return "Defer"
|
||||
case TypePushTransform:
|
||||
return "PushTransform"
|
||||
case TypeTransform:
|
||||
return "Transform"
|
||||
case TypePopTransform:
|
||||
return "PopTransform"
|
||||
case TypePushOpacity:
|
||||
return "PushOpacity"
|
||||
case TypePopOpacity:
|
||||
return "PopOpacity"
|
||||
case TypeInvalidate:
|
||||
return "Invalidate"
|
||||
case TypeImage:
|
||||
|
||||
@@ -30,7 +30,7 @@ func (h ReadOp) Add(o *op.Ops) {
|
||||
}
|
||||
|
||||
func (h WriteOp) Add(o *op.Ops) {
|
||||
data := ops.Write1(&o.Internal, ops.TypeClipboardWriteLen, &h.Text)
|
||||
data := ops.Write1String(&o.Internal, ops.TypeClipboardWriteLen, h.Text)
|
||||
data[0] = byte(ops.TypeClipboardWrite)
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -323,8 +323,7 @@ func (h InputOp) Add(o *op.Ops) {
|
||||
if h.Tag == nil {
|
||||
panic("Tag must be non-nil")
|
||||
}
|
||||
filter := h.Keys
|
||||
data := ops.Write2(&o.Internal, ops.TypeKeyInputLen, h.Tag, &filter)
|
||||
data := ops.Write2String(&o.Internal, ops.TypeKeyInputLen, h.Tag, string(h.Keys))
|
||||
data[0] = byte(ops.TypeKeyInput)
|
||||
data[1] = byte(h.Hint)
|
||||
}
|
||||
@@ -343,7 +342,7 @@ func (h FocusOp) Add(o *op.Ops) {
|
||||
}
|
||||
|
||||
func (s SnippetOp) Add(o *op.Ops) {
|
||||
data := ops.Write2(&o.Internal, ops.TypeSnippetLen, s.Tag, &s.Text)
|
||||
data := ops.Write2String(&o.Internal, ops.TypeSnippetLen, s.Tag, s.Text)
|
||||
data[0] = byte(ops.TypeSnippet)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[1:], uint32(s.Range.Start))
|
||||
|
||||
+4
-4
@@ -490,11 +490,11 @@ func (q *Router) collect() {
|
||||
}
|
||||
kc.softKeyboard(op.Show)
|
||||
case ops.TypeKeyInput:
|
||||
filter := encOp.Refs[1].(*key.Set)
|
||||
filter := key.Set(*encOp.Refs[1].(*string))
|
||||
op := key.InputOp{
|
||||
Tag: encOp.Refs[0].(event.Tag),
|
||||
Hint: key.InputHint(encOp.Data[1]),
|
||||
Keys: *filter,
|
||||
Keys: filter,
|
||||
}
|
||||
a := pc.currentArea()
|
||||
b := pc.currentAreaBounds()
|
||||
@@ -532,10 +532,10 @@ func (q *Router) collect() {
|
||||
|
||||
// Semantic ops.
|
||||
case ops.TypeSemanticLabel:
|
||||
lbl := encOp.Refs[0].(string)
|
||||
lbl := *encOp.Refs[0].(*string)
|
||||
pc.semanticLabel(lbl)
|
||||
case ops.TypeSemanticDesc:
|
||||
desc := encOp.Refs[0].(string)
|
||||
desc := *encOp.Refs[0].(*string)
|
||||
pc.semanticDesc(desc)
|
||||
case ops.TypeSemanticClass:
|
||||
class := semantic.ClassOp(encOp.Data[1])
|
||||
|
||||
@@ -40,12 +40,12 @@ type SelectedOp bool
|
||||
type DisabledOp bool
|
||||
|
||||
func (l LabelOp) Add(o *op.Ops) {
|
||||
data := ops.Write1(&o.Internal, ops.TypeSemanticLabelLen, string(l))
|
||||
data := ops.Write1String(&o.Internal, ops.TypeSemanticLabelLen, string(l))
|
||||
data[0] = byte(ops.TypeSemanticLabel)
|
||||
}
|
||||
|
||||
func (d DescriptionOp) Add(o *op.Ops) {
|
||||
data := ops.Write1(&o.Internal, ops.TypeSemanticDescLen, string(d))
|
||||
data := ops.Write1String(&o.Internal, ops.TypeSemanticDescLen, string(d))
|
||||
data[0] = byte(ops.TypeSemanticDesc)
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +204,9 @@ func (p *Path) Line(delta f32.Point) {
|
||||
|
||||
// LineTo moves the pen to the absolute point specified, recording a line.
|
||||
func (p *Path) LineTo(to f32.Point) {
|
||||
if to == p.pen {
|
||||
return
|
||||
}
|
||||
data := ops.WriteMulti(p.ops, scene.CommandSize+4)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[0:], uint32(p.contour))
|
||||
@@ -250,6 +253,9 @@ func (p *Path) Quad(ctrl, to f32.Point) {
|
||||
// QuadTo records a quadratic Bézier from the pen to end
|
||||
// with the control point ctrl, with absolute coordinates.
|
||||
func (p *Path) QuadTo(ctrl, to f32.Point) {
|
||||
if ctrl == p.pen && to == p.pen {
|
||||
return
|
||||
}
|
||||
data := ops.WriteMulti(p.ops, scene.CommandSize+4)
|
||||
bo := binary.LittleEndian
|
||||
bo.PutUint32(data[0:], uint32(p.contour))
|
||||
|
||||
@@ -44,6 +44,14 @@ type LinearGradientOp struct {
|
||||
type PaintOp struct {
|
||||
}
|
||||
|
||||
// OpacityStack represents an opacity applied to all painting operations
|
||||
// until Pop is called.
|
||||
type OpacityStack struct {
|
||||
id ops.StackID
|
||||
macroID int
|
||||
ops *ops.Ops
|
||||
}
|
||||
|
||||
// NewImageOp creates an ImageOp backed by src.
|
||||
//
|
||||
// NewImageOp assumes the backing image is immutable, and may cache a
|
||||
@@ -145,3 +153,31 @@ func Fill(ops *op.Ops, c color.NRGBA) {
|
||||
ColorOp{Color: c}.Add(ops)
|
||||
PaintOp{}.Add(ops)
|
||||
}
|
||||
|
||||
// PushOpacity creates a drawing layer with an opacity in the range [0;1].
|
||||
// The layer includes every subsequent drawing operation until [OpacityStack.Pop]
|
||||
// is called.
|
||||
//
|
||||
// The layer is drawn in two steps. First, the layer operations are
|
||||
// drawn to a separate image. Then, the image is blended on top of
|
||||
// the frame, with the opacity used as the blending factor.
|
||||
func PushOpacity(o *op.Ops, opacity float32) OpacityStack {
|
||||
if opacity > 1 {
|
||||
opacity = 1
|
||||
}
|
||||
if opacity < 0 {
|
||||
opacity = 0
|
||||
}
|
||||
id, macroID := ops.PushOp(&o.Internal, ops.OpacityStack)
|
||||
data := ops.Write(&o.Internal, ops.TypePushOpacityLen)
|
||||
bo := binary.LittleEndian
|
||||
data[0] = byte(ops.TypePushOpacity)
|
||||
bo.PutUint32(data[1:], math.Float32bits(opacity))
|
||||
return OpacityStack{ops: &o.Internal, id: id, macroID: macroID}
|
||||
}
|
||||
|
||||
func (t OpacityStack) Pop() {
|
||||
ops.PopOp(t.ops, ops.OpacityStack, t.id, t.macroID)
|
||||
data := ops.Write(t.ops, ops.TypePopOpacityLen)
|
||||
data[0] = byte(ops.TypePopOpacity)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user