├── .builds
├── apple.yml
├── freebsd.yml
├── linux.yml
└── openbsd.yml
├── .gitattributes
├── LICENSE
├── README.md
├── app
├── Gio.java
├── GioActivity.java
├── GioView.java
├── app.go
├── d3d11_windows.go
├── datadir.go
├── doc.go
├── egl_android.go
├── egl_wayland.go
├── egl_windows.go
├── egl_x11.go
├── framework_ios.h
├── gl_ios.go
├── gl_ios.m
├── gl_js.go
├── gl_macos.go
├── gl_macos.m
├── ime.go
├── ime_test.go
├── internal
│ ├── windows
│ │ └── windows.go
│ └── xkb
│ │ └── xkb_unix.go
├── log_android.go
├── log_ios.go
├── log_windows.go
├── metal_darwin.go
├── metal_ios.go
├── metal_macos.go
├── os.go
├── os_android.go
├── os_darwin.go
├── os_darwin.m
├── os_ios.go
├── os_ios.m
├── os_js.go
├── os_macos.go
├── os_macos.m
├── os_unix.go
├── os_wayland.c
├── os_wayland.go
├── os_windows.go
├── os_x11.go
├── permission
│ ├── bluetooth
│ │ └── main.go
│ ├── camera
│ │ └── main.go
│ ├── doc.go
│ ├── networkstate
│ │ └── main.go
│ ├── storage
│ │ └── main.go
│ └── wakelock
│ │ └── wakelock.go
├── runmain.go
├── system.go
├── vulkan.go
├── vulkan_android.go
├── vulkan_wayland.go
├── vulkan_x11.go
├── wayland_text_input.c
├── wayland_text_input.h
├── wayland_xdg_decoration.c
├── wayland_xdg_decoration.h
├── wayland_xdg_shell.c
├── wayland_xdg_shell.h
└── window.go
├── f32
├── affine.go
├── affine_test.go
└── f32.go
├── flake.lock
├── flake.nix
├── font
├── font.go
├── gofont
│ └── gofont.go
└── opentype
│ └── opentype.go
├── gesture
├── gesture.go
└── gesture_test.go
├── go.mod
├── go.sum
├── gpu
├── api.go
├── caches.go
├── clip.go
├── clip_test.go
├── gpu.go
├── headless
│ ├── driver_test.go
│ ├── headless.go
│ ├── headless_darwin.go
│ ├── headless_egl.go
│ ├── headless_js.go
│ ├── headless_test.go
│ ├── headless_vulkan.go
│ └── headless_windows.go
├── internal
│ ├── d3d11
│ │ ├── d3d11.go
│ │ └── d3d11_windows.go
│ ├── driver
│ │ ├── api.go
│ │ └── driver.go
│ ├── metal
│ │ ├── metal.go
│ │ └── metal_darwin.go
│ ├── opengl
│ │ ├── opengl.go
│ │ └── srgb.go
│ ├── rendertest
│ │ ├── bench_test.go
│ │ ├── clip_test.go
│ │ ├── doc.go
│ │ ├── refs
│ │ │ ├── TestBuildOffscreen.png
│ │ │ ├── TestBuildOffscreen_1.png
│ │ │ ├── TestClipOffset.png
│ │ │ ├── TestClipPaintOffset.png
│ │ │ ├── TestClipRotate.png
│ │ │ ├── TestClipScale.png
│ │ │ ├── TestComplicatedTransform.png
│ │ │ ├── TestDeferredPaint.png
│ │ │ ├── TestDepthOverlap.png
│ │ │ ├── TestGapsInPath
│ │ │ │ ├── Outline.png
│ │ │ │ └── Stroke.png
│ │ │ ├── TestImageRGBA.png
│ │ │ ├── TestImageRGBA_ScaleLinear.png
│ │ │ ├── TestImageRGBA_ScaleNearest.png
│ │ │ ├── TestInstancedRects.png
│ │ │ ├── TestLinearGradientAngled.png
│ │ │ ├── TestNegativeOverlaps.png
│ │ │ ├── TestNoClipFromPaint.png
│ │ │ ├── TestOffsetScaleTexture.png
│ │ │ ├── TestOffsetTexture.png
│ │ │ ├── TestOpacity.png
│ │ │ ├── TestPaintAbsolute.png
│ │ │ ├── TestPaintArc.png
│ │ │ ├── TestPaintClippedCircle.png
│ │ │ ├── TestPaintClippedRect.png
│ │ │ ├── TestPaintClippedTexture.png
│ │ │ ├── TestPaintOffset.png
│ │ │ ├── TestPaintRect.png
│ │ │ ├── TestPaintRotate.png
│ │ │ ├── TestPaintShear.png
│ │ │ ├── TestPaintTexture.png
│ │ │ ├── TestPathReuse.png
│ │ │ ├── TestRepeatedPaintsZ.png
│ │ │ ├── TestReuseStencil.png
│ │ │ ├── TestRotateClipTexture.png
│ │ │ ├── TestRotateTexture.png
│ │ │ ├── TestStrokedPathBalloon.png
│ │ │ ├── TestStrokedPathCoincidentControlPoint.png
│ │ │ ├── TestStrokedPathZeroWidth.png
│ │ │ ├── TestStrokedRect.png
│ │ │ ├── TestTexturedStroke.png
│ │ │ ├── TestTexturedStrokeClipped.png
│ │ │ ├── TestTransformMacro.png
│ │ │ └── TestTransformOrder.png
│ │ ├── render_test.go
│ │ ├── transform_test.go
│ │ └── util_test.go
│ └── vulkan
│ │ ├── vulkan.go
│ │ └── vulkan_nosupport.go
├── pack.go
├── pack_test.go
├── path.go
└── timer.go
├── internal
├── byteslice
│ └── byteslice.go
├── cocoainit
│ └── cocoa_darwin.go
├── d3d11
│ └── d3d11_windows.go
├── debug
│ └── debug.go
├── egl
│ ├── egl.go
│ ├── egl_unix.go
│ └── egl_windows.go
├── f32
│ └── f32.go
├── f32color
│ ├── f32colorgen
│ │ └── main.go
│ ├── rgba.go
│ ├── rgba_test.go
│ └── tables.go
├── fling
│ ├── animation.go
│ ├── extrapolation.go
│ └── extrapolation_test.go
├── gl
│ ├── gl.go
│ ├── gl_js.go
│ ├── gl_unix.go
│ ├── gl_windows.go
│ ├── types.go
│ ├── types_js.go
│ └── util.go
├── ops
│ ├── ops.go
│ └── reader.go
├── scene
│ └── scene.go
├── stroke
│ ├── stroke.go
│ └── stroke_test.go
└── vk
│ ├── vulkan.go
│ ├── vulkan_android.go
│ ├── vulkan_wayland.go
│ └── vulkan_x11.go
├── io
├── clipboard
│ └── clipboard.go
├── event
│ └── event.go
├── input
│ ├── clipboard.go
│ ├── clipboard_test.go
│ ├── doc.go
│ ├── key.go
│ ├── key_test.go
│ ├── pointer.go
│ ├── pointer_test.go
│ ├── router.go
│ ├── router_test.go
│ └── semantic_test.go
├── key
│ ├── key.go
│ ├── mod.go
│ └── mod_darwin.go
├── pointer
│ ├── doc.go
│ ├── pointer.go
│ └── pointer_test.go
├── semantic
│ └── semantic.go
├── system
│ ├── decoration.go
│ └── locale.go
└── transfer
│ └── transfer.go
├── layout
├── alloc_test.go
├── context.go
├── doc.go
├── example_test.go
├── flex.go
├── layout.go
├── layout_test.go
├── list.go
├── list_test.go
├── stack.go
└── stack_test.go
├── op
├── clip
│ ├── clip.go
│ ├── clip_test.go
│ ├── doc.go
│ ├── shapes.go
│ └── shapes_test.go
├── op.go
├── op_test.go
└── paint
│ ├── doc.go
│ └── paint.go
├── text
├── family_parser.go
├── family_parser_test.go
├── gotext.go
├── gotext_test.go
├── lru.go
├── lru_test.go
├── shaper.go
├── shaper_test.go
├── testdata
│ └── fuzz
│ │ └── FuzzLayout
│ │ ├── 2a7730fcbcc3550718b37415eb6104cf09159c7d756cc3530ccaa9007c0a2c06
│ │ ├── 3fc3ee939f0df44719bee36a67df3aa3a562e2f2f1cfb665a650f60e7e0ab4e6
│ │ ├── 40894369fe6a0c11
│ │ ├── 4197f52af4459898
│ │ ├── 594e4fda2e3462061d50b6a74279d7e3e486d8ac211e45049cfa4833257ef236
│ │ ├── 6b452fd81f16c000dbe525077b6f4ba91b040119d82d55991d568014f4ba671e
│ │ ├── 6b5a1e9cd750a9aa8dafe11bc74e2377c0664f64bbf2ac8b1cdd0ce9f55461b3
│ │ ├── 940ce2b5ed93df01
│ │ ├── be56090b98f3c84da6ff9e857d5487d1a89309f7312ccde5ff8b94fcd29521eb
│ │ ├── dda958d1b1bb9df71f81c575cb8900f97fc1c20e19de09fc0ffca8ff0c8d9e5a
│ │ ├── de31ec6b1ac7797daefecf39baaace51fa4348bed0e3b662dd50aa341f67d10c
│ │ ├── e79034d6c7a6ce74d7a689f4ea99e50a6ecd0d4134e3a77234172d13787ac4ea
│ │ ├── f1f0611baadc20469863e483f58a3ca4eba895087316b31e598f0b05c978d87c
│ │ └── f2ebea678c72f6c394d7f860599b281593098b0aad864231d756c3e9699029c1
└── text.go
├── unit
├── unit.go
└── unit_test.go
└── widget
├── bool.go
├── border.go
├── buffer.go
├── button.go
├── button_test.go
├── decorations.go
├── dnd.go
├── dnd_test.go
├── doc.go
├── editor.go
├── editor_test.go
├── enum.go
├── example_test.go
├── fit.go
├── fit_test.go
├── float.go
├── icon.go
├── icon_test.go
├── image.go
├── image_test.go
├── index.go
├── index_test.go
├── label.go
├── label_test.go
├── list.go
├── material
├── button.go
├── checkable.go
├── checkbox.go
├── decorations.go
├── doc.go
├── editor.go
├── label.go
├── list.go
├── list_test.go
├── loader.go
├── progressbar.go
├── progresscircle.go
├── radiobutton.go
├── slider.go
├── switch.go
└── theme.go
├── selectable.go
├── selectable_test.go
├── testdata
└── fuzz
│ └── FuzzEditorEditing
│ ├── 18c534da60e6b61361786a120fe7b82978ac0e5c16afb519beb080f73c470d3f
│ ├── a489f2d9f9226d13b55846645d025ac15316f1210d4aa307cde333cc87fdad55
│ ├── clusterIndexForCrash1
│ └── clusterIndexForCrash2
├── text.go
├── text_bench_test.go
└── widget_test.go
/.builds/apple.yml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Unlicense OR MIT
2 | image: debian/testing
3 | packages:
4 | - clang
5 | - cmake
6 | - curl
7 | - autoconf
8 | - libxml2-dev
9 | - libssl-dev
10 | - libz-dev
11 | - llvm-dev # cctools
12 | - uuid-dev # cctools
13 | - ninja-build # cctools
14 | - systemtap-sdt-dev # cctools
15 | - libbsd-dev # cctools
16 | - linux-libc-dev # cctools
17 | - libplist-utils # for gogio
18 | sources:
19 | - https://git.sr.ht/~eliasnaur/applesdks
20 | - https://git.sr.ht/~eliasnaur/gio
21 | - https://git.sr.ht/~eliasnaur/giouiorg
22 | - https://github.com/tpoechtrager/cctools-port
23 | - https://github.com/tpoechtrager/apple-libtapi
24 | - https://github.com/tpoechtrager/apple-libdispatch
25 | - https://github.com/mackyle/xar
26 | environment:
27 | APPLE_TOOLCHAIN_ROOT: /home/build/appletools
28 | PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin
29 | tasks:
30 | - install_go: |
31 | mkdir -p /home/build/sdk
32 | curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
33 | - prepare_toolchain: |
34 | mkdir -p $APPLE_TOOLCHAIN_ROOT
35 | cd $APPLE_TOOLCHAIN_ROOT
36 | tar xJf /home/build/applesdks/applesdks.tar.xz
37 | mkdir bin tools
38 | cd bin
39 | ln -s ../toolchain/bin/x86_64-apple-darwin19-ld ld
40 | ln -s ../toolchain/bin/x86_64-apple-darwin19-ar ar
41 | ln -s /home/build/cctools-port/cctools/misc/lipo lipo
42 | ln -s ../tools/appletoolchain xcrun
43 | ln -s /usr/bin/plistutil plutil
44 | cd ../tools
45 | ln -s appletoolchain clang-ios
46 | ln -s appletoolchain clang-macos
47 | - install_appletoolchain: |
48 | cd giouiorg
49 | go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
50 | - build_libdispatch: |
51 | cd apple-libdispatch
52 | cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$APPLE_TOOLCHAIN_ROOT/libdispatch .
53 | ninja
54 | ninja install
55 | - build_xar: |
56 | cd xar/xar
57 | ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
58 | make
59 | sudo make install
60 | - build_libtapi: |
61 | cd apple-libtapi
62 | INSTALLPREFIX=$APPLE_TOOLCHAIN_ROOT/libtapi ./build.sh
63 | ./install.sh
64 | - build_cctools: |
65 | cd cctools-port/cctools
66 | ./configure --target=x86_64-apple-darwin19 --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --with-libdispatch=$APPLE_TOOLCHAIN_ROOT/libdispatch --with-libblocksruntime=$APPLE_TOOLCHAIN_ROOT/libdispatch
67 | make install
68 | - test_macos: |
69 | cd gio
70 | export PATH=/home/build/appletools/bin:$PATH
71 | CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
72 | - test_ios: |
73 | cd gio
74 | 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 ./...
75 |
--------------------------------------------------------------------------------
/.builds/freebsd.yml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Unlicense OR MIT
2 | image: freebsd/latest
3 | packages:
4 | - libX11
5 | - libxkbcommon
6 | - libXcursor
7 | - libXfixes
8 | - vulkan-headers
9 | - wayland
10 | - mesa-libs
11 | - xorg-vfbserver
12 | sources:
13 | - https://git.sr.ht/~eliasnaur/gio
14 | environment:
15 | PATH: /home/build/sdk/go/bin:/bin:/usr/local/bin:/usr/bin
16 | tasks:
17 | - install_go: |
18 | mkdir -p /home/build/sdk
19 | curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
20 | - test_gio: |
21 | cd gio
22 | go test ./...
23 |
--------------------------------------------------------------------------------
/.builds/linux.yml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Unlicense OR MIT
2 | image: debian/testing
3 | packages:
4 | - curl
5 | - pkg-config
6 | - gcc-multilib
7 | - libwayland-dev
8 | - libx11-dev
9 | - libx11-xcb-dev
10 | - libxkbcommon-dev
11 | - libxkbcommon-x11-dev
12 | - libgles2-mesa-dev
13 | - libegl1-mesa-dev
14 | - libffi-dev
15 | - libvulkan-dev
16 | - libxcursor-dev
17 | - libxrandr-dev
18 | - libxinerama-dev
19 | - libxi-dev
20 | - libxxf86vm-dev
21 | - mesa-vulkan-drivers
22 | - wine
23 | - xvfb
24 | - xdotool
25 | - scrot
26 | - sway
27 | - grim
28 | - unzip
29 | sources:
30 | - https://git.sr.ht/~eliasnaur/gio
31 | environment:
32 | PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig/:/usr/lib/i386-linux-gnu/pkgconfig/
33 | PATH: /home/build/sdk/go/bin:/usr/bin:/home/build/go/bin:/home/build/android/tools/bin
34 | ANDROID_SDK_ROOT: /home/build/android
35 | android_sdk_tools_zip: sdk-tools-linux-3859397.zip
36 | android_ndk_zip: android-ndk-r20-linux-x86_64.zip
37 | github_mirror: git@github.com:gioui/gio
38 | secrets:
39 | - 75d8a1eb-5fc5-4074-8a36-db6015d6ed5a
40 | tasks:
41 | - install_go: |
42 | mkdir -p /home/build/sdk
43 | curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
44 | - check_gofmt: |
45 | cd gio
46 | test -z "$(gofmt -s -l .)"
47 | - check_sign_off: |
48 | set +x -e
49 | cd gio
50 | for hash in $(git log -n 20 --format="%H"); do
51 | message=$(git log -1 --format=%B $hash)
52 | if [[ ! "$message" =~ "Signed-off-by: " ]]; then
53 | echo "Missing 'Signed-off-by' in commit $hash"
54 | exit 1
55 | fi
56 | done
57 | - mirror: |
58 | # mirror to github
59 | ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio && git push --mirror "$github_mirror" || echo "failed mirroring"
60 | - add_32bit_arch: |
61 | sudo dpkg --add-architecture i386
62 | sudo apt-get update
63 | sudo apt-get install -y "libwayland-dev:i386" "libx11-dev:i386" "libx11-xcb-dev:i386" "libxkbcommon-dev:i386" "libxkbcommon-x11-dev:i386" "libgles2-mesa-dev:i386" "libegl1-mesa-dev:i386" "libffi-dev:i386" "libvulkan-dev:i386" "libxcursor-dev:i386"
64 | - test_gio: |
65 | cd gio
66 | go test -race ./...
67 | CGO_ENABLED=1 GOARCH=386 go test ./...
68 | GOOS=windows go test -exec=wine ./...
69 | GOOS=js GOARCH=wasm go build -o /dev/null ./...
70 | - install_jdk8: |
71 | curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb"
72 | sudo apt-get -qq install -y -f ./jdk.deb
73 | - install_android: |
74 | mkdir android
75 | cd android
76 | curl -so sdk-tools.zip https://dl.google.com/android/repository/$android_sdk_tools_zip
77 | unzip -q sdk-tools.zip
78 | rm sdk-tools.zip
79 | curl -so ndk.zip https://dl.google.com/android/repository/$android_ndk_zip
80 | unzip -q ndk.zip
81 | rm ndk.zip
82 | mv android-ndk-* ndk-bundle
83 | # sdkmanager needs lots of file descriptors
84 | ulimit -n 10000
85 | yes|sdkmanager --licenses
86 | sdkmanager "platforms;android-31" "build-tools;32.0.0"
87 | - test_android: |
88 | cd gio
89 | CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build ./...
90 | CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang GOOS=android GOARCH=arm CGO_ENABLED=1 go build ./...
91 |
--------------------------------------------------------------------------------
/.builds/openbsd.yml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Unlicense OR MIT
2 | image: openbsd/latest
3 | packages:
4 | - libxkbcommon
5 | - go
6 | sources:
7 | - https://git.sr.ht/~eliasnaur/gio
8 | environment:
9 | PATH: /home/build/sdk/go/bin:/bin:/usr/local/bin:/usr/bin
10 | tasks:
11 | - install_go: |
12 | mkdir -p /home/build/sdk
13 | curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
14 | cd /home/build/sdk/go/src
15 | ./make.bash
16 | - test_gio: |
17 | cd gio
18 | go test ./...
19 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Treat all files as binary, with no git magic updating
2 | # line endings.
3 | * -text
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This project is provided under the terms of the UNLICENSE or
2 | the MIT license denoted by the following SPDX identifier:
3 |
4 | SPDX-License-Identifier: Unlicense OR MIT
5 |
6 | You may use the project under the terms of either license.
7 |
8 | Both licenses are reproduced below.
9 |
10 | ----
11 | The MIT License (MIT)
12 |
13 | Copyright (c) 2019 The Gio authors
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining a copy
16 | of this software and associated documentation files (the "Software"), to deal
17 | in the Software without restriction, including without limitation the rights
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | copies of the Software, and to permit persons to whom the Software is
20 | furnished to do so, subject to the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included in
23 | all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 | THE SOFTWARE.
32 | ---
33 |
34 |
35 |
36 | ---
37 | The UNLICENSE
38 |
39 | This is free and unencumbered software released into the public domain.
40 |
41 | Anyone is free to copy, modify, publish, use, compile, sell, or
42 | distribute this software, either in source code form or as a compiled
43 | binary, for any purpose, commercial or non-commercial, and by any
44 | means.
45 |
46 | In jurisdictions that recognize copyright laws, the author or authors
47 | of this software dedicate any and all copyright interest in the
48 | software to the public domain. We make this dedication for the benefit
49 | of the public at large and to the detriment of our heirs and
50 | successors. We intend this dedication to be an overt act of
51 | relinquishment in perpetuity of all present and future rights to this
52 | software under copyright law.
53 |
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
55 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
56 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
57 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
58 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
59 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
60 | OTHER DEALINGS IN THE SOFTWARE.
61 |
62 | For more information, please refer to
63 | ---
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gio - https://gioui.org
2 |
3 | Immediate mode GUI programs in Go for Android, iOS, macOS, Linux,
4 | FreeBSD, OpenBSD, Windows, and WebAssembly (experimental).
5 |
6 | # Installation, examples, documentation
7 |
8 | Go to [gioui.org](https://gioui.org).
9 |
10 | [](https://builds.sr.ht/~eliasnaur/gio)
11 |
12 | ## Issues
13 |
14 | File bugs and TODOs through the [issue tracker](https://todo.sr.ht/~eliasnaur/gio) or send an email
15 | to [~eliasnaur/gio@todo.sr.ht](mailto:~eliasnaur/gio@todo.sr.ht). For general discussion, use the
16 | mailing list: [~eliasnaur/gio@lists.sr.ht](mailto:~eliasnaur/gio@lists.sr.ht).
17 |
18 | ## Contributing
19 |
20 | Post discussion to the [mailing list](https://lists.sr.ht/~eliasnaur/gio) and patches to
21 | [gio-patches](https://lists.sr.ht/~eliasnaur/gio-patches). No Sourcehut
22 | account is required and you can post without being subscribed.
23 |
24 | See the [contribution guide](https://gioui.org/doc/contribute) for more details.
25 |
26 | An [official GitHub mirror](https://github.com/gioui/gio) is available.
27 |
28 | ## Tags
29 |
30 | Pre-1.0 tags are provided for reference only, and do not designate releases with ongoing support. Bugfixes will not be backported to older tags.
31 |
32 | Tags follow semantic versioning. In particular, as the major version is zero:
33 |
34 | - breaking API or behavior changes will increment the *minor* version component.
35 | - non-breaking changes will increment the *patch* version component.
36 |
--------------------------------------------------------------------------------
/app/Gio.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package org.gioui;
4 |
5 | import android.content.ClipboardManager;
6 | import android.content.ClipData;
7 | import android.content.Context;
8 | import android.os.Handler;
9 | import android.os.Looper;
10 |
11 | import java.io.UnsupportedEncodingException;
12 |
13 | public final class Gio {
14 | private static final Object initLock = new Object();
15 | private static boolean jniLoaded;
16 | private static final Handler handler = new Handler(Looper.getMainLooper());
17 |
18 | /**
19 | * init loads and initializes the Go native library and runs
20 | * the Go main function.
21 | *
22 | * It is exported for use by Android apps that need to run Go code
23 | * outside the lifecycle of the Gio activity.
24 | */
25 | public static synchronized void init(Context appCtx) {
26 | synchronized (initLock) {
27 | if (jniLoaded) {
28 | return;
29 | }
30 | String dataDir = appCtx.getFilesDir().getAbsolutePath();
31 | byte[] dataDirUTF8;
32 | try {
33 | dataDirUTF8 = dataDir.getBytes("UTF-8");
34 | } catch (UnsupportedEncodingException e) {
35 | throw new RuntimeException(e);
36 | }
37 | System.loadLibrary("gio");
38 | runGoMain(dataDirUTF8, appCtx);
39 | jniLoaded = true;
40 | }
41 | }
42 |
43 | static private native void runGoMain(byte[] dataDir, Context context);
44 |
45 | static void writeClipboard(Context ctx, String s) {
46 | ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
47 | m.setPrimaryClip(ClipData.newPlainText(null, s));
48 | }
49 |
50 | static String readClipboard(Context ctx) {
51 | ClipboardManager m = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
52 | ClipData c = m.getPrimaryClip();
53 | if (c == null || c.getItemCount() < 1) {
54 | return null;
55 | }
56 | return c.getItemAt(0).coerceToText(ctx).toString();
57 | }
58 |
59 | static void wakeupMainThread() {
60 | handler.post(new Runnable() {
61 | @Override public void run() {
62 | scheduleMainFuncs();
63 | }
64 | });
65 | }
66 |
67 | static private native void scheduleMainFuncs();
68 | }
69 |
--------------------------------------------------------------------------------
/app/GioActivity.java:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package org.gioui;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.content.res.Configuration;
8 | import android.view.ViewGroup;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.FrameLayout;
12 |
13 | public final class GioActivity extends Activity {
14 | private GioView view;
15 | public FrameLayout layer;
16 |
17 | @Override public void onCreate(Bundle state) {
18 | super.onCreate(state);
19 |
20 | layer = new FrameLayout(this);
21 | view = new GioView(this);
22 |
23 | view.setLayoutParams(new FrameLayout.LayoutParams(
24 | FrameLayout.LayoutParams.MATCH_PARENT,
25 | FrameLayout.LayoutParams.MATCH_PARENT
26 | ));
27 | view.setFocusable(true);
28 | view.setFocusableInTouchMode(true);
29 |
30 | layer.addView(view);
31 | setContentView(layer);
32 | }
33 |
34 | @Override public void onDestroy() {
35 | view.destroy();
36 | super.onDestroy();
37 | }
38 |
39 | @Override public void onStart() {
40 | super.onStart();
41 | view.start();
42 | }
43 |
44 | @Override public void onStop() {
45 | view.stop();
46 | super.onStop();
47 | }
48 |
49 | @Override public void onConfigurationChanged(Configuration c) {
50 | super.onConfigurationChanged(c);
51 | view.configurationChanged();
52 | }
53 |
54 | @Override public void onLowMemory() {
55 | super.onLowMemory();
56 | GioView.onLowMemory();
57 | }
58 |
59 | @Override public void onBackPressed() {
60 | if (!view.backPressed())
61 | super.onBackPressed();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/datadir.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !android
4 | // +build !android
5 |
6 | package app
7 |
8 | import "os"
9 |
10 | func dataDir() (string, error) {
11 | return os.UserConfigDir()
12 | }
13 |
--------------------------------------------------------------------------------
/app/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package app provides a platform-independent interface to operating system
5 | functionality for running graphical user interfaces.
6 |
7 | See https://gioui.org for instructions to set up and run Gio programs.
8 |
9 | # Windows
10 |
11 | A Window is run by calling its Event method in a loop. The first time a
12 | method on Window is called, a new GUI window is created and shown. On mobile
13 | platforms or when Gio is embedded in another project, Window merely connects
14 | with a previously created GUI window.
15 |
16 | The most important event is [FrameEvent] that prompts an update of the window
17 | contents.
18 |
19 | For example:
20 |
21 | w := new(app.Window)
22 | for {
23 | e := w.Event()
24 | if e, ok := e.(app.FrameEvent); ok {
25 | ops.Reset()
26 | // Add operations to ops.
27 | ...
28 | // Completely replace the window contents and state.
29 | e.Frame(ops)
30 | }
31 | }
32 |
33 | A program must keep receiving events from the event channel until
34 | [DestroyEvent] is received.
35 |
36 | # Main
37 |
38 | The Main function must be called from a program's main function, to hand over
39 | control of the main thread to operating systems that need it.
40 |
41 | Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
42 |
43 | For example, to display a blank but otherwise functional window:
44 |
45 | package main
46 |
47 | import "gioui.org/app"
48 |
49 | func main() {
50 | go func() {
51 | w := app.NewWindow()
52 | for {
53 | w.Event()
54 | }
55 | }()
56 | app.Main()
57 | }
58 |
59 | # Permissions
60 |
61 | The packages under gioui.org/app/permission should be imported
62 | by a Gio program or by one of its dependencies to indicate that specific
63 | operating-system permissions are required. Please see documentation for
64 | package gioui.org/app/permission for more information.
65 | */
66 | package app
67 |
--------------------------------------------------------------------------------
/app/egl_android.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !noopengl
4 |
5 | package app
6 |
7 | /*
8 | #include
9 | #include
10 | */
11 | import "C"
12 |
13 | import (
14 | "unsafe"
15 |
16 | "gioui.org/internal/egl"
17 | )
18 |
19 | type androidContext struct {
20 | win *window
21 | eglSurf egl.NativeWindowType
22 | *egl.Context
23 | }
24 |
25 | func init() {
26 | newAndroidGLESContext = func(w *window) (context, error) {
27 | ctx, err := egl.NewContext(nil)
28 | if err != nil {
29 | return nil, err
30 | }
31 | return &androidContext{win: w, Context: ctx}, nil
32 | }
33 | }
34 |
35 | func (c *androidContext) Release() {
36 | if c.Context != nil {
37 | c.Context.Release()
38 | c.Context = nil
39 | }
40 | }
41 |
42 | func (c *androidContext) Refresh() error {
43 | c.Context.ReleaseSurface()
44 | if err := c.win.setVisual(c.Context.VisualID()); err != nil {
45 | return err
46 | }
47 | win, _, _ := c.win.nativeWindow()
48 | c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
49 | return nil
50 | }
51 |
52 | func (c *androidContext) Lock() error {
53 | // The Android emulator creates a broken surface if it is not
54 | // created on the same thread as the context is made current.
55 | if c.eglSurf != nil {
56 | if err := c.Context.CreateSurface(c.eglSurf); err != nil {
57 | return err
58 | }
59 | c.eglSurf = nil
60 | }
61 | return c.Context.MakeCurrent()
62 | }
63 |
64 | func (c *androidContext) Unlock() {
65 | c.Context.ReleaseCurrent()
66 | }
67 |
--------------------------------------------------------------------------------
/app/egl_wayland.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
4 | // +build linux,!android freebsd
5 | // +build !nowayland
6 | // +build !noopengl
7 |
8 | package app
9 |
10 | import (
11 | "errors"
12 | "unsafe"
13 |
14 | "gioui.org/internal/egl"
15 | )
16 |
17 | /*
18 | #cgo linux pkg-config: egl wayland-egl
19 | #cgo freebsd openbsd LDFLAGS: -lwayland-egl
20 | #cgo CFLAGS: -DEGL_NO_X11
21 |
22 | #include
23 | #include
24 | #include
25 | */
26 | import "C"
27 |
28 | type wlContext struct {
29 | win *window
30 | *egl.Context
31 | eglWin *C.struct_wl_egl_window
32 | }
33 |
34 | func init() {
35 | newWaylandEGLContext = func(w *window) (context, error) {
36 | disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
37 | ctx, err := egl.NewContext(disp)
38 | if err != nil {
39 | return nil, err
40 | }
41 | return &wlContext{Context: ctx, win: w}, nil
42 | }
43 | }
44 |
45 | func (c *wlContext) Release() {
46 | if c.Context != nil {
47 | c.Context.Release()
48 | c.Context = nil
49 | }
50 | if c.eglWin != nil {
51 | C.wl_egl_window_destroy(c.eglWin)
52 | c.eglWin = nil
53 | }
54 | }
55 |
56 | func (c *wlContext) Refresh() error {
57 | c.Context.ReleaseSurface()
58 | if c.eglWin != nil {
59 | C.wl_egl_window_destroy(c.eglWin)
60 | c.eglWin = nil
61 | }
62 | surf, width, height := c.win.surface()
63 | if surf == nil {
64 | return errors.New("wayland: no surface")
65 | }
66 | eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
67 | if eglWin == nil {
68 | return errors.New("wayland: wl_egl_window_create failed")
69 | }
70 | c.eglWin = eglWin
71 | eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
72 | if err := c.Context.CreateSurface(eglSurf); err != nil {
73 | return err
74 | }
75 | if err := c.Context.MakeCurrent(); err != nil {
76 | return err
77 | }
78 | defer c.Context.ReleaseCurrent()
79 | // We're in charge of the frame callbacks, don't let eglSwapBuffers
80 | // wait for callbacks that may never arrive.
81 | c.Context.EnableVSync(false)
82 | return nil
83 | }
84 |
85 | func (c *wlContext) Lock() error {
86 | return c.Context.MakeCurrent()
87 | }
88 |
89 | func (c *wlContext) Unlock() {
90 | c.Context.ReleaseCurrent()
91 | }
92 |
--------------------------------------------------------------------------------
/app/egl_windows.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !noopengl
4 |
5 | package app
6 |
7 | import (
8 | "gioui.org/internal/egl"
9 | )
10 |
11 | type glContext struct {
12 | win *window
13 | *egl.Context
14 | }
15 |
16 | func init() {
17 | drivers = append(drivers, gpuAPI{
18 | priority: 2,
19 | initializer: func(w *window) (context, error) {
20 | disp := egl.NativeDisplayType(w.HDC())
21 | ctx, err := egl.NewContext(disp)
22 | if err != nil {
23 | return nil, err
24 | }
25 | win, _, _ := w.HWND()
26 | eglSurf := egl.NativeWindowType(win)
27 | if err := ctx.CreateSurface(eglSurf); err != nil {
28 | ctx.Release()
29 | return nil, err
30 | }
31 | if err := ctx.MakeCurrent(); err != nil {
32 | ctx.Release()
33 | return nil, err
34 | }
35 | defer ctx.ReleaseCurrent()
36 | ctx.EnableVSync(true)
37 | return &glContext{win: w, Context: ctx}, nil
38 | },
39 | })
40 | }
41 |
42 | func (c *glContext) Release() {
43 | if c.Context != nil {
44 | c.Context.Release()
45 | c.Context = nil
46 | }
47 | }
48 |
49 | func (c *glContext) Refresh() error {
50 | return nil
51 | }
52 |
53 | func (c *glContext) Lock() error {
54 | return c.Context.MakeCurrent()
55 | }
56 |
57 | func (c *glContext) Unlock() {
58 | c.Context.ReleaseCurrent()
59 | }
60 |
--------------------------------------------------------------------------------
/app/egl_x11.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
4 | // +build linux,!android freebsd openbsd
5 | // +build !nox11
6 | // +build !noopengl
7 |
8 | package app
9 |
10 | import (
11 | "unsafe"
12 |
13 | "gioui.org/internal/egl"
14 | )
15 |
16 | type x11Context struct {
17 | win *x11Window
18 | *egl.Context
19 | }
20 |
21 | func init() {
22 | newX11EGLContext = func(w *x11Window) (context, error) {
23 | disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
24 | ctx, err := egl.NewContext(disp)
25 | if err != nil {
26 | return nil, err
27 | }
28 | win, _, _ := w.window()
29 | eglSurf := egl.NativeWindowType(uintptr(win))
30 | if err := ctx.CreateSurface(eglSurf); err != nil {
31 | ctx.Release()
32 | return nil, err
33 | }
34 | if err := ctx.MakeCurrent(); err != nil {
35 | ctx.Release()
36 | return nil, err
37 | }
38 | defer ctx.ReleaseCurrent()
39 | ctx.EnableVSync(true)
40 | return &x11Context{win: w, Context: ctx}, nil
41 | }
42 | }
43 |
44 | func (c *x11Context) Release() {
45 | if c.Context != nil {
46 | c.Context.Release()
47 | c.Context = nil
48 | }
49 | }
50 |
51 | func (c *x11Context) Refresh() error {
52 | return nil
53 | }
54 |
55 | func (c *x11Context) Lock() error {
56 | return c.Context.MakeCurrent()
57 | }
58 |
59 | func (c *x11Context) Unlock() {
60 | c.Context.ReleaseCurrent()
61 | }
62 |
--------------------------------------------------------------------------------
/app/framework_ios.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | #include
4 |
5 | @interface GioViewController : UIViewController
6 | @end
7 |
--------------------------------------------------------------------------------
/app/gl_ios.m:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // +build darwin,ios,nometal
4 |
5 | @import UIKit;
6 | @import OpenGLES;
7 |
8 | #include "_cgo_export.h"
9 |
10 | Class gio_layerClass(void) {
11 | return [CAEAGLLayer class];
12 | }
13 |
14 | int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
15 | EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
16 | CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
17 | return (int)[ctx renderbufferStorage:buffer fromDrawable:layer];
18 | }
19 |
20 | int gio_presentRenderbuffer(CFTypeRef ctxRef, GLenum buffer) {
21 | EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
22 | return (int)[ctx presentRenderbuffer:buffer];
23 | }
24 |
25 | int gio_makeCurrent(CFTypeRef ctxRef) {
26 | EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
27 | return (int)[EAGLContext setCurrentContext:ctx];
28 | }
29 |
30 | CFTypeRef gio_createContext(void) {
31 | EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
32 | if (ctx == nil) {
33 | return nil;
34 | }
35 | return CFBridgingRetain(ctx);
36 | }
37 |
38 | CFTypeRef gio_createGLLayer(void) {
39 | CAEAGLLayer *layer = [[CAEAGLLayer layer] init];
40 | if (layer == nil) {
41 | return nil;
42 | }
43 | layer.drawableProperties = @{kEAGLDrawablePropertyColorFormat: kEAGLColorFormatSRGBA8};
44 | layer.opaque = YES;
45 | layer.anchorPoint = CGPointMake(0, 0);
46 | return CFBridgingRetain(layer);
47 | }
48 |
--------------------------------------------------------------------------------
/app/gl_js.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package app
4 |
5 | import (
6 | "errors"
7 | "syscall/js"
8 |
9 | "gioui.org/gpu"
10 | "gioui.org/internal/gl"
11 | )
12 |
13 | type glContext struct {
14 | ctx js.Value
15 | cnv js.Value
16 | w *window
17 | }
18 |
19 | func newContext(w *window) (*glContext, error) {
20 | args := map[string]interface{}{
21 | // Enable low latency rendering.
22 | // See https://developers.google.com/web/updates/2019/05/desynchronized.
23 | "desynchronized": true,
24 | "preserveDrawingBuffer": true,
25 | }
26 | ctx := w.cnv.Call("getContext", "webgl2", args)
27 | if ctx.IsNull() {
28 | ctx = w.cnv.Call("getContext", "webgl", args)
29 | }
30 | if ctx.IsNull() {
31 | return nil, errors.New("app: webgl is not supported")
32 | }
33 | c := &glContext{
34 | ctx: ctx,
35 | cnv: w.cnv,
36 | w: w,
37 | }
38 | return c, nil
39 | }
40 |
41 | func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
42 | if c.w.contextStatus != contextStatusOkay {
43 | return nil, gpu.ErrDeviceLost
44 | }
45 | return gpu.OpenGLRenderTarget{}, nil
46 | }
47 |
48 | func (c *glContext) API() gpu.API {
49 | return gpu.OpenGL{Context: gl.Context(c.ctx)}
50 | }
51 |
52 | func (c *glContext) Release() {
53 | }
54 |
55 | func (c *glContext) Present() error {
56 | return nil
57 | }
58 |
59 | func (c *glContext) Lock() error {
60 | return nil
61 | }
62 |
63 | func (c *glContext) Unlock() {}
64 |
65 | func (c *glContext) Refresh() error {
66 | switch c.w.contextStatus {
67 | case contextStatusLost:
68 | return errOutOfDate
69 | case contextStatusRestored:
70 | c.w.contextStatus = contextStatusOkay
71 | return gpu.ErrDeviceLost
72 | default:
73 | return nil
74 | }
75 | }
76 |
77 | func (w *window) NewContext() (context, error) {
78 | return newContext(w)
79 | }
80 |
--------------------------------------------------------------------------------
/app/gl_macos.m:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // +build darwin,!ios,nometal
4 |
5 | #import
6 | #include
7 | #include
8 | #include "_cgo_export.h"
9 |
10 | CALayer *gio_layerFactory(BOOL presentWithTrans) {
11 | @autoreleasepool {
12 | return [CALayer layer];
13 | }
14 | }
15 |
16 | CFTypeRef gio_createGLContext(void) {
17 | @autoreleasepool {
18 | NSOpenGLPixelFormatAttribute attr[] = {
19 | NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
20 | NSOpenGLPFAColorSize, 24,
21 | NSOpenGLPFAAccelerated,
22 | // Opt-in to automatic GPU switching. CGL-only property.
23 | kCGLPFASupportsAutomaticGraphicsSwitching,
24 | NSOpenGLPFAAllowOfflineRenderers,
25 | 0
26 | };
27 | NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
28 |
29 | NSOpenGLContext *ctx = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext: nil];
30 | return CFBridgingRetain(ctx);
31 | }
32 | }
33 |
34 | void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) {
35 | NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
36 | NSView *view = (__bridge NSView *)viewRef;
37 | [view setWantsBestResolutionOpenGLSurface:YES];
38 | [ctx setView:view];
39 | }
40 |
41 | void gio_clearCurrentContext(void) {
42 | @autoreleasepool {
43 | [NSOpenGLContext clearCurrentContext];
44 | }
45 | }
46 |
47 | void gio_updateContext(CFTypeRef ctxRef) {
48 | @autoreleasepool {
49 | NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
50 | [ctx update];
51 | }
52 | }
53 |
54 | void gio_makeCurrentContext(CFTypeRef ctxRef) {
55 | @autoreleasepool {
56 | NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
57 | [ctx makeCurrentContext];
58 | }
59 | }
60 |
61 | void gio_lockContext(CFTypeRef ctxRef) {
62 | @autoreleasepool {
63 | NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
64 | CGLLockContext([ctx CGLContextObj]);
65 | }
66 | }
67 |
68 | void gio_unlockContext(CFTypeRef ctxRef) {
69 | @autoreleasepool {
70 | NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
71 | CGLUnlockContext([ctx CGLContextObj]);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/log_android.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package app
4 |
5 | /*
6 | #cgo LDFLAGS: -llog
7 |
8 | #include
9 | #include
10 | */
11 | import "C"
12 |
13 | import (
14 | "bufio"
15 | "log"
16 | "os"
17 | "runtime"
18 | "syscall"
19 | "unsafe"
20 | )
21 |
22 | // 1024 is the truncation limit from android/log.h, plus a \n.
23 | const logLineLimit = 1024
24 |
25 | var logTag = C.CString(ID)
26 |
27 | func init() {
28 | // Android's logcat already includes timestamps.
29 | log.SetFlags(log.Flags() &^ log.LstdFlags)
30 | log.SetOutput(new(androidLogWriter))
31 |
32 | // Redirect stdout and stderr to the Android logger.
33 | logFd(os.Stdout.Fd())
34 | logFd(os.Stderr.Fd())
35 | }
36 |
37 | type androidLogWriter struct {
38 | // buf has room for the maximum log line, plus a terminating '\0'.
39 | buf [logLineLimit + 1]byte
40 | }
41 |
42 | func (w *androidLogWriter) Write(data []byte) (int, error) {
43 | n := 0
44 | for len(data) > 0 {
45 | msg := data
46 | // Truncate the buffer, leaving space for the '\0'.
47 | if max := len(w.buf) - 1; len(msg) > max {
48 | msg = msg[:max]
49 | }
50 | buf := w.buf[:len(msg)+1]
51 | copy(buf, msg)
52 | // Terminating '\0'.
53 | buf[len(msg)] = 0
54 | C.__android_log_write(C.ANDROID_LOG_INFO, logTag, (*C.char)(unsafe.Pointer(&buf[0])))
55 | n += len(msg)
56 | data = data[len(msg):]
57 | }
58 | return n, nil
59 | }
60 |
61 | func logFd(fd uintptr) {
62 | r, w, err := os.Pipe()
63 | if err != nil {
64 | panic(err)
65 | }
66 | if err := syscall.Dup3(int(w.Fd()), int(fd), syscall.O_CLOEXEC); err != nil {
67 | panic(err)
68 | }
69 | go func() {
70 | lineBuf := bufio.NewReaderSize(r, logLineLimit)
71 | // The buffer to pass to C, including the terminating '\0'.
72 | buf := make([]byte, lineBuf.Size()+1)
73 | cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
74 | for {
75 | line, _, err := lineBuf.ReadLine()
76 | if err != nil {
77 | break
78 | }
79 | copy(buf, line)
80 | buf[len(line)] = 0
81 | C.__android_log_write(C.ANDROID_LOG_INFO, logTag, cbuf)
82 | }
83 | // The garbage collector doesn't know that w's fd was dup'ed.
84 | // Avoid finalizing w, and thereby avoid its finalizer closing its fd.
85 | runtime.KeepAlive(w)
86 | }()
87 | }
88 |
--------------------------------------------------------------------------------
/app/log_ios.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build darwin && ios
4 | // +build darwin,ios
5 |
6 | package app
7 |
8 | /*
9 | #cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
10 |
11 | @import Foundation;
12 |
13 | static void nslog(char *str) {
14 | NSLog(@"%@", @(str));
15 | }
16 | */
17 | import "C"
18 |
19 | import (
20 | "bufio"
21 | "io"
22 | "log"
23 | "unsafe"
24 |
25 | _ "gioui.org/internal/cocoainit"
26 | )
27 |
28 | func init() {
29 | // macOS Console already includes timestamps.
30 | log.SetFlags(log.Flags() &^ log.LstdFlags)
31 | log.SetOutput(newNSLogWriter())
32 | }
33 |
34 | func newNSLogWriter() io.Writer {
35 | r, w := io.Pipe()
36 | go func() {
37 | // 1024 is an arbitrary truncation limit, taken from Android's
38 | // log buffer size.
39 | lineBuf := bufio.NewReaderSize(r, 1024)
40 | // The buffer to pass to C, including the terminating '\0'.
41 | buf := make([]byte, lineBuf.Size()+1)
42 | cbuf := (*C.char)(unsafe.Pointer(&buf[0]))
43 | for {
44 | line, _, err := lineBuf.ReadLine()
45 | if err != nil {
46 | break
47 | }
48 | copy(buf, line)
49 | buf[len(line)] = 0
50 | C.nslog(cbuf)
51 | }
52 | }()
53 | return w
54 | }
55 |
--------------------------------------------------------------------------------
/app/log_windows.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package app
4 |
5 | import (
6 | "log"
7 | "unsafe"
8 |
9 | syscall "golang.org/x/sys/windows"
10 | )
11 |
12 | type logger struct{}
13 |
14 | var (
15 | kernel32 = syscall.NewLazySystemDLL("kernel32")
16 | outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
17 | debugView *logger
18 | )
19 |
20 | func init() {
21 | // Windows DebugView already includes timestamps.
22 | if syscall.Stderr == 0 {
23 | log.SetFlags(log.Flags() &^ log.LstdFlags)
24 | log.SetOutput(debugView)
25 | }
26 | }
27 |
28 | func (l *logger) Write(buf []byte) (int, error) {
29 | p, err := syscall.UTF16PtrFromString(string(buf))
30 | if err != nil {
31 | return 0, err
32 | }
33 | outputDebugStringW.Call(uintptr(unsafe.Pointer(p)))
34 | return len(buf), nil
35 | }
36 |
--------------------------------------------------------------------------------
/app/metal_ios.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !nometal
4 | // +build !nometal
5 |
6 | package app
7 |
8 | /*
9 | #cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
10 |
11 | @import UIKit;
12 |
13 | @import QuartzCore.CAMetalLayer;
14 |
15 | #include
16 |
17 | Class gio_layerClass(void) {
18 | return [CAMetalLayer class];
19 | }
20 |
21 | static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
22 | @autoreleasepool {
23 | UIView *view = (__bridge UIView *)viewRef;
24 | CAMetalLayer *l = (CAMetalLayer *)view.layer;
25 | l.needsDisplayOnBoundsChange = YES;
26 | l.presentsWithTransaction = YES;
27 | return CFBridgingRetain(l);
28 | }
29 | }
30 |
31 | static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
32 | @autoreleasepool {
33 | UIView *view = (__bridge UIView *)viewRef;
34 | CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
35 | layer.contentsScale = view.contentScaleFactor;
36 | CGSize size = layer.bounds.size;
37 | size.width *= layer.contentsScale;
38 | size.height *= layer.contentsScale;
39 | layer.drawableSize = size;
40 | }
41 | }
42 | */
43 | import "C"
44 |
45 | func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
46 | return C.getMetalLayer(view)
47 | }
48 |
49 | func resizeDrawable(view, layer C.CFTypeRef) {
50 | C.resizeDrawable(view, layer)
51 | }
52 |
--------------------------------------------------------------------------------
/app/metal_macos.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build darwin && !ios && !nometal
4 | // +build darwin,!ios,!nometal
5 |
6 | package app
7 |
8 | /*
9 | #cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
10 |
11 | #import
12 | #import
13 | #include
14 |
15 | CALayer *gio_layerFactory(BOOL presentWithTrans) {
16 | @autoreleasepool {
17 | CAMetalLayer *l = [CAMetalLayer layer];
18 | l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
19 | l.needsDisplayOnBoundsChange = YES;
20 | l.presentsWithTransaction = presentWithTrans;
21 | return l;
22 | }
23 | }
24 |
25 | static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
26 | @autoreleasepool {
27 | NSView *view = (__bridge NSView *)viewRef;
28 | return CFBridgingRetain(view.layer);
29 | }
30 | }
31 |
32 | static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
33 | @autoreleasepool {
34 | NSView *view = (__bridge NSView *)viewRef;
35 | CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
36 | CGSize size = layer.bounds.size;
37 | size.width *= layer.contentsScale;
38 | size.height *= layer.contentsScale;
39 | layer.drawableSize = size;
40 | }
41 | }
42 | */
43 | import "C"
44 |
45 | func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
46 | return C.getMetalLayer(view)
47 | }
48 |
49 | func resizeDrawable(view, layer C.CFTypeRef) {
50 | C.resizeDrawable(view, layer)
51 | }
52 |
--------------------------------------------------------------------------------
/app/os_darwin.m:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | #import
4 |
5 | #include "_cgo_export.h"
6 |
7 | void gio_wakeupMainThread(void) {
8 | dispatch_async(dispatch_get_main_queue(), ^{
9 | gio_dispatchMainFuncs();
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/app/permission/bluetooth/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package bluetooth implements permissions to access Bluetooth and Bluetooth
5 | Low Energy hardware, including the ability to discover and pair devices.
6 |
7 | # Android
8 |
9 | The following entries will be added to AndroidManifest.xml:
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Note that ACCESS_FINE_LOCATION is required on Android before the Bluetooth
18 | device may be used.
19 | See https://developer.android.com/guide/topics/connectivity/bluetooth.
20 |
21 | ACCESS_FINE_LOCATION is a "dangerous" permission. See documentation for
22 | package gioui.org/app/permission for more information.
23 | */
24 | package bluetooth
25 |
--------------------------------------------------------------------------------
/app/permission/camera/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package camera implements permissions to access camera hardware.
5 |
6 | # Android
7 |
8 | The following entries will be added to AndroidManifest.xml:
9 |
10 |
11 |
12 |
13 | CAMERA is a "dangerous" permission. See documentation for package
14 | gioui.org/app/permission for more information.
15 | */
16 | package camera
17 |
--------------------------------------------------------------------------------
/app/permission/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package permission includes sub-packages that should be imported
5 | by a Gio program or by one of its dependencies to indicate that specific
6 | operating-system permissions are required. For example, if a Gio
7 | program requires access to a device's Bluetooth interface, it
8 | should import "gioui.org/app/permission/bluetooth" as follows:
9 |
10 | package main
11 |
12 | import (
13 | "gioui.org/app"
14 | _ "gioui.org/app/permission/bluetooth"
15 | )
16 |
17 | func main() {
18 | ...
19 | }
20 |
21 | Since there are no exported identifiers in the app/permission/bluetooth
22 | package, the import uses the anonymous identifier (_) as the imported
23 | package name.
24 |
25 | As a special case, the gogio tool detects when a program directly or
26 | indirectly depends on the "net" package from the Go standard library as an
27 | indication that the program requires network access permissions. If a program
28 | requires network permissions but does not directly or indirectly import
29 | "net", it will be necessary to add the following code somewhere in the
30 | program's source code:
31 |
32 | import (
33 | ...
34 | _ "net"
35 | )
36 |
37 | # Android -- Dangerous Permissions
38 |
39 | Certain permissions on Android are marked with a protection level of
40 | "dangerous". This means that, in addition to including the relevant
41 | Gio permission packages, your app will need to prompt the user
42 | specifically to request access. To access the Android Activity
43 | required for prompting, use app.ViewEvent (only available on Android).
44 | app.ViewEvent exposes the underlying Android View, on which the
45 | getContext method returns the Activity.
46 |
47 | For more information on dangerous permissions, see:
48 | https://developer.android.com/guide/topics/permissions/overview#dangerous_permissions
49 | */
50 | package permission
51 |
--------------------------------------------------------------------------------
/app/permission/networkstate/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package networkstate implements permissions to access network connectivity information.
5 |
6 | # Android
7 |
8 | The following entries will be added to AndroidManifest.xml:
9 |
10 |
11 | */
12 | package networkstate
13 |
--------------------------------------------------------------------------------
/app/permission/storage/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package storage implements read and write storage permissions
5 | on mobile devices.
6 |
7 | # Android
8 |
9 | The following entries will be added to AndroidManifest.xml:
10 |
11 |
12 |
13 |
14 | READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are "dangerous" permissions.
15 | See documentation for package gioui.org/app/permission for more information.
16 | */
17 | package storage
18 |
--------------------------------------------------------------------------------
/app/permission/wakelock/wakelock.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package wakelock implements permission to acquire locks that keep the system
5 | from suspending.
6 |
7 | # Android
8 |
9 | The following entries will be added to AndroidManifest.xml:
10 |
11 |
12 | */
13 | package wakelock
14 |
--------------------------------------------------------------------------------
/app/runmain.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build android || (darwin && ios)
4 | // +build android darwin,ios
5 |
6 | package app
7 |
8 | // Android only supports non-Java programs as c-shared libraries.
9 | // Unfortunately, Go does not run a program's main function in
10 | // library mode. To make Gio programs simpler and uniform, we'll
11 | // link to the main function here and call it from Java.
12 |
13 | import (
14 | "sync"
15 | _ "unsafe" // for go:linkname
16 | )
17 |
18 | //go:linkname mainMain main.main
19 | func mainMain()
20 |
21 | var runMainOnce sync.Once
22 |
23 | func runMain() {
24 | runMainOnce.Do(func() {
25 | // Indirect call, since the linker does not know the address of main when
26 | // laying down this package.
27 | fn := mainMain
28 | fn()
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/app/system.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package app
4 |
5 | // DestroyEvent is the last event sent through
6 | // a window event channel.
7 | type DestroyEvent struct {
8 | // Err is nil for normal window closures. If a
9 | // window is prematurely closed, Err is the cause.
10 | Err error
11 | }
12 |
13 | func (DestroyEvent) ImplementsEvent() {}
14 |
--------------------------------------------------------------------------------
/app/vulkan_android.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !novulkan
4 | // +build !novulkan
5 |
6 | package app
7 |
8 | import (
9 | "unsafe"
10 |
11 | "gioui.org/gpu"
12 | "gioui.org/internal/vk"
13 | )
14 |
15 | type wlVkContext struct {
16 | win *window
17 | inst vk.Instance
18 | surf vk.Surface
19 | ctx *vkContext
20 | }
21 |
22 | func init() {
23 | newAndroidVulkanContext = func(w *window) (context, error) {
24 | inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_android_surface")
25 | if err != nil {
26 | return nil, err
27 | }
28 | window, _, _ := w.nativeWindow()
29 | surf, err := vk.CreateAndroidSurface(inst, unsafe.Pointer(window))
30 | if err != nil {
31 | vk.DestroyInstance(inst)
32 | return nil, err
33 | }
34 | ctx, err := newVulkanContext(inst, surf)
35 | if err != nil {
36 | vk.DestroySurface(inst, surf)
37 | vk.DestroyInstance(inst)
38 | return nil, err
39 | }
40 | c := &wlVkContext{
41 | win: w,
42 | inst: inst,
43 | surf: surf,
44 | ctx: ctx,
45 | }
46 | return c, nil
47 | }
48 | }
49 |
50 | func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
51 | return c.ctx.RenderTarget()
52 | }
53 |
54 | func (c *wlVkContext) API() gpu.API {
55 | return c.ctx.api()
56 | }
57 |
58 | func (c *wlVkContext) Release() {
59 | c.ctx.release()
60 | if c.surf != 0 {
61 | vk.DestroySurface(c.inst, c.surf)
62 | }
63 | vk.DestroyInstance(c.inst)
64 | *c = wlVkContext{}
65 | }
66 |
67 | func (c *wlVkContext) Present() error {
68 | return c.ctx.present()
69 | }
70 |
71 | func (c *wlVkContext) Lock() error {
72 | return nil
73 | }
74 |
75 | func (c *wlVkContext) Unlock() {}
76 |
77 | func (c *wlVkContext) Refresh() error {
78 | win, w, h := c.win.nativeWindow()
79 | if c.surf != 0 {
80 | c.ctx.destroySwapchain()
81 | vk.DestroySurface(c.inst, c.surf)
82 | c.surf = 0
83 | }
84 | surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win))
85 | if err != nil {
86 | return err
87 | }
88 | c.surf = surf
89 | return c.ctx.refresh(c.surf, w, h)
90 | }
91 |
--------------------------------------------------------------------------------
/app/vulkan_wayland.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build ((linux && !android) || freebsd) && !nowayland && !novulkan
4 | // +build linux,!android freebsd
5 | // +build !nowayland
6 | // +build !novulkan
7 |
8 | package app
9 |
10 | import (
11 | "unsafe"
12 |
13 | "gioui.org/gpu"
14 | "gioui.org/internal/vk"
15 | )
16 |
17 | type wlVkContext struct {
18 | win *window
19 | inst vk.Instance
20 | surf vk.Surface
21 | ctx *vkContext
22 | }
23 |
24 | func init() {
25 | newWaylandVulkanContext = func(w *window) (context, error) {
26 | inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_wayland_surface")
27 | if err != nil {
28 | return nil, err
29 | }
30 | disp := w.display()
31 | wlSurf, _, _ := w.surface()
32 | surf, err := vk.CreateWaylandSurface(inst, unsafe.Pointer(disp), unsafe.Pointer(wlSurf))
33 | if err != nil {
34 | vk.DestroyInstance(inst)
35 | return nil, err
36 | }
37 | ctx, err := newVulkanContext(inst, surf)
38 | if err != nil {
39 | vk.DestroySurface(inst, surf)
40 | vk.DestroyInstance(inst)
41 | return nil, err
42 | }
43 | c := &wlVkContext{
44 | win: w,
45 | inst: inst,
46 | surf: surf,
47 | ctx: ctx,
48 | }
49 | return c, nil
50 | }
51 | }
52 |
53 | func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) {
54 | return c.ctx.RenderTarget()
55 | }
56 |
57 | func (c *wlVkContext) API() gpu.API {
58 | return c.ctx.api()
59 | }
60 |
61 | func (c *wlVkContext) Release() {
62 | c.ctx.release()
63 | vk.DestroySurface(c.inst, c.surf)
64 | vk.DestroyInstance(c.inst)
65 | *c = wlVkContext{}
66 | }
67 |
68 | func (c *wlVkContext) Present() error {
69 | return c.ctx.present()
70 | }
71 |
72 | func (c *wlVkContext) Lock() error {
73 | return nil
74 | }
75 |
76 | func (c *wlVkContext) Unlock() {}
77 |
78 | func (c *wlVkContext) Refresh() error {
79 | _, w, h := c.win.surface()
80 | return c.ctx.refresh(c.surf, w, h)
81 | }
82 |
--------------------------------------------------------------------------------
/app/vulkan_x11.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build ((linux && !android) || freebsd) && !nox11 && !novulkan
4 | // +build linux,!android freebsd
5 | // +build !nox11
6 | // +build !novulkan
7 |
8 | package app
9 |
10 | import (
11 | "unsafe"
12 |
13 | "gioui.org/gpu"
14 | "gioui.org/internal/vk"
15 | )
16 |
17 | type x11VkContext struct {
18 | win *x11Window
19 | inst vk.Instance
20 | surf vk.Surface
21 | ctx *vkContext
22 | }
23 |
24 | func init() {
25 | newX11VulkanContext = func(w *x11Window) (context, error) {
26 | inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_xlib_surface")
27 | if err != nil {
28 | return nil, err
29 | }
30 | disp := w.display()
31 | window, _, _ := w.window()
32 | surf, err := vk.CreateXlibSurface(inst, unsafe.Pointer(disp), uintptr(window))
33 | if err != nil {
34 | vk.DestroyInstance(inst)
35 | return nil, err
36 | }
37 | ctx, err := newVulkanContext(inst, surf)
38 | if err != nil {
39 | vk.DestroySurface(inst, surf)
40 | vk.DestroyInstance(inst)
41 | return nil, err
42 | }
43 | c := &x11VkContext{
44 | win: w,
45 | inst: inst,
46 | surf: surf,
47 | ctx: ctx,
48 | }
49 | return c, nil
50 | }
51 | }
52 |
53 | func (c *x11VkContext) RenderTarget() (gpu.RenderTarget, error) {
54 | return c.ctx.RenderTarget()
55 | }
56 |
57 | func (c *x11VkContext) API() gpu.API {
58 | return c.ctx.api()
59 | }
60 |
61 | func (c *x11VkContext) Release() {
62 | c.ctx.release()
63 | vk.DestroySurface(c.inst, c.surf)
64 | vk.DestroyInstance(c.inst)
65 | *c = x11VkContext{}
66 | }
67 |
68 | func (c *x11VkContext) Present() error {
69 | return c.ctx.present()
70 | }
71 |
72 | func (c *x11VkContext) Lock() error {
73 | return nil
74 | }
75 |
76 | func (c *x11VkContext) Unlock() {}
77 |
78 | func (c *x11VkContext) Refresh() error {
79 | _, w, h := c.win.window()
80 | return c.ctx.refresh(c.surf, w, h)
81 | }
82 |
--------------------------------------------------------------------------------
/app/wayland_xdg_decoration.c:
--------------------------------------------------------------------------------
1 | //go:build ((linux && !android) || freebsd) && !nowayland
2 | // +build linux,!android freebsd
3 | // +build !nowayland
4 |
5 | /* Generated by wayland-scanner 1.19.0 */
6 |
7 | /*
8 | * Copyright © 2018 Simon Ser
9 | *
10 | * Permission is hereby granted, free of charge, to any person obtaining a
11 | * copy of this software and associated documentation files (the "Software"),
12 | * to deal in the Software without restriction, including without limitation
13 | * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | * and/or sell copies of the Software, and to permit persons to whom the
15 | * Software is furnished to do so, subject to the following conditions:
16 | *
17 | * The above copyright notice and this permission notice (including the next
18 | * paragraph) shall be included in all copies or substantial portions of the
19 | * Software.
20 | *
21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 | * DEALINGS IN THE SOFTWARE.
28 | */
29 |
30 | #include
31 | #include
32 | #include "wayland-util.h"
33 |
34 | #ifndef __has_attribute
35 | # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
36 | #endif
37 |
38 | #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
39 | #define WL_PRIVATE __attribute__ ((visibility("hidden")))
40 | #else
41 | #define WL_PRIVATE
42 | #endif
43 |
44 | extern const struct wl_interface xdg_toplevel_interface;
45 | extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
46 |
47 | static const struct wl_interface *xdg_decoration_unstable_v1_types[] = {
48 | NULL,
49 | &zxdg_toplevel_decoration_v1_interface,
50 | &xdg_toplevel_interface,
51 | };
52 |
53 | static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
54 | { "destroy", "", xdg_decoration_unstable_v1_types + 0 },
55 | { "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 },
56 | };
57 |
58 | WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
59 | "zxdg_decoration_manager_v1", 1,
60 | 2, zxdg_decoration_manager_v1_requests,
61 | 0, NULL,
62 | };
63 |
64 | static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
65 | { "destroy", "", xdg_decoration_unstable_v1_types + 0 },
66 | { "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
67 | { "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
68 | };
69 |
70 | static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
71 | { "configure", "u", xdg_decoration_unstable_v1_types + 0 },
72 | };
73 |
74 | WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
75 | "zxdg_toplevel_decoration_v1", 1,
76 | 3, zxdg_toplevel_decoration_v1_requests,
77 | 1, zxdg_toplevel_decoration_v1_events,
78 | };
79 |
80 |
--------------------------------------------------------------------------------
/f32/f32.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package f32 is a float32 implementation of package image's
5 | Point and affine transformations.
6 |
7 | The coordinate space has the origin in the top left
8 | corner with the axes extending right and down.
9 | */
10 | package f32
11 |
12 | import (
13 | "image"
14 | "math"
15 | "strconv"
16 | )
17 |
18 | // A Point is a two dimensional point.
19 | type Point struct {
20 | X, Y float32
21 | }
22 |
23 | // String return a string representation of p.
24 | func (p Point) String() string {
25 | return "(" + strconv.FormatFloat(float64(p.X), 'f', -1, 32) +
26 | "," + strconv.FormatFloat(float64(p.Y), 'f', -1, 32) + ")"
27 | }
28 |
29 | // Pt is shorthand for Point{X: x, Y: y}.
30 | func Pt(x, y float32) Point {
31 | return Point{X: x, Y: y}
32 | }
33 |
34 | // Add return the point p+p2.
35 | func (p Point) Add(p2 Point) Point {
36 | return Point{X: p.X + p2.X, Y: p.Y + p2.Y}
37 | }
38 |
39 | // Sub returns the vector p-p2.
40 | func (p Point) Sub(p2 Point) Point {
41 | return Point{X: p.X - p2.X, Y: p.Y - p2.Y}
42 | }
43 |
44 | // Mul returns p scaled by s.
45 | func (p Point) Mul(s float32) Point {
46 | return Point{X: p.X * s, Y: p.Y * s}
47 | }
48 |
49 | // Div returns the vector p/s.
50 | func (p Point) Div(s float32) Point {
51 | return Point{X: p.X / s, Y: p.Y / s}
52 | }
53 |
54 | // Round returns the integer point closest to p.
55 | func (p Point) Round() image.Point {
56 | return image.Point{
57 | X: int(math.Round(float64(p.X))),
58 | Y: int(math.Round(float64(p.Y))),
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1747953325,
6 | "narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=",
7 | "owner": "NixOS",
8 | "repo": "nixpkgs",
9 | "rev": "55d1f923c480dadce40f5231feb472e81b0bab48",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "NixOS",
14 | "ref": "nixos-25.05",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs",
22 | "utils": "utils"
23 | }
24 | },
25 | "systems": {
26 | "locked": {
27 | "lastModified": 1681028828,
28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
29 | "owner": "nix-systems",
30 | "repo": "default",
31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
32 | "type": "github"
33 | },
34 | "original": {
35 | "owner": "nix-systems",
36 | "repo": "default",
37 | "type": "github"
38 | }
39 | },
40 | "utils": {
41 | "inputs": {
42 | "systems": "systems"
43 | },
44 | "locked": {
45 | "lastModified": 1731533236,
46 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
47 | "owner": "numtide",
48 | "repo": "flake-utils",
49 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
50 | "type": "github"
51 | },
52 | "original": {
53 | "owner": "numtide",
54 | "repo": "flake-utils",
55 | "type": "github"
56 | }
57 | }
58 | },
59 | "root": "root",
60 | "version": 7
61 | }
62 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Unlicense OR MIT
2 | {
3 | description = "Gio build environment";
4 |
5 | inputs = {
6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
7 | utils.url = "github:numtide/flake-utils";
8 | };
9 |
10 | outputs = { self, nixpkgs, utils }:
11 | utils.lib.eachDefaultSystem (system:
12 | let
13 | pkgs = import nixpkgs {
14 | inherit system;
15 |
16 | # allow unfree Android packages.
17 | config.allowUnfree = true;
18 | # accept the Android SDK license.
19 | config.android_sdk.accept_license = true;
20 | };
21 | in {
22 | devShells = let
23 | android-sdk = let
24 | androidComposition = pkgs.androidenv.composeAndroidPackages {
25 | platformVersions = [ "latest" ];
26 | abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
27 | # Omit the deprecated tools package.
28 | toolsVersion = null;
29 | includeNDK = true;
30 | };
31 | in androidComposition.androidsdk;
32 | in {
33 | default = with pkgs;
34 | mkShell (rec {
35 | ANDROID_HOME = "${android-sdk}/libexec/android-sdk";
36 | packages = [ android-sdk jdk clang ]
37 | ++ (if stdenv.isLinux then [
38 | vulkan-headers
39 | libxkbcommon
40 | wayland
41 | xorg.libX11
42 | xorg.libXcursor
43 | xorg.libXfixes
44 | libGL
45 | pkg-config
46 | ] else
47 | [ ]);
48 | } // (if stdenv.isLinux then {
49 | LD_LIBRARY_PATH = "${vulkan-loader}/lib";
50 | } else
51 | { }));
52 | };
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/font/gofont/gofont.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package gofont exports the Go fonts as a text.Collection.
4 | //
5 | // See https://blog.golang.org/go-fonts for a description of the
6 | // fonts, and the golang.org/x/image/font/gofont packages for the
7 | // font data.
8 | package gofont
9 |
10 | import (
11 | "fmt"
12 | "sync"
13 |
14 | "golang.org/x/image/font/gofont/gobold"
15 | "golang.org/x/image/font/gofont/gobolditalic"
16 | "golang.org/x/image/font/gofont/goitalic"
17 | "golang.org/x/image/font/gofont/gomedium"
18 | "golang.org/x/image/font/gofont/gomediumitalic"
19 | "golang.org/x/image/font/gofont/gomono"
20 | "golang.org/x/image/font/gofont/gomonobold"
21 | "golang.org/x/image/font/gofont/gomonobolditalic"
22 | "golang.org/x/image/font/gofont/gomonoitalic"
23 | "golang.org/x/image/font/gofont/goregular"
24 | "golang.org/x/image/font/gofont/gosmallcaps"
25 | "golang.org/x/image/font/gofont/gosmallcapsitalic"
26 |
27 | "gioui.org/font"
28 | "gioui.org/font/opentype"
29 | )
30 |
31 | var (
32 | regOnce sync.Once
33 | reg []font.FontFace
34 | once sync.Once
35 | collection []font.FontFace
36 | )
37 |
38 | func loadRegular() {
39 | regOnce.Do(func() {
40 | faces, err := opentype.ParseCollection(goregular.TTF)
41 | if err != nil {
42 | panic(fmt.Errorf("failed to parse font: %v", err))
43 | }
44 | reg = faces
45 | collection = append(collection, reg[0])
46 | })
47 | }
48 |
49 | // Regular returns a collection of only the Go regular font face.
50 | func Regular() []font.FontFace {
51 | loadRegular()
52 | return reg
53 | }
54 |
55 | // Regular returns a collection of all available Go font faces.
56 | func Collection() []font.FontFace {
57 | loadRegular()
58 | once.Do(func() {
59 | register(goitalic.TTF)
60 | register(gobold.TTF)
61 | register(gobolditalic.TTF)
62 | register(gomedium.TTF)
63 | register(gomediumitalic.TTF)
64 | register(gomono.TTF)
65 | register(gomonobold.TTF)
66 | register(gomonobolditalic.TTF)
67 | register(gomonoitalic.TTF)
68 | register(gosmallcaps.TTF)
69 | register(gosmallcapsitalic.TTF)
70 | // Ensure that any outside appends will not reuse the backing store.
71 | n := len(collection)
72 | collection = collection[:n:n]
73 | })
74 | return collection
75 | }
76 |
77 | func register(ttf []byte) {
78 | faces, err := opentype.ParseCollection(ttf)
79 | if err != nil {
80 | panic(fmt.Errorf("failed to parse font: %v", err))
81 | }
82 | collection = append(collection, faces[0])
83 | }
84 |
--------------------------------------------------------------------------------
/gesture/gesture_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gesture
4 |
5 | import (
6 | "image"
7 | "testing"
8 | "time"
9 |
10 | "gioui.org/f32"
11 | "gioui.org/io/event"
12 | "gioui.org/io/input"
13 | "gioui.org/io/pointer"
14 | "gioui.org/op"
15 | "gioui.org/op/clip"
16 | )
17 |
18 | func TestHover(t *testing.T) {
19 | ops := new(op.Ops)
20 | var h Hover
21 | rect := image.Rect(20, 20, 40, 40)
22 | stack := clip.Rect(rect).Push(ops)
23 | h.Add(ops)
24 | stack.Pop()
25 | r := new(input.Router)
26 | h.Update(r.Source())
27 | r.Frame(ops)
28 |
29 | r.Queue(
30 | pointer.Event{Kind: pointer.Move, Position: f32.Pt(30, 30)},
31 | )
32 | if !h.Update(r.Source()) {
33 | t.Fatal("expected hovered")
34 | }
35 |
36 | r.Queue(
37 | pointer.Event{Kind: pointer.Move, Position: f32.Pt(50, 50)},
38 | )
39 | if h.Update(r.Source()) {
40 | t.Fatal("expected not hovered")
41 | }
42 | }
43 |
44 | func TestMouseClicks(t *testing.T) {
45 | for _, tc := range []struct {
46 | label string
47 | events []event.Event
48 | clicks []int // number of combined clicks per click (single, double...)
49 | }{
50 | {
51 | label: "single click",
52 | events: mouseClickEvents(200 * time.Millisecond),
53 | clicks: []int{1},
54 | },
55 | {
56 | label: "double click",
57 | events: mouseClickEvents(
58 | 100*time.Millisecond,
59 | 100*time.Millisecond+doubleClickDuration-1),
60 | clicks: []int{1, 2},
61 | },
62 | {
63 | label: "two single clicks",
64 | events: mouseClickEvents(
65 | 100*time.Millisecond,
66 | 100*time.Millisecond+doubleClickDuration+1),
67 | clicks: []int{1, 1},
68 | },
69 | } {
70 | t.Run(tc.label, func(t *testing.T) {
71 | var click Click
72 | var ops op.Ops
73 | click.Add(&ops)
74 |
75 | var r input.Router
76 | click.Update(r.Source())
77 | r.Frame(&ops)
78 | r.Queue(tc.events...)
79 |
80 | var clicks []ClickEvent
81 | for {
82 | ev, ok := click.Update(r.Source())
83 | if !ok {
84 | break
85 | }
86 | if ev.Kind == KindClick {
87 | clicks = append(clicks, ev)
88 | }
89 | }
90 | if got, want := len(clicks), len(tc.clicks); got != want {
91 | t.Fatalf("got %d mouse clicks, expected %d", got, want)
92 | }
93 |
94 | for i, click := range clicks {
95 | if got, want := click.NumClicks, tc.clicks[i]; got != want {
96 | t.Errorf("got %d combined mouse clicks, expected %d", got, want)
97 | }
98 | }
99 | })
100 | }
101 | }
102 |
103 | func mouseClickEvents(times ...time.Duration) []event.Event {
104 | press := pointer.Event{
105 | Kind: pointer.Press,
106 | Source: pointer.Mouse,
107 | Buttons: pointer.ButtonPrimary,
108 | }
109 | events := make([]event.Event, 0, 2*len(times))
110 | for _, t := range times {
111 | press := press
112 | press.Time = t
113 | release := press
114 | release.Kind = pointer.Release
115 | events = append(events, press, release)
116 | }
117 | return events
118 | }
119 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module gioui.org
2 |
3 | go 1.23.8
4 |
5 | require (
6 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
7 | gioui.org/shader v1.0.8
8 | github.com/go-text/typesetting v0.3.0
9 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
10 | golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
11 | golang.org/x/image v0.26.0
12 | golang.org/x/sys v0.33.0
13 | golang.org/x/text v0.24.0
14 | )
15 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
2 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
3 | gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
4 | gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
5 | gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
6 | github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
7 | github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
8 | github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
9 | github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
10 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
11 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
12 | golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
13 | golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
14 | golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
15 | golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
16 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
17 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
18 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
19 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
20 |
--------------------------------------------------------------------------------
/gpu/api.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gpu
4 |
5 | import "gioui.org/gpu/internal/driver"
6 |
7 | // An API carries the necessary GPU API specific resources to create a Device.
8 | // There is an API type for each supported GPU API such as OpenGL and Direct3D.
9 | type API = driver.API
10 |
11 | // A RenderTarget denotes the destination framebuffer for a frame.
12 | type RenderTarget = driver.RenderTarget
13 |
14 | // OpenGLRenderTarget is a render target suitable for the OpenGL backend.
15 | type OpenGLRenderTarget = driver.OpenGLRenderTarget
16 |
17 | // Direct3D11RenderTarget is a render target suitable for the Direct3D 11 backend.
18 | type Direct3D11RenderTarget = driver.Direct3D11RenderTarget
19 |
20 | // MetalRenderTarget is a render target suitable for the Metal backend.
21 | type MetalRenderTarget = driver.MetalRenderTarget
22 |
23 | // VulkanRenderTarget is a render target suitable for the Vulkan backend.
24 | type VulkanRenderTarget = driver.VulkanRenderTarget
25 |
26 | // OpenGL denotes the OpenGL or OpenGL ES API.
27 | type OpenGL = driver.OpenGL
28 |
29 | // Direct3D11 denotes the Direct3D API.
30 | type Direct3D11 = driver.Direct3D11
31 |
32 | // Metal denotes the Apple Metal API.
33 | type Metal = driver.Metal
34 |
35 | // Vulkan denotes the Vulkan API.
36 | type Vulkan = driver.Vulkan
37 |
38 | // ErrDeviceLost is returned from GPU operations when the underlying GPU device
39 | // is lost and should be recreated.
40 | var ErrDeviceLost = driver.ErrDeviceLost
41 |
--------------------------------------------------------------------------------
/gpu/clip_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gpu
4 |
5 | import (
6 | "testing"
7 |
8 | "gioui.org/internal/f32"
9 | )
10 |
11 | func BenchmarkEncodeQuadTo(b *testing.B) {
12 | var data [vertStride * 4]byte
13 | for i := 0; b.Loop(); i++ {
14 | v := float32(i)
15 | encodeQuadTo(data[:], 123,
16 | f32.Point{X: v, Y: v},
17 | f32.Point{X: v, Y: v},
18 | f32.Point{X: v, Y: v},
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/gpu/headless/headless_darwin.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package headless
4 |
5 | import (
6 | "errors"
7 |
8 | "gioui.org/gpu"
9 | _ "gioui.org/internal/cocoainit"
10 | )
11 |
12 | /*
13 | #cgo CFLAGS: -Werror -Wno-deprecated-declarations -fobjc-arc -x objective-c
14 | #cgo LDFLAGS: -framework CoreGraphics -framework Metal -framework Foundation
15 |
16 | #import
17 |
18 | static CFTypeRef createDevice(void) {
19 | @autoreleasepool {
20 | id dev = MTLCreateSystemDefaultDevice();
21 | return CFBridgingRetain(dev);
22 | }
23 | }
24 |
25 | static CFTypeRef newCommandQueue(CFTypeRef devRef) {
26 | @autoreleasepool {
27 | id dev = (__bridge id)devRef;
28 | return CFBridgingRetain([dev newCommandQueue]);
29 | }
30 | }
31 | */
32 | import "C"
33 |
34 | type mtlContext struct {
35 | dev C.CFTypeRef
36 | queue C.CFTypeRef
37 | }
38 |
39 | func init() {
40 | newContextPrimary = func() (context, error) {
41 | dev := C.createDevice()
42 | if dev == 0 {
43 | return nil, errors.New("headless: failed to create Metal device")
44 | }
45 | queue := C.newCommandQueue(dev)
46 | if queue == 0 {
47 | C.CFRelease(dev)
48 | return nil, errors.New("headless: failed to create MTLQueue")
49 | }
50 | return &mtlContext{dev: dev, queue: queue}, nil
51 | }
52 | }
53 |
54 | func (c *mtlContext) API() gpu.API {
55 | return gpu.Metal{
56 | Device: uintptr(c.dev),
57 | Queue: uintptr(c.queue),
58 | PixelFormat: int(C.MTLPixelFormatRGBA8Unorm_sRGB),
59 | }
60 | }
61 |
62 | func (c *mtlContext) MakeCurrent() error {
63 | return nil
64 | }
65 |
66 | func (c *mtlContext) ReleaseCurrent() {}
67 |
68 | func (d *mtlContext) Release() {
69 | C.CFRelease(d.dev)
70 | C.CFRelease(d.queue)
71 | *d = mtlContext{}
72 | }
73 |
--------------------------------------------------------------------------------
/gpu/headless/headless_egl.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build linux || freebsd || openbsd
4 | // +build linux freebsd openbsd
5 |
6 | package headless
7 |
8 | import (
9 | "gioui.org/internal/egl"
10 | )
11 |
12 | func init() {
13 | newContextPrimary = func() (context, error) {
14 | return egl.NewContext(egl.EGL_DEFAULT_DISPLAY)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/gpu/headless/headless_js.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package headless
4 |
5 | import (
6 | "errors"
7 | "syscall/js"
8 |
9 | "gioui.org/gpu"
10 | "gioui.org/internal/gl"
11 | )
12 |
13 | type jsContext struct {
14 | ctx js.Value
15 | }
16 |
17 | func init() {
18 | newContextPrimary = func() (context, error) {
19 | doc := js.Global().Get("document")
20 | cnv := doc.Call("createElement", "canvas")
21 | ctx := cnv.Call("getContext", "webgl2")
22 | if ctx.IsNull() {
23 | ctx = cnv.Call("getContext", "webgl")
24 | }
25 | if ctx.IsNull() {
26 | return nil, errors.New("headless: webgl is not supported")
27 | }
28 | c := &jsContext{
29 | ctx: ctx,
30 | }
31 | return c, nil
32 | }
33 | }
34 |
35 | func (c *jsContext) API() gpu.API {
36 | return gpu.OpenGL{Context: gl.Context(c.ctx)}
37 | }
38 |
39 | func (c *jsContext) Release() {
40 | }
41 |
42 | func (c *jsContext) ReleaseCurrent() {
43 | }
44 |
45 | func (c *jsContext) MakeCurrent() error {
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/gpu/headless/headless_vulkan.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build (linux || freebsd) && !novulkan
4 | // +build linux freebsd
5 | // +build !novulkan
6 |
7 | package headless
8 |
9 | import (
10 | "unsafe"
11 |
12 | "gioui.org/gpu"
13 | "gioui.org/internal/vk"
14 | )
15 |
16 | type vkContext struct {
17 | physDev vk.PhysicalDevice
18 | inst vk.Instance
19 | dev vk.Device
20 | queueFam int
21 | }
22 |
23 | func init() {
24 | newContextFallback = newVulkanContext
25 | }
26 |
27 | func newVulkanContext() (context, error) {
28 | inst, err := vk.CreateInstance()
29 | if err != nil {
30 | return nil, err
31 | }
32 | physDev, qFam, err := vk.ChoosePhysicalDevice(inst, 0)
33 | if err != nil {
34 | vk.DestroyInstance(inst)
35 | return nil, err
36 | }
37 | dev, err := vk.CreateDeviceAndQueue(physDev, qFam)
38 | if err != nil {
39 | vk.DestroyInstance(inst)
40 | return nil, err
41 | }
42 | ctx := &vkContext{
43 | physDev: physDev,
44 | inst: inst,
45 | dev: dev,
46 | queueFam: qFam,
47 | }
48 | return ctx, nil
49 | }
50 |
51 | func (c *vkContext) API() gpu.API {
52 | return gpu.Vulkan{
53 | PhysDevice: unsafe.Pointer(c.physDev),
54 | Device: unsafe.Pointer(c.dev),
55 | Format: int(vk.FORMAT_R8G8B8A8_SRGB),
56 | QueueFamily: c.queueFam,
57 | QueueIndex: 0,
58 | }
59 | }
60 |
61 | func (c *vkContext) MakeCurrent() error {
62 | return nil
63 | }
64 |
65 | func (c *vkContext) ReleaseCurrent() {
66 | }
67 |
68 | func (c *vkContext) Release() {
69 | vk.DeviceWaitIdle(c.dev)
70 |
71 | vk.DestroyDevice(c.dev)
72 | vk.DestroyInstance(c.inst)
73 | *c = vkContext{}
74 | }
75 |
--------------------------------------------------------------------------------
/gpu/headless/headless_windows.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package headless
4 |
5 | import (
6 | "unsafe"
7 |
8 | "gioui.org/gpu"
9 | "gioui.org/internal/d3d11"
10 | )
11 |
12 | type d3d11Context struct {
13 | dev *d3d11.Device
14 | }
15 |
16 | func init() {
17 | newContextPrimary = func() (context, error) {
18 | dev, ctx, _, err := d3d11.CreateDevice(
19 | d3d11.DRIVER_TYPE_HARDWARE,
20 | 0,
21 | )
22 | if err != nil {
23 | return nil, err
24 | }
25 | // Don't need it.
26 | d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release)
27 | return &d3d11Context{dev: dev}, nil
28 | }
29 | }
30 |
31 | func (c *d3d11Context) API() gpu.API {
32 | return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)}
33 | }
34 |
35 | func (c *d3d11Context) MakeCurrent() error {
36 | return nil
37 | }
38 |
39 | func (c *d3d11Context) ReleaseCurrent() {
40 | }
41 |
42 | func (c *d3d11Context) Release() {
43 | d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
44 | c.dev = nil
45 | }
46 |
--------------------------------------------------------------------------------
/gpu/internal/d3d11/d3d11.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // This file exists so this package builds on non-Windows platforms.
4 |
5 | package d3d11
6 |
--------------------------------------------------------------------------------
/gpu/internal/metal/metal.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // This file exists so this package builds on non-Darwin platforms.
4 |
5 | package metal
6 |
--------------------------------------------------------------------------------
/gpu/internal/rendertest/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package rendertest is intended for testing of drawing ops only.
4 | package rendertest
5 |
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestBuildOffscreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestBuildOffscreen.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestBuildOffscreen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestBuildOffscreen_1.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestClipOffset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestClipOffset.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestClipPaintOffset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestClipPaintOffset.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestClipRotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestClipRotate.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestClipScale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestClipScale.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestComplicatedTransform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestComplicatedTransform.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestDeferredPaint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestDeferredPaint.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestDepthOverlap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestDepthOverlap.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestGapsInPath/Outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestGapsInPath/Outline.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestGapsInPath/Stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestGapsInPath/Stroke.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestImageRGBA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestImageRGBA.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestImageRGBA_ScaleLinear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestImageRGBA_ScaleLinear.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestImageRGBA_ScaleNearest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestImageRGBA_ScaleNearest.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestInstancedRects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestInstancedRects.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestLinearGradientAngled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestLinearGradientAngled.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestNegativeOverlaps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestNegativeOverlaps.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestNoClipFromPaint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestNoClipFromPaint.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestOffsetScaleTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestOffsetScaleTexture.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestOffsetTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestOffsetTexture.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestOpacity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestOpacity.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintAbsolute.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintAbsolute.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintArc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintArc.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintClippedCircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintClippedCircle.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintClippedRect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintClippedRect.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintClippedTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintClippedTexture.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintOffset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintOffset.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintRect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintRect.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintRotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintRotate.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintShear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintShear.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPaintTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPaintTexture.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestPathReuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestPathReuse.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestRepeatedPaintsZ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestRepeatedPaintsZ.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestReuseStencil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestReuseStencil.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestRotateClipTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestRotateClipTexture.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestRotateTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestRotateTexture.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestStrokedPathBalloon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestStrokedPathBalloon.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestStrokedPathCoincidentControlPoint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestStrokedPathCoincidentControlPoint.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestStrokedPathZeroWidth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestStrokedPathZeroWidth.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestStrokedRect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestStrokedRect.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestTexturedStroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestTexturedStroke.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestTexturedStrokeClipped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestTexturedStrokeClipped.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestTransformMacro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestTransformMacro.png
--------------------------------------------------------------------------------
/gpu/internal/rendertest/refs/TestTransformOrder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gioui/gio/8104d527c746a70863dbfdc90727f6f8996dfe8b/gpu/internal/rendertest/refs/TestTransformOrder.png
--------------------------------------------------------------------------------
/gpu/internal/vulkan/vulkan_nosupport.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package vulkan
4 |
5 | // Empty file to avoid the build error for platforms without Vulkan support.
6 |
--------------------------------------------------------------------------------
/gpu/pack.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gpu
4 |
5 | import (
6 | "image"
7 | )
8 |
9 | // packer packs a set of many smaller rectangles into
10 | // much fewer larger atlases.
11 | type packer struct {
12 | maxDims image.Point
13 | spaces []image.Rectangle
14 |
15 | sizes []image.Point
16 | pos image.Point
17 | }
18 |
19 | type placement struct {
20 | Idx int
21 | Pos image.Point
22 | }
23 |
24 | // add adds the given rectangle to the atlases and
25 | // return the allocated position.
26 | func (p *packer) add(s image.Point) (placement, bool) {
27 | if place, ok := p.tryAdd(s); ok {
28 | return place, true
29 | }
30 | p.newPage()
31 | return p.tryAdd(s)
32 | }
33 |
34 | func (p *packer) clear() {
35 | p.sizes = p.sizes[:0]
36 | p.spaces = p.spaces[:0]
37 | }
38 |
39 | func (p *packer) newPage() {
40 | p.pos = image.Point{}
41 | p.sizes = append(p.sizes, image.Point{})
42 | p.spaces = p.spaces[:0]
43 | p.spaces = append(p.spaces, image.Rectangle{
44 | Max: image.Point{X: 1e6, Y: 1e6},
45 | })
46 | }
47 |
48 | func (p *packer) tryAdd(s image.Point) (placement, bool) {
49 | if len(p.spaces) == 0 || len(p.sizes) == 0 {
50 | return placement{}, false
51 | }
52 |
53 | var (
54 | bestIdx *image.Rectangle
55 | bestSize = p.maxDims
56 | lastSize = p.sizes[len(p.sizes)-1]
57 | )
58 | // Go backwards to prioritize smaller spaces.
59 | for i := range p.spaces {
60 | space := &p.spaces[i]
61 | rightSpace := space.Dx() - s.X
62 | bottomSpace := space.Dy() - s.Y
63 | if rightSpace < 0 || bottomSpace < 0 {
64 | continue
65 | }
66 | size := lastSize
67 | if x := space.Min.X + s.X; x > size.X {
68 | if x > p.maxDims.X {
69 | continue
70 | }
71 | size.X = x
72 | }
73 | if y := space.Min.Y + s.Y; y > size.Y {
74 | if y > p.maxDims.Y {
75 | continue
76 | }
77 | size.Y = y
78 | }
79 | if size.X*size.Y < bestSize.X*bestSize.Y {
80 | bestIdx = space
81 | bestSize = size
82 | }
83 | }
84 | if bestIdx == nil {
85 | return placement{}, false
86 | }
87 | // Remove space.
88 | bestSpace := *bestIdx
89 | *bestIdx = p.spaces[len(p.spaces)-1]
90 | p.spaces = p.spaces[:len(p.spaces)-1]
91 | // Put s in the top left corner and add the (at most)
92 | // two smaller spaces.
93 | pos := bestSpace.Min
94 | if rem := bestSpace.Dy() - s.Y; rem > 0 {
95 | p.spaces = append(p.spaces, image.Rectangle{
96 | Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
97 | Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y},
98 | })
99 | }
100 | if rem := bestSpace.Dx() - s.X; rem > 0 {
101 | p.spaces = append(p.spaces, image.Rectangle{
102 | Min: image.Point{X: pos.X + s.X, Y: pos.Y},
103 | Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y},
104 | })
105 | }
106 | idx := len(p.sizes) - 1
107 | p.sizes[idx] = bestSize
108 | return placement{Idx: idx, Pos: pos}, true
109 | }
110 |
--------------------------------------------------------------------------------
/gpu/pack_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gpu
4 |
5 | import (
6 | "image"
7 | "testing"
8 | )
9 |
10 | func BenchmarkPacker(b *testing.B) {
11 | var p packer
12 | p.maxDims = image.Point{X: 4096, Y: 4096}
13 | for i := 0; b.Loop(); i++ {
14 | p.clear()
15 | p.newPage()
16 | for k := range 500 {
17 | _, ok := p.tryAdd(xy(k))
18 | if !ok {
19 | b.Fatal("add failed", i, k, xy(k))
20 | }
21 | }
22 | }
23 | }
24 |
25 | func xy(v int) image.Point {
26 | return image.Point{
27 | X: ((v / 16) % 16) + 8,
28 | Y: (v % 16) + 8,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/gpu/timer.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gpu
4 |
5 | import (
6 | "time"
7 |
8 | "gioui.org/gpu/internal/driver"
9 | )
10 |
11 | type timers struct {
12 | backend driver.Device
13 | timers []*timer
14 | }
15 |
16 | type timer struct {
17 | Elapsed time.Duration
18 | backend driver.Device
19 | timer driver.Timer
20 | state timerState
21 | }
22 |
23 | type timerState uint8
24 |
25 | const (
26 | timerIdle timerState = iota
27 | timerRunning
28 | timerWaiting
29 | )
30 |
31 | func newTimers(b driver.Device) *timers {
32 | return &timers{
33 | backend: b,
34 | }
35 | }
36 |
37 | func (t *timers) newTimer() *timer {
38 | if t == nil {
39 | return nil
40 | }
41 | tt := &timer{
42 | backend: t.backend,
43 | timer: t.backend.NewTimer(),
44 | }
45 | t.timers = append(t.timers, tt)
46 | return tt
47 | }
48 |
49 | func (t *timer) begin() {
50 | if t == nil || t.state != timerIdle {
51 | return
52 | }
53 | t.timer.Begin()
54 | t.state = timerRunning
55 | }
56 |
57 | func (t *timer) end() {
58 | if t == nil || t.state != timerRunning {
59 | return
60 | }
61 | t.timer.End()
62 | t.state = timerWaiting
63 | }
64 |
65 | func (t *timers) ready() bool {
66 | if t == nil {
67 | return false
68 | }
69 | for _, tt := range t.timers {
70 | switch tt.state {
71 | case timerIdle:
72 | continue
73 | case timerRunning:
74 | return false
75 | }
76 | d, ok := tt.timer.Duration()
77 | if !ok {
78 | return false
79 | }
80 | tt.state = timerIdle
81 | tt.Elapsed = d
82 | }
83 | return t.backend.IsTimeContinuous()
84 | }
85 |
86 | func (t *timers) Release() {
87 | if t == nil {
88 | return
89 | }
90 | for _, tt := range t.timers {
91 | tt.timer.Release()
92 | }
93 | t.timers = nil
94 | }
95 |
--------------------------------------------------------------------------------
/internal/byteslice/byteslice.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package byteslice provides byte slice views of other Go values such as
4 | // slices and structs.
5 | package byteslice
6 |
7 | import (
8 | "reflect"
9 | "unsafe"
10 | )
11 |
12 | // Struct returns a byte slice view of a struct.
13 | func Struct(s any) []byte {
14 | v := reflect.ValueOf(s)
15 | sz := int(v.Elem().Type().Size())
16 | return unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz)
17 | }
18 |
19 | // Uint32 returns a byte slice view of a uint32 slice.
20 | func Uint32(s []uint32) []byte {
21 | n := len(s)
22 | if n == 0 {
23 | return nil
24 | }
25 | blen := n * int(unsafe.Sizeof(s[0]))
26 | return unsafe.Slice((*byte)(unsafe.Pointer(&s[0])), blen)
27 | }
28 |
29 | // Slice returns a byte slice view of a slice.
30 | func Slice(s any) []byte {
31 | v := reflect.ValueOf(s)
32 | first := v.Index(0)
33 | sz := int(first.Type().Size())
34 | res := unsafe.Slice((*byte)(unsafe.Pointer(v.Pointer())), sz*v.Cap())
35 | return res[:sz*v.Len()]
36 | }
37 |
--------------------------------------------------------------------------------
/internal/cocoainit/cocoa_darwin.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package cocoainit initializes support for multithreaded
4 | // programs in Cocoa.
5 | package cocoainit
6 |
7 | /*
8 | #cgo CFLAGS: -xobjective-c -fobjc-arc
9 | #cgo LDFLAGS: -framework Foundation
10 | #import
11 |
12 | static inline void activate_cocoa_multithreading() {
13 | [[NSThread new] start];
14 | }
15 | #pragma GCC visibility push(hidden)
16 | */
17 | import "C"
18 |
19 | func init() {
20 | C.activate_cocoa_multithreading()
21 | }
22 |
--------------------------------------------------------------------------------
/internal/debug/debug.go:
--------------------------------------------------------------------------------
1 | // Package debug provides general debug feature management for Gio, including
2 | // the ability to toggle debug features using the GIODEBUG environment variable.
3 | package debug
4 |
5 | import (
6 | "fmt"
7 | "os"
8 | "strings"
9 | "sync"
10 | "sync/atomic"
11 | )
12 |
13 | const (
14 | debugVariable = "GIODEBUG"
15 | textSubsystem = "text"
16 | silentFeature = "silent"
17 | )
18 |
19 | // Text controls whether the text subsystem has debug logging enabled.
20 | var Text atomic.Bool
21 |
22 | var parseOnce sync.Once
23 |
24 | // Parse processes the current value of GIODEBUG. If it is unset, it does nothing.
25 | // Otherwise it process its value, printing usage info the stderr if the value is
26 | // not understood. Parse will be automatically invoked when the first application
27 | // window is created, allowing applications to manipulate GIODEBUG programmatically
28 | // before it is parsed.
29 | func Parse() {
30 | parseOnce.Do(func() {
31 | val, ok := os.LookupEnv(debugVariable)
32 | if !ok {
33 | return
34 | }
35 | print := false
36 | silent := false
37 | for _, part := range strings.Split(val, ",") {
38 | switch part {
39 | case textSubsystem:
40 | Text.Store(true)
41 | case silentFeature:
42 | silent = true
43 | default:
44 | print = true
45 | }
46 | }
47 | if print && !silent {
48 | fmt.Fprintf(os.Stderr,
49 | `Usage of %s:
50 | A comma-delimited list of debug subsystems to enable. Currently recognized systems:
51 |
52 | - %s: text debug info including system font resolution
53 | - %s: silence this usage message even if GIODEBUG contains invalid content
54 | `, debugVariable, textSubsystem, silentFeature)
55 | }
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/internal/egl/egl_unix.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build linux || freebsd || openbsd
4 | // +build linux freebsd openbsd
5 |
6 | package egl
7 |
8 | /*
9 | #cgo linux,!android pkg-config: egl
10 | #cgo freebsd openbsd android LDFLAGS: -lEGL
11 | #cgo freebsd CFLAGS: -I/usr/local/include
12 | #cgo freebsd LDFLAGS: -L/usr/local/lib
13 | #cgo openbsd CFLAGS: -I/usr/X11R6/include
14 | #cgo openbsd LDFLAGS: -L/usr/X11R6/lib
15 | #cgo CFLAGS: -DEGL_NO_X11
16 |
17 | #include
18 | #include
19 | */
20 | import "C"
21 |
22 | type (
23 | _EGLint = C.EGLint
24 | _EGLDisplay = C.EGLDisplay
25 | _EGLConfig = C.EGLConfig
26 | _EGLContext = C.EGLContext
27 | _EGLSurface = C.EGLSurface
28 | NativeDisplayType = C.EGLNativeDisplayType
29 | NativeWindowType = C.EGLNativeWindowType
30 | )
31 |
32 | func loadEGL() error {
33 | return nil
34 | }
35 |
36 | func eglChooseConfig(disp _EGLDisplay, attribs []_EGLint) (_EGLConfig, bool) {
37 | var cfg C.EGLConfig
38 | var ncfg C.EGLint
39 | if C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &ncfg) != C.EGL_TRUE {
40 | return nilEGLConfig, false
41 | }
42 | return _EGLConfig(cfg), true
43 | }
44 |
45 | func eglCreateContext(disp _EGLDisplay, cfg _EGLConfig, shareCtx _EGLContext, attribs []_EGLint) _EGLContext {
46 | ctx := C.eglCreateContext(disp, cfg, shareCtx, &attribs[0])
47 | return _EGLContext(ctx)
48 | }
49 |
50 | func eglDestroySurface(disp _EGLDisplay, surf _EGLSurface) bool {
51 | return C.eglDestroySurface(disp, surf) == C.EGL_TRUE
52 | }
53 |
54 | func eglDestroyContext(disp _EGLDisplay, ctx _EGLContext) bool {
55 | return C.eglDestroyContext(disp, ctx) == C.EGL_TRUE
56 | }
57 |
58 | func eglGetConfigAttrib(disp _EGLDisplay, cfg _EGLConfig, attr _EGLint) (_EGLint, bool) {
59 | var val _EGLint
60 | ret := C.eglGetConfigAttrib(disp, cfg, attr, &val)
61 | return val, ret == C.EGL_TRUE
62 | }
63 |
64 | func eglGetError() _EGLint {
65 | return C.eglGetError()
66 | }
67 |
68 | func eglInitialize(disp _EGLDisplay) (_EGLint, _EGLint, bool) {
69 | var maj, min _EGLint
70 | ret := C.eglInitialize(disp, &maj, &min)
71 | return maj, min, ret == C.EGL_TRUE
72 | }
73 |
74 | func eglMakeCurrent(disp _EGLDisplay, draw, read _EGLSurface, ctx _EGLContext) bool {
75 | return C.eglMakeCurrent(disp, draw, read, ctx) == C.EGL_TRUE
76 | }
77 |
78 | func eglReleaseThread() bool {
79 | return C.eglReleaseThread() == C.EGL_TRUE
80 | }
81 |
82 | func eglSwapBuffers(disp _EGLDisplay, surf _EGLSurface) bool {
83 | return C.eglSwapBuffers(disp, surf) == C.EGL_TRUE
84 | }
85 |
86 | func eglSwapInterval(disp _EGLDisplay, interval _EGLint) bool {
87 | return C.eglSwapInterval(disp, interval) == C.EGL_TRUE
88 | }
89 |
90 | func eglTerminate(disp _EGLDisplay) bool {
91 | return C.eglTerminate(disp) == C.EGL_TRUE
92 | }
93 |
94 | func eglQueryString(disp _EGLDisplay, name _EGLint) string {
95 | return C.GoString(C.eglQueryString(disp, name))
96 | }
97 |
98 | func eglGetDisplay(disp NativeDisplayType) _EGLDisplay {
99 | return C.eglGetDisplay(disp)
100 | }
101 |
102 | func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win NativeWindowType, attribs []_EGLint) _EGLSurface {
103 | eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
104 | return eglSurf
105 | }
106 |
107 | func eglWaitClient() bool {
108 | return C.eglWaitClient() == C.EGL_TRUE
109 | }
110 |
--------------------------------------------------------------------------------
/internal/f32color/f32colorgen/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "bytes"
7 | "flag"
8 | "fmt"
9 | "go/format"
10 | "math"
11 | "os"
12 | )
13 |
14 | func main() {
15 | out := flag.String("out", "", "output file")
16 | flag.Parse()
17 |
18 | var b bytes.Buffer
19 | printf := func(content string, args ...any) {
20 | fmt.Fprintf(&b, content, args...)
21 | }
22 |
23 | printf("// SPDX-License-Identifier: Unlicense OR MIT\n\n")
24 | printf("// Code generated by f32colorgen. DO NOT EDIT.\n")
25 | printf("\n")
26 | printf("package f32color\n\n")
27 |
28 | printf("// table corresponds to sRGBToLinear(float32(index)/0xff)\n")
29 | printf("var srgb8ToLinear = [...]float32{")
30 | for b := 0; b <= 0xFF; b++ {
31 | if b%0x10 == 0 {
32 | printf("\n\t")
33 | }
34 | v := sRGBToLinear(float32(b) / 0xff)
35 | printf("%#v,", v)
36 | }
37 | printf("\n}\n")
38 |
39 | data, err := format.Source(b.Bytes())
40 | if err != nil {
41 | fmt.Fprint(os.Stderr, b.String())
42 | panic(err)
43 | }
44 |
45 | err = os.WriteFile(*out, data, 0o755)
46 | if err != nil {
47 | panic(err)
48 | }
49 | }
50 |
51 | // sRGBToLinear transforms color value from sRGB to linear.
52 | func sRGBToLinear(c float32) float32 {
53 | // Formula from EXT_sRGB.
54 | if c <= 0.04045 {
55 | return c / 12.92
56 | } else {
57 | return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/internal/f32color/rgba_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package f32color
4 |
5 | import (
6 | "image/color"
7 | "testing"
8 | )
9 |
10 | func TestNRGBAToLinearRGBA_Boundary(t *testing.T) {
11 | for col := 0; col <= 0xFF; col++ {
12 | for alpha := 0; alpha <= 0xFF; alpha++ {
13 | in := color.NRGBA{R: uint8(col), A: uint8(alpha)}
14 | premul := NRGBAToLinearRGBA(in)
15 | if premul.A != uint8(alpha) {
16 | t.Errorf("%v: got %v expected %v", in, premul.A, alpha)
17 | }
18 | if premul.R > premul.A {
19 | t.Errorf("%v: R=%v > A=%v", in, premul.R, premul.A)
20 | }
21 | }
22 | }
23 | }
24 |
25 | func TestLinearToRGBARoundtrip(t *testing.T) {
26 | for col := 0; col <= 0xFF; col++ {
27 | for alpha := 0; alpha <= 0xFF; alpha++ {
28 | want := color.NRGBA{R: uint8(col), A: uint8(alpha)}
29 | if alpha == 0 {
30 | want.R = 0
31 | }
32 | got := LinearFromSRGB(want).SRGB()
33 | if want != got {
34 | t.Errorf("got %v expected %v", got, want)
35 | }
36 | }
37 | }
38 | }
39 |
40 | var sink RGBA
41 |
42 | func BenchmarkLinearFromSRGB(b *testing.B) {
43 | b.Run("opaque", func(b *testing.B) {
44 | for i := 0; b.Loop(); i++ {
45 | sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0xFF})
46 | }
47 | })
48 | b.Run("translucent", func(b *testing.B) {
49 | for i := 0; b.Loop(); i++ {
50 | sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x50})
51 | }
52 | })
53 | b.Run("transparent", func(b *testing.B) {
54 | for i := 0; b.Loop(); i++ {
55 | sink = LinearFromSRGB(color.NRGBA{R: byte(i), G: byte(i >> 8), B: byte(i >> 16), A: 0x00})
56 | }
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/internal/f32color/tables.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Code generated by f32colorgen. DO NOT EDIT.
4 |
5 | package f32color
6 |
7 | // table corresponds to sRGBToLinear(float32(index)/0xff)
8 | var srgb8ToLinear = [...]float32{
9 | 0, 0.000303527, 0.000607054, 0.000910581, 0.001214108, 0.001517635, 0.001821162, 0.0021246888, 0.002428216, 0.002731743, 0.00303527, 0.0033465363, 0.0036765079, 0.004024718, 0.004391443, 0.004776954,
10 | 0.005181518, 0.0056053926, 0.006048834, 0.0065120924, 0.0069954116, 0.007499033, 0.008023194, 0.008568126, 0.009134059, 0.00972122, 0.010329825, 0.010960096, 0.011612247, 0.012286489, 0.0129830325, 0.013702083,
11 | 0.014443846, 0.015208517, 0.015996296, 0.016807377, 0.017641956, 0.01850022, 0.019382365, 0.020288566, 0.021219013, 0.022173887, 0.023153368, 0.024157634, 0.025186861, 0.026241226, 0.027320895, 0.028426042,
12 | 0.029556837, 0.030713446, 0.031896036, 0.033104766, 0.03433981, 0.035601318, 0.03688945, 0.03820437, 0.039546244, 0.040915202, 0.042311415, 0.043735035, 0.04518621, 0.04666509, 0.048171826, 0.049706567,
13 | 0.05126947, 0.05286066, 0.05448029, 0.056128502, 0.05780544, 0.059511248, 0.06124608, 0.06301004, 0.06480329, 0.06662596, 0.06847819, 0.07036012, 0.07227187, 0.07421359, 0.0761854, 0.078187436,
14 | 0.080219835, 0.08228272, 0.08437622, 0.08650047, 0.08865561, 0.09084174, 0.09305899, 0.09530749, 0.09758737, 0.09989875, 0.102241755, 0.10461651, 0.10702312, 0.10946173, 0.11193245, 0.11443539,
15 | 0.11697068, 0.11953844, 0.122138806, 0.12477185, 0.12743771, 0.1301365, 0.13286835, 0.13563335, 0.13843164, 0.14126332, 0.14412849, 0.14702728, 0.1499598, 0.15292618, 0.15592648, 0.15896088,
16 | 0.16202942, 0.16513222, 0.16826941, 0.17144111, 0.1746474, 0.17788842, 0.18116425, 0.18447499, 0.18782078, 0.19120169, 0.19461782, 0.19806932, 0.20155625, 0.20507872, 0.20863685, 0.21223074,
17 | 0.21586055, 0.21952623, 0.223228, 0.2269659, 0.23074009, 0.23455067, 0.23839766, 0.24228121, 0.24620141, 0.25015837, 0.25415218, 0.25818294, 0.26225075, 0.2663557, 0.27049786, 0.2746774,
18 | 0.27889434, 0.28314883, 0.2874409, 0.29177073, 0.29613835, 0.30054384, 0.30498737, 0.30946898, 0.31398878, 0.31854683, 0.32314327, 0.32777816, 0.33245158, 0.33716366, 0.34191447, 0.3467041,
19 | 0.35153273, 0.35640025, 0.3613069, 0.36625272, 0.37123778, 0.37626222, 0.3813261, 0.38642955, 0.39157256, 0.39675534, 0.40197787, 0.4072403, 0.4125427, 0.41788515, 0.42326775, 0.42869058,
20 | 0.4341537, 0.43965724, 0.44520128, 0.45078585, 0.4564111, 0.46207705, 0.46778387, 0.47353154, 0.47932023, 0.48515, 0.4910209, 0.49693304, 0.5028866, 0.50888145, 0.5149178, 0.5209957,
21 | 0.5271153, 0.53327656, 0.5394796, 0.5457246, 0.55201155, 0.5583405, 0.56471163, 0.5711249, 0.5775806, 0.58407855, 0.59061897, 0.5972019, 0.6038274, 0.6104957, 0.61720663, 0.6239605,
22 | 0.6307572, 0.63759696, 0.64447975, 0.6514057, 0.6583749, 0.66538733, 0.6724432, 0.67954254, 0.6866855, 0.6938719, 0.7011021, 0.70837593, 0.71569365, 0.7230553, 0.7304609, 0.73791057,
23 | 0.74540436, 0.7529423, 0.76052463, 0.7681513, 0.77582234, 0.7835379, 0.79129803, 0.79910284, 0.80695236, 0.8148467, 0.82278585, 0.83076996, 0.8387991, 0.84687334, 0.8549927, 0.8631573,
24 | 0.8713672, 0.87962234, 0.8879232, 0.89626944, 0.90466136, 0.9130987, 0.92158204, 0.9301109, 0.9386859, 0.9473066, 0.9559735, 0.9646863, 0.9734455, 0.9822506, 0.9911022, 1,
25 | }
26 |
--------------------------------------------------------------------------------
/internal/fling/animation.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package fling
4 |
5 | import (
6 | "math"
7 | "runtime"
8 | "time"
9 |
10 | "gioui.org/unit"
11 | )
12 |
13 | type Animation struct {
14 | // Current offset in pixels.
15 | x float32
16 | // Initial time.
17 | t0 time.Time
18 | // Initial velocity in pixels pr second.
19 | v0 float32
20 | }
21 |
22 | const (
23 | // dp/second.
24 | minFlingVelocity = unit.Dp(50)
25 | maxFlingVelocity = unit.Dp(8000)
26 | thresholdVelocity = 1
27 | )
28 |
29 | // Start a fling given a starting velocity. Returns whether a
30 | // fling was started.
31 | func (f *Animation) Start(c unit.Metric, now time.Time, velocity float32) bool {
32 | min := float32(c.Dp(minFlingVelocity))
33 | v := velocity
34 | if -min <= v && v <= min {
35 | return false
36 | }
37 | max := float32(c.Dp(maxFlingVelocity))
38 | if v > max {
39 | v = max
40 | } else if v < -max {
41 | v = -max
42 | }
43 | f.init(now, v)
44 | return true
45 | }
46 |
47 | func (f *Animation) init(now time.Time, v0 float32) {
48 | f.t0 = now
49 | f.v0 = v0
50 | f.x = 0
51 | }
52 |
53 | func (f *Animation) Active() bool {
54 | return f.v0 != 0
55 | }
56 |
57 | // Tick computes and returns a fling distance since
58 | // the last time Tick was called.
59 | func (f *Animation) Tick(now time.Time) int {
60 | if !f.Active() {
61 | return 0
62 | }
63 | var k float32
64 | if runtime.GOOS == "darwin" {
65 | k = -2 // iOS
66 | } else {
67 | k = -4.2 // Android and default
68 | }
69 | t := now.Sub(f.t0)
70 | // The acceleration x''(t) of a point mass with a drag
71 | // force, f, proportional with velocity, x'(t), is
72 | // governed by the equation
73 | //
74 | // x''(t) = kx'(t)
75 | //
76 | // Given the starting position x(0) = 0, the starting
77 | // velocity x'(0) = v0, the position is then
78 | // given by
79 | //
80 | // x(t) = v0*e^(k*t)/k - v0/k
81 | //
82 | ekt := float32(math.Exp(float64(k) * t.Seconds()))
83 | x := f.v0*ekt/k - f.v0/k
84 | dist := x - f.x
85 | idist := int(dist)
86 | f.x += float32(idist)
87 | // Solving for the velocity x'(t) gives us
88 | //
89 | // x'(t) = v0*e^(k*t)
90 | v := f.v0 * ekt
91 | if -thresholdVelocity < v && v < thresholdVelocity {
92 | f.v0 = 0
93 | }
94 | return idist
95 | }
96 |
--------------------------------------------------------------------------------
/internal/fling/extrapolation_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package fling
4 |
5 | import "testing"
6 |
7 | func TestDecomposeQR(t *testing.T) {
8 | A := &matrix{
9 | rows: 3, cols: 3,
10 | data: []float32{
11 | 12, 6, -4,
12 | -51, 167, 24,
13 | 4, -68, -41,
14 | },
15 | }
16 | Q, Rt, ok := decomposeQR(A)
17 | if !ok {
18 | t.Fatal("decomposeQR failed")
19 | }
20 | R := Rt.transpose()
21 | QR := Q.mul(R)
22 | if !A.approxEqual(QR) {
23 | t.Log("A\n", A)
24 | t.Log("Q\n", Q)
25 | t.Log("R\n", R)
26 | t.Log("QR\n", QR)
27 | t.Fatal("Q*R not approximately equal to A")
28 | }
29 | }
30 |
31 | func TestFit(t *testing.T) {
32 | X := []float32{-1, 0, 1}
33 | Y := []float32{2, 0, 2}
34 |
35 | got, ok := polyFit(X, Y)
36 | if !ok {
37 | t.Fatal("polyFit failed")
38 | }
39 | want := coefficients{0, 0, 2}
40 | if !got.approxEqual(want) {
41 | t.Fatalf("polyFit: got %v want %v", got, want)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/internal/gl/types.go:
--------------------------------------------------------------------------------
1 | //go:build !js
2 | // +build !js
3 |
4 | package gl
5 |
6 | type (
7 | Object struct{ V uint }
8 | Buffer Object
9 | Framebuffer Object
10 | Program Object
11 | Renderbuffer Object
12 | Shader Object
13 | Texture Object
14 | Query Object
15 | Uniform struct{ V int }
16 | VertexArray Object
17 | )
18 |
19 | func (o Object) valid() bool {
20 | return o.V != 0
21 | }
22 |
23 | func (o Object) equal(o2 Object) bool {
24 | return o == o2
25 | }
26 |
27 | func (u Framebuffer) Valid() bool {
28 | return Object(u).valid()
29 | }
30 |
31 | func (u Uniform) Valid() bool {
32 | return u.V != -1
33 | }
34 |
35 | func (p Program) Valid() bool {
36 | return Object(p).valid()
37 | }
38 |
39 | func (s Shader) Valid() bool {
40 | return Object(s).valid()
41 | }
42 |
43 | func (a VertexArray) Valid() bool {
44 | return Object(a).valid()
45 | }
46 |
47 | func (f Framebuffer) Equal(f2 Framebuffer) bool {
48 | return Object(f).equal(Object(f2))
49 | }
50 |
51 | func (p Program) Equal(p2 Program) bool {
52 | return Object(p).equal(Object(p2))
53 | }
54 |
55 | func (s Shader) Equal(s2 Shader) bool {
56 | return Object(s).equal(Object(s2))
57 | }
58 |
59 | func (u Uniform) Equal(u2 Uniform) bool {
60 | return u == u2
61 | }
62 |
63 | func (a VertexArray) Equal(a2 VertexArray) bool {
64 | return Object(a).equal(Object(a2))
65 | }
66 |
67 | func (r Renderbuffer) Equal(r2 Renderbuffer) bool {
68 | return Object(r).equal(Object(r2))
69 | }
70 |
71 | func (t Texture) Equal(t2 Texture) bool {
72 | return Object(t).equal(Object(t2))
73 | }
74 |
75 | func (b Buffer) Equal(b2 Buffer) bool {
76 | return Object(b).equal(Object(b2))
77 | }
78 |
--------------------------------------------------------------------------------
/internal/gl/types_js.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gl
4 |
5 | import "syscall/js"
6 |
7 | type (
8 | Object js.Value
9 | Buffer Object
10 | Framebuffer Object
11 | Program Object
12 | Renderbuffer Object
13 | Shader Object
14 | Texture Object
15 | Query Object
16 | Uniform Object
17 | VertexArray Object
18 | )
19 |
20 | func (o Object) valid() bool {
21 | return js.Value(o).Truthy()
22 | }
23 |
24 | func (o Object) equal(o2 Object) bool {
25 | return js.Value(o).Equal(js.Value(o2))
26 | }
27 |
28 | func (b Buffer) Valid() bool {
29 | return Object(b).valid()
30 | }
31 |
32 | func (f Framebuffer) Valid() bool {
33 | return Object(f).valid()
34 | }
35 |
36 | func (p Program) Valid() bool {
37 | return Object(p).valid()
38 | }
39 |
40 | func (r Renderbuffer) Valid() bool {
41 | return Object(r).valid()
42 | }
43 |
44 | func (s Shader) Valid() bool {
45 | return Object(s).valid()
46 | }
47 |
48 | func (t Texture) Valid() bool {
49 | return Object(t).valid()
50 | }
51 |
52 | func (u Uniform) Valid() bool {
53 | return Object(u).valid()
54 | }
55 |
56 | func (a VertexArray) Valid() bool {
57 | return Object(a).valid()
58 | }
59 |
60 | func (f Framebuffer) Equal(f2 Framebuffer) bool {
61 | return Object(f).equal(Object(f2))
62 | }
63 |
64 | func (p Program) Equal(p2 Program) bool {
65 | return Object(p).equal(Object(p2))
66 | }
67 |
68 | func (s Shader) Equal(s2 Shader) bool {
69 | return Object(s).equal(Object(s2))
70 | }
71 |
72 | func (u Uniform) Equal(u2 Uniform) bool {
73 | return Object(u).equal(Object(u2))
74 | }
75 |
76 | func (a VertexArray) Equal(a2 VertexArray) bool {
77 | return Object(a).equal(Object(a2))
78 | }
79 |
80 | func (r Renderbuffer) Equal(r2 Renderbuffer) bool {
81 | return Object(r).equal(Object(r2))
82 | }
83 |
84 | func (t Texture) Equal(t2 Texture) bool {
85 | return Object(t).equal(Object(t2))
86 | }
87 |
88 | func (b Buffer) Equal(b2 Buffer) bool {
89 | return Object(b).equal(Object(b2))
90 | }
91 |
--------------------------------------------------------------------------------
/internal/gl/util.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gl
4 |
5 | import (
6 | "errors"
7 | "fmt"
8 | "strings"
9 | )
10 |
11 | func CreateProgram(ctx *Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
12 | vs, err := CreateShader(ctx, VERTEX_SHADER, vsSrc)
13 | if err != nil {
14 | return Program{}, err
15 | }
16 | defer ctx.DeleteShader(vs)
17 | fs, err := CreateShader(ctx, FRAGMENT_SHADER, fsSrc)
18 | if err != nil {
19 | return Program{}, err
20 | }
21 | defer ctx.DeleteShader(fs)
22 | prog := ctx.CreateProgram()
23 | if !prog.Valid() {
24 | return Program{}, errors.New("glCreateProgram failed")
25 | }
26 | ctx.AttachShader(prog, vs)
27 | ctx.AttachShader(prog, fs)
28 | for i, a := range attribs {
29 | ctx.BindAttribLocation(prog, Attrib(i), a)
30 | }
31 | ctx.LinkProgram(prog)
32 | if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
33 | log := ctx.GetProgramInfoLog(prog)
34 | ctx.DeleteProgram(prog)
35 | return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
36 | }
37 | return prog, nil
38 | }
39 |
40 | func CreateComputeProgram(ctx *Functions, src string) (Program, error) {
41 | cs, err := CreateShader(ctx, COMPUTE_SHADER, src)
42 | if err != nil {
43 | return Program{}, err
44 | }
45 | defer ctx.DeleteShader(cs)
46 | prog := ctx.CreateProgram()
47 | if !prog.Valid() {
48 | return Program{}, errors.New("glCreateProgram failed")
49 | }
50 | ctx.AttachShader(prog, cs)
51 | ctx.LinkProgram(prog)
52 | if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
53 | log := ctx.GetProgramInfoLog(prog)
54 | ctx.DeleteProgram(prog)
55 | return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
56 | }
57 | return prog, nil
58 | }
59 |
60 | func CreateShader(ctx *Functions, typ Enum, src string) (Shader, error) {
61 | sh := ctx.CreateShader(typ)
62 | if !sh.Valid() {
63 | return Shader{}, errors.New("glCreateShader failed")
64 | }
65 | ctx.ShaderSource(sh, src)
66 | ctx.CompileShader(sh)
67 | if ctx.GetShaderi(sh, COMPILE_STATUS) == 0 {
68 | log := ctx.GetShaderInfoLog(sh)
69 | ctx.DeleteShader(sh)
70 | return Shader{}, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
71 | }
72 | return sh, nil
73 | }
74 |
75 | func ParseGLVersion(glVer string) (version [2]int, gles bool, err error) {
76 | var ver [2]int
77 | if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
78 | return ver, true, nil
79 | } else if _, err := fmt.Sscanf(glVer, "WebGL %d.%d", &ver[0], &ver[1]); err == nil {
80 | // WebGL major version v corresponds to OpenGL ES version v + 1
81 | ver[0]++
82 | return ver, true, nil
83 | } else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
84 | return ver, false, nil
85 | }
86 | return ver, false, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer)
87 | }
88 |
--------------------------------------------------------------------------------
/internal/stroke/stroke_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package stroke
4 |
5 | import (
6 | "strconv"
7 | "testing"
8 |
9 | "gioui.org/internal/f32"
10 | )
11 |
12 | func BenchmarkSplitCubic(b *testing.B) {
13 | type scenario struct {
14 | segments int
15 | from, ctrl0, ctrl1, to f32.Point
16 | }
17 |
18 | scenarios := []scenario{
19 | {
20 | segments: 4,
21 | from: f32.Pt(0, 0),
22 | ctrl0: f32.Pt(10, 10),
23 | ctrl1: f32.Pt(10, 10),
24 | to: f32.Pt(20, 0),
25 | },
26 | {
27 | segments: 8,
28 | from: f32.Pt(-145.90305, 703.21277),
29 | ctrl0: f32.Pt(-940.20215, 606.05994),
30 | ctrl1: f32.Pt(74.58341, 405.815),
31 | to: f32.Pt(104.35474, -241.543),
32 | },
33 | {
34 | segments: 16,
35 | from: f32.Pt(770.35626, 639.77765),
36 | ctrl0: f32.Pt(735.57135, 545.07837),
37 | ctrl1: f32.Pt(286.7138, 853.7052),
38 | to: f32.Pt(286.7138, 890.5413),
39 | },
40 | {
41 | segments: 33,
42 | from: f32.Pt(0, 0),
43 | ctrl0: f32.Pt(0, 0),
44 | ctrl1: f32.Pt(100, 100),
45 | to: f32.Pt(100, 100),
46 | },
47 | }
48 |
49 | for _, s := range scenarios {
50 | s := s
51 | b.Run(strconv.Itoa(s.segments), func(b *testing.B) {
52 | from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to
53 | quads := make([]QuadSegment, s.segments)
54 | b.ResetTimer()
55 | for b.Loop() {
56 | quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0])
57 | }
58 | if len(quads) != s.segments {
59 | // this is just for checking that we are benchmarking similar splits
60 | // when splitting algorithm splits differently, then it's fine to adjust the
61 | // parameters to give appropriate number of segments.
62 | b.Fatalf("expected %d but got %d", s.segments, len(quads))
63 | }
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/internal/vk/vulkan_android.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !nowayland
4 | // +build !nowayland
5 |
6 | package vk
7 |
8 | /*
9 | #define VK_USE_PLATFORM_ANDROID_KHR
10 | #define VK_NO_PROTOTYPES 1
11 | #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
12 | #include
13 | #include
14 |
15 | static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkInstance instance, const VkAndroidSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) {
16 | return f(instance, pCreateInfo, pAllocator, pSurface);
17 | }
18 | */
19 | import "C"
20 |
21 | import (
22 | "fmt"
23 | "unsafe"
24 | )
25 |
26 | var wlFuncs struct {
27 | vkCreateAndroidSurfaceKHR C.PFN_vkCreateAndroidSurfaceKHR
28 | }
29 |
30 | func init() {
31 | loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) {
32 | wlFuncs.vkCreateAndroidSurfaceKHR = dlopen("vkCreateAndroidSurfaceKHR")
33 | })
34 | }
35 |
36 | func CreateAndroidSurface(inst Instance, window unsafe.Pointer) (Surface, error) {
37 | inf := C.VkAndroidSurfaceCreateInfoKHR{
38 | sType: C.VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
39 | window: (*C.ANativeWindow)(window),
40 | }
41 | var surf Surface
42 | if err := vkErr(C.vkCreateAndroidSurfaceKHR(wlFuncs.vkCreateAndroidSurfaceKHR, inst, &inf, nil, &surf)); err != nil {
43 | return 0, fmt.Errorf("vulkan: vkCreateAndroidSurfaceKHR: %w", err)
44 | }
45 | return surf, nil
46 | }
47 |
--------------------------------------------------------------------------------
/internal/vk/vulkan_wayland.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build ((linux && !android) || freebsd) && !nowayland
4 | // +build linux,!android freebsd
5 | // +build !nowayland
6 |
7 | package vk
8 |
9 | /*
10 | #cgo linux pkg-config: wayland-client
11 |
12 | #define VK_USE_PLATFORM_WAYLAND_KHR
13 | #define VK_NO_PROTOTYPES 1
14 | #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
15 | #include
16 |
17 | static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkInstance instance, const VkWaylandSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) {
18 | return f(instance, pCreateInfo, pAllocator, pSurface);
19 | }
20 | */
21 | import "C"
22 |
23 | import (
24 | "fmt"
25 | "unsafe"
26 | )
27 |
28 | var wlFuncs struct {
29 | vkCreateWaylandSurfaceKHR C.PFN_vkCreateWaylandSurfaceKHR
30 | }
31 |
32 | func init() {
33 | loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) {
34 | wlFuncs.vkCreateWaylandSurfaceKHR = dlopen("vkCreateWaylandSurfaceKHR")
35 | })
36 | }
37 |
38 | func CreateWaylandSurface(inst Instance, disp unsafe.Pointer, wlSurf unsafe.Pointer) (Surface, error) {
39 | inf := C.VkWaylandSurfaceCreateInfoKHR{
40 | sType: C.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
41 | display: (*C.struct_wl_display)(disp),
42 | surface: (*C.struct_wl_surface)(wlSurf),
43 | }
44 | var surf Surface
45 | if err := vkErr(C.vkCreateWaylandSurfaceKHR(wlFuncs.vkCreateWaylandSurfaceKHR, inst, &inf, nil, &surf)); err != nil {
46 | return 0, fmt.Errorf("vulkan: vkCreateWaylandSurfaceKHR: %w", err)
47 | }
48 | return surf, nil
49 | }
50 |
--------------------------------------------------------------------------------
/internal/vk/vulkan_x11.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build ((linux && !android) || freebsd) && !nox11
4 | // +build linux,!android freebsd
5 | // +build !nox11
6 |
7 | package vk
8 |
9 | /*
10 | #define VK_USE_PLATFORM_XLIB_KHR
11 | #define VK_NO_PROTOTYPES 1
12 | #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
13 | #include
14 |
15 | static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance instance, const VkXlibSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) {
16 | return f(instance, pCreateInfo, pAllocator, pSurface);
17 | }
18 | */
19 | import "C"
20 |
21 | import (
22 | "fmt"
23 | "unsafe"
24 | )
25 |
26 | var x11Funcs struct {
27 | vkCreateXlibSurfaceKHR C.PFN_vkCreateXlibSurfaceKHR
28 | }
29 |
30 | func init() {
31 | loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) {
32 | x11Funcs.vkCreateXlibSurfaceKHR = dlopen("vkCreateXlibSurfaceKHR")
33 | })
34 | }
35 |
36 | func CreateXlibSurface(inst Instance, dpy unsafe.Pointer, window uintptr) (Surface, error) {
37 | inf := C.VkXlibSurfaceCreateInfoKHR{
38 | sType: C.VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
39 | dpy: (*C.Display)(dpy),
40 | window: (C.Window)(window),
41 | }
42 | var surf Surface
43 | if err := vkErr(C.vkCreateXlibSurfaceKHR(x11Funcs.vkCreateXlibSurfaceKHR, inst, &inf, nil, &surf)); err != nil {
44 | return 0, fmt.Errorf("vulkan: vkCreateXlibSurfaceKHR: %w", err)
45 | }
46 | return surf, nil
47 | }
48 |
--------------------------------------------------------------------------------
/io/clipboard/clipboard.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package clipboard
4 |
5 | import (
6 | "io"
7 |
8 | "gioui.org/io/event"
9 | )
10 |
11 | // WriteCmd copies Text to the clipboard.
12 | type WriteCmd struct {
13 | Type string
14 | Data io.ReadCloser
15 | }
16 |
17 | // ReadCmd requests the text of the clipboard, delivered to
18 | // the handler through an [io/transfer.DataEvent].
19 | type ReadCmd struct {
20 | Tag event.Tag
21 | }
22 |
23 | func (WriteCmd) ImplementsCommand() {}
24 | func (ReadCmd) ImplementsCommand() {}
25 |
--------------------------------------------------------------------------------
/io/event/event.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package event contains types for event handling.
4 | package event
5 |
6 | import (
7 | "gioui.org/internal/ops"
8 | "gioui.org/op"
9 | )
10 |
11 | // Tag is the stable identifier for an event handler.
12 | // For a handler h, the tag is typically &h.
13 | type Tag any
14 |
15 | // Event is the marker interface for events.
16 | type Event interface {
17 | ImplementsEvent()
18 | }
19 |
20 | // Filter represents a filter for [Event] types.
21 | type Filter interface {
22 | ImplementsFilter()
23 | }
24 |
25 | // Op declares a tag for input routing at the current transformation
26 | // and clip area hierarchy. It panics if tag is nil.
27 | func Op(o *op.Ops, tag Tag) {
28 | if tag == nil {
29 | panic("Tag must be non-nil")
30 | }
31 | data := ops.Write1(&o.Internal, ops.TypeInputLen, tag)
32 | data[0] = byte(ops.TypeInput)
33 | }
34 |
--------------------------------------------------------------------------------
/io/input/clipboard.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package input
4 |
5 | import (
6 | "io"
7 | "slices"
8 |
9 | "gioui.org/io/clipboard"
10 | "gioui.org/io/event"
11 | )
12 |
13 | // clipboardState contains the state for clipboard event routing.
14 | type clipboardState struct {
15 | receivers []event.Tag
16 | }
17 |
18 | type clipboardQueue struct {
19 | // request avoid read clipboard every frame while waiting.
20 | requested bool
21 | mime string
22 | text []byte
23 | }
24 |
25 | // WriteClipboard returns the most recent data to be copied
26 | // to the clipboard, if any.
27 | func (q *clipboardQueue) WriteClipboard() (mime string, content []byte, ok bool) {
28 | if q.text == nil {
29 | return "", nil, false
30 | }
31 | content = q.text
32 | q.text = nil
33 | return q.mime, content, true
34 | }
35 |
36 | // ClipboardRequested reports if any new handler is waiting
37 | // to read the clipboard.
38 | func (q *clipboardQueue) ClipboardRequested(state clipboardState) bool {
39 | req := len(state.receivers) > 0 && q.requested
40 | q.requested = false
41 | return req
42 | }
43 |
44 | func (q *clipboardQueue) Push(state clipboardState, e event.Event) (clipboardState, []taggedEvent) {
45 | var evts []taggedEvent
46 | for _, r := range state.receivers {
47 | evts = append(evts, taggedEvent{tag: r, event: e})
48 | }
49 | state.receivers = nil
50 | return state, evts
51 | }
52 |
53 | func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
54 | defer req.Data.Close()
55 | content, err := io.ReadAll(req.Data)
56 | if err != nil {
57 | return
58 | }
59 | q.mime = req.Type
60 | q.text = content
61 | }
62 |
63 | func (q *clipboardQueue) ProcessReadClipboard(state clipboardState, tag event.Tag) clipboardState {
64 | if slices.Contains(state.receivers, tag) {
65 | return state
66 | }
67 | n := len(state.receivers)
68 | state.receivers = append(state.receivers[:n:n], tag)
69 | q.requested = true
70 | return state
71 | }
72 |
--------------------------------------------------------------------------------
/io/input/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package input implements input routing and tracking of interface
5 | state for a window.
6 |
7 | The [Source] is the interface between the window and the widgets
8 | of a user interface and is exposed by [gioui.org/app.FrameEvent]
9 | received from windows.
10 |
11 | The [Router] is used by [gioui.org/app.Window] to track window state and route
12 | events from the platform to event handlers. It is otherwise only
13 | useful for using Gio with external window implementations.
14 | */
15 | package input
16 |
--------------------------------------------------------------------------------
/io/input/router_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package input
4 |
5 | import (
6 | "testing"
7 |
8 | "gioui.org/io/pointer"
9 | "gioui.org/op"
10 | )
11 |
12 | func TestNoFilterAllocs(t *testing.T) {
13 | b := testing.Benchmark(func(b *testing.B) {
14 | var r Router
15 | s := r.Source()
16 | b.ReportAllocs()
17 | b.ResetTimer()
18 | for b.Loop() {
19 | s.Event(pointer.Filter{})
20 | }
21 | })
22 | if allocs := b.AllocsPerOp(); allocs != 0 {
23 | t.Fatalf("expected 0 AllocsPerOp, got %d", allocs)
24 | }
25 | }
26 |
27 | func TestRouterWakeup(t *testing.T) {
28 | r := new(Router)
29 | r.Source().Execute(op.InvalidateCmd{})
30 | r.Frame(new(op.Ops))
31 | if _, wake := r.WakeupTime(); !wake {
32 | t.Errorf("InvalidateCmd did not trigger a redraw")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/io/key/mod.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !darwin
4 | // +build !darwin
5 |
6 | package key
7 |
8 | // ModShortcut is the platform's shortcut modifier, usually the ctrl
9 | // modifier. On Apple platforms it is the cmd key.
10 | const ModShortcut = ModCtrl
11 |
12 | // ModShortcutAlt is the platform's alternative shortcut modifier,
13 | // usually the ctrl modifier. On Apple platforms it is the alt modifier.
14 | const ModShortcutAlt = ModCtrl
15 |
--------------------------------------------------------------------------------
/io/key/mod_darwin.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package key
4 |
5 | // ModShortcut is the platform's shortcut modifier, usually the ctrl
6 | // modifier. On Apple platforms it is the cmd key.
7 | const ModShortcut = ModCommand
8 |
9 | // ModShortcut is the platform's alternative shortcut modifier,
10 | // usually the ctrl modifier. On Apple platforms it is the alt modifier.
11 | const ModShortcutAlt = ModAlt
12 |
--------------------------------------------------------------------------------
/io/pointer/pointer_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package pointer
4 |
5 | import (
6 | "testing"
7 | )
8 |
9 | func TestTypeString(t *testing.T) {
10 | for _, tc := range []struct {
11 | typ Kind
12 | res string
13 | }{
14 | {Cancel, "Cancel"},
15 | {Press, "Press"},
16 | {Release, "Release"},
17 | {Move, "Move"},
18 | {Drag, "Drag"},
19 | {Enter, "Enter"},
20 | {Leave, "Leave"},
21 | {Scroll, "Scroll"},
22 | {Enter | Leave, "Enter|Leave"},
23 | {Press | Release, "Press|Release"},
24 | {Enter | Leave | Press | Release, "Press|Release|Enter|Leave"},
25 | {Move | Scroll, "Move|Scroll"},
26 | } {
27 | t.Run(tc.res, func(t *testing.T) {
28 | if want, got := tc.res, tc.typ.String(); want != got {
29 | t.Errorf("got %q; want %q", got, want)
30 | }
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/io/semantic/semantic.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package semantic provides operations for semantic descriptions of a user
4 | // interface, to facilitate presentation and interaction in external software
5 | // such as screen readers.
6 | //
7 | // Semantic descriptions are organized in a tree, with clip operations as
8 | // nodes. Operations in this package are associated with the current semantic
9 | // node, that is the most recent pushed clip operation.
10 | package semantic
11 |
12 | import (
13 | "gioui.org/internal/ops"
14 | "gioui.org/op"
15 | )
16 |
17 | // LabelOp provides the content of a textual component.
18 | type LabelOp string
19 |
20 | // DescriptionOp describes a component.
21 | type DescriptionOp string
22 |
23 | // ClassOp provides the component class.
24 | type ClassOp int
25 |
26 | const (
27 | Unknown ClassOp = iota
28 | Button
29 | CheckBox
30 | Editor
31 | RadioButton
32 | Switch
33 | )
34 |
35 | // SelectedOp describes the selected state for components that have
36 | // boolean state.
37 | type SelectedOp bool
38 |
39 | // EnabledOp describes the enabled state.
40 | type EnabledOp bool
41 |
42 | func (l LabelOp) Add(o *op.Ops) {
43 | data := ops.Write1String(&o.Internal, ops.TypeSemanticLabelLen, string(l))
44 | data[0] = byte(ops.TypeSemanticLabel)
45 | }
46 |
47 | func (d DescriptionOp) Add(o *op.Ops) {
48 | data := ops.Write1String(&o.Internal, ops.TypeSemanticDescLen, string(d))
49 | data[0] = byte(ops.TypeSemanticDesc)
50 | }
51 |
52 | func (c ClassOp) Add(o *op.Ops) {
53 | data := ops.Write(&o.Internal, ops.TypeSemanticClassLen)
54 | data[0] = byte(ops.TypeSemanticClass)
55 | data[1] = byte(c)
56 | }
57 |
58 | func (s SelectedOp) Add(o *op.Ops) {
59 | data := ops.Write(&o.Internal, ops.TypeSemanticSelectedLen)
60 | data[0] = byte(ops.TypeSemanticSelected)
61 | if s {
62 | data[1] = 1
63 | }
64 | }
65 |
66 | func (e EnabledOp) Add(o *op.Ops) {
67 | data := ops.Write(&o.Internal, ops.TypeSemanticEnabledLen)
68 | data[0] = byte(ops.TypeSemanticEnabled)
69 | if e {
70 | data[1] = 1
71 | }
72 | }
73 |
74 | func (c ClassOp) String() string {
75 | switch c {
76 | case Unknown:
77 | return "Unknown"
78 | case Button:
79 | return "Button"
80 | case CheckBox:
81 | return "CheckBox"
82 | case Editor:
83 | return "Editor"
84 | case RadioButton:
85 | return "RadioButton"
86 | case Switch:
87 | return "Switch"
88 | default:
89 | panic("invalid ClassOp")
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/io/system/decoration.go:
--------------------------------------------------------------------------------
1 | package system
2 |
3 | import (
4 | "strings"
5 |
6 | "gioui.org/internal/ops"
7 | "gioui.org/op"
8 | )
9 |
10 | // ActionAreaOp makes the current clip area available for
11 | // system gestures.
12 | //
13 | // Note: only ActionMove is supported.
14 | type ActionInputOp Action
15 |
16 | // Action is a set of window decoration actions.
17 | type Action uint
18 |
19 | const (
20 | // ActionMinimize minimizes a window.
21 | ActionMinimize Action = 1 << iota
22 | // ActionMaximize maximizes a window.
23 | ActionMaximize
24 | // ActionUnmaximize restores a maximized window.
25 | ActionUnmaximize
26 | // ActionFullscreen makes a window fullscreen.
27 | ActionFullscreen
28 | // ActionRaise requests that the platform bring this window to the top of all open windows.
29 | // Some platforms do not allow this except under certain circumstances, such as when
30 | // a window from the same application already has focus. If the platform does not
31 | // support it, this method will do nothing.
32 | ActionRaise
33 | // ActionCenter centers the window on the screen.
34 | // It is ignored in Fullscreen mode and on Wayland.
35 | ActionCenter
36 | // ActionClose closes a window.
37 | // Only applicable on macOS, Windows, X11 and Wayland.
38 | ActionClose
39 | // ActionMove moves a window directed by the user.
40 | ActionMove
41 | )
42 |
43 | func (op ActionInputOp) Add(o *op.Ops) {
44 | data := ops.Write(&o.Internal, ops.TypeActionInputLen)
45 | data[0] = byte(ops.TypeActionInput)
46 | data[1] = byte(op)
47 | }
48 |
49 | func (a Action) String() string {
50 | var buf strings.Builder
51 | for b := Action(1); a != 0; b <<= 1 {
52 | if a&b != 0 {
53 | if buf.Len() > 0 {
54 | buf.WriteByte('|')
55 | }
56 | buf.WriteString(b.string())
57 | a &^= b
58 | }
59 | }
60 | return buf.String()
61 | }
62 |
63 | func (a Action) string() string {
64 | switch a {
65 | case ActionMinimize:
66 | return "ActionMinimize"
67 | case ActionMaximize:
68 | return "ActionMaximize"
69 | case ActionUnmaximize:
70 | return "ActionUnmaximize"
71 | case ActionClose:
72 | return "ActionClose"
73 | case ActionMove:
74 | return "ActionMove"
75 | }
76 | return ""
77 | }
78 |
--------------------------------------------------------------------------------
/io/system/locale.go:
--------------------------------------------------------------------------------
1 | package system
2 |
3 | // Locale provides language information for the current system.
4 | type Locale struct {
5 | // Language is the BCP-47 tag for the primary language of the system.
6 | Language string
7 | // Direction indicates the primary direction of text and layout
8 | // flow for the system.
9 | Direction TextDirection
10 | }
11 |
12 | const (
13 | axisShift = iota
14 | progressionShift
15 | )
16 |
17 | // TextDirection defines a direction for text flow.
18 | type TextDirection byte
19 |
20 | const (
21 | // LTR is left-to-right text.
22 | LTR TextDirection = TextDirection(Horizontal<> axisShift)
30 | }
31 |
32 | // Progression returns the way that the text flows relative to the origin.
33 | func (d TextDirection) Progression() TextProgression {
34 | return TextProgression((d & (1 << progressionShift)) >> progressionShift)
35 | }
36 |
37 | func (d TextDirection) String() string {
38 | switch d {
39 | case RTL:
40 | return "RTL"
41 | default:
42 | return "LTR"
43 | }
44 | }
45 |
46 | // TextAxis defines the layout axis of text.
47 | type TextAxis byte
48 |
49 | const (
50 | // Horizontal indicates text that flows along the X axis.
51 | Horizontal TextAxis = iota
52 | // Vertical indicates text that flows along the Y axis.
53 | Vertical
54 | )
55 |
56 | // TextProgression indicates how text flows along an axis relative to the
57 | // origin. For these purposes, the origin is defined as the upper-left
58 | // corner of coordinate space.
59 | type TextProgression byte
60 |
61 | const (
62 | // FromOrigin indicates text that flows along its axis away from the
63 | // origin (upper left corner).
64 | FromOrigin TextProgression = iota
65 | // TowardOrigin indicates text that flows along its axis towards the
66 | // origin (upper left corner).
67 | TowardOrigin
68 | )
69 |
--------------------------------------------------------------------------------
/layout/alloc_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | //go:build !race
4 | // +build !race
5 |
6 | package layout
7 |
8 | import (
9 | "image"
10 | "testing"
11 |
12 | "gioui.org/op"
13 | )
14 |
15 | func TestStackAllocs(t *testing.T) {
16 | var ops op.Ops
17 | allocs := testing.AllocsPerRun(1, func() {
18 | ops.Reset()
19 | gtx := Context{
20 | Ops: &ops,
21 | }
22 | Stack{}.Layout(gtx,
23 | Stacked(func(gtx Context) Dimensions {
24 | return Dimensions{Size: image.Point{X: 50, Y: 50}}
25 | }),
26 | )
27 | })
28 | if allocs != 0 {
29 | t.Errorf("expected no allocs, got %f", allocs)
30 | }
31 | }
32 |
33 | func TestFlexAllocs(t *testing.T) {
34 | var ops op.Ops
35 | allocs := testing.AllocsPerRun(1, func() {
36 | ops.Reset()
37 | gtx := Context{
38 | Ops: &ops,
39 | }
40 | Flex{}.Layout(gtx,
41 | Rigid(func(gtx Context) Dimensions {
42 | return Dimensions{Size: image.Point{X: 50, Y: 50}}
43 | }),
44 | )
45 | })
46 | if allocs != 0 {
47 | t.Errorf("expected no allocs, got %f", allocs)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/layout/context.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package layout
4 |
5 | import (
6 | "time"
7 |
8 | "gioui.org/io/input"
9 | "gioui.org/io/system"
10 | "gioui.org/op"
11 | "gioui.org/unit"
12 | )
13 |
14 | // Context carries the state needed by almost all layouts and widgets.
15 | // A zero value Context never returns events, map units to pixels
16 | // with a scale of 1.0, and returns the zero time from Now.
17 | type Context struct {
18 | // Constraints track the constraints for the active widget or
19 | // layout.
20 | Constraints Constraints
21 |
22 | Metric unit.Metric
23 | // Now is the animation time.
24 | Now time.Time
25 |
26 | // Locale provides information on the system's language preferences.
27 | // BUG(whereswaldon): this field is not currently populated automatically.
28 | // Interested users must look up and populate these values manually.
29 | Locale system.Locale
30 |
31 | input.Source
32 | *op.Ops
33 | }
34 |
35 | // Dp converts v to pixels.
36 | func (c Context) Dp(v unit.Dp) int {
37 | return c.Metric.Dp(v)
38 | }
39 |
40 | // Sp converts v to pixels.
41 | func (c Context) Sp(v unit.Sp) int {
42 | return c.Metric.Sp(v)
43 | }
44 |
45 | // Disabled returns a copy of this context that don't deliver any events.
46 | func (c Context) Disabled() Context {
47 | c.Source = c.Source.Disabled()
48 | return c
49 | }
50 |
--------------------------------------------------------------------------------
/layout/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package layout implements layouts common to GUI programs.
5 |
6 | # Constraints and dimensions
7 |
8 | Constraints and dimensions form the interface between layouts and
9 | interface child elements. This package operates on Widgets, functions
10 | that compute Dimensions from a a set of constraints for acceptable
11 | widths and heights. Both the constraints and dimensions are maintained
12 | in an implicit Context to keep the Widget declaration short.
13 |
14 | For example, to add space above a widget:
15 |
16 | var gtx layout.Context
17 |
18 | // Configure a top inset.
19 | inset := layout.Inset{Top: 8, ...}
20 | // Use the inset to lay out a widget.
21 | inset.Layout(gtx, func() {
22 | // Lay out widget and determine its size given the constraints
23 | // in gtx.Constraints.
24 | ...
25 | return layout.Dimensions{...}
26 | })
27 |
28 | Note that the example does not generate any garbage even though the
29 | Inset is transient. Layouts that don't accept user input are designed
30 | to not escape to the heap during their use.
31 |
32 | Layout operations are recursive: a child in a layout operation can
33 | itself be another layout. That way, complex user interfaces can
34 | be created from a few generic layouts.
35 |
36 | This example both aligns and insets a child:
37 |
38 | inset := layout.Inset{...}
39 | inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
40 | align := layout.Alignment(...)
41 | return align.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
42 | return widget.Layout(gtx, ...)
43 | })
44 | })
45 |
46 | More complex layouts such as Stack and Flex lay out multiple children,
47 | and stateful layouts such as List accept user input.
48 | */
49 | package layout
50 |
--------------------------------------------------------------------------------
/layout/stack_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package layout
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "gioui.org/op"
10 | )
11 |
12 | func BenchmarkStack(b *testing.B) {
13 | gtx := Context{
14 | Ops: new(op.Ops),
15 | Constraints: Constraints{
16 | Max: image.Point{X: 100, Y: 100},
17 | },
18 | }
19 | b.ReportAllocs()
20 |
21 | for b.Loop() {
22 | gtx.Ops.Reset()
23 |
24 | Stack{}.Layout(gtx,
25 | Expanded(emptyWidget{
26 | Size: image.Point{X: 60, Y: 60},
27 | }.Layout),
28 | Stacked(emptyWidget{
29 | Size: image.Point{X: 30, Y: 30},
30 | }.Layout),
31 | )
32 | }
33 | }
34 |
35 | func BenchmarkBackground(b *testing.B) {
36 | gtx := Context{
37 | Ops: new(op.Ops),
38 | Constraints: Constraints{
39 | Max: image.Point{X: 100, Y: 100},
40 | },
41 | }
42 | b.ReportAllocs()
43 |
44 | for b.Loop() {
45 | gtx.Ops.Reset()
46 |
47 | Background{}.Layout(gtx,
48 | emptyWidget{
49 | Size: image.Point{X: 60, Y: 60},
50 | }.Layout,
51 | emptyWidget{
52 | Size: image.Point{X: 30, Y: 30},
53 | }.Layout,
54 | )
55 | }
56 | }
57 |
58 | type emptyWidget struct {
59 | Size image.Point
60 | }
61 |
62 | func (w emptyWidget) Layout(gtx Context) Dimensions {
63 | return Dimensions{Size: w.Size}
64 | }
65 |
--------------------------------------------------------------------------------
/op/clip/clip_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package clip_test
4 |
5 | import (
6 | "image/color"
7 | "math"
8 | "testing"
9 |
10 | "gioui.org/f32"
11 | "gioui.org/gpu/headless"
12 | "gioui.org/op"
13 | "gioui.org/op/clip"
14 | "gioui.org/op/paint"
15 | )
16 |
17 | func TestPathOutline(t *testing.T) {
18 | t.Run("closed path", func(t *testing.T) {
19 | defer func() {
20 | if err := recover(); err != nil {
21 | t.Error("Outline of a closed path did panic")
22 | }
23 | }()
24 | var p clip.Path
25 | p.Begin(new(op.Ops))
26 | p.MoveTo(f32.Pt(300, 200))
27 | p.LineTo(f32.Pt(150, 200))
28 | p.MoveTo(f32.Pt(150, 200))
29 | p.ArcTo(f32.Pt(300, 200), f32.Pt(300, 200), 3*math.Pi/4)
30 | p.LineTo(f32.Pt(300, 200))
31 | p.Close()
32 | clip.Outline{Path: p.End()}.Op()
33 | })
34 | }
35 |
36 | func TestPathBegin(t *testing.T) {
37 | ops := new(op.Ops)
38 | var p clip.Path
39 | p.Begin(ops)
40 | p.LineTo(f32.Pt(10, 10))
41 | p.Close()
42 | stack := clip.Outline{Path: p.End()}.Op().Push(ops)
43 | paint.Fill(ops, color.NRGBA{A: 255})
44 | stack.Pop()
45 | w := newWindow(t, 100, 100)
46 | if w == nil {
47 | return
48 | }
49 | // The following should not panic.
50 | _ = w.Frame(ops)
51 | }
52 |
53 | func TestTransformChecks(t *testing.T) {
54 | defer func() {
55 | if err := recover(); err == nil {
56 | t.Error("cross-macro Pop didn't panic")
57 | }
58 | }()
59 | var ops op.Ops
60 | st := clip.Op{}.Push(&ops)
61 | op.Record(&ops)
62 | st.Pop()
63 | }
64 |
65 | func TestEmptyPath(t *testing.T) {
66 | var ops op.Ops
67 | p := clip.Path{}
68 | p.Begin(&ops)
69 | defer clip.Stroke{
70 | Path: p.End(),
71 | Width: 3,
72 | }.Op().Push(&ops).Pop()
73 | }
74 |
75 | func newWindow(t testing.TB, width, height int) *headless.Window {
76 | w, err := headless.NewWindow(width, height)
77 | if err != nil {
78 | t.Skipf("failed to create headless window, skipping: %v", err)
79 | }
80 | return w
81 | }
82 |
--------------------------------------------------------------------------------
/op/clip/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package clip provides operations for defining areas that applies to operations
5 | such as paints and pointer handlers.
6 |
7 | The current clip is initially the infinite set. Pushing an Op sets the clip
8 | to the intersection of the current clip and pushed clip area. Popping the
9 | area restores the clip to its state before pushing.
10 |
11 | General clipping areas are constructed with Path. Common cases such as
12 | rectangular clip areas also exist as convenient constructors.
13 | */
14 | package clip
15 |
--------------------------------------------------------------------------------
/op/clip/shapes_test.go:
--------------------------------------------------------------------------------
1 | package clip_test
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | "testing"
7 |
8 | "gioui.org/op"
9 | "gioui.org/op/clip"
10 | "gioui.org/op/paint"
11 | )
12 |
13 | func TestZeroEllipse(t *testing.T) {
14 | p := image.Pt(1.0, 2.0)
15 | e := clip.Ellipse{Min: p, Max: p}
16 | ops := new(op.Ops)
17 | paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, e.Op(ops))
18 | }
19 |
--------------------------------------------------------------------------------
/op/op_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package op
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "gioui.org/internal/ops"
10 | )
11 |
12 | func TestTransformChecks(t *testing.T) {
13 | defer func() {
14 | if err := recover(); err == nil {
15 | t.Error("cross-macro Pop didn't panic")
16 | }
17 | }()
18 | var ops Ops
19 | trans := Offset(image.Point{}).Push(&ops)
20 | Record(&ops)
21 | trans.Pop()
22 | }
23 |
24 | func TestIncompleteMacroReader(t *testing.T) {
25 | var o Ops
26 | // Record, but don't Stop it.
27 | Record(&o)
28 | Offset(image.Point{}).Push(&o)
29 |
30 | var r ops.Reader
31 |
32 | r.Reset(&o.Internal)
33 | if _, more := r.Decode(); more {
34 | t.Error("decoded an operation from a semantically empty Ops")
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/op/paint/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package paint provides drawing operations for 2D graphics.
5 |
6 | The PaintOp operation fills the current clip with the current brush, taking the
7 | current transformation into account. Drawing outside the current clip area is
8 | ignored.
9 |
10 | The current brush is set by either a ColorOp for a constant color, or
11 | ImageOp for an image, or LinearGradientOp for gradients.
12 |
13 | All color.NRGBA values are in the sRGB color space.
14 | */
15 | package paint
16 |
--------------------------------------------------------------------------------
/text/lru_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package text
4 |
5 | import (
6 | "strconv"
7 | "testing"
8 |
9 | "gioui.org/op/clip"
10 | )
11 |
12 | func TestLayoutLRU(t *testing.T) {
13 | c := new(layoutCache)
14 | put := func(i int) {
15 | c.Put(layoutKey{str: strconv.Itoa(i)}, document{})
16 | }
17 | get := func(i int) bool {
18 | _, ok := c.Get(layoutKey{str: strconv.Itoa(i)})
19 | return ok
20 | }
21 | testLRU(t, put, get)
22 | }
23 |
24 | func TestPathLRU(t *testing.T) {
25 | c := new(pathCache)
26 | shaped := []Glyph{{ID: 1}}
27 | put := func(i int) {
28 | c.Put(uint64(i), shaped, clip.PathSpec{})
29 | }
30 | get := func(i int) bool {
31 | _, ok := c.Get(uint64(i), shaped)
32 | return ok
33 | }
34 | testLRU(t, put, get)
35 | }
36 |
37 | func testLRU(t *testing.T, put func(i int), get func(i int) bool) {
38 | for i := range maxSize {
39 | put(i)
40 | }
41 | for i := range maxSize {
42 | if !get(i) {
43 | t.Fatalf("key %d was evicted", i)
44 | }
45 | }
46 | put(maxSize)
47 | for i := 1; i < maxSize+1; i++ {
48 | if !get(i) {
49 | t.Fatalf("key %d was evicted", i)
50 | }
51 | }
52 | if i := 0; get(i) {
53 | t.Fatalf("key %d was not evicted", i)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/2a7730fcbcc3550718b37415eb6104cf09159c7d756cc3530ccaa9007c0a2c06:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\x1d")
3 | bool(true)
4 | bool(false)
5 | byte('\x1c')
6 | uint16(227)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/3fc3ee939f0df44719bee36a67df3aa3a562e2f2f1cfb665a650f60e7e0ab4e6:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("0")
3 | bool(true)
4 | bool(false)
5 | uint8(27)
6 | uint16(200)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/40894369fe6a0c11:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\n")
3 | bool(false)
4 | bool(true)
5 | byte('±')
6 | uint16(0)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/4197f52af4459898:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\n")
3 | bool(false)
4 | bool(false)
5 | byte('±')
6 | uint16(0)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/594e4fda2e3462061d50b6a74279d7e3e486d8ac211e45049cfa4833257ef236:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\u2029")
3 | bool(false)
4 | bool(false)
5 | byte('*')
6 | uint16(72)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/6b452fd81f16c000dbe525077b6f4ba91b040119d82d55991d568014f4ba671e:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("Aͮ000000000000000")
3 | bool(false)
4 | bool(false)
5 | byte('\u0087')
6 | uint16(111)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/6b5a1e9cd750a9aa8dafe11bc74e2377c0664f64bbf2ac8b1cdd0ce9f55461b3:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\x1e")
3 | bool(true)
4 | bool(false)
5 | byte('\n')
6 | uint16(254)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/940ce2b5ed93df01:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("000000000000000 00000000 ٰ00000")
3 | bool(true)
4 | bool(false)
5 | byte('\n')
6 | uint16(121)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/be56090b98f3c84da6ff9e857d5487d1a89309f7312ccde5ff8b94fcd29521eb:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\r")
3 | bool(false)
4 | bool(false)
5 | byte('T')
6 | uint16(200)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/dda958d1b1bb9df71f81c575cb8900f97fc1c20e19de09fc0ffca8ff0c8d9e5a:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\u0085")
3 | bool(true)
4 | bool(false)
5 | byte('\x10')
6 | uint16(271)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/de31ec6b1ac7797daefecf39baaace51fa4348bed0e3b662dd50aa341f67d10c:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("0")
3 | bool(false)
4 | bool(false)
5 | byte('\x00')
6 | uint16(142)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/e79034d6c7a6ce74d7a689f4ea99e50a6ecd0d4134e3a77234172d13787ac4ea:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\n")
3 | bool(true)
4 | bool(false)
5 | byte('\t')
6 | uint16(200)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/f1f0611baadc20469863e483f58a3ca4eba895087316b31e598f0b05c978d87c:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("ع0 ׂ0")
3 | bool(false)
4 | bool(false)
5 | byte('\u0098')
6 | uint16(198)
7 |
--------------------------------------------------------------------------------
/text/testdata/fuzz/FuzzLayout/f2ebea678c72f6c394d7f860599b281593098b0aad864231d756c3e9699029c1:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\x1c")
3 | bool(true)
4 | bool(false)
5 | byte('\u009c')
6 | uint16(200)
7 |
--------------------------------------------------------------------------------
/text/text.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package text
4 |
5 | import (
6 | "fmt"
7 |
8 | "gioui.org/io/system"
9 | "golang.org/x/image/math/fixed"
10 | )
11 |
12 | type Alignment uint8
13 |
14 | const (
15 | Start Alignment = iota
16 | End
17 | Middle
18 | )
19 |
20 | func (a Alignment) String() string {
21 | switch a {
22 | case Start:
23 | return "Start"
24 | case End:
25 | return "End"
26 | case Middle:
27 | return "Middle"
28 | default:
29 | panic("invalid Alignment")
30 | }
31 | }
32 |
33 | // Align returns the x offset that should be applied to text with width so that it
34 | // appears correctly aligned within a space of size maxWidth and with the primary
35 | // text direction dir.
36 | func (a Alignment) Align(dir system.TextDirection, width fixed.Int26_6, maxWidth int) fixed.Int26_6 {
37 | mw := fixed.I(maxWidth)
38 | if dir.Progression() == system.TowardOrigin {
39 | switch a {
40 | case Start:
41 | a = End
42 | case End:
43 | a = Start
44 | }
45 | }
46 | switch a {
47 | case Middle:
48 | return (mw - width) / 2
49 | case End:
50 | return (mw - width)
51 | case Start:
52 | return 0
53 | default:
54 | panic(fmt.Errorf("unknown alignment %v", a))
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/unit/unit.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | /*
4 | Package unit implements device independent units.
5 |
6 | Device independent pixel, or dp, is the unit for sizes independent of
7 | the underlying display device.
8 |
9 | Scaled pixels, or sp, is the unit for text sizes. An sp is like dp with
10 | text scaling applied.
11 |
12 | Finally, pixels, or px, is the unit for display dependent pixels. Their
13 | size vary between platforms and displays.
14 |
15 | To maintain a constant visual size across platforms and displays, always
16 | use dps or sps to define user interfaces. Only use pixels for derived
17 | values.
18 | */
19 | package unit
20 |
21 | import (
22 | "math"
23 | )
24 |
25 | // Metric converts Values to device-dependent pixels, px. The zero
26 | // value represents a 1-to-1 scale from dp, sp to pixels.
27 | type Metric struct {
28 | // PxPerDp is the device-dependent pixels per dp.
29 | PxPerDp float32
30 | // PxPerSp is the device-dependent pixels per sp.
31 | PxPerSp float32
32 | }
33 |
34 | type (
35 | // Dp represents device independent pixels. 1 dp will
36 | // have the same apparent size across platforms and
37 | // display resolutions.
38 | Dp float32
39 | // Sp is like UnitDp but for font sizes.
40 | Sp float32
41 | )
42 |
43 | // Dp converts v to pixels, rounded to the nearest integer value.
44 | func (c Metric) Dp(v Dp) int {
45 | return int(math.Round(float64(nonZero(c.PxPerDp)) * float64(v)))
46 | }
47 |
48 | // Sp converts v to pixels, rounded to the nearest integer value.
49 | func (c Metric) Sp(v Sp) int {
50 | return int(math.Round(float64(nonZero(c.PxPerSp)) * float64(v)))
51 | }
52 |
53 | // DpToSp converts v dp to sp.
54 | func (c Metric) DpToSp(v Dp) Sp {
55 | return Sp(float32(v) * nonZero(c.PxPerDp) / nonZero(c.PxPerSp))
56 | }
57 |
58 | // SpToDp converts v sp to dp.
59 | func (c Metric) SpToDp(v Sp) Dp {
60 | return Dp(float32(v) * nonZero(c.PxPerSp) / nonZero(c.PxPerDp))
61 | }
62 |
63 | // PxToSp converts v px to sp.
64 | func (c Metric) PxToSp(v int) Sp {
65 | return Sp(float32(v) / nonZero(c.PxPerSp))
66 | }
67 |
68 | // PxToDp converts v px to dp.
69 | func (c Metric) PxToDp(v int) Dp {
70 | return Dp(float32(v) / nonZero(c.PxPerDp))
71 | }
72 |
73 | func nonZero(v float32) float32 {
74 | if v == 0. {
75 | return 1
76 | }
77 | return v
78 | }
79 |
--------------------------------------------------------------------------------
/unit/unit_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package unit_test
4 |
5 | import (
6 | "testing"
7 |
8 | "gioui.org/unit"
9 | )
10 |
11 | func TestMetric_DpToSp(t *testing.T) {
12 | m := unit.Metric{
13 | PxPerDp: 2,
14 | PxPerSp: 3,
15 | }
16 |
17 | {
18 | exp := m.Dp(5)
19 | got := m.Sp(m.DpToSp(5))
20 | if got != exp {
21 | t.Errorf("DpToSp conversion mismatch %v != %v", exp, got)
22 | }
23 | }
24 |
25 | {
26 | exp := m.Sp(5)
27 | got := m.Dp(m.SpToDp(5))
28 | if got != exp {
29 | t.Errorf("SpToDp conversion mismatch %v != %v", exp, got)
30 | }
31 | }
32 |
33 | {
34 | exp := unit.Dp(5)
35 | got := m.PxToDp(m.Dp(5))
36 | if got != exp {
37 | t.Errorf("PxToDp conversion mismatch %v != %v", exp, got)
38 | }
39 | }
40 |
41 | {
42 | exp := unit.Sp(5)
43 | got := m.PxToSp(m.Sp(5))
44 | if got != exp {
45 | t.Errorf("PxToSp conversion mismatch %v != %v", exp, got)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/widget/bool.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "gioui.org/io/semantic"
7 | "gioui.org/layout"
8 | )
9 |
10 | type Bool struct {
11 | Value bool
12 |
13 | clk Clickable
14 | }
15 |
16 | // Update the widget state and report whether Value was changed.
17 | func (b *Bool) Update(gtx layout.Context) bool {
18 | changed := false
19 | for b.clk.clicked(b, gtx) {
20 | b.Value = !b.Value
21 | changed = true
22 | }
23 | return changed
24 | }
25 |
26 | // Hovered reports whether pointer is over the element.
27 | func (b *Bool) Hovered() bool {
28 | return b.clk.Hovered()
29 | }
30 |
31 | // Pressed reports whether pointer is pressing the element.
32 | func (b *Bool) Pressed() bool {
33 | return b.clk.Pressed()
34 | }
35 |
36 | func (b *Bool) History() []Press {
37 | return b.clk.History()
38 | }
39 |
40 | func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
41 | b.Update(gtx)
42 | dims := b.clk.layout(b, gtx, func(gtx layout.Context) layout.Dimensions {
43 | semantic.SelectedOp(b.Value).Add(gtx.Ops)
44 | semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
45 | return w(gtx)
46 | })
47 | return dims
48 | }
49 |
--------------------------------------------------------------------------------
/widget/border.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 | "image/color"
8 |
9 | "gioui.org/layout"
10 | "gioui.org/op/clip"
11 | "gioui.org/op/paint"
12 | "gioui.org/unit"
13 | )
14 |
15 | // Border lays out a widget and draws a border inside it.
16 | type Border struct {
17 | Color color.NRGBA
18 | CornerRadius unit.Dp
19 | Width unit.Dp
20 | }
21 |
22 | func (b Border) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
23 | dims := w(gtx)
24 | sz := dims.Size
25 |
26 | rr := gtx.Dp(b.CornerRadius)
27 | width := gtx.Dp(b.Width)
28 | whalf := (width + 1) / 2
29 | sz.X -= whalf * 2
30 | sz.Y -= whalf * 2
31 |
32 | r := image.Rectangle{Max: sz}
33 | r = r.Add(image.Point{X: whalf, Y: whalf})
34 |
35 | paint.FillShape(gtx.Ops,
36 | b.Color,
37 | clip.Stroke{
38 | Path: clip.UniformRRect(r, rr).Path(gtx.Ops),
39 | Width: float32(width),
40 | }.Op(),
41 | )
42 |
43 | return dims
44 | }
45 |
--------------------------------------------------------------------------------
/widget/button_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget_test
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "gioui.org/io/input"
10 | "gioui.org/io/key"
11 | "gioui.org/layout"
12 | "gioui.org/op"
13 | "gioui.org/widget"
14 | )
15 |
16 | func TestClickable(t *testing.T) {
17 | var (
18 | r input.Router
19 | b1 widget.Clickable
20 | b2 widget.Clickable
21 | )
22 | gtx := layout.Context{
23 | Ops: new(op.Ops),
24 | Source: r.Source(),
25 | }
26 | layout := func() {
27 | b1.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
28 | return layout.Dimensions{Size: image.Pt(100, 100)}
29 | })
30 | // buttons are on top of each other but we only use focus and keyevents, so this is fine
31 | b2.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
32 | return layout.Dimensions{Size: image.Pt(100, 100)}
33 | })
34 | }
35 | frame := func() {
36 | gtx.Reset()
37 | layout()
38 | r.Frame(gtx.Ops)
39 | }
40 | gtx.Execute(key.FocusCmd{Tag: &b1})
41 | frame()
42 | if !gtx.Focused(&b1) {
43 | t.Error("button 1 did not gain focus")
44 | }
45 | if gtx.Focused(&b2) {
46 | t.Error("button 2 should not have focus")
47 | }
48 | r.Queue(
49 | key.Event{
50 | Name: key.NameReturn,
51 | State: key.Press,
52 | },
53 | key.Event{
54 | Name: key.NameReturn,
55 | State: key.Release,
56 | },
57 | )
58 | if !b1.Clicked(gtx) {
59 | t.Error("button 1 did not get clicked when it got return press & release")
60 | }
61 | if b2.Clicked(gtx) {
62 | t.Error("button 2 got clicked when it did not have focus")
63 | }
64 | r.Queue(
65 | key.Event{
66 | Name: key.NameReturn,
67 | State: key.Press,
68 | },
69 | )
70 | if b1.Clicked(gtx) {
71 | t.Error("button 1 got clicked, even if it only got return press")
72 | }
73 | frame()
74 | gtx.Execute(key.FocusCmd{Tag: &b2})
75 | frame()
76 | if gtx.Focused(&b1) {
77 | t.Error("button 1 should not have focus")
78 | }
79 | if !gtx.Focused(&b2) {
80 | t.Error("button 2 did not gain focus")
81 | }
82 | r.Queue(
83 | key.Event{
84 | Name: key.NameReturn,
85 | State: key.Release,
86 | },
87 | )
88 | if b1.Clicked(gtx) {
89 | t.Error("button 1 got clicked, even if it had lost focus")
90 | }
91 | if b2.Clicked(gtx) {
92 | t.Error("button 2 should not have been clicked, as it only got return release")
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/widget/decorations.go:
--------------------------------------------------------------------------------
1 | package widget
2 |
3 | import (
4 | "fmt"
5 | "math/bits"
6 |
7 | "gioui.org/io/system"
8 | "gioui.org/layout"
9 | "gioui.org/op/clip"
10 | )
11 |
12 | // Decorations handles the states of window decorations.
13 | type Decorations struct {
14 | // Maximized controls the look and behaviour of the maximize
15 | // button. It is the user's responsibility to set Maximized
16 | // according to the window state reported through [app.ConfigEvent].
17 | Maximized bool
18 | clicks map[int]*Clickable
19 | }
20 |
21 | // LayoutMove lays out the widget that makes a window movable.
22 | func (d *Decorations) LayoutMove(gtx layout.Context, w layout.Widget) layout.Dimensions {
23 | dims := w(gtx)
24 | defer clip.Rect{Max: dims.Size}.Push(gtx.Ops).Pop()
25 | system.ActionInputOp(system.ActionMove).Add(gtx.Ops)
26 | return dims
27 | }
28 |
29 | // Clickable returns the clickable for the given single action.
30 | func (d *Decorations) Clickable(action system.Action) *Clickable {
31 | if bits.OnesCount(uint(action)) != 1 {
32 | panic(fmt.Errorf("not a single action"))
33 | }
34 | idx := bits.TrailingZeros(uint(action))
35 | click, found := d.clicks[idx]
36 | if !found {
37 | click = new(Clickable)
38 | if d.clicks == nil {
39 | d.clicks = make(map[int]*Clickable)
40 | }
41 | d.clicks[idx] = click
42 | }
43 | return click
44 | }
45 |
46 | // Update the state and return the set of actions activated by the user.
47 | func (d *Decorations) Update(gtx layout.Context) system.Action {
48 | var actions system.Action
49 | for idx, clk := range d.clicks {
50 | if !clk.Clicked(gtx) {
51 | continue
52 | }
53 | action := system.Action(1 << idx)
54 | switch {
55 | case action == system.ActionMaximize && d.Maximized:
56 | action = system.ActionUnmaximize
57 | case action == system.ActionUnmaximize && !d.Maximized:
58 | action = system.ActionMaximize
59 | }
60 | actions |= action
61 | }
62 | return actions
63 | }
64 |
--------------------------------------------------------------------------------
/widget/dnd.go:
--------------------------------------------------------------------------------
1 | package widget
2 |
3 | import (
4 | "io"
5 |
6 | "gioui.org/f32"
7 | "gioui.org/gesture"
8 | "gioui.org/io/event"
9 | "gioui.org/io/pointer"
10 | "gioui.org/io/transfer"
11 | "gioui.org/layout"
12 | "gioui.org/op"
13 | "gioui.org/op/clip"
14 | )
15 |
16 | // Draggable makes a widget draggable.
17 | type Draggable struct {
18 | // Type contains the MIME type and matches transfer.SourceOp.
19 | Type string
20 |
21 | drag gesture.Drag
22 | click f32.Point
23 | pos f32.Point
24 | }
25 |
26 | func (d *Draggable) Layout(gtx layout.Context, w, drag layout.Widget) layout.Dimensions {
27 | if !gtx.Enabled() {
28 | return w(gtx)
29 | }
30 | dims := w(gtx)
31 |
32 | stack := clip.Rect{Max: dims.Size}.Push(gtx.Ops)
33 | d.drag.Add(gtx.Ops)
34 | event.Op(gtx.Ops, d)
35 | stack.Pop()
36 |
37 | if drag != nil && d.drag.Pressed() {
38 | rec := op.Record(gtx.Ops)
39 | op.Offset(d.pos.Round()).Add(gtx.Ops)
40 | drag(gtx)
41 | op.Defer(gtx.Ops, rec.Stop())
42 | }
43 |
44 | return dims
45 | }
46 |
47 | // Dragging returns whether d is being dragged.
48 | func (d *Draggable) Dragging() bool {
49 | return d.drag.Dragging()
50 | }
51 |
52 | // Update the draggable and returns the MIME type for which the Draggable was
53 | // requested to offer data, if any
54 | func (d *Draggable) Update(gtx layout.Context) (mime string, requested bool) {
55 | pos := d.pos
56 | for {
57 | ev, ok := d.drag.Update(gtx.Metric, gtx.Source, gesture.Both)
58 | if !ok {
59 | break
60 | }
61 | switch ev.Kind {
62 | case pointer.Press:
63 | d.click = ev.Position
64 | pos = f32.Point{}
65 | case pointer.Drag, pointer.Release:
66 | pos = ev.Position.Sub(d.click)
67 | }
68 | }
69 | d.pos = pos
70 |
71 | for {
72 | e, ok := gtx.Event(transfer.SourceFilter{Target: d, Type: d.Type})
73 | if !ok {
74 | break
75 | }
76 | if e, ok := e.(transfer.RequestEvent); ok {
77 | return e.Type, true
78 | }
79 | }
80 | return "", false
81 | }
82 |
83 | // Offer the data ready for a drop. Must be called after being Requested.
84 | // The mime must be one in the requested list.
85 | func (d *Draggable) Offer(gtx layout.Context, mime string, data io.ReadCloser) {
86 | gtx.Execute(transfer.OfferCmd{Tag: d, Type: mime, Data: data})
87 | }
88 |
89 | // Pos returns the drag position relative to its initial click position.
90 | func (d *Draggable) Pos() f32.Point {
91 | return d.pos
92 | }
93 |
--------------------------------------------------------------------------------
/widget/dnd_test.go:
--------------------------------------------------------------------------------
1 | package widget
2 |
3 | import (
4 | "image"
5 | "testing"
6 |
7 | "gioui.org/f32"
8 | "gioui.org/io/event"
9 | "gioui.org/io/input"
10 | "gioui.org/io/pointer"
11 | "gioui.org/io/transfer"
12 | "gioui.org/layout"
13 | "gioui.org/op"
14 | "gioui.org/op/clip"
15 | )
16 |
17 | func TestDraggable(t *testing.T) {
18 | var r input.Router
19 | gtx := layout.Context{
20 | Constraints: layout.Exact(image.Pt(100, 100)),
21 | Source: r.Source(),
22 | Ops: new(op.Ops),
23 | }
24 |
25 | drag := &Draggable{
26 | Type: "file",
27 | }
28 | tgt := new(int)
29 | defer pointer.PassOp{}.Push(gtx.Ops).Pop()
30 | dims := drag.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
31 | return layout.Dimensions{Size: gtx.Constraints.Min}
32 | }, nil)
33 | stack := clip.Rect{Max: dims.Size}.Push(gtx.Ops)
34 | event.Op(gtx.Ops, tgt)
35 | stack.Pop()
36 |
37 | drag.Update(gtx)
38 | r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
39 | r.Frame(gtx.Ops)
40 | r.Queue(
41 | pointer.Event{
42 | Position: f32.Pt(10, 10),
43 | Kind: pointer.Press,
44 | },
45 | pointer.Event{
46 | Position: f32.Pt(20, 10),
47 | Kind: pointer.Move,
48 | },
49 | pointer.Event{
50 | Position: f32.Pt(20, 10),
51 | Kind: pointer.Release,
52 | },
53 | )
54 | ofr := &offer{data: "hello"}
55 | drag.Update(gtx)
56 | r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
57 | drag.Offer(gtx, "file", ofr)
58 |
59 | e, ok := r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
60 | if !ok {
61 | t.Fatalf("expected event")
62 | }
63 | ev := e.(transfer.DataEvent)
64 | if got, want := ev.Type, "file"; got != want {
65 | t.Errorf("expected %v; got %v", got, want)
66 | }
67 | if ofr.closed {
68 | t.Error("offer closed prematurely")
69 | }
70 | e, ok = r.Event(transfer.TargetFilter{Target: tgt, Type: drag.Type})
71 | if !ok {
72 | t.Fatalf("expected event")
73 | }
74 | if _, ok := e.(transfer.CancelEvent); !ok {
75 | t.Fatalf("expected transfer.CancelEvent event")
76 | }
77 | r.Frame(gtx.Ops)
78 | if !ofr.closed {
79 | t.Error("offer was not closed")
80 | }
81 | }
82 |
83 | // offer satisfies io.ReadCloser for use in data transfers.
84 | type offer struct {
85 | data string
86 | closed bool
87 | }
88 |
89 | func (*offer) Read([]byte) (int, error) { return 0, nil }
90 |
91 | func (o *offer) Close() error {
92 | o.closed = true
93 | return nil
94 | }
95 |
--------------------------------------------------------------------------------
/widget/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package widget implements state tracking and event handling of
4 | // common user interface controls. To draw widgets, use a theme
5 | // packages such as package [gioui.org/widget/material].
6 | package widget
7 |
--------------------------------------------------------------------------------
/widget/enum.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "gioui.org/gesture"
7 | "gioui.org/io/event"
8 | "gioui.org/io/key"
9 | "gioui.org/io/pointer"
10 | "gioui.org/io/semantic"
11 | "gioui.org/layout"
12 | "gioui.org/op"
13 | "gioui.org/op/clip"
14 | )
15 |
16 | type Enum struct {
17 | Value string
18 | hovered string
19 | hovering bool
20 |
21 | focus string
22 | focused bool
23 |
24 | keys []*enumKey
25 | }
26 |
27 | type enumKey struct {
28 | key string
29 | click gesture.Click
30 | tag struct{}
31 | }
32 |
33 | func (e *Enum) index(k string) *enumKey {
34 | for _, v := range e.keys {
35 | if v.key == k {
36 | return v
37 | }
38 | }
39 | return nil
40 | }
41 |
42 | // Update the state and report whether Value has changed by user interaction.
43 | func (e *Enum) Update(gtx layout.Context) bool {
44 | if !gtx.Enabled() {
45 | e.focused = false
46 | }
47 | e.hovering = false
48 | changed := false
49 | for _, state := range e.keys {
50 | for {
51 | ev, ok := state.click.Update(gtx.Source)
52 | if !ok {
53 | break
54 | }
55 | switch ev.Kind {
56 | case gesture.KindPress:
57 | if ev.Source == pointer.Mouse {
58 | gtx.Execute(key.FocusCmd{Tag: &state.tag})
59 | }
60 | case gesture.KindClick:
61 | if state.key != e.Value {
62 | e.Value = state.key
63 | changed = true
64 | }
65 | }
66 | }
67 | for {
68 | ev, ok := gtx.Event(
69 | key.FocusFilter{Target: &state.tag},
70 | key.Filter{Focus: &state.tag, Name: key.NameReturn},
71 | key.Filter{Focus: &state.tag, Name: key.NameSpace},
72 | )
73 | if !ok {
74 | break
75 | }
76 | switch ev := ev.(type) {
77 | case key.FocusEvent:
78 | if ev.Focus {
79 | e.focused = true
80 | e.focus = state.key
81 | } else if state.key == e.focus {
82 | e.focused = false
83 | }
84 | case key.Event:
85 | if ev.State != key.Release {
86 | break
87 | }
88 | if ev.Name != key.NameReturn && ev.Name != key.NameSpace {
89 | break
90 | }
91 | if state.key != e.Value {
92 | e.Value = state.key
93 | changed = true
94 | }
95 | }
96 | }
97 | if state.click.Hovered() {
98 | e.hovered = state.key
99 | e.hovering = true
100 | }
101 | }
102 |
103 | return changed
104 | }
105 |
106 | // Hovered returns the key that is highlighted, or false if none are.
107 | func (e *Enum) Hovered() (string, bool) {
108 | return e.hovered, e.hovering
109 | }
110 |
111 | // Focused reports the focused key, or false if no key is focused.
112 | func (e *Enum) Focused() (string, bool) {
113 | return e.focus, e.focused
114 | }
115 |
116 | // Layout adds the event handler for the key k.
117 | func (e *Enum) Layout(gtx layout.Context, k string, content layout.Widget) layout.Dimensions {
118 | e.Update(gtx)
119 | m := op.Record(gtx.Ops)
120 | dims := content(gtx)
121 | c := m.Stop()
122 | defer clip.Rect{Max: dims.Size}.Push(gtx.Ops).Pop()
123 |
124 | state := e.index(k)
125 | if state == nil {
126 | state = &enumKey{
127 | key: k,
128 | }
129 | e.keys = append(e.keys, state)
130 | }
131 | clk := &state.click
132 | clk.Add(gtx.Ops)
133 | event.Op(gtx.Ops, &state.tag)
134 | semantic.SelectedOp(k == e.Value).Add(gtx.Ops)
135 | semantic.EnabledOp(gtx.Enabled()).Add(gtx.Ops)
136 | c.Add(gtx.Ops)
137 |
138 | return dims
139 | }
140 |
--------------------------------------------------------------------------------
/widget/fit.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 |
8 | "gioui.org/f32"
9 | "gioui.org/layout"
10 | )
11 |
12 | // Fit scales a widget to fit and clip to the constraints.
13 | type Fit uint8
14 |
15 | const (
16 | // Unscaled does not alter the scale of a widget.
17 | Unscaled Fit = iota
18 | // Contain scales widget as large as possible without cropping
19 | // and it preserves aspect-ratio.
20 | Contain
21 | // Cover scales the widget to cover the constraint area and
22 | // preserves aspect-ratio.
23 | Cover
24 | // ScaleDown scales the widget smaller without cropping,
25 | // when it exceeds the constraint area.
26 | // It preserves aspect-ratio.
27 | ScaleDown
28 | // Fill stretches the widget to the constraints and does not
29 | // preserve aspect-ratio.
30 | Fill
31 | )
32 |
33 | // scale computes the new dimensions and transformation required to fit dims to cs, given the position.
34 | func (fit Fit) scale(cs layout.Constraints, pos layout.Direction, dims layout.Dimensions) (layout.Dimensions, f32.Affine2D) {
35 | widgetSize := dims.Size
36 |
37 | if fit == Unscaled || dims.Size.X == 0 || dims.Size.Y == 0 {
38 | dims.Size = cs.Constrain(dims.Size)
39 |
40 | offset := pos.Position(widgetSize, dims.Size)
41 | dims.Baseline += offset.Y
42 | return dims, f32.Affine2D{}.Offset(layout.FPt(offset))
43 | }
44 |
45 | scale := f32.Point{
46 | X: float32(cs.Max.X) / float32(dims.Size.X),
47 | Y: float32(cs.Max.Y) / float32(dims.Size.Y),
48 | }
49 |
50 | switch fit {
51 | case Contain:
52 | if scale.Y < scale.X {
53 | scale.X = scale.Y
54 | } else {
55 | scale.Y = scale.X
56 | }
57 | case Cover:
58 | if scale.Y > scale.X {
59 | scale.X = scale.Y
60 | } else {
61 | scale.Y = scale.X
62 | }
63 | case ScaleDown:
64 | if scale.Y < scale.X {
65 | scale.X = scale.Y
66 | } else {
67 | scale.Y = scale.X
68 | }
69 |
70 | // The widget would need to be scaled up, no change needed.
71 | if scale.X >= 1 {
72 | dims.Size = cs.Constrain(dims.Size)
73 |
74 | offset := pos.Position(widgetSize, dims.Size)
75 | dims.Baseline += offset.Y
76 | return dims, f32.Affine2D{}.Offset(layout.FPt(offset))
77 | }
78 | case Fill:
79 | }
80 |
81 | var scaledSize image.Point
82 | scaledSize.X = int(float32(widgetSize.X) * scale.X)
83 | scaledSize.Y = int(float32(widgetSize.Y) * scale.Y)
84 | dims.Size = cs.Constrain(scaledSize)
85 | dims.Baseline = int(float32(dims.Baseline) * scale.Y)
86 |
87 | offset := pos.Position(scaledSize, dims.Size)
88 | trans := f32.Affine2D{}.
89 | Scale(f32.Point{}, scale).
90 | Offset(layout.FPt(offset))
91 |
92 | dims.Baseline += offset.Y
93 |
94 | return dims, trans
95 | }
96 |
--------------------------------------------------------------------------------
/widget/fit_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "gioui.org/f32"
10 | "gioui.org/layout"
11 | )
12 |
13 | func TestFit(t *testing.T) {
14 | type test struct {
15 | Dims image.Point
16 | Scale f32.Point
17 | Result image.Point
18 | }
19 |
20 | fittests := [...][]test{
21 | Unscaled: {
22 | {
23 | Dims: image.Point{0, 0},
24 | Scale: f32.Point{X: 1, Y: 1},
25 | Result: image.Point{X: 0, Y: 0},
26 | }, {
27 | Dims: image.Point{50, 25},
28 | Scale: f32.Point{X: 1, Y: 1},
29 | Result: image.Point{X: 50, Y: 25},
30 | }, {
31 | Dims: image.Point{50, 200},
32 | Scale: f32.Point{X: 1, Y: 1},
33 | Result: image.Point{X: 50, Y: 100},
34 | },
35 | },
36 | Contain: {
37 | {
38 | Dims: image.Point{50, 25},
39 | Scale: f32.Point{X: 2, Y: 2},
40 | Result: image.Point{X: 100, Y: 50},
41 | }, {
42 | Dims: image.Point{50, 200},
43 | Scale: f32.Point{X: 0.5, Y: 0.5},
44 | Result: image.Point{X: 25, Y: 100},
45 | },
46 | },
47 | Cover: {
48 | {
49 | Dims: image.Point{50, 25},
50 | Scale: f32.Point{X: 4, Y: 4},
51 | Result: image.Point{X: 100, Y: 100},
52 | }, {
53 | Dims: image.Point{50, 200},
54 | Scale: f32.Point{X: 2, Y: 2},
55 | Result: image.Point{X: 100, Y: 100},
56 | },
57 | },
58 | ScaleDown: {
59 | {
60 | Dims: image.Point{50, 25},
61 | Scale: f32.Point{X: 1, Y: 1},
62 | Result: image.Point{X: 50, Y: 25},
63 | }, {
64 | Dims: image.Point{50, 200},
65 | Scale: f32.Point{X: 0.5, Y: 0.5},
66 | Result: image.Point{X: 25, Y: 100},
67 | },
68 | },
69 | Fill: {
70 | {
71 | Dims: image.Point{50, 25},
72 | Scale: f32.Point{X: 2, Y: 4},
73 | Result: image.Point{X: 100, Y: 100},
74 | }, {
75 | Dims: image.Point{50, 200},
76 | Scale: f32.Point{X: 2, Y: 0.5},
77 | Result: image.Point{X: 100, Y: 100},
78 | },
79 | },
80 | }
81 |
82 | for fit, tests := range fittests {
83 | fit := Fit(fit)
84 | for i, test := range tests {
85 | cs := layout.Constraints{
86 | Max: image.Point{X: 100, Y: 100},
87 | }
88 | result, trans := fit.scale(cs, layout.NW, layout.Dimensions{Size: test.Dims})
89 | sx, _, _, _, sy, _ := trans.Elems()
90 | if scale := f32.Pt(sx, sy); scale != test.Scale {
91 | t.Errorf("got scale %v expected %v", scale, test.Scale)
92 | }
93 |
94 | if result.Size != test.Result {
95 | t.Errorf("fit %v, #%v: expected %#v, got %#v", fit, i, test.Result, result.Size)
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/widget/float.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 |
8 | "gioui.org/gesture"
9 | "gioui.org/io/pointer"
10 | "gioui.org/layout"
11 | "gioui.org/op/clip"
12 | "gioui.org/unit"
13 | )
14 |
15 | // Float is for selecting a value in a range.
16 | type Float struct {
17 | // Value is the value of the Float, in the [0; 1] range.
18 | Value float32
19 |
20 | drag gesture.Drag
21 | axis layout.Axis
22 | length float32
23 | }
24 |
25 | // Dragging returns whether the value is being interacted with.
26 | func (f *Float) Dragging() bool { return f.drag.Dragging() }
27 |
28 | func (f *Float) Layout(gtx layout.Context, axis layout.Axis, pointerMargin unit.Dp) layout.Dimensions {
29 | f.Update(gtx)
30 | size := gtx.Constraints.Min
31 | f.length = float32(axis.Convert(size).X)
32 | f.axis = axis
33 |
34 | margin := axis.Convert(image.Pt(gtx.Dp(pointerMargin), 0))
35 | rect := image.Rectangle{
36 | Min: margin.Mul(-1),
37 | Max: size.Add(margin),
38 | }
39 | defer clip.Rect(rect).Push(gtx.Ops).Pop()
40 | f.drag.Add(gtx.Ops)
41 |
42 | return layout.Dimensions{Size: size}
43 | }
44 |
45 | // Update the Value according to drag events along the f's main axis.
46 | // The return value reports whether the value was changed.
47 | //
48 | // The range of f is set by the minimum constraints main axis value.
49 | func (f *Float) Update(gtx layout.Context) bool {
50 | changed := false
51 | for {
52 | e, ok := f.drag.Update(gtx.Metric, gtx.Source, gesture.Axis(f.axis))
53 | if !ok {
54 | break
55 | }
56 | if f.length > 0 && (e.Kind == pointer.Press || e.Kind == pointer.Drag) {
57 | pos := e.Position.X
58 | if f.axis == layout.Vertical {
59 | pos = f.length - e.Position.Y
60 | }
61 | f.Value = pos / f.length
62 | if f.Value < 0 {
63 | f.Value = 0
64 | } else if f.Value > 1 {
65 | f.Value = 1
66 | }
67 | changed = true
68 | }
69 | }
70 | return changed
71 | }
72 |
--------------------------------------------------------------------------------
/widget/icon.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 | "image/color"
8 | "image/draw"
9 |
10 | "gioui.org/internal/f32color"
11 | "gioui.org/layout"
12 | "gioui.org/op/clip"
13 | "gioui.org/op/paint"
14 | "gioui.org/unit"
15 |
16 | "golang.org/x/exp/shiny/iconvg"
17 | )
18 |
19 | type Icon struct {
20 | src []byte
21 | // Cached values.
22 | op paint.ImageOp
23 | imgSize int
24 | imgColor color.NRGBA
25 | }
26 |
27 | const defaultIconSize = unit.Dp(24)
28 |
29 | // NewIcon returns a new Icon from IconVG data.
30 | func NewIcon(data []byte) (*Icon, error) {
31 | _, err := iconvg.DecodeMetadata(data)
32 | if err != nil {
33 | return nil, err
34 | }
35 | return &Icon{src: data}, nil
36 | }
37 |
38 | // Layout displays the icon with its size set to the X minimum constraint.
39 | func (ic *Icon) Layout(gtx layout.Context, color color.NRGBA) layout.Dimensions {
40 | sz := gtx.Constraints.Min.X
41 | if sz == 0 {
42 | sz = gtx.Dp(defaultIconSize)
43 | }
44 | size := gtx.Constraints.Constrain(image.Pt(sz, sz))
45 | defer clip.Rect{Max: size}.Push(gtx.Ops).Pop()
46 |
47 | ico := ic.image(size.X, color)
48 | ico.Add(gtx.Ops)
49 | paint.PaintOp{}.Add(gtx.Ops)
50 | return layout.Dimensions{
51 | Size: ico.Size(),
52 | }
53 | }
54 |
55 | func (ic *Icon) image(sz int, color color.NRGBA) paint.ImageOp {
56 | if sz == ic.imgSize && color == ic.imgColor {
57 | return ic.op
58 | }
59 | m, _ := iconvg.DecodeMetadata(ic.src)
60 | dx, dy := m.ViewBox.AspectRatio()
61 | img := image.NewRGBA(image.Rectangle{Max: image.Point{X: sz, Y: int(float32(sz) * dy / dx)}})
62 | var ico iconvg.Rasterizer
63 | ico.SetDstImage(img, img.Bounds(), draw.Src)
64 | m.Palette[0] = f32color.NRGBAToLinearRGBA(color)
65 | iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{
66 | Palette: &m.Palette,
67 | })
68 | ic.op = paint.NewImageOp(img)
69 | ic.imgSize = sz
70 | ic.imgColor = color
71 | return ic.op
72 | }
73 |
--------------------------------------------------------------------------------
/widget/icon_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 | "image/color"
8 | "testing"
9 |
10 | "gioui.org/layout"
11 | "gioui.org/op"
12 |
13 | "golang.org/x/exp/shiny/materialdesign/icons"
14 | )
15 |
16 | func TestIcon_Alpha(t *testing.T) {
17 | icon, err := NewIcon(icons.ToggleCheckBox)
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 |
22 | col := color.NRGBA{B: 0xff, A: 0x40}
23 |
24 | gtx := layout.Context{
25 | Ops: new(op.Ops),
26 | Constraints: layout.Exact(image.Pt(100, 100)),
27 | }
28 |
29 | _ = icon.Layout(gtx, col)
30 | }
31 |
32 | // TestWidgetConstraints tests that widgets returns dimensions within their constraints.
33 | func TestWidgetConstraints(t *testing.T) {
34 | _cs := func(v ...layout.Constraints) []layout.Constraints { return v }
35 | for _, tc := range []struct {
36 | label string
37 | widget layout.Widget
38 | constraints []layout.Constraints
39 | }{
40 | {
41 | label: "Icon",
42 | widget: func(gtx layout.Context) layout.Dimensions {
43 | ic, _ := NewIcon(icons.ToggleCheckBox)
44 | return ic.Layout(gtx, color.NRGBA{A: 0xff})
45 | },
46 | constraints: _cs(
47 | layout.Constraints{
48 | Min: image.Pt(20, 0),
49 | Max: image.Pt(100, 100),
50 | },
51 | layout.Constraints{
52 | Max: image.Pt(100, 100),
53 | },
54 | ),
55 | },
56 | } {
57 | t.Run(tc.label, func(t *testing.T) {
58 | for _, cs := range tc.constraints {
59 | gtx := layout.Context{
60 | Constraints: cs,
61 | Ops: new(op.Ops),
62 | }
63 | dims := tc.widget(gtx)
64 | csr := image.Rectangle{
65 | Min: cs.Min,
66 | Max: cs.Max,
67 | }
68 | if !dims.Size.In(csr) {
69 | t.Errorf("dims size %v not within constraints %v", dims.Size, csr)
70 | }
71 | }
72 | })
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/widget/image.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 |
8 | "gioui.org/f32"
9 | "gioui.org/layout"
10 | "gioui.org/op"
11 | "gioui.org/op/clip"
12 | "gioui.org/op/paint"
13 | "gioui.org/unit"
14 | )
15 |
16 | // Image is a widget that displays an image.
17 | type Image struct {
18 | // Src is the image to display.
19 | Src paint.ImageOp
20 | // Fit specifies how to scale the image to the constraints.
21 | // By default it does not do any scaling.
22 | Fit Fit
23 | // Position specifies where to position the image within
24 | // the constraints.
25 | Position layout.Direction
26 | // Scale is the factor used for converting image pixels to dp.
27 | // If Scale is zero it defaults to 1.
28 | //
29 | // To map one image pixel to one output pixel, set Scale to 1.0 / gtx.Metric.PxPerDp.
30 | Scale float32
31 | }
32 |
33 | func (im Image) Layout(gtx layout.Context) layout.Dimensions {
34 | scale := im.Scale
35 | if scale == 0 {
36 | scale = 1
37 | }
38 |
39 | size := im.Src.Size()
40 | wf, hf := float32(size.X), float32(size.Y)
41 | w, h := gtx.Dp(unit.Dp(wf*scale)), gtx.Dp(unit.Dp(hf*scale))
42 |
43 | dims, trans := im.Fit.scale(gtx.Constraints, im.Position, layout.Dimensions{Size: image.Pt(w, h)})
44 | defer clip.Rect{Max: dims.Size}.Push(gtx.Ops).Pop()
45 |
46 | pixelScale := scale * gtx.Metric.PxPerDp
47 | trans = trans.Mul(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(pixelScale, pixelScale)))
48 | defer op.Affine(trans).Push(gtx.Ops).Pop()
49 |
50 | im.Src.Add(gtx.Ops)
51 | paint.PaintOp{}.Add(gtx.Ops)
52 |
53 | return dims
54 | }
55 |
--------------------------------------------------------------------------------
/widget/image_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "gioui.org/layout"
10 | "gioui.org/op"
11 | "gioui.org/op/paint"
12 | )
13 |
14 | func TestImageScale(t *testing.T) {
15 | var ops op.Ops
16 | gtx := layout.Context{
17 | Ops: &ops,
18 | Constraints: layout.Constraints{
19 | Max: image.Pt(50, 50),
20 | },
21 | }
22 | imgSize := image.Pt(10, 10)
23 | img := image.NewNRGBA(image.Rectangle{Max: imgSize})
24 | imgOp := paint.NewImageOp(img)
25 |
26 | // Ensure the default scales correctly.
27 | dims := Image{Src: imgOp}.Layout(gtx)
28 | expectedSize := imgSize
29 | expectedSize.X = int(float32(expectedSize.X))
30 | expectedSize.Y = int(float32(expectedSize.Y))
31 | if dims.Size != expectedSize {
32 | t.Fatalf("non-scaled image is wrong size, expected %v, got %v", expectedSize, dims.Size)
33 | }
34 |
35 | // Ensure scaling the image via the Scale field works.
36 | currentScale := float32(0.5)
37 | dims = Image{Src: imgOp, Scale: float32(currentScale)}.Layout(gtx)
38 | expectedSize = imgSize
39 | expectedSize.X = int(float32(expectedSize.X) * currentScale)
40 | expectedSize.Y = int(float32(expectedSize.Y) * currentScale)
41 | if dims.Size != expectedSize {
42 | t.Fatalf(".5 scale image is wrong size, expected %v, got %v", expectedSize, dims.Size)
43 | }
44 |
45 | // Ensure the image responds to changes in DPI.
46 | currentScale = float32(1)
47 | gtx.Metric.PxPerDp = 2
48 | dims = Image{Src: imgOp, Scale: float32(currentScale)}.Layout(gtx)
49 | expectedSize = imgSize
50 | expectedSize.X = int(float32(expectedSize.X) * currentScale * gtx.Metric.PxPerDp)
51 | expectedSize.Y = int(float32(expectedSize.Y) * currentScale * gtx.Metric.PxPerDp)
52 | if dims.Size != expectedSize {
53 | t.Fatalf("HiDPI non-scaled image is wrong size, expected %v, got %v", expectedSize, dims.Size)
54 | }
55 |
56 | // Ensure scaling the image responds to changes in DPI.
57 | currentScale = float32(.5)
58 | gtx.Metric.PxPerDp = 2
59 | dims = Image{Src: imgOp, Scale: float32(currentScale)}.Layout(gtx)
60 | expectedSize = imgSize
61 | expectedSize.X = int(float32(expectedSize.X) * currentScale * gtx.Metric.PxPerDp)
62 | expectedSize.Y = int(float32(expectedSize.Y) * currentScale * gtx.Metric.PxPerDp)
63 | if dims.Size != expectedSize {
64 | t.Fatalf("HiDPI .5 scale image is wrong size, expected %v, got %v", expectedSize, dims.Size)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/widget/material/checkable.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image"
7 | "image/color"
8 |
9 | "gioui.org/font"
10 | "gioui.org/internal/f32color"
11 | "gioui.org/layout"
12 | "gioui.org/op"
13 | "gioui.org/op/clip"
14 | "gioui.org/op/paint"
15 | "gioui.org/text"
16 | "gioui.org/unit"
17 | "gioui.org/widget"
18 | )
19 |
20 | type checkable struct {
21 | Label string
22 | Color color.NRGBA
23 | Font font.Font
24 | TextSize unit.Sp
25 | IconColor color.NRGBA
26 | Size unit.Dp
27 | shaper *text.Shaper
28 | checkedStateIcon *widget.Icon
29 | uncheckedStateIcon *widget.Icon
30 | }
31 |
32 | func (c *checkable) layout(gtx layout.Context, checked, hovered bool) layout.Dimensions {
33 | var icon *widget.Icon
34 | if checked {
35 | icon = c.checkedStateIcon
36 | } else {
37 | icon = c.uncheckedStateIcon
38 | }
39 |
40 | dims := layout.Flex{Alignment: layout.Middle}.Layout(gtx,
41 | layout.Rigid(func(gtx layout.Context) layout.Dimensions {
42 | return layout.Stack{Alignment: layout.Center}.Layout(gtx,
43 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
44 | size := gtx.Dp(c.Size) * 4 / 3
45 | dims := layout.Dimensions{
46 | Size: image.Point{X: size, Y: size},
47 | }
48 | if !hovered {
49 | return dims
50 | }
51 |
52 | background := f32color.MulAlpha(c.IconColor, 70)
53 |
54 | b := image.Rectangle{Max: image.Pt(size, size)}
55 | paint.FillShape(gtx.Ops, background, clip.Ellipse(b).Op(gtx.Ops))
56 |
57 | return dims
58 | }),
59 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
60 | return layout.UniformInset(2).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
61 | size := gtx.Dp(c.Size)
62 | col := c.IconColor
63 | if !gtx.Enabled() {
64 | col = f32color.Disabled(col)
65 | }
66 | gtx.Constraints.Min = image.Point{X: size}
67 | icon.Layout(gtx, col)
68 | return layout.Dimensions{
69 | Size: image.Point{X: size, Y: size},
70 | }
71 | })
72 | }),
73 | )
74 | }),
75 |
76 | layout.Rigid(func(gtx layout.Context) layout.Dimensions {
77 | return layout.UniformInset(2).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
78 | colMacro := op.Record(gtx.Ops)
79 | paint.ColorOp{Color: c.Color}.Add(gtx.Ops)
80 | return widget.Label{}.Layout(gtx, c.shaper, c.Font, c.TextSize, c.Label, colMacro.Stop())
81 | })
82 | }),
83 | )
84 | return dims
85 | }
86 |
--------------------------------------------------------------------------------
/widget/material/checkbox.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "gioui.org/io/semantic"
7 | "gioui.org/layout"
8 | "gioui.org/widget"
9 | )
10 |
11 | type CheckBoxStyle struct {
12 | checkable
13 | CheckBox *widget.Bool
14 | }
15 |
16 | func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle {
17 | c := CheckBoxStyle{
18 | CheckBox: checkBox,
19 | checkable: checkable{
20 | Label: label,
21 | Color: th.Palette.Fg,
22 | IconColor: th.Palette.ContrastBg,
23 | TextSize: th.TextSize * 14.0 / 16.0,
24 | Size: 26,
25 | shaper: th.Shaper,
26 | checkedStateIcon: th.Icon.CheckBoxChecked,
27 | uncheckedStateIcon: th.Icon.CheckBoxUnchecked,
28 | },
29 | }
30 | c.checkable.Font.Typeface = th.Face
31 | return c
32 | }
33 |
34 | // Layout updates the checkBox and displays it.
35 | func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions {
36 | return c.CheckBox.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
37 | semantic.CheckBox.Add(gtx.Ops)
38 | return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered() || gtx.Focused(c.CheckBox))
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/widget/material/doc.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package material implements the Material design.
4 | //
5 | // To maximize reusability and visual flexibility, user interface controls are
6 | // split into two parts: the stateful widget and the stateless drawing of it.
7 | //
8 | // For example, widget.Clickable encapsulates the state and event
9 | // handling of all clickable areas, while the Theme is responsible to
10 | // draw a specific area, for example a button.
11 | //
12 | // This snippet defines a button that prints a message when clicked:
13 | //
14 | // var gtx layout.Context
15 | // button := new(widget.Clickable)
16 | //
17 | // for button.Clicked(gtx) {
18 | // fmt.Println("Clicked!")
19 | // }
20 | //
21 | // Use a Theme to draw the button:
22 | //
23 | // theme := material.NewTheme(...)
24 | //
25 | // material.Button(theme, button, "Click me!").Layout(gtx)
26 | //
27 | // # Customization
28 | //
29 | // Quite often, a program needs to customize the theme-provided defaults. Several
30 | // options are available, depending on the nature of the change.
31 | //
32 | // Mandatory parameters: Some parameters are not part of the widget state but
33 | // have no obvious default. In the program above, the button text is a
34 | // parameter to the Theme.Button method.
35 | //
36 | // Theme-global parameters: For changing the look of all widgets drawn with a
37 | // particular theme, adjust the `Theme` fields:
38 | //
39 | // theme.Palette.Fg = color.NRGBA{...}
40 | //
41 | // Widget-local parameters: For changing the look of a particular widget,
42 | // adjust the widget specific theme object:
43 | //
44 | // btn := material.Button(theme, button, "Click me!")
45 | // btn.Font.Style = text.Italic
46 | // btn.Layout(gtx)
47 | //
48 | // Widget variants: A widget can have several distinct representations even
49 | // though the underlying state is the same. A widget.Clickable can be drawn as a
50 | // round icon button:
51 | //
52 | // icon := widget.NewIcon(...)
53 | //
54 | // material.IconButton(theme, button, icon, "Click me!").Layout(gtx)
55 | //
56 | // Specialized widgets: Theme both define a generic Label method
57 | // that takes a text size, and specialized methods for standard text
58 | // sizes such as Theme.H1 and Theme.Body2.
59 | package material
60 |
--------------------------------------------------------------------------------
/widget/material/editor.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image/color"
7 |
8 | "gioui.org/font"
9 | "gioui.org/internal/f32color"
10 | "gioui.org/layout"
11 | "gioui.org/op"
12 | "gioui.org/op/paint"
13 | "gioui.org/text"
14 | "gioui.org/unit"
15 | "gioui.org/widget"
16 | )
17 |
18 | type EditorStyle struct {
19 | Font font.Font
20 | // LineHeight controls the distance between the baselines of lines of text.
21 | // If zero, a sensible default will be used.
22 | LineHeight unit.Sp
23 | // LineHeightScale applies a scaling factor to the LineHeight. If zero, a
24 | // sensible default will be used.
25 | LineHeightScale float32
26 | TextSize unit.Sp
27 | // Color is the text color.
28 | Color color.NRGBA
29 | // Hint contains the text displayed when the editor is empty.
30 | Hint string
31 | // HintColor is the color of hint text.
32 | HintColor color.NRGBA
33 | // SelectionColor is the color of the background for selected text.
34 | SelectionColor color.NRGBA
35 | Editor *widget.Editor
36 |
37 | shaper *text.Shaper
38 | }
39 |
40 | func Editor(th *Theme, editor *widget.Editor, hint string) EditorStyle {
41 | return EditorStyle{
42 | Editor: editor,
43 | Font: font.Font{
44 | Typeface: th.Face,
45 | },
46 | TextSize: th.TextSize,
47 | Color: th.Palette.Fg,
48 | shaper: th.Shaper,
49 | Hint: hint,
50 | HintColor: f32color.MulAlpha(th.Palette.Fg, 0xbb),
51 | SelectionColor: f32color.MulAlpha(th.Palette.ContrastBg, 0x60),
52 | }
53 | }
54 |
55 | func (e EditorStyle) Layout(gtx layout.Context) layout.Dimensions {
56 | // Choose colors.
57 | textColorMacro := op.Record(gtx.Ops)
58 | paint.ColorOp{Color: e.Color}.Add(gtx.Ops)
59 | textColor := textColorMacro.Stop()
60 | hintColorMacro := op.Record(gtx.Ops)
61 | paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops)
62 | hintColor := hintColorMacro.Stop()
63 | selectionColorMacro := op.Record(gtx.Ops)
64 | paint.ColorOp{Color: blendDisabledColor(!gtx.Enabled(), e.SelectionColor)}.Add(gtx.Ops)
65 | selectionColor := selectionColorMacro.Stop()
66 |
67 | var maxlines int
68 | if e.Editor.SingleLine {
69 | maxlines = 1
70 | }
71 |
72 | macro := op.Record(gtx.Ops)
73 | tl := widget.Label{
74 | Alignment: e.Editor.Alignment,
75 | MaxLines: maxlines,
76 | LineHeight: e.LineHeight,
77 | LineHeightScale: e.LineHeightScale,
78 | }
79 | dims := tl.Layout(gtx, e.shaper, e.Font, e.TextSize, e.Hint, hintColor)
80 | call := macro.Stop()
81 |
82 | if w := dims.Size.X; gtx.Constraints.Min.X < w {
83 | gtx.Constraints.Min.X = w
84 | }
85 | if h := dims.Size.Y; gtx.Constraints.Min.Y < h {
86 | gtx.Constraints.Min.Y = h
87 | }
88 | e.Editor.LineHeight = e.LineHeight
89 | e.Editor.LineHeightScale = e.LineHeightScale
90 | dims = e.Editor.Layout(gtx, e.shaper, e.Font, e.TextSize, textColor, selectionColor)
91 | if e.Editor.Len() == 0 {
92 | call.Add(gtx.Ops)
93 | }
94 | return dims
95 | }
96 |
97 | func blendDisabledColor(disabled bool, c color.NRGBA) color.NRGBA {
98 | if disabled {
99 | return f32color.Disabled(c)
100 | }
101 | return c
102 | }
103 |
--------------------------------------------------------------------------------
/widget/material/list_test.go:
--------------------------------------------------------------------------------
1 | package material_test
2 |
3 | import (
4 | "image"
5 | "testing"
6 |
7 | "gioui.org/layout"
8 | "gioui.org/op"
9 | "gioui.org/unit"
10 | "gioui.org/widget"
11 | "gioui.org/widget/material"
12 | )
13 |
14 | func TestListAnchorStrategies(t *testing.T) {
15 | gtx := layout.Context{
16 | Ops: new(op.Ops),
17 | Metric: unit.Metric{
18 | PxPerDp: 1,
19 | PxPerSp: 1,
20 | },
21 | Constraints: layout.Exact(image.Point{
22 | X: 500,
23 | Y: 500,
24 | }),
25 | }
26 | gtx.Constraints.Min = image.Point{}
27 |
28 | var spaceConstraints layout.Constraints
29 | space := func(gtx layout.Context, index int) layout.Dimensions {
30 | spaceConstraints = gtx.Constraints
31 | if spaceConstraints.Min.X < 0 || spaceConstraints.Min.Y < 0 ||
32 | spaceConstraints.Max.X < 0 || spaceConstraints.Max.Y < 0 {
33 | t.Errorf("invalid constraints at index %d: %#+v", index, spaceConstraints)
34 | }
35 | return layout.Dimensions{Size: image.Point{
36 | X: gtx.Constraints.Max.X,
37 | Y: gtx.Dp(20),
38 | }}
39 | }
40 |
41 | var list widget.List
42 | list.Axis = layout.Vertical
43 | elements := 100
44 | th := material.NewTheme()
45 | materialList := material.List(th, &list)
46 | indicatorWidth := gtx.Dp(materialList.Width())
47 |
48 | materialList.AnchorStrategy = material.Occupy
49 | occupyDims := materialList.Layout(gtx, elements, space)
50 | occupyConstraints := spaceConstraints
51 |
52 | materialList.AnchorStrategy = material.Overlay
53 | overlayDims := materialList.Layout(gtx, elements, space)
54 | overlayConstraints := spaceConstraints
55 |
56 | // Both anchor strategies should use all space available if their elements do.
57 | if occupyDims != overlayDims {
58 | t.Errorf("expected occupy dims (%v) to be equal to overlay dims (%v)", occupyDims, overlayDims)
59 | }
60 | // The overlay strategy should not reserve any space for the scroll indicator,
61 | // so the constraints that it presents to its elements should be larger than
62 | // those presented by the occupy strategy.
63 | if overlayConstraints.Max.X != occupyConstraints.Max.X+indicatorWidth {
64 | t.Errorf("overlay max width (%d) != occupy max width (%d) + indicator width (%d)",
65 | overlayConstraints.Max.X, occupyConstraints.Max.X, indicatorWidth)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/widget/material/loader.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image"
7 | "image/color"
8 | "math"
9 | "time"
10 |
11 | "gioui.org/f32"
12 | "gioui.org/layout"
13 | "gioui.org/op"
14 | "gioui.org/op/clip"
15 | "gioui.org/op/paint"
16 | )
17 |
18 | type LoaderStyle struct {
19 | Color color.NRGBA
20 | }
21 |
22 | func Loader(th *Theme) LoaderStyle {
23 | return LoaderStyle{
24 | Color: th.Palette.ContrastBg,
25 | }
26 | }
27 |
28 | func (l LoaderStyle) Layout(gtx layout.Context) layout.Dimensions {
29 | diam := gtx.Constraints.Min.X
30 | if minY := gtx.Constraints.Min.Y; minY > diam {
31 | diam = minY
32 | }
33 | if diam == 0 {
34 | diam = gtx.Dp(24)
35 | }
36 | sz := gtx.Constraints.Constrain(image.Pt(diam, diam))
37 | radius := sz.X / 2
38 | defer op.Offset(image.Pt(radius, radius)).Push(gtx.Ops).Pop()
39 |
40 | dt := float32((time.Duration(gtx.Now.UnixNano()) % (time.Second)).Seconds())
41 | startAngle := dt * math.Pi * 2
42 | endAngle := startAngle + math.Pi*1.5
43 |
44 | defer clipLoader(gtx.Ops, startAngle, endAngle, float32(radius)).Push(gtx.Ops).Pop()
45 | paint.ColorOp{
46 | Color: l.Color,
47 | }.Add(gtx.Ops)
48 | defer op.Offset(image.Pt(-radius, -radius)).Push(gtx.Ops).Pop()
49 | paint.PaintOp{}.Add(gtx.Ops)
50 | gtx.Execute(op.InvalidateCmd{})
51 | return layout.Dimensions{
52 | Size: sz,
53 | }
54 | }
55 |
56 | func clipLoader(ops *op.Ops, startAngle, endAngle, radius float32) clip.Op {
57 | const thickness = .25
58 |
59 | var (
60 | width = radius * thickness
61 | delta = endAngle - startAngle
62 |
63 | vy, vx = math.Sincos(float64(startAngle))
64 |
65 | inner = radius * (1. - thickness*.5)
66 | pen = f32.Pt(float32(vx), float32(vy)).Mul(inner)
67 | center = f32.Pt(0, 0).Sub(pen)
68 |
69 | p clip.Path
70 | )
71 |
72 | p.Begin(ops)
73 | p.Move(pen)
74 | p.Arc(center, center, delta)
75 | return clip.Stroke{
76 | Path: p.End(),
77 | Width: width,
78 | }.Op()
79 | }
80 |
--------------------------------------------------------------------------------
/widget/material/progressbar.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image"
7 | "image/color"
8 |
9 | "gioui.org/internal/f32color"
10 | "gioui.org/layout"
11 | "gioui.org/op/clip"
12 | "gioui.org/op/paint"
13 | "gioui.org/unit"
14 | )
15 |
16 | type ProgressBarStyle struct {
17 | Color color.NRGBA
18 | Height unit.Dp
19 | Radius unit.Dp
20 | TrackColor color.NRGBA
21 | Progress float32
22 | }
23 |
24 | func ProgressBar(th *Theme, progress float32) ProgressBarStyle {
25 | return ProgressBarStyle{
26 | Progress: progress,
27 | Height: unit.Dp(4),
28 | Radius: unit.Dp(2),
29 | Color: th.Palette.ContrastBg,
30 | TrackColor: f32color.MulAlpha(th.Palette.Fg, 0x88),
31 | }
32 | }
33 |
34 | func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
35 | shader := func(width int, color color.NRGBA) layout.Dimensions {
36 | d := image.Point{X: width, Y: gtx.Dp(p.Height)}
37 | rr := gtx.Dp(p.Radius)
38 |
39 | defer clip.UniformRRect(image.Rectangle{Max: image.Pt(width, d.Y)}, rr).Push(gtx.Ops).Pop()
40 | paint.ColorOp{Color: color}.Add(gtx.Ops)
41 | paint.PaintOp{}.Add(gtx.Ops)
42 |
43 | return layout.Dimensions{Size: d}
44 | }
45 |
46 | progressBarWidth := gtx.Constraints.Max.X
47 | return layout.Stack{Alignment: layout.W}.Layout(gtx,
48 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
49 | return shader(progressBarWidth, p.TrackColor)
50 | }),
51 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
52 | fillWidth := int(float32(progressBarWidth) * clamp1(p.Progress))
53 | fillColor := p.Color
54 | if !gtx.Enabled() {
55 | fillColor = f32color.Disabled(fillColor)
56 | }
57 | if fillWidth < int(p.Radius*2) {
58 | fillWidth = int(p.Radius * 2)
59 | }
60 | return shader(fillWidth, fillColor)
61 | }),
62 | )
63 | }
64 |
65 | // clamp1 limits v to range [0..1].
66 | func clamp1(v float32) float32 {
67 | if v >= 1 {
68 | return 1
69 | } else if v <= 0 {
70 | return 0
71 | } else {
72 | return v
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/widget/material/progresscircle.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image"
7 | "image/color"
8 | "math"
9 |
10 | "gioui.org/layout"
11 | "gioui.org/op"
12 | "gioui.org/op/paint"
13 | )
14 |
15 | type ProgressCircleStyle struct {
16 | Color color.NRGBA
17 | Progress float32
18 | }
19 |
20 | func ProgressCircle(th *Theme, progress float32) ProgressCircleStyle {
21 | return ProgressCircleStyle{
22 | Color: th.Palette.ContrastBg,
23 | Progress: progress,
24 | }
25 | }
26 |
27 | func (p ProgressCircleStyle) Layout(gtx layout.Context) layout.Dimensions {
28 | diam := gtx.Constraints.Min.X
29 | if minY := gtx.Constraints.Min.Y; minY > diam {
30 | diam = minY
31 | }
32 | if diam == 0 {
33 | diam = gtx.Dp(24)
34 | }
35 | sz := gtx.Constraints.Constrain(image.Pt(diam, diam))
36 | radius := sz.X / 2
37 | defer op.Offset(image.Pt(radius, radius)).Push(gtx.Ops).Pop()
38 |
39 | defer clipLoader(gtx.Ops, -math.Pi/2, -math.Pi/2+math.Pi*2*p.Progress, float32(radius)).Push(gtx.Ops).Pop()
40 | paint.ColorOp{
41 | Color: p.Color,
42 | }.Add(gtx.Ops)
43 | paint.PaintOp{}.Add(gtx.Ops)
44 | return layout.Dimensions{
45 | Size: sz,
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/widget/material/radiobutton.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "gioui.org/io/semantic"
7 | "gioui.org/layout"
8 | "gioui.org/widget"
9 | )
10 |
11 | type RadioButtonStyle struct {
12 | checkable
13 | Key string
14 | Group *widget.Enum
15 | }
16 |
17 | // RadioButton returns a RadioButton with a label. The key specifies
18 | // the value for the Enum.
19 | func RadioButton(th *Theme, group *widget.Enum, key, label string) RadioButtonStyle {
20 | r := RadioButtonStyle{
21 | Group: group,
22 | checkable: checkable{
23 | Label: label,
24 |
25 | Color: th.Palette.Fg,
26 | IconColor: th.Palette.ContrastBg,
27 | TextSize: th.TextSize * 14.0 / 16.0,
28 | Size: 26,
29 | shaper: th.Shaper,
30 | checkedStateIcon: th.Icon.RadioChecked,
31 | uncheckedStateIcon: th.Icon.RadioUnchecked,
32 | },
33 | Key: key,
34 | }
35 | r.checkable.Font.Typeface = th.Face
36 | return r
37 | }
38 |
39 | // Layout updates enum and displays the radio button.
40 | func (r RadioButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
41 | r.Group.Update(gtx)
42 | hovered, hovering := r.Group.Hovered()
43 | focus, focused := r.Group.Focused()
44 | return r.Group.Layout(gtx, r.Key, func(gtx layout.Context) layout.Dimensions {
45 | semantic.RadioButton.Add(gtx.Ops)
46 | highlight := hovering && hovered == r.Key || focused && focus == r.Key
47 | return r.layout(gtx, r.Group.Value == r.Key, highlight)
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/widget/material/slider.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image"
7 | "image/color"
8 |
9 | "gioui.org/internal/f32color"
10 | "gioui.org/layout"
11 | "gioui.org/op"
12 | "gioui.org/op/clip"
13 | "gioui.org/op/paint"
14 | "gioui.org/unit"
15 | "gioui.org/widget"
16 | )
17 |
18 | // Slider is for selecting a value in a range.
19 | func Slider(th *Theme, float *widget.Float) SliderStyle {
20 | return SliderStyle{
21 | Color: th.Palette.ContrastBg,
22 | Float: float,
23 | FingerSize: th.FingerSize,
24 | }
25 | }
26 |
27 | type SliderStyle struct {
28 | Axis layout.Axis
29 | Color color.NRGBA
30 | Float *widget.Float
31 |
32 | FingerSize unit.Dp
33 | }
34 |
35 | func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
36 | const thumbRadius unit.Dp = 6
37 | tr := gtx.Dp(thumbRadius)
38 | trackWidth := gtx.Dp(2)
39 |
40 | axis := s.Axis
41 | // Keep a minimum length so that the track is always visible.
42 | minLength := tr + 3*tr + tr
43 | // Try to expand to finger size, but only if the constraints
44 | // allow for it.
45 | touchSizePx := min(gtx.Dp(s.FingerSize), axis.Convert(gtx.Constraints.Max).Y)
46 | sizeMain := max(axis.Convert(gtx.Constraints.Min).X, minLength)
47 | sizeCross := max(2*tr, touchSizePx)
48 | size := axis.Convert(image.Pt(sizeMain, sizeCross))
49 |
50 | o := axis.Convert(image.Pt(tr, 0))
51 | trans := op.Offset(o).Push(gtx.Ops)
52 | gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*tr, sizeCross))
53 | dims := s.Float.Layout(gtx, axis, thumbRadius)
54 | gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross)))
55 | thumbPos := tr + int(s.Float.Value*float32(axis.Convert(dims.Size).X))
56 | trans.Pop()
57 |
58 | color := s.Color
59 | if !gtx.Enabled() {
60 | color = f32color.Disabled(color)
61 | }
62 |
63 | rect := func(minx, miny, maxx, maxy int) image.Rectangle {
64 | r := image.Rect(minx, miny, maxx, maxy)
65 | if axis == layout.Vertical {
66 | r.Max.X, r.Min.X = sizeMain-r.Min.X, sizeMain-r.Max.X
67 | }
68 | r.Min = axis.Convert(r.Min)
69 | r.Max = axis.Convert(r.Max)
70 | return r
71 | }
72 |
73 | // Draw track before thumb.
74 | track := rect(
75 | tr, sizeCross/2-trackWidth/2,
76 | thumbPos, sizeCross/2+trackWidth/2,
77 | )
78 | paint.FillShape(gtx.Ops, color, clip.Rect(track).Op())
79 |
80 | // Draw track after thumb.
81 | track = rect(
82 | thumbPos, axis.Convert(track.Min).Y,
83 | sizeMain-tr, axis.Convert(track.Max).Y,
84 | )
85 | paint.FillShape(gtx.Ops, f32color.MulAlpha(color, 96), clip.Rect(track).Op())
86 |
87 | // Draw thumb.
88 | pt := image.Pt(thumbPos, sizeCross/2)
89 | thumb := rect(
90 | pt.X-tr, pt.Y-tr,
91 | pt.X+tr, pt.Y+tr,
92 | )
93 | paint.FillShape(gtx.Ops, color, clip.Ellipse(thumb).Op(gtx.Ops))
94 |
95 | return layout.Dimensions{Size: size}
96 | }
97 |
98 | func max(a, b int) int {
99 | if a > b {
100 | return a
101 | }
102 | return b
103 | }
104 |
105 | func min(a, b int) int {
106 | if a < b {
107 | return a
108 | }
109 | return b
110 | }
111 |
--------------------------------------------------------------------------------
/widget/material/theme.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package material
4 |
5 | import (
6 | "image/color"
7 |
8 | "golang.org/x/exp/shiny/materialdesign/icons"
9 |
10 | "gioui.org/font"
11 | "gioui.org/text"
12 | "gioui.org/unit"
13 | "gioui.org/widget"
14 | )
15 |
16 | // Palette contains the minimal set of colors that a widget may need to
17 | // draw itself.
18 | type Palette struct {
19 | // Bg is the background color atop which content is currently being
20 | // drawn.
21 | Bg color.NRGBA
22 |
23 | // Fg is a color suitable for drawing on top of Bg.
24 | Fg color.NRGBA
25 |
26 | // ContrastBg is a color used to draw attention to active,
27 | // important, interactive widgets such as buttons.
28 | ContrastBg color.NRGBA
29 |
30 | // ContrastFg is a color suitable for content drawn on top of
31 | // ContrastBg.
32 | ContrastFg color.NRGBA
33 | }
34 |
35 | // Theme holds the general theme of an app or window. Different top-level
36 | // windows should have different instances of Theme (with different Shapers;
37 | // see the godoc for [text.Shaper]), though their other fields can be equal.
38 | type Theme struct {
39 | Shaper *text.Shaper
40 | Palette
41 | TextSize unit.Sp
42 | Icon struct {
43 | CheckBoxChecked *widget.Icon
44 | CheckBoxUnchecked *widget.Icon
45 | RadioChecked *widget.Icon
46 | RadioUnchecked *widget.Icon
47 | }
48 | // Face selects the default typeface for text.
49 | Face font.Typeface
50 |
51 | // FingerSize is the minimum touch target size.
52 | FingerSize unit.Dp
53 | }
54 |
55 | // NewTheme constructs a theme (and underlying text shaper).
56 | func NewTheme() *Theme {
57 | t := &Theme{Shaper: &text.Shaper{}}
58 | t.Palette = Palette{
59 | Fg: rgb(0x000000),
60 | Bg: rgb(0xffffff),
61 | ContrastBg: rgb(0x3f51b5),
62 | ContrastFg: rgb(0xffffff),
63 | }
64 | t.TextSize = 16
65 |
66 | t.Icon.CheckBoxChecked = mustIcon(widget.NewIcon(icons.ToggleCheckBox))
67 | t.Icon.CheckBoxUnchecked = mustIcon(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank))
68 | t.Icon.RadioChecked = mustIcon(widget.NewIcon(icons.ToggleRadioButtonChecked))
69 | t.Icon.RadioUnchecked = mustIcon(widget.NewIcon(icons.ToggleRadioButtonUnchecked))
70 |
71 | // 38dp is on the lower end of possible finger size.
72 | t.FingerSize = 38
73 |
74 | return t
75 | }
76 |
77 | func (t Theme) WithPalette(p Palette) Theme {
78 | t.Palette = p
79 | return t
80 | }
81 |
82 | func mustIcon(ic *widget.Icon, err error) *widget.Icon {
83 | if err != nil {
84 | panic(err)
85 | }
86 | return ic
87 | }
88 |
89 | func rgb(c uint32) color.NRGBA {
90 | return argb(0xff000000 | c)
91 | }
92 |
93 | func argb(c uint32) color.NRGBA {
94 | return color.NRGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)}
95 | }
96 |
--------------------------------------------------------------------------------
/widget/testdata/fuzz/FuzzEditorEditing/18c534da60e6b61361786a120fe7b82978ac0e5c16afb519beb080f73c470d3f:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("\xa3\xd90")
3 | int16(-11)
4 | int16(2237)
5 |
--------------------------------------------------------------------------------
/widget/testdata/fuzz/FuzzEditorEditing/a489f2d9f9226d13b55846645d025ac15316f1210d4aa307cde333cc87fdad55:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("")
3 | int16(44)
4 | int16(2299)
5 |
--------------------------------------------------------------------------------
/widget/testdata/fuzz/FuzzEditorEditing/clusterIndexForCrash1:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("و سأعر مثpل dolor sitamet, لم يتحم ج د adipscin eit, seddo الحصول علىpمhزة nرidiإun utNlaboe أ يتد magna aliqua.\nPorotit.r رادتهم فيتسوي rbi non aنcuيدكن ما يعقبها .\nNibhiنشجب ورستنكر ommodo nulla\nبكل سهولة ورونة ut consquat لهذا، من من .nam liber jظsto.يRisعs n hمndrerit لينا اضواجبالعمل.نNatoqe تكون Lدتا علي magis disparturient يخسك زمم الأمور ويخرار.\nIn جد ما يsنعنeu أcelerisquecونرا للاتزامات التي ةer entum.إMattis شرط وعندما لا neلue viverra.\nيمسكrزام الأمور abaan لهذر، .\nNisl تي يفض ا علينانfaucًbus ،من منا rm nec.\nSedكauue rلي الاختيار غر vitacongue eu nsequat.\nAt quis risus ك زمام الأمخر ومتا.tSit amet volutpat conseqt maسrisالأمو يiتارثإما nisi.\nDiguissim واجبوالعdل tincidunt نتنمزل feugiatn\nFauلibus التزاماتin eu m bإbendum.رOdio وcخرار إما أن ير صاسر السعادة ned adpiscig ذا، منeنا لم tistiue.\nFeretum leo vel ور ويختور ما pulvi\nr\nUtرإما أ يرفض صادر الmعادة من in metus تlون قدرتوايعليقgىelis imeodلet.\nي التتgر غي مقيدةمبشر et mنمsuada iamمs acنturpis.\nVennatis عل ميزة أو فائدة؟ ولكن oege nuc scele pque سزام ولأمو ويختار ما in.\nرتنا ultricieوtristique ي لااتيار غير قيدة بشرط enim trtor.\nRisus اختيiر غير قيدةeشط عندما quam سان الحكيم عليه أن .suspedsse in.Interمm vاlit نظرا للالتزام التي pellentesue massa pعaceiat ل مرضويتار إما أن يرفض acus.nProin دا تكون قدرتنا علي الاختيار lec us a.\nAuqtor الوقم عيدا تون aegue neque مال q fermentm et.\nطمeet ماك زام الأمور uيختار يmet cursuم لم يتحملجهدو نictum.uIn\nfermetum et sollicitudin ac orci nhبsellus علo الاخت.ا غ rutrum\nTemus mperdietلالمفترض أن.نفرق pellentesque ت بك سهو ة eget raidaم\nرonsequat id prvaمصادا السادة cras ned.\nVlputat رعلي الاختيارغير قيدة sitاamet aliquam\nmongue mauis حيمن ونظراً للالتزاnات التي elelit.\nRgsus qeis vهrius اuam quisque id ار غير قدة بشرط elementumوPreti m تي يفرضهاعuينا الاجب io in vitee.\n شاق إلا مب أل retium qua احكيم عليه أنsيمسك suspendss in et.Vlit ونظراً للالتزامات التي يفرض ultrice .\n الوقت عندما تكون velit dinissim يه ن يمس .\nرnc sclerisque vverramauris inلaliquam sim ً إا أن ut.\nالسعادt ك أجل مl هو أكثر أهمية أو يتحل الألم\nConvillis pفsuere morbi leo una molestie at.")
3 | int16(613)
4 | int16(1976)
5 |
--------------------------------------------------------------------------------
/widget/testdata/fuzz/FuzzEditorEditing/clusterIndexForCrash2:
--------------------------------------------------------------------------------
1 | go test fuzz v1
2 | string("د عرمثال dstي met لم aqل جدmوpمg lرe dرd لو عل ميrةsdiduntut lab renنيتذدagلaaiua.ئPocttأior رادرsاي mيrbلmnonaيdتد ماةعcلخ\nد بوولنكcmme ul\nبsسة uولnوuoeaهnا،من mن tn libeo jtsteاisusهin hen عnنادmب eلعملNaلeسكنعقرنا علي ااs diلre rinti مسl زي.pلrور و \nار.تn ندمم نا e sc\nlلrsueونً Tالت mاL التمeزe.\nMaيit e و وكدمl مfeierta.\nمسmزامhالر قhi tnoلهاموoرNu تrFر ع ي اauipus ،i نالمneauemiاm علنتنارغيرvitas au c n eqnt.\nAtuسuخ sبمام grأور ختaغ.\n mee v utهateس e alاmis\nأمأاكيخاار إماsi.igmi واجب ا rتnتunt iنتنزiueلia.ضةaucb التتا n eu mi رمndكOd ايي. اإهنيرe مدرلخ اد sd ndiاun ذ من مالe triلu.iقmcnoui أo وaوياوإ.pulvinar.\nrت إماأ يرف مصSاfعدمنiو مts iن ورتنم eliاieيرt.\nي ا شغرحميمبط ettlsuda apeاsa turqe.Veneتtisiع ميةوأو sة؟ كن eرncaيliuatم امnر ايتارإga uemاutrci biqu ً.لiيار ي مlي لطen اtو or\nRssخياoغيراميe شmcصناو uaيويمعله دس su.endiRseini\nnrاie\nt نرللسنز يات لتي pelqe iact لأمو را تريض macsmPoاا ا نأSدتن امدrت م lntuا u.\nAuatr سيتiياtجون ut nequ ضيى eo seض etumثtجaoee مسl tم.مuأيت وmt crrsغ c sمs اdictn.I أ,entrm Doiiludin c iaseleusيعلالختيررر rutrmmيmp mprieh فeرةa ن dف\nesمeldمsue يdكل امrخd egt i a eu.uCs uaن drtaمشدر لعدةشrنeظ\nVlpuatrعي pختيارهmرcمقيoةit am t اleua.\n ongulmaقisين.نnرل لللتز لتي م.\niousqui varNs اeل uiue ld uريحميcن برeneeum\nPrtزm يفرضا عاالاجب ن sيeشدelممن جل prstiتiqu rا\nحكlم ل ها،نك uspe duse اn ًaنeliت ً رازv لتي رفfيىutلise lsق oدمتكونيvelidgsدc tvle sNe أscاplsqنeمvشساtuتsرin aliqطi e ر إمدأنةقut لع ن أجuلما ه vرمة tصتحماللم\nConveli posمeل er leرr mلee .")
3 | int16(1228)
4 | int16(782)
5 |
--------------------------------------------------------------------------------
/widget/widget_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package widget_test
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "gioui.org/f32"
10 | "gioui.org/io/input"
11 | "gioui.org/io/pointer"
12 | "gioui.org/io/semantic"
13 | "gioui.org/layout"
14 | "gioui.org/op"
15 | "gioui.org/widget"
16 | )
17 |
18 | func TestBool(t *testing.T) {
19 | var (
20 | r input.Router
21 | b widget.Bool
22 | )
23 | gtx := layout.Context{
24 | Ops: new(op.Ops),
25 | Source: r.Source(),
26 | }
27 | layout := func() {
28 | b.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
29 | semantic.CheckBox.Add(gtx.Ops)
30 | semantic.DescriptionOp("description").Add(gtx.Ops)
31 | return layout.Dimensions{Size: image.Pt(100, 100)}
32 | })
33 | }
34 | layout()
35 | r.Frame(gtx.Ops)
36 | r.Queue(
37 | pointer.Event{
38 | Source: pointer.Touch,
39 | Kind: pointer.Press,
40 | Position: f32.Pt(50, 50),
41 | },
42 | pointer.Event{
43 | Source: pointer.Touch,
44 | Kind: pointer.Release,
45 | Position: f32.Pt(50, 50),
46 | },
47 | )
48 | gtx.Reset()
49 | layout()
50 | r.Frame(gtx.Ops)
51 | tree := r.AppendSemantics(nil)
52 | n := tree[0].Children[0].Desc
53 | if n.Description != "description" {
54 | t.Errorf("unexpected semantic description: %s", n.Description)
55 | }
56 | if n.Class != semantic.CheckBox {
57 | t.Errorf("unexpected semantic class: %v", n.Class)
58 | }
59 | if !b.Value || !n.Selected {
60 | t.Error("click did not select")
61 | }
62 | }
63 |
--------------------------------------------------------------------------------