2 Commits

Author SHA1 Message Date
Elias Naur 8e47316332 app: [Windows] suppress double-click behaviour for custom decorations
Fixes: https://todo.sr.ht/~eliasnaur/gio/600
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-07-15 13:01:41 +08:00
Chris Waldon 55ae5c5b84 app: [Wayland] prevent recursive scroll event processing
This commit zeroes the accumulated scroll distance on the window before invoking the
event delivery code, since the event delivery code is able to call back into the scroll
processing. Prior to this change, the callback could re-processing the scroll delta
while magnifying it by a factor of 10.

Updates: https://todo.sr.ht/~eliasnaur/gio/599
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-07-08 10:12:04 -04:00
36 changed files with 3088 additions and 846 deletions
+1 -1
View File
@@ -71,4 +71,4 @@ tasks:
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./... CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
- test_ios: | - test_ios: |
cd gio cd gio
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./... CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
+1 -1
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Unlicense OR MIT # SPDX-License-Identifier: Unlicense OR MIT
image: freebsd/latest image: freebsd/13.x
packages: packages:
- libX11 - libX11
- libxkbcommon - libxkbcommon
-4
View File
@@ -259,10 +259,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
} }
private void setHighRefreshRate() { private void setHighRefreshRate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
Context context = getContext(); Context context = getContext();
Display display = context.getDisplay(); Display display = context.getDisplay();
Display.Mode[] supportedModes = display.getSupportedModes(); Display.Mode[] supportedModes = display.getSupportedModes();
+1 -1
View File
@@ -266,6 +266,7 @@ const (
WM_MOUSEWHEEL = 0x020A WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E WM_MOUSEHWHEEL = 0x020E
WM_NCACTIVATE = 0x0086 WM_NCACTIVATE = 0x0086
WM_NCLBUTTONDBLCLK = 0x00A3
WM_NCHITTEST = 0x0084 WM_NCHITTEST = 0x0084
WM_NCCALCSIZE = 0x0083 WM_NCCALCSIZE = 0x0083
WM_PAINT = 0x000F WM_PAINT = 0x000F
@@ -274,7 +275,6 @@ const (
WM_SETFOCUS = 0x0007 WM_SETFOCUS = 0x0007
WM_SHOWWINDOW = 0x0018 WM_SHOWWINDOW = 0x0018
WM_SIZE = 0x0005 WM_SIZE = 0x0005
WM_STYLECHANGED = 0x007D
WM_SYSKEYDOWN = 0x0104 WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105 WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204 WM_RBUTTONDOWN = 0x0204
+2 -3
View File
@@ -4,15 +4,14 @@ package app
import ( import (
"log" "log"
"syscall"
"unsafe" "unsafe"
syscall "golang.org/x/sys/windows"
) )
type logger struct{} type logger struct{}
var ( var (
kernel32 = syscall.NewLazySystemDLL("kernel32") kernel32 = syscall.NewLazyDLL("kernel32")
outputDebugStringW = kernel32.NewProc("OutputDebugStringW") outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
debugView *logger debugView *logger
) )
+2
View File
@@ -195,6 +195,8 @@ type driver interface {
Configure([]Option) Configure([]Option)
// SetCursor updates the current cursor to name. // SetCursor updates the current cursor to name.
SetCursor(cursor pointer.Cursor) SetCursor(cursor pointer.Cursor)
// Wakeup wakes up the event loop and sends a WakeupEvent.
// Wakeup()
// Perform actions on the window. // Perform actions on the window.
Perform(system.Action) Perform(system.Action)
// EditorStateChanged notifies the driver that the editor state changed. // EditorStateChanged notifies the driver that the editor state changed.
+4 -4
View File
@@ -575,7 +575,10 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
if !exist { if !exist {
return return
} }
w.draw(env, false) if w.visible && w.animating {
w.draw(env, false)
callVoidMethod(env, w.view, gioView.postFrameCallback)
}
} }
//export Java_org_gioui_GioView_onBack //export Java_org_gioui_GioView_onBack
@@ -879,9 +882,6 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
}, },
Sync: sync, Sync: sync,
}) })
if w.animating {
callVoidMethod(env, w.view, gioView.postFrameCallback)
}
a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive) a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
if err != nil { if err != nil {
panic(err) panic(err)
+139 -228
View File
@@ -109,14 +109,6 @@ static void makeKeyAndOrderFront(CFTypeRef windowRef) {
} }
} }
static void makeFirstResponder(CFTypeRef windowRef, CFTypeRef viewRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
NSView *view = (__bridge NSView *)viewRef;
[window makeFirstResponder:view];
}
}
static void toggleFullScreen(CFTypeRef windowRef) { static void toggleFullScreen(CFTypeRef windowRef) {
@autoreleasepool { @autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef; NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -246,13 +238,6 @@ static int isWindowZoomed(CFTypeRef windowRef) {
} }
} }
static int isWindowMiniaturized(CFTypeRef windowRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
return window.miniaturized ? 1 : 0;
}
}
static void zoomWindow(CFTypeRef windowRef) { static void zoomWindow(CFTypeRef windowRef) {
@autoreleasepool { @autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef; NSWindow *window = (__bridge NSWindow *)windowRef;
@@ -312,21 +297,6 @@ static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
} }
} }
} }
static void interpretKeyEvents(CFTypeRef viewRef, CFTypeRef eventRef) {
@autoreleasepool {
NSView *view = (__bridge NSView *)viewRef;
NSEvent *event = (__bridge NSEvent *)eventRef;
[view interpretKeyEvents:[NSArray arrayWithObject:event]];
}
}
static int isMiniaturized(CFTypeRef windowRef) {
@autoreleasepool {
NSWindow *window = (__bridge NSWindow *)windowRef;
return window.miniaturized ? 1 : 0;
}
}
*/ */
import "C" import "C"
@@ -348,6 +318,7 @@ type window struct {
view C.CFTypeRef view C.CFTypeRef
w *callbacks w *callbacks
anim bool anim bool
visible bool
displayLink *displayLink displayLink *displayLink
// redraw is a single entry channel for making sure only one // redraw is a single entry channel for making sure only one
// display link redraw request is in flight. // display link redraw request is in flight.
@@ -355,20 +326,9 @@ type window struct {
cursor pointer.Cursor cursor pointer.Cursor
pointerBtns pointer.Buttons pointerBtns pointer.Buttons
loop *eventLoop loop *eventLoop
lastMods C.NSUInteger
scale float32 scale float32
config Config config Config
keysDown map[key.Name]struct{}
// cmdKeys is for storing the current key event while
// waiting for a doCommandBySelector.
cmdKeys cmdKeys
}
type cmdKeys struct {
eventStr string
eventMods key.Modifiers
} }
// launched is closed when applicationDidFinishLaunching is called. // launched is closed when applicationDidFinishLaunching is called.
@@ -407,23 +367,11 @@ func (w *window) WriteClipboard(mime string, s []byte) {
} }
func (w *window) updateWindowMode() { func (w *window) updateWindowMode() {
w.scale = float32(C.getViewBackingScale(w.view))
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
w.config.Size = image.Point{
X: int(wf*w.scale + .5),
Y: int(hf*w.scale + .5),
}
w.config.Mode = Windowed
window := C.windowForView(w.view)
if window == 0 {
return
}
style := int(C.getWindowStyleMask(C.windowForView(w.view))) style := int(C.getWindowStyleMask(C.windowForView(w.view)))
switch { if style&C.NSWindowStyleMaskFullScreen != 0 {
case style&C.NSWindowStyleMaskFullScreen != 0:
w.config.Mode = Fullscreen w.config.Mode = Fullscreen
case C.isWindowZoomed(window) != 0: } else {
w.config.Mode = Maximized w.config.Mode = Windowed
} }
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0 w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
} }
@@ -431,82 +379,105 @@ func (w *window) updateWindowMode() {
func (w *window) Configure(options []Option) { func (w *window) Configure(options []Option) {
screenScale := float32(C.getScreenBackingScale()) screenScale := float32(C.getScreenBackingScale())
cfg := configFor(screenScale) cfg := configFor(screenScale)
prev := w.config
w.updateWindowMode()
cnf := w.config cnf := w.config
cnf.apply(cfg, options) cnf.apply(cfg, options)
window := C.windowForView(w.view) window := C.windowForView(w.view)
mask := C.getWindowStyleMask(window)
fullscreen := mask&C.NSWindowStyleMaskFullScreen != 0
switch cnf.Mode { switch cnf.Mode {
case Fullscreen: case Fullscreen:
if C.isWindowMiniaturized(window) != 0 { switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
} fallthrough
if !fullscreen { default:
w.config.Mode = Fullscreen
C.toggleFullScreen(window) C.toggleFullScreen(window)
} }
case Minimized: case Minimized:
C.hideWindow(window) switch prev.Mode {
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
C.hideWindow(window)
}
case Maximized: case Maximized:
if C.isWindowMiniaturized(window) != 0 { switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
} fallthrough
if fullscreen { default:
C.toggleFullScreen(window) w.config.Mode = Maximized
} w.setTitle(prev, cnf)
w.setTitle(cnf.Title) if C.isWindowZoomed(window) == 0 {
if C.isWindowZoomed(window) == 0 { C.zoomWindow(window)
C.zoomWindow(window) }
} }
case Windowed: case Windowed:
if C.isWindowMiniaturized(window) != 0 { switch prev.Mode {
C.unhideWindow(window) case Fullscreen:
}
if fullscreen {
C.toggleFullScreen(window) C.toggleFullScreen(window)
case Minimized:
C.unhideWindow(window)
case Maximized:
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
} }
w.setTitle(cnf.Title) w.config.Mode = Windowed
w.config.Size = cnf.Size w.setTitle(prev, cnf)
cnf.Size = cnf.Size.Div(int(screenScale)) if prev.Size != cnf.Size {
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) w.config.Size = cnf.Size
w.config.MinSize = cnf.MinSize cnf.Size = cnf.Size.Div(int(screenScale))
cnf.MinSize = cnf.MinSize.Div(int(screenScale)) C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) }
w.config.MaxSize = cnf.MaxSize if prev.MinSize != cnf.MinSize {
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) w.config.MinSize = cnf.MinSize
if cnf.MaxSize != (image.Point{}) { 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)) C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
} }
if C.isWindowZoomed(window) != 0 { }
C.zoomWindow(window) 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)
// When toggling the titlebar, the layer doesn't update its frame
// until the next resize. Force it.
C.resetLayerFrame(w.view)
} }
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable) w.ProcessEvent(ConfigEvent{Config: w.config})
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)
// When toggling the titlebar, the layer doesn't update its frame
// until the next resize. Force it.
C.resetLayerFrame(w.view)
} }
func (w *window) setTitle(title string) { func (w *window) setTitle(prev, cnf Config) {
w.config.Title = title if prev.Title != cnf.Title {
titleC := stringToNSString(title) w.config.Title = cnf.Title
defer C.CFRelease(titleC) title := stringToNSString(cnf.Title)
C.setTitle(C.windowForView(w.view), titleC) defer C.CFRelease(title)
C.setTitle(C.windowForView(w.view), title)
}
} }
func (w *window) Perform(acts system.Action) { func (w *window) Perform(acts system.Action) {
@@ -549,8 +520,7 @@ func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) SetAnimating(anim bool) { func (w *window) SetAnimating(anim bool) {
w.anim = anim w.anim = anim
window := C.windowForView(w.view) if w.anim && w.visible {
if w.anim && window != 0 && C.isMiniaturized(window) == 0 {
w.displayLink.Start() w.displayLink.Start()
} else { } else {
w.displayLink.Stop() w.displayLink.Stop()
@@ -568,92 +538,23 @@ func (w *window) runOnMain(f func()) {
} }
//export gio_onKeys //export gio_onKeys
func gio_onKeys(h C.uintptr_t, event C.CFTypeRef, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
w := windowFor(h)
if w.keysDown == nil {
w.keysDown = make(map[key.Name]struct{})
}
str := nsstringToString(cstr) str := nsstringToString(cstr)
kmods := convertMods(mods) kmods := convertMods(mods)
ks := key.Release ks := key.Release
if keyDown { if keyDown {
ks = key.Press ks = key.Press
w.cmdKeys.eventStr = str
w.cmdKeys.eventMods = kmods
C.interpretKeyEvents(w.view, event)
} }
w := windowFor(h)
for _, k := range str { for _, k := range str {
if n, ok := convertKey(k); ok { if n, ok := convertKey(k); ok {
ke := key.Event{ w.ProcessEvent(key.Event{
Name: n, Name: n,
Modifiers: kmods, Modifiers: kmods,
State: ks, State: ks,
}
if keyDown {
w.keysDown[ke.Name] = struct{}{}
if _, isCmd := convertCommandKey(k); isCmd || kmods.Contain(key.ModCommand) {
// doCommandBySelector already processed the event.
return
}
} else {
if _, pressed := w.keysDown[n]; !pressed {
continue
}
delete(w.keysDown, n)
}
w.ProcessEvent(ke)
}
}
}
//export gio_onCommandBySelector
func gio_onCommandBySelector(h C.uintptr_t) C.bool {
w := windowFor(h)
ev := w.cmdKeys
w.cmdKeys = cmdKeys{}
handled := false
for _, k := range ev.eventStr {
n, ok := convertCommandKey(k)
if !ok && ev.eventMods.Contain(key.ModCommand) {
n, ok = convertKey(k)
}
if !ok {
continue
}
ke := key.Event{
Name: n,
Modifiers: ev.eventMods,
State: key.Press,
}
handled = w.processEvent(ke) || handled
}
return C.bool(handled)
}
//export gio_onFlagsChanged
func gio_onFlagsChanged(h C.uintptr_t, curMods C.NSUInteger) {
w := windowFor(h)
mods := []C.NSUInteger{C.NSControlKeyMask, C.NSAlternateKeyMask, C.NSShiftKeyMask, C.NSCommandKeyMask}
keys := []key.Name{key.NameCtrl, key.NameAlt, key.NameShift, key.NameCommand}
for i, mod := range mods {
wasPressed := w.lastMods&mod != 0
isPressed := curMods&mod != 0
if wasPressed != isPressed {
st := key.Release
if isPressed {
st = key.Press
}
w.ProcessEvent(key.Event{
Name: keys[i],
State: st,
}) })
} }
} }
w.lastMods = curMods
} }
//export gio_onText //export gio_onText
@@ -846,15 +747,6 @@ func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRan
//export gio_insertText //export gio_insertText
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) { func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
w := windowFor(h) w := windowFor(h)
str := nsstringToString(cstr)
// macOS IME in some cases calls insertText for command keys such as backspace
// instead of doCommandBySelector.
for _, r := range str {
if _, ok := convertCommandKey(r); ok {
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
return
}
}
state := w.w.EditorState() state := w.w.EditorState()
rng := state.compose rng := state.compose
if rng.Start == -1 { if rng.Start == -1 {
@@ -866,6 +758,7 @@ func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
End: state.RunesIndex(int(crng.location + crng.length)), End: state.RunesIndex(int(crng.location + crng.length)),
} }
} }
str := nsstringToString(cstr)
w.w.EditorReplace(rng, str) w.w.EditorReplace(rng, str)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
start := rng.Start start := rng.Start
@@ -906,19 +799,24 @@ func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRa
} }
func (w *window) draw() { func (w *window) draw() {
cnf := w.config
w.updateWindowMode()
if w.config != cnf {
w.ProcessEvent(ConfigEvent{Config: w.config})
}
select { select {
case <-w.redraw: case <-w.redraw:
default: default:
} }
w.visible = true
if w.anim { if w.anim {
w.SetAnimating(w.anim) w.SetAnimating(w.anim)
} }
sz := w.config.Size 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.ProcessEvent(ConfigEvent{Config: w.config})
}
if sz.X == 0 || sz.Y == 0 { if sz.X == 0 || sz.Y == 0 {
return return
} }
@@ -926,7 +824,7 @@ func (w *window) draw() {
w.ProcessEvent(frameEvent{ w.ProcessEvent(frameEvent{
FrameEvent: FrameEvent{ FrameEvent: FrameEvent{
Now: time.Now(), Now: time.Now(),
Size: sz, Size: w.config.Size,
Metric: cfg, Metric: cfg,
}, },
Sync: true, Sync: true,
@@ -934,13 +832,8 @@ func (w *window) draw() {
} }
func (w *window) ProcessEvent(e event.Event) { func (w *window) ProcessEvent(e event.Event) {
w.processEvent(e) w.w.ProcessEvent(e)
}
func (w *window) processEvent(e event.Event) bool {
handled := w.w.ProcessEvent(e)
w.loop.FlushEvents() w.loop.FlushEvents()
return handled
} }
func (w *window) Event() event.Event { func (w *window) Event() event.Event {
@@ -974,6 +867,7 @@ func gio_onAttached(h C.uintptr_t, attached C.int) {
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)}) w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
} else { } else {
w.ProcessEvent(AppKitViewEvent{}) w.ProcessEvent(AppKitViewEvent{})
w.visible = false
w.SetAnimating(w.anim) w.SetAnimating(w.anim)
} }
} }
@@ -988,6 +882,33 @@ func gio_onDestroy(h C.uintptr_t) {
w.view = 0 w.view = 0
} }
//export gio_onHide
func gio_onHide(h C.uintptr_t) {
w := windowFor(h)
w.visible = false
w.SetAnimating(w.anim)
}
//export gio_onShow
func gio_onShow(h C.uintptr_t) {
w := windowFor(h)
w.draw()
}
//export gio_onFullscreen
func gio_onFullscreen(h C.uintptr_t) {
w := windowFor(h)
w.config.Mode = Fullscreen
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onWindowed
func gio_onWindowed(h C.uintptr_t) {
w := windowFor(h)
w.config.Mode = Windowed
w.ProcessEvent(ConfigEvent{Config: w.config})
}
//export gio_onFinishLaunching //export gio_onFinishLaunching
func gio_onFinishLaunching() { func gio_onFinishLaunching() {
close(launched) close(launched)
@@ -1010,9 +931,10 @@ func newWindow(win *callbacks, options []Option) {
w.ProcessEvent(DestroyEvent{Err: err}) w.ProcessEvent(DestroyEvent{Err: err})
return return
} }
window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y), 0, 0, 0, 0) window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
// Release our reference now that the NSWindow has it. // Release our reference now that the NSWindow has it.
C.CFRelease(w.view) C.CFRelease(w.view)
w.updateWindowMode()
w.Configure(options) w.Configure(options)
if nextTopLeft.x == 0 && nextTopLeft.y == 0 { if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
// cascadeTopLeftFromPoint treats (0, 0) as a no-op, // cascadeTopLeftFromPoint treats (0, 0) as a no-op,
@@ -1020,7 +942,6 @@ func newWindow(win *callbacks, options []Option) {
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
} }
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
C.makeFirstResponder(window, w.view)
// makeKeyAndOrderFront assumes ownership of our window reference. // makeKeyAndOrderFront assumes ownership of our window reference.
C.makeKeyAndOrderFront(window) C.makeKeyAndOrderFront(window)
}) })
@@ -1045,7 +966,9 @@ func (w *window) init(customRenderer bool) error {
return return
} }
w.runOnMain(func() { w.runOnMain(func() {
C.setNeedsDisplay(w.view) if w.visible {
C.setNeedsDisplay(w.view)
}
}) })
}) })
w.displayLink = dl w.displayLink = dl
@@ -1065,10 +988,10 @@ func osMain() {
C.gio_main() C.gio_main()
} }
func convertCommandKey(k rune) (key.Name, bool) { func convertKey(k rune) (key.Name, bool) {
var n key.Name var n key.Name
switch k { switch k {
case '\x1b': // ASCII escape. case 0x1b:
n = key.NameEscape n = key.NameEscape
case C.NSLeftArrowFunctionKey: case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow n = key.NameLeftArrow
@@ -1078,36 +1001,22 @@ func convertCommandKey(k rune) (key.Name, bool) {
n = key.NameUpArrow n = key.NameUpArrow
case C.NSDownArrowFunctionKey: case C.NSDownArrowFunctionKey:
n = key.NameDownArrow n = key.NameDownArrow
case '\r': case 0xd:
n = key.NameReturn n = key.NameReturn
case '\x03': case 0x3:
n = key.NameEnter n = key.NameEnter
case C.NSHomeFunctionKey: case C.NSHomeFunctionKey:
n = key.NameHome n = key.NameHome
case C.NSEndFunctionKey: case C.NSEndFunctionKey:
n = key.NameEnd n = key.NameEnd
case '\x7f', '\b': case 0x7f:
n = key.NameDeleteBackward n = key.NameDeleteBackward
case C.NSDeleteFunctionKey: case C.NSDeleteFunctionKey:
n = key.NameDeleteForward n = key.NameDeleteForward
case '\t', 0x19:
n = key.NameTab
case C.NSPageUpFunctionKey: case C.NSPageUpFunctionKey:
n = key.NamePageUp n = key.NamePageUp
case C.NSPageDownFunctionKey: case C.NSPageDownFunctionKey:
n = key.NamePageDown n = key.NamePageDown
default:
return "", false
}
return n, true
}
func convertKey(k rune) (key.Name, bool) {
if n, ok := convertCommandKey(k); ok {
return n, true
}
var n key.Name
switch k {
case C.NSF1FunctionKey: case C.NSF1FunctionKey:
n = key.NameF1 n = key.NameF1
case C.NSF2FunctionKey: case C.NSF2FunctionKey:
@@ -1132,6 +1041,8 @@ func convertKey(k rune) (key.Name, bool) {
n = key.NameF11 n = key.NameF11
case C.NSF12FunctionKey: case C.NSF12FunctionKey:
n = key.NameF12 n = key.NameF12
case 0x09, 0x19:
n = key.NameTab
case 0x20: case 0x20:
n = key.NameSpace n = key.NameSpace
default: default:
+14 -16
View File
@@ -23,22 +23,22 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWi
- (void)windowWillMiniaturize:(NSNotification *)notification { - (void)windowWillMiniaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onHide(view.handle);
} }
- (void)windowDidDeminiaturize:(NSNotification *)notification { - (void)windowDidDeminiaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onShow(view.handle);
} }
- (void)windowWillEnterFullScreen:(NSNotification *)notification { - (void)windowWillEnterFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onFullscreen(view.handle);
} }
- (void)windowWillExitFullScreen:(NSNotification *)notification { - (void)windowWillExitFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
GioView *view = (GioView *)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onDraw(view.handle); gio_onWindowed(view.handle);
} }
- (void)windowDidChangeScreen:(NSNotification *)notification { - (void)windowDidChangeScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
@@ -132,25 +132,22 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
handleMouse(self, event, MOUSE_SCROLL, dx, dy); handleMouse(self, event, MOUSE_SCROLL, dx, dy);
} }
- (void)keyDown:(NSEvent *)event { - (void)keyDown:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
}
- (void)flagsChanged:(NSEvent *)event {
[self interpretKeyEvents:[NSArray arrayWithObject:event]]; [self interpretKeyEvents:[NSArray arrayWithObject:event]];
gio_onFlagsChanged(self.handle, [event modifierFlags]); NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
} }
- (void)keyUp:(NSEvent *)event { - (void)keyUp:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers]; NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false); gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
} }
- (void)insertText:(id)string { - (void)insertText:(id)string {
gio_onText(self.handle, (__bridge CFTypeRef)string); gio_onText(self.handle, (__bridge CFTypeRef)string);
} }
- (void)doCommandBySelector:(SEL)action { - (void)doCommandBySelector:(SEL)sel {
if (!gio_onCommandBySelector(self.handle)) { // Don't pass commands up the responder chain.
[super doCommandBySelector:action]; // They will end up in a beep.
}
} }
- (BOOL)hasMarkedText { - (BOOL)hasMarkedText {
int res = gio_hasMarkedText(self.handle); int res = gio_hasMarkedText(self.handle);
return res ? YES : NO; return res ? YES : NO;
@@ -205,10 +202,10 @@ static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFl
return [[self window] convertRectToScreen:r]; return [[self window] convertRectToScreen:r];
} }
- (void)applicationWillUnhide:(NSNotification *)notification { - (void)applicationWillUnhide:(NSNotification *)notification {
gio_onDraw(self.handle); gio_onShow(self.handle);
} }
- (void)applicationDidHide:(NSNotification *)notification { - (void)applicationDidHide:(NSNotification *)notification {
gio_onDraw(self.handle); gio_onHide(self.handle);
} }
- (void)dealloc { - (void)dealloc {
gio_onDestroy(self.handle); gio_onDestroy(self.handle);
@@ -390,6 +387,7 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
[window setAcceptsMouseMovedEvents:YES]; [window setAcceptsMouseMovedEvents:YES];
NSView *view = (__bridge NSView *)viewRef; NSView *view = (__bridge NSView *)viewRef;
[window setContentView:view]; [window setContentView:view];
[window makeFirstResponder:view];
window.delegate = globalWindowDel; window.delegate = globalWindowDel;
return (__bridge_retained CFTypeRef)window; return (__bridge_retained CFTypeRef)window;
} }
+53 -56
View File
@@ -46,13 +46,14 @@ type window struct {
cursorIn bool cursorIn bool
cursor syscall.Handle cursor syscall.Handle
// placement saves the previous window position when in full screen mode.
placement *windows.WindowPlacement
animating bool animating bool
borderSize image.Point borderSize image.Point
config Config config Config
// frameDims stores the last seen window frame width and height. loop *eventLoop
frameDims image.Point
loop *eventLoop
} }
const _WM_WAKEUP = windows.WM_USER + iota const _WM_WAKEUP = windows.WM_USER + iota
@@ -107,8 +108,8 @@ func newWindow(win *callbacks, options []Option) {
} }
winMap.Store(w.hwnd, w) winMap.Store(w.hwnd, w)
defer winMap.Delete(w.hwnd) defer winMap.Delete(w.hwnd)
w.Configure(options)
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)}) w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
w.Configure(options)
windows.SetForegroundWindow(w.hwnd) windows.SetForegroundWindow(w.hwnd)
windows.SetFocus(w.hwnd) windows.SetFocus(w.hwnd)
// Since the window class for the cursor is null, // Since the window class for the cursor is null,
@@ -184,39 +185,21 @@ func (w *window) init() error {
return nil return nil
} }
// update handles changes done by the user, and updates the configuration. // update() handles changes done by the user, and updates the configuration.
// It reads the window style and size/position and updates w.config. // It reads the window style and size/position and updates w.config.
// If anything has changed it emits a ConfigEvent to notify the application. // If anything has changed it emits a ConfigEvent to notify the application.
func (w *window) update() { func (w *window) update() {
p := windows.GetWindowPlacement(w.hwnd) cr := windows.GetClientRect(w.hwnd)
if !p.IsMinimized() { w.config.Size = image.Point{
r := windows.GetWindowRect(w.hwnd) X: int(cr.Right - cr.Left),
cr := windows.GetClientRect(w.hwnd) Y: int(cr.Bottom - cr.Top),
w.config.Size = image.Point{
X: int(cr.Right - cr.Left),
Y: int(cr.Bottom - cr.Top),
}
w.frameDims = image.Point{
X: int(r.Right - r.Left),
Y: int(r.Bottom - r.Top),
}.Sub(w.config.Size)
} }
w.borderSize = image.Pt( w.borderSize = image.Pt(
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME), windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME), windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
) )
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
switch {
case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
w.config.Mode = Maximized
case p.IsMaximized():
w.config.Mode = Fullscreen
default:
w.config.Mode = Windowed
}
w.ProcessEvent(ConfigEvent{Config: w.config}) w.ProcessEvent(ConfigEvent{Config: w.config})
w.draw(true)
} }
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
@@ -314,7 +297,6 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
case windows.WM_DESTROY: case windows.WM_DESTROY:
w.ProcessEvent(Win32ViewEvent{}) w.ProcessEvent(Win32ViewEvent{})
w.ProcessEvent(DestroyEvent{}) w.ProcessEvent(DestroyEvent{})
w.w = nil
if w.hdc != 0 { if w.hdc != 0 {
windows.ReleaseDC(w.hdc) windows.ReleaseDC(w.hdc)
w.hdc = 0 w.hdc = 0
@@ -322,7 +304,6 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
// The system destroys the HWND for us. // The system destroys the HWND for us.
w.hwnd = 0 w.hwnd = 0
windows.PostQuitMessage(0) windows.PostQuitMessage(0)
return 0
case windows.WM_NCCALCSIZE: case windows.WM_NCCALCSIZE:
if w.config.Decorated { if w.config.Decorated {
// Let Windows handle decorations. // Let Windows handle decorations.
@@ -345,33 +326,45 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
mi := windows.GetMonitorInfo(w.hwnd) mi := windows.GetMonitorInfo(w.hwnd)
szp.Rgrc[0] = mi.WorkArea szp.Rgrc[0] = mi.WorkArea
return 0 return 0
case windows.WM_NCLBUTTONDBLCLK:
if !w.config.Decorated {
// Override Windows behaviour when we
// draw decorations.
return 0
}
case windows.WM_PAINT: case windows.WM_PAINT:
w.draw(true) w.draw(true)
case windows.WM_STYLECHANGED:
w.update()
case windows.WM_WINDOWPOSCHANGED:
w.update()
case windows.WM_SIZE: case windows.WM_SIZE:
w.update() w.update()
switch wParam {
case windows.SIZE_MINIMIZED:
w.config.Mode = Minimized
case windows.SIZE_MAXIMIZED:
w.config.Mode = Maximized
case windows.SIZE_RESTORED:
if w.config.Mode != Fullscreen {
w.config.Mode = Windowed
}
}
case windows.WM_GETMINMAXINFO: case windows.WM_GETMINMAXINFO:
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam)) mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
var bw, bh int32
var frameDims image.Point
if w.config.Decorated { if w.config.Decorated {
frameDims = w.frameDims 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 { if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
p = p.Add(frameDims)
mm.PtMinTrackSize = windows.Point{ mm.PtMinTrackSize = windows.Point{
X: int32(p.X), X: int32(p.X) + bw,
Y: int32(p.Y), Y: int32(p.Y) + bh,
} }
} }
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 { if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
p = p.Add(frameDims)
mm.PtMaxTrackSize = windows.Point{ mm.PtMaxTrackSize = windows.Point{
X: int32(p.X), X: int32(p.X) + bw,
Y: int32(p.Y), Y: int32(p.Y) + bh,
} }
} }
return 0 return 0
@@ -463,6 +456,9 @@ func getModifiers() key.Modifiers {
// hitTest returns the non-client area hit by the point, needed to // hitTest returns the non-client area hit by the point, needed to
// process WM_NCHITTEST. // process WM_NCHITTEST.
func (w *window) hitTest(x, y int) uintptr { func (w *window) hitTest(x, y int) uintptr {
if w.config.Mode == Fullscreen {
return windows.HTCLIENT
}
if w.config.Mode != Windowed { if w.config.Mode != Windowed {
// Only windowed mode should allow resizing. // Only windowed mode should allow resizing.
return windows.HTCLIENT return windows.HTCLIENT
@@ -572,8 +568,7 @@ func (w *window) runLoop() {
loop: loop:
for { for {
anim := w.animating anim := w.animating
p := windows.GetWindowPlacement(w.hwnd) if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
w.draw(false) w.draw(false)
continue continue
} }
@@ -696,13 +691,8 @@ func (w *window) readClipboard() error {
func (w *window) Configure(options []Option) { func (w *window) Configure(options []Option) {
dpi := windows.GetSystemDPI() dpi := windows.GetSystemDPI()
metric := configForDPI(dpi) metric := configForDPI(dpi)
cnf := w.config w.config.apply(metric, options)
cnf.apply(metric, options) windows.SetWindowText(w.hwnd, w.config.Title)
w.config.Title = cnf.Title
w.config.Decorated = cnf.Decorated
w.config.MinSize = cnf.MinSize
w.config.MaxSize = cnf.MaxSize
windows.SetWindowText(w.hwnd, cnf.Title)
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE) style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
var showMode int32 var showMode int32
@@ -710,7 +700,7 @@ func (w *window) Configure(options []Option) {
swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED) swpStyle := uintptr(windows.SWP_NOZORDER | windows.SWP_FRAMECHANGED)
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW) winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
style &^= winStyle style &^= winStyle
switch cnf.Mode { switch w.config.Mode {
case Minimized: case Minimized:
style |= winStyle style |= winStyle
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
@@ -725,13 +715,13 @@ func (w *window) Configure(options []Option) {
style |= winStyle style |= winStyle
showMode = windows.SW_SHOWNORMAL showMode = windows.SW_SHOWNORMAL
// Get target for client area size. // Get target for client area size.
width = int32(cnf.Size.X) width = int32(w.config.Size.X)
height = int32(cnf.Size.Y) height = int32(w.config.Size.Y)
// Get the current window size and position. // Get the current window size and position.
wr := windows.GetWindowRect(w.hwnd) wr := windows.GetWindowRect(w.hwnd)
x = wr.Left x = wr.Left
y = wr.Top y = wr.Top
if cnf.Decorated { if w.config.Decorated {
// Compute client size and position. Note that the client size is // Compute client size and position. Note that the client size is
// equal to the window size when we are in control of decorations. // equal to the window size when we are in control of decorations.
r := windows.Rect{ r := windows.Rect{
@@ -741,18 +731,25 @@ func (w *window) Configure(options []Option) {
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle) windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
width = r.Right - r.Left width = r.Right - r.Left
height = r.Bottom - r.Top height = r.Bottom - r.Top
} else { }
if !w.config.Decorated {
// Enable drop shadows when we draw decorations. // Enable drop shadows when we draw decorations.
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1}) windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
} }
case Fullscreen: case Fullscreen:
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
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_SHOWMAXIMIZED showMode = windows.SW_SHOWMAXIMIZED
} }
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style) windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle) windows.SetWindowPos(w.hwnd, 0, x, y, width, height, swpStyle)
windows.ShowWindow(w.hwnd, showMode) windows.ShowWindow(w.hwnd, showMode)
w.update()
} }
func (w *window) WriteClipboard(mime string, s []byte) { func (w *window) WriteClipboard(mime string, s []byte) {
+1 -1
View File
@@ -175,7 +175,7 @@ func (c *vkContext) refresh(surf vk.Surface, width, height int) error {
if err != nil { if err != nil {
return err return err
} }
minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps) minExt, maxExt := caps.MinExtent(), caps.MaxExtent()
if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height { if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height {
return errOutOfDate return errOutOfDate
} }
+4 -13
View File
@@ -89,9 +89,6 @@ type Window struct {
} }
imeState editorState imeState editorState
driver driver driver driver
// gpuErr tracks the GPU error that is to be reported when
// the window is closed.
gpuErr error
// invMu protects mayInvalidate. // invMu protects mayInvalidate.
invMu sync.Mutex invMu sync.Mutex
@@ -230,8 +227,7 @@ func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
w.lastFrame.deco.Add(wrapper) w.lastFrame.deco.Add(wrapper)
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil { if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
w.destroyGPU() w.destroyGPU()
w.gpuErr = err w.driver.ProcessEvent(DestroyEvent{Err: err})
w.driver.Perform(system.ActionClose)
return return
} }
w.updateState() w.updateState()
@@ -394,8 +390,6 @@ func (c *callbacks) SetDriver(d driver) {
if d == nil { if d == nil {
panic("nil driver") panic("nil driver")
} }
c.w.invMu.Lock()
defer c.w.invMu.Unlock()
c.w.driver = d c.w.driver = d
} }
@@ -643,14 +637,11 @@ func (w *Window) processEvent(e event.Event) bool {
e2.Size = e2.Size.Sub(offset) e2.Size = e2.Size.Sub(offset)
w.coalesced.frame = &e2 w.coalesced.frame = &e2
case DestroyEvent: case DestroyEvent:
if w.gpuErr != nil {
e2.Err = w.gpuErr
}
w.destroyGPU() w.destroyGPU()
w.invMu.Lock() w.invMu.Lock()
w.mayInvalidate = false w.mayInvalidate = false
w.driver = nil
w.invMu.Unlock() w.invMu.Unlock()
w.driver = nil
if q := w.timer.quit; q != nil { if q := w.timer.quit; q != nil {
q <- struct{}{} q <- struct{}{}
<-q <-q
@@ -665,7 +656,6 @@ func (w *Window) processEvent(e event.Event) bool {
} }
w.coalesced.view = &e2 w.coalesced.view = &e2
case ConfigEvent: case ConfigEvent:
w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
wasFocused := w.decorations.Config.Focused wasFocused := w.decorations.Config.Focused
w.decorations.Config = e2.Config w.decorations.Config = e2.Config
e2.Config = w.effectiveConfig() e2.Config = w.effectiveConfig()
@@ -728,7 +718,7 @@ func (w *Window) Event() event.Event {
if w.driver == nil { if w.driver == nil {
e, ok := w.nextEvent() e, ok := w.nextEvent()
if !ok { if !ok {
panic("window initialization failed without a DestroyEvent") panic("window initializion failed without a DestroyEvent")
} }
return e return e
} }
@@ -811,6 +801,7 @@ func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
default: default:
panic(fmt.Errorf("unknown WindowMode %v", m)) panic(fmt.Errorf("unknown WindowMode %v", m))
} }
deco.Perform(actions)
gtx := layout.Context{ gtx := layout.Context{
Ops: o, Ops: o,
Now: e.Now, Now: e.Now,
Generated
+31 -15
View File
@@ -9,11 +9,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1733430059, "lastModified": 1701721028,
"narHash": "sha256-o3O5tjrMMebRLuHQt7BbEw3jZgWRW5vnOptNXv8WdO4=", "narHash": "sha256-2z4YrdHPLoMZNWR1MPOjNZMqPg057i1eZXaYI6RTahQ=",
"owner": "tadfisher", "owner": "tadfisher",
"repo": "android-nixpkgs", "repo": "android-nixpkgs",
"rev": "d2f3c1ea99c0bea9d28a0e59daeb482f50d4cd35", "rev": "c923f9ec0f4dd0d7dc725dc5b73fbf03658e50dd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -27,14 +27,15 @@
"nixpkgs": [ "nixpkgs": [
"android", "android",
"nixpkgs" "nixpkgs"
] ],
"systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1728330715, "lastModified": 1701697687,
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", "narHash": "sha256-dLLE5wQBVv+pIb4bWmKFSw2DvLVyuEk0F7ng6hpZPSU=",
"owner": "numtide", "owner": "numtide",
"repo": "devshell", "repo": "devshell",
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", "rev": "c3bd77911391eb1638af6ce773de86da57ee6df5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -45,14 +46,14 @@
}, },
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1701680307,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -63,16 +64,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1733261153, "lastModified": 1701282334,
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=", "narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88", "rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.11", "ref": "23.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@@ -97,6 +98,21 @@
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+7 -1
View File
@@ -3,7 +3,7 @@
description = "Gio build environment"; description = "Gio build environment";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; nixpkgs.url = "github:NixOS/nixpkgs/23.11";
android.url = "github:tadfisher/android-nixpkgs"; android.url = "github:tadfisher/android-nixpkgs";
android.inputs.nixpkgs.follows = "nixpkgs"; android.inputs.nixpkgs.follows = "nixpkgs";
}; };
@@ -47,6 +47,12 @@
xorg.libXfixes xorg.libXfixes
libGL libGL
pkg-config pkg-config
] else if stdenv.isDarwin then [
darwin.apple_sdk_11_0.frameworks.Foundation
darwin.apple_sdk_11_0.frameworks.Metal
darwin.apple_sdk_11_0.frameworks.QuartzCore
darwin.apple_sdk_11_0.frameworks.AppKit
darwin.apple_sdk_11_0.MacOSX-SDK
] else [ ]); ] else [ ]);
} // (if stdenv.isLinux then { } // (if stdenv.isLinux then {
LD_LIBRARY_PATH = "${vulkan-loader}/lib"; LD_LIBRARY_PATH = "${vulkan-loader}/lib";
+1 -1
View File
@@ -34,7 +34,7 @@ type Font struct {
// Face is an opaque handle to a typeface. The concrete implementation depends // Face is an opaque handle to a typeface. The concrete implementation depends
// upon the kind of font and shaper in use. // upon the kind of font and shaper in use.
type Face interface { type Face interface {
Face() *font.Face Face() font.Face
} }
// Typeface identifies a list of font families to attempt to use for displaying // Typeface identifies a list of font families to attempt to use for displaying
+43 -41
View File
@@ -16,21 +16,23 @@ import (
_ "image/png" _ "image/png"
giofont "gioui.org/font" giofont "gioui.org/font"
fontapi "github.com/go-text/typesetting/font" "github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/font/opentype" 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 // 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 // should construct a face for any given font file once, reusing it across different
// text shapers. // text shapers.
type Face struct { type Face struct {
face *fontapi.Font face font.Font
font giofont.Font font giofont.Font
} }
// Parse constructs a Face from source bytes. // Parse constructs a Face from source bytes.
func Parse(src []byte) (Face, error) { func Parse(src []byte) (Face, error) {
ld, err := opentype.NewLoader(bytes.NewReader(src)) ld, err := loader.NewLoader(bytes.NewReader(src))
if err != nil { if err != nil {
return Face{}, err return Face{}, err
} }
@@ -47,11 +49,11 @@ func Parse(src []byte) (Face, error) {
// ParseCollection parse an Opentype font file, with support for collections. // ParseCollection parse an Opentype font file, with support for collections.
// Single font files are supported, returning a slice with length 1. // Single font files are supported, returning a slice with length 1.
// The returned fonts are automatically wrapped in a text.FontFace with // The returned fonts are automatically wrapped in a text.FontFace with
// inferred font font. // inferred font metadata.
// BUG(whereswaldon): the only Variant that can be detected automatically is // BUG(whereswaldon): the only Variant that can be detected automatically is
// "Mono". // "Mono".
func ParseCollection(src []byte) ([]giofont.FontFace, error) { func ParseCollection(src []byte) ([]giofont.FontFace, error) {
lds, err := opentype.NewLoaders(bytes.NewReader(src)) lds, err := loader.NewLoaders(bytes.NewReader(src))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -74,7 +76,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
return out, nil return out, nil
} }
func DescriptionToFont(md fontapi.Description) giofont.Font { func DescriptionToFont(md metadata.Description) giofont.Font {
return giofont.Font{ return giofont.Font{
Typeface: giofont.Typeface(md.Family), Typeface: giofont.Typeface(md.Family),
Style: gioStyle(md.Aspect.Style), Style: gioStyle(md.Aspect.Style),
@@ -82,30 +84,30 @@ func DescriptionToFont(md fontapi.Description) giofont.Font {
} }
} }
func FontToDescription(font giofont.Font) fontapi.Description { func FontToDescription(font giofont.Font) metadata.Description {
return fontapi.Description{ return metadata.Description{
Family: string(font.Typeface), Family: string(font.Typeface),
Aspect: fontapi.Aspect{ Aspect: metadata.Aspect{
Style: mdStyle(font.Style), Style: mdStyle(font.Style),
Weight: mdWeight(font.Weight), Weight: mdWeight(font.Weight),
}, },
} }
} }
// parseLoader parses the contents of the loader into a face and its font. // parseLoader parses the contents of the loader into a face and its metadata.
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) { func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) {
ft, err := fontapi.NewFont(ld) ft, err := fontapi.NewFont(ld)
if err != nil { if err != nil {
return nil, giofont.Font{}, err return nil, giofont.Font{}, err
} }
data := DescriptionToFont(ft.Describe()) data := DescriptionToFont(metadata.Metadata(ld))
return ft, data, nil return ft, data, nil
} }
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper. // 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 // Face many be invoked any number of times and is safe so long as each return value is
// only used by one goroutine. // only used by one goroutine.
func (f Face) Face() *fontapi.Face { func (f Face) Face() font.Face {
return &fontapi.Face{Font: f.face} return &fontapi.Face{Font: f.face}
} }
@@ -117,74 +119,74 @@ func (f Face) Font() giofont.Font {
return f.font return f.font
} }
func gioStyle(s fontapi.Style) giofont.Style { func gioStyle(s metadata.Style) giofont.Style {
switch s { switch s {
case fontapi.StyleItalic: case metadata.StyleItalic:
return giofont.Italic return giofont.Italic
case fontapi.StyleNormal: case metadata.StyleNormal:
fallthrough fallthrough
default: default:
return giofont.Regular return giofont.Regular
} }
} }
func mdStyle(g giofont.Style) fontapi.Style { func mdStyle(g giofont.Style) metadata.Style {
switch g { switch g {
case giofont.Italic: case giofont.Italic:
return fontapi.StyleItalic return metadata.StyleItalic
case giofont.Regular: case giofont.Regular:
fallthrough fallthrough
default: default:
return fontapi.StyleNormal return metadata.StyleNormal
} }
} }
func gioWeight(w fontapi.Weight) giofont.Weight { func gioWeight(w metadata.Weight) giofont.Weight {
switch w { switch w {
case fontapi.WeightThin: case metadata.WeightThin:
return giofont.Thin return giofont.Thin
case fontapi.WeightExtraLight: case metadata.WeightExtraLight:
return giofont.ExtraLight return giofont.ExtraLight
case fontapi.WeightLight: case metadata.WeightLight:
return giofont.Light return giofont.Light
case fontapi.WeightNormal: case metadata.WeightNormal:
return giofont.Normal return giofont.Normal
case fontapi.WeightMedium: case metadata.WeightMedium:
return giofont.Medium return giofont.Medium
case fontapi.WeightSemibold: case metadata.WeightSemibold:
return giofont.SemiBold return giofont.SemiBold
case fontapi.WeightBold: case metadata.WeightBold:
return giofont.Bold return giofont.Bold
case fontapi.WeightExtraBold: case metadata.WeightExtraBold:
return giofont.ExtraBold return giofont.ExtraBold
case fontapi.WeightBlack: case metadata.WeightBlack:
return giofont.Black return giofont.Black
default: default:
return giofont.Normal return giofont.Normal
} }
} }
func mdWeight(g giofont.Weight) fontapi.Weight { func mdWeight(g giofont.Weight) metadata.Weight {
switch g { switch g {
case giofont.Thin: case giofont.Thin:
return fontapi.WeightThin return metadata.WeightThin
case giofont.ExtraLight: case giofont.ExtraLight:
return fontapi.WeightExtraLight return metadata.WeightExtraLight
case giofont.Light: case giofont.Light:
return fontapi.WeightLight return metadata.WeightLight
case giofont.Normal: case giofont.Normal:
return fontapi.WeightNormal return metadata.WeightNormal
case giofont.Medium: case giofont.Medium:
return fontapi.WeightMedium return metadata.WeightMedium
case giofont.SemiBold: case giofont.SemiBold:
return fontapi.WeightSemibold return metadata.WeightSemibold
case giofont.Bold: case giofont.Bold:
return fontapi.WeightBold return metadata.WeightBold
case giofont.ExtraBold: case giofont.ExtraBold:
return fontapi.WeightExtraBold return metadata.WeightExtraBold
case giofont.Black: case giofont.Black:
return fontapi.WeightBlack return metadata.WeightBlack
default: default:
return fontapi.WeightNormal return metadata.WeightNormal
} }
} }
+8 -6
View File
@@ -4,11 +4,13 @@ go 1.21
require ( require (
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
gioui.org/shader v1.0.8 gioui.org/shader v1.0.8
github.com/go-text/typesetting v0.2.1 github.com/go-text/typesetting v0.1.1
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
golang.org/x/image v0.18.0 golang.org/x/image v0.5.0
golang.org/x/sys v0.22.0 golang.org/x/sys v0.5.0
golang.org/x/text v0.16.0
) )
require golang.org/x/text v0.9.0
+40 -14
View File
@@ -1,19 +1,45 @@
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/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/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU= 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/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o= golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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=
+2193
View File
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
// 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)
}
+5 -16
View File
@@ -9,11 +9,11 @@ package gpu
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"math" "math"
"os"
"reflect" "reflect"
"time" "time"
"unsafe" "unsafe"
@@ -343,12 +343,13 @@ func New(api API) (GPU, error) {
func NewWithDevice(d driver.Device) (GPU, error) { func NewWithDevice(d driver.Device) (GPU, error) {
d.BeginFrame(nil, false, image.Point{}) d.BeginFrame(nil, false, image.Point{})
defer d.EndFrame() defer d.EndFrame()
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
feats := d.Caps().Features feats := d.Caps().Features
switch { switch {
case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB): case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
return newGPU(d) return newGPU(d)
} }
return nil, errors.New("no available GPU driver") return newCompute(d)
} }
func newGPU(ctx driver.Device) (*gpu, error) { func newGPU(ctx driver.Device) (*gpu, error) {
@@ -1483,7 +1484,7 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
// as needed and feeds them to the supplied splitter. // as needed and feeds them to the supplied splitter.
func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) { func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
for len(pathData) >= scene.CommandSize+4 { for len(pathData) >= scene.CommandSize+4 {
qs.contour = binary.LittleEndian.Uint32(pathData) qs.contour = bo.Uint32(pathData)
cmd := ops.DecodeCommand(pathData[4:]) cmd := ops.DecodeCommand(pathData[4:])
switch cmd.Op() { switch cmd.Op() {
case scene.OpLine: case scene.OpLine:
@@ -1578,15 +1579,3 @@ func isPureOffset(t f32.Affine2D) bool {
a, b, _, d, e, _ := t.Elems() a, b, _, d, e, _ := t.Elems()
return a == 1 && b == 0 && d == 0 && e == 1 return a == 1 && b == 0 && d == 0 && e == 1
} }
func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) {
vert, err = ctx.NewVertexShader(vsrc)
if err != nil {
return
}
frag, err = ctx.NewFragmentShader(fsrc)
if err != nil {
vert.Release()
}
return
}
+2 -2
View File
@@ -862,8 +862,8 @@ func (b *Backend) BindUniforms(buffer driver.Buffer) {
buf := buffer.(*Buffer) buf := buffer.(*Buffer)
cmdBuf := b.currentCmdBuf() cmdBuf := b.currentCmdBuf()
for _, s := range b.pipe.pushRanges { for _, s := range b.pipe.pushRanges {
off := vk.PushConstantRangeOffset(s) off := s.Offset()
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)]) vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
} }
} }
+26 -48
View File
@@ -9,6 +9,8 @@ import (
"unsafe" "unsafe"
syscall "golang.org/x/sys/windows" syscall "golang.org/x/sys/windows"
"gioui.org/internal/gl"
) )
type ( type (
@@ -22,23 +24,23 @@ type (
) )
var ( var (
libEGL = syscall.DLL{} libEGL = syscall.NewLazyDLL("libEGL.dll")
_eglChooseConfig *syscall.Proc _eglChooseConfig = libEGL.NewProc("eglChooseConfig")
_eglCreateContext *syscall.Proc _eglCreateContext = libEGL.NewProc("eglCreateContext")
_eglCreateWindowSurface *syscall.Proc _eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface")
_eglDestroyContext *syscall.Proc _eglDestroyContext = libEGL.NewProc("eglDestroyContext")
_eglDestroySurface *syscall.Proc _eglDestroySurface = libEGL.NewProc("eglDestroySurface")
_eglGetConfigAttrib *syscall.Proc _eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib")
_eglGetDisplay *syscall.Proc _eglGetDisplay = libEGL.NewProc("eglGetDisplay")
_eglGetError *syscall.Proc _eglGetError = libEGL.NewProc("eglGetError")
_eglInitialize *syscall.Proc _eglInitialize = libEGL.NewProc("eglInitialize")
_eglMakeCurrent *syscall.Proc _eglMakeCurrent = libEGL.NewProc("eglMakeCurrent")
_eglReleaseThread *syscall.Proc _eglReleaseThread = libEGL.NewProc("eglReleaseThread")
_eglSwapInterval *syscall.Proc _eglSwapInterval = libEGL.NewProc("eglSwapInterval")
_eglSwapBuffers *syscall.Proc _eglSwapBuffers = libEGL.NewProc("eglSwapBuffers")
_eglTerminate *syscall.Proc _eglTerminate = libEGL.NewProc("eglTerminate")
_eglQueryString *syscall.Proc _eglQueryString = libEGL.NewProc("eglQueryString")
_eglWaitClient *syscall.Proc _eglWaitClient = libEGL.NewProc("eglWaitClient")
) )
var loadOnce sync.Once var loadOnce sync.Once
@@ -52,45 +54,21 @@ func loadEGL() error {
} }
func loadDLLs() error { func loadDLLs() error {
if err := loadDLL(&libEGL, "libEGL.dll"); err != nil { if err := loadDLL(libEGL, "libEGL.dll"); err != nil {
return err return err
} }
if err := loadDLL(gl.LibGLESv2, "libGLESv2.dll"); err != nil {
procs := map[string]**syscall.Proc{ return err
"eglChooseConfig": &_eglChooseConfig,
"eglCreateContext": &_eglCreateContext,
"eglCreateWindowSurface": &_eglCreateWindowSurface,
"eglDestroyContext": &_eglDestroyContext,
"eglDestroySurface": &_eglDestroySurface,
"eglGetConfigAttrib": &_eglGetConfigAttrib,
"eglGetDisplay": &_eglGetDisplay,
"eglGetError": &_eglGetError,
"eglInitialize": &_eglInitialize,
"eglMakeCurrent": &_eglMakeCurrent,
"eglReleaseThread": &_eglReleaseThread,
"eglSwapInterval": &_eglSwapInterval,
"eglSwapBuffers": &_eglSwapBuffers,
"eglTerminate": &_eglTerminate,
"eglQueryString": &_eglQueryString,
"eglWaitClient": &_eglWaitClient,
} }
for name, proc := range procs { // d3dcompiler_47.dll is needed internally for shader compilation to function.
p, err := libEGL.FindProc(name) return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll")
if err != nil {
return fmt.Errorf("failed to locate %s in %s: %w", name, libEGL.Name, err)
}
*proc = p
}
return nil
} }
func loadDLL(dll *syscall.DLL, name string) error { func loadDLL(dll *syscall.LazyDLL, name string) error {
handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) err := dll.Load()
if err != nil { if err != nil {
return fmt.Errorf("egl: failed to load %s: %v", name, err) return fmt.Errorf("egl: failed to load %s: %v", name, err)
} }
dll.Handle = handle
dll.Name = name
return nil return nil
} }
+89 -207
View File
@@ -3,217 +3,103 @@
package gl package gl
import ( import (
"fmt"
"math" "math"
"runtime" "runtime"
"sync"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func loadGLESv2Procs() error {
dllName := "libGLESv2.dll"
handle, err := windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
if err != nil {
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
}
gles := windows.DLL{Handle: handle, Name: dllName}
// d3dcompiler_47.dll is needed internally for shader compilation to function.
dllName = "d3dcompiler_47.dll"
_, err = windows.LoadLibraryEx(dllName, 0, windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
if err != nil {
return fmt.Errorf("gl: failed to load %s: %v", dllName, err)
}
procs := map[string]**windows.Proc{
"glActiveTexture": &_glActiveTexture,
"glAttachShader": &_glAttachShader,
"glBeginQuery": &_glBeginQuery,
"glBindAttribLocation": &_glBindAttribLocation,
"glBindBuffer": &_glBindBuffer,
"glBindBufferBase": &_glBindBufferBase,
"glBindFramebuffer": &_glBindFramebuffer,
"glBindRenderbuffer": &_glBindRenderbuffer,
"glBindTexture": &_glBindTexture,
"glBindVertexArray": &_glBindVertexArray,
"glBlendEquation": &_glBlendEquation,
"glBlendFuncSeparate": &_glBlendFuncSeparate,
"glBufferData": &_glBufferData,
"glBufferSubData": &_glBufferSubData,
"glCheckFramebufferStatus": &_glCheckFramebufferStatus,
"glClear": &_glClear,
"glClearColor": &_glClearColor,
"glClearDepthf": &_glClearDepthf,
"glDeleteQueries": &_glDeleteQueries,
"glDeleteVertexArrays": &_glDeleteVertexArrays,
"glCompileShader": &_glCompileShader,
"glCopyTexSubImage2D": &_glCopyTexSubImage2D,
"glGenerateMipmap": &_glGenerateMipmap,
"glGenBuffers": &_glGenBuffers,
"glGenFramebuffers": &_glGenFramebuffers,
"glGenVertexArrays": &_glGenVertexArrays,
"glGetUniformBlockIndex": &_glGetUniformBlockIndex,
"glCreateProgram": &_glCreateProgram,
"glGenRenderbuffers": &_glGenRenderbuffers,
"glCreateShader": &_glCreateShader,
"glGenTextures": &_glGenTextures,
"glDeleteBuffers": &_glDeleteBuffers,
"glDeleteFramebuffers": &_glDeleteFramebuffers,
"glDeleteProgram": &_glDeleteProgram,
"glDeleteShader": &_glDeleteShader,
"glDeleteRenderbuffers": &_glDeleteRenderbuffers,
"glDeleteTextures": &_glDeleteTextures,
"glDepthFunc": &_glDepthFunc,
"glDepthMask": &_glDepthMask,
"glDisableVertexAttribArray": &_glDisableVertexAttribArray,
"glDisable": &_glDisable,
"glDrawArrays": &_glDrawArrays,
"glDrawElements": &_glDrawElements,
"glEnable": &_glEnable,
"glEnableVertexAttribArray": &_glEnableVertexAttribArray,
"glEndQuery": &_glEndQuery,
"glFinish": &_glFinish,
"glFlush": &_glFlush,
"glFramebufferRenderbuffer": &_glFramebufferRenderbuffer,
"glFramebufferTexture2D": &_glFramebufferTexture2D,
"glGenQueries": &_glGenQueries,
"glGetError": &_glGetError,
"glGetRenderbufferParameteriv": &_glGetRenderbufferParameteriv,
"glGetFloatv": &_glGetFloatv,
"glGetFramebufferAttachmentParameteriv": &_glGetFramebufferAttachmentParameteriv,
"glGetIntegerv": &_glGetIntegerv,
"glGetIntegeri_v": &_glGetIntegeri_v,
"glGetProgramiv": &_glGetProgramiv,
"glGetProgramInfoLog": &_glGetProgramInfoLog,
"glGetQueryObjectuiv": &_glGetQueryObjectuiv,
"glGetShaderiv": &_glGetShaderiv,
"glGetShaderInfoLog": &_glGetShaderInfoLog,
"glGetString": &_glGetString,
"glGetUniformLocation": &_glGetUniformLocation,
"glGetVertexAttribiv": &_glGetVertexAttribiv,
"glGetVertexAttribPointerv": &_glGetVertexAttribPointerv,
"glInvalidateFramebuffer": &_glInvalidateFramebuffer,
"glIsEnabled": &_glIsEnabled,
"glLinkProgram": &_glLinkProgram,
"glPixelStorei": &_glPixelStorei,
"glReadPixels": &_glReadPixels,
"glRenderbufferStorage": &_glRenderbufferStorage,
"glScissor": &_glScissor,
"glShaderSource": &_glShaderSource,
"glTexImage2D": &_glTexImage2D,
"glTexStorage2D": &_glTexStorage2D,
"glTexSubImage2D": &_glTexSubImage2D,
"glTexParameteri": &_glTexParameteri,
"glUniformBlockBinding": &_glUniformBlockBinding,
"glUniform1f": &_glUniform1f,
"glUniform1i": &_glUniform1i,
"glUniform2f": &_glUniform2f,
"glUniform3f": &_glUniform3f,
"glUniform4f": &_glUniform4f,
"glUseProgram": &_glUseProgram,
"glVertexAttribPointer": &_glVertexAttribPointer,
"glViewport": &_glViewport,
}
for name, proc := range procs {
p, err := gles.FindProc(name)
if err != nil {
return fmt.Errorf("failed to locate %s in %s: %w", name, gles.Name, err)
}
*proc = p
}
return nil
}
var ( var (
glInitOnce sync.Once LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll")
_glActiveTexture *windows.Proc _glActiveTexture = LibGLESv2.NewProc("glActiveTexture")
_glAttachShader *windows.Proc _glAttachShader = LibGLESv2.NewProc("glAttachShader")
_glBeginQuery *windows.Proc _glBeginQuery = LibGLESv2.NewProc("glBeginQuery")
_glBindAttribLocation *windows.Proc _glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation")
_glBindBuffer *windows.Proc _glBindBuffer = LibGLESv2.NewProc("glBindBuffer")
_glBindBufferBase *windows.Proc _glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase")
_glBindFramebuffer *windows.Proc _glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer")
_glBindRenderbuffer *windows.Proc _glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer")
_glBindTexture *windows.Proc _glBindTexture = LibGLESv2.NewProc("glBindTexture")
_glBindVertexArray *windows.Proc _glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray")
_glBlendEquation *windows.Proc _glBlendEquation = LibGLESv2.NewProc("glBlendEquation")
_glBlendFuncSeparate *windows.Proc _glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate")
_glBufferData *windows.Proc _glBufferData = LibGLESv2.NewProc("glBufferData")
_glBufferSubData *windows.Proc _glBufferSubData = LibGLESv2.NewProc("glBufferSubData")
_glCheckFramebufferStatus *windows.Proc _glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus")
_glClear *windows.Proc _glClear = LibGLESv2.NewProc("glClear")
_glClearColor *windows.Proc _glClearColor = LibGLESv2.NewProc("glClearColor")
_glClearDepthf *windows.Proc _glClearDepthf = LibGLESv2.NewProc("glClearDepthf")
_glDeleteQueries *windows.Proc _glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries")
_glDeleteVertexArrays *windows.Proc _glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays")
_glCompileShader *windows.Proc _glCompileShader = LibGLESv2.NewProc("glCompileShader")
_glCopyTexSubImage2D *windows.Proc _glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D")
_glGenerateMipmap *windows.Proc _glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap")
_glGenBuffers *windows.Proc _glGenBuffers = LibGLESv2.NewProc("glGenBuffers")
_glGenFramebuffers *windows.Proc _glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers")
_glGenVertexArrays *windows.Proc _glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays")
_glGetUniformBlockIndex *windows.Proc _glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex")
_glCreateProgram *windows.Proc _glCreateProgram = LibGLESv2.NewProc("glCreateProgram")
_glGenRenderbuffers *windows.Proc _glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers")
_glCreateShader *windows.Proc _glCreateShader = LibGLESv2.NewProc("glCreateShader")
_glGenTextures *windows.Proc _glGenTextures = LibGLESv2.NewProc("glGenTextures")
_glDeleteBuffers *windows.Proc _glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers")
_glDeleteFramebuffers *windows.Proc _glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers")
_glDeleteProgram *windows.Proc _glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram")
_glDeleteShader *windows.Proc _glDeleteShader = LibGLESv2.NewProc("glDeleteShader")
_glDeleteRenderbuffers *windows.Proc _glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers")
_glDeleteTextures *windows.Proc _glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures")
_glDepthFunc *windows.Proc _glDepthFunc = LibGLESv2.NewProc("glDepthFunc")
_glDepthMask *windows.Proc _glDepthMask = LibGLESv2.NewProc("glDepthMask")
_glDisableVertexAttribArray *windows.Proc _glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray")
_glDisable *windows.Proc _glDisable = LibGLESv2.NewProc("glDisable")
_glDrawArrays *windows.Proc _glDrawArrays = LibGLESv2.NewProc("glDrawArrays")
_glDrawElements *windows.Proc _glDrawElements = LibGLESv2.NewProc("glDrawElements")
_glEnable *windows.Proc _glEnable = LibGLESv2.NewProc("glEnable")
_glEnableVertexAttribArray *windows.Proc _glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray")
_glEndQuery *windows.Proc _glEndQuery = LibGLESv2.NewProc("glEndQuery")
_glFinish *windows.Proc _glFinish = LibGLESv2.NewProc("glFinish")
_glFlush *windows.Proc _glFlush = LibGLESv2.NewProc("glFlush")
_glFramebufferRenderbuffer *windows.Proc _glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer")
_glFramebufferTexture2D *windows.Proc _glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D")
_glGenQueries *windows.Proc _glGenQueries = LibGLESv2.NewProc("glGenQueries")
_glGetError *windows.Proc _glGetError = LibGLESv2.NewProc("glGetError")
_glGetRenderbufferParameteriv *windows.Proc _glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv")
_glGetFloatv *windows.Proc _glGetFloatv = LibGLESv2.NewProc("glGetFloatv")
_glGetFramebufferAttachmentParameteriv *windows.Proc _glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv")
_glGetIntegerv *windows.Proc _glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv")
_glGetIntegeri_v *windows.Proc _glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v")
_glGetProgramiv *windows.Proc _glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv")
_glGetProgramInfoLog *windows.Proc _glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog")
_glGetQueryObjectuiv *windows.Proc _glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv")
_glGetShaderiv *windows.Proc _glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv")
_glGetShaderInfoLog *windows.Proc _glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog")
_glGetString *windows.Proc _glGetString = LibGLESv2.NewProc("glGetString")
_glGetUniformLocation *windows.Proc _glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation")
_glGetVertexAttribiv *windows.Proc _glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv")
_glGetVertexAttribPointerv *windows.Proc _glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv")
_glInvalidateFramebuffer *windows.Proc _glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer")
_glIsEnabled *windows.Proc _glIsEnabled = LibGLESv2.NewProc("glIsEnabled")
_glLinkProgram *windows.Proc _glLinkProgram = LibGLESv2.NewProc("glLinkProgram")
_glPixelStorei *windows.Proc _glPixelStorei = LibGLESv2.NewProc("glPixelStorei")
_glReadPixels *windows.Proc _glReadPixels = LibGLESv2.NewProc("glReadPixels")
_glRenderbufferStorage *windows.Proc _glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage")
_glScissor *windows.Proc _glScissor = LibGLESv2.NewProc("glScissor")
_glShaderSource *windows.Proc _glShaderSource = LibGLESv2.NewProc("glShaderSource")
_glTexImage2D *windows.Proc _glTexImage2D = LibGLESv2.NewProc("glTexImage2D")
_glTexStorage2D *windows.Proc _glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D")
_glTexSubImage2D *windows.Proc _glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D")
_glTexParameteri *windows.Proc _glTexParameteri = LibGLESv2.NewProc("glTexParameteri")
_glUniformBlockBinding *windows.Proc _glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding")
_glUniform1f *windows.Proc _glUniform1f = LibGLESv2.NewProc("glUniform1f")
_glUniform1i *windows.Proc _glUniform1i = LibGLESv2.NewProc("glUniform1i")
_glUniform2f *windows.Proc _glUniform2f = LibGLESv2.NewProc("glUniform2f")
_glUniform3f *windows.Proc _glUniform3f = LibGLESv2.NewProc("glUniform3f")
_glUniform4f *windows.Proc _glUniform4f = LibGLESv2.NewProc("glUniform4f")
_glUseProgram *windows.Proc _glUseProgram = LibGLESv2.NewProc("glUseProgram")
_glVertexAttribPointer *windows.Proc _glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer")
_glViewport *windows.Proc _glViewport = LibGLESv2.NewProc("glViewport")
) )
type Functions struct { type Functions struct {
@@ -229,11 +115,7 @@ func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
if ctx != nil { if ctx != nil {
panic("non-nil context") panic("non-nil context")
} }
var err error return new(Functions), nil
glInitOnce.Do(func() {
err = loadGLESv2Procs()
})
return new(Functions), err
} }
func (c *Functions) ActiveTexture(t Enum) { func (c *Functions) ActiveTexture(t Enum) {
+6 -6
View File
@@ -1893,27 +1893,27 @@ func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ Descripto
} }
} }
func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags { func (r PushConstantRange) StageFlags() ShaderStageFlags {
return r.stageFlags return r.stageFlags
} }
func PushConstantRangeOffset(r PushConstantRange) int { func (r PushConstantRange) Offset() int {
return int(r.offset) return int(r.offset)
} }
func PushConstantRangeSize(r PushConstantRange) int { func (r PushConstantRange) Size() int {
return int(r.size) return int(r.size)
} }
func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags { func (p QueueFamilyProperties) Flags() QueueFlags {
return p.queueFlags return p.queueFlags
} }
func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) image.Point { func (c SurfaceCapabilities) MinExtent() image.Point {
return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height)) return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
} }
func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) image.Point { func (c SurfaceCapabilities) MaxExtent() image.Point {
return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height)) return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
} }
+6 -6
View File
@@ -60,7 +60,7 @@ type Router struct {
} }
// Source implements the interface between a Router and user interface widgets. // Source implements the interface between a Router and user interface widgets.
// The zero-value Source is disabled. // The value Source is disabled.
type Source struct { type Source struct {
r *Router r *Router
} }
@@ -171,22 +171,22 @@ func (q *Router) Source() Source {
// Execute a command. // Execute a command.
func (s Source) Execute(c Command) { func (s Source) Execute(c Command) {
if !s.enabled() { if !s.Enabled() {
return return
} }
s.r.execute(c) s.r.execute(c)
} }
// enabled reports whether the source is enabled. Only enabled // Enabled reports whether the source is enabled. Only enabled
// Sources deliver events and respond to commands. // Sources deliver events and respond to commands.
func (s Source) enabled() bool { func (s Source) Enabled() bool {
return s.r != nil return s.r != nil
} }
// Focused reports whether tag is focused, according to the most recent // Focused reports whether tag is focused, according to the most recent
// [key.FocusEvent] delivered. // [key.FocusEvent] delivered.
func (s Source) Focused(tag event.Tag) bool { func (s Source) Focused(tag event.Tag) bool {
if !s.enabled() { if !s.Enabled() {
return false return false
} }
return s.r.state().keyState.focus == tag return s.r.state().keyState.focus == tag
@@ -194,7 +194,7 @@ func (s Source) Focused(tag event.Tag) bool {
// Event returns the next event that matches at least one of filters. // Event returns the next event that matches at least one of filters.
func (s Source) Event(filters ...event.Filter) (event.Event, bool) { func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
if !s.enabled() { if !s.Enabled() {
return nil, false return nil, false
} }
return s.r.Event(filters...) return s.r.Event(filters...)
+3 -17
View File
@@ -5,7 +5,6 @@ package layout
import ( import (
"time" "time"
"gioui.org/io/event"
"gioui.org/io/input" "gioui.org/io/input"
"gioui.org/io/system" "gioui.org/io/system"
"gioui.org/op" "gioui.org/op"
@@ -29,7 +28,6 @@ type Context struct {
// Interested users must look up and populate these values manually. // Interested users must look up and populate these values manually.
Locale system.Locale Locale system.Locale
disabled bool
input.Source input.Source
*op.Ops *op.Ops
} }
@@ -44,21 +42,9 @@ func (c Context) Sp(v unit.Sp) int {
return c.Metric.Sp(v) return c.Metric.Sp(v)
} }
func (c Context) Event(filters ...event.Filter) (event.Event, bool) { // Disabled returns a copy of this context with a disabled Source,
if c.disabled { // blocking widgets from changing its state and receiving events.
return nil, false
}
return c.Source.Event(filters...)
}
// Enabled reports whether this context is enabled. Disabled contexts
// don't report events.
func (c Context) Enabled() bool {
return !c.disabled
}
// Disabled returns a copy of this context that don't deliver any events.
func (c Context) Disabled() Context { func (c Context) Disabled() Context {
c.disabled = true c.Source = input.Source{}
return c return c
} }
+90 -41
View File
@@ -12,9 +12,10 @@ import (
"github.com/go-text/typesetting/di" "github.com/go-text/typesetting/di"
"github.com/go-text/typesetting/font" "github.com/go-text/typesetting/font"
gotextot "github.com/go-text/typesetting/font/opentype"
"github.com/go-text/typesetting/fontscan" "github.com/go-text/typesetting/fontscan"
"github.com/go-text/typesetting/language" "github.com/go-text/typesetting/language"
"github.com/go-text/typesetting/opentype/api"
"github.com/go-text/typesetting/opentype/api/metadata"
"github.com/go-text/typesetting/shaping" "github.com/go-text/typesetting/shaping"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
@@ -200,7 +201,7 @@ type runLayout struct {
// Direction is the layout direction of the glyphs. // Direction is the layout direction of the glyphs.
Direction system.TextDirection Direction system.TextDirection
// face is the font face that the ID of each Glyph in the Layout refers to. // face is the font face that the ID of each Glyph in the Layout refers to.
face *font.Face face font.Face
// truncator indicates that this run is a text truncator standing in for remaining // truncator indicates that this run is a text truncator standing in for remaining
// text. // text.
truncator bool truncator bool
@@ -210,8 +211,8 @@ type runLayout struct {
type shaperImpl struct { type shaperImpl struct {
// Fields for tracking fonts/faces. // Fields for tracking fonts/faces.
fontMap *fontscan.FontMap fontMap *fontscan.FontMap
faces []*font.Face faces []font.Face
faceToIndex map[*font.Font]int faceToIndex map[font.Font]int
faceMeta []giofont.Font faceMeta []giofont.Font
defaultFaces []string defaultFaces []string
logger interface { logger interface {
@@ -253,7 +254,7 @@ func newShaperImpl(systemFonts bool, collection []FontFace) *shaperImpl {
var shaper shaperImpl var shaper shaperImpl
shaper.logger = newDebugLogger() shaper.logger = newDebugLogger()
shaper.fontMap = fontscan.NewFontMap(shaper.logger) shaper.fontMap = fontscan.NewFontMap(shaper.logger)
shaper.faceToIndex = make(map[*font.Font]int) shaper.faceToIndex = make(map[font.Font]int)
if systemFonts { if systemFonts {
str, err := os.UserCacheDir() str, err := os.UserCacheDir()
if err != nil { if err != nil {
@@ -281,7 +282,7 @@ func (s *shaperImpl) Load(f FontFace) {
s.addFace(f.Face.Face(), f.Font) s.addFace(f.Face.Face(), f.Font)
} }
func (s *shaperImpl) addFace(f *font.Face, md giofont.Font) { func (s *shaperImpl) addFace(f font.Face, md giofont.Font) {
if _, ok := s.faceToIndex[f.Font]; ok { if _, ok := s.faceToIndex[f.Font]; ok {
return return
} }
@@ -376,11 +377,11 @@ func (s *shaperImpl) splitBidi(input shaping.Input) []shaping.Input {
// ResolveFace allows shaperImpl to implement shaping.FontMap, wrapping its fontMap // ResolveFace allows shaperImpl to implement shaping.FontMap, wrapping its fontMap
// field and ensuring that any faces loaded as part of the search are registered with // field and ensuring that any faces loaded as part of the search are registered with
// ids so that they can be referred to by a GlyphID. // ids so that they can be referred to by a GlyphID.
func (s *shaperImpl) ResolveFace(r rune) *font.Face { func (s *shaperImpl) ResolveFace(r rune) font.Face {
face := s.fontMap.ResolveFace(r) face := s.fontMap.ResolveFace(r)
if face != nil { if face != nil {
family, aspect := s.fontMap.FontMetadata(face.Font) family, aspect := s.fontMap.FontMetadata(face.Font)
md := opentype.DescriptionToFont(font.Description{ md := opentype.DescriptionToFont(metadata.Description{
Family: family, Family: family,
Aspect: aspect, Aspect: aspect,
}) })
@@ -490,11 +491,9 @@ func wrapPolicyToGoText(p WrapPolicy) shaping.LineBreakPolicy {
// shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format. // shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) { func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
wc := shaping.WrapConfig{ wc := shaping.WrapConfig{
Direction: mapDirection(params.Locale.Direction), TruncateAfterLines: params.MaxLines,
TruncateAfterLines: params.MaxLines, TextContinues: params.forceTruncate,
TextContinues: params.forceTruncate, BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
DisableTrailingWhitespaceTrim: params.DisableSpaceTrim,
} }
families := s.defaultFaces families := s.defaultFaces
if params.Font.Typeface != "" { if params.Font.Typeface != "" {
@@ -664,7 +663,7 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
scaleFactor := fixedToFloat(ppem) / float32(face.Upem()) scaleFactor := fixedToFloat(ppem) / float32(face.Upem())
glyphData := face.GlyphData(gid) glyphData := face.GlyphData(gid)
switch glyphData := glyphData.(type) { switch glyphData := glyphData.(type) {
case font.GlyphOutline: case api.GlyphOutline:
outline := glyphData outline := glyphData
// Move to glyph position. // Move to glyph position.
pos := f32.Point{ pos := f32.Point{
@@ -679,9 +678,9 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
for _, fseg := range outline.Segments { for _, fseg := range outline.Segments {
nargs := 1 nargs := 1
switch fseg.Op { switch fseg.Op {
case gotextot.SegmentOpQuadTo: case api.SegmentOpQuadTo:
nargs = 2 nargs = 2
case gotextot.SegmentOpCubeTo: case api.SegmentOpCubeTo:
nargs = 3 nargs = 3
} }
var args [3]f32.Point var args [3]f32.Point
@@ -696,13 +695,13 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
} }
} }
switch fseg.Op { switch fseg.Op {
case gotextot.SegmentOpMoveTo: case api.SegmentOpMoveTo:
builder.Move(args[0]) builder.Move(args[0])
case gotextot.SegmentOpLineTo: case api.SegmentOpLineTo:
builder.Line(args[0]) builder.Line(args[0])
case gotextot.SegmentOpQuadTo: case api.SegmentOpQuadTo:
builder.Quad(args[0], args[1]) builder.Quad(args[0], args[1])
case gotextot.SegmentOpCubeTo: case api.SegmentOpCubeTo:
builder.Cube(args[0], args[1], args[2]) builder.Cube(args[0], args[1], args[2])
default: default:
panic("unsupported segment op") panic("unsupported segment op")
@@ -743,16 +742,16 @@ func (s *shaperImpl) Bitmaps(ops *op.Ops, gs []Glyph) op.CallOp {
} }
glyphData := face.GlyphData(gid) glyphData := face.GlyphData(gid)
switch glyphData := glyphData.(type) { switch glyphData := glyphData.(type) {
case font.GlyphBitmap: case api.GlyphBitmap:
var imgOp paint.ImageOp var imgOp paint.ImageOp
var imgSize image.Point var imgSize image.Point
bitmapData, ok := s.bitmapGlyphCache.Get(g.ID) bitmapData, ok := s.bitmapGlyphCache.Get(g.ID)
if !ok { if !ok {
var img image.Image var img image.Image
switch glyphData.Format { switch glyphData.Format {
case font.PNG, font.JPG, font.TIFF: case api.PNG, api.JPG, api.TIFF:
img, _, _ = image.Decode(bytes.NewReader(glyphData.Data)) img, _, _ = image.Decode(bytes.NewReader(glyphData.Data))
case font.BlackAndWhite: case api.BlackAndWhite:
// This is a complex family of uncompressed bitmaps that don't seem to be // This is a complex family of uncompressed bitmaps that don't seem to be
// very common in practice. We can try adding support later if needed. // very common in practice. We can try adding support later if needed.
fallthrough fallthrough
@@ -808,7 +807,7 @@ type langConfig struct {
} }
// toInput converts its parameters into a shaping.Input. // toInput converts its parameters into a shaping.Input.
func toInput(face *font.Face, ppem fixed.Int26_6, lc langConfig, runes []rune) shaping.Input { func toInput(face font.Face, ppem fixed.Int26_6, lc langConfig, runes []rune) shaping.Input {
var input shaping.Input var input shaping.Input
input.Direction = lc.Direction input.Direction = lc.Direction
input.Text = runes input.Text = runes
@@ -868,14 +867,13 @@ func toGioGlyphs(in []shaping.Glyph, ppem fixed.Int26_6, faceIdx int) []glyph {
} }
// toLine converts the output into a Line with the provided dominant text direction. // toLine converts the output into a Line with the provided dominant text direction.
func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirection) line { func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirection) line {
if len(o) < 1 { if len(o) < 1 {
return line{} return line{}
} }
line := line{ line := line{
runs: make([]runLayout, len(o)), runs: make([]runLayout, len(o)),
direction: dir, direction: dir,
visualOrder: make([]int, len(o)),
} }
maxSize := fixed.Int26_6(0) maxSize := fixed.Int26_6(0)
for i := range o { for i := range o {
@@ -883,7 +881,7 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
if run.Size > maxSize { if run.Size > maxSize {
maxSize = run.Size maxSize = run.Size
} }
var font *font.Font var font font.Font
if run.Face != nil { if run.Face != nil {
font = run.Face.Font font = run.Face.Font
} }
@@ -893,13 +891,11 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
Count: run.Runes.Count, Count: run.Runes.Count,
Offset: line.runeCount, Offset: line.runeCount,
}, },
Direction: unmapDirection(run.Direction), Direction: unmapDirection(run.Direction),
face: run.Face, face: run.Face,
Advance: run.Advance, Advance: run.Advance,
PPEM: run.Size, PPEM: run.Size,
VisualPosition: int(run.VisualIndex),
} }
line.visualOrder[run.VisualIndex] = i
line.runeCount += run.Runes.Count line.runeCount += run.Runes.Count
line.width += run.Advance line.width += run.Advance
if line.ascent < run.LineBounds.Ascent { if line.ascent < run.LineBounds.Ascent {
@@ -910,11 +906,64 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
} }
} }
line.lineHeight = maxSize line.lineHeight = maxSize
// Iterate and resolve the X of each run. computeVisualOrder(&line)
x := fixed.Int26_6(0)
for _, runIdx := range line.visualOrder {
line.runs[runIdx].X = x
x += line.runs[runIdx].Advance
}
return line return line
} }
// computeVisualOrder will populate the Line's VisualOrder field and the
// VisualPosition field of each element in Runs.
func computeVisualOrder(l *line) {
l.visualOrder = make([]int, len(l.runs))
const none = -1
bidiRangeStart := none
// visPos returns the visual position for an individual logically-indexed
// run in this line, taking only the line's overall text direction into
// account.
visPos := func(logicalIndex int) int {
if l.direction.Progression() == system.TowardOrigin {
return len(l.runs) - 1 - logicalIndex
}
return logicalIndex
}
// resolveBidi populated the line's VisualOrder fields for the elements in the
// half-open range [bidiRangeStart:bidiRangeEnd) indicating that those elements
// should be displayed in reverse-visual order.
resolveBidi := func(bidiRangeStart, bidiRangeEnd int) {
firstVisual := bidiRangeEnd - 1
// Just found the end of a bidi range.
for startIdx := bidiRangeStart; startIdx < bidiRangeEnd; startIdx++ {
pos := visPos(firstVisual)
l.runs[startIdx].VisualPosition = pos
l.visualOrder[pos] = startIdx
firstVisual--
}
bidiRangeStart = none
}
for runIdx, run := range l.runs {
if run.Direction.Progression() != l.direction.Progression() {
if bidiRangeStart == none {
bidiRangeStart = runIdx
}
continue
} else if bidiRangeStart != none {
// Just found the end of a bidi range.
resolveBidi(bidiRangeStart, runIdx)
bidiRangeStart = none
}
pos := visPos(runIdx)
l.runs[runIdx].VisualPosition = pos
l.visualOrder[pos] = runIdx
}
if bidiRangeStart != none {
// We ended iteration within a bidi segment, resolve it.
resolveBidi(bidiRangeStart, len(l.runs))
}
// Iterate and resolve the X of each run.
x := fixed.Int26_6(0)
for _, runIdx := range l.visualOrder {
l.runs[runIdx].X = x
x += l.runs[runIdx].Advance
}
}
+115
View File
@@ -3,6 +3,7 @@ package text
import ( import (
"fmt" "fmt"
"math" "math"
"reflect"
"strconv" "strconv"
"testing" "testing"
@@ -449,6 +450,120 @@ func TestToLine(t *testing.T) {
} }
} }
func TestComputeVisualOrder(t *testing.T) {
type testcase struct {
name string
input line
expectedVisualOrder []int
}
for _, tc := range []testcase{
{
name: "ltr",
input: line{
direction: system.LTR,
runs: []runLayout{
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.LTR},
},
},
expectedVisualOrder: []int{0, 1, 2},
},
{
name: "rtl",
input: line{
direction: system.RTL,
runs: []runLayout{
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.RTL},
},
},
expectedVisualOrder: []int{2, 1, 0},
},
{
name: "bidi-ltr",
input: line{
direction: system.LTR,
runs: []runLayout{
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
},
},
expectedVisualOrder: []int{0, 3, 2, 1, 4},
},
{
name: "bidi-ltr-complex",
input: line{
direction: system.LTR,
runs: []runLayout{
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
},
},
expectedVisualOrder: []int{1, 0, 2, 4, 3, 5, 7, 6, 8, 10, 9},
},
{
name: "bidi-rtl",
input: line{
direction: system.RTL,
runs: []runLayout{
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
},
},
expectedVisualOrder: []int{4, 1, 2, 3, 0},
},
{
name: "bidi-rtl-complex",
input: line{
direction: system.RTL,
runs: []runLayout{
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
},
},
expectedVisualOrder: []int{9, 10, 8, 6, 7, 5, 3, 4, 2, 0, 1},
},
} {
t.Run(tc.name, func(t *testing.T) {
computeVisualOrder(&tc.input)
if !reflect.DeepEqual(tc.input.visualOrder, tc.expectedVisualOrder) {
t.Errorf("expected visual order %v, got %v", tc.expectedVisualOrder, tc.input.visualOrder)
}
for i, visualIndex := range tc.input.visualOrder {
if pos := tc.input.runs[visualIndex].VisualPosition; pos != i {
t.Errorf("line.VisualOrder[%d]=%d, but line.Runs[%d].VisualPosition=%d", i, visualIndex, visualIndex, pos)
}
}
})
}
}
func FuzzLayout(f *testing.F) { func FuzzLayout(f *testing.F) {
ltrFace, _ := opentype.Parse(goregular.TTF) ltrFace, _ := opentype.Parse(goregular.TTF)
rtlFace, _ := opentype.Parse(nsareg.TTF) rtlFace, _ := opentype.Parse(nsareg.TTF)
+1 -14
View File
@@ -76,11 +76,6 @@ type Parameters struct {
// text with a MaxLines. It is unexported because this behavior only makes sense for the // text with a MaxLines. It is unexported because this behavior only makes sense for the
// shaper to control when it iterates paragraphs of text. // shaper to control when it iterates paragraphs of text.
forceTruncate bool forceTruncate bool
// DisableSpaceTrim prevents the width of the final whitespace glyph on a line from being zeroed.
// This is desirable for text editors (so that the whitespace can be selected), but is undesirable
// for ordinary display text.
DisableSpaceTrim bool
} }
type FontFace = giofont.FontFace type FontFace = giofont.FontFace
@@ -204,15 +199,7 @@ func (f Flags) String() string {
type GlyphID uint64 type GlyphID uint64
// Shaper converts strings of text into glyphs that can be displayed. The same // Shaper converts strings of text into glyphs that can be displayed.
// Shaper should not be used in different goroutines.
//
// The Shaper controls text layout and has a cache, implemented as a map, and
// so laying out text in two different goroutines can easily result in
// concurrent access to said map, resulting in a panic.
//
// Practically speaking, this means you should use different Shapers for
// different top-level windows.
type Shaper struct { type Shaper struct {
config struct { config struct {
disableSystemFonts bool disableSystemFonts bool
+23 -6
View File
@@ -11,11 +11,8 @@ import (
// Decorations handles the states of window decorations. // Decorations handles the states of window decorations.
type Decorations struct { type Decorations struct {
// Maximized controls the look and behaviour of the maximize
// button. It is the user's responsibility to set Maximized
// according to the window state reported through [app.ConfigEvent].
Maximized bool
clicks map[int]*Clickable clicks map[int]*Clickable
maximized bool
} }
// LayoutMove lays out the widget that makes a window movable. // LayoutMove lays out the widget that makes a window movable.
@@ -43,6 +40,17 @@ func (d *Decorations) Clickable(action system.Action) *Clickable {
return click return click
} }
// Perform updates the decorations as if the specified actions were
// performed by the user.
func (d *Decorations) Perform(actions system.Action) {
if actions&system.ActionMaximize != 0 {
d.maximized = true
}
if actions&(system.ActionUnmaximize|system.ActionMinimize|system.ActionFullscreen) != 0 {
d.maximized = false
}
}
// Update the state and return the set of actions activated by the user. // Update the state and return the set of actions activated by the user.
func (d *Decorations) Update(gtx layout.Context) system.Action { func (d *Decorations) Update(gtx layout.Context) system.Action {
var actions system.Action var actions system.Action
@@ -52,12 +60,21 @@ func (d *Decorations) Update(gtx layout.Context) system.Action {
} }
action := system.Action(1 << idx) action := system.Action(1 << idx)
switch { switch {
case action == system.ActionMaximize && d.Maximized: case action == system.ActionMaximize && d.maximized:
action = system.ActionUnmaximize action = system.ActionUnmaximize
case action == system.ActionUnmaximize && !d.Maximized: case action == system.ActionUnmaximize && !d.maximized:
action = system.ActionMaximize action = system.ActionMaximize
} }
switch action {
case system.ActionMaximize, system.ActionUnmaximize:
d.maximized = !d.maximized
}
actions |= action actions |= action
} }
return actions return actions
} }
// Maximized returns whether the window is maximized.
func (d *Decorations) Maximized() bool {
return d.maximized
}
-1
View File
@@ -610,7 +610,6 @@ func (e *Editor) initBuffer() {
e.text.SingleLine = e.SingleLine e.text.SingleLine = e.SingleLine
e.text.Mask = e.Mask e.text.Mask = e.Mask
e.text.WrapPolicy = e.WrapPolicy e.text.WrapPolicy = e.WrapPolicy
e.text.DisableSpaceTrim = true
} }
// Update the state of the editor in response to input events. Update consumes editor // Update the state of the editor in response to input events. Update consumes editor
+47 -65
View File
@@ -2,18 +2,12 @@ package widget
import ( import (
"bytes" "bytes"
"image"
"image/png"
"io" "io"
"os"
"testing" "testing"
nsareg "eliasnaur.com/font/noto/sans/arabic/regular" nsareg "eliasnaur.com/font/noto/sans/arabic/regular"
"gioui.org/font" "gioui.org/font"
"gioui.org/font/opentype" "gioui.org/font/opentype"
"gioui.org/gpu/headless"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/text" "gioui.org/text"
"golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
@@ -22,11 +16,11 @@ import (
// makePosTestText returns two bidi samples of shaped text at the given // makePosTestText returns two bidi samples of shaped text at the given
// font size and wrapped to the given line width. The runeLimit, if nonzero, // font size and wrapped to the given line width. The runeLimit, if nonzero,
// truncates the sample text to ensure shorter output for expensive tests. // truncates the sample text to ensure shorter output for expensive tests.
func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (shaper *text.Shaper, source string, bidiLTR, bidiRTL []text.Glyph) { func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (source string, bidiLTR, bidiRTL []text.Glyph) {
ltrFace, _ := opentype.Parse(goregular.TTF) ltrFace, _ := opentype.Parse(goregular.TTF)
rtlFace, _ := opentype.Parse(nsareg.TTF) rtlFace, _ := opentype.Parse(nsareg.TTF)
shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection([]font.FontFace{ shaper := text.NewShaper(text.NoSystemFonts(), text.WithCollection([]font.FontFace{
{ {
Font: font.Font{Typeface: "LTR"}, Font: font.Font{Typeface: "LTR"},
Face: ltrFace, Face: ltrFace,
@@ -64,7 +58,7 @@ func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (shaper *text.
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() { for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
bidiRTL = append(bidiRTL, g) bidiRTL = append(bidiRTL, g)
} }
return shaper, bidiSource, bidiLTR, bidiRTL return bidiSource, bidiLTR, bidiRTL
} }
// makeAccountingTestText shapes text designed to stress rune accounting // makeAccountingTestText shapes text designed to stress rune accounting
@@ -266,7 +260,7 @@ func TestIndexPositionWhitespace(t *testing.T) {
func TestIndexPositionBidi(t *testing.T) { func TestIndexPositionBidi(t *testing.T) {
fontSize := 16 fontSize := 16
lineWidth := fontSize * 10 lineWidth := fontSize * 10
shaper, _, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false) _, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
type testcase struct { type testcase struct {
name string name string
glyphs []text.Glyph glyphs []text.Glyph
@@ -290,12 +284,13 @@ func TestIndexPositionBidi(t *testing.T) {
name: "bidi rtl", name: "bidi rtl",
glyphs: bidiRTLText, glyphs: bidiRTLText,
expectedXs: []fixed.Int26_6{ expectedXs: []fixed.Int26_6{
// Line 0 2646, 3272, 3842, 4412, 4697, 5267, 5837, 6090, 6602, 7114, 2646, 2380, 1577, 985, 687, 266, // Positions on line 0.
5718, 6344, 6914, 7484, 7769, 8339, 8909, 9162, 9674, 10186, 5718, 5452, 4649, 4057, 3759, 3338, 3072, 2304, 1536, 768, 0,
// Line 1 7867, 7099, 6331, 5563, 4795, 4510, 4212, 3914, 3648, 2281, 2566, 3136, 3648, 2281, 2015, 1709, 1117, 266, // Positions on line 1.
9170, 8872, 8574, 8308, 6941, 7226, 7796, 8308, 6941, 6675, 6369, 5777, 4926, 4660, 3892, 3124, 2356, 1588, 1303, 788, 406, 0,
// Line 2 8794, 8026, 7258, 6490, 5722, 5437, 4922, 4540, 4134, 3868, 0, 290, 860, 1430, 1715, 1989, 2559, 3071, 3583, // Positions on line 2.
324, 614, 1184, 1754, 2039, 2313, 2883, 3395, 3907, 4192, 4762, 5332, 5902, 324, 0,
324, 894, 1464, 2034, 324, 0, // Positions on line 3.
}, },
}, },
} { } {
@@ -342,35 +337,6 @@ func TestIndexPositionBidi(t *testing.T) {
printPositions(t, gi.positions) printPositions(t, gi.positions)
if t.Failed() { if t.Failed() {
printGlyphs(t, tc.glyphs) printGlyphs(t, tc.glyphs)
width := lineWidth
height := 100
cap := image.NewRGBA(image.Rect(0, 0, width, height))
w, _ := headless.NewWindow(width, height)
defer w.Release()
ops := new(op.Ops)
gtx := layout.Context{
Constraints: layout.Constraints{Max: image.Pt(width, height)},
Ops: ops,
}
it := textIterator{viewport: image.Rectangle{Max: image.Point{X: width, Y: height}}}
for _, g := range tc.glyphs {
it.processGlyph(g, true)
}
var glyphs [32]text.Glyph
line := glyphs[:0]
for _, g := range gi.glyphs {
var ok bool
if line, ok = it.paintGlyph(gtx, shaper, g, line); !ok {
break
}
}
w.Frame(ops)
w.Screenshot(cap)
b := new(bytes.Buffer)
_ = png.Encode(b, cap)
screenshotName := tc.name + ".png"
_ = os.WriteFile(screenshotName, b.Bytes(), 0o644)
t.Logf("wrote %q", screenshotName)
} }
}) })
} }
@@ -379,8 +345,8 @@ func TestIndexPositionBidi(t *testing.T) {
func TestIndexPositionLines(t *testing.T) { func TestIndexPositionLines(t *testing.T) {
fontSize := 16 fontSize := 16
lineWidth := fontSize * 10 lineWidth := fontSize * 10
_, source1, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false) source1, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
_, source2, bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true) source2, bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
type testcase struct { type testcase struct {
name string name string
source string source string
@@ -413,7 +379,7 @@ func TestIndexPositionLines(t *testing.T) {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 60, yOff: 60,
glyphs: 18, glyphs: 18,
width: fixed.Int26_6(8528), width: fixed.Int26_6(8813),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
@@ -435,24 +401,32 @@ func TestIndexPositionLines(t *testing.T) {
{ {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 22, yOff: 22,
glyphs: 20, glyphs: 15,
width: fixed.Int26_6(10186), width: fixed.Int26_6(7114),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 41, yOff: 41,
glyphs: 19, glyphs: 15,
width: fixed.Int26_6(9170), width: fixed.Int26_6(7867),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 60, yOff: 60,
glyphs: 13, glyphs: 18,
width: fixed.Int26_6(5902), width: fixed.Int26_6(8794),
ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756),
},
{
xOff: fixed.Int26_6(0),
yOff: 79,
glyphs: 4,
width: fixed.Int26_6(2034),
ascent: fixed.Int26_6(968), ascent: fixed.Int26_6(968),
descent: fixed.Int26_6(216), descent: fixed.Int26_6(216),
}, },
@@ -480,10 +454,10 @@ func TestIndexPositionLines(t *testing.T) {
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(1712), xOff: fixed.Int26_6(1427),
yOff: 60, yOff: 60,
glyphs: 18, glyphs: 18,
width: fixed.Int26_6(8528), width: fixed.Int26_6(8813),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
@@ -503,26 +477,34 @@ func TestIndexPositionLines(t *testing.T) {
glyphs: bidiRTLTextOpp, glyphs: bidiRTLTextOpp,
expectedLines: []lineInfo{ expectedLines: []lineInfo{
{ {
xOff: fixed.Int26_6(54), xOff: fixed.Int26_6(3126),
yOff: 22, yOff: 22,
glyphs: 20, glyphs: 15,
width: fixed.Int26_6(10186), width: fixed.Int26_6(7114),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(1070), xOff: fixed.Int26_6(2373),
yOff: 41, yOff: 41,
glyphs: 19, glyphs: 15,
width: fixed.Int26_6(9170), width: fixed.Int26_6(7867),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(4338), xOff: fixed.Int26_6(1446),
yOff: 60, yOff: 60,
glyphs: 13, glyphs: 18,
width: fixed.Int26_6(5902), width: fixed.Int26_6(8794),
ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756),
},
{
xOff: fixed.Int26_6(8206),
yOff: 79,
glyphs: 4,
width: fixed.Int26_6(2034),
ascent: fixed.Int26_6(968), ascent: fixed.Int26_6(968),
descent: fixed.Int26_6(216), descent: fixed.Int26_6(216),
}, },
+1 -1
View File
@@ -75,7 +75,7 @@ func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimension
case system.ActionMinimize: case system.ActionMinimize:
w = minimizeWindow w = minimizeWindow
case system.ActionMaximize: case system.ActionMaximize:
if d.Decorations.Maximized { if d.Decorations.Maximized() {
w = maximizedWindow w = maximizedWindow
} else { } else {
w = maximizeWindow w = maximizeWindow
-3
View File
@@ -32,9 +32,6 @@ type Palette struct {
ContrastFg color.NRGBA ContrastFg color.NRGBA
} }
// Theme holds the general theme of an app or window. Different top-level
// windows should have different instances of Theme (with different Shapers;
// see the godoc for [text.Shaper]), though their other fields can be equal.
type Theme struct { type Theme struct {
Shaper *text.Shaper Shaper *text.Shaper
Palette Palette
-7
View File
@@ -61,9 +61,6 @@ type textView struct {
Truncator string Truncator string
// WrapPolicy configures how displayed text will be broken into lines. // WrapPolicy configures how displayed text will be broken into lines.
WrapPolicy text.WrapPolicy WrapPolicy text.WrapPolicy
// DisableSpaceTrim configures whether trailing whitespace on a line will have its
// width zeroed. Set to true for editors, but false for non-editable text.
DisableSpaceTrim bool
// Mask replaces the visual display of each rune in the contents with the given rune. // Mask replaces the visual display of each rune in the contents with the given rune.
// Newline characters are not masked. When non-zero, the unmasked contents // Newline characters are not masked. When non-zero, the unmasked contents
// are accessed by Len, Text, and SetText. // are accessed by Len, Text, and SetText.
@@ -288,10 +285,6 @@ func (e *textView) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, s
e.params.LineHeightScale = e.LineHeightScale e.params.LineHeightScale = e.LineHeightScale
e.invalidate() e.invalidate()
} }
if e.DisableSpaceTrim != e.params.DisableSpaceTrim {
e.params.DisableSpaceTrim = e.DisableSpaceTrim
e.invalidate()
}
e.makeValid() e.makeValid()