43 Commits

Author SHA1 Message Date
Elias Naur f200f0e9a3 app: [Windows] don't draw minimized windows
Fixes: https://todo.sr.ht/~eliasnaur/gio/621
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-01-14 11:29:45 +01:00
Elias Naur 1ae2b9b8fe app: [macOS] don't draw when minimized
References: https://todo.sr.ht/~eliasnaur/gio/621
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-01-14 11:29:45 +01:00
Elias Naur fe4bf00c70 app: [macOS] propagate unhandled key shortcuts
By propagation, we restore the system behaviour for shortcuts the program
don't want, for example the system beep.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-01-14 11:12:34 +01:00
Elias Naur 8107ec2206 app: [macOS] remove support for key bindings
The fix for #616 went to far by attempting to support macOS key bindings
through doCommandBySelector. Issue #625 is a consequence, but more
fundamentally, key bindings does not work with support for key.Release
events.

Remove key binding translation and fix #626, and add a check whether an
IME swallowed a key event, keeping #616 fixed.

While here, replace the KEY_ constants with ASCII codes.

Fixes: https://todo.sr.ht/~eliasnaur/gio/625
References: https://todo.sr.ht/~eliasnaur/gio/616
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-01-13 23:24:25 +01:00
Chris Waldon d2db4f6875 internal/{egl,gl}: [Windows] restrict graphics DLL sources
In order to avoid DLL preloading attacks, we should be careful about where we
load DLLs from. These packages load graphics DLLs, which may be provided by the
OS, by a graphics vendor, or even by individual applications. As such, we can't
restrict loading them to just system32-provided paths. Instead, we invoke
LoadLibraryEx [0] with the LOAD_LIBRARY_SEARCH_DEFAULT_DIRS path, which will search
system32, application-defined paths, and the path of the primary application
executable. This mode ignores the system %PATH% variable, which dramatically
reduces the attack surface of malicious or unintended DLLs.

Applications may add custom paths to the search list by calling the standard
windows AddDllDirectory function [1] prior to attempting to initialize GL.

Thanks to Mohsen Mirzakhani and Utkarsh Satya Prakash for bringing this to
our attention.

[0] https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa
[1] https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-adddlldirectory

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2025-01-09 21:57:47 +01:00
Elias Naur 971f86ea7e .builds: work around iOS build failure
Later versions of clang no longer accepts our ancient SDK root. Force it
by supressing a warning.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-01-09 11:25:27 +01:00
Elias Naur e1fbb189e5 .builds: upgrade FreeBSD builder
It's currently failing, and upgrading it may fix it.

While here, track lates FreeBSD to avoid upgrade toil.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-01-09 10:05:03 +01:00
Chris Waldon a206e5e847 app: [Windows] use NewLazySystemDLL for kernel32.dll
In order to avoid DLL preloading attacks, we should always load our system
dependencies using the helper that only searches the system library path.

Thanks to Mohsen Mirzakhani and Utkarsh Satya Prakash for bringing this to
our attention.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2025-01-09 09:54:15 +01:00
Chris Waldon e025ed1344 widget: ensure editor does not trim trailing whitespace
This commit makes the editor widget suppress the trimming of trailing whitespace
so that the spaces can be selected intuitively.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-12-18 13:28:16 -05:00
Chris Waldon b576252963 text: allow disabling space trimming
This commit adds a shaping parameter that disables the trimming of trailing
whitespace from lines. Text editors and similar use-cases want trailing whitspace
glyphs to be selectable, which means they must occupy space.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-12-18 13:28:16 -05:00
Chris Waldon 1d0d5f0383 widget: update text indexing expectations
The typesetting package has smarter line wrapping now, which is making our
test text require fewer lines to display. We needed to update the expected
data accordingly.

I've also added a feature that takes a screenshot of the rendered output of
one of our most complex cursoring tests. This will make it much easier to
verify its behavior in the future. This feature currently only triggers if
the test case fails.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-12-18 13:28:16 -05:00
Chris Waldon 26cddb00b1 text: use upstream bidi visual order algorithm
We've migrated the processing of bidi run ordering into the upstream typesetting
package, so now we can just consume the already-ordered runs instead of computing
their ordering ourselves.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-12-18 13:28:16 -05:00
Benoit KUGLER 0cbbacc45a [text, font] Bump go-text version to 0.2.1
Signed-off-by: Benoit KUGLER <benoit.kugler@gmail.com>
2024-12-18 13:28:10 -05:00
Elias Naur 1d95c7c6b3 flake.*: upgrade to nixpkgs 24.11
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-12-06 10:23:02 +01:00
Elias Naur 7337c06daf app: [macOS] send keypress events for modifier keys
This change generates keypress and release events for modifier keys in macOS.
Specifically the Control, Alt, Shift and Command keys.

Signed-off-by: Jeff Williams <kanobe@gmail.com>
2024-12-04 18:12:15 +01:00
Elias Naur 94355e5201 internal/vk: remove methods on C types, for Go 1.24 compatibility
Go 1.24 no longer allows methods on C types (golang.org/issue/60725).

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-11-26 10:31:04 +01:00
Elias Naur ea456f42c7 gpu: remove compute renderer
The compute renderer is a failed experiment: a better port of the
Vello vector renderer exists[0] and the upcoming Go 1.24 release
no longer builds the gioui.org/cpu module because of #60725.

Remove it.

[0] https://github.com/dominikh/jello

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-11-26 08:59:36 +00:00
Elias Naur 8daff13af6 app: [windows] ensure no callbacks after DestroyEvent
Setting the callback handler to nil in DestroyEvent should have no effect,
but may help debugging #603.

Also don't call the default window handler for WM_DESTROY since we're
already handling it.

References: https://todo.sr.ht/~eliasnaur/gio/603
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-25 22:06:57 +02:00
Elias Naur aa158e0c9c app: fix race condition between Window.Invalidate and event loop
References: https://todo.sr.ht/~eliasnaur/gio/603
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-25 20:38:37 +02:00
Elias Naur 520efdfa75 app: fix typo
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-25 20:29:21 +02:00
Elias Naur d7a1ec7461 app: [macos] don't relay key events handled by the IME
Widgets such as Editor use certain key events such as the backspace key
to implement text editing. On macOS, such key events are sometimes used
by an input method, and in those cases the key effect would be applied
twice: first by the IME and then the Editor.

Report such key events through the doCommandBySelector callback, which
receives key events not handled by the IME.

References: https://todo.sr.ht/~eliasnaur/gio/616
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-25 19:39:20 +02:00
Elias Naur d4c5e54375 app: remove dead code
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-25 19:39:20 +02:00
Elias Naur 44ac50506d app: [android] ensure a new frame is always scheduled when visible and animating
Possible fix to #614.

References: https://todo.sr.ht/~eliasnaur/gio/614
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-25 12:16:54 +02:00
inkeliz 38e4b1c6a4 app: [android] fix compatibility with older Android versions
Previously, setHighRefreshRate requires APIs restricted
to Android 30, or higher.

Tested on Android 6.0.1 (released on 2015).

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
2024-10-25 11:44:13 +02:00
Chris Waldon 5d886b4d7f go.*: update typesetting for unifont panic fix
This commit updates our typesetting dependency to avoid a crash when shaping GNU
unifont. Thanks to Jeff Williams for raising the issue on the mailing list.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-10-22 19:05:27 +02:00
Elias Naur c7277581f8 app: [Window] don't report Win32ViewEvent before window configuration
Fixes: https://todo.sr.ht/~eliasnaur/gio/609
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur a5f7e7b2c7 app: [Windows] compute min, max window sizes correctly when un-minimzing
Fixes: https://todo.sr.ht/~eliasnaur/gio/608
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 0781e62b56 app: [Windows] don't ignore Min|MaxSize options
The support for minimum and maximum window sizes were broken by a recent
change. Found while investigating #608.

References: https://todo.sr.ht/~eliasnaur/gio/608
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Larry Clapp 95f63c66b6 text,widget/material: Update doc for Shaper & Theme
Note that you should use different Themes, with different Shapers, for
different top-level windows, and explain why.

Signed-off-by: Larry Clapp <larry@theclapp.org>
2024-10-22 19:05:27 +02:00
Elias Naur 6efcb65c4b Revert "app: [Windows] don't draw after WM_DESTROY destroyed the window"
This reverts commit 635df374952019ff8d274646ea9ce040744daa0f because it
didn't fix #603 after all.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur f6e9f6861d app: close the window before reporting a GPU error
When a GPU error occurs forcing the reporting of a DestroyEvent is not
appropriate, because the backend that controls the underlying window
is not aware of the error and will continue to report events.

Replace the crude DestroyEvent by stashing the error and asking the
window nicely to close. The, report the stashed error in the
otherwise regular DestroyEvent.

Hopefully, this second attempt fixes #603.

Fixes: https://todo.sr.ht/~eliasnaur/gio/603
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 6c6cc157c4 layout,io/input: [API] change Context.Disabled to only disable events
Also unexport Source.Enabled because the nil-ness of its embedded router
is now an implementation detail.

Fixes: https://todo.sr.ht/~eliasnaur/gio/605
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 8cf449034c app: [Windows] remove redundant code
Change f7aa4b5c8 changed the fullscreen implementation to no longer require
the position and size of the fullscreen window.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 6722c7960a widget: [API] change Decorations to leave the user in control of window state
As suggested by ~egonelbre, Decorations should not be the source of truth
for the windows state, because external gestures may also change state.

This breaking change removes Decorations.Perform and exposes Maximized
as a bool which is the user's responsibility to set.

Fixes: https://todo.sr.ht/~eliasnaur/gio/600
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 97044e53b5 app: [Windows] track window state changes initiated by the OS
Like the previous change, update the Windows backend to track and report
window state changes initiated by the OS.

References: https://todo.sr.ht/~eliasnaur/gio/600
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur af6dda67a5 app: [macOS] track window state changes initiated by the operating system
Before this change, the window state was explicitly updated whenever
Window.Option was called. However, the system may also change window
state as a result of user gestures, but those changes did not result in
ConfigEvents reflecting them.

Remove the explicit state updates and track them when the system tells
us it has changed.

This is a step towards fixing #600 which require accurate window state
tracking.

References: https://todo.sr.ht/~eliasnaur/gio/600
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 042ed4ab49 app: [Windows] don't draw after WM_DESTROY destroyed the window
There may be a window of time from WM_DESTROY is received to the WM_QUIT
message is delivered by PostQuitMessage. If so, we must not call w.draw.

Fixes: https://todo.sr.ht/~eliasnaur/gio/603
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 6aa027136e app: [Windows] remove redundant code
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur b1db32ef72 io/input: fix typo
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:27 +02:00
Elias Naur 9dceca6c95 Revert "app: [Windows] suppress double-click behaviour for custom decorations"
This reverts commit 82cbb7b8da.

As Egon Elbre points out in

https://todo.sr.ht/~eliasnaur/gio/600#event-377179

this is not the right fix.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-10-22 19:05:19 +02:00
Egon Elbre 7293fa8a41 go.mod: update golang.org/x dependencies
This gets rid of a CVE warning about golang.org/x/image/tiff.

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2024-07-16 15:59:05 +02:00
Elias Naur 82cbb7b8da 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-13 18:09:13 +02:00
Chris Waldon 6c19821a6c 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.

Fixes: https://todo.sr.ht/~eliasnaur/gio/599
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-07-13 11:03:14 +02:00
37 changed files with 851 additions and 3084 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
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./... 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 ./...
+1 -1
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Unlicense OR MIT # SPDX-License-Identifier: Unlicense OR MIT
image: freebsd/13.x image: freebsd/latest
packages: packages:
- libX11 - libX11
- libxkbcommon - libxkbcommon
+4
View File
@@ -259,6 +259,10 @@ 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
View File
@@ -274,6 +274,7 @@ 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
+3 -2
View File
@@ -4,14 +4,15 @@ 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.NewLazyDLL("kernel32") kernel32 = syscall.NewLazySystemDLL("kernel32")
outputDebugStringW = kernel32.NewProc("OutputDebugStringW") outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
debugView *logger debugView *logger
) )
-2
View File
@@ -195,8 +195,6 @@ 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,10 +575,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
if !exist { if !exist {
return return
} }
if w.visible && w.animating { w.draw(env, false)
w.draw(env, false)
callVoidMethod(env, w.view, gioView.postFrameCallback)
}
} }
//export Java_org_gioui_GioView_onBack //export Java_org_gioui_GioView_onBack
@@ -882,6 +879,9 @@ 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)
+227 -138
View File
@@ -109,6 +109,14 @@ 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;
@@ -238,6 +246,13 @@ 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;
@@ -297,6 +312,21 @@ 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"
@@ -318,7 +348,6 @@ 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.
@@ -326,9 +355,20 @@ 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.
@@ -367,11 +407,23 @@ 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)))
if style&C.NSWindowStyleMaskFullScreen != 0 { switch {
case style&C.NSWindowStyleMaskFullScreen != 0:
w.config.Mode = Fullscreen w.config.Mode = Fullscreen
} else { case C.isWindowZoomed(window) != 0:
w.config.Mode = Windowed w.config.Mode = Maximized
} }
w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0 w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0
} }
@@ -379,105 +431,82 @@ 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:
switch prev.Mode { if C.isWindowMiniaturized(window) != 0 {
case Fullscreen:
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
fallthrough }
default: if !fullscreen {
w.config.Mode = Fullscreen
C.toggleFullScreen(window) C.toggleFullScreen(window)
} }
case Minimized: case Minimized:
switch prev.Mode { C.hideWindow(window)
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
C.hideWindow(window)
}
case Maximized: case Maximized:
switch prev.Mode { if C.isWindowMiniaturized(window) != 0 {
case Fullscreen:
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
fallthrough }
default: if fullscreen {
w.config.Mode = Maximized C.toggleFullScreen(window)
w.setTitle(prev, cnf) }
if C.isWindowZoomed(window) == 0 { w.setTitle(cnf.Title)
C.zoomWindow(window) if C.isWindowZoomed(window) == 0 {
} C.zoomWindow(window)
} }
case Windowed: case Windowed:
switch prev.Mode { if C.isWindowMiniaturized(window) != 0 {
case Fullscreen:
C.toggleFullScreen(window)
case Minimized:
C.unhideWindow(window) C.unhideWindow(window)
case Maximized:
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
} }
w.config.Mode = Windowed if fullscreen {
w.setTitle(prev, cnf) C.toggleFullScreen(window)
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
cnf.Size = cnf.Size.Div(int(screenScale))
C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
} }
if prev.MinSize != cnf.MinSize { w.setTitle(cnf.Title)
w.config.MinSize = cnf.MinSize w.config.Size = cnf.Size
cnf.MinSize = cnf.MinSize.Div(int(screenScale)) cnf.Size = cnf.Size.Div(int(screenScale))
C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
} w.config.MinSize = cnf.MinSize
if prev.MaxSize != cnf.MaxSize { cnf.MinSize = cnf.MinSize.Div(int(screenScale))
w.config.MaxSize = cnf.MaxSize C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) w.config.MaxSize = cnf.MaxSize
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
if cnf.MaxSize != (image.Point{}) {
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 {
if cnf.Decorated != prev.Decorated { C.zoomWindow(window)
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)
} }
w.ProcessEvent(ConfigEvent{Config: w.config}) 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)
} }
func (w *window) setTitle(prev, cnf Config) { func (w *window) setTitle(title string) {
if prev.Title != cnf.Title { w.config.Title = title
w.config.Title = cnf.Title titleC := stringToNSString(title)
title := stringToNSString(cnf.Title) defer C.CFRelease(titleC)
defer C.CFRelease(title) C.setTitle(C.windowForView(w.view), titleC)
C.setTitle(C.windowForView(w.view), title)
}
} }
func (w *window) Perform(acts system.Action) { func (w *window) Perform(acts system.Action) {
@@ -520,7 +549,8 @@ func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) SetAnimating(anim bool) { func (w *window) SetAnimating(anim bool) {
w.anim = anim w.anim = anim
if w.anim && w.visible { window := C.windowForView(w.view)
if w.anim && window != 0 && C.isMiniaturized(window) == 0 {
w.displayLink.Start() w.displayLink.Start()
} else { } else {
w.displayLink.Stop() w.displayLink.Stop()
@@ -538,23 +568,92 @@ func (w *window) runOnMain(f func()) {
} }
//export gio_onKeys //export gio_onKeys
func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { func gio_onKeys(h C.uintptr_t, event C.CFTypeRef, 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 {
w.ProcessEvent(key.Event{ ke := 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
@@ -747,6 +846,15 @@ 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 {
@@ -758,7 +866,6 @@ 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
@@ -799,24 +906,19 @@ 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)
} }
w.scale = float32(C.getViewBackingScale(w.view)) sz := w.config.Size
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
} }
@@ -824,7 +926,7 @@ func (w *window) draw() {
w.ProcessEvent(frameEvent{ w.ProcessEvent(frameEvent{
FrameEvent: FrameEvent{ FrameEvent: FrameEvent{
Now: time.Now(), Now: time.Now(),
Size: w.config.Size, Size: sz,
Metric: cfg, Metric: cfg,
}, },
Sync: true, Sync: true,
@@ -832,8 +934,13 @@ func (w *window) draw() {
} }
func (w *window) ProcessEvent(e event.Event) { func (w *window) ProcessEvent(e event.Event) {
w.w.ProcessEvent(e) 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 {
@@ -867,7 +974,6 @@ 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)
} }
} }
@@ -882,33 +988,6 @@ 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)
@@ -931,10 +1010,9 @@ func newWindow(win *callbacks, options []Option) {
w.ProcessEvent(DestroyEvent{Err: err}) w.ProcessEvent(DestroyEvent{Err: err})
return return
} }
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0) window := C.gio_createWindow(w.view, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y), 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,
@@ -942,6 +1020,7 @@ 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)
}) })
@@ -966,9 +1045,7 @@ func (w *window) init(customRenderer bool) error {
return return
} }
w.runOnMain(func() { w.runOnMain(func() {
if w.visible { C.setNeedsDisplay(w.view)
C.setNeedsDisplay(w.view)
}
}) })
}) })
w.displayLink = dl w.displayLink = dl
@@ -988,10 +1065,10 @@ func osMain() {
C.gio_main() C.gio_main()
} }
func convertKey(k rune) (key.Name, bool) { func convertCommandKey(k rune) (key.Name, bool) {
var n key.Name var n key.Name
switch k { switch k {
case 0x1b: case '\x1b': // ASCII escape.
n = key.NameEscape n = key.NameEscape
case C.NSLeftArrowFunctionKey: case C.NSLeftArrowFunctionKey:
n = key.NameLeftArrow n = key.NameLeftArrow
@@ -1001,22 +1078,36 @@ func convertKey(k rune) (key.Name, bool) {
n = key.NameUpArrow n = key.NameUpArrow
case C.NSDownArrowFunctionKey: case C.NSDownArrowFunctionKey:
n = key.NameDownArrow n = key.NameDownArrow
case 0xd: case '\r':
n = key.NameReturn n = key.NameReturn
case 0x3: case '\x03':
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 0x7f: case '\x7f', '\b':
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:
@@ -1041,8 +1132,6 @@ 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:
+16 -14
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_onHide(view.handle); gio_onDraw(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_onShow(view.handle); gio_onDraw(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_onFullscreen(view.handle); gio_onDraw(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_onWindowed(view.handle); gio_onDraw(view.handle);
} }
- (void)windowDidChangeScreen:(NSNotification *)notification { - (void)windowDidChangeScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
@@ -132,22 +132,25 @@ 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 {
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
NSString *keys = [event charactersIgnoringModifiers]; NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true); gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
}
- (void)flagsChanged:(NSEvent *)event {
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
gio_onFlagsChanged(self.handle, [event modifierFlags]);
} }
- (void)keyUp:(NSEvent *)event { - (void)keyUp:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers]; NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false); gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__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)sel { - (void)doCommandBySelector:(SEL)action {
// Don't pass commands up the responder chain. if (!gio_onCommandBySelector(self.handle)) {
// They will end up in a beep. [super doCommandBySelector:action];
}
} }
- (BOOL)hasMarkedText { - (BOOL)hasMarkedText {
int res = gio_hasMarkedText(self.handle); int res = gio_hasMarkedText(self.handle);
return res ? YES : NO; return res ? YES : NO;
@@ -202,10 +205,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_onShow(self.handle); gio_onDraw(self.handle);
} }
- (void)applicationDidHide:(NSNotification *)notification { - (void)applicationDidHide:(NSNotification *)notification {
gio_onHide(self.handle); gio_onDraw(self.handle);
} }
- (void)dealloc { - (void)dealloc {
gio_onDestroy(self.handle); gio_onDestroy(self.handle);
@@ -387,7 +390,6 @@ 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;
} }
+8 -6
View File
@@ -1633,6 +1633,14 @@ func (w *window) flushScroll() {
if total == (f32.Point{}) { if total == (f32.Point{}) {
return return
} }
if w.scroll.steps == (image.Point{}) {
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
}
// Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
// re-process the scroll distance.
w.scroll.dist = f32.Point{}
w.scroll.steps = image.Point{}
w.ProcessEvent(pointer.Event{ w.ProcessEvent(pointer.Event{
Kind: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Mouse, Source: pointer.Mouse,
@@ -1642,12 +1650,6 @@ func (w *window) flushScroll() {
Time: w.scroll.time, Time: w.scroll.time,
Modifiers: w.disp.xkb.Modifiers(), Modifiers: w.disp.xkb.Modifiers(),
}) })
if w.scroll.steps == (image.Point{}) {
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
}
w.scroll.dist = f32.Point{}
w.scroll.steps = image.Point{}
} }
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) { func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
+56 -47
View File
@@ -46,14 +46,13 @@ 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
loop *eventLoop // frameDims stores the last seen window frame width and height.
frameDims image.Point
loop *eventLoop
} }
const _WM_WAKEUP = windows.WM_USER + iota const _WM_WAKEUP = windows.WM_USER + iota
@@ -108,8 +107,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.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
w.Configure(options) w.Configure(options)
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
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,
@@ -185,21 +184,39 @@ 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() {
cr := windows.GetClientRect(w.hwnd) p := windows.GetWindowPlacement(w.hwnd)
w.config.Size = image.Point{ if !p.IsMinimized() {
X: int(cr.Right - cr.Left), r := windows.GetWindowRect(w.hwnd)
Y: int(cr.Bottom - cr.Top), cr := windows.GetClientRect(w.hwnd)
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 {
@@ -297,6 +314,7 @@ 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
@@ -304,6 +322,7 @@ 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.
@@ -328,37 +347,31 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
return 0 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 {
r := windows.GetWindowRect(w.hwnd) frameDims = w.frameDims
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) + bw, X: int32(p.X),
Y: int32(p.Y) + bh, Y: int32(p.Y),
} }
} }
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) + bw, X: int32(p.X),
Y: int32(p.Y) + bh, Y: int32(p.Y),
} }
} }
return 0 return 0
@@ -450,9 +463,6 @@ 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
@@ -562,7 +572,8 @@ func (w *window) runLoop() {
loop: loop:
for { for {
anim := w.animating anim := w.animating
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) { p := windows.GetWindowPlacement(w.hwnd)
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
w.draw(false) w.draw(false)
continue continue
} }
@@ -685,8 +696,13 @@ 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)
w.config.apply(metric, options) cnf := w.config
windows.SetWindowText(w.hwnd, w.config.Title) cnf.apply(metric, options)
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
@@ -694,7 +710,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 w.config.Mode { switch cnf.Mode {
case Minimized: case Minimized:
style |= winStyle style |= winStyle
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
@@ -709,13 +725,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(w.config.Size.X) width = int32(cnf.Size.X)
height = int32(w.config.Size.Y) height = int32(cnf.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 w.config.Decorated { if cnf.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{
@@ -725,25 +741,18 @@ 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 := caps.MinExtent(), caps.MaxExtent() minExt, maxExt := vk.SurfaceCapabilitiesMinExtent(caps), vk.SurfaceCapabilitiesMaxExtent(caps)
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
} }
+13 -4
View File
@@ -89,6 +89,9 @@ 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
@@ -227,7 +230,8 @@ 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.driver.ProcessEvent(DestroyEvent{Err: err}) w.gpuErr = err
w.driver.Perform(system.ActionClose)
return return
} }
w.updateState() w.updateState()
@@ -390,6 +394,8 @@ 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
} }
@@ -637,11 +643,14 @@ 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.invMu.Unlock()
w.driver = nil w.driver = nil
w.invMu.Unlock()
if q := w.timer.quit; q != nil { if q := w.timer.quit; q != nil {
q <- struct{}{} q <- struct{}{}
<-q <-q
@@ -656,6 +665,7 @@ 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()
@@ -718,7 +728,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 initializion failed without a DestroyEvent") panic("window initialization failed without a DestroyEvent")
} }
return e return e
} }
@@ -801,7 +811,6 @@ 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
+15 -31
View File
@@ -9,11 +9,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1701721028, "lastModified": 1733430059,
"narHash": "sha256-2z4YrdHPLoMZNWR1MPOjNZMqPg057i1eZXaYI6RTahQ=", "narHash": "sha256-o3O5tjrMMebRLuHQt7BbEw3jZgWRW5vnOptNXv8WdO4=",
"owner": "tadfisher", "owner": "tadfisher",
"repo": "android-nixpkgs", "repo": "android-nixpkgs",
"rev": "c923f9ec0f4dd0d7dc725dc5b73fbf03658e50dd", "rev": "d2f3c1ea99c0bea9d28a0e59daeb482f50d4cd35",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -27,15 +27,14 @@
"nixpkgs": [ "nixpkgs": [
"android", "android",
"nixpkgs" "nixpkgs"
], ]
"systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1701697687, "lastModified": 1728330715,
"narHash": "sha256-dLLE5wQBVv+pIb4bWmKFSw2DvLVyuEk0F7ng6hpZPSU=", "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
"owner": "numtide", "owner": "numtide",
"repo": "devshell", "repo": "devshell",
"rev": "c3bd77911391eb1638af6ce773de86da57ee6df5", "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -46,14 +45,14 @@
}, },
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems_2" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1701680307, "lastModified": 1731533236,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -64,16 +63,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1701282334, "lastModified": 1733261153,
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=", "narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e", "rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "23.11", "ref": "nixos-24.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@@ -98,21 +97,6 @@
"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",
+1 -7
View File
@@ -3,7 +3,7 @@
description = "Gio build environment"; description = "Gio build environment";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/23.11"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
android.url = "github:tadfisher/android-nixpkgs"; android.url = "github:tadfisher/android-nixpkgs";
android.inputs.nixpkgs.follows = "nixpkgs"; android.inputs.nixpkgs.follows = "nixpkgs";
}; };
@@ -47,12 +47,6 @@
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
+41 -43
View File
@@ -16,23 +16,21 @@ import (
_ "image/png" _ "image/png"
giofont "gioui.org/font" giofont "gioui.org/font"
"github.com/go-text/typesetting/font" fontapi "github.com/go-text/typesetting/font"
fontapi "github.com/go-text/typesetting/opentype/api/font" "github.com/go-text/typesetting/font/opentype"
"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 font.Font face *fontapi.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 := loader.NewLoader(bytes.NewReader(src)) ld, err := opentype.NewLoader(bytes.NewReader(src))
if err != nil { if err != nil {
return Face{}, err return Face{}, err
} }
@@ -49,11 +47,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 metadata. // inferred font font.
// 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 := loader.NewLoaders(bytes.NewReader(src)) lds, err := opentype.NewLoaders(bytes.NewReader(src))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -76,7 +74,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
return out, nil return out, nil
} }
func DescriptionToFont(md metadata.Description) giofont.Font { func DescriptionToFont(md fontapi.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),
@@ -84,30 +82,30 @@ func DescriptionToFont(md metadata.Description) giofont.Font {
} }
} }
func FontToDescription(font giofont.Font) metadata.Description { func FontToDescription(font giofont.Font) fontapi.Description {
return metadata.Description{ return fontapi.Description{
Family: string(font.Typeface), Family: string(font.Typeface),
Aspect: metadata.Aspect{ Aspect: fontapi.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 metadata. // parseLoader parses the contents of the loader into a face and its font.
func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) { func parseLoader(ld *opentype.Loader) (*fontapi.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(metadata.Metadata(ld)) data := DescriptionToFont(ft.Describe())
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() font.Face { func (f Face) Face() *fontapi.Face {
return &fontapi.Face{Font: f.face} return &fontapi.Face{Font: f.face}
} }
@@ -119,74 +117,74 @@ func (f Face) Font() giofont.Font {
return f.font return f.font
} }
func gioStyle(s metadata.Style) giofont.Style { func gioStyle(s fontapi.Style) giofont.Style {
switch s { switch s {
case metadata.StyleItalic: case fontapi.StyleItalic:
return giofont.Italic return giofont.Italic
case metadata.StyleNormal: case fontapi.StyleNormal:
fallthrough fallthrough
default: default:
return giofont.Regular return giofont.Regular
} }
} }
func mdStyle(g giofont.Style) metadata.Style { func mdStyle(g giofont.Style) fontapi.Style {
switch g { switch g {
case giofont.Italic: case giofont.Italic:
return metadata.StyleItalic return fontapi.StyleItalic
case giofont.Regular: case giofont.Regular:
fallthrough fallthrough
default: default:
return metadata.StyleNormal return fontapi.StyleNormal
} }
} }
func gioWeight(w metadata.Weight) giofont.Weight { func gioWeight(w fontapi.Weight) giofont.Weight {
switch w { switch w {
case metadata.WeightThin: case fontapi.WeightThin:
return giofont.Thin return giofont.Thin
case metadata.WeightExtraLight: case fontapi.WeightExtraLight:
return giofont.ExtraLight return giofont.ExtraLight
case metadata.WeightLight: case fontapi.WeightLight:
return giofont.Light return giofont.Light
case metadata.WeightNormal: case fontapi.WeightNormal:
return giofont.Normal return giofont.Normal
case metadata.WeightMedium: case fontapi.WeightMedium:
return giofont.Medium return giofont.Medium
case metadata.WeightSemibold: case fontapi.WeightSemibold:
return giofont.SemiBold return giofont.SemiBold
case metadata.WeightBold: case fontapi.WeightBold:
return giofont.Bold return giofont.Bold
case metadata.WeightExtraBold: case fontapi.WeightExtraBold:
return giofont.ExtraBold return giofont.ExtraBold
case metadata.WeightBlack: case fontapi.WeightBlack:
return giofont.Black return giofont.Black
default: default:
return giofont.Normal return giofont.Normal
} }
} }
func mdWeight(g giofont.Weight) metadata.Weight { func mdWeight(g giofont.Weight) fontapi.Weight {
switch g { switch g {
case giofont.Thin: case giofont.Thin:
return metadata.WeightThin return fontapi.WeightThin
case giofont.ExtraLight: case giofont.ExtraLight:
return metadata.WeightExtraLight return fontapi.WeightExtraLight
case giofont.Light: case giofont.Light:
return metadata.WeightLight return fontapi.WeightLight
case giofont.Normal: case giofont.Normal:
return metadata.WeightNormal return fontapi.WeightNormal
case giofont.Medium: case giofont.Medium:
return metadata.WeightMedium return fontapi.WeightMedium
case giofont.SemiBold: case giofont.SemiBold:
return metadata.WeightSemibold return fontapi.WeightSemibold
case giofont.Bold: case giofont.Bold:
return metadata.WeightBold return fontapi.WeightBold
case giofont.ExtraBold: case giofont.ExtraBold:
return metadata.WeightExtraBold return fontapi.WeightExtraBold
case giofont.Black: case giofont.Black:
return metadata.WeightBlack return fontapi.WeightBlack
default: default:
return metadata.WeightNormal return fontapi.WeightNormal
} }
} }
+6 -8
View File
@@ -4,13 +4,11 @@ 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.1.1 github.com/go-text/typesetting v0.2.1
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 golang.org/x/exp v0.0.0-20240707233637-46b078467d37
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37
golang.org/x/image v0.5.0 golang.org/x/image v0.18.0
golang.org/x/sys v0.5.0 golang.org/x/sys v0.22.0
golang.org/x/text v0.16.0
) )
require golang.org/x/text v0.9.0
+14 -40
View File
@@ -1,45 +1,19 @@
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.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo= github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI= github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
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 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
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
@@ -1,129 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"unsafe"
"gioui.org/cpu"
)
// This file contains code specific to running compute shaders on the CPU.
// dispatcher dispatches CPU compute programs across multiple goroutines.
type dispatcher struct {
// done is notified when a worker completes its work slice.
done chan struct{}
// work receives work slice indices. It is closed when the dispatcher is released.
work chan work
// dispatch receives compute jobs, which is then split among workers.
dispatch chan dispatch
// sync receives notification when a Sync completes.
sync chan struct{}
}
type work struct {
ctx *cpu.DispatchContext
index int
}
type dispatch struct {
_type jobType
program *cpu.ProgramInfo
descSet unsafe.Pointer
x, y, z int
}
type jobType uint8
const (
jobDispatch jobType = iota
jobBarrier
jobSync
)
func newDispatcher(workers int) *dispatcher {
d := &dispatcher{
work: make(chan work, workers),
done: make(chan struct{}, workers),
// Leave some room to avoid blocking calls to Dispatch.
dispatch: make(chan dispatch, 20),
sync: make(chan struct{}),
}
for i := 0; i < workers; i++ {
go d.worker()
}
go d.dispatcher()
return d
}
func (d *dispatcher) dispatcher() {
defer close(d.work)
var free []*cpu.DispatchContext
defer func() {
for _, ctx := range free {
ctx.Free()
}
}()
var used []*cpu.DispatchContext
for job := range d.dispatch {
switch job._type {
case jobDispatch:
if len(free) == 0 {
free = append(free, cpu.NewDispatchContext())
}
ctx := free[len(free)-1]
free = free[:len(free)-1]
used = append(used, ctx)
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
for i := 0; i < cap(d.work); i++ {
d.work <- work{
ctx: ctx,
index: i,
}
}
case jobBarrier:
// Wait for all outstanding dispatches to complete.
for i := 0; i < len(used)*cap(d.work); i++ {
<-d.done
}
free = append(free, used...)
used = used[:0]
case jobSync:
d.sync <- struct{}{}
}
}
}
func (d *dispatcher) worker() {
thread := cpu.NewThreadContext()
defer thread.Free()
for w := range d.work {
w.ctx.Dispatch(w.index, thread)
d.done <- struct{}{}
}
}
func (d *dispatcher) Barrier() {
d.dispatch <- dispatch{_type: jobBarrier}
}
func (d *dispatcher) Sync() {
d.dispatch <- dispatch{_type: jobSync}
<-d.sync
}
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
d.dispatch <- dispatch{
_type: jobDispatch,
program: program,
descSet: descSet,
x: x,
y: y,
z: z,
}
}
func (d *dispatcher) Stop() {
close(d.dispatch)
}
+16 -5
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,13 +343,12 @@ 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 !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB): case feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
return newGPU(d) return newGPU(d)
} }
return newCompute(d) return nil, errors.New("no available GPU driver")
} }
func newGPU(ctx driver.Device) (*gpu, error) { func newGPU(ctx driver.Device) (*gpu, error) {
@@ -1484,7 +1483,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 = bo.Uint32(pathData) qs.contour = binary.LittleEndian.Uint32(pathData)
cmd := ops.DecodeCommand(pathData[4:]) cmd := ops.DecodeCommand(pathData[4:])
switch cmd.Op() { switch cmd.Op() {
case scene.OpLine: case scene.OpLine:
@@ -1579,3 +1578,15 @@ 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 := s.Offset() off := vk.PushConstantRangeOffset(s)
vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()]) vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, vk.PushConstantRangeStageFlags(s), off, buf.store[off:off+vk.PushConstantRangeSize(s)])
} }
} }
+48 -26
View File
@@ -9,8 +9,6 @@ import (
"unsafe" "unsafe"
syscall "golang.org/x/sys/windows" syscall "golang.org/x/sys/windows"
"gioui.org/internal/gl"
) )
type ( type (
@@ -24,23 +22,23 @@ type (
) )
var ( var (
libEGL = syscall.NewLazyDLL("libEGL.dll") libEGL = syscall.DLL{}
_eglChooseConfig = libEGL.NewProc("eglChooseConfig") _eglChooseConfig *syscall.Proc
_eglCreateContext = libEGL.NewProc("eglCreateContext") _eglCreateContext *syscall.Proc
_eglCreateWindowSurface = libEGL.NewProc("eglCreateWindowSurface") _eglCreateWindowSurface *syscall.Proc
_eglDestroyContext = libEGL.NewProc("eglDestroyContext") _eglDestroyContext *syscall.Proc
_eglDestroySurface = libEGL.NewProc("eglDestroySurface") _eglDestroySurface *syscall.Proc
_eglGetConfigAttrib = libEGL.NewProc("eglGetConfigAttrib") _eglGetConfigAttrib *syscall.Proc
_eglGetDisplay = libEGL.NewProc("eglGetDisplay") _eglGetDisplay *syscall.Proc
_eglGetError = libEGL.NewProc("eglGetError") _eglGetError *syscall.Proc
_eglInitialize = libEGL.NewProc("eglInitialize") _eglInitialize *syscall.Proc
_eglMakeCurrent = libEGL.NewProc("eglMakeCurrent") _eglMakeCurrent *syscall.Proc
_eglReleaseThread = libEGL.NewProc("eglReleaseThread") _eglReleaseThread *syscall.Proc
_eglSwapInterval = libEGL.NewProc("eglSwapInterval") _eglSwapInterval *syscall.Proc
_eglSwapBuffers = libEGL.NewProc("eglSwapBuffers") _eglSwapBuffers *syscall.Proc
_eglTerminate = libEGL.NewProc("eglTerminate") _eglTerminate *syscall.Proc
_eglQueryString = libEGL.NewProc("eglQueryString") _eglQueryString *syscall.Proc
_eglWaitClient = libEGL.NewProc("eglWaitClient") _eglWaitClient *syscall.Proc
) )
var loadOnce sync.Once var loadOnce sync.Once
@@ -54,21 +52,45 @@ 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 {
return err procs := map[string]**syscall.Proc{
"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,
} }
// d3dcompiler_47.dll is needed internally for shader compilation to function. for name, proc := range procs {
return loadDLL(syscall.NewLazyDLL("d3dcompiler_47.dll"), "d3dcompiler_47.dll") p, err := libEGL.FindProc(name)
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.LazyDLL, name string) error { func loadDLL(dll *syscall.DLL, name string) error {
err := dll.Load() handle, err := syscall.LoadLibraryEx(name, 0, syscall.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
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
} }
+207 -89
View File
@@ -3,103 +3,217 @@
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 (
LibGLESv2 = windows.NewLazyDLL("libGLESv2.dll") glInitOnce sync.Once
_glActiveTexture = LibGLESv2.NewProc("glActiveTexture") _glActiveTexture *windows.Proc
_glAttachShader = LibGLESv2.NewProc("glAttachShader") _glAttachShader *windows.Proc
_glBeginQuery = LibGLESv2.NewProc("glBeginQuery") _glBeginQuery *windows.Proc
_glBindAttribLocation = LibGLESv2.NewProc("glBindAttribLocation") _glBindAttribLocation *windows.Proc
_glBindBuffer = LibGLESv2.NewProc("glBindBuffer") _glBindBuffer *windows.Proc
_glBindBufferBase = LibGLESv2.NewProc("glBindBufferBase") _glBindBufferBase *windows.Proc
_glBindFramebuffer = LibGLESv2.NewProc("glBindFramebuffer") _glBindFramebuffer *windows.Proc
_glBindRenderbuffer = LibGLESv2.NewProc("glBindRenderbuffer") _glBindRenderbuffer *windows.Proc
_glBindTexture = LibGLESv2.NewProc("glBindTexture") _glBindTexture *windows.Proc
_glBindVertexArray = LibGLESv2.NewProc("glBindVertexArray") _glBindVertexArray *windows.Proc
_glBlendEquation = LibGLESv2.NewProc("glBlendEquation") _glBlendEquation *windows.Proc
_glBlendFuncSeparate = LibGLESv2.NewProc("glBlendFuncSeparate") _glBlendFuncSeparate *windows.Proc
_glBufferData = LibGLESv2.NewProc("glBufferData") _glBufferData *windows.Proc
_glBufferSubData = LibGLESv2.NewProc("glBufferSubData") _glBufferSubData *windows.Proc
_glCheckFramebufferStatus = LibGLESv2.NewProc("glCheckFramebufferStatus") _glCheckFramebufferStatus *windows.Proc
_glClear = LibGLESv2.NewProc("glClear") _glClear *windows.Proc
_glClearColor = LibGLESv2.NewProc("glClearColor") _glClearColor *windows.Proc
_glClearDepthf = LibGLESv2.NewProc("glClearDepthf") _glClearDepthf *windows.Proc
_glDeleteQueries = LibGLESv2.NewProc("glDeleteQueries") _glDeleteQueries *windows.Proc
_glDeleteVertexArrays = LibGLESv2.NewProc("glDeleteVertexArrays") _glDeleteVertexArrays *windows.Proc
_glCompileShader = LibGLESv2.NewProc("glCompileShader") _glCompileShader *windows.Proc
_glCopyTexSubImage2D = LibGLESv2.NewProc("glCopyTexSubImage2D") _glCopyTexSubImage2D *windows.Proc
_glGenerateMipmap = LibGLESv2.NewProc("glGenerateMipmap") _glGenerateMipmap *windows.Proc
_glGenBuffers = LibGLESv2.NewProc("glGenBuffers") _glGenBuffers *windows.Proc
_glGenFramebuffers = LibGLESv2.NewProc("glGenFramebuffers") _glGenFramebuffers *windows.Proc
_glGenVertexArrays = LibGLESv2.NewProc("glGenVertexArrays") _glGenVertexArrays *windows.Proc
_glGetUniformBlockIndex = LibGLESv2.NewProc("glGetUniformBlockIndex") _glGetUniformBlockIndex *windows.Proc
_glCreateProgram = LibGLESv2.NewProc("glCreateProgram") _glCreateProgram *windows.Proc
_glGenRenderbuffers = LibGLESv2.NewProc("glGenRenderbuffers") _glGenRenderbuffers *windows.Proc
_glCreateShader = LibGLESv2.NewProc("glCreateShader") _glCreateShader *windows.Proc
_glGenTextures = LibGLESv2.NewProc("glGenTextures") _glGenTextures *windows.Proc
_glDeleteBuffers = LibGLESv2.NewProc("glDeleteBuffers") _glDeleteBuffers *windows.Proc
_glDeleteFramebuffers = LibGLESv2.NewProc("glDeleteFramebuffers") _glDeleteFramebuffers *windows.Proc
_glDeleteProgram = LibGLESv2.NewProc("glDeleteProgram") _glDeleteProgram *windows.Proc
_glDeleteShader = LibGLESv2.NewProc("glDeleteShader") _glDeleteShader *windows.Proc
_glDeleteRenderbuffers = LibGLESv2.NewProc("glDeleteRenderbuffers") _glDeleteRenderbuffers *windows.Proc
_glDeleteTextures = LibGLESv2.NewProc("glDeleteTextures") _glDeleteTextures *windows.Proc
_glDepthFunc = LibGLESv2.NewProc("glDepthFunc") _glDepthFunc *windows.Proc
_glDepthMask = LibGLESv2.NewProc("glDepthMask") _glDepthMask *windows.Proc
_glDisableVertexAttribArray = LibGLESv2.NewProc("glDisableVertexAttribArray") _glDisableVertexAttribArray *windows.Proc
_glDisable = LibGLESv2.NewProc("glDisable") _glDisable *windows.Proc
_glDrawArrays = LibGLESv2.NewProc("glDrawArrays") _glDrawArrays *windows.Proc
_glDrawElements = LibGLESv2.NewProc("glDrawElements") _glDrawElements *windows.Proc
_glEnable = LibGLESv2.NewProc("glEnable") _glEnable *windows.Proc
_glEnableVertexAttribArray = LibGLESv2.NewProc("glEnableVertexAttribArray") _glEnableVertexAttribArray *windows.Proc
_glEndQuery = LibGLESv2.NewProc("glEndQuery") _glEndQuery *windows.Proc
_glFinish = LibGLESv2.NewProc("glFinish") _glFinish *windows.Proc
_glFlush = LibGLESv2.NewProc("glFlush") _glFlush *windows.Proc
_glFramebufferRenderbuffer = LibGLESv2.NewProc("glFramebufferRenderbuffer") _glFramebufferRenderbuffer *windows.Proc
_glFramebufferTexture2D = LibGLESv2.NewProc("glFramebufferTexture2D") _glFramebufferTexture2D *windows.Proc
_glGenQueries = LibGLESv2.NewProc("glGenQueries") _glGenQueries *windows.Proc
_glGetError = LibGLESv2.NewProc("glGetError") _glGetError *windows.Proc
_glGetRenderbufferParameteriv = LibGLESv2.NewProc("glGetRenderbufferParameteriv") _glGetRenderbufferParameteriv *windows.Proc
_glGetFloatv = LibGLESv2.NewProc("glGetFloatv") _glGetFloatv *windows.Proc
_glGetFramebufferAttachmentParameteriv = LibGLESv2.NewProc("glGetFramebufferAttachmentParameteriv") _glGetFramebufferAttachmentParameteriv *windows.Proc
_glGetIntegerv = LibGLESv2.NewProc("glGetIntegerv") _glGetIntegerv *windows.Proc
_glGetIntegeri_v = LibGLESv2.NewProc("glGetIntegeri_v") _glGetIntegeri_v *windows.Proc
_glGetProgramiv = LibGLESv2.NewProc("glGetProgramiv") _glGetProgramiv *windows.Proc
_glGetProgramInfoLog = LibGLESv2.NewProc("glGetProgramInfoLog") _glGetProgramInfoLog *windows.Proc
_glGetQueryObjectuiv = LibGLESv2.NewProc("glGetQueryObjectuiv") _glGetQueryObjectuiv *windows.Proc
_glGetShaderiv = LibGLESv2.NewProc("glGetShaderiv") _glGetShaderiv *windows.Proc
_glGetShaderInfoLog = LibGLESv2.NewProc("glGetShaderInfoLog") _glGetShaderInfoLog *windows.Proc
_glGetString = LibGLESv2.NewProc("glGetString") _glGetString *windows.Proc
_glGetUniformLocation = LibGLESv2.NewProc("glGetUniformLocation") _glGetUniformLocation *windows.Proc
_glGetVertexAttribiv = LibGLESv2.NewProc("glGetVertexAttribiv") _glGetVertexAttribiv *windows.Proc
_glGetVertexAttribPointerv = LibGLESv2.NewProc("glGetVertexAttribPointerv") _glGetVertexAttribPointerv *windows.Proc
_glInvalidateFramebuffer = LibGLESv2.NewProc("glInvalidateFramebuffer") _glInvalidateFramebuffer *windows.Proc
_glIsEnabled = LibGLESv2.NewProc("glIsEnabled") _glIsEnabled *windows.Proc
_glLinkProgram = LibGLESv2.NewProc("glLinkProgram") _glLinkProgram *windows.Proc
_glPixelStorei = LibGLESv2.NewProc("glPixelStorei") _glPixelStorei *windows.Proc
_glReadPixels = LibGLESv2.NewProc("glReadPixels") _glReadPixels *windows.Proc
_glRenderbufferStorage = LibGLESv2.NewProc("glRenderbufferStorage") _glRenderbufferStorage *windows.Proc
_glScissor = LibGLESv2.NewProc("glScissor") _glScissor *windows.Proc
_glShaderSource = LibGLESv2.NewProc("glShaderSource") _glShaderSource *windows.Proc
_glTexImage2D = LibGLESv2.NewProc("glTexImage2D") _glTexImage2D *windows.Proc
_glTexStorage2D = LibGLESv2.NewProc("glTexStorage2D") _glTexStorage2D *windows.Proc
_glTexSubImage2D = LibGLESv2.NewProc("glTexSubImage2D") _glTexSubImage2D *windows.Proc
_glTexParameteri = LibGLESv2.NewProc("glTexParameteri") _glTexParameteri *windows.Proc
_glUniformBlockBinding = LibGLESv2.NewProc("glUniformBlockBinding") _glUniformBlockBinding *windows.Proc
_glUniform1f = LibGLESv2.NewProc("glUniform1f") _glUniform1f *windows.Proc
_glUniform1i = LibGLESv2.NewProc("glUniform1i") _glUniform1i *windows.Proc
_glUniform2f = LibGLESv2.NewProc("glUniform2f") _glUniform2f *windows.Proc
_glUniform3f = LibGLESv2.NewProc("glUniform3f") _glUniform3f *windows.Proc
_glUniform4f = LibGLESv2.NewProc("glUniform4f") _glUniform4f *windows.Proc
_glUseProgram = LibGLESv2.NewProc("glUseProgram") _glUseProgram *windows.Proc
_glVertexAttribPointer = LibGLESv2.NewProc("glVertexAttribPointer") _glVertexAttribPointer *windows.Proc
_glViewport = LibGLESv2.NewProc("glViewport") _glViewport *windows.Proc
) )
type Functions struct { type Functions struct {
@@ -115,7 +229,11 @@ func NewFunctions(ctx Context, forceES bool) (*Functions, error) {
if ctx != nil { if ctx != nil {
panic("non-nil context") panic("non-nil context")
} }
return new(Functions), nil var err error
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 (r PushConstantRange) StageFlags() ShaderStageFlags { func PushConstantRangeStageFlags(r PushConstantRange) ShaderStageFlags {
return r.stageFlags return r.stageFlags
} }
func (r PushConstantRange) Offset() int { func PushConstantRangeOffset(r PushConstantRange) int {
return int(r.offset) return int(r.offset)
} }
func (r PushConstantRange) Size() int { func PushConstantRangeSize(r PushConstantRange) int {
return int(r.size) return int(r.size)
} }
func (p QueueFamilyProperties) Flags() QueueFlags { func QueueFamilyPropertiesFlags(p QueueFamilyProperties) QueueFlags {
return p.queueFlags return p.queueFlags
} }
func (c SurfaceCapabilities) MinExtent() image.Point { func SurfaceCapabilitiesMinExtent(c SurfaceCapabilities) 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 (c SurfaceCapabilities) MaxExtent() image.Point { func SurfaceCapabilitiesMaxExtent(c SurfaceCapabilities) 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 value Source is disabled. // The zero-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...)
+17 -3
View File
@@ -5,6 +5,7 @@ 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"
@@ -28,6 +29,7 @@ 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
} }
@@ -42,9 +44,21 @@ func (c Context) Sp(v unit.Sp) int {
return c.Metric.Sp(v) return c.Metric.Sp(v)
} }
// Disabled returns a copy of this context with a disabled Source, func (c Context) Event(filters ...event.Filter) (event.Event, bool) {
// blocking widgets from changing its state and receiving events. if c.disabled {
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.Source = input.Source{} c.disabled = true
return c return c
} }
+39 -88
View File
@@ -12,10 +12,9 @@ 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"
@@ -201,7 +200,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
@@ -211,8 +210,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 {
@@ -254,7 +253,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 {
@@ -282,7 +281,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
} }
@@ -377,11 +376,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(metadata.Description{ md := opentype.DescriptionToFont(font.Description{
Family: family, Family: family,
Aspect: aspect, Aspect: aspect,
}) })
@@ -491,9 +490,11 @@ 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{
TruncateAfterLines: params.MaxLines, Direction: mapDirection(params.Locale.Direction),
TextContinues: params.forceTruncate, TruncateAfterLines: params.MaxLines,
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy), TextContinues: params.forceTruncate,
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
DisableTrailingWhitespaceTrim: params.DisableSpaceTrim,
} }
families := s.defaultFaces families := s.defaultFaces
if params.Font.Typeface != "" { if params.Font.Typeface != "" {
@@ -663,7 +664,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 api.GlyphOutline: case font.GlyphOutline:
outline := glyphData outline := glyphData
// Move to glyph position. // Move to glyph position.
pos := f32.Point{ pos := f32.Point{
@@ -678,9 +679,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 api.SegmentOpQuadTo: case gotextot.SegmentOpQuadTo:
nargs = 2 nargs = 2
case api.SegmentOpCubeTo: case gotextot.SegmentOpCubeTo:
nargs = 3 nargs = 3
} }
var args [3]f32.Point var args [3]f32.Point
@@ -695,13 +696,13 @@ func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
} }
} }
switch fseg.Op { switch fseg.Op {
case api.SegmentOpMoveTo: case gotextot.SegmentOpMoveTo:
builder.Move(args[0]) builder.Move(args[0])
case api.SegmentOpLineTo: case gotextot.SegmentOpLineTo:
builder.Line(args[0]) builder.Line(args[0])
case api.SegmentOpQuadTo: case gotextot.SegmentOpQuadTo:
builder.Quad(args[0], args[1]) builder.Quad(args[0], args[1])
case api.SegmentOpCubeTo: case gotextot.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")
@@ -742,16 +743,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 api.GlyphBitmap: case font.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 api.PNG, api.JPG, api.TIFF: case font.PNG, font.JPG, font.TIFF:
img, _, _ = image.Decode(bytes.NewReader(glyphData.Data)) img, _, _ = image.Decode(bytes.NewReader(glyphData.Data))
case api.BlackAndWhite: case font.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
@@ -807,7 +808,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
@@ -867,13 +868,14 @@ 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 {
@@ -881,7 +883,7 @@ func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirect
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
} }
@@ -891,11 +893,13 @@ func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirect
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 {
@@ -906,64 +910,11 @@ func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirect
} }
} }
line.lineHeight = maxSize line.lineHeight = maxSize
computeVisualOrder(&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. // Iterate and resolve the X of each run.
x := fixed.Int26_6(0) x := fixed.Int26_6(0)
for _, runIdx := range l.visualOrder { for _, runIdx := range line.visualOrder {
l.runs[runIdx].X = x line.runs[runIdx].X = x
x += l.runs[runIdx].Advance x += line.runs[runIdx].Advance
} }
return line
} }
-115
View File
@@ -3,7 +3,6 @@ package text
import ( import (
"fmt" "fmt"
"math" "math"
"reflect"
"strconv" "strconv"
"testing" "testing"
@@ -450,120 +449,6 @@ 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)
+14 -1
View File
@@ -76,6 +76,11 @@ 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
@@ -199,7 +204,15 @@ func (f Flags) String() string {
type GlyphID uint64 type GlyphID uint64
// Shaper converts strings of text into glyphs that can be displayed. // Shaper converts strings of text into glyphs that can be displayed. The same
// 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
+6 -23
View File
@@ -11,8 +11,11 @@ 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.
@@ -40,17 +43,6 @@ 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
@@ -60,21 +52,12 @@ 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,6 +610,7 @@ 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
+65 -47
View File
@@ -2,12 +2,18 @@ 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"
@@ -16,11 +22,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) (source string, bidiLTR, bidiRTL []text.Glyph) { func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (shaper *text.Shaper, 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,
@@ -58,7 +64,7 @@ func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (source string
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 bidiSource, bidiLTR, bidiRTL return shaper, bidiSource, bidiLTR, bidiRTL
} }
// makeAccountingTestText shapes text designed to stress rune accounting // makeAccountingTestText shapes text designed to stress rune accounting
@@ -260,7 +266,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
_, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false) shaper, _, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
type testcase struct { type testcase struct {
name string name string
glyphs []text.Glyph glyphs []text.Glyph
@@ -284,13 +290,12 @@ func TestIndexPositionBidi(t *testing.T) {
name: "bidi rtl", name: "bidi rtl",
glyphs: bidiRTLText, glyphs: bidiRTLText,
expectedXs: []fixed.Int26_6{ expectedXs: []fixed.Int26_6{
2646, 3272, 3842, 4412, 4697, 5267, 5837, 6090, 6602, 7114, 2646, 2380, 1577, 985, 687, 266, // Positions on line 0. // Line 0
5718, 6344, 6914, 7484, 7769, 8339, 8909, 9162, 9674, 10186, 5718, 5452, 4649, 4057, 3759, 3338, 3072, 2304, 1536, 768, 0,
7867, 7099, 6331, 5563, 4795, 4510, 4212, 3914, 3648, 2281, 2566, 3136, 3648, 2281, 2015, 1709, 1117, 266, // Positions on line 1. // Line 1
9170, 8872, 8574, 8308, 6941, 7226, 7796, 8308, 6941, 6675, 6369, 5777, 4926, 4660, 3892, 3124, 2356, 1588, 1303, 788, 406, 0,
8794, 8026, 7258, 6490, 5722, 5437, 4922, 4540, 4134, 3868, 0, 290, 860, 1430, 1715, 1989, 2559, 3071, 3583, // Positions on line 2. // 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.
}, },
}, },
} { } {
@@ -337,6 +342,35 @@ 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)
} }
}) })
} }
@@ -345,8 +379,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
@@ -379,7 +413,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(8813), width: fixed.Int26_6(8528),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
@@ -401,32 +435,24 @@ func TestIndexPositionLines(t *testing.T) {
{ {
xOff: fixed.Int26_6(0), xOff: fixed.Int26_6(0),
yOff: 22, yOff: 22,
glyphs: 15, glyphs: 20,
width: fixed.Int26_6(7114), width: fixed.Int26_6(10186),
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: 15, glyphs: 19,
width: fixed.Int26_6(7867), width: fixed.Int26_6(9170),
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: 18, glyphs: 13,
width: fixed.Int26_6(8794), width: fixed.Int26_6(5902),
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),
}, },
@@ -454,10 +480,10 @@ func TestIndexPositionLines(t *testing.T) {
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(1427), xOff: fixed.Int26_6(1712),
yOff: 60, yOff: 60,
glyphs: 18, glyphs: 18,
width: fixed.Int26_6(8813), width: fixed.Int26_6(8528),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
@@ -477,34 +503,26 @@ func TestIndexPositionLines(t *testing.T) {
glyphs: bidiRTLTextOpp, glyphs: bidiRTLTextOpp,
expectedLines: []lineInfo{ expectedLines: []lineInfo{
{ {
xOff: fixed.Int26_6(3126), xOff: fixed.Int26_6(54),
yOff: 22, yOff: 22,
glyphs: 15, glyphs: 20,
width: fixed.Int26_6(7114), width: fixed.Int26_6(10186),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(2373), xOff: fixed.Int26_6(1070),
yOff: 41, yOff: 41,
glyphs: 15, glyphs: 19,
width: fixed.Int26_6(7867), width: fixed.Int26_6(9170),
ascent: fixed.Int26_6(1407), ascent: fixed.Int26_6(1407),
descent: fixed.Int26_6(756), descent: fixed.Int26_6(756),
}, },
{ {
xOff: fixed.Int26_6(1446), xOff: fixed.Int26_6(4338),
yOff: 60, yOff: 60,
glyphs: 18, glyphs: 13,
width: fixed.Int26_6(8794), width: fixed.Int26_6(5902),
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,6 +32,9 @@ 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,6 +61,9 @@ 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.
@@ -285,6 +288,10 @@ 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()