Repository: Clemapfel/mousetrap.jl Branch: main Commit: 3fe2e89d1361 Files: 39 Total size: 1.1 MB Directory structure: gitextract_bwbj8psc/ ├── .github/ │ └── workflows/ │ └── CI.yml ├── .gitignore ├── Artifacts.toml ├── LICENSE ├── Manifest.toml ├── Project.toml ├── README.md ├── docs/ │ ├── make.jl │ └── src/ │ ├── 01_manual/ │ │ ├── 01_installation.md │ │ ├── 02_signals.md │ │ ├── 03_actions.md │ │ ├── 04_widgets.md │ │ ├── 05_event_handling.md │ │ ├── 06_image.md │ │ ├── 07_os_interface.md │ │ ├── 08_menus.md │ │ ├── 09_native_rendering.md │ │ ├── 10_theme_customization.md │ │ ├── 11_app_distribution.md │ │ └── 12_opengl_integration.md │ ├── assets/ │ │ ├── animation_fade_out.webm │ │ ├── animation_spin.webm │ │ ├── css_style_animation_spin.webm │ │ └── mousetrip.webm │ └── index.md ├── jll/ │ ├── build_tarballs.jl │ ├── build_tarballs.jl.in │ └── deploy.jl ├── logo.xcf ├── src/ │ ├── Mousetrap.jl │ ├── docgen/ │ │ ├── enums.jl │ │ ├── functions.jl │ │ ├── signals.jl │ │ └── types.jl │ ├── docs.jl │ └── key_codes.jl └── test/ ├── example.jl ├── makie_test.jl └── runtests.jl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/CI.yml ================================================ name: CI on: push: pull_request: defaults: run: shell: bash jobs: test: name: Julia ${{ matrix.julia-version }} ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: julia-version: - "1.7" - "1.9" os: - ubuntu-latest - macos-latest - windows-latest julia-arch: - x64 include: - os: ubuntu-latest prefix: xvfb-run steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: version: ${{ matrix.julia-version }} arch: ${{ matrix.julia-arch }} show-versioninfo: true - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 docs: name: Documentation runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: version: '1.7' - run: julia --project=docs -e ' using Pkg; Pkg.develop(PackageSpec(; path=pwd())); Pkg.instantiate(); Pkg.add(url=\"https://github.com/JuliaDocs/Documenter.jl\");' - run: julia --project=docs docs/make.jl env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} ================================================ FILE: .gitignore ================================================ docs/build/ docs/mousetrap docs/src/02_library/classes.md docs/src/02_library/enums.md docs/src/02_library/functions.md jll/build jll/products .idea/ ================================================ FILE: Artifacts.toml ================================================ ================================================ FILE: LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: Manifest.toml ================================================ # This file is machine-generated - editing it directly is not advised julia_version = "1.9.3" manifest_format = "2.0" project_hash = "35df27af9c014d69e11708d80e596801030231d1" [[deps.ANSIColoredPrinters]] git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" version = "0.0.1" [[deps.AbstractTrees]] git-tree-sha1 = "faa260e4cb5aba097a73fab382dd4b5819d8ec8c" uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" version = "0.4.4" [[deps.ArgParse]] deps = ["Logging", "TextWrap"] git-tree-sha1 = "3102bce13da501c9104df33549f511cd25264d7d" uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" version = "1.1.4" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" version = "1.1.1" [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [[deps.AssetRegistry]] deps = ["Distributed", "JSON", "Pidfile", "SHA", "Test"] git-tree-sha1 = "b25e88db7944f98789130d7b503276bc34bc098e" uuid = "bf4720bc-e11a-5d0c-854e-bdca1663c893" version = "0.1.0" [[deps.Attr_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "b132f9aeb209b8790dcc286c857f300369219d8d" uuid = "1fd713ca-387f-5abc-8002-d8b8b1623b73" version = "2.5.1+0" [[deps.AutoHashEquals]] git-tree-sha1 = "45bb6705d93be619b81451bb2006b7ee5d4e4453" uuid = "15f4f7f2-30c1-5605-9d31-71845cf9641f" version = "0.2.0" [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[deps.BinaryBuilder]] deps = ["ArgParse", "BinaryBuilderBase", "Dates", "Downloads", "GitHub", "HTTP", "JLD2", "JSON", "LibGit2", "Libdl", "Logging", "LoggingExtras", "ObjectFile", "OutputCollectors", "Pkg", "PkgLicenses", "REPL", "Random", "Registrator", "RegistryTools", "SHA", "Scratch", "Sockets", "TOML", "UUIDs", "ghr_jll"] git-tree-sha1 = "d3e0f34be7db381be6a4b201f7d1b2f4212a4379" uuid = "12aac903-9f7c-5d81-afc2-d9565ea332ae" version = "0.5.6" [[deps.BinaryBuilderBase]] deps = ["Bzip2_jll", "CodecZlib", "Downloads", "Gzip_jll", "HistoricalStdlibVersions", "InteractiveUtils", "JLLWrappers", "JSON", "LibGit2", "LibGit2_jll", "Libdl", "Logging", "OrderedCollections", "OutputCollectors", "Pkg", "Printf", "ProgressMeter", "REPL", "Random", "SHA", "Scratch", "SimpleBufferStream", "TOML", "Tar", "Tar_jll", "UUIDs", "XZ_jll", "Zstd_jll", "p7zip_jll", "pigz_jll"] git-tree-sha1 = "5d3967982f7f293703404d3adc3014bd54d2c580" uuid = "7f725544-6523-48cd-82d1-3fa08ff4056e" version = "1.25.0" [[deps.BitFlags]] git-tree-sha1 = "43b1a4a8f797c1cddadf60499a8a077d4af2cd2d" uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" version = "0.1.7" [[deps.Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2" uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" version = "1.0.8+0" [[deps.Cairo_jll]] deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] git-tree-sha1 = "4b859a208b2397a7a623a03449e4636bdb17bcf2" uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" version = "1.16.1+1" [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] git-tree-sha1 = "02aa26a4cf76381be7f66e020a3eddeb27b0a092" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.2" [[deps.Compat]] deps = ["UUIDs"] git-tree-sha1 = "8a62af3e248a8c4bad6b32cbbe663ae02275e32c" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" version = "4.10.0" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] CompatLinearAlgebraExt = "LinearAlgebra" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" version = "1.0.5+0" [[deps.ConcurrentUtilities]] deps = ["Serialization", "Sockets"] git-tree-sha1 = "5372dbbf8f0bdb8c700db5367132925c0771ef7e" uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" version = "2.2.1" [[deps.CxxWrap]] deps = ["Libdl", "MacroTools", "libcxxwrap_julia_jll"] git-tree-sha1 = "67ebbc028b385658142d3c0644e07992616653d9" uuid = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" version = "0.14.0" [[deps.DataAPI]] git-tree-sha1 = "8da84edb865b0b5b0100c0666a9bc9a0b71c553c" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" version = "1.15.0" [[deps.DataValueInterfaces]] git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" version = "1.0.0" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" [[deps.Distributed]] deps = ["Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" [[deps.DocStringExtensions]] deps = ["LibGit2"] git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" version = "0.9.3" [[deps.Documenter]] deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "Dates", "DocStringExtensions", "Downloads", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "Test", "Unicode"] git-tree-sha1 = "f667b805e90d643aeb1ca70189827f991a7cc115" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" version = "1.1.0" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" version = "1.6.0" [[deps.EpollShim_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "8e9441ee83492030ace98f9789a654a6d0b1f643" uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" version = "0.0.20230411+0" [[deps.ExceptionUnwrapping]] deps = ["Test"] git-tree-sha1 = "e90caa41f5a86296e014e148ee061bd6c3edec96" uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" version = "0.1.9" [[deps.Expat_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "4558ab818dcceaab612d1bb8c19cee87eda2b83c" uuid = "2e619515-83b5-522b-bb60-26c02a35a201" version = "2.5.0+0" [[deps.ExprTools]] git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" version = "0.1.10" [[deps.FileIO]] deps = ["Pkg", "Requires", "UUIDs"] git-tree-sha1 = "299dc33549f68299137e51e6d49a13b5b1da9673" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" version = "1.16.1" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" [[deps.Fontconfig_jll]] deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"] git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03" uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" version = "2.13.93+0" [[deps.Formatting]] deps = ["Printf"] git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8" uuid = "59287772-0a20-5a39-b81b-1366585eb4c0" version = "0.4.2" [[deps.FreeType2_jll]] deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] git-tree-sha1 = "d8db6a5a2fe1381c1ea4ef2cab7c69c2de7f9ea0" uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" version = "2.13.1+0" [[deps.FriBidi_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91" uuid = "559328eb-81f9-559d-9380-de523a88c83c" version = "1.0.10+0" [[deps.GLEW_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXi_jll"] git-tree-sha1 = "0333b4790daf4e20cdd61b79cae9b86e2b98f359" uuid = "bde7f898-03f7-559e-8810-194d950ce600" version = "2.2.0+0" [[deps.GLU_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg"] git-tree-sha1 = "65af046f4221e27fb79b28b6ca89dd1d12bc5ec7" uuid = "bd17208b-e95e-5925-bf81-e2f59b3e5c61" version = "9.0.1+0" [[deps.GTK4_jll]] deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "Graphene_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Libepoxy_jll", "Pango_jll", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libX11_jll", "Xorg_libXcursor_jll", "Xorg_libXdamage_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "Xorg_libXrender_jll", "gdk_pixbuf_jll", "iso_codes_jll", "xkbcommon_jll"] git-tree-sha1 = "e4f9ed37b104967acb37989984d34ad07f3a2d26" uuid = "6ebb71f1-8434-552f-b6b1-dc18babcca63" version = "4.10.5+0" [[deps.Gettext_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" version = "0.21.0+0" [[deps.GitForge]] deps = ["Dates", "HTTP", "JSON3", "StructTypes", "TimeZones", "UUIDs"] git-tree-sha1 = "79f1366c7130a92c3719b296f04e96fe90c26626" uuid = "8f6bce27-0656-5410-875b-07a5572985df" version = "0.4.2" [[deps.GitHub]] deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"] git-tree-sha1 = "5688002de970b9eee14b7af7bbbd1fdac10c9bbe" uuid = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26" version = "5.8.2" [[deps.Glib_jll]] deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] git-tree-sha1 = "e94c92c7bf4819685eb80186d51c43e71d4afa17" uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" version = "2.76.5+0" [[deps.Graphene_jll]] deps = ["Artifacts", "Glib_jll", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "61850a17f562453e3485a489c9c8cccb3abcab93" uuid = "75302f13-0b7e-5bab-a6d1-23fa92e4c2ea" version = "1.10.6+0" [[deps.Graphite2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" version = "1.3.14+0" [[deps.Gzip_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "e7a48abe6d5ba74904df632160aa4486b0e80bf0" uuid = "be1be57a-8558-53c3-a7e5-50095f79957e" version = "1.12.0+0" [[deps.HTTP]] deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] git-tree-sha1 = "5eab648309e2e060198b45820af1a37182de3cce" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" version = "1.10.0" [[deps.HarfBuzz_jll]] deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" version = "2.8.1+1" [[deps.Hiccup]] deps = ["MacroTools", "Test"] git-tree-sha1 = "6187bb2d5fcbb2007c39e7ac53308b0d371124bd" uuid = "9fb69e20-1954-56bb-a84f-559cc56a8ff7" version = "0.2.2" [[deps.HistoricalStdlibVersions]] git-tree-sha1 = "56ce882a06a846583e82c7c2c2d5194029eec232" uuid = "6df8b67a-e8a0-4029-b4b7-ac196fe72102" version = "1.2.1" [[deps.IOCapture]] deps = ["Logging", "Random"] git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6" uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" version = "0.2.3" [[deps.InlineStrings]] deps = ["Parsers"] git-tree-sha1 = "9cc2baf75c6d09f9da536ddf58eb2f29dedaf461" uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" version = "1.4.0" [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.IteratorInterfaceExtensions]] git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" uuid = "82899510-4779-5014-852e-03e436cf321d" version = "1.0.0" [[deps.JLD2]] deps = ["FileIO", "MacroTools", "Mmap", "OrderedCollections", "Pkg", "Printf", "Reexport", "Requires", "TranscodingStreams", "UUIDs"] git-tree-sha1 = "c11d691a0dc8e90acfa4740d293ade57f68bfdbb" uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" version = "0.4.35" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" version = "1.5.0" [[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "0.21.4" [[deps.JSON3]] deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] git-tree-sha1 = "95220473901735a0f4df9d1ca5b171b568b2daa3" uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" version = "1.13.2" [[deps.JpegTurbo_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "6f2675ef130a300a112286de91973805fcc5ffbc" uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" version = "2.1.91+0" [[deps.LERC_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" uuid = "88015f11-f218-50d7-93a8-a6af411a945d" version = "3.0.0+1" [[deps.LLVMOpenMP_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "f689897ccbe049adb19a065c495e75f372ecd42b" uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" version = "15.0.4+0" [[deps.LZO_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6" uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" version = "2.10.1+0" [[deps.LaTeXStrings]] git-tree-sha1 = "f2355693d6778a178ade15952b7ac47a4ff97996" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" version = "1.3.0" [[deps.Latexify]] deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Printf", "Requires"] git-tree-sha1 = "f428ae552340899a935973270b8d98e5a31c49fe" uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" version = "0.16.1" [deps.Latexify.extensions] DataFramesExt = "DataFrames" SymEngineExt = "SymEngine" [deps.Latexify.weakdeps] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" [[deps.LazilyInitializedFields]] git-tree-sha1 = "410fe4739a4b092f2ffe36fcb0dcc3ab12648ce1" uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" version = "1.2.1" [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" [[deps.LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" version = "0.6.3" [[deps.LibCURL_jll]] deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" version = "7.84.0+0" [[deps.LibGit2]] deps = ["Base64", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" [[deps.LibGit2_jll]] deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" version = "1.5.0+1" [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" version = "1.10.2+0" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" [[deps.Libepoxy_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libX11_jll"] git-tree-sha1 = "7a0158b71f8be5c771e7a273183b2d0ac35278c5" uuid = "42c93a91-0102-5b3f-8f9d-e41de60ac950" version = "1.5.10+0" [[deps.Libffi_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" version = "3.2.2+1" [[deps.Libgcrypt_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"] git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae" uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" version = "1.8.7+0" [[deps.Libglvnd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"] git-tree-sha1 = "6f73d1dd803986947b2c750138528a999a6c7733" uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" version = "1.6.0+0" [[deps.Libgpg_error_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9" uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" version = "1.42.0+0" [[deps.Libiconv_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" version = "1.17.0+0" [[deps.Libmount_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73" uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" version = "2.35.0+0" [[deps.Libtiff_jll]] deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" uuid = "89763e89-9b03-5906-acba-b20f662cd828" version = "4.4.0+0" [[deps.Libuuid_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066" uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" version = "2.36.0+0" [[deps.LinearAlgebra]] deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" [[deps.LoggingExtras]] deps = ["Dates", "Logging"] git-tree-sha1 = "5d4d2d9904227b8bd66386c1138cf4d5ffa826bf" uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" version = "0.4.9" [[deps.MacroTools]] deps = ["Markdown", "Random"] git-tree-sha1 = "9ee1618cbf5240e6d4e0371d6f24065083f60c48" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.11" [[deps.Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[deps.MarkdownAST]] deps = ["AbstractTrees", "Markdown"] git-tree-sha1 = "e8513266815200c0c8f522d6d44ffb5e9b366ae4" uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" version = "0.1.1" [[deps.MbedTLS]] deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "Random", "Sockets"] git-tree-sha1 = "03a9b9718f5682ecb107ac9f7308991db4ce395b" uuid = "739be429-bea8-5141-9913-cc70e7f3736d" version = "1.1.7" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" version = "2.28.2+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" [[deps.Mocking]] deps = ["Compat", "ExprTools"] git-tree-sha1 = "4cc0c5a83933648b615c36c2b956d94fda70641e" uuid = "78c3b35d-d492-501b-9361-3d52fe80e533" version = "0.7.7" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" version = "2022.10.11" [[deps.Mustache]] deps = ["Printf", "Tables"] git-tree-sha1 = "821e918c170ead5298ff84bffee41dd28929a681" uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" version = "1.0.17" [[deps.Mux]] deps = ["AssetRegistry", "Base64", "HTTP", "Hiccup", "MbedTLS", "Pkg", "Sockets"] git-tree-sha1 = "0bdaa479939d2a1f85e2f93e38fbccfcb73175a5" uuid = "a975b10e-0019-58db-a62f-e48ff68538c9" version = "1.0.1" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" [[deps.ObjectFile]] deps = ["Reexport", "StructIO"] git-tree-sha1 = "55ce61d43409b1fb0279d1781bf3b0f22c83ab3b" uuid = "d8793406-e978-5875-9003-1fc021f44a92" version = "0.3.7" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" version = "0.3.21+4" [[deps.OpenGLMathematics_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "909a33f40ec4bc2ecc9fbc9379c1314bd480a48f" uuid = "cc7be9be-d298-5888-8f50-b85d5f9d6d73" version = "0.9.9+0" [[deps.OpenSSL]] deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"] git-tree-sha1 = "51901a49222b09e3743c65b8847687ae5fc78eb2" uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" version = "1.4.1" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "ceeda72c9fd6bbebc4f4f598560789145a8b6c4c" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" version = "3.0.11+0" [[deps.OrderedCollections]] git-tree-sha1 = "2e73fe17cac3c62ad1aebe70d44c963c3cfdc3e3" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.6.2" [[deps.OutputCollectors]] git-tree-sha1 = "5d3f2b3b2e2a9d7d6f1774c78e94530ac7f360cc" uuid = "6c11c7d4-943b-4e2b-80de-f2cfc2930a8c" version = "0.1.1" [[deps.PCRE2_jll]] deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" version = "10.42.0+0" [[deps.Pango_jll]] deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] git-tree-sha1 = "4745216e94f71cb768d58330b059c9b76f32cb66" uuid = "36c8627f-9965-5494-a995-c6b170f724f3" version = "1.50.14+0" [[deps.Parsers]] deps = ["Dates", "PrecompileTools", "UUIDs"] git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" version = "2.7.2" [[deps.Pidfile]] deps = ["FileWatching", "Test"] git-tree-sha1 = "2d8aaf8ee10df53d0dfb9b8ee44ae7c04ced2b03" uuid = "fa939f87-e72e-5be4-a000-7fc836dbe307" version = "1.3.0" [[deps.Pixman_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] git-tree-sha1 = "64779bc4c9784fee475689a1752ef4d5747c5e87" uuid = "30392449-352a-5448-841d-b1acce4e97dc" version = "0.42.2+0" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.9.2" [[deps.PkgLicenses]] deps = ["Test"] git-tree-sha1 = "0af826be249c6751a3e783c07b8cd3034f508943" uuid = "fc669557-7ec9-5e45-bca9-462afbc28879" version = "0.2.0" [[deps.PrecompileTools]] deps = ["Preferences"] git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" version = "1.2.0" [[deps.Preferences]] deps = ["TOML"] git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e" uuid = "21216c6a-2e73-6563-6e65-726566657250" version = "1.4.1" [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" [[deps.ProgressMeter]] deps = ["Distributed", "Printf"] git-tree-sha1 = "00099623ffee15972c16111bcf84c58a0051257c" uuid = "92933f4c-e287-5a05-a399-4b506db050ca" version = "1.9.0" [[deps.REPL]] deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.Random]] deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[deps.Reexport]] git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" uuid = "189a3867-3050-52da-a836-e630ba90ab69" version = "1.2.2" [[deps.Registrator]] deps = ["AutoHashEquals", "Base64", "Dates", "Distributed", "FileWatching", "GitForge", "GitHub", "HTTP", "JSON", "LibGit2", "Logging", "MbedTLS", "Mocking", "Mustache", "Mux", "Pkg", "RegistryTools", "Serialization", "Sockets", "TimeToLive", "URIs", "UUIDs", "ZMQ"] git-tree-sha1 = "64a7d49e56cf609973854cdd48eb265ac418f6ee" uuid = "4418983a-e44d-11e8-3aec-9789530b3b3e" version = "1.6.0" [[deps.RegistryInstances]] deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" version = "0.1.0" [[deps.RegistryTools]] deps = ["AutoHashEquals", "LibGit2", "Pkg", "SHA", "UUIDs"] git-tree-sha1 = "47ab54eff26db6be2496e6300d959e16d8203723" uuid = "d1eb7eb1-105f-429d-abf5-b0f65cb9e2c4" version = "1.9.1" [[deps.Requires]] deps = ["UUIDs"] git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" uuid = "ae029012-a4dd-5104-9daa-d747884805df" version = "1.3.0" [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" version = "0.7.0" [[deps.Scratch]] deps = ["Dates"] git-tree-sha1 = "30449ee12237627992a99d5e30ae63e4d78cd24a" uuid = "6c6a2e73-6563-6170-7368-637461726353" version = "1.2.0" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" [[deps.SimpleBufferStream]] git-tree-sha1 = "874e8867b33a00e784c8a7e4b60afe9e037b74e1" uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" version = "1.1.0" [[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" [[deps.SodiumSeal]] deps = ["Base64", "Libdl", "libsodium_jll"] git-tree-sha1 = "80cef67d2953e33935b41c6ab0a178b9987b1c99" uuid = "2133526b-2bfb-4018-ac12-889fb3908a75" version = "0.1.1" [[deps.StructIO]] deps = ["Test"] git-tree-sha1 = "010dc73c7146869c042b49adcdb6bf528c12e859" uuid = "53d494c1-5632-5724-8f4c-31dff12d585f" version = "0.3.0" [[deps.StructTypes]] deps = ["Dates", "UUIDs"] git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70" uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" version = "1.10.0" [[deps.TOML]] deps = ["Dates"] uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" version = "1.0.3" [[deps.TZJData]] deps = ["Artifacts"] git-tree-sha1 = "d39314cdbaf5b90a047db33858626f8d1cc973e1" uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7" version = "1.0.0+2023c" [[deps.TableTraits]] deps = ["IteratorInterfaceExtensions"] git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" version = "1.0.1" [[deps.Tables]] deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits"] git-tree-sha1 = "a1f34829d5ac0ef499f6d84428bd6b4c71f02ead" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" version = "1.11.0" [[deps.Tar]] deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" version = "1.10.0" [[deps.Tar_jll]] deps = ["Artifacts", "Attr_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] git-tree-sha1 = "85e7d0ef5248971fbd824f29c52ab6168b895dfd" uuid = "9b64493d-8859-5bf3-93d7-7c32dd38186f" version = "1.35.0+0" [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [[deps.TextWrap]] git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf" uuid = "b718987f-49a8-5099-9789-dcd902bef87d" version = "1.0.1" [[deps.TimeToLive]] deps = ["Dates"] git-tree-sha1 = "1f1389007d16385ec02e497bef6c2caffba99b65" uuid = "37f0c46e-897f-50ef-b453-b26c3eed3d6c" version = "0.3.0" [[deps.TimeZones]] deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"] git-tree-sha1 = "89e64d61ef3cd9e80f7fc12b7d13db2d75a23c03" uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" version = "1.13.0" [deps.TimeZones.extensions] TimeZonesRecipesBaseExt = "RecipesBase" [deps.TimeZones.weakdeps] RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" [[deps.TranscodingStreams]] deps = ["Random", "Test"] git-tree-sha1 = "9a6ae7ed916312b41236fcef7e0af564ef934769" uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" version = "0.9.13" [[deps.URIs]] git-tree-sha1 = "b7a5e99f24892b6824a954199a45e9ffcc1c70f0" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" version = "1.5.0" [[deps.UUIDs]] deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.Wayland_jll]] deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"] git-tree-sha1 = "7558e29847e99bc3f04d6569e82d0f5c54460703" uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" version = "1.21.0+1" [[deps.Wayland_protocols_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "4528479aa01ee1b3b4cd0e6faef0e04cf16466da" uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91" version = "1.25.0+0" [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] git-tree-sha1 = "24b81b59bd35b3c42ab84fa589086e19be919916" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" version = "2.11.5+0" [[deps.XSLT_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" uuid = "aed1982a-8fda-507f-9586-7b0439959a61" version = "1.1.34+0" [[deps.XZ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "cf2c7de82431ca6f39250d2fc4aacd0daa1675c0" uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" version = "5.4.4+0" [[deps.Xorg_libX11_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" version = "1.8.6+0" [[deps.Xorg_libXau_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" version = "1.0.11+0" [[deps.Xorg_libXcursor_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd" uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" version = "1.2.0+4" [[deps.Xorg_libXdamage_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll"] git-tree-sha1 = "fe4ffb2024ba3eddc862c6e1d70e2b070cd1c2bf" uuid = "0aeada51-83db-5f97-b67e-184615cfc6f6" version = "1.1.5+4" [[deps.Xorg_libXdmcp_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" version = "1.1.4+0" [[deps.Xorg_libXext_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3" uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" version = "1.3.4+4" [[deps.Xorg_libXfixes_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4" uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" version = "5.0.3+4" [[deps.Xorg_libXi_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246" uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" version = "1.7.10+4" [[deps.Xorg_libXinerama_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"] git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123" uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" version = "1.1.4+4" [[deps.Xorg_libXrandr_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"] git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631" uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" version = "1.5.2+4" [[deps.Xorg_libXrender_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"] git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96" uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" version = "0.9.10+4" [[deps.Xorg_libpthread_stubs_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" version = "0.1.1+0" [[deps.Xorg_libxcb_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d" uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" version = "1.15.0+0" [[deps.Xorg_libxkbfile_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] git-tree-sha1 = "730eeca102434283c50ccf7d1ecdadf521a765a4" uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" version = "1.1.2+0" [[deps.Xorg_xkbcomp_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] git-tree-sha1 = "330f955bc41bb8f5270a369c473fc4a5a4e4d3cb" uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" version = "1.4.6+0" [[deps.Xorg_xkeyboard_config_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] git-tree-sha1 = "691634e5453ad362044e2ad653e79f3ee3bb98c3" uuid = "33bec58e-1273-512f-9401-5d533626f822" version = "2.39.0+0" [[deps.Xorg_xtrans_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" version = "1.5.0+0" [[deps.ZMQ]] deps = ["FileWatching", "Sockets", "ZeroMQ_jll"] git-tree-sha1 = "356d2bdcc0bce90aabee1d1c0f6d6f301eda8f77" uuid = "c2297ded-f4af-51ae-bb23-16f91089e4e1" version = "1.2.2" [[deps.ZeroMQ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "libsodium_jll"] git-tree-sha1 = "fe5c65a526f066fb3000da137d5785d9649a8a47" uuid = "8f1865be-045e-5c20-9c9f-bfbfb0764568" version = "4.3.4+0" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" version = "1.2.13+0" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "49ce682769cd5de6c72dcf1b94ed7790cd08974c" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" version = "1.5.5+0" [[deps.gdk_pixbuf_jll]] deps = ["Artifacts", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg", "Xorg_libX11_jll", "libpng_jll"] git-tree-sha1 = "e9190f9fb03f9c3b15b9fb0c380b0d57a3c8ea39" uuid = "da03df04-f53b-5353-a52f-6a8b0620ced0" version = "2.42.8+0" [[deps.ghr_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "a83b3feeda837dd3f3cad19076bda0f0a524d687" uuid = "07c12ed4-43bc-5495-8a2a-d5838ef8d533" version = "0.14.0+0" [[deps.iso_codes_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "51559b9071db7e363047a34f658d495843ccd35c" uuid = "bf975903-5238-5d20-8243-bc370bc1e7e5" version = "4.11.0+0" [[deps.libadwaita_jll]] deps = ["Artifacts", "GTK4_jll", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "51352620ef59bc200289a398fbc65c587f0034d7" uuid = "583852a3-1c13-5035-b52b-3b742a7b3316" version = "1.2.0+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.8.0+0" [[deps.libcxxwrap_julia_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "d2058c04963d424ae69f95460fc34fdfce4dc0cf" uuid = "3eaa8342-bff7-56a5-9981-c04077f7cee7" version = "0.11.0+1" [[deps.libpng_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c" uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" version = "1.6.38+0" [[deps.libsodium_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "848ab3d00fe39d6fbc2a8641048f8f272af1c51e" uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" version = "1.0.20+0" [[deps.mousetrap_jll]] deps = ["Artifacts", "GLEW_jll", "GLU_jll", "GTK4_jll", "JLLWrappers", "Libdl", "OpenGLMathematics_jll", "libadwaita_jll", "libcxxwrap_julia_jll"] git-tree-sha1 = "6e5d87111686530feb339af45210317f56c27a82" uuid = "0e90efc8-2bbd-550f-bf3c-306a2edaaeef" version = "0.3.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" version = "1.48.0+0" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" version = "17.4.0+0" [[deps.pigz_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] git-tree-sha1 = "3c0c0b0c133b6ab53e1af05dc526091ce8781f16" uuid = "1bc43ea1-30af-5bc8-a9d4-c018457e6e3e" version = "2.7.0+0" [[deps.xkbcommon_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] git-tree-sha1 = "9c304562909ab2bab0262639bd4f444d7bc2be37" uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" version = "1.4.1+1" ================================================ FILE: Project.toml ================================================ name = "Mousetrap" uuid = "5deeb4b9-6e04-4da7-8b7f-c77fb1eae65e" authors = ["C.Cords "] version = "0.3.1" [deps] BinaryBuilder = "12aac903-9f7c-5d81-afc2-d9565ea332ae" CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" GTK4_jll = "6ebb71f1-8434-552f-b6b1-dc18babcca63" Glib_jll = "7746bdde-850d-59dc-9ae8-88ece973131d" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" libadwaita_jll = "583852a3-1c13-5035-b52b-3b742a7b3316" libcxxwrap_julia_jll = "3eaa8342-bff7-56a5-9981-c04077f7cee7" mousetrap_jll = "0e90efc8-2bbd-550f-bf3c-306a2edaaeef" [compat] CxxWrap = "0.14.0" mousetrap_jll = "0.3.0" GTK4_jll = "4.10" Glib_jll = "2.76" ================================================ FILE: README.md ================================================ # Mousetrap ![](docs/src/assets/banner.png) Mousetrap is a GUI library designed for Julia. It fully wraps [GTK4](https://docs.gtk.org/gtk4/) (which is written in C), *vastly* simplifying its interface to improve ease-of-use without sacrificing flexibility. It aims to give developers of all skill levels the tools to start creating complex GUI applications with little time and effort, while taking full advantage of Julias idiosyncrasies. > **Note**: Mousetrap is under active development. Consider participating in the development by [opening an issue](https://github.com/clemapfel/mousetrap.jl) when you encounter an error, bug, question, or missing feature. > **Note**: February 8th, 2024: I've been having some health issues at the moment, it may take me some time to get to open issues. Mousetrap is still usable and (mostly) stable, the repo will be maintained in the immediate future to the best of my abilities. Thank you for your understanding, C. --- ## Table of Contents 0. [Introduction](https://github.com/Clemapfel/mousetrap.jl)
1. [Features](#features)
2. [Planned Features](#planned-features)
3. [Showcase](#showcase)
3.1 [Hello World](#hello-world)
3.2 [Swapping between Light- and Dark Themes](#swapping-between-light--and-dark-themes)
3.3 [Opening a File Explorer Dialog](#opening-a-file-explorer-dialog)
3.4 [Rendering a Rectangle using OpenGL](#rendering-a-rectangle-with-opengl)
3.5 [Displaying a GLMakie Plot in a Mousetrap Window](#displaying-a-glmakie-plot-in-a-mousetrap-window)
4. [Supported Platforms](#supported-platforms)
6. [Documentation](#documentation)
5. [Installation](#installation)
7. [Credits & Donations](#credits--donations)
8. [License](#license)
--- ## Features + Create complex GUI application for Linux, Windows, and macOS + Choose from over 40 different kinds of pre-made widgets, or create your own + Supports mice, keyboards, touchscreens, touchpads, and stylus devices + Image processing facilities, well-suited for image manipulation programs + Built using OpenGL, allowing for high-performance, hardware-accelerated rendering, and integration of other OpenGL-based libraries such as [GLMakie](https://github.com/MakieOrg/Makie.jl) + [Hand-written manual and extensive documentation](https://clemens-cords.com/mousetrap): every exported symbol is documented --- ## Planned Features In order of priority, highest first: + Allow bundling of Mousetrap apps using [`PackageCompiler.jl`](https://github.com/JuliaLang/PackageCompiler.jl) + Implement installation of .desktop files on end-user computers + Implement drag-and-drop for files, images, and widgets + Allow filtering and searching of selectable widget containers such as `ListView` and `ColumnView` + Allow adding custom signals that use the GLib marshalling system + Make all functions that modify the global state thread-safe --- ## Showcase ### Hello World ```julia using Mousetrap main() do app::Application window = Window(app) set_child!(window, Label("Hello World!")) present!(window) end ``` ![](docs/src/assets/readme_hello_world.png) --- ### Swapping between Light- and Dark Themes ```julia set_current_theme!(app, THEME_DEFAULT_LIGHT) ``` ![](docs/src/assets/light_dark_theme.png) --- ### Opening a File Explorer Dialog ```julia file_chooser = FileChooser() on_accept!(file_chooser) do self::FileChooser, files println("selected files: $files") end present!(file_chooser) ``` ![](docs/src/assets/readme_file_chooser.png) --- ### Rendering a Rectangle with OpenGL ```julia render_area = RenderArea() rectangle = Rectangle(Vector2f(-0.5, 0.5), Vector2f(1, 1)) add_render_task!(render_area, RenderTask(rectangle)) ``` ![](docs/src/assets/readme_opengl_rectangle.png) --- ### Displaying a GLMakie Plot in a Mousetrap Window ```julia using GLMakie, MousetrapMakie canvas = GLMakieArea() window = Mousetrap.Window() set_child!(window, canvas) # can be used like any other widget screen = create_glmakie_screen(canvas) display(screen, scatter(rand(123))) ``` ![](docs/src/assets/makie_scatter.png) (**Note**: This feature is still experimental. See [here](https://github.com/Clemapfel/MousetrapMakie.jl) for more information) --- ## Supported Platforms Since `v0.3.0`, Mousetrap is fully portable. All features are available for all 64-bit versions of Linux, FreeBSD, macOS, and Windows. > **Note**: Linux systems running Wayland may require additional configuration before the `RenderArea` widget becomes available. See [here](http://clemens-cords.com/mousetrap/01_manual/09_native_rendering/) for more information. > **Note**: Ubuntu systems using proprietary NVIDIA drivers may encounter a crash on initialization, a fix is available [here](https://github.com/Clemapfel/Mousetrap.jl/issues/25#issuecomment-1731349366). --- ## Documentation Documentation is available [here](https://clemens-cords.com/mousetrap). This includes a tutorial on how to get started using Mousetrap, a manual introducing users to Mousetrap and GUI programming in general, as well as an index of all classes, enums, and functions. --- ## Installation In the Julia REPL, execute: ```julia import Pkg; begin Pkg.add(url="https://github.com/clemapfel/mousetrap.jl") Pkg.test("Mousetrap") end ``` At the end, it should say `Mousetrap tests passed`. > **Note**: On Windows, some `GLib` log messages regarding dbus connections may appear during testing. These do not indicate a problem. > **Note**: On Linux Wayland, a warning regarding EGL displays may appear during installation. See the [here](http://clemens-cords.com/mousetrap/01_manual/09_native_rendering/) for how to fix this issue. --- If you have had Mousetrap version 0.3.0 or earlier installed on your device before, run the following before installing the current version of Mousetrap: ```julia import Pkg begin try Pkg.rm("mousetrap") catch end try Pkg.rm("mousetrap_windows_jll") catch end try Pkg.rm("mousetrap_linux_jll") catch end try Pkg.rm("mousetrap_apple_jll") catch end try Pkg.rm("libmousetrap_jll") catch end Pkg.gc() end ``` This will remove any trace of older versions that may cause conflicts. --- ## Credits & Donations Mousetrap was designed and implemented by [C.Cords](https://clemens-cords.com). It was created with no expectation of compensation and made available for free. Consider **donating** to reward past work and support the continued development of this library: + [GitHub Sponsors](https://github.com/sponsors/Clemapfel) + [PayPal](https://www.paypal.com/donate/?hosted_button_id=8KWF3JTDF8XL2) The goal is for Mousetrap to be 100% stable and flawless when Julia [static compilation](https://github.com/JuliaLang/PackageCompiler.jl) finishes development. Static compilation and the lack of fully featured, easy-to-use, GUI libraries are currently the largest factors as to why Julia is ill-suited for front-end development. Mousetrap aims to address this. --- ## License The current and all past version of Mousetrap, including any text or assets used in Mousetraps documentation, are licensed under [GNU Lesser General Public License (Version 3.0)](https://www.gnu.org/licenses/lgpl-3.0.en.html). This means it can be used in both free, open-source, as well as commercial, closed-source software. ================================================ FILE: docs/make.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # https://github.com/clemapfel/mousetrap.jl # # Copyright © 2023, Licensed under lGPL3-0 # using Documenter, Pkg, InteractiveUtils using Mousetrap let file = open("docs/src/02_library/classes.md", "w+") @info "Mousetrap: Exporting Index..." write(file, "# Index: Classes\n") for name in sort(union( Mousetrap.types, Mousetrap.signal_emitters, Mousetrap.widgets, Mousetrap.event_controllers, Mousetrap.abstract_types, )) if name in [ :Vector2f, :Vector3f, :Vector4f, :Vector2i, :Vector3i, :Vector4i, :Vector2ui, :Vector3ui, :Vector4ui ] continue end if occursin("STYLE_CLASS", string(name)) continue end out = "## $name\n" out *= "```@docs\n" out *= "$name\n" out *= "```\n" binding = getproperty(Mousetrap, name) already_seen = Set{Symbol}() once = true signal_methods = [] non_signal_methods = [] for method in methodswith(binding, Mousetrap) if isnothing(match(r".*_signal_.*", string(method.name))) # first or second argument is type, this is the equivalent of a member function in Julia try if hasproperty(method.sig, :parameters) && (method.sig.parameters[2] == binding || method.sig.parameters[3] == binding) if method.name in [:copy!, :flush, :bind, :download] push!(non_signal_methods, Symbol("Mousetrap." * string(method.name))) else push!(non_signal_methods, method.name) end end catch e end else push!(signal_methods, method.name) end end # sort by signal, as opposed to alphabetically signals = Set{String}() for method in signal_methods m = match(r".*_signal_(.*)_", string(method)) if !isnothing(m) push!(signals, m.captures[1]) end end signal_methods_sorted = [] for signal in sort([signals...]) for method in signal_methods if occursin("_" * signal, string(method)) push!(signal_methods_sorted, method) end end push!(signal_methods_sorted, Symbol("")) end if length(non_signal_methods) + length(signal_methods) > 0 for m in [non_signal_methods..., Symbol(""), signal_methods_sorted...] if once out *= "#### Functions that operate on this type:\n" once = false end as_string = string(m) if isempty(as_string) out *= "\n\n" continue end seen = m in already_seen push!(already_seen, m) try if seen continue elseif binding === String # skip ID typedefs continue elseif getproperty(Mousetrap, m) isa Type # omit ctors continue end catch end # to deal with copy!, flush, etc. out *= "+ [`$m`](@ref)\n" end end out *= "---\n" write(file, out) end close(file) file = open("docs/src/02_library/enums.md", "w+") write(file, "# Index: Enums\n") for enum_name in Mousetrap.enums write(file, "## $enum_name\n") write(file, "```@docs\n") write(file, "$enum_name\n") enum = getproperty(Mousetrap, enum_name) values = [] for value_name in Mousetrap.enum_values if typeof(getproperty(Mousetrap, value_name)) <: enum write(file, "$value_name\n") end end write(file, "```\n") write(file, "---\n") end close(file) file = open("docs/src/02_library/functions.md", "w+") write(file, "# Index: Functions\n") for f in Mousetrap.functions write(file, "## `$f`\n") write(file, "```@docs\n") write(file, "Mousetrap.$f\n") write(file, "```\n") end close(file) end makedocs( sitename="Mousetrap", format = Documenter.HTML( size_threshold_warn = nothing, size_threshold = Integer(2e+6) ) ) ================================================ FILE: docs/src/01_manual/01_installation.md ================================================ # Chapter 1: Installation & Workflow In this chapter, we will learn: + How to install Mousetrap.jl + How to create our first Mousetrap application + Basic Julia skills that are needed to understand the rest of this manual --- ## Installation To install Mousetrap, in the REPL, press the `]` key, then ```julia import Pkg; begin Pkg.add(url="https://github.com/clemapfel/mousetrap.jl") Pkg.test("Mousetrap") end ``` Installation may take a long time. Once installation is succesfull, `Mousetrap tests passed` will be printed. !!! compat "Removing older versions" Mousetraps installation procedure has changed starting with `v0.3.0`. If older versions of Mousetrap are installed on our computer, we should make sure to delete any trace of the older versions by executing the following, before running `add Mousetrap`: ```julia import Pkg begin try Pkg.rm("mousetrap") catch end try Pkg.rm("mousetrap_windows_jll") catch end try Pkg.rm("mousetrap_linux_jll") catch end try Pkg.rm("mousetrap_apple_jll") catch end try Pkg.rm("libmousetrap_jll") catch end end ``` ## Hello World To create our first Mousetrap app, we create a Julia file `main.jl`, with the following contents: ```julia using Mousetrap main() do app::Application window = Window(app) set_child!(window, Label("Hello World!")) present!(window) end ``` To start our app, we navigate to the location of `main.jl` in our console, then execute: ```shell # in same directory as `main.jl` julia main.jl ``` ![](../assets/readme_hello_world.png) !!! compat "GIO Warnings on non-Linux" On Windows and macOS, running `main` may produce a warning of the type: ``` (julia:10512): GLib-GIO-WARNING **: 15:29:40.047: dbus binary failed to launch bus, maybe incompatible version ``` This is due to a non-critical bug in one of Mousetraps dependencies, and does not indicate a problem. !!! compat "Interactive Use" Interactive use inside the Julia REPL is only available for Mousetrap `v0.2.1` or newer. --- ## Julia Crash Course The rest of this manual will assume that readers are familiar with the basics of Julia and some fundamentals of graphics programming. To bring anyone who considers themselves not in this group up to speed, this section contains a crash course on programming basics needed to understand the rest of this manual. ### Glossary The following terms may be unfamiliar to some. #### Invocation To "invoke" a function means to execute it using a command, possibly providing arguments. For example, the second line in the following snippet *invokes function `foo`*: ```julia foo(x) = println(x) # definition foo(1234) # invocation ``` #### Instantiation, Construction In Julia, for a type `T`, to create an actual object of this type, we need to call its *constructor*. This is a function that returns an object of that type: ```julia struct T function T() # constructor return new() end end ``` We call an object returned by a constructor an **instance** of `T`. The act of creating an instance is called **instantiation** of `T`. In the above, `T()` (the constructor, which is a function), *instantiates* an object of type `T`, then returns that *instance*. #### Scope "Scope" refers to where a variable is available after it is defined. For example, the following function introduces what is called a "hard scope", meaning we do not have access to any variable defined inside the *function's scope*, which is the block of code between `function` and `end` ```julia function f(x) # hard scope begin y = x + 1 return y end # hard scope end println(y) # errors because y is not available, it was defined in hard scope ``` `begin`-`end` blocks are a "soft scope", meaning we can access definitions from within this soft scope from the outer scope: ```julia begin # soft scope begin z = 1234 end # soft scope end println(z) # works ``` ``` 1234 ``` A "global" variable is a variable that is defined in *module scope*. For example, in the following snippet, **both** `a` and `b` are defined in module scope: ```julia a = 1234 module M b = "abcd" end ``` This is because all Julia code is scoped in module `Main`. In the above, `a`s scope is `Main`, while `b`s scope is `Main.M`. Both are global in respect to their module. #### Front-End, Back-End, Engine Regarding GUI apps, developers will often refer to "front-end" vs. "back-end" code. The exact meaning of these can vary depending on the field; in this manual, *front-end* refers to any code that produces an object the user can see on screen, meaning the actual GUI. *back-end*, then, is anything that is not *front-end*. An *engine* is a programming library that allows developers to create the *front-end*. For this package, Mousetrap is an *engine* for your (the readers) app. #### Rendering, Frames In our `main.jl` above, Mousetrap created a window and presented it on the physical screen. This process of drawing graphics to the screen is also called *rendering*. Each screen updates at a set frequency, for example 60hz, which means a new image is drawn to the screen every 1/60th of a second. Each of these drawing steps is called a *frame*. This is why we often refer to the speed at which a graphical apps updates as *frames-per-second* (fps), the number of times a new frame is drawn to the screen - per second. In Mousetrap, fps is tied to the monitor's refresh rate. If the user's monitor updates at 120Hz, Mousetrap will attempt to draw a new image 120 times per second. Depending on the user's machine, this could be too costly performance-wise, which is why Mousetrap features a "lazy" rendering process. An area on the screen will only be updated if it needs to be. For example, in the `main.jl` above, the label `"Hello World!"` will only be drawn once. Because it is static (it stays the same and does not move) there is no need to redraw it every frame, unless the window is moved or the label is changed. This is in opposition to how many video games work. Usually, in video game engines, each frame will make it such that the entire screen is re-drawn every time. This difference is important to realize. #### Native Rendering Native rendering, in Mousetrap, is the process of updating the currently displayed frame using the graphics card, making it a hardware-accelerated, GPU-side operation. This is in opposition to CPU-side rendering, which is generally slower. Native rendering in Mousetrap is performed using [OpenGL](https://www.khronos.org/opengl/wiki/), with an entire chapter of this manual dedicated to this technique. --- ## Object-Oriented Design While Julia is technically object-oriented, it lacks many of the features of "proper" OOP languages such as C++ or Java. Examples of missing features include [member functions](https://en.cppreference.com/w/cpp/language/member_functions) and [inheritance from concrete types](https://learn.microsoft.com/en-us/cpp/cpp/inheritance-cpp?view=msvc-170). Additionally, in Mousetrap specifically, most objects will have **no public properties**. To interact with an object, we use *outer methods*, which are functions defined in global scope that operate on one of their arguments by modifying its hidden properties. If our object is of type `T`, an outer method will have the structure ```julia function get_foo(instance::T) ::Foo # ... end function set_foo!(instance::T, new_value::Foo) ::Nothing # ... end ``` Where `get_foo` accesses a hidden property of our `T` instance, while `set_foo!` modifies that property of the instance. The `!` at the end of the method name signals that it will modify the `T` instance. In Mousetrap, only functions marked with `!` will mutate (modify). This is the equivalent of [non-const methods](https://learn.microsoft.com/en-us/cpp/cpp/const-cpp?view=msvc-170#const-member-functions) in other OOP languages. Because we cannot inspect an object's properties to learn about it, we are reliant on the Mousetrap documentation to know which functions are available for which object. Navigating to the [index of classes](../02_library/classes.md), we see that after each class, there is a list of all "member functions", that is, all functions that operate on that object. Another way to find out which functions are available is to use [`methodswith`](https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#InteractiveUtils.methodswith) from within the REPL: ```julia using Mousetrap methodswith(Window) ``` which will print a list of all functions that have at least one argument of type `Window`. --- ## C Enums Mousetraps back-end is written in C++, whose enums differ from Julia enums in several ways. To assure compatibility, Mousetrap uses its own enum definitions, it does not use Julias `@enum`. Each enum is a proper Mousetrap type, while each enum *value* is a numerical constant which is defined as being of that type. For example, the enum `Orientation`, which describes whether an object is vertically or horizontally oriented, is a type called `Mousetrap.Orientation`. The **values** of `Orientation` are global constants: + `ORIENTATION_HORIZONTAL` + `ORIENTATION_VERTICAL` In this example, `Orientation` is the enum, while `ORIENTATION_HORIZONTAL` and `ORIENTATION_VERTICAL` are the enums values. Inspecting the values in the REPL, we see that they are actually just numbers: ``` julia> ORIENTATION_HORIZONTAL Orientation(0) julia> ORIENTATION_VERTICAL Orientation(1) ``` But both are of type `Orientation`: ```julia julia> ORIENTATION_HORIZONTAL isa Orientation && ORIENTATION_VERTICAL isa Orientation true ``` All enum values are written in `SCREAMING_SNAKE_CASE`, while the enum types name uses `UpperCamelCase`. To check which enum has which values, we can again use the [Mousetrap documentation](../02_library/enums.md). --- ## Do Syntax In Julia, any function whose **first argument is another function** can use **do-syntax**. For example, the following function takes two arguments: ```julia function example_f(f, arg) f(arg) end ``` It applies its first argument, a function, to its second argument. Invoking `example_f`, we could do: ```julia to_invoke(x::Integer) = println(x) example_f(to_invoke, 1234) ``` ``` 1234 ``` Where `to_invoke` is used as the **first** argument. Because it is the first, we can also write the above using do-syntax: ```julia example_f(1234) do x::Integer println(x) end ``` Here, the first argument of `example_f` was omitted, while the second argument, `1234` remained. Instead of the first argument, we append the line `do x::Integer`, where `x` is the name of the anonymous function's argument. After this line, we define the function's body, then `end`. ## Anonymous Functions in Stacktraces In the REPL, we can print any object's name to inspect it. Creating a new function, which prints its argument's name: ```julia print_f_name(f) = println(f) ``` We see that if we invoke this function using regular function syntax, we get the following output: ```julia function to_invoke() # do nothing end print_f_name(to_invoke) ``` ``` to_invoke ``` If we instead call this function using do-syntax: ```julia print_f_name() do # do nothing end ``` ``` #9 ``` We get `#9`. This is a **temporary name** used by Julia to keep track of anonymous functions. A stacktrace in Mousetrap will often contain many anonymous function names like this: ```julia main() do app::Application throw(ErrorException("error")) end ``` ``` [ERROR] In Mousetrap.main: error Stacktrace: [1] (::var"#11#12")(app::Application) @ Main ./REPL[15]:2 [2] (::TypedFunction)(args::Application) @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:74 [3] (::Mousetrap.var"#15#17"{TypedFunction})(app::Application) @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:1571 [4] (::TypedFunction)(args::Application) @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:74 [5] (::Mousetrap.var"#6#8"{TypedFunction})(x::Tuple{CxxWrap.CxxWrapCore.CxxRef{Mousetrap.detail._Application}}) @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:620 [6] safe_call(scope::String, f::Function, args::Tuple{CxxWrap.CxxWrapCore.CxxRef{Mousetrap.detail._Application}}) @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:144 [7] run!(arg1::Mousetrap.detail._ApplicationAllocated) @ Mousetrap.detail ~/.julia/packages/CxxWrap/aXNBY/src/CxxWrap.jl:624 [8] run!(app::Application) @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:1538 [9] (::Mousetrap.var"#14#16"{var"#11#12", String})() @ Mousetrap ~/Workspace/Mousetrap.jl/src/Mousetrap.jl:1581 ``` We see that the anonymous function was allocated as `var"#11#12"`. This refers to the function defined using the do-block after `main()`. Mousetrap stacktraces can get quite long, so it's best to parse them by reading the original message at the top first: ``` [ERROR] In Mousetrap.main: error ``` We see that the message mentions that the error occurred during invokation of `Mousetrap.main`. We should therefore look for an error inside the do-block after `main`. Knowledge about anonymous functions and how to read stacktraces will greatly aid us in debugging Mousetrap applications while learning. ================================================ FILE: docs/src/01_manual/02_signals.md ================================================ # Chapter 2: Signals In this chapter, we will learn: + What signals and signal handlers are + How to connect to a signal + How to check which signature a signal expects + How and why to block signals --- ## Signal Architecture Central to Mousetrap, as well as other GUI libraries like [GTK4](https://docs.gtk.org/gtk4/) and Qt, is **signal architecture**, or [**signal programming**](https://en.wikipedia.org/wiki/Signal_programming), which is a type of software architecture that triggers behavior using signals. A **signal**, in this context, has three components: + an **ID**, which uniquely identifies it. IDs may not be shared between signals + an **emitter**, which is a non-signal object + a **callback** or **signal handler**, which is a function called when an emitter emits a signal It may be easiest to consider an example: One of the simpler interactions with a GUI is clicking a button. In Mousetrap, the [`Button`](@ref) class is made for this purpose. `Button` has the signal `clicked`, which is emitted when a user presses the left mouse button while the cursor hovers over the graphical element of the button. In this case, the signals' **ID** is `clicked`, while the signal **emitter** is an instance of `Button`. When a user clicks the button, the in-memory object emits signal `clicked`. If we want to tie program behavior to the user clicking the button, we **connect a callback** (a function) to this signal. Once connected, when the button is clicked, `clicked` is emitted, which in turn will trigger invocation of the connected function: ```julia # create `Button` instance button = Button() # create a signal handler on_clicked(self::Button) = println("clicked") # connect signal handler to the signal connect_signal_clicked!(on_clicked, button) ``` Which can also be written more succinctly using [do-syntax](https://docs.julialang.org/en/v1/manual/functions/#Do-Block-Syntax-for-Function-Arguments): ```julia button = Button() connect_signal_clicked!(button) do self::Button println("clicked") end ``` !!! details "Running Code Snippets" In this section, code snippets will only show the relevant lines. To compile and run the code stated here, we need to create a Julia script with the following content: ```julia using Mousetrap main() do app::Application window = Window(app) # code snippet goes here set_child!(window, widget) # add whatever widget the code snippet is about here present!(window) end ``` For example, to execute the example snippet above, we would create the following `main.jl` file: ```julia using Mousetrap main() do app::Application window = Window(app) # snippet start button = Button() connect_signal_clicked!(button) do self::Button println("clicked") end # snippet end set_child!(window, button) # add the button to the window present!(window) end ``` Then execute it from the console by calling `julia main.jl` When we execute this code, we see that a small window opens that contains our button. By clicking it, we get: ``` clicked ``` Only one callback can be connected to each signal of a signal emitter. If we call `connect_signal_clicked!` again with a new callback, the old callback will be overridden. If we want to trigger two functions, `callback_01` and `callback_02` at the same time, we can simply do the following: ```julia callback_01() = # ... callback_02() = # ... # call both functions from the signal handler connect_signal_clicked!(button) do self::Button callback_01() callback_02() end ``` --- ## SignalEmitters `Button`, like most classes in Mousetrap, is a subtype of an abstract type called [`SignalEmitter`](@ref). Subtyping `SignalEmitter` is equivalent to saying "This object can emit signals". Not all objects in Mousetrap are signal emitters, but most are. When we say "an object can emit signal ``", what that means is that the following functions are defined for that object: + `connect_signal_!` + `disconnect_signal_!` + `emit_signal_` + `set_signal__blocked!` + `get_signal__blocked` For example, `Button` supports the signal with ID `clicked`, so the following functions are defined for it: + `connect_signal_clicked!` + `disconnect_signal_clicked!` + `emit_signal_clicked` + `set_signal_clicked_blocked!` + `get_signal_clicked_blocked` We'll now go through what each of these functions does and how to use them. --- ## Connecting Signal Handlers Above, we've already seen an example of how to connect a signal handler to a signal using `connect_signal_clicked!`. What may not have been obvious is that the signal handler, the anonymous function in the above code snippet, is required to **conform to a specific signature**. !!! note "Function Signature Syntax" A function's **signature** describes a function's return- and argument types. For example, the function ```julia function foo(i::Int32, s::String) ::String return string(i) * s end ``` Has the signature `(::Int32, ::String) -> String`. It takes a 32-bit integer and a string, and it returns a string. The anonymous function from this do-block: ```julia connect_signal_clicked!(button) do self::Button println("clicked") end ``` has the signature `(::Button) -> Nothing`. It takes an instance of type `Button` and returns `nothing`. For a function with an optional argument like this: ```julia function foo_optional(i::Int32, string::String, optional::Bool = true) ::String return string(i) * string * string(optional) end ``` We convey that the last argument is optional by enclosing it in `[]`: `(::Int32, ::String, [::Bool]) -> String` In general, a function with argument types `Arg1_t, Arg2_t, ...`, return type `Return_t`, and optional arguments `Optional1_t, Optional2_t` has the signature ``` (Arg1_t, Arg2_t, ..., [Optional1_t, Optional2_t, ...]) -> Return_t`. ``` If and only if the `Return_t` of a function is `Nothing`, we can omit the return types along with the trailing `->`. Each signal requires it's a callback to conform to a specific signature. This signature is different for each signal. If we attempt to connect a handler that has the wrong signature, an `AssertionError` will be thrown at compile time. This makes it important to know how to check which signal requires which signature. ## Checking Signal Signature Working with our example, signal `clicked` of class `Button`, let's say we do not know what function signature this signal expects. To find out, we check the Mousetrap documentation, either by visiting [`Button`](@ref)s documentation online, or from within the REPL by pressing `?` and entering the name of the class we want to look up: ``` help?> Mousetrap.Button ``` ``` Button <: Widget ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ Button with a label. Connect to signal clicked or specify an action via set_action! in order to react to the user clicking the button. Constructors ============== Button() Button(label::Widget) Button(::Icon) Signals ========= │ clicked │ --------- │ │ │ (::Button, [::Data_t]) -> Nothing │ │ Emitted when the user clicks a widget using a mouse or touchscreen. Fields ======== (no public fields) Example ========= button = Button() set_child!(button, Label("Click Me")) connect_signal_clicked!(button) do x::Button println("clicked!") end set_child!(window, button) ``` We see that button has a single signal, `clicked`. Along with this information, a description of when that signal is emitted is given, and that it requires the signature `(::Button, [::Data_t]) -> Nothing`, where `Data_t` is an optional argument of arbitrary type, which we can use to hand data to the signal handler. ## Handing Data to Signal Handlers While we do get passed the signal emitter instance as the first argument to the signal handler, `::Button` in this case, we will often need to reference other objects. This may necessitate accessing global variables, which [is discouraged in Julia](https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-untyped-global-variables). Instead, Mousetrap allows adding an optional, arbitrarily typed, *single* argument to the end of any signal handler signature. This object is often referred to as `data`, its type will therefore be called `Data_t`. Expanding on our previous example, if we want to send a customized message when the user clicks our button, we can change the signal handler as follows: ```julia button = Button() # new signal handler that takes two arguments on_clicked(self::Button, data) = println(data) # connect the signal handler, providing a third argument as `data` connect_signal_clicked!(on_clicked, button, "custom message") ``` Or, using do-syntax: ```julia button = Button() connect_signal_clicked!(button, "custom message") do self::Button, data println(data) end ``` By clicking the button, we now get: ``` custom message ``` Any and all objects can be provided as `data`, but they have to be packaged as exactly **one** argument. ### Grouping Data Arguments Because there is only one `data`, it may seem limiting as to what or how much data we can pass to the signal handlers. In practice, this is not true, because we can use a simple trick to group any number of objects into a single argument. Let's say we want to forward a string `"abc"`, an integer `999`, and a vector of floats `[1.0, 2.0, 3.0]` to the signal handler. To achieve this, we can do the following: ```julia button = Button() function on_clicked(self::Button, data) println(data.string_value) println(data.integer_value) println(data.vector_value) end # create a named tuple that groups the arguments named_tuple = ( string_value = "abc", integer_value = 999, vector_value = [1.0, 2.0, 3.0] ) # provide the named tuple as connect_signal_clicked!(on_clicked, button, named_tuple) ``` Here, we grouped the values in a [named tuple](https://docs.julialang.org/en/v1/manual/types/#Named-Tuple-Types), then accessed each value using an easy-to-read name. Again, we can write the above more succinctly using do-syntax: ```julia button = Button() connect_signal_clicked!(button, ( string_value = "abc", integer_value = 999, vector_value = [1.0, 2.0, 3.0] )) do self::Button, data println(data.string_value) println(data.integer_value) println(data.vector_value) end ``` Using this technique, we can forward any objects to the signal handler via the optional `[::Data_t]` argument. This technique is available for all signals. ## Implicit Return Types Julia allows functions to return a value without using the `return` keyword. This may cause side effects in applications where functions are required to conform to a specific signature. Consider the following example: ```julia button = Button() to_append = [] function on_clicked(self::Button) push!(to_append, 1234) end connect_signal_clicked!(on_clicked, button) ``` Here we are appending a value to `to_append`, a vector, from within the signal handler for signal `clicked`, which is required to have the signature: ```julia (::Button, [::Data_t]) -> Nothing ``` `on_clicked`, in this example, does not explicitly return a value, yet running the above code we get: ``` [ERROR] In Mousetrap.main: AssertionError: Object `on_clicked` is not invokable as function with signature `(Button) -> Nothing`, because its return type is not `Nothing` ``` This is because `Base.push!` actually **does** return a value, the vector it is operating on: ```julia to_append = [] out = push!(to_append, 1234) out == to_append # true ``` Because `push!` returns a value and it is the last line of the `on_clicked` definition, `on_clicked`, in turn, returns a value, meaning its return type is no longer `Nothing`, which triggers the error. In Mousetrap, all functions whose documentation does not explicitly mention a return type will return `nothing`. This may not be true for functions in `Base` or foreign libraries, so we should take care to be aware of implicit return types. To fix the above error, we should return `nothing` manually: ```julia to_append = [] function on_clicked(self::Button) push!(to_append, 1234) return nothing end connect_signal_clicked!(on_clicked, button) # works ``` --- ## Blocking Signal Emission If we want an object to *not* call the signal handler on signal emission, we have two options: Using `disconnect_signal_`, we can **disconnect** the signal, which will permanently remove the registered signal handler, deallocating it and fully dissociating it from the original signal emitter instance. This is a quite costly operation and should only rarely be necessary. A much more performant and convenient method to **temporarily** prevent signal emission is **blocking** the signal. Blocking a signal will prevent the invocation of the signal handler. This means, for our `Button` example, once we call: ```julia set_signal_clicked_blocked(button, true) ``` The user can still click the button, but the connected handler is not called. To block a signal, we use `set_signal__blocked!`, which takes a boolean as its second argument. We can check whether a signal is currently blocked using `get_signal__blocked`. If no signal handler is connected, this function will return `true`. ### Signal Blocking: An Example When is blocking necessary? Consider the following use-case: ```julia # declare two buttons button_01 = Button() button_02 = Button() # when button 01 is clicked, 02 is triggered programmatically connect_signal_clicked!(button_01, button_02) do button_01::Button, button_02::Button # button_01 is self, button_02 is data println("01 clicked") emit_signal_clicked(button_02) end # when button 02 is clicked, 01 is triggered programmatically connect_signal_clicked!(button_02, button_01) do button_02::Button, button_01::Button # button_02 is self, button_01 is data println("02 clicked") emit_signal_clicked(button_01) end # add both buttons to the window set_child!(window, hbox(button_01, button_02)) ``` In which we use [`emit_signal_clicked`](@ref), which manually triggers emission of signal `clicked`. [`hbox`](@ref) in the last line makes it so that we can display both buttons in the window. The intended behavior is that if the user clicks either one of the buttons, both buttons emit their signal. Clicking one button should always trigger both, regardless of which button is clicked first. Running the above code as-is and clicking `button_01`, we get the following output: ``` 01 clicked 02 clicked 01 clicked 02 clicked 01 clicked 02 clicked 01 clicked 02 clicked ... ``` And our application deadlocks. This is, of course, extremely undesirable, so let's talk through why this happens. When `button_01` is clicked, it emits signal `clicked`, which invokes the connected signal handler. Going line-by-line through the handler : + `button_01`s handler prints `"01 clicked"` + `button_01`s handler activates `button_02`, triggering emission of signal `clicked` on `button_02` + `button_02`s handler prints `"02 clicked"` + `button_02`s handler activates `button_01`, triggering emission of signal `clicked` on `button_01` + `button_01`'s handler prints `"01 clicked"` + etc. We created an infinite loop. We can avoid this behavior by **blocking signals** at strategic times: ```julia button_01 = Button() button_02 = Button() connect_signal_clicked!(button_01, button_02) do button_01::Button, button_02::Button println("01 clicked") # block self (01) set_signal_clicked_blocked!(button_01, true) # activate other (02) emit_signal_clicked(button_02) # unblock self (01) set_signal_clicked_blocked!(button_01, false) end connect_signal_clicked!(button_02, button_01) do button_02::Button, button_01::Button println("02 clicked") # block self (02) set_signal_clicked_blocked!(button_02, true) # activate other (01) emit_signal_clicked(button_01) # unblock self (02) set_signal_clicked_blocked!(button_02, false) end set_child!(window, hbox(button_01, button_02)) ``` Let's talk through what happens when the user clicks one of the two buttons now, again assuming `button_01` is the first to be clicked: + `button_01` invokes its signal handler + `button_01`s signal handler prints `01 clicked` + `button_01` blocks invocation of its signal handler + `button_01` activates `button_02`, triggering emission of signal `clicked` + `button_02`s signal handler prints `02 clicked` + `button_02` blocks invocation of its signal handler + `button_02` attempts to activate `button_01`, **but that buttons signal is blocked, so nothing happens** + `button_02` unblocks itself + `button_01` unblocks itself + both signal handlers return normally ``` 01 clicked 02 clicked ``` By blocking signals, we get the correct behavior of both buttons being triggered exactly once. Because they unblock themselves at the end of the signal handler, after the two buttons are done, everything returns to the way it was before, meaning both buttons can be clicked once again. To verify this is indeed the resulting behavior, we can use the following `main.jl`: ```julia using Mousetrap main() do app::Application window = Window(app) button_01 = Button() button_02 = Button() connect_signal_clicked!(button_01, button_02) do button_01::Button, button_02::Button println("01 clicked") set_signal_clicked_blocked!(button_01, true) emit_signal_clicked(button_02) set_signal_clicked_blocked!(button_01, false) end connect_signal_clicked!(button_02, button_01) do button_02::Button, button_01::Button println("02 clicked") set_signal_clicked_blocked!(button_02, true) emit_signal_clicked(button_01) set_signal_clicked_blocked!(button_02, false) end set_child!(window, hbox(button_01, button_02)) present!(window) end ``` ![](../assets/double_button_signal_blocking.png) --- ================================================ FILE: docs/src/01_manual/03_actions.md ================================================ # Chapter 3: Actions In this chapter, we will learn: + How and why to use the command pattern to encapsulate application functionality + How to create and use `Action` + How to trigger actions using `Button`, or by pressing a keyboard shortcut --- ## Introduction: The Command Pattern As we create more and more complex applications, keeping track of how / when to trigger which functionality gets harder and harder. An application can have hundreds, if not thousands, of functions, all linked to one or more triggers such as buttons, menus, keyboard shortcuts, etc. Things will get out of hand very quickly, which is why there's a software design pattern just for this purpose: the [**command pattern**](https://en.wikipedia.org/wiki/Command_pattern). A **command**, henceforth called **action**, is an object that has the following components: + A **function**, which is the action's behavior + An **ID** that uniquely identifies the action + An optional **shortcut trigger**, also often called a keybinding In Mousetrap, a command is represented by the type [`Action`](@ref). ## Action As early as possible, we should drop the habit of defining application behavior inside a global function. Unless a function is used exactly once, it should be an action. For example, in the previous chapter, we declared a [`Button`](@ref) with the following behavior: ```julia button = Button() connect_signal_clicked!(button) do self::Button println("clicked") end ``` In this section, we will learn how to reproduce this behavior using the command pattern and why we should prefer this over connecting a signal handler for signal `clicked`. ### Action IDs When creating an action, we first need to choose the action's **ID**. An ID is an identifier that uniquely identifies the action. The ID can only contain the character `[a-zA-Z0-9_-.]`, that is, all Roman letters, numbers `0` to `9`, `_`, `-` and `.`. The dot is usually reserved to simulate scoping. For example, one action could be called `image_file.save`, while another is called `text_file.save`. Both actions say what they do, `save` a file, but the prefix makes it clear which part of the application they act on. An appropriate ID for our button behavior would therefore be `example.print_clicked`. ### Action Function Armed with this ID, we can create an action: ```julia action = Action("example.print_clicked", app) ``` Where `app` is the application instance from our `main`. The second part of an action is its function, also called its callback. We assign an actions function using [`set_function!`](@ref): ```julia function on_example_print_clicked(x::Action) ::Nothing println("clicked") end action = Action("example.print_clicked", app) set_function!(on_example_print_clicked, action) ``` or, using do-syntax: ```julia action = Action("example.print_clicked", app) set_function!(action) do x::Action println("clicked") end ``` The function registered using `set_function!` is required to have the following signature: ```julia (::Action, [::Data_t]) -> Nothing ``` We see that, much like with signal handlers, the callback is provided the `Action` instance, along with an optional `data` argument. `Action` also provides a constructor that directly takes the function as its first argument. Using this, we can write the above even more succinctly: ```julia action = Action("example.print_clicked", app) do x::Action println("clicked") end ``` ### Triggering Actions At any point, we can call [`activate!`](@ref) to trigger the actions' callback. This is not the only way to trigger an action, however. `Button` provides [`set_action!`](@ref), which makes it such that when the button is clicked, the action is triggered: ```julia action = Action("example.print_clicked", app) set_function!(action) do x::Action println("clicked") end button = Button() set_action!(button, action) ``` ``` clicked ``` So far, this doesn't seem to have any upsides over just connecting to signal `clicked`. This is about to change. ## Disabling Actions Similarly to how blocking signals work, we can disable an action using [`set_enabled!`](@ref). If set to `false`, calling `activate!` will trigger no behavior. Furthermore, **all objects the action is connected to are automatically disabled**. This means we do not need to keep track of which button calls which action. To disable all of them, we can simply disable the action. ## Action Maps We recall that `Action`s constructor requires an instance of our `Application` as its second argument. This is because the two are linked internally, all actions are registered with the application and are accessible only from within that application. In this way, `Application` itself acts as an **action map**, an index of all actions. Once `set_function!` was called, we can, at any point, retrieve the action from the application using `get_action!`: ```julia let action = Action("example.print_clicked", app) set_function!(action) do x::Action println("clicked") end end # `action` is no longer defined here because of `let` activate!(get_action(app, "example.print_clicked")) # but we can retrieve it anyway ``` ``` clicked ``` Where we used a [let-block](https://docs.julialang.org/en/v1/base/base/#let) to create a "hard" scope, meaning at the end of the block, `action`, the Julia-side object, is no longer defined. We can nonetheless retrieve it by calling `get_action!` on our `Application` instance. This way, we do not have to keep track of actions ourselves; by simply remembering the action's ID, we can, at any point, trigger the action from anywhere in our application. --- ## Shortcuts An action can have any number of optional **shortcut triggers**, which are also called **keybindings**. A keybinding is a combination of keyboard keys that, when pressed, trigger an action exactly once. Common keyboard shortcuts familiar to most users of modern operating systems are `Control + C` to copy, `Control + A` to "select all", etc. Most of the time, we will have to implement behavior like this and associate a shortcut with it manually, using actions. ### Shortcut Trigger Syntax Before we can learn about keybindings, we need to talk about keys. In Mousetrap, keyboard keys are split into two groups: **modifiers** and **non-modifiers**. A modifier is one of the following: + `Shift` + `Control` + `Alt` !!! note Additional modifiers include `CapsLock`, `AltGr`, `Meta`, `Apple`, and `Win`. These are keyboard-layout and/or OS-specific. See [here](https://docs.gtk.org/gdk4/flags.ModifierType.html) for more information. A non-modifier, then, is any key that is not a modifier. A keybinding, or **shortcut trigger**, henceforth also called "shortcut", is the combination of **any number of modifiers, along with exactly one non-modifier key**. A few examples: + `a` (that is the `A` keyboard key) is a shortcut + `plus` (that is the `+` keyboard key, along with the `Control` and `Shift` modifiers) is a shortcut + `` is **not** a shortcut, because it does not contain a non-modifier + `xy` (that is the `X` key *and* the `Y` key) is **not** a shortcut because it contains more than one non-modifier key Shortcuts are represented as strings, which have a specific syntax. As seen above, each modifier is enclosed in `<``>`, with no spaces in between. After the group of modifiers, the non-modifier key is placed after the last modifier`>`. Some more examples: + "Control + C" is written `c` + "Alt + LeftArrow" is written as `Left` (sic, `L` is capitalized) + "Shift + 1" is written as `exclam` That last one requires an explanation. On most keyboard layouts, to type `!`, the user has to press the shift modifier key, then press the `1` key. When "Shift + 1" is pressed, Mousetrap does not receive this keyboard key event as-is, instead, it receives a single key event for the `!` key with no modifiers. The identifier of `!` is `exclam`, hence why "Shift + 1" is written as `exclam`. !!! tip "Looking up Key Identifiers" An example of how to look up the identifier of any key will be performed here. Let's say we want to write the shortcut "Control + Space". We know that we can write "Control" as ``. Next, we navigate to [https://github.com/Clemapfel/mousetrap.jl/blob/main/src/key_codes.jl](https://github.com/Clemapfel/mousetrap.jl/blob/main/src/key_codes.jl), which has a list of all keys recognized by Mousetrap. In [line 1039](https://github.com/Clemapfel/mousetrap.jl/blob/main/src/key_codes.jl#L1039), we find that the constant for the space key is called `KEY_space`. The identifier of a key used for shortcuts is this name, without the `KEY_` prefix. For the space bar key, the enum value is `KEY_space`, the identifier is therefore `space`. One more obscure example: to write "Alt + Beta", that is, the `β` key on the somewhat rare Greek keyboard layout, we find the constant named `KEY_Greek_BETA` in [line 3034](https://github.com/Clemapfel/mousetrap.jl/blob/main/src/key_codes.jl#L3034). Erasing `KEY_` again, the key's identifier is `Greek_BETA`. "Alt + Beta" is therefore written as `Greek_BETA` If we make an error and use the wrong identifier, a soft warning will be printed at runtime, informing us of this. To access a list of key codes from within the REPL, we can search the vector `Mousetrap.key_codes`, which contains the symbols of all key codes recognized by Mousetrap, with the `KEY_` prefix already removed: ```julia julia> Mousetrap.key_codes 2278-element Vector{Symbol}: :0 :1 :2 :3 ... ``` !!! warning "Operating System Priority" Depending on the operating system, some shortcuts will already be assigned. If this is the case, we should take care not to use them in our application. For example, the abovementioned `space` shortcut [is reserved for changing input sources on macOS](https://discussions.apple.com/thread/8507324), while on Windows `Delete` will always open the task manager. ### Assigning Shortcuts to Actions Now that we know how to write a shortcut as a shortcut trigger string, we can assign it to our actions. For this, we use [`add_shortcut!`](@ref): ```julia shortcut_action = Action("example.shortcut_action", app) do self::Action println("shortcut action called") end add_shortcut!(shortcut_action, "M") ``` An action can have multiple shortcuts, and one shortcut can be associated with two or more actions, though the latter is usually not recommended. We need one more thing before we can trigger our action: an object that can receive keyboard key events. We will learn much more about the event model [in the chapter dedicated to it](./05_event_handling.md). For now, we can use [`set_listens_for_shortcut_action!`](@ref) on our top-level window. This makes the window instance listen for any keyboard presses. If it recognizes that a keybinding associated with an action it is listening for was pressed, it will trigger that action. A complete `main.jl` file showing how to trigger an action using a shortcut is given here: ```julia using Mousetrap main() do app::Application # create a window window = Window(app) # create an action that prints `shortcut action called` action = Action("example.shortcut_action", app) do action::Action println("shortcut action called") end # add the shortcut `Control + M` add_shortcut!(action, "M") # make `window` listen for all shortcuts of `action` set_listens_for_shortcut_action!(window, action) # show the window to the user present!(window) end ``` Pressing "Control + M", we get: ``` shortcut action called ``` ================================================ FILE: docs/src/01_manual/04_widgets.md ================================================ # Chapter 4: Widgets In this chapter, we will learn: + What a widget is + Properties that all widgets share + What widgets are available in Mousetrap and how to use each of them + How to create compound widgets --- !!! note "Running snippets from this Chapter" To run any partial code snippet from this section, we can use the following `main.jl` file: ```julia using Mousetrap main() do app::Application window = Window(app) # snippet here, creates widget, and adds it to `window' using `set_child!` present!(window) end ``` !!! note "Images in this Chapter" Images for this chapter were captured on a Fedora Linux machine running Gnome 44.2. The exact look of each window and widget may be slightly different, depending on the user's operating system and application theme. We will learn how to manually change the look of widgets by creating our own theme in the [section on app customization](./10_theme_customization.md). --- ## What is a widget? Widgets are the central element of all GUI applications. In general, a widget is anything that can be rendered on screen. Often, widgets are **interactable**, which means that the user can trigger behavior by interacting with the widget using a device such as a mouse, keyboard, or touch screen. For example, to interact with the widget [`Button`](@ref) from the previous chapter, the user has to move the cursor over the area of the button on the screen, and then press the left mouse button. This will trigger an animation where the button changes its appearance to look "pressed in", emit its signal `clicked` to trigger custom behavior, then return to its previous state. Having used computers for many years, most of us never think about how things work in this gradual of a manner. `Button` makes it so we don't have to, all of these steps are already implemented for us. All we have to do is place the button and connect to its signals. ## Widget Signals In Mousetrap, [`Widget`](@ref) is an abstract type that all widgets subtype. `Widget` is a subtype of `SignalEmitter`, meaning all widgets are signal emitters, but not all signal emitters are widgets. All widgets **share a number of signals**. These signals are accessible for every subtype of `Widget`: | Signal ID | Signature | |------------|---------------------------------| | `realize` | `(::T, [::Data_t]) -> Nothing` | | `destroy` | `(::T, [::Data_t]) -> Nothing` | | `show` | `(::T, [::Data_t]) -> Nothing` | | `hide` | `(::T, [::Data_t]) -> Nothing` | | `map` | `(::T, [::Data_t]) -> Nothing` | | `unmap` | `(::T, [::Data_t]) -> Nothing` | Where `T` is the subtype. For example, since `Button` is a `Widget`, the signature of `Button`s signal `realize` is `(::Button, [::Data_t]) -> Nothing`. By the end of this chapter, we will have learned when all these signals are emitted and what they mean. For now, we will just note that all widgets share these signals. For any class subtyping `Widget`, these signals are available. --- ## Widget Properties When displayed on screen, a widget's size and location will be chosen dynamically. Resizing the window may or may not resize all widgets inside such that they fill the entire window. A somewhat complex heuristic determines the exact position and size of a widget during runtime. We can influence this process using multiple properties all widgets share. Each widget will choose a position and size on screen. We call this area, an [axis-aligned rectangle](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box), the widget's **allocated area**. The allocated area can change over the course of runtime, most widgets are easily resized either by us, the developers, or the user. ### Parent and Children Widgets can be inside other widgets. A widget that can contain other widgets is called a **container** widget. Each widget inside this container is called the **child** of the container. `Window`, in our previous `main.jl`, is a container widget, we inserted `Button`, a widget, into it using `set_child!`. How many children a widget can contain depends on the type of widget. Some may have no children, exactly one child, exactly two, exactly three, or any number of children. Shared for all widgets, however, is that each widget has exactly one **parent**. This is the widget it is contained within. Because a widget can only have exactly one parent, we cannot put the same widget instance into two containers. If we want two identical `Button`s in two different positions on screen, we have to create two button instances. ```julia box_a = Box(ORIENTATION_HORIZONTAL) box_b = Box(ORIENTATION_HORIZONTAL) button = Button() # insert `button` into box A push_back!(box_a, button) # insert `button` into box B also push_back!(box_b, button) ``` This latter call will print a warning ``` (julia:445703): Mousetrap-CRITICAL **: 18:13:31.132: In Box::push_back: Attempting to insert widget into a container, but that widget already has a parent. ``` because `button`s parent is already `box_a`. Instead, we should create two buttons: ```julia box_a = Box(ORIENTATION_HORIZONTAL) box_b = Box(ORIENTATION_HORIZONTAL) button_a = Button() button_b = Button() push_back!(box_a, button_a) push_back!(box_b, button_b) ``` By connecting the same handler to both of these buttons' signals, we have two identically behaving objects, that are still separate widget instances. ### Size Request Moving onto the properties that determine the widget's size, we have its **size request**. This is a [`Vector2f`](@ref Vector2) which governs the minimum width and height of the widget, in pixels. Once set with [`set_size_request!`](@ref), no matter what, that widget will always allocate at least that amount of space. By default, all widget's size request is `Vector2f(0, 0)`. Setting the width and/or height of a widget's size request to `0` will tell the size manager, the algorithm determining the widget's final size on screen, that the widget has not requested a minimum size. A size request may not be negative. Manipulating the size request to influence a widget's minimum size is also called **size-hinting**. ### Accessing Widget Size We can query information about a widget's current and target size using multiple functions, some of which are only available after a widget is **realized**. Realization means that the widget is initialized, has chosen its final size on screen, and is ready to be displayed. When these conditions are met, any widget will emit its signal `realize`. Once realized, [`get_allocated_size`](@ref) and [`get_position`](@ref) return the current size and position of a widget, in pixels. This size may or may not be equal to what we size-hinted the widget to, as size-hinting only determines the widget's *minimum size*. The layout manager is free to allocate a size larger than that. Lastly, [`get_natural_size`](@ref) will access the size preferred by the layout manager. This size will always be equal to or larger than the size requested. When trying to predict the size a widget has, `get_natural_size` will give us the best estimate. Once the widget and all its children are realized, `get_allocated_size` and `get_position` will give us the exact value. Layout management is complex and the algorithm behind managing the size of the totality of all widgets is sophisticated. Users of Mousetrap are not required to understand this exact mechanism, only how to influence it. On top of a widget's size request, a widget's final allocated size depends on a number of other variables. ### Margin Any widget has four margins: `start`, `end`, `top`, and `bottom`. Usually, these correspond to space left, right, above, and below the widget, respectively. Margins are rendered as empty space added to the corresponding side of the widget. In this way, they work similarly to the [css properties of the same name](https://www.w3schools.com/css/css_margin.asp), though in Mousetrap, margins may not be negative. We use [`set_margin_start!`](@ref), [`set_margin_end!`](@ref), [`set_margin_top!`](@ref) and [`set_margin_bottom!`](@ref) to control each individual margin: ```julia widget = # ... set_margin_start!(widget, 10) set_margin_end!(widget, 10) set_margin_top!(widget, 10) set_margin_bottom!(widget, 10) # equivalent to set_margin_horizontal(widget, 10) set_margin_vertical(widget, 10) # equivalent to set_margin!(widget, 10) ``` Where [`set_margin_horizontal!`](@ref), [`set_margin_vertical!`](@ref) set two of the margins at the same time, while [`set_margin!`](@ref) sets all four margins at once. Margins are used extensively in UI design. They make an application look more professional and aesthetically pleasing. A good rule of thumb is that for a 1920x1080 display, the **margin unit** should be 10 pixels. That is, all margins should be a multiple of 10. If the display has a higher or lower resolution, the margin unit should be adjusted. ### Expansion If the size of the parent of a widget changes, for example when resizing the window a `Button` is contained within, the child widget may or may not **expand**. Expansion governs if a widget should fill out the entire space available to it. We set expansion along the x- and y-axis separately using [`set_expand_horizontally!`](@ref) and [`set_expand_vertically!`](@ref). If set to `false`, a widget will usually not grow past its natural size. [`set_expand!`](@ref) is a convenience function that sets expansion along both axes simultaneously. ```julia widget = # ... set_expand_horizontally!(widget, true) set_expand_vertically!(widget, true) # equivalent to set_expand!(widget, true) ``` ### Alignment Widget **alignment** governs where inside its container a widget will attempt to position itself. An example: a `Button` size-hinted to 100x100 pixels has expansion disabled (`set_expand!` was set to `false`). It has a margin of 0 and is placed inside a `Window` of 200x200. When we scale the window, the button will not change size, and the button does not fill the entire area of the window. Alignment, then, governs **where in the window the button is positioned**. We set alignment for the horizontal and vertical axis separately using [`set_horizontal_alignment!`](@ref) and [`set_vertical_alignment!`](@ref), which both take values of the enum [`Alignment`](@ref). This enum has three possible values, whose meaning depends on whether we use this value for the horizontal or vertical alignment: + `ALIGNMENT_START`: left if horizontal, top if vertical + `ALIGNMENT_END`: right if horizontal, bottom if vertical + `ALIGNMENT_CENTER`: center of axis, regardless of orientation We note that the horizontal x-axis is oriented from left to right, while the vertical y-axis is oriented from top to bottom. For our example, the button would take on these locations based on which enum value we chose for each alignment axis: | Vertical Alignment | Horizontal Alignment | Resulting Position | |--------------------|----------------------|---------------------| | `ALIGNMENT_START` | `ALIGNMENT_START` | top left corner | | `ALIGNMENT_START` | `ALIGNMENT_CENTER` | top center | | `ALIGNMENT_START` | `ALIGNMENT_END` | top right corner | | `ALIGNMENT_CENTER` | `ALIGNMENT_START` | center left | | `ALIGNMENT_CENTER` | `ALIGNMENT_CENTER` | center | | `ALIGNMENT_CENTER` | `ALIGNMENT_END` | center right | | `ALIGNMENT_END` | `ALIGNMENT_START` | bottom left corner | | `ALIGNMENT_END` | `ALIGNMENT_CENTER` | bottom center | | `ALIGNMENT_END` | `ALIGNMENT_END` | bottom right corner | ```julia widget = # ... set_horizontal_alignment!(widget, ALIGNMENT_START) set_vertical_alignment!(widget, ALIGNMENT_START) # equivalent to set_alignment!(widget, ALIGNMENT_START) ``` Using alignment, size-hinting, and expansion, we can fully control where and at what size a widget will appear on screen, without having to worry about manually placing it by choosing the exact position or size. --- ### Visibility & Opacity Once a widget is realized, when we call [`present!`](@ref) on the window it is contained within, it is **shown**, appearing on screen and emitting signal `show`. If the widget leaves the screen, for example, because it is removed from a container or its window is closed, it is **hidden**, emitting signal `hide`. To hide a shown widget or show a hidden widget, we use [`set_is_visible!`](@ref): ```julia button = Button() connect_signal_clicked!(button) do self::Button set_is_visible!(self, false) end set_child!(window, button) ``` In which a button is hidden when it is clicked. This means the button cannot be clicked again, as its interactivity is only available while it is shown. Once the button is hidden, its allocated size becomes `(0, 0)`. If we instead just want to make the button invisible but still have it be clickable, we should use [`set_opacity!`](@ref). This function takes a float in `[0, 1]`, where `0` is fully transparent, `1` is fully opaque: ```julia # make a button invisible if it is visible, or visible if it is invisible button = Button() connect_signal_clicked!(button) do self::Button current = get_opacity(self) if current < 1.0 set_opacity!(button, 1.0) else set_opacity!(button, 0.0) end end set_child!(window, button) ``` Setting opacity does **not** emit the `hide` or `show` signal. While the widget may be fully transparent and thus invisible to us humans, it retains its interactivity and allocated area on screen. --- ### Cursor Type Each widget has a property governing what the user's cursor will look like while it is inside the widget's allocated area. By default, the cursor is a simple arrow. A widget intended for text entry would want the cursor to be a [caret](https://en.wikipedia.org/wiki/Cursor_(user_interface)), while a clickable widget would likely want a [pointer](https://en.wikipedia.org/wiki/Cursor_(user_interface)#Pointer). Some widgets already set the cursor to an appropriate shape automatically, but we can control the cursor shape for each widget manually using [`set_cursor!`](@ref), which takes a value of the enum [`CursorType`](@ref): | `CursorType` value | Appearance | |--------------------------------|-----------------------------------------------------------------------------------------------------| | `CURSOR_TYPE_NONE` | Invisible cursor | | `CURSOR_TYPE_DEFAULT` | Default arrow pointer | | `CURSOR_TYPE_POINTER` | Hand pointing | | `CURSOR_TYPE_TEXT` | Caret | | `CURSOR_TYPE_GRAB` | Hand, not yet grabbing | | `CURSOR_TYPE_GRABBING` | Hand, currently grabbing | | `CURSOR_TYPE_CELL` | Cross, used for selecting cells from a table | | `CURSOR_TYPE_CROSSHAIR` | Crosshair, used for making pixel-perfect selections | | `CURSOR_TYPE_HELP` | Questionmark, instructs the user that clicking or hovering above this element will open a help menu | | `CURSOR_TYPE_CONTEXT_MENU` | Questionmark, instructs the user that clicking will open a context menu | | `CURSOR_TYPE_NOT_ALLOWED` | Instructs the user that this action is currently disabled | | `CURSOR_TYPE_PROGRESS` | Spinning animation, signifies that the object is currently busy | | `CURSOR_TYPE_WAIT` | Loading animation, Instructs the user that an action will become available soon | | `CURSOR_TYPE_ZOOM_IN` | Lens, usually with a plus icon | | `CURSOR_TYPE_ALL_SCROLL` | Omni-directional scrolling | | `CURSOR_TYPE_MOVE` | 4-directional arrow | | `CURSOR_TYPE_NORTH_RESIZE` | Up-arrow | | `CURSOR_TYPE_NORTH_EAST_RESIZE` | Up-left arrow | | `CURSOR_TYPE_EAST_RESIZE` | Left arrow | | `CURSOR_TYPE_SOUTH_EAST_RESIZE` | Down-left arrow | | `CURSOR_TYPE_SOUTH_RESIZE` | Down arrow | | `CURSOR_TYPE_SOUTH_WEST_RESIZE` | Down-right arrow | | `CURSOR_TYPE_WEST_RESIZE` | Right arrow | | `CURSOR_TYPE_NORTH_WEST_RESIZE` | Up-right arrow | | `CURSOR_TYPE_ROW_RESIZE` | Up-down arrow | | `CURSOR_TYPE_COLUMN_RESIZE` | Left-right arrow | `Button`s default cursor is `CURSOR_TYPE_DEFAULT`. If we want to indicate to the user that the button should be clicked, we can instead set it to be a pointer: ```julia button = Button() set_cursor!(button, CURSOR_TYPE_POINTER) ``` The exact look of each cursor type depends on the user's operating system and UI configuration. To choose a fully custom cursor, we use [`set_cursor_from_image!`](@ref), which takes an `Image`. We will learn more about `Image` in the [chapter dedicated to it](./06_image.md). Until then, this is how we set a cursor from a `.png` file on disk: ```julia widget = # ... set_cursor_from_image!(widget, Image("/path/to/image.png")) ``` --- ### Tooltip Each widget can have a **tooltip**. This is a little window that opens when the cursor hovers over a widget's allocated area for enough time. The exact duration is decided by the user's OS, we do not have control over it. Tooltips are usually a simple text message. We can set the text directly using [`set_tooltip_text!`](@ref): ```julia button = Button() set_tooltip_text!(button, "Click to Open") ``` ![](../assets/widget_tooltip_text.png) If we want to use something more complex than just simple text, we can register an arbitrarily complex widget as a tooltip by calling [`set_tooltip_widget!`](@ref). As a matter of style, this widget should not be interactable, though there is no mechanism in place to enforce this. --- Now that we know the properties shared by all widgets, we can continue onto learning about all the specific widget types. From this point onwards, we can be sure that **all widgets support all properties and signals discussed so far**. ## Window For our first widget, we have [`Window`](@ref). Windows are central to any application, as such, `Window` and `Application` are inherently connected. We cannot create a `Window` without an `Application` instance. If all windows are closed, the underlying application usually exists. While windows are widgets, they occupy a somewhat of a special place. `Window` is the only widget that does not have a parent. This is called being **top-level**, nothing is "above" the window in the parent-child hierarchy. `Window` has exactly one child, which we set with `set_child!`, as we have so far already. ### Opening / Closing a Window When we create a `Window` instance, it will be initially hidden. None of its children will be realized or shown, and the user has no way to know that the window exists. A `Window`s lifetime only begins once we call [`present!`](@ref). This opens the window and shows it to the user, realizing all its children. We've seen this in our `main` functions before: ```julia main() do app::Application # create the window window = Window(app) # show the window, this realizes all widgets inside present!(window) end ``` At any point, we can call `close!`, which hides the window. This does not destroy the window permanently unless [`set_hide_on_close!`](@ref) was set to `false` previously, we can `present!` to show the window again. For an application to exit, all its windows only need to be hidden, not permanently destroyed. Therefore, calling `close!` on all windows may cause the application to attempt to exit. ### Close Request `Window` has three signals, only the latter of which is relevant to us for now. ```@eval using Mousetrap Mousetrap.@signal_table(Window, activate_default_widget, activate_focused_widget, close_request ) ``` When the window handler of the user's OS asks the window to close, for example, because the user pressed the "x" button, signal `close_request` will be emitted. Its result, of type [`WindowCloseRequestResult`](@ref), determines whether the window does close. ```julia # create a window that cannot be closed window = Window(app) connect_signal_close_request!(window) do self::Window return WINDOW_CLOSE_REQUEST_RESULT_PREVENT_CLOSE end present!(window) ``` If the signal handler instead returns `WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE`, the window will close, which is the default behavior. We should never call `close!` from within the signal handler of `closer_request`. Whether the window is closed should only be controlled by the handler's return value. ### Window Properties Other than its singular child, `Window` has a number of other properties. #### Title & Header Bar [`set_title!`](@ref) sets the name displayed in the window's **header bar**, which is the part on top of the content area. By default, this name will be the name of the application. We can choose to hide the title by simply calling `set_title!(window, "")`. By default, the header bar will show the window title, a minimize-, maximize-, and close-button. We can completely hide the header bar using `set_is_decorated!(window, false)`, which also means the user has no way to move or close the window. #### Modality & Transience When dealing with multiple windows, we can influence the way two windows interact with each other. Two of these interactions are determined by whether a window is **modal** and whether it is **transient** for another window. By setting [`set_is_modal!`](@ref) to true, if the window is revealed, **all other windows of the application will be deactivated**, preventing user interaction with them. This also freezes animations, it essentially pauses all other windows. The most common use-case for this is for dialogs, for example, if the user requests to close the application, it is common to open a small dialog requesting the user to confirm exiting the application. While this dialog is shown, the main window should be disabled and all other processes should halt until a selection is made. This is possible by making the dialog window *modal*. If two modal windows are active at the same time, the user can choose to swap between active windows by clicking a currently inactive window. Using [`set_transient_for!`](@ref), we can make sure a window will always be shown in front of another. `set_transient_for!(A, B)` will make it so, while `A` overlaps `B` on the user's desktop, `A` will be shown in front of `B`. --- ## Label In contention for being *the* most used widget, `Label`s are important to understand. A [`Label`](@ref) displays static text, meaning it is not interactable. It is initialized as one would expect: ```julia label = Label("text") ``` To change a `Label`s text after initialization, we use [`set_text!`](@ref). This can be any number of lines, `Label` is not just for single-line text. If our text has more than one line, a number of additional formatting options are available. ### Justify Mode [Justification](https://en.wikipedia.org/wiki/Typographic_alignment) determines how words are distributed along the horizontal axis. There are 5 modes in total, all of which are values of the enum [`JustifyMode`](@ref), set using [`set_justify_mode!`](@ref): ![](../assets/text_justify_left.png) `JUSTIFY_MODE_LEFT` ![](../assets/text_justify_center.png) `JUSTIFY_MODE_CENTER` ![](../assets/text_justify_right.png) `JUSTIFY_MODE_RIGHT` ![](../assets/text_justify_fill.png) `JUSTIFY_MODE_FILL` Where the fifth mode is `JUSTIFY_MODE_NONE`, which arranges all text in exactly one line. ### Wrapping Wrapping determines where a line break is inserted if the linewidth exceeds that of `Label`s allocated area. For wrapping to happen at all, the `JustifyMode` has to be set to anything other than `LABEL_WRAP_MODE_NONE`. Wrapping modes are values of the enum [`LabelWrapMode`](@ref). We set the wrap mode of a `Label` using [`set_wrap_mode!`](@ref). | `LabelWrapMode` value | Meaning | Example | |------------------------|---------------------------------------------------------|-------------------------| | `NONE` | no wrapping | `"humane mousetrap"` | | `ONLY_ON_WORD` | line will only be split between two words | `"humane\nmousetrap"` | | `ONLY_ON_CHAR` | line will only be split between syllables, adding a `-` | `"hu-\nmane mouse-\ntrap"` | | `WORD_OR_CHAR` | line will be split between words and/or syllables | `"humane\nmouse-\ntrap"` | Where `\n` is the newline character. ### Ellipsize Mode If a line is too long for the available space and wrapping is disabled, **ellipsizing** will take place. The corresponding enum [`EllipsizeMode`](@ref) has four possible values, which we set using [`set_ellipsize_mode!`](@ref). | `EllipsizeMode` value | Meaning | Example | |-----------------------|----------------------------------------------------------|-----------------------------| | `NONE` | text will not be ellipsized | `"Humane mousetrap engineer"` | | `START` | starts with `...`, showing only the last few words | `"...engineer"` | | `END` | ends with `...`, showing only the first few words | `"Humane mousetrap..."` | | `MIDDLE` | `...` in the center, shows start and beginning | `"Humane...engineer"` | ### Markup Labels support **markup**, which allows users to change properties about individual words or characters in a way similar to text formatting in HTML. Markup in Mousetrap uses [Pango attributes](https://docs.gtk.org/Pango/pango_markup.html), which allows for styles including the following: | Tag | Example | Result | |--------------|--------------------------|-------------------------| | `b` | `bold` | bold | | `i` | `italic` | italic | | `u` | `underline` | underline | | `s` | `strikethrough` | strike-through | | `tt` | `inline_code` | inline_code | | `small` | `small` | small | | `big` | `big` |

big

| | `sub` | `xsubscript` | xsubscript | | `sup` | `xsuperscript` | xsuperscript | | `&#` and `;` | `🪤` | 🪤 | Where in the last row, we used the [decimal html code](https://www.compart.com/en/unicode/U+1FAA4) for the Mousetrap emoji provided by unicode. !!! warning Pango only accepts the **decimal** code, not *hexadecimal*. For example, the Mousetrap emoji has the decimal code `129700`, while its hexadecimal code is `x1FAA4`. To use this emoji in text, we choose `🪤`, **not** `🪤`. The latter will not work. !!! note All `<`, `>` will be parsed as style tags, regardless of whether they are escaped. To display them as characters, we use `<` (less-than) and `>` (greater-than) instead of `<` and `>`. For example, we would write `x < y` as `"x < y"`. Pango also supports colors, different fonts, text direction, and more. For these, we can [consult the Pango documentation](https://docs.gtk.org/Pango/pango_markup.html) directly. ```julia label = Label("<tt>01234</tt> is rendered as 01234") set_child!(window, label) ``` ![](../assets/label_example.png) --- ## Box [`Box`](@ref) is a multi-widget container that aligns its children horizontally or vertically, depending on **orientation**. A number of widgets are orientable like this, which means they support the functions [`set_orientation!`](@ref) and [`get_orientation`](@ref), which take / return an enum value of [`Orientation`](@ref): | `Orientation` Value | Meaning | |---------------------------|------------------------------------------| | `ORIENTATION_HORIZONTAL` | Oriented left-to-right, along the x-axis | | `ORIENTATION_VERTICAL` | Oriented top-to-bottom, along the y-axis | To add widgets to the `Box`, we use `push_front!`, `push_back!` and `insert_after!`: ```julia left = Label("LEFT") set_margin_start!(left, 10) center = Label("CENTER") set_margin_horizontal!(center, 10) right = Label("RIGHT") set_margin_end!(right, 10) # create a horizontal box box = Box(ORIENTATION_HORIZONTAL) # add `left` to the start push_front!(box, left) # add `right to the end push_back!(box, right) # insert `center` after `left` insert_after!(box, center, left) # add box to window set_child!(window, box) ``` ![](../assets/box_example.png) In this example, we use margins to add a 10px gap in between each child. This can be done more succinctly using the boxe's own **spacing** property. By setting [`set_spacing!`](@ref) to `10`, it will automatically insert a 10 pixel gap in between any two children, in addition to the children's regular margin. [`hbox`](@ref) and [`vbox`](@ref) are two convenience functions that take any number of widgets and return a horizontal or vertical box with those widgets already inserted. Using this, and spacing instead of margins, we can write the above as two lines: ```julia box = hbox(Label("LEFT"), Label("CENTER"), Label("RIGHT")) set_spacing!(box, 10) set_child!(window, box) ``` --- ## CenterBox [`CenterBox`](@ref) is an orientable container that has exactly three children. `CenterBox` prioritizes keeping the designated center-child centered at all costs, making it a good choice when symmetry is desired. We use [`set_start_child!`](@ref), [`set_center_child!`](@ref), and [`set_end_child!`](@ref) to insert a child widget in the corresponding position: ```julia center_box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(center_box, Label("start")) set_center_child!(center_box, Button()) set_end_child!(center_box, Label("end")) ``` ![](../assets/center_box.png) Using `CenterBox`s constructor, we can also write the above as a one-liner: ```julia center_box = CenterBox(ORIENTATION_HORIZONTAL, Label("start"), Button(), Label("end")) ``` --- ## FlowBox Third of the `Box` relatives, we have [`FlowBox`](@ref). This widget is similar to `Box`, except that it will **redistribute** its children along more than one row (or column, if vertical) depending on the available width (or height) of the `FlowBox`. This is useful for situations where we want to group a number of widgets in a way that does not impact resizability. ```julia flow_box = FlowBox(ORIENTATION_VERTICAL) for i in 1:7 push_back!(flow_box, Label(string(i))) end ``` ![](../assets/flow_box.png) --- ## HeaderBar The visual element on top of a window, which usually contains the window's title along with the title buttons, is its own separate widget called [`HeaderBar`](@ref). All `Window` instances come with their own `HeaderBar`, which we can access using [`get_header_bar`](@ref). It's rarely necessary to create a `HeaderBar` on our own. Each `HeaderBar` has a title widget, which will usually be a `Label`, along with two areas for widgets to be inserted. To insert widgets left of the title, we use [`push_front!](@ref), while inserting widgets right of the title is done using [`push_back!`](@ref). Each `HeaderBar` can have a close-, minimize- and maximize- button, all of which are optional. To specify which buttons should appear and in what order, we use [`set_layout!`](@ref). This function takes a string, which has the following components: + `close` + `minimize` + `maximize` Each element is separated using `,`. The string has to furthermore contain a `:`. Each element before the `:` will appear left of the title, while elements after `:` will appear right of the title. Note that this just affects the close-, minimize-, and maximize buttons, any widget inserted using `push_front!` or `push_back!` is unaffected. A few examples: | `set_layout!` string | close button | minimize button | maximize button | |----------------------|--------------|----------------|-----------------| | `:minimize,maximize,close` | right of title | right of title | right of title | | `close:` | left of title | hidden | hidden | | `minimize:maximize` | hidden | left of title | right of title | | `:` | hidden | hidden | hidden | For example, to create a `HeaderBar` that has no elements, meaning no title and none of the title buttons, we would do the following: ```julia window = Window(app) # access windows header bar instance header_bar = get_header_bar(window) # hide title buttons set_layout!(header_bar, ":") # hide default title by replacing it with an empty label set_title_widget!(header_bar, Label("")) ``` ![](../assets/header_bar_blank.png) --- ## Separator Perhaps the simplest widget is [`Separator`](@ref). It simply fills its allocated area with a solid color: ```julia separator = Separator() set_margin!(separator, 20) set_expand!(separator, true) set_child!(window, separator) ``` ![](../assets/separator_example.png) This widget is used as a background to another widget, to fill empty space, or as an element visually separating two sections. Often, we want to have the separator be a specific thickness. This can be accomplished using size-hinting. For example, to draw a horizontal line similar to the `
` element in HTML, we would do the following: ```julia hr = Separator() set_expand_horizontally!(hr, true) set_expand_vertically!(hr, false) set_size_request!(hr, Vector2f( 0, # width: any 3 # height: exactly 3 px )); ``` This will render as a line that has a height of `3` px at all times but will assume the entire width of its parent. --- ## ImageDisplay [`ImageDisplay`](@ref) is used to display static images. Assuming we have an image at the absolute path `/assets/image.png`, we can create an `ImageDisplay` like so: ```julia image_display = ImageDisplay() create_from_file!(image_display, "/assets/image.png") # equivalent to image_display = ImageDisplay("/assets/image.png") ``` The following image formats are supported by `ImageDisplay`: | Format Name | File Extensions | |-------------------------|----------------------------| | PNG | `.png` | | JPEG | `.jpeg` `.jpe` `.jpg` | | JPEG XL image | `.jxl` | | Windows Metafile | `.wmf` `.apm` | | Windows animated cursor | `.ani` | | BMP | `.bmp` | | GIF | `.gif` | | MacOS X icon | `.icns` | | Windows icon | `.ico` `.cur` | | PNM/PBM/PGM/PPM | `.pnm` `.pbm` `.pgm` `.ppm` | | QuickTime | `.qtif` `.qif` | | Scalable Vector Graphics | `.svg` `.svgz` `.svg.gz` | | Targa | `.tga` `.targa` | | TIFF | `.tiff` `.tif` | | WebP | `.webp` | | XBM | `.xbm` | | XPM | `.xpm` | After realization, we cannot change the contents of `ImageDisplay` directly. If the file on disk changes, `ImageDisplay` remains unchanged. If we want to update `ImageDisplay`, we need to call [`create_from_file!`](@ref) manually again. --- ## Button Familiar from previous chapters, [`Button`](@ref) is commonly used to trigger behavior. It has one signal, which is emitted when the button is activated: ```@eval using Mousetrap Mousetrap.@signal_table(Button, clicked ) ``` We can manually emit this signal using `emit_signal_clicked`, or by calling `activate!` on the button instance. The latter will also play the animation associated with a user physically clicking the button. `Button` has a single child that is used as its label. We set it using `set_child!`. Other than this child widget, we can customize the look of a button further. `set_has_frame!` will make all graphical elements of the button other than its label invisible, while `set_is_circular!` changes the button from rectangular to fully rounded: ![](../assets/button_types.png) !!! details "How to generate this image" ```julia using Mousetrap main() do app::Application window = Window(app) normal = Button() set_child!(normal, Label("01")) no_frame = Button() set_has_frame!(no_frame, false) set_child!(no_frame, Label("02")) circular = Button() set_is_circular!(circular, true) set_child!(circular, Label("03")) box = CenterBox(ORIENTATION_HORIZONTAL, normal, no_frame, circular) set_margin!(box, 75) set_child!(window, box) pesent!(window) end ``` Where the above-shown buttons have the following properties: | Button | `set_has_frame!` | `set_is_circular!`| |--------|------------------|-------------------| | 01 | `true` | `false` | | 02 | `false` | `false` | | 03 | `true` | `true` | --- ## ToggleButton [`ToggleButton`](@ref) is a specialized form of `Button`. It supports most of `Button`s methods / signals, including `set_child!`, `set_has_frame!`, `set_is_circular!`, and signal `clicked`. Unique to `ToggleButton` is that, if clicked, the button will **remain pressed**. When clicked again, it returns to being unpressed. Anytime the state of the `ToggleButton` changes, signal `toggled` will be emitted. In this way, `ToggleButton` can be used to track a boolean state. ```@eval using Mousetrap Mousetrap.@signal_table(ToggleButton, toggled, clicked ) ``` To check whether the button is currently toggled, we use `get_is_active`, which returns `true` if the button is currently depressed, `false` otherwise. ```julia toggle_button = ToggleButton() connect_signal_toggled!(toggle_button) do self::ToggleButton println("state is now: $(get_is_active(self))") end set_child!(window, toggle_button) ``` --- ## CheckButton [`CheckButton`](@ref) is very similar to `ToggleButton` in function - but not in appearance. `CheckButton` is an empty box in which a checkmark appears when it is toggled. Just like before, we query whether it is pressed by calling `get_is_active`. ```@eval using Mousetrap Mousetrap.@signal_table(ToggleButton, toggled ) ``` `CheckButton` can be in one of **three** states, which are represented by the enum [`CheckButtonState`](@ref). The button can either be `CHECK_BUTTON_STATE_ACTIVE`, `CHECK_BUTTON_STATE_INACTIVE`, or `CHECK_BUTTON_STATE_INCONSISTENT`. This changes the appearance of the button: ![](../assets/check_button_states.png) !!! details "How to generate this image" ```julia using Mousetrap main() do app::Application window = Window(app) active = CheckButton() set_state!(active, CHECK_BUTTON_STATE_ACTIVE) active_box = vbox(active, Label("active")) inconsistent = CheckButton() set_state!(inconsistent, CHECK_BUTTON_STATE_INCONSISTENT) inconsistent_box = vbox(inconsistent, Label("inconsistent")) inactive = CheckButton() set_state!(inactive, CHECK_BUTTON_STATE_INACTIVE) inactive_box = vbox(inactive, Label("inactive")) for button in [active, inconsistent, inactive] set_horizontal_alignment!(button, ALIGNMENT_CENTER) end box = CenterBox(ORIENTATION_HORIZONTAL, active_box, inconsistent_box, inactive_box) set_margin!(box, 75) set_child!(window, box) present!(window) end ``` Note that `get_is_active` will only return `true` if the current state is specifically `CHECK_BUTTON_STATE_ACTIVE`. `toggled` is emitted whenever the state changes, regardless of which state the `CheckButton` was in. --- ## Switch As the last widget intended to convey a boolean state to the user, we have [`Switch`](@ref), which has an appearance similar to a light switch. `Switch` does not emit `toggled`, instead, we connect to the `switched` signal, which is emitted anytime the switch's internal state changes: ```@eval using Mousetrap Mousetrap.@signal_table(Switch, switched ) ``` ![](../assets/switch_states.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) active = Switch() set_is_active!(active, true) active_box = vbox(active, Label("active")) inactive = Switch() set_is_active!(inactive, false) inactive_box = vbox(inactive, Label("inactive")) for switch in [active, inactive] set_horizontal_alignment!(switch, ALIGNMENT_CENTER) set_margin!(switch, 10) end box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(box, active_box) set_end_child!(box, inactive_box) set_margin!(box, 75) set_child!(window, box) present!(window) end ``` --- --- ## Adjustment From widgets conveying a boolean state, we'll now move on to widgets conveying a discrete number. These let the user choose a value from a **range**, which, in Mousetrap, is represented by a signal emitter called [`Adjustment`](@ref). `Adjustment` has four properties: + `lower`: lower bound of the range + `upper`: upper bound of the range + `increment`: step increment + `value`: current value, in `[lower, upper]` For example, the following `Adjustment`: ```julia adjustment = Adjustment( 1, # value 0, # lower 2, # upper 0.5 # step increment ) ``` Expresses the range `{0, 0.5, 1, 1.5, 2}`, with `1` being the value on initialization. We usually do not need to create our own `Adjustment`, rather, it is provided by a number of widgets that use it to select their value. Notably, if the `Adjustment` is modified, that widget's appearance is modified, and if the widget is modified, the adjustment is, too. `Adjustment` has two signals: ```@eval using Mousetrap Mousetrap.@signal_table(Adjustment, value_changed, properties_changed ) ``` We can connect to `value_changed` to monitor the `value` property of an `Adjustment` (and thus whatever widget is controlled by it), while `properties_changed` is emitted when one of `upper`, `lower` or `step increment` changes. --- ## SpinButton `SpinButton` is used to pick an exact value from a range. The user can click the rectangular area and manually enter a value using the keyboard, or they can increase or decrease the current value by the step increment of the widgets `Adjustment` by pressing the plus or minus button. We supply the properties of the range underlying the `SpinButton` to its constructor: ```julia # create SpinButton with range [0, 2] and increment 0.5 spin_button = SpinButton(0, 2, 0.5) ``` ![](../assets/spin_button.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) horizontal = SpinButton(0, 2, 0.5) set_value!(horizontal, 1) # Add invisible separator buffers above and below spin button for better symmetry horizontal_buffer = CenterBox( ORIENTATION_VERTICAL, Separator(; opacity = 0.0), horizontal, Separator(; opacity = 0.0) ) vertical = SpinButton(0, 2, 0.5) set_value!(vertical, 1) set_orientation!(vertical, ORIENTATION_VERTICAL) box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(box, horizontal_buffer) set_end_child!(box, vertical) set_child!(window, box) present!(window) end ``` We set and access any property of spin button using `get_value`, `set_value!`, `get_lower`, `set_lower!`, etc. These work exactly as if we were modifying the underlying `Adjustment`, which we can also obtain using `get_adjustment`. Along with being *orientable*, `SpinButton` has two signals, one of which, `value_changed`, we recognize from `Adjustment`. To react to the user changing the value of a `SpinButton`, we would do the following: ```julia spin_button = SpinButton(0, 2, 0.5) connect_signal_value_changed!(spin_button) do self::SpinButton println("Value is now: $(get_value(self))") end ``` The other signal is `wrapped`, which is emitted when [`set_should_wrap!`](@ref) is set to `true` and the `SpinButton`'s value under- or overflows. --- ## Scale ![](../assets/scale_no_value.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) horizontal = Scale(0, 2, 0.5) set_orientation!(horizontal, ORIENTATION_HORIZONTAL) set_value!(horizontal, 1) set_size_request!(horizontal, Vector2f(200, 0)) vertical = Scale(0, 2, 0.5) set_orientation!(vertical, ORIENTATION_VERTICAL) set_value!(vertical, 1) set_size_request!(vertical, Vector2f(0, 200)) box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(box, horizontal) set_end_child!(box, vertical) set_margin_horizontal!(box, 75) set_margin_vertical!(box, 40) set_child!(window, box) present!(window) end ``` [`Scale`](@ref), just like `SpinButton`, is a widget that allows a user to choose a value from the underlying `Adjustment`. This is done by click-dragging the knob of the scale or clicking anywhere on its rail. In this way, it is usually harder to pick an exact decimal value on a `Scale` as opposed to a `SpinButton`. We can aid in this task by displaying the exact value next to the scale, which is enabled with [`set_should_draw_value!`](@ref): ![](../assets/scale_with_value.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) window = Window(app) horizontal = Scale(0, 2, 0.5) set_orientation!(horizontal, ORIENTATION_HORIZONTAL) set_value!(horizontal, 1) set_size_request!(horizontal, Vector2f(200, 0)) set_should_draw_value!(horizontal, true) vertical = Scale(0, 2, 0.5) set_orientation!(vertical, ORIENTATION_VERTICAL) set_value!(vertical, 1) set_size_request!(vertical, Vector2f(0, 200)) set_should_draw_value!(vertical, true) box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(box, horizontal) set_end_child!(box, vertical) set_margin_horizontal!(box, 75) set_margin_vertical!(box, 40) set_child!(window, box) present!(window) end ``` `Scale` supports most of `SpinButton`'s functions, including querying information about its underlying range, setting the orientation, and signal `value_changed`: ```julia scale = Scale(0, 2, 0.5) connect_signal_value_changed!(scale) do self::Scale println("Value is now: $(get_value(self))") end ``` --- ## LevelBar [`LevelBar`](@ref) is used to display a fraction to indicate the level of something, for example, the volume of a playback device. This widget is static, it cannot be interacted with. To create a `LevelBar`, we need to specify the minimum and maximum value of the range we wish to display. We can then set the current value using `set_value!`. The resulting fraction is computed automatically, based on the upper and lower limit we supplied to the constructor: ```julia # create a LevelBar for range [0, 2] level_bar = LevelBar(0, 2) set_value!(level_bar, 1.0) # set to 50% ``` Unlike the previous widgets, `LevelBar` does not have a step increment. Once the bar reaches 75%, it changes color: ![](../assets/level_bar.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) box = Box(ORIENTATION_VERTICAL) set_spacing!(box, 10) set_margin!(box, 10) n_bars = 5 for i in 1:n_bars fraction = Float32(i) / n_bars label = Label(string(Int64(round(fraction * 100))) * "%") set_size_request!(label, Vector2f(50, 0)) bar = LevelBar(0, 1) set_value!(bar, fraction) set_expand_horizontally!(bar, true) row_box = Box(ORIENTATION_HORIZONTAL) set_spacing!(box, 10) push_back!(row_box, label) push_back!(row_box, bar) push_back!(box, row_box) end set_child!(window, box) present!(window) end ``` `LevelBar` also supports displaying a discrete value, in which case it will be drawn segmented. To enable this, we set [`set_mode!`](@ref) to `LEVEL_BAR_DISPLAY_MODE_DISCRETE`, as opposed to `LEVEL_BAR_MODE_CONTINUOUS`, which is the default. --- ## ProgressBar Similarly to `LevelBar`, [`ProgressBar`](@ref) communicates a fraction to the user, which is frequently used to show the user how much of a task is currently completed. `ProgressBar` only expresses values in `[0, 1]`, and [`set_fraction!`](@ref) will only accept values in this range. Using `set_show_text!`, we can make it so the current percentage is drawn along with the progress bar, or we can draw a custom label using `set_text!` ![](../assets/progress_bar.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) box = Box(ORIENTATION_VERTICAL) progress_bar = ProgressBar() set_fraction!(progress_bar, 0.47) set_vertical_alignment!(progress_bar, ALIGNMENT_CENTER) set_expand!(progress_bar, true) set_show_text!(progress_bar, true) set_margin!(progress_bar, 10) set_child!(window, progress_bar) present!(window) end ``` --- ## Spinner To signal progress when we do not have an exact fraction, we use [`Spinner`](@ref) which is a small spinning icon. Once we set [`set_is_spinning!`](@ref) to `true`, a spinning animation will play, indicating to the user that work is being done. ![](../assets/spinner.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) spinner = Spinner() set_is_spinning!(spinner, true) set_child!(window, spinner) present!(window) end ``` --- ## Entry Text entry is central to many applications. Mousetrap offers two widgets that allow the user to type freely. [`Entry`](@ref) is the widget of choice for **single-line** text entry. The entries currently displayed text is stored in an internal text buffer. We can freely access or modify the buffer's content with [`get_text`](@ref) and [`set_text!`](@ref). While we could control the size of an `Entry` using size-hinting, a better way is [`set_max_width_chars!`](@ref), which resizes the entry such that its width is enough to fit a certain number of characters into its area. This automatically respects the system font and font size. `Entry` supports "password mode", in which each character typed is replaced with a dot. This is to prevent a third party in the real world looking at a user's screen and seeing what they are typing. To enter password mode, we set [`set_text_visible!`](@ref) to `false`. Note that this does not encrypt the text buffer in memory, it is a purely visual change. ![](../assets/entry.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) clear = Entry() set_text!(clear, "text") password = Entry() set_text!(password, "text") set_text_visible!(password, false) box = vbox(clear, password) set_spacing!(box, 10) set_margin_horizontal!(box, 75) set_margin_vertical!(box, 40) set_child!(window, box) present!(window) end ``` Lastly, `Entry` is **activatable**, when the user presses the enter key while the cursor is inside the entries text area, it will emit signal `activate`. Its other signal, `text_changed`, is emitted whenever the internal text buffer changes: ```@eval using Mousetrap Mousetrap.@signal_table(Entry, activate, text_changed ) ``` We would therefore connect a handler that reacts to the text of an entry changing like so: ```julia entry = Entry() set_text!(entry, "Write here") connect_signal_text_changed!(entry) do self::Entry println("text is now: $(get_text(self))") end ``` Note that the user cannot insert a newline character using the enter key. `Entry` should exclusively be used for text prompts that have **no line breaks**. For multi-line text entry, we should use the next widget instead. ## TextView [`TextView`](@ref) is the multi-line equivalent of `Entry`. It supports several basic text-editor features, including **undo / redo**, which are triggered by the user pressing `Control + Z` and `Control + Y` respectively. We as developers can also trigger this behavior manually with [`undo!`](@ref) / [`redo!`](@ref). Much like `Label`, we can set how the text aligns horizontally using `set_justify_mode!`. To further customize how text is displayed, we can choose the **internal margin**, which is the distance between the frame of the `TextView` and the text inside of it. `set_left_margin!`, `set_right_margin!`, `set_top_margin!` and `set_bottom_margin!` allow us to choose these values freely. `TextView` does **not** have the `activate` signal, pressing enter while the cursor is inside the widget will simply create a new line. Instead, it only has signal `text_changed`, which behaves identically to that of `Entry`: ```@eval using Mousetrap Mousetrap.@signal_table(TextView, text_changed ) ``` ```julia text_view = TextView() set_text!(text_view, "Write here") connect_signal_text_changed!(text_view) do self::TextView println("text is now: $(get_text(self))") end ``` --- ## Dropdown We sometimes want users to be able to pick a value from a **set list of values**, which may or may not be numeric. [`DropDown`](@ref) allows for this. If clicked, a small popup presents the user with a list of items. When clicking one of these items, that item becomes the active item. We add an item using `push_back!`, which takes a string that will be used as the item's label: ```julia dropdown = DropDown() item_01_id = push_back!(dropdown, "Item #01") item_02_id = push_back!(dropdown, "Item #02") item_03_id = push_back!(dropdown, "Item #03") ``` ![](../assets/dropdown_simple.png) `push_back!` returns the internal ID of the item. We should keep track of this ID, as it will be used to identify the currently selected item when using [`get_selected`](@ref). If we do lose track of the ID, we can always retrieve it using [`get_item_at`](@ref), which returns the ID of the item at a given position. `push_back!`, and its equivalents `push_front!` and `insert_at!`, provide a method that also takes a *callback*. This callback will be invoked when the item is selected. It is required to have the signature: ``` (::DropDown, [::Data_t]) -> Nothing ``` ```julia dropdown = DropDown() push_back!(dropdown, "Item #01") do self::DropDown println("Item #01 selected") end push_back!(dropdown, "Item #02") do self::DropDown println("Item #03 selected") end push_back!(dropdown, "Item #03") do self::DropDown println("Item #03 selected") end ``` This gives us a better mechanism for keeping track of which item is currently selected. Instead of querying the `DropDown` using `get_selected` and reacting to its result, we should instead register a callback using this method, in a similar way to using signals. Lastly, sometimes we want a different label for when an item is selected, and for when the user opens the menu to select an item. For this situation, `push_back!` offers a method that lets us specify the label widgets separately: ```julia dropdown = DropDown() push_back!(dropdown, Label("Item #01"), # Widget displayed in dropdown menu Label("01") # Widget displayed when item is selected ) push_back!(dropdown, Label("Item #02"), Label("02")) push_back!(dropdown, Label("Item #03"), Label("03")) ``` ![](../assets/dropdown_separate.png) Where we had to first create a `Label` instance, then use it as the label widget, as this method of `push_back!` takes any two *widgets*, as opposed to just strings. This gives us full flexibility with how we want the dropdown to be displayed. This method, along with all methods of `push_front!` and `insert_at!`, also supports adding a callback as the first argument, which behaves exactly as before. --- ## Frame [`Frame`](@ref) is a purely cosmetic widget that displays its singular child in a frame with a small border and rounded corners: ![](../assets/frame.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) left = Separator() right = Separator() for separator in [left, right] set_size_request!(separator, Vector2f(50, 50)) set_expand!(separator, false) end box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(box, left) set_end_child!(box, Frame(right)) set_margin_horizontal!(box, 75) set_margin_vertical!(box, 40) set_child!(window, box) present!(window) end ``` Using [`set_label_widget!`](@ref), we can furthermore choose a widget to be displayed above the child widget of the frame. This will usually be a `Label`, though `set_label_widget!` accepts any kind of widget. `Frame` is rarely necessary, but will make GUIs seem more aesthetically pleasing and polished. --- ## AspectFrame Not to be confused with `Frame`, [`AspectFrame`](@ref) adds no graphical element to its singular child. Instead, the widget added with `set_child!` will be forced to allocate a size that conforms to a specific **aspect ratio**. That is, its width-to-height ratio will stay constant, regardless of the size of its parent. If expansion is enabled, the `AspectFrame` will still try to expand to the maximum size that still fulfills the aspect ratio requirement. We choose the aspect ratio in `AspectFrame`s constructor, though we can later adjust it using [`set_ratio!`](@ref). Both of these functions accept a floating point ratio calculated as `width / height`. For example, if we want to force a widget to keep an aspect ratio of 4:3, we would do: ```julia child_widget = # ... aspect_frame = AspectFrame(4.0 / 3.0) set_child!(aspect_frame, child_widget); ``` --- ## ClampFrame Using size-hinting, we can control the *minimum* size of a widget. No widget property lets us control the *maximum size*, however. For this, we need [`ClampFrame`](@ref), a widget that constrains its singular child to never exceed the given maximum width, or height if the frame's orientation is `ORIENTATION_VERTICAL`. We choose the maximum size in pixels during construction, or using [`set_maximum_size!`](@ref): ```julia child_widget = # ... width_clamp_frame = ClampFrame(150, ORIENTATION_HORIZONTAL) height_clamp_frame = ClampFame(150, ORIENTATION_VERTICAL) set_child!(width_clamp_frame, child_widget) set_child!(height_clamp_frame, width_clamp_frame) ``` In which we use two nested `ClampFrame`s, such that `child_widget` can never exceed `150px` for both its width and height. --- ## Overlay So far, all widget containers have aligned their children such that they do not overlap. In cases where we do want this to happen, for example, if we want to render one widget in front of another, we have to use [`Overlay`](@ref). `Overlay` has one "base" widget, which is at the conceptual bottom of the overlay. It is set using `set_child!`. We can then add any number of widgets "on top" of the base widget using [`add_overlay!`](@ref): ![](../assets/overlay_buttons.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) lower = Button() set_horizontal_alignment!(lower, ALIGNMENT_START) set_vertical_alignment!(lower, ALIGNMENT_START) upper = Button() set_horizontal_alignment!(upper, ALIGNMENT_END) set_vertical_alignment!(upper, ALIGNMENT_END) overlay = Overlay() set_child!(overlay, lower) add_overlay!(overlay, upper) set_child!(window, AspectFrame(1, overlay)) present!(window) end ``` Where the position and size of overlayed widgets depend on their expansion and alignment properties. By default, `Overlay` will allocate exactly as much space as the base widget (set with `set_child!`) does. If one of the overlaid widgets takes up more space than the base widget, it will be truncated. We can avoid this by supplying a second argument to `add_overlay!`, which is a boolean keyword argument indicating whether the overlay widget should be included in the entire container's size allocation. That is, if the overlaid widget is larger than the base widget, should the `Overlay` resize itself such that the entire overlaid widget is visible: ```julia add_overlay!(overlay, overlaid_widge; include_in_measurement = true) ``` --- ## Paned [`Paned`](@ref) is a widget that always has exactly two children. Between the two children, a visual barrier is drawn. The user can click on this barrier and drag it horizontally or vertically, depending on the orientation of the `Paned`. This gives the user the option to resize how much of a shared space the two widgets allocate. `Paned` is orientable. Depending on its orientation, `set_start_child!` and `set_end_child!` add a widget to the corresponding side. ![](../assets/paned.png) !!! details "How to generate this image" ```julia function generate_child(label::String) out = Frame(Overlay(Separator(), Label(label))) set_margin!(out, 10) return out end main() do app::Application window = Window(app) paned = Paned(ORIENTATION_HORIZONTAL) set_start_child!(paned, generate_child("Left")) set_end_child!(paned, generate_child("Right")) set_start_child_shrinkable!(paned, true) set_end_child_shrinkable!(paned, true) set_child!(window, paned) present!(window) end ``` `Paned` has two per-child properties: whether a child is **resizable** and whether it is **shrinkable**. Resizable means that if the `Paned` changes size, the allocated area of its child should resize accordingly. Shrinkable sets whether the side of the `Paned` can be made smaller than the allocated size of that side's child widget. If set to `true`, the user can drag the `Paned`s barrier, such that one of the widgets is partially or completely hidden: ```julia set_start_child_shrinkable!(paned, true) set_end_child_shrinkable!(paned, true) ``` ![](../assets/paned_shrinkable.png) --- ## Revealer While not technically necessary, animations can improve user experience drastically. Not only do they add visual style, but they can also hide abrupt transitions or small loading times. As such, they should be in any advanced GUI designer's repertoire. One of the most common applications for animations is the act of hiding or showing a widget. [`Revealer`](@ref) was made for this purpose. To trigger the `Revealer`s animation and change whether its singular child widget is currently visible, we call [`set_is_revealed!`](@ref) which takes a boolean as its argument. If the widget goes from hidden to shown or shown to hidden, the animation will play. Once the animation is done, signal `revealed` will be emitted. ### Transition Animation We have control over the kind and speed of the transition animation. By calling [`set_transition_duration!`](@ref), we can set the exact amount of time an animation should take. For example, to set the animation duration to 1 second: ```julia revealer = Revealer() set_child!(revealer, #= widget =#) set_transition_duration!(revealer, seconds(1)); ``` Where `seconds` returns a [`Mousetrap.Time`](@ref). Apart from the speed, we also have a choice of animation **type**, represented by the enum [`RevealerTransitionType`](@ref). Animations include a simple cross-fade, sliding, swinging, or no animation at all, which instantly shows or hides the widget. ![](../assets/revealer.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) # create child child = Frame(Overlay(Separator(), Label("[Item]"))) set_margin!(child, 10) set_size_request!(child, Vector2f(0, 100)) # setup revealer revealer = Revealer() set_child!(revealer, child) set_transition_duration!(revealer, seconds(1)) set_transition_type!(revealer, REVEALER_TRANSITION_TYPE_SLIDE_DOWN) # create a button that, when clicked, triggers the revealer animation button = Button() connect_signal_clicked!(button, revealer) do self::Button, revealer::Revealer set_is_revealed!(revealer, !get_revealed(revealer)) end set_child!(window, vbox(button, revealer)) present!(window) end ``` --- ## ActionBar One common application for using a `Revealer` is to show or hide a *toolbar*, which is a horizontal box with any number of buttons for contextual actions. For this purpose, [`ActionBar`](@ref) is well suited, because it can be shown/hidden using [`set_is_revealed!`](@ref) all by itself, making it so we don't need to use a separate `Revealer` instance. `ActionBar` is always horizontal, it cannot be oriented. It has space for any number of widgets on either side, along with having a singular centered widget. We can pack widgets to either side using `push_start!` and `push_end!`, while the centered widget is set using `set_center_widget!`. --- ## Expander [`Expander`](@ref) is similar to `Revealer`, in that it also has exactly one child widget, and it shows / hides the widget. Unlike `Revealer`, there is no animation attached to `Expander`. Instead, it hides the widget behind a collapsible label. Expander has two children, the label, set with `set_label_widget!`, and its child, set with `set_child!`, which is the widget that will be shown / hidden. ![](../assets/expander.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) child = Frame(Overlay(Separator(), Label("Child"))) set_margin!(child, 10) set_size_request!(child, Vector2f(0, 100)) label = Label("Label") set_margin!(label, 10) expander_and_frame = Frame(Expander(child, label)) set_margin!(expander_and_frame, 10) set_child!(window, expander_and_frame) present!(window) end ``` Note that `Expander` should not be used to create nested lists, as `ListView`, a widget we will learn about later in this chapter, is better suited for this purpose. --- ## Viewport By default, most containers will allocate a size equal to or exceeding that of its children. For example, if we create a widget that has a natural size of 5000x1000 px and use it as the child of a `Window`, the `Window` will attempt to allocate 5000x1000 pixels on screen, making the window far larger than most monitors can display. Sometimes, widgets that are this large are unavoidable. In situations like this, we can use [`Viewport`](@ref) to only display part of a widget. We set the viewport's singular child using `set_child!`, after which the user can operate the two scrollbars to change which part of the child is currently visible: ![](../assets/viewport.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) child = Frame(Overlay(Separator(), Label("CHILD"))) set_margin!(child, 10); viewport = Viewport() set_child!(viewport, child) set_child!(window, viewport) present!(window) end ``` ### Size Propagation By default, `Viewport` will disregard the size of its child and simply allocate an area based only on the properties of the `Viewport` itself. This behavior can be overridden by setting the viewport's **size propagation**. If [`set_propagate_natural_height!`](@ref) is set to true, the viewport's height will be equal to the height of its child. Conversely, [`set_propagate_natural_width!`](@ref) does the same for the child's width. ```julia set_propagate_natural_width!(viewport, true) set_propagate_natural_height!(viewport, false) ``` ![](../assets/viewport_propagation.png) Here, the viewport will be the same width as the child, but the viewport's height is independent of that of its child. ### Scrollbar Policy `Viewport` has two scrollbars, controlling the horizontal and vertical position. By default, these will automatically reveal themself when the user's cursor enters the viewport, hiding again once the cursor exists. If and when to reveal the scrollbars is determined by the viewports **scrollbar policy**, set with [`set_horizontal_scrollbar_policy!`](@ref) and [`set_vertical_scrollbar_policy!`](@ref), both of which take a value of the enum [`ScrollbarVisibilityPolicy`](@ref), which has the following instances: | `ScrollbarVisibilityPolicy` | Meaning | |-----------------------------|-----------| | `SCROLLBAR_VISIBILITY_POLICY_NEVER` | scrollbar is always hidden | | `SCROLLBAR_VISIBILITY_POLICY_ALWAYS` | scrollbar is always shown | | `SCROLLBAR_VISIBILITY_POLICY_AUTOMATIC` | scrollbar hides/shows automatically| If `set_propagate_natural_height!` is set to `true`, the vertical scrollbar will always be hidden, regardless of policy. The same is true for `set_propagate_natural_width!` and the horizontal scrollbar. ### Scrollbar Position Lastly, we can customize the location of both scrollbars at the same time using [`set_scrollbar_placement!`](@ref), which takes one of the following values of the enum [`CornerPlacement`](@ref). | `CornerPlacement` | Meaning | |-------------------|---------| | `CORNER_PLACEMENT_TOP_LEFT` | horizontal scrollbar at the top, vertical scrollbar on the left | | `CORNER_PLACEMENT_TOP_RIGHT` | horizontal at the top, vertical on the right | | `CORNER_PLACEMENT_BOTTOM_LEFT` | horizontal at the bottom, vertical on the left | | `CORNER_PLACEMENT_BOTTOM_RIGHT` | horizontal at the bottom, vertical on the right | ### Signals If we want to react to the user scrolling the `Viewport`'s child, we can either connect to its signal `scroll_child`, or we can access the `Adjustment` controlling each scrollbar using [`get_horizontal_adjustment`](@ref) and [`get_vertical_adjustment`](@ref), then connect to the signals of the obtained `Adjustment`s. With this, scrollbar policy, and size propagation we have full control over every aspect of `Viewport`. --- ## Scrollbar `Viewport` comes with two scrollbars, but we can also create our own. Using [`Scrollbar`](@ref), which takes an `Orientation` as well as an `Adjustment` for its constructor, we can create a fully custom scrolling widget. To react to the user scrolling, we need to connect to the signals of the `Adjustment`, as `Scrollbar` does not provide any signals itself: ```julia adjustment = Adjustment(0.5, 0, 1, 0.01) scrollbar = Scrollbar(ORIENTATION_HORIZONTAL, adjustment) connect_signal_value_changed!(adjustment) do self::Adjustment println("Value is now $(get_value(self))") end set_child!(window, scrollbar) ``` Where we made it such that the scrollbar expresses the range `[0, 1]`, with a step increment of `0.01` and an initial value of `0.5`. If we lose track of the `Adjustment` instance after constructing the `ScrollBar`, we can retrieve it anytime using `get_adjustment`. --- ## Popover A [`Popover`](@ref) is a special kind of window. It is always [modal](#modality--transience). Rather than having the normal window decoration with a close button and title, `Popover` closes dynamically (or when requested by the application). Showing the popover is called **popup**, closing the popover is called **popdown**, `Popover` correspondingly has [`popup!`](@ref) and [`popdown!`](@ref) to show or hide itself. ![](../assets/popover.png) !!! details "How to generate this image" ```julia using Mousetrap main() do app::Application window = Window(app) child = Frame(Overlay(Separator(), Label("Child"))) set_size_request!(child, Vector2f(100, 75)) set_margin!(child, 10); popover = Popover() set_child!(popover, child) button = Button() # when the button is clicked, the popover is shown. It hides automatically connect_signal_clicked!(button, popover) do self::Button, popover::Popover popup!(popover) end # to show the popover, it needs to be inside the same container as the child it should be attached to set_child!(window, vbox(popover, button)) present!(window) end ``` Manually calling `popup!` or `popdown!` to show or hide the `Popover` can be bothersome. To address this, Mousetrap offers a widget that automatically manages the popover for us: [`PopoverButton`](@ref) ## PopoverButton `PopoverButton` has a single child and one signal: `activate`: ```@eval using Mousetrap Mousetrap.@signal_table(PopoverButton, activate ) ``` Instead of triggering behavior, `PopoverButton`'s purpose is to reveal and hide a `Popover`. We first create the `Popover`, then supply it as the argument to `PopoverButton`'s constructor: ```julia popover = Popover() popover_button = PopoverButton(popover) ``` Additionally, an arrow is shown next to the label of the `PopoverButton`, indicating to the user that, when it is clicked, a popover will open. --- ## SelectionModel We will now move on to **selectable widgets**, which tend to be the most complex and powerful widgets in Mousetrap. All selectable widgets have one thing in common: their multiple children are managed by a **selection model**. This model is an ordered list of widgets. For each widget, the model will keep track of whether that widget is currently selected. If it is, a graphical element will be added to the selectable widget that indicates to the user which item(s) are currently selected: ![](../assets/list_view_selected.png) Modifying the model will modify the selectable widget, and modifying the selectable widget will modify the model. In this way, the two are linked, similar to how `Adjustment` works. We use [`select!`](@ref) and [`unselect!`](@ref) to change the selection manually, while [`get_selection`](@ref) returns a vector with one or more of the selected items indices. `SelectionModel` has signal `selection_changed`, which is emitted anytime an item is selected or unselected in any way. This signal requires the signature ``` (::SelectionModel, position::Integer, n_items::Integer, [::Data_t]) -> Nothing ``` Where `position` is the new index of the changed item (1-based), while `n_items` is the number of currently selected items. Each model has an associated property called the **selection mode**, which is expressed by the enum [`SelectionMode`](@ref). This governs how many items can be selected at one time: | `SelectionMode` | Number of Items | |---------------------------|-----------------| | `SELECTION_MODE_NONE` | exactly zero | | `SELECTION_MODE_SINGLE` | exactly one | | `SELECTION_MODE_MULTIPLE` | zero or more | We do not create instances of `SelectionModel` ourselves, instead, they are automatically created along with the selectable widget. Because of this, we will need to specify the selection mode in the selectable widget's constructor. ## ListView For our first selectable widget, we have [`ListView`](@ref). This is a widget that arranges its children in a row or column, depending on orientation. We add children using `push_back!`, `push_front!` and `insert_at!`: ```julia list_view = ListView(ORIENTATION_VERTICAL, SELECTION_MODE_SINGLE) push_back!(list_view, Label("Child #01")) push_back!(list_view, Label("Child #02")) push_back!(list_view, Label("Child #03")) ``` ![](../assets/list_view_simple.png) Where the second child is currently selected. `ListView` can be requested to automatically show separators in between two items by setting [`set_show_separators!`](@ref) to `true`. To check which item is selected, we query its selection model, which we obtain using [`get_selection_model`](@ref). ### Nested Trees By default, `ListView` displays its children in a linear list, either horizontally or vertically. `ListView` also supports **nested lists**, sometimes called a **tree view**: ![](../assets/list_view_nested.png) !!! details "How to generate this image" ```julia main() do app::Application window = Window(app) list_view = ListView(ORIENTATION_VERTICAL, SELECTION_MODE_SINGLE) push_back!(list_view, Label("Child #01")) child_02_it = push_back!(list_view, Label("Child #02")) push_back!(list_view, Label("Child #03")) push_back!(list_view, Label("Nested Child #01"), child_02_it) nested_child_02_it = push_back!(list_view, Label("Nested Child #02"), child_02_it) push_back!(list_view, Label("Inner Child #01"), nested_child_02_it) frame = Frame(list_view) set_margin!(frame, 10) set_child!(window, frame) present!(window) end ``` The user can click any item to hide or show its children. Items that do not have any children will appear just as with a non-nested `ListView`. Each item of a `ListView` can in itself be made a list view. To do this, we use an optional argument of `push_back!` (or `push_front!`, `insert_at!`), which is of type [`ListViewIterator`](@ref). This iterator identifies which list view to insert the item in. We obtain an iterator like so: ```julia list_view = ListView() child_01_it = push_back!(list_view, Label("Child #01")) child_02_it = push_back!(list_view, Lable("Child #02")) child_03_it = push_back!(list_view, Lable("Child #03")) ``` If we want to convert the item containing the label `Child #02` to a list view and insert an item as its child, we use that item's iterator as the optional argument: ```julia nested_child_01_it = push_back!(list_view, Label("Nested Child #01"), child_02_it) ``` To insert a new widget as the child of this already nested list, we use its iterator. Through this mechanism, we can create arbitrarily deep nested lists. If we do not want a nested list, we can instead completely ignore the iterator. Specifying no iterator when using `push_back!` means we will be inserting items into the outermost list. --- ## GridView [`GridView`](@ref) supports many of the same functions as `ListView`, including `push_back!`, `push_front!`, and `insert_at!`. Unlike `ListView`, `GridView` cannot be nested, as it instead displays its children in a **uniform grid**. `GridView`s constructor also takes an orientation as well as the selection mode. The orientation determines in which order elements will be shown. Consider the next two images, the first of which is a `GridView` whose orientation is `ORIENTATION_HORIZONTAL`, while the latter is `ORIENTATION_VERTICAL`: ![](../assets/grid_view_horizontal.png) *A horizontal `GridView`* ![](../assets/grid_view_vertical.png) *A vertical `GridView`* !!! details "How to generate this image" ```julia function generate_child(label::String) ::Widget child = Frame(Overlay(Separator(), Label(label))) set_size_request!(child, Vector2f(50, 50)) set_expand!(child, false) return AspectFrame(1, child) end main() do app::Application window = Window(app) grid_view = GridView(ORIENTATION_VERTICAL) # or `ORIENTATION_HORIZONTAL` set_expand!(grid_view, true) for i in 1:9 push_back!(grid_view, generate_child("0$i")) end separator = Separator() set_expand!(separator, true) set_expand!(grid_view, false) set_child!(window, vbox(separator, grid_view)) present!(window) end ``` We can control the exact distribution of widgets more closely by using [`set_max_n_columns!`](@ref) and [`set_min_n_columns!`](@ref), which make it so the grid view will always have the given number of columns (or rows, for a horizontal `GridView`). --- ## Column View [`ColumnView`](@ref) is used to display widgets as a table, with rows and columns. Each column has a title, which uniquely identifies it. To fill our `ColumnView`, we first instance it, then allocate a non-zero number of columns: ```julia column_view = ColumnView() column_01 = push_back_column!(column_view, "Column #01") column_02 = push_back_column!(column_view, "Column #02") column_03 = push_back_column!(column_view, "Column #03") ``` Each column requires a title that should be unique to that column. We can also add a column at any point, even after rows have been added. Along with [`push_back_column!`](@ref), [`push_front_column!`](@ref) and [`insert_column_at!`](@ref) are also available. All of these functions return an object of type [`ColumnViewColumn`](@ref). To add a widget into the n-th row (1-based) of a `ColumnViewColumn`, we use `set_widget_at!`: ```julia # add 3 labels into column 1, rows 1 - 3 set_widget_at!(column_view, column_01, Label("01")) set_widget_at!(column_view, column_01, Label("02")) set_widget_at!(column_view, column_01, Label("03")) ``` ![](../assets/column_view.png) !!! details "How to generate this image" ```julia main() do app::Application println("called") window = Window(app) set_title!(window, "Mousetrap.jl") column_view = ColumnView() column_01 = push_back_column!(column_view, "Column #01") column_02 = push_back_column!(column_view, "Column #02") column_03 = push_back_column!(column_view, "Column #03") column_i = 1 for column in [column_01, column_02, column_03] for row_i in 1:9 set_widget_at!(column_view, column, row_i, Label("0$column_i | 0$row_i")) end column_i = column_i + 1 end set_expand!(column_view, true) set_child!(window, column_view) present!(window) end ``` Any rows that do not yet have widgets will be backfilled and appear empty. If we lose track of the `ColumnViewColumn` instance returned when adding a new column, we can retrieve it using `get_column_at` or `get_column_with_title`, the latter of which takes the unique title we chose when adding the column. Since most of the time, we will want all cells in a row to contain a widget, we can also use [`push_back_row!`](@ref), [`push_front_row!`](@ref), or [`insert_row_at!`](@ref), which insert n widgets at once, where n is the number of columns: ```julia # add 1st widget to 1st column, 2nd widget to 2nd column, etc. push_back_row!(column_view, Label("Column 01 Child"), Label("Column 02 Child"), Label("Column 03 Child")) ``` This is a more convenient way to fill the column view, though if we later want to edit it, we will have to use `set_widget_at!` to override widgets in any rows. `ColumnViewColumn` has several other features. We can make it so the user can freely resize each column by setting [`set_is_resizable!`](@ref) to `true`, or we can force each column to have an exact width using [`set_fixed_width!`](@ref), which takes the widget width in pixels. --- ## Stack [`Stack`](@ref) is a selectable widget that can only ever display exactly one child at a time. Each child of the stack is called a **page**. We add a page using `add_child!`, which takes any widget, and the title of the page. This title is mandatory and it has to uniquely identify the page. `add_child!` returns the page's ID, which, similarly to how adding elements to `DropDown` works, we need to keep track of to later refer to pages in a position-independent manner. ```julia stack = Stack() id_01 = add_child!(stack, page_01_widget, "Page #01") id_02 = add_child!(stack, page_02_widget, "Page #02") id_03 = add_child!(stack, page_03_widget, "Page #03") ``` To check which page is currently visible, we use [`get_visible_child`](@ref), which returns that page's ID. If we loose track of it, we can retrieve the ID of a stack page at a given position using [`get_child_at`](@ref). To keep track of which page is currently selected, we should connect to the stack's underlying `SelectionModel`, just like we would with `ListView` and `GridView`: ```julia function on_selection_changed(self::SelectionModel, position::Integer, n_items::Integer, stack::Stack) ::Nothing println("Current stack page is now: $(get_child_at(stack, position))") end stack = Stack() stack_model = get_selection_model(stack) connect_signal_selection_changed!(on_selection_changed, stack_model, stack) ``` Where we provided the `Stack` instance to the selection model's signal handler as the optional `data` argument, so that we can reference it from within the signal handler. While we can change the currently active page using `set_visible_child!`, our user cannot. To allow them to change the page of a `Stack`, we either need to provide another widget that modifies the stack, or we can use one of two pre-made widgets for this purpose: `StackSwitcher` and `StackSidebar`. ### StackSwitcher [`StackSwitcher`](@ref) presents the user with a row of buttons, each of which uses the corresponding stack page's title: ![](../assets/stack_switcher.png) !!! details "How to generate this image" ```julia function generate_child(label::String) ::Widget child = Frame(Overlay(Separator(), Label(label))) set_size_request!(child, Vector2f(150, 150)) set_margin!(child, 10) return child end main() do app::Application window = Window(app) stack = Stack() add_child!(stack, generate_child("Child #01"), "Page #01") add_child!(stack, generate_child("Child #02"), "Page #02") add_child!(stack, generate_child("Child #03"), "Page #03") stack_model = get_selection_model(stack) connect_signal_selection_changed!(stack_model, stack) do x::SelectionModel, position::Integer, n_items::Integer, stack::Stack println("Current stack page is now: $(get_child_at(stack, position))") end set_child!(window, vbox(stack, StackSwitcher(stack))) # create StackSwitcher from stack present!(window) end ``` `StackSwitcher` has no other methods or properties, though it provides the signals that all widgets share. ### StackSidebar [`StackSidebar`](@ref) has the same purpose as `StackSwitcher`, except it displays the list of stack pages as a vertical list: !!! details "How to generate this image" ```julia function generate_child(label::String) ::Widget child = Frame(Overlay(Separator(), Label(label))) set_size_request!(child, Vector2f(150, 150)) set_margin!(child, 10) return child end main() do app::Application window = Window(app) stack = Stack() add_child!(stack, generate_child("Child #01"), "Page #01") add_child!(stack, generate_child("Child #02"), "Page #02") add_child!(stack, generate_child("Child #03"), "Page #03") stack_model = get_selection_model(stack) connect_signal_selection_changed!(stack_model, stack) do x::SelectionModel, position::Integer, n_items::Integer, stack::Stack println("Current stack page is now: $(get_child_at(stack, position))") end set_child!(window, vbox(stack, StackSwitcher(stack))) # Create StackSidebar from stack present!(window) end ``` Other than this visual component, its purpose is identical to that of `StackSwitcher`. ### Transition Animation When changing which of the stack pages is currently shown, regardless of how that selection was triggered, an animation transitioning from one page to the other plays. Similar to `Revealer`, we can influence the type and speed of the animation in multiple ways: + [`set_transition_duration!`](@ref) chooses how long the animation will take to complete + [`set_should_interpolate_size!`](@ref), if set to `true`, makes it, such that while the transition animation plays, the stack will change from the size of the previous child to the size of the current child gradually. If set to `false`, this size change happens instantly + [`set_transition_type!`](@ref) governs the type of animation, which is one of the enum values of [`StackTransitionType`](@ref). If we want all of the stacks children to allocate the same size, we can set [`set_is_vertically_homogeneous!`](@ref) and [`set_is_horizontally_homogeneous!`](@ref) to `true`, in which case the stack will assume the height or widget of its largest page, respectively. --- Moving on from selectable widgets, we have a few more widget containers to get through. These offer more flexibility in arranging widgets, but do not offer `get_selection_model` and its related features. ## Grid Not to be confused with `GridView`, [`Grid`](@ref) arranges its children in a **non-uniform** grid: ![](../assets/grid.png) !!! details "How to generate this image" ```julia function generate_child(label::String) ::Widget child = Frame(Overlay(Separator(), Label(label))) set_size_request!(child, Vector2f(50, 50)) return child end main() do app::Application window = Window(app) grid = Grid() Mousetrap.insert_at!(grid, generate_child("01"), 1, 1, 2, 1) Mousetrap.insert_at!(grid, generate_child("02"), 3, 1, 1, 2) Mousetrap.insert_at!(grid, generate_child("03"), 4, 1, 1, 1) Mousetrap.insert_at!(grid, generate_child("04"), 1, 2, 1, 2) Mousetrap.insert_at!(grid, generate_child("05"), 2, 2, 1, 1) Mousetrap.insert_at!(grid, generate_child("06"), 4, 2, 1, 1) Mousetrap.insert_at!(grid, generate_child("07"), 2, 3, 3, 1) set_margin!(grid, 10) set_child!(window, grid) present!(window) end ``` Each widget in the grid has an x- and y-position, along with a widget and height, both measured in **number of cells** . For example, in the image above, the widget labeled `05` has an x-position of 2, y-position of 2, a width of 1 cell, and a height of 1 cell. ```julia Mousetrap.insert_at!( grid, widget_05, 2, # x-position 2, # y-position 1, # width 1 # height ) ``` Meanwhile, the widget labeled `07` has an x-position of 2, y-position of 3, width of 3 cells, and height of 1 cell. ```julia Mousetrap.insert_at!( grid, widget_07, 2, # x-position 3, # y-position 3, # width 1 # height ) ``` When using `insert_at!`, we have to make sure that no two widgets overlap. For a less manual way of arranging widgets, we can insert a widget next to another widget already in the grid using [`insert_next_to!`](@ref), which takes an argument of type [`RelativePosition`](@ref) in order to specify whether we want to insert the new widget above, below, left of, or right of the widget already inside the grid: ```julia child = # ... # insert `child` at position (1, 1) insert_at!(grid, child, 1, 1) new_widget = # ... # insert new widget left of child insert_next_to!(grid, new_widget, child, RELATIVE_POSITION_LEFT_OF) ``` Where we omitted the width and height in cells, which default to `1` respectively. Note that in this example, `child` is at position `(1, 1)`, and `new_widget` was inserted left of `child`, meaning it is now at position `(0, 1)`. `Grid` will dynamically insert rows and columns as needed, meaning that the x- and y-position may be `0` or even negative. Lastly, `set_column_spacing!` and `set_row_spacing!` automatically insert margins in between columns and rows, while `set_rows_homogeneous!` and `set_columns_homogeneous!` make it so all rows or columns will allocate the same height and width, respectively. `Grid` offers a more flexible, but also more manual way of arranging widgets in 2D space. --- ## Notebook [`Notebook`](@ref) is very similar to `Stack`, it always displays exactly one child, though `Notebook` is not based on a `SelectionModel`. Unlike `Stack`, it comes with a built-in way for users to choose which child to show. Each notebook page has two widgets, the **child widget**, which is displayed in the content area of each page, and the **label widget**, which is the label used for the tab. This will usually be a `Label`, though any widget can be used. We add pages using `push_back!`, `push_front!` or `insert_at!`, which take the child and label widget as their arguments: ```julia notebook = Notebook() push_back!(notebook, child_01, Label("Label #01")) push_back!(notebook, child_02, Label("Label #02")) push_back!(notebook, child_03, Label("Label #03")) ``` ![](../assets/notebook.png) !!! details "How to generate this image" ```julia function generate_child(label::String) ::Widget child = Frame(Overlay(Separator(), Label(label))) set_size_request!(child, Vector2f(150, 150)) set_margin!(child, 10) return child end main() do app::Application window = Window(app) notebook = Notebook() push_back!(notebook, generate_child("Child #01"), Label("Label #01")) push_back!(notebook, generate_child("Child #02"), Label("Label #02")) push_back!(notebook, generate_child("Child #03"), Label("Label #03")) set_child!(window, notebook) present!(window) end ``` We can allow the user to reorder the pages by setting [`set_tabs_reorderable!`](@ref) to `true`. This will make it so they can click drag-and-drop the label widget, moving the entire notebook page to that position. If we want to change the currently selected page, we use `next_page!`, `previous_page!`, or `goto_page!`, the latter of which takes the page position as an integer. To react to the user changing the currently selected page, we have to connect to one of `Notebook`'s unique signals, all of which require a signal handler with the same signature: ```@eval using Mousetrap Mousetrap.@signal_table(Notebook, page_added, page_removed, page_reordered, page_selection_changed ) ``` For example, if we want to react to the currently selected page changing, we would connect to signal `page_selection_changed` like so: ```julia notebook = Notebook() connect_signal_page_selection_changed!(notebook) do self::Notebook, index::Integer println("Page #$index is now selected") end ``` --- ## Compound Widgets Now that we have many new widgets in our arsenal, a natural question is "How do we make our own?". If we want to construct a completely new widget pixel-by-pixel, we will have to wait until the chapter on [native rendering](./09_native_rendering.md). Until then, we are already perfectly capable of creating what we'll call a **compound widget**. A compound widget is a widget that groups many other, pre-made widgets together. Compound widgets give an easy mechanism to logically group a collection of widgets and treat them as a whole, instead of having to operate on each of its parts individually. In order for a Julia object to be considered a `Widget`, that is, all functions that take a `Mousetrap.Widget` also work on this new object, it has to implement the **widget interface**. An object is considered a `Widget` if... + it is a direct or indirect subtype of `Widget` + [`Mousetrap.get_top_level_widget`](@ref), which maps it to its top-level widget, is defined for this type In this section we will work through an example, explaining what both of these conditions mean, and how to fulfill them. ### Example: Placeholder In the previous section on selectable containers such as `ListView` and `GridView`, we used this "placeholder" widget: ![](../assets/compound_widget_list_view.png) In this image we have a `ListView` with four elements, each of the elements is an object of type `Placeholder`, which is a newly created compound widget. Looking closely, we see that each placeholder has a `Label` with the title and a `Separator` behind the label. Because they are rendered on top of each other, an `Overlay` has to be involved. A `Frame` is used to give the element rounded corners, lastly, each element is square at all times, which means its size is managed by an `AspectFrame`. Creating a new Julia struct with these elements, we do: ```julia struct Placeholder label::Label separator::Separator overlay::Overlay frame::Frame aspect_frame::AspectFrame end ``` We add a constructor that correctly assembles these widgets like so: ```julia function Placeholder(text::String) out = Placeholder( Label("" * text * "") # label with monospaced text Separator() # background Overlay() # overlay to draw `label` on top of `separator` background Frame() # frame for outline and rounded corners AspectFrame(1.0) # square aspect frame ) # make the background the lower most layer set_child!(out.overlay, out.separator) # add the label on top add_overlay(out.overlay, out.label; include_in_measurement = true) # add everything into the `Frame` set_child!(frame, overlay) # force frame to be square set_child!(aspect_frame, frame) return out end ``` With our compound widget assembled, we will want to make it the child of a `Window` or other container: ```julia main() do app::Application window = Window(app) set_child!(window, Placeholder("Test")) present!(window) end ``` ``` [ERROR] In Mousetrap.main: MethodError: no method matching set_child!(::Window, ::Placeholder) Closest candidates are: set_child!(::Window, ::Widget) ``` We can't, because `Placeholder`, the new Julia struct, is not yet considered a `Mousetrap.Widget`. ### Widget Interface To solve this, we come back to our two properties that make something a widget: 1) it is a direct or indirect subtype of `Widget` 2) `get_top_level_widget`, which maps it to its top-level widget, is defined for this type Solving 1), we make `Placeholder` a subtype of `mousetrap.Widget`: ```julia struct Placeholder <: Widget label::Label separator::Separator overlay::Overlay frame::Frame aspect_frame::AspectFrame end ``` To implement `get_top_level_widget`, we need to look at how we assembled `Placeholder` during its constructor, then figure out which widget is indeed top-level. We can write the `Placeholder` architecture out like so: ```cpp AspectFrame \ Frame \ Overlay \ Label Separator ``` Where a widget with `\` is a widget container. The only widget without a direct parent is the `AspectFrame`, therefore `aspect_frame` is the top-level widget. Given this, we implement `get_top_level_widget` like so: ```julia Mousetrap.get_top_level_widget(x::Placeholder) = x.aspect_frame ``` Where the `Mousetrap.` prefix is necessary to tell the compiler that we are overloading that method, not creating a new method in our current module. With `Widget` subtyped and `get_top_level_widget` implemented, our `main.jl` now looks like this: ```julia struct Placeholder <: Widget label::Label separator::Separator overlay::Overlay frame::Frame aspect_frame::AspectFrame end function Placeholder(text::String) out = Placeholder( Label("" * text * ""), Separator(), Overlay(), Frame(), AspectFrame(1.0) ) set_child!(out.overlay, out.separator) add_overlay!(out.overlay, out.label; include_in_measurement = true) set_child!(out.frame, out.overlay) set_child!(out.aspect_frame, out.frame) return out end Mousetrap.get_top_level_widget(x::Placeholder) = x.aspect_frame main() do app::Application window = Window(app) set_child!(window, Placeholder("TEST")) present!(window) end ``` ![](../assets/compound_widget_complete.png) Now that `Placeholder` is a proper widget, all of Mousetraps functions, including all widget signals, have become available to use. We need to keep in mind that these functions will modify the top-level widget, so only signals of the top-level widget are available to compound widgets. Using this technique, we can build an application piece by piece. A compound widget itself can be made up of multiple other compound widgets. In some way, the entire application itself is just one giant compound widget, with a `Window` as the top-level widget. !!! compat Since Mousetrap `v0.3.0`, compound widgets will assume the signals of their top-level widgets. For example, if the compound widget `EntryWrapper` has its top-level widget be an `Entry`, then `connect_signal_activate!` will work on instances of `EntryWrapper`, since `EntryWarpper` assumed signal `activate` Our users are only able to interact with the compound widget by interacting with each of its components, which works but is fairly limiting. In the next chapter, this will change. By learning about **event handling**, we will be able to react to any kind of user interaction, giving us the last tool needed to create an application that isn't just a collection of pre-made widgets, but something we have built ourselves. ## Custom Signals !!! compat This feature is not yet implemented, this section is incomplete. See [here](https://github.com/users/Clemapfel/projects/2?pane=issue&itemId=35716010#) for more information. ================================================ FILE: docs/src/01_manual/05_event_handling.md ================================================ # Chapter 5: Event Handling In this chapter, we will learn: + What input focus is + What an event controller is + How to connect an event controller to any widget + How to react to any user input event --- ## Introduction: Event Model So far, we have been able to react to a user interacting with the GUI through pre-made widgets. For example, if the user clicks a `Button`, that button will emit the signal `clicked`, which can trigger our custom behavior. While this mechanism works, it can also be fairly limiting. Pre-defined widgets will only have pre-defined ways of interacting with them. We cannot react to the user pressing the space bar with just `Button`. To do that, we will need an **event controller**. ### What is an Event? When the user interacts with a computer in the physical world, they will control some kind of device, for example, a mouse, keyboard, touchpad, webcam, stylus, etc. Through a driver, the device will send data to the operating system, which then processes it into what is called an **event**. Pressing a keyboard key is an event; releasing the key is a new event. Moving the cursor by one unit is an event; pressing a stylus against a touchpad is an event, etc. Mousetrap is based on GTK4, which has very powerful and versatile event abstraction. We don't have to deal with OS-specific events directly, instead, the GTK backend will automatically transform and distribute events for us, regardless of the operating system or peripheral manufacturer. To receive events, we need an [`EventController`](@ref). The `EventController` type is abstract, meaning we cannot use it directly. Instead, we deal with one or more of its subtypes, each of which handles one conceptual type of event. **For an event controller to be able to capture events, it needs to be connected to a widget**. Once connected, if the widget holds **input focus**, events are forwarded to the event controller, whose signals we can then connect custom behavior to. ## Input Focus The concept of [**input focus**](https://en.wikipedia.org/wiki/Focus_(computing)) is important to understand. In Mousetrap (and GUIs in general), each widget has a hidden property that indicates whether the widget currently **holds focus**. If a widget holds focus, all its children hold focus as well. For example, if the focused widget is a `Box`, all widgets inside that box also hold focus, while the widget the box is contained within does not. **Only a widget holding focus can receive input events**. Which widget acquires focus is controlled by a somewhat complex heuristic, usually using things like which window is on top and where the user last interacted with the GUI. For most situations, this mechanism works very well and we don't have to worry about it much, in the rare cases we do, we can control the focus directly. ### Preventing Focus Only `focusable` widgets can hold focus. We can make a widget focusable by calling [`set_is_focusable!`](@ref). Not all widgets are focusable by default. To know which are and are not focusable, we can use [`get_is_focusable`](@ref), or common sense: Any widget that has a way of interacting with it (such as a `Button`, `Entry`, `Scale`, `SpinButton`, etc.) will be focusable by default. Widgets that have no native way of interacting with them are not focusable unless we specifically request them to be. This includes widgets like `Label`, `ImageDisplay`, `Separator`, etc. If a container widget has at least one focusable child, it itself is focusable. To prevent a widget from being able to gain focus, we can either disable the widget entirely by setting [`set_can_respond_to_input!`](@ref) to `false`, or we can declare it as not focusable using [`set_is_focusable!`](@ref). ### Requesting Focus [`grab_focus!`](@ref) will make a widget attempt to gain input focus, stealing it from whatever other widget is currently focused. If this is not possible, for example, because the widget is disabled, not yet shown, or is not focusable, nothing will happen. We can check if a widget currently has focus by calling [`get_has_focus`](@ref). Many widgets will automatically grab focus if interacted with. For example, if the user places the text cursor inside an `Entry`, that entry will grab focus. Pressing enter now activates the entry, even if another widget held focus before. If a `SpinButton` is clicked, it will usually grab focus. We can make any widget, even those that do not require interaction, grab focus when clicked by setting [`set_focus_on_click!`](@ref) to `true` after they have been made focusable. The user can also decide which widget should hold focus, usually by pressing the tab key. If [`set_focus_visible!`](@ref) was set to `true` for the top-level window, the focused widget will be highlighted using a transparent border. --- ## Event Controllers ### Connecting a Controller Using our newly gained knowledge about focus, we'll create our first event controller: [`FocusEventController`](@ref). This controller reacts to a widget gaining or losing input focus. After creating an event controller, it will not yet react to any events. We need to **add the controller to a widget**. For this chapter, we will assume that this widget is the top-level window, called `window` in the code snippets henceforth. We create and connect a `FocusEventController` like so: ```julia focus_controller = FocusEventController() add_controller!(window, focus_controller) ``` While the controller will now receive events, nothing else will happen. We need to connect to one or more of its signals, using the familiar signal handler mechanism. ## Gaining / Losing Focus: FocusEventController `FocusEventController` has two signals: ```@eval using Mousetrap return Mousetrap.@signal_table(FocusEventController, focus_gained, focus_lost ) ``` After connecting to these signals: ```julia function on_focus_gained(self::FocusEventController) ::Nothing println("focus gained") end function on_focus_lost(self::FocusEventController) ::Nothing println("focus lost") end focus_controller = FocusEventController() connect_signal_focus_gained!(on_focus_gained, focus_controller) connect_signal_focus_gained!(on_focus_lost, focus_controller) add_controller!(window, focus_controller) ``` We have successfully created our first event controller. Now, whenever `window` gains focus, a message will be printed. --- ## Keyboard Keys: KeyEventController Monitoring focus is rarely necessary, for something much more commonly used, we turn to keyboard keystrokes. Events of this type are emitted whenever a key on a keyboard goes from not-pressed to pressed (down), or pressed to not-pressed (up). We capture events of this type using [`KeyEventController`](@ref). ### Key Identification From the [chapter on actions](./03_actions.md) we recall that keyboard keys are split into two groups, **modifiers** and **non-modifiers**. Like with shortcut triggers, these are handled separately. Each non-modifier key has a key code, which will be a constant defined by `Mousetrap`. A full list of constants is available [here](https://github.com/Clemapfel/mousetrap.jl/blob/main/src/key_codes.jl), or as the global `Mousetrap.key_codes`, which provides a human-readable way to identify keys. For example, to refer to the space key, Mousetrap uses a hard-coded integer internally. We as developers do not need to remember this value, instead, we use the `KEY_space` constant: ```@repl using Mousetrap KEY_space ``` ### KeyEventController Signals Now that we know how to identify keys, we can instance `KeyEventController`, which has 3 signals: ```@eval using Mousetrap return Mousetrap.@signal_table(KeyEventController, key_pressed, key_released, modifiers_changed ) ``` We see that pressing and releasing a non-modifier key are handled in separate signals. These can be used to track whether a key is currently up or down. Lastly, pressing or releasing any modifier key (such as control, alt, shift, etc.) is handled by the `modifiers_changed` signal. Note that for `KeyEventController`, the left and right mouse buttons are also considered modifiers. The signal handler for any of the three signals is handed two arguments, a `KeyCode`, which is the key code constant of the keyboard key that triggered the signals, as well as `ModifierState`. This is an object that holds information about which modifiers are currently pressed. To query it, we use [`control_pressed`](@ref), [`shift_pressed](@ref), [`alt_pressed`](@ref), [`mouse_button_01_pressed`](@ref), and [`mouse_button_02_pressed`](@ref), which return `true` or `false` depending on whether the modifier is currently down. For example, to test whether the user pressed the space key while the shift key is held, we could do: ```julia function on_key_pressed(self::KeyEventController, code::KeyCode, modifier_state::ModifierState) ::Nothing if code == KEY_space && shift_pressed(modifier_state) println("shift + space pressed") end end key_controller = KeyEventController() connect_signal_key_pressed!(on_key_pressed, key_controller) add_controller!(window, key_controller) ``` While we would connect to the `KeyEventController`s other signals like so: ```julia function on_key_released(self::KeyEventController, code::KeyCode, modifier_state::ModifierState) ::Nothing # handle key here end function on_modifiers_changed(self::KeyEventController, modifier_state::ModifierState) ::Nothing # handle modifiers here end connect_signal_key_released!(on_key_released, key_controller) connect_signal_modifiers_changed!(on_modifiers_changed, key_controller) ``` `KeyEventController` should be used if we have to monitor both pressing **and releasing** a key or key combination. If all we want to do is trigger behavior when the user presses a combination once, we should use `ShortcutEventController` instead. ## Detecting Key Bindings: ShortcutEventController Recall that in the [chapter on actions](./03_actions.md), we learned that a shortcut trigger, or keybinding, is made up of any number of modifier keys, along with exactly one non-modifier. To react to the user pressing such a shortcut, we should use [`ShortcutEventController`](@ref), which has an easier-to-use and more flexible interface than `KeyEventController`. We first need an `Action`, which we associate a shortcut trigger with: ```julia action = Action("shortcut_controller.example", app) do self::Action println("shift + space pressed") end add_shortcut!(action, "space") ``` Where `app` is an `Application` instance. We can then create a `ShortcutEventController` instance and call `add_action!`, after which it will listen for the associated keybinding of that action. Any number of actions can be added to a `ShortcutEventController`, and they can be removed at any time using [`remove_action!`](@ref). For the shortcut controller to start receiving events, we also need to connect it to a widget: ```julia shortcut_controller = ShortcutEventController() add_action!(shortcut_controller, action) add_controller!(window, shortcut_controller) ``` Where `window` is the top-level window. Note that `ShortcutEventController` does not have any signals to connect to it. Instead, it automatically listens for shortcuts depending on which `Action` we added. This mechanism is far more automated than having to manually check from within a `KeyEventController` signal handler if the current combination of buttons should trigger a shortcut action. This is why `ShortcutEventController` should be preferred in situations where we do not care about individual keystrokes. --- ## Cursor Motion: MotionEventController Now that we know how to handle keyboard events, we will turn our attention to mouse-based events. There are two types of events a mouse can emit, **cursor motion** and **mouse button presses**. These are handled by two different controllers. For cursor motion, the event controller is called [`MotionEventController`](@ref), which has 3 signals: ```@eval using Mousetrap return Mousetrap.@signal_table(MotionEventController, motion_enter, motion, motion_leave ) ``` `motion_enter` is emitted once when the cursor enters the widget area, then, once per frame, `motion` is emitted to update us about the current cursor position. Lastly, when the cursor leaves the widget's allocated area, `motion_leave` is emitted exactly once. `motion_enter` and `motion` supply us with the current cursor position, which is relative to the current widget's origin, in pixels. That is, for a widget whose top-left corner (its origin) is at position `(widget_position_x, widget_position_y)`, if the coordinate supplied by the signals is `(x, y)`, then the cursor is at position `(widget_position_x + x, widget_position_y + y)`, in pixels. Shown here is an example of how to connect to these signals, where we forwarded `window`, the host widget of the controller, as the `data` argument of signal `motion`, to calculate the absolute position of the cursor on screen. ```julia function on_motion(::MotionEventController, x::AbstractFloat, y::AbstractFloat, data::Widget) ::Nothing widget_position = get_position(data) cursor_position = Vector2f(x, y) println("Absolute Cursor Position: $(widget_position + cursor_position)") end window = Window(app) motion_controller = MotionEventController() connect_signal_motion!(on_motion, motion_controller, window) add_controller!(window, motion_controller) ``` While we would connect to `MotionEventController`s other signals like so: ```julia function on_motion_enter(::MotionEventController, x::AbstractFloat, y::AbstractFloat) ::Nothing # handle cursor enter end function on_motion_leave(::MotionEventController) ::Nothing # handle cursor leave end connect_signal_motion_enter!(on_motion_enter, motion_controller, window) connect_signal_motion_leave!(on_motion_leave, motion_controller, window) ``` --- ## Mouse Button Presses: ClickEventController With cursor movement taken care of, we now turn our attention to handling the other type of mouse event: button presses. A mouse button is... any button on a mouse, which is less intuitive than it sounds. Mice are hugely varied, with some having exactly one button, while some mice have six or more buttons. If the user uses no mouse at all, for example, when choosing to control the app with a trackpad, trackball, or touchscreen, touchscreen "taps" will be registered as if the left mouse button was pressed. We track mouse button presses with [`ClickEventController`](@ref) which has 3 signals: ```@eval using Mousetrap return Mousetrap.@signal_table(ClickEventController, click_pressed, click_released, click_stopped ) ``` Much like with `MotionEventController`, the signals provide the handler with `x` and `y`, the position of the cursor at the time the click happened, relative to the host widget's origin, in pixels. The first argument for two of the signals `click_pressed` and `click_released`, `n_presses`, is the number of presses in the current click sequence. For example, `n_presses = 2` means that this is the second time a mouse button was pressed in sequence, `click_stopped` is emitted when that sequence of clicks has ended. It may be helpful to consider an example: Let's say the user clicks the left mouse button two times total, then stops clicking. This will emit the following events in this order: | Order | Signal ID | `n_pressed` value | |--------|-----------|------------------| | 1 | `click_pressed` | 1 | | 2 | `click_released` | 1 | | 3 | `click_pressed` | 2 | | 4 | `click_released` | 2 | | 5 | `click_stopped`| (none) | Thanks to the `n_pressed` argument, we can easily handle double clicks without any external function keeping track of how often the user has clicked so far. The delay after which a click sequence stops is system-dependent and usually decided by the user's operating system, not Mousetrap. ### Differentiating Mouse Buttons `ClickEventController` is one of a few event controllers that are also a sub-type of [`SingleClickGesture`](@ref). This interface provides functionality that lets us distinguish between different mouse buttons. Mousetrap supports up to 9 different mouse buttons, identified by the enum [`ButtonID`](@ref): + `BUTTON_ID_BUTTON_01` is usually the left mouse button (or a touchpad tap) + `BUTTON_ID_BUTTON_02` is usually the right mouse button + `BUTTON_ID_ANY` is used as a catch-all for all possible mouse buttons + `BUTTON_ID_BUTTON_03 - BUTTON_ID_BUTTON_09` are additional mouse-model-specific buttons + `BUTTON_ID_NONE` is none of the above To check which mouse button was pressed, we use [`get_current_button`](@ref) on the event controller instance from within the signal handler, which returns an ID as stated above. If we only want signals to be emitted for certain buttons, we can use [`set_only_listens_to_button!`](@ref) to restrict the choice of button. [`set_touch_only!`](@ref) filters all click events except those coming from touch devices. As an example, if we want to check if the user pressed the left mouse button twice, we can do the following: ```julia function on_click_pressed(self::ClickEventController, n_presses::Integer, x::AbstractFloat, y::AbstractFloat) ::Nothing if n_presses == 2 && get_current_button(self) == BUTTON_ID_BUTTON_01 println("double click registered at ($x, $y)") end end click_controller = ClickEventController() connect_signal_click_pressed!(on_click_pressed, click_controller) add_controller!(window, click_controller) ``` While we would connect to `ClickEventController`s other signals like so: ```julia function on_click_released(self::ClickEventController, n_presses::Integer, x::AbstractFloat, y::AbstractFloat) ::Nothing # handle button up end function on_click_stopped(self::ClickEventController) ::Nothing # handle end of a series of clicks end connect_signal_click_released!(on_click_released, click_controller) connect_signal_click_stopped!(on_click_stopped, click_controller) ``` While `ClickEventController` gives us full control over one or more clicks, there is a more specialized controller for a similar, but slightly different gesture: *long presses*. --- ## Long-Presses: LongPressEventController [`LongPressEventController`](@ref) reacts to a specific sequence of events, called a **long press** gesture. This gesture is recognized when the user presses a mouse button, and then keeps that button depressed without moving the cursor. After enough time has passed, `LongPressEventController` will emit its signals: ```@eval using Mousetrap return Mousetrap.@signal_table(LongPressEventController, pressed, press_cancelled ) ``` Where `pressed` is emitted in the same frame the long press is recognized. If the user releases the button before this time threshold is met, `press_cancelled` is emitted instead. Similar to `clicked`, `LongPressEventController` provides us with the location of the cursor, relative to the host widget's origin. `LongPressEventController`, like `ClickEventController`, subtypes `SingleClickGesture`, which allows us to differentiate between different mouse buttons or a touchscreen, just as before. ```julia function on_pressed(self::LongPressEventController, x::AbstractFloat, y::AbstractFloat) ::Nothing println("long press registered at ($x, $y)") end function on_press_cancelled(self::LongPressEventController) ::Nothing println("long press aborted") end long_press_controller = LongPressEventController() connect_signal_pressed!(on_pressed, long_press_controller) connect_signal_press_cancelled!(on_press_cancelled, long_press_controller) add_controller!(window, long_press_controller) ``` --- ## Click-Dragging: DragEventController A long press is a gesture in which a user clicks a mouse button, does not move the cursor, then **holds that position** for an amount of time. In contrast, **click-dragging** is slightly different: the user clicks a mouse button, holds it, but **does move the cursor**. This is often used to "drag and drop" an UI element, such as dragging a file icon from one location to another or dragging the knob of a `Scale`-like widget. Click-dragging gestures are automatically recognized by [`DragEventController`](@ref), which has three signals: ```@eval using Mousetrap return Mousetrap.@signal_table(DragEventController, drag_begin, drag, drag_end ) ``` When a click-drag is first recognized, `drag_begin` is emitted. In each frame the drag is taking place, `drag` is emitted once per frame to update us about the cursor position. Once the gesture ends, `drag_end` is emitted exactly once. All three signals supply two additional arguments with signal-specific meaning: + `scroll_begin` supplies the cursor location, relative to the host widget's origin, in pixels + `scroll` and `scroll_end` supply the **offset** between the current cursor position, and the position at the start of the gesture, in pixels To get the current position of the cursor, we have to add the offset from `scroll` or `scroll_end` to the initial position. We can get the initial position either in `scroll_begin`, or by calling [`get_start_position`](@ref). To track the cursor position during a drag gesture, we can connect to `DragEventController`s signals like so: ```julia function on_drag_begin(self::DragEventController, start_x::AbstractFloat, start_y::AbstractFloat) ::Nothing println("drag start: ($start_x, $start_y)") end function on_drag(self::DragEventController, x_offset::AbstractFloat, y_offset::AbstractFloat) ::Nothing start = get_start_position(self) println("drag update: ($(start.x + x_offset), $(start.y + y_offset)))") end function on_drag_end(self::DragEventController, x_offset::AbstractFloat, y_offset::AbstractFloat) ::Nothing start = get_start_position(self) println("drag end: ($(start.x + x_offset), $(start.y + y_offset)))") end drag_controller = DragEventController(); connect_signal_drag_begin!(on_drag_begin, drag_controller) connect_signal_drag!(on_drag, drag_controller) connect_signal_drag_end!(on_drag_end, drag_controller) add_controller!(window, drag_controller) ``` --- ## Panning: PanEventController **Panning** is similar to dragging, in that the user presses the mouse button (or touchscreen), and then holds the button while moving the cursor to a different location. The difference between panning and click-dragging is that **panning can only occur along exactly one of the two axes**: left-right (the x-axis) or top-bottom (the y-axis). This is commonly used in touchscreen UIs, for example, the user may scroll horizontally by using a pan gesture as opposed to a scroll wheel or 2-finger swipe. Panning is handled by the appropriately named [`PanEventController`](@ref), which is the first controller in this chapter that takes an argument to its constructor. We supply an [`Orientation`](@ref), which decides along which axis the controller should listen to panning for, `ORIENTATION_HORIZONTAL` for the x-axis, `ORIENTATION_VERTICAL` for the y-axis. A pan controller cannot listen to both axes at once, though we can connect two controllers, one for each axis, to the same widget to emulate this behavior. `PanEventController` only has one signal: ```@eval using Mousetrap return Mousetrap.@signal_table(PanEventController, pan ) ``` Which is emitted once per frame while the gesture is active. [`PanDirection`](@ref) is an enum with four values: `PAN_DIRECTION_LEFT`, `PAN_DIRECTION_RIGHT`, `PAN_DIRECTION_UP` and `PAN_DIRECTION_DOWN`. If the orientation was set to `ORIENTATION_HORIZONTAL`, only `PAN_DIRECTION_LEFT` and `PAN_DIRECTION_RIGHT` can occur, and vice-versa for `ORIENTATION_VERTICAL`. The second argument is the current offset, that is, the distance between the current position of the cursor and the position at which the gesture was first recognized, relative to the host widget's origin, in pixels. ```julia function on_pan(self::PanEventController, direction::PanDirection, offset::AbstractFloat) ::Nothing if direction == PAN_DIRECTION_LEFT println("panning left by $offset") elseif direction == PAN_DIRECTION_RIGHT println("panning right by $offset") end end pan_controller = PanEventController(ORIENTATION_HORIZONTAL) connect_signal_pan!(on_pan, pan_controller) add_controller!(window, pan_controller) ``` --- ## Mousewheel-Scrolling: ScrollEventController Other than clicking and cursor movement, many mice have a third function: scrolling. This is usually done with a designated wheel, though some operating systems also recognize scroll gestures using a trackpad or touchscreen. Either way, scroll events are registered by [`ScrollEventController`](@ref), which has four signals: ```@eval using Mousetrap return Mousetrap.@signal_table(ScrollEventController, scroll_begin, scroll, scroll_end, kinetic_scroll_decelerate ) ``` Three of these are fairly straightforward, when the user starts scrolling, `scroll_begin` is emitted once. As the user keeps scrolling, `scroll` is emitted every frame, updating us about the current position of the scroll wheel. The two additional arguments to the `scroll` signal handler, `delta_x` and `delta_y` are the vertical and horizontal offset, relative to the cursor position at which the scroll gesture was first recognized. Many systems support both vertical and horizontal scrolling (usually by clicking the mouse scroll wheel), which is why we get two offsets. When the user stops scrolling, `scroll_end` is emitted once. If we want to keep track of how far the user has scrolled a widget that had a `ScrollEventController` connect, we do the following: ```julia # variable to keep track of distance scrolled distance_scrolled = Ref{Vector2f}(Vector2f(0, 0)) # at the start of scroll, set sum to 0 function on_scroll_begin(self::ScrollEventController) ::Nothing global distance_scrolled[] = Vector2f(0, 0) println("scroll start") end # each frame, increase distance scrolled function on_scroll(self::ScrollEventController, x_delta::AbstractFloat, y_delta::AbstractFloat) ::Nothing global distance_scrolled[] += Vector2f(x_delta, y_delta) return nothing end # at the end of scroll, print distance function on_scroll_end(self::ScrollEventController) ::Nothing println("scrolled ($(distance_scrolled[].x), $(distance_scrolled[].y))") end # instance controller and connect signals scroll_controller = ScrollEventController() connect_signal_scroll_begin!(on_scroll_begin, scroll_controller) connect_signal_scroll!(on_scroll, scroll_controller) connect_signal_scroll_end!(on_scroll_end, scroll_controller) add_controller!(window, scroll_controller) ``` Where we used a global [`Ref`](https://docs.julialang.org/en/v1/base/c/#Core.Ref) to safely reference the value of `distance_scrolled` from within the signal handlers. ### Kinetic Scrolling `ScrollEventController` has a fourth signal that reacts to **kinetic scrolling**. Kinetic scrolling is a feature of modern UI, where the scroll movement simulates inertia. When the user triggers scrolling, the widget will continue scrolling even when the user is no longer touching the screen. The "friction" of the scrolling widget will slowly reduce the scroll velocity until it comes to a stop. `kinetic_scroll_decelerate` is emitted during this period, after the user has stopped touching the screen, but before the widget has a scroll velocity of 0. Its additional arguments `x_velocity` and `y_velocity` inform us of the velocity the widget should currently be scrolling at. To allow for kinetic scrolling, we need to enable it using [`set_kinetic_scrolling_enabled!`](@ref), then connect to the appropriate signal: ```julia # enable kinetic scrolling set_kinetic_scrolling_enabled!(scroll_controller, true) # signal handler function on_kinetic_scroll_decelerate(::ScrollEventController, x_velocity::AbstractFloat, y_velocity::AbstractFloat) ::Nothing println("kinetically scrolling with velocity of ($x_velocity, $y_velocity)") end # connect handler connect_signal_kinetic_scroll_decelerate!(on_kinetic_scroll_decelerate, scroll_controller) ``` --- ## Pinch-Zoom: PinchZoomEventController While `MotionEventController`, `ClickEventController`, etc. recognize both events from a mouse and touchscreen, Mousetrap offers some touch-only gestures, though many trackpads also support them. These are usually gestures performed using two fingers, the first of which is **pinch-zoom**. Pinch-zoom is when the user places two fingers on the touchscreen, then moves either, such that the distance between the fingers changes. This gesture is commonly used to zoom a view in or out. It is recognized by [`PinchZoomEventController`](@ref), which only has one signal: ```@eval using Mousetrap return Mousetrap.@signal_table(PinchZoomEventController, scale_changed ) ``` The argument `scale` is a *relative* scale, where `1` means no change between the distance of the fingers when compared to the distance at the start of the gesture, `0.5` means the distance halved, `2` means the distance doubled. `scale_changed` is usually emitted once per frame after the gesture is first recognized. Applications should react to every tick, as opposed to only the last. This will make the application feel more responsive and create a better user experience. To detect whether a user is currently zooming out (pinching) or zooming in, we could do the following: ```julia function on_scale_changed(self::PinchZoomEventController, scale::AbstractFloat) ::Nothing if scale < 1 println("zooming in") elseif scale > 1 pintln("zooming out") end end zoom_controller = PinchZoomEventController() connect_signal_scale_changed!(on_scale_changed, zoom_controller) add_controller!(window, zoom_controller) ``` --- ## 2-Finger Rotate: RotateEventController Another touch-only gesture is the **two-finger-rotate**. With this gesture, the user places two fingers on the touchscreen and then rotates them around a constant point in between the two fingers. This is commonly used to change the angle of something. This gesture is handled by [`RotateEventController`](@ref), which has one signal: ```@eval using Mousetrap return Mousetrap.@signal_table(RotateEventController, rotation_changed ) ``` It takes two arguments: `angle_absolute` and `angle_delta`. `angle_absolute` provides the current angle between the two fingers. `angle_delta` is the difference between the current angle and the angle at the start of the gesture. Both `angle_absolute` and `angle_delta` are provided in radians, to convert them we can use [`Mousetrap.Angle`](@ref): ```julia function on_rotation_changed(self::RotateEventController, angle_delta, angle_absolute) ::Nothing # convert to unit-agnostic Mousetrap.Angle absolute = radians(angle_absolute) delta = radians(angle_delta) println("Current Angle: $(as_degrees(absolute))°") end rotation_controller = RotateEventController() connect_signal_rotation_changed!(on_rotation_changed, rotation_controller) add_controller!(window, rotation_controller) ``` --- ## Swipe: SwipeEventController The last touch-only gesture is **swiping**, which is very similar to click-dragging, except with two fingers. Swiping is often used to "flick" through pages, for example, those of a `Stack`. Swiping is recognized by [`SwipeEventController`](@ref), which also only has one signal: ```@eval using Mousetrap return Mousetrap.@signal_table(SwipeEventController, swipe ) ``` The signal handler provides two arguments, `x_velocity` and `y_velocity`, which describe the velocity along both the x- and y-axis. To illustrate how to deduce the direction of the swipe, consider this example: ```julia function on_swipe(self::SwipeEventController, x_velocity::AbstractFloat, y_velocity::AbstractFloat) ::Nothing print("swiping ") if (y_velocity < 0) print("up ") elseif (y_velocity > 0) print("down ") end if (x_velocity < 0) println("left") elseif (x_velocity > 0) println("right") end end swipe_controller = SwipeEventController() connect_signal_swipe!(on_swipe, swipe_controller) add_controller!(window, swipe_controller) ``` UIs should react to both the direction and magnitude of the vector, even though the latter is ignored in this example. --- ## Touchpad & Stylus: StylusEventController Lastly, we have a highly specialized event controller. Common in illustration and animation, the **touchpad stylus** is usually a pen-like device that is used along with a **touchpad**, which may or may not be the screen of the device. Like with mice, there is a huge variety of stylus models. Shared among all models is a detection mechanism for whether the pen is **currently touching**, **not touching**, or **about to touch** the touchpad, along with a way to determine the position of the tip of the stylus. Additional features such as pressure or angle detection are manufacturer-specific. Many of these additional features are also recognized by [`StylusEventController`](@ref), the event controller used to handle these kinds of devices. `StylusEventController` has four signals: ```@eval using Mousetrap return Mousetrap.@signal_table(StylusEventController, stylus_up, stylus_down, proximity, motion ) ``` We recognize signal `motion` from `MotionEventController`. It behaves [exactly the same](#cursor-motion-motioneventcontroller), where `x` and `y` are the cursor position, in widget space. The three other signals are used to react to the physical distance between the stylus and touchpad. `stylus_down` is emitted when the pen's tip makes contact with the touchpad, `stylus_up` is emitted when this contact is broken, `proximity` is emitted when the stylus is about to touch the touchpad, or just left the touchpad. ```julia function on_stylus_up(self::StylusEventController, x::AbstractFloat, y::AbstractFloat) ::Nothing println("stylus is no longer touching touchpad, position: ($x, $y)") end function on_stylus_down(self::StylusEventController, x::AbstractFloat, y::AbstractFloat) ::Nothing println("stylus is ow touching touchpad, position: ($x, $y)") end function on_proximity(self::StylusEventController, x::AbstractFloat, y::AbstractFloat) ::Nothing println("stylus entered proximity range, position: ($x, $y)") end function on_motion(self::StylusEventController, x::AbstractFloat, y::AbstractFloat) ::Nothing println("stylus position: ($x, $y)") end stylus_controller = StylusEventController() connect_signal_stylus_up!(on_stylus_up, stylus_controller) connect_signal_stylus_down!(on_stylus_down, stylus_controller) connect_signal_proximity!(on_proximity, stylus_controller) connect_signal_motion!(on_motion, stylus_controller) add_controller!(window, stylus_controller) ``` ### Stylus Axis None of the above-mentioned signals provide information about additional stylus sensors. Because not all devices share these features, the mechanism for querying these are different. Each sensor provides a floating point value within a given range. This range is called a **device axis**. Axes are described by the enum [`DeviceAxis`](@ref), whose values identify all types of axes recognized by Mousetrap. We can query the value of each axis using [`get_axis_value`](@ref). This function will return 0 if the axis is not present. If it is, it will return the device-supplied axis-specific value. To check whether a device has a specific axis, we use [`has_axis`](@ref). Mousetrap recognizes the following axes: | `DeviceAxis` value | Meaning | |--------------------|-------------------------------------------------------------------| | `DEVICE_AXIS_X` | x-position of the pen | | `DEVICE_AXIS_Y` | y-position of the pen | | `DEVICE_AXIS_DELTA_X` | delta of horizontal scrolling | | `DEVICE_AXIS_DELTA_Y` | delta of vertical scrolling | | `DEVICE_AXIS_PRESSURE` | pressure sensor of the stylus' tip | | `DEVICE_AXIS_X_TILT` | angle relative to the screen, x-axis | | `DEVICE_AXIS_Y_TILT` | angle relative to the screen, y-axis | | `DEVICE_AXIS_WHEEL` | state of the stylus' wheel (if present) | | `DEVICE_AXIS_DISTANCE` | current distance between the touchpad surface and the stylus' tip | | `DEVICE_AXIS_ROTATION` | angle of the pen around the normal | | `DEVICE_AXIS_SLIDER` | value of the pen's slider (if present) | ### Stylus Mode Some styluses have a "mode" function, where the user can choose between different pen modes. This is driver-specific, and not all devices support this feature. For those that do, we can use [`get_tool_type`](@ref) to check which mode is currently selected. The recognized modes are: | `ToolType` value | Meaning | |--------------------|---------------------------| | `TOOL_TYPE_PEN` | basic drawing tip | | `TOOL_TYPE_ERASER` | eraser | | `TOOL_TYPE_BRUSH` | variable-width drawing tip | | `TOOL_TYPE_PENCIL` | fixed-width drawing tip | | `TOOL_TYPE_AIRBRUSH` | airbrush tip | | `TOOL_TYPE_LENS` | zoom tool | | `TOOL_TYPE_MOUSE` | cursor tool | | `TOOL_TYPE_UNKNOWN` | none of the above | If the stylus does not support modes, `TOOL_TYPE_UNKNOWN` will be returned. ================================================ FILE: docs/src/01_manual/06_image.md ================================================ # Chapter 6: Images In this chapter, we will learn: + How to use colors in Mousetrap + How to present the user with a color chooser dialog + How to load, store, modify, and display 2D images --- ## Introduction Mousetrap was originally written as the GUI engine for an unreleased frame-by-frame animation app. This history is why it contains a fully-featured image processing suite, making it well-suited for image-editing applications right out of the box. ## Colors Mousetrap offers two color representations, [`RGBA`](@ref) and [`HSVA`](@ref), which have the following components: | representation | component | meaning | |----------------|-------------|-----------------| | `RGBA` | `r` | red | | `RGBA` | `g` | green | | `RGBA` | `b` | blue | | `RGBA` | `a` | opacity (alpha) | | ---------- | ----------- | -------- | | `HSVA` | `h` | hue | | `HSVA` | `s` | saturation | | `HSVA` | `v` | value (chroma) | | `HSVA` | `a` | opacity (alpha) | For more information on these color systems, see [here for RGBA](https://en.wikipedia.org/wiki/RGBA_color_model) and [here for HSVA](https://en.wikipedia.org/wiki/HSL_and_HSV). For both representations, all components are 32-bit floats in `[0, 1]`. The **alpha** component is also called **opacity**, which is the inverse of *transparency*. An alpha value of 1 means the color is fully opaque, a value of 0 means it is fully transparent, making it invisible when displayed on screen. ### Converting Colors We can freely convert between `RGBA` and `HSVA`. To do this, we use [`rgba_to_hsva`](@ref) and [`hsva_to_rgba`](@ref): ```julia rgba = RGBA(0.1, 0.2, 0.3, 0.4) as_hsva = rgba_to_hsva(rgba) as_rgba = hsva_to_rgba(as_hsva) @assert rgba == as_rgba # true ``` ### Color to Hexadecimal Mousetrap offers a function to convert `RGBA` to its HTML color code. This code is a a string of the form `#RRGGBB`, where `RR` is the red, `GG` the green, and `BB` the blue component, in unsigned 8-bit hexadecimal. For example, the color `RGBA(1, 0, 1, 1)` would have the HTML-code `#FF00FF`, where the alpha component was omitted. Using `html_code_to_rgba` and `rgba_to_html_code`, we can freely convert between a colors in-memory and hexadecimal representation. For example, if we want to use an `Entry` for the user to be able to enter a color as an HTML color code, we could do the following: ```julia entry = Entry() connect_signal_activate!(entry) do self::Entry text = get_text(self) println(text) if is_valid_html_code(text) println("User entered: $(html_code_to_rgba(text))") else # handle malformatted string end return nothing end ``` If parsing was successful, `is_valid_html_code` will return `true`, at which point we can be sure that `html_code_to_rgba` will return a valid color. --- ## Color Chooser While manual entry like this works, it is hardly very user-friendly. For a more intuitive way to have our users select a color, Mousetrap offers a purpose-built dialog: [`ColorChooser`](@ref). `ColorChooser`s constructor takes the title of the window as its only argument. After initialization, we can show the dialog to the user by calling `present!`, just like with a `Window`: ```julia color_chooser = ColorChooser("Choose Color") present!(color_chooser) ``` ![](../assets/color_chooser_selection.png) If the user clicks on the `+` in the bottom left corner, they are taken to a new page that lets them select each component of the color directly: ![](../assets/color_chooser_custom_color.png) To actually trigger behavior once the user selects a color, we need to register a *callback*. `ColorChooser` has two callbacks, one invoked when the user makes the selection by clicking "select", and another when the user dismisses the dialog, for example by closing its window. We register the former using `on_accept!`, which requires a function with the signature: ``` (::ColorChooser, color::RGBA, [::Data_t]) -> Nothing ``` Where `color` will be the color the user selected. The function called when the dialog is dismissed is registered using `on_cancel!`, which requires a callback with the signature: ``` (::ColorChooser, [::Data_t]) -> Nothing ``` We would use these two functions like so: ```julia color_chooser = ColorChooser("Choose Color") # react to user selection on_accept!(color_chooser) do self::ColorChooser, color::RGBA println("Selected $color") end # react to use dismissing the dialog on_cancel!(color_chooser) do self::ColorChooser println("color selection canceleld") end present!(color_chooser) ``` At any point, we can also access the last selected color by calling [`get_color`](@ref) on the `ColorChooser` instance. --- ## Images Now that we know how to handle colors, we continue onto images. In general, an image is a two-dimensional matrix of colors. Each element in the matrix is called a **pixel**. An image of size 400x300 will have 400 * 300 = 120000 pixels. Each pixel is a color in `RGBA` format. Images are represented by the [`Image`](@ref) class. This class is not a widget or signal emitter; it is simply a way to manage memory (in the form of a pixel matrix) in RAM. If we want to show an image on screen, we need the help of other widgets. ### Creating an Image #### Loading an Image from Disk Most commonly, we will want to load an image from an already existing file. This can be achieved with [`create_from_file!`](@ref), which takes the path as a string: ```julia image = Image() create_from_file!(image, "/path/to/image.png"); ``` #### Supported Image Formats The following image formats are supported: | Format Name | File Extensions | |-------------------------|-----------------| | PNG | `.png` | | JPEG | `.jpeg` `.jpe` `.jpg` | | JPEG XL image | `.jxl` | | Windows Metafile | `.wmf` `.apm` | | Windows animated cursor | `.ani` | | BMP | `.bmp` | | GIF | `.gif` | | MacOS X icon | `.icns` | | Windows icon | `.ico` `.cur` | | PNM/PBM/PGM/PPM | `.pnm` `.pbm` `.pgm` `.ppm` | | QuickTime | `.qtif` `.qif` | | Scalable Vector Graphics | `.svg` `.svgz` `.svg.gz` | | Targa | `.tga` `.targa` | | TIFF | `.tiff` `.tif` | | WebP | `.webp` | | XBM | `.xbm` | | XPM | `.xpm` | It is recommended to use `Image` only for raster-based file types. While possible, for vector-graphics-based types like `.ico` and `.svg`, Mousetrap offers another more specialized class, [`Icon`](@ref), which we will learn about in the [next chapter](./07_os_interface.md). #### Creating an Image from Scratch Sometimes, we want to fill an image with our custom image data programmatically. For this, we use [`create!`](@ref), which allocates an image of a given size and fills each pixel with the color supplied as an optional argument. For example, the following allocates an image of size 400x300, then sets every pixel to red (`RGBA(1, 0, 0, 1)`): ```cpp image = Image() create!(image, 400, 300, RGBA(1, 0, 0, 1)); ``` If unspecified, the image will be filled with `RGBA(0, 0, 0, 0)`, making it appear fully transparent when displayed using a widget. ### Modifying an Image An all-red image will usually not be very useful, of course. To overwrite a single pixel, we use [`set_pixel!`](@ref), which takes as its first argument the pixel coordinate (1-based, with the origin at the top-left, just like a matrix), along with a color. We can access any pixel using [`get_pixel`](@ref), which only takes the pixel coordinates. If the coordinates are out of range, `RGBA(0, 0, 0, 0)` will be returned. ```julia # set the alpha component of the pixel at 32, 32 to zero image = # ... color = get_pixel(image, 32, 32) color.a = 0 set_pixel!(image, 32, 32, color) ``` ### Scaling To change an image's size, we have two options: **scaling** the image or **cropping** it. These operations work identically to those in common image-manipulation programs, such as GIMP or Photoshop. To scale an image, we call [`as_scaled`](@ref). This function returns a new image; it **does not modify the original image**. For example, scaling our 400x300 image to 800x600: ```cpp image = // ... 400x300 image scaled = as_scaled(image, 800, 600); ``` Only `scaled` will be of size `800x600`, `image` has not changed. #### Interpolation When scaling, we have a choice of scaling algorithm that fills in newly generated pixels through **interpolation**. Mousetrap offers four interpolation types, supplied by the enum [`InterpolationType`](@ref). Below, we see how each type affects the final image, where the image labeled with `1x` is the original image with a resolution of 10x10 pixels. ![](../assets/interpolation_nearest.png) `INTERPOLATION_TYPE_NEAREST` ![](../assets/interpolation_bilinear.png) `INTERPOLATION_TYPE_BILINEAR` ![](../assets/interpolation_hyperbolic.png) `INTERPOLATION_TYPE_HYPERBOLIC` ![](../assets/interpolation_tiles.png) `INTERPOLATION_TYPE_TILES` The main difference between `INTERPOLATION_TYPE_BILINEAR` and `INTERPOLATION_TYPE_HYPERBOLIC` is that of performance. Hyperbolic interpolation offers superior smoothing but does so at about 1.5 times the speed when compared to bilinear interpolation, meaning hyperbolic is about 50% slower. For an image this small, this will hardly matter, but when working with very large images, this can be a difference of seconds. If no interpolation type is specified when calling `as_scaled`, `INTERPOLATION_TYPE_TILES` will be chosen as the default. ### Cropping To crop an image, we use [`as_cropped`](@ref). Similar to `as_scaled`, this function returns a newly allocated image, it does not modify the original image. `as_cropped` takes 4 arguments, the new width and height, and the x- and y-**offset**. The offset specifies which pixel is used as the new top-left coordinate of the cropped image. This offset can be negative. We can also specify a new resolution greater than that of the current image. Any newly allocated space that is not part of the original image will be filled with `RGBA(0, 0, 0, 0)`: ```julia image = # ... # add a 10 px empty border around the image, keeping the original centered current_size = get_size(image) resized_image = as_cropped(-10, -10, current_size.x + 10, current_size.y + 10) ``` Cropping like this is often called "Resize Canvas" in common image manipulation apps. ### Flipping Lastly, we have [`as_flipped`](@ref) which flips the image along the x- and/or y-axes. Just like before, `as_flipped` returns a newly allocated image and does not modify the original. It takes two booleans, indicating along which axis the image should be flipped: ```julia image = # ... horizontally_flipped = as_flipped(image, true, false) vertically_flipped = as_flipped(image, false, true) ``` ### Saving an Image to Disk Having edited our image, we can store it on the users' disk using [`save_to_file`](@ref). This function takes a path as a string and returns a boolean indicating whether the operation was successful. The resulting image format will be deduced based on the file extension. For example, to save an image as a `.png` to the location `/assets/export`, we would do: ```julia image = # ... save_to_file(image, "/assets/export/out.png") ``` --- ## Displaying Images Now that we know how to load and manipulate images in memory, we will most likely want a way to display them. We've already seen a widget capable of this: [`ImageDisplay`](@ref). So far, we have been using `create_from_file!` to load the image directly from the disk. To create an `ImageDisplay` from an image in memory we use [`create_from_image!`](@ref). `ImageDisplay` [deep-copies](https://docs.julialang.org/en/v1/base/base/#Base.deepcopy) the contents of the image, the underlying data cannot be modified after this point. This means if we change the original `Image`, `ImageDisplay` **will not change**. To update the `ImageDisplay`, we need to call `create_from_image!` again. By default, `ImageDisplay` will expand according to the widget property of the same name. The graphical component of all widgets is expanded using linear interpolation, which may blur images in an undesirable way. To make sure `ImageDisplay` is always at the correct resolution and displays an image 1-to-1 (that is, 1 pixel of the image is exactly 1 pixel on the screen), we can use the following trick: ```julia image = Image() create_from_file!(image, #= load image of size 400x300 =#) image_display = ImageDisplay() create_from_image!(image_display, image) # prevent expansion along both dimensions set_expand!(image_display, false) # set minimum size to images original resolution set_size_request!(image_display, get_size(image_display)) ``` where `get_size` returns the resolution of the image the `ImageDisplay` was created from. Because expansion is disabled, `ImageDisplay` will always be exactly the size of its size request, which we set as the original resolution of the underlying image, making it so it will always be exactly 400x300 pixels on screen. ## Updating Images on Screen `create_from_image!` is a costly operation and would be insufficient to, for example, fluently display an animation at 60fps. We would have to call `create_from_image!` every frame, which is not feasible on most machines. In situations like this, we should instead use a custom render widget to display the image as an **OpenGL texture**, which has no problems rendering large, frequently updated images in a performant manner. We will learn more about textures in the [chapter on native rendering](./09_native_rendering.md). ================================================ FILE: docs/src/01_manual/07_os_interface.md ================================================ # Chapter 7: Operating System Interface In this chapter, we will learn: + What other features `Application` has + How to properly do logging + How to copy / move / create / delete files + How to automatically open a file or URL for the user + How to access a file's metadata + How to monitor a file changing + How to open a dialog that lets users select files + How to open an alert dialog that the user has to dismiss + How to store arbitrary objects in an .ini file + How to load and use icons --- ## Application We have already used it many times so far, but [`Application`](@ref), which is a signal emitter, offers several additional functionalities other than just storing our actions for us and keeping track of windows. It provides the following signals: ```@eval using Mousetrap Mousetrap.@signal_table(Application, activate, shutdown ) ``` All initialization of widgets and anything else our app uses should be done inside the `activate` signal handler, while `shutdown` can be used to safely free assets. [`main`](@ref), which we have used so far instead of connecting to signals, is actually just a convenience function that wraps the following behavior: ```julia main("com.example") do app::Application # behavior here end # is mostly equivalent to app = Application("com.example") connect_signal_activate!(app) do app::Application # behavior here return nothing end run!(app) ``` ### ID In the previous code snippet, `"com.example"` is the **application ID**. This ID will be used to identify the application on the user's OS. It should be unique, meaning that no other application on the user's operating system shares this ID. The application ID has to contain at least one `.` and should be a human-readable identifier in [RDNN format](https://docs.flatpak.org/en/latest/conventions.html#application-ids). For example, if our app is called "Foo Image Manipulation Program" and the app's website domain is `fimp.org`, we should use `org.fimp` as our ID. !!! warning "Running two apps with the same ID" If two applications with the same ID are active at the same time, **they will share assets**. This may introduce side effects, if both instances modify the same internal variable or widget, it may create a [race-condition](https://en.wikipedia.org/wiki/Race_condition#In_software). ### Starting / Ending Runtime [`run!`](@ref) starts the application. This function initializes the various back-ends needed to show widgets, after which it emits signal `activate`. This is why we cannot initialize a widget before `run!` is called, or we will get an error: ``` julia> label = Label() (process:15357): Mousetrap-ERROR **: 21:22:29.003: Attempting to construct a widget, but the GTK4 backend has not yet been initialized. (...) You have most likely attempted to construct a widget outside of `main` while using Mousetrap interactively. ``` We can force initialization without an `Application` instance with `Mousetrap.detail.initialize()`, though this is usually not recommended. At any point, we can attempt to end runtime by calling [`quit!`](@ref). This will usually cause the application to emit its signal `shutdown`. We should always `quit!` before calling Julias `exit()`, otherwise the app's process will be killed immediately, which can lead to undefined behavior. ### Holding & Busy Each app on a user's system has two boolean flags: whether it is currently **holding** and whether it is currently **busy**. Holding means that the application will attempt to prevent exiting in any way. We set this flag by calling [`hold!`](@ref), which should be used to prevent the user from accidentally ending runtime while an important process is running. We have to make sure to call [`release!`](@ref) to undo a previous `hold!`, after which the app can exit normally again. Being **busy** marks the app so that the OS recognizes that it is currently busy. This will prevent the "`app` is not responding" dialog many OS will trigger automatically when an app freezes. Sometimes, freezing is unavoidable because a costly operation is taking place. During times like this, we should call [`mark_as_busy!`](@ref), which notifies the OS that everything is still working as intended, it will just take a while. Once the expensive task is complete, [`unmark_as_busy!`](@ref) reverts the flag. ## Logging ### Introduction When shipping applications, stability is paramount. Nobody will use an app if it keeps crashing, especially if that crash may corrupt important files. The best way to prevent crashes is to follow [proper testing procedures](https://www.globalapptesting.com/blog/software-testing). For a small team, it is inevitable that some things will slip through the cracks. When an end-user comes to us with a problem or bug, they most likely will not be able to precisely describe the state of the application, and, depending on the user base, they may not be able to describe the problem at all. This is where objective information about what exactly was happening right before the crash is invaluable. **Logging** is the act of creating this information. Information about the current and past states of the application is stored in a file. This way, when a crash or bug occurs, we can simply ask the user to provide us with the log file to analyze ourselves. When working through past chapters, we may have already encountered some logging information. For example, if we try to do the following: ```cpp box = Box() push_back!(box, box) ``` We get the following message, printed to our console: ``` (example_target:45245): Mousetrap-CRITICAL **: 16:44:27.065: In Box::push_back!: Attempting to insert widget into itself. This would cause an infinite loop ``` We cannot insert a widget into itself, Mousetrap prevented this action and printed a log message to inform us of this instead. This protects the applications' stability from potential developer errors. Any and all functions should follow this philosophy: **prevent the error or bug, print a log message instead**. ### Log Message Properties Let's go through each part of the above message, one-by-one: #### Application ID First, we have `(example_target:45245)`, which is the identification of our application. During normal runtime, this information may not be very useful. Once the log is stored in a system-level log file, however, many applications may log at the same time to the same file. Knowing which log message came from which application is integral in this context. #### Log Domain Next, we have `Mousetrap-CRITICAL`. The word before the `-` is the **log domain**. This is a developer-defined identification that should state which part of the application or library caused the logging message. Pre-defined domains include `Mousetrap` and `Mousetrap.jl` for Mousetrap-specific warnings, `GTK` for GTK-based warnings, `GLib`, `Gio`, etc. As a user of Mousetrap, we should choose a new log domain. For example, if we create a new application called "Foo Image Manipulation Program", we should choose a descriptive log domain, such as `foo_image_manipulation_program`, `FIMP`, or `foo`. #### Log Levels `CRITICAL` is the messages **log level**. Mousetrap offers the following log levels: + `DEBUG` is for messages that should not appear when the end user operates the application, they are **only meant for developers**. These messages will not be stored or printed to the console unless we specifically request the logging suite to do so + `INFO` is for **benign status updates**, for example, `successfully opened file at (...)`. We should not overuse these, as they can clutter up log files. + `WARNING` is for messages that should attempt to **prevent undesirable but not critical behavior before it occurs**. For example, when attempting to close a file while it is still being written to, a warning should be printed and the closing should be postponed until the writing is done. + `CRITICAL` is for errors. In many languages, an error means the end of runtime, which is unacceptable for GUI applications. If the application throws a Julia exception, that exception [should be caught](https://docs.julialang.org/en/v1/manual/control-flow/#Exception-Handling) and printed as a `CRITICAL` log message instead. + `FATAL` is the most severe log level and should only be used as an absolute last resort. Once a `FATAL` warning is printed, the application exits immediately. These should be reserved for issues that make it impossible to run an application, for example, `no graphics card detected. quitting...` We see that our message from before was designated as `CRITICAL`. This is because adding a widget to itself would effectively deadlock the application, ending runtime. This makes it an issue too severe for a `WARNING`, but it is still recoverable (by preventing the insertion), therefore `FATAL` would be inappropriate. `WARNING`s may be triggered by users. If a user can trigger a `CRITICAL` log message, this inherently means we as developers failed to prevent the user from doing so. An issue like this should be addressed by redesigning the application, as opposed to educating users. For a large enough user base, the latter will inevitably fail and cause the error to happen anyway. #### Time Stamp After the log level, we have `16:44:27.065`, this is the **time stamp** of the log message, with millisecond precision. When storing the message to a file, the current date and year are also appended to the time stamp. #### Message Lastly, we have the **log message**. Log messages should contain the name of the function they are called from; for example, in the above message, it says `In Box::push_back!`, telling developers that the error happened in that function. This makes debugging easier. Messages should not end with a `\n` (a newline), as one is automatically appended to the end of the message. ### Printing Messages All interaction with the log is handled by only a few functions. To print a log message of a given log level, we use `log_debug`, `log_info`, `log_warning`, `log_critical`, and `log_fatal`. These functions take as their first argument the log domain and as their second argument the message as a string. As mentioned before, messages of level `DEBUG` are only printed if we specifically request them to do so. We enable these on a per-log-domain basis, [`set_surpress_debug!`](@ref), while we can choose to suppress message with log level `INFO` using [`set_surpress_info!`](@ref). For example, if our log domain is `foo`: ```julia # define custom domain const FOO_DOMAIN = "foo" # print `DEBUG` level message but nothing will happen because it is suppressed by default log_debug(FOO_DOMAIN, "Surpressed message") # enable `INFO` level messages set_surpress_debug!(FOO_DOMAIN, false) # message will be printed log_debug(FOO_DOMAIN, "No longer suppressed message") ``` Note that logging will only work once our `Application` instance is initialized. This is because the logging system needs a valid application ID, which is only registered once `Application` emits its `activate` signal. ### Logging to a File If the operating system is Linux, many log messages will be written to the default location, usually `/var/log`. On other operating systems, messages may not be stored at all. Regardless of OS, we can forward all logging, including that of Mousetrap itself, to a file using [`set_log_file!`](@ref), which takes the file path as a string. If the file already exists, it will be appended to (as opposed to being overwritten). If the file does not yet exist, it will be created. On successfully opening the file, `true` will be returned. We should react to the function's result, as not being able to log should be considered a fatal error. When stored to a file, logging messages will have a different format that may or may not list additional information when compared to logging to a console. The philosophy behind this is that it is better to log as much information as possible, and then use second-party software to filter it, as opposed to missing crucial information for the sake of brevity: ```cpp const LogDomain FOO_DOMAIN = "foo" if !set_log_file(FOO_DOMAIN, "example_log.txt") log_fatal(FOO_DOMAIN, "In set_log_file: Unable to create file at `example_log.txt`") end log_warning(FOO_DOMAIN, "Example Message") ``` Will add the following lines to a `example_log.txt` ``` [23-05-06 23:01:34,920]: In example.main: Example Message GLIB_DOMAIN foo MOUSETRAP_LEVEL WARNING PRIORITY 4 ``` Mousetraps logging system should be preferred over the native Julia one. Sending a message with Julias `@info`, will not be printed to the Mousetrap log file and will not be accessible by a Mousetrap application. --- ## File System Most GUI applications on desktops are centralized around modifying files. A text or image editor will often want to export files, while a video game will want to create or load a save file. Conversely, Mousetrap offers a robust, operating-system-agnostic way of interacting with the user's file system. There are two kinds of objects in a file system: **files**, which contain arbitrary data, and **directories**, which contain other files and/or other directories. We also call a directory a **folder**. Examples of files that are not folders include `.png`, `.txt`, `.jl` text files, shared libraries, binaries, or executable files. A **path** is a string, made up of folder names separated by `/`, (or `\` on windows, though this should be avoided). Examples include `/var/log`, `~/Desktop`, etc. A path starting at root (`/` on Unix, usually `C:/` on Windows) is called an **absolute path**, while any other path is called a **relative path**. An **URI** (universal resource identifier) is another way to express the location of the file. It follows a [strict scheme](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier), which is followed by most internet browsers, and should be preferred to regular paths for file transfers between different machines, or when referring to files on the internet. ### FileDescriptor When querying information about a file, we use [`FileDescriptor`](@ref), which represents information about a file or folder. This object is **non-mutating**, meaning it is incapable of changing anything about the actual file on the disk. In other words, **`FileDescriptor` is read-only**. We can create a file descriptor from a path like so: ```cpp readonly = FileDescriptor() create_from_path!(readonly, "/home/user/Desktop/example.txt"); ``` Where the argument to [`create_from_path!`](@ref) will be automatically detected as either a relative or absolute path. If it is not an absolute path, it will be prefixed with the application's runtime directory. For example, if we create a `FileDescriptor` from path `"assets/image.png"`, and our application is located in `/usr/bin/foo`, then the path will be treated as `/usr/bin/foo/assets/image.png`. `FileDescriptor` does not make sure the underlying file or folder exists or that it is a valid file. Creating a descriptor from an invalid path or a path that does not point to a file or folder works just fine, and we won't get a warning. To check whether a file descriptor points to a valid file or folder, we have to use [`exists`](@ref). To check whether a `FileDescriptor` points to a file (as opposed to a directory), we use [`is_file`](@ref) or [`is_folder`](@ref), respectively. If the file pointed to by `FileDescriptor` does not exist, both of these functions will return `false`. `FileDescriptor` allows us to query a variety of information about the file or folder, including, but not limited to: + [`get_path`](@ref) returns the location of the file as a path, eg. `~/Desktop/example.txt` + [`get_uri`](@ref) returns the location as an URI, eg. `file://~/Desktop/example.txt` + [`get_file_extension`](@ref) returns the file extension, eg. `txt` + [`is_executable`](@ref) checks whether the file is executable + [`get_content_type`](@ref) returns the [MIME type](https://en.wikipedia.org/wiki/Media_type), eg. `text/plain` For less common metadata information, we can use [`query_info`](@ref), which takes an **attribute identifier** as a string. A list of identifiers can be found [here](https://gitlab.gnome.org/GNOME/glib/-/blob/main/gio/gfileinfo.h#L46), though, depending on the type of file and operating system, not all of these attributes may have a corresponding value. If the file is a folder, we can use [`get_children`](@ref) to get all files and/or directories inside that folder. `get_children` takes a boolean as its other argument, which specifies whether it should list all children recursively. --- ## Manipulating the Disk `FileDescriptor` being non-mutating means we need a different part of Mousetrap in order to modify files on the users' disk. For file input/output, such as reading the contents of files, we should use the [Julia standard library](https://docs.julialang.org/en/v1/base/file/), which is well-suited for this task. For manipulating files as a whole, as opposed to their contents, Mousetrap offers multiple functions for common tasks: ### Creating Files [`create_file_at!`](@ref) creates a file at a given location. It takes a file descriptor as its only argument. If `should_replace` is set to `false` and the file already exists, no operation will be performed ```julia if create_file_at!(FileDescriptor("/absolute/path/to/file.txt"), replace = false) # use file end ``` [`create_directory_at!`](@ref) performs a similar action, except it creates a directory instead of a file. ### Deleting Files To permanently delete a file, we use [`delete_at!`](@ref), which takes a file descriptor as its argument. This immediately deletes the file, making it unable to be recovered. In some cases, this may be too risky, in which case should use [`move_to_trash!`](@ref) instead: ```julia to_delete = FileDescriptor("/path/to/delete/file.txt") if !move_to_trash(to_delete) log_warning(FOO_DOMAIN, "In example: Unable to delete file at `$(get_path(to_delete))`") end ``` ### Moving / Copying File To move a file from one location to another, we use [`move!`](@ref). If we want to copy a file or directory instead of moving it, we use [`Mousetrap.copy!`](@ref): ```julia from = FileDescriptor("/path/from/file.txt") to = FileDescriptor("/different_path/to/file.txt") if !move!(from, to) log_warning(FOO_DOMAIN, "In example: Unable to move file from `$(get_path(from))` to `$(get_path(to))`") end ``` ### Changing File Metadata !!! info (this feature is not yet implemented) ### Opening a File or URL Often, we will want to open an external file for the user, for example, showing the license of our app in a text editor or opening a donation page from a menu. Mousetrap offers three functions well-suited for this. [`open_file`](@ref) will open a file on disk, usually presenting a user with a number of applications that can open the file. For example, when opening a `.txt` file, the user will be presented with a list of text editors installed on their system. When they select one, that application will be started and open the file. Similarly, [`show_in_file_explorer`](@ref) will open the user's file explorer to the enclosing folder of the file. Lastly, [`open_url`](@ref) takes a URL as a string, opening the user's default internet browser to show that page. All of these functions are designed to work on all operating systems, making them a convenient way to perform what would be quite a complex task otherwise. --- ### Monitoring File Changes Often, when writing a GUI, we want the graphical interface to reflect the contents of a file on the disk. A good example would be a text editor. We can modify the file from inside our application, however, if the file is modified by a third entity, such as another application, a conflict may arise. In this case, we will usually want to update the state of our application such that it reflects the state of the file on disk. This should happen whenever the underlying file changes. This is made possible by [`FileMonitor`](@ref), which monitors a file or directory for changes. `FileMonitor` cannot be created directly, instead, we first create a `FileDescriptor`, then call [`create_monitor`](@ref), which returns the `FileMonitor` instance. `FileMonitor` works similarly to a signal emitter. To register a function that is called whenever the file changes, we use [`on_file_changed!`](@ref), which expects a function with the signature ```julia (::FileMonitor, event::FileMonitorEvent, self::FileDescriptor, other::FileDescriptor, [::Data_t]) -> Nothing ``` where + `event` is a [`FileMonitorEvent`](@ref), describing the type of action performed, see below + `self` is a descriptor pointing to the file or folder that is being monitored + `other` is a descriptor that may or may not point to the other relevant file, see below + `Data_t` is optional arbitrary data The following monitor events are supported: | `FileMonitorEvent` | Meaning | value of `self` | value of `other` | |----------------------------------------|------------------------------|--------------------------|--------------------| | `FILE_MONITOR_EVENT_CHANGED` | File's content was modified in any way | modified file | none | | `FILE_MONITOR_EVENT_DELETED` | File was deleted | monitored file or folder | deleted file | | `FILE_MONITOR_EVENT_CREATED` | File was created | monitored file or folder | newly created file | | `FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED` | File metadata changed | changed file | none | | `FILE_MONITOR_EVENT_RENAMED` | File's name changed | changed file | none | | `FILE_MONITOR_EVENT_MOVED_IN` | File was moved into self | monitored folder | moved file | | `FILE_MONITOR_EVENT_MOVED_OUT` | File was moved out of self | monitored folder | moved file | For example, if we want to trigger an action whenever `/path/to/file.txt` changes its content, we could do the following: ```julia to_watch = FileDescriptor("/path/to/file.txt") # equivalent to create_from_path! monitor = create_monitor(to_watch) function on_file_changed_callback(monitor::FileMonitor, event::FileMonitorEvent, self::FileDescriptor, other::FileDescriptor) if event == FILE_MONITOR_EVENT_CHANGED println("File at $(get_path(self)) changed.") end end on_file_changed!(on_file_changed_callback, monitor) ``` If we no longer want to monitor a file, we can call [`cancel!`](@ref), at which point the `FileMonitor` instance may be safely deallocated. --- ## File Chooser Dialog Opening a dialog to allow a user to select a file or folder is a task so common, that most operating systems provide a native widget just for this purpose. Mousetrap, conversely, also has an object tailor-made for this: [`FileChooser`](@ref) `FileChooser` is not a widget, and it cannot emit any signals. It is what's called a **dialog**, which is a graphical object that can only exist in its own window. Its constructor takes two arguments: a [`FileChooserAction`](@ref) and the resulting dialog window's title. `FileChooserAction` is an enum whose value determines which **mode** the `FileChooser` will perform in: | `FileChooserAction` value | Users may select... | |---------------------------|-----------------------------| | `FILE_CHOOSER_ACTION_OPEN_FILE` | exactly one file | | `FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES` | one or more files | | `FILE_CHOOSER_ACTION_SELECT_FOLDER` | zero or one folder | | `FILE_CHOOSER_ACTION_SELECT_MULTIPLE_FOLDERS` | zero or more folders | | `FILE_CHOOSER_ACTION_SAVE` | file name and location | Depending on which `FileChooserAction` we choose, `FileChooser` will automatically change its layout and behavior. After creating the object, we can show it to the user using `present!`: ```cpp file_chooser = FileChooser(FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES) present!(file_chooser) ``` ![](../assets/file_chooser.png) To react to the user making a selection or canceling the operation, we need to register a **callback** with the file chooser. [`on_accept!`](@ref) takes a function that is invoked when the user makes a file selection, for example by pressing the "Open" button. This function is required to have the signature ```julia (::FileChooser, files::Vector{FileDescriptor}, [::Data_t]) -> Nothing ``` Where `files` may contain zero or more file descriptors, depending on the `FileChooserAction` used when creating the dialog. The callback registered using [`on_cancel!`](@ref) is called when the user cancels or otherwise closes the dialog. This function requires a different signature: ```julia (::FileChooser, [::Data_t]) -> Nothing ``` Using these, we can trigger custom behavior if / when the user makes a selection: ```julia file_chooser = FileChooser(FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES) on_accept!(file_chooser) do self::FileChooser, files::Vector{FileDescriptor} println("User chose files at $files") end on_cancel!(file_chooser) do self::FileChooser println("User cancelled the dialog") end present!(file_chooser) ``` ### FileFilter Looking again at the previous screenshot in this section, we see that in the bottom right corner of the dialog, a drop-down with the currently selected item `(None)` is seen. This is the currently active **filter**, where `(None)` means no filter is active. By adding a filter, we make it so only files that pass that filter will be shown - and thus be selectable. This is useful when we want to limit file selection to only a certain type of file; for example, an image manipulation application would only allow loadable image files as the file type for an `Open...` dialog. We construct a [`FileFilter`](@ref) by first choosing a name. This string will be used as the title of the filter, which is shown in the `FileChooser`s drop-down: ```julia file_filter = FileFilter("Julia Files") ``` We now have to specify which files should pass the filter. `FileFilter` offers multiple functions for this: | `FileFilter` Method | Argument | Resulting Allowed Files | |-------------------------------------------------|----------------|-----------------------------------------------------------| | [`add_allowed_suffix!`](@ref) | `jl` | files ending in `.jl` | | [`add_allow_all_supported_image_formats!`](@ref) | (no argument) | file types that can be loaded by [`ImageDisplay`](@ref)s | | [`add_allowed_mime_type!`](@ref) | `text/plain` | files classified as plain text, for example `.txt` | | [`add_allowed_pattern!`](@ref) | `*.jl` | files whose name match the given regular expression | A table with the allowed image formats is available in [the chapter on images](06_image.md#supported-image-formats). After having set up our filter, we simply add it to the `FileChooser` instance using [`add_filter!`](@ref): ```julia filter = FileFilter("*.jl") add_allowed_suffix!(filter, "jl") add_filter!(file_chooser, filter) ``` ![](../assets/file_chooser_filter.png) By default, no `FileFilter`s will be registered, which means the `FileChooser` will display all possible file types. We can control which filter is active when the dialog opens using [`set_initial_filter!`](@ref). !!! info We can call `set_initial_filter!` with a filter that has not yet been added with `add_filter!`. This will make it so the initial filter is active when the dialog opens, but cannot be changed, as only filters added with `add_filter!` will be selectable from the dialogs dropdown. --- ## Alert Dialog A very common task for an application that manipulates files is to make sure the user knows they are overwriting a file, for example, when the user selects a location using a `FileChooser` whose action is `FILE_CHOOSER_ACTION_SAVE_FILE`. While we could construct a custom widget for this purpose, put that widget in a `Window`, and then present that window to the user, a task as common as this should be possible in only a few lines. For this purpose, Mousetrap offers [`AlertDialog`](@ref), which is a dialog that shows a message to the user, along with one or more buttons they can click. Each `AlertDialog` has a **message**, a **detailed description**, which we during `AlertDialogs`constructor: ```julia overwrite_file_warning_dialog = AlertDialog( "A file with this name already exists, continue?", # message "The original file will be overwritten, this cannot be undone." # detail description ) ``` With just this, the only way for the user to interact with the dialog is to press escape, which closes it. We will most likely want to add buttons, which we accomplish using [`add_button!`](@ref) ```julia add_button!(overwrite_file_warning_dialog, "Continue") add_button!(overwrite_file_warning_dialog, "Cancel") ``` While we could `present!` this dialog to the user now: ![](../assets/alert_dialog.png) We haven't yet connected any behavior to the user pressing a button. To do this, we use [`on_selection!`](@ref), which takes a callback with the following signature: ```julia (::AlertDialog, button_index::Integer, [::Data_t]) -> Nothing ``` Where `button_index` is the index of the button, from left to right (1-based), or `0` if the dialog was dismissed without pressing a button. Continuing our example of warning the user when they're about to overwrite a file, we would do the following: ```julia # create the dialog overwrite_file_warning_dialog = AlertDialog( "A file with this name already exists, continue?", # message "The original file will be overridden, this cannot be undone." # detailed description ) add_button!(overwrite_file_warning_dialog, "Continue") add_button!(overwrite_file_warning_dialog, "Cancel") # add a callback on_selection!(overwrite_file_warning_dialog) do self::AlertDialog, button_index::Integer if button_index == 1 # "Continue" pressed # write file elseif button_index == 2 # "Cancel" pressed # ... else # dialog closed without pressing a button # ... end end # show the dialog, because it is modal, this will pause all other windows present!(overwrite_file_warning_dialog) ``` Note that we do not need to close the dialog from within the `on_selection!` callback, it is closed automatically. On top of buttons, `AlertDialog` furthermore has a spot for a custom widget, which will be displayed underneath the detail message. We choose this widget with [`set_extra_widget!`](@ref), which gives us some additional flexibility when we want a dialog that has more than just text. With `AlertDialog`, we have a vastly simplified mechanism for showing short, message-style dialogs to the user. Each of these dialogs will be *modal* by default, meaning all other windows and actions will be paused until the dialog is dismissed. ## Popup Messages Mousetrap offers a less intrusive way of showing a message to a user, facilitated by [`PopupMessageOverlay`](@ref). This widget is a container that has a single child. It adds no graphical element to its child, instead, we can show a small popup message which will be shown above the chosen child: ![](../assets/popup_message.png) !!! details "How to generate this image" ```julia using Mousetrap main() do app::Application window = Window(app) child = Separator() message_overlay = PopupMessageOverlay() set_child!(message_overlay, child) show_message_action = Action("example.show_message", app) set_function!(show_message_action, message_overlay) do self::Action, message_overlay::PopupMessageOverlay message = PopupMessage("This is a message") set_button_label!(message, "OK") set_button_action!(message, self) show_message!(message_overlay, message) end button = Button() set_opacity!(button, 0) set_action!(button, show_message_action) push_front!(get_header_bar(window), button) set_child!(window, message_overlay) present!(window) end ``` To send a message, we first need to instance an object of type [`PopupMessage`](@ref), which, unlike `PopupMessageOverlay`, is **not** a widget, though it is a `SignalEmitter`. A popup message always has a title, which we supply to its constructor. It will furthermore always have a close button the user can use to hide the message. After instancing the `PopupMessage`, we present it to the user using [`show_message!`](@ref): ```julia message_overlay = PopupMessageOverlay() set_child!(message_overlay, widget) popup_message = PopupMessage("Message Text") show_message!(message_overlay, popup_message) ``` We can set the message to hide itself automatically by setting [`set_timeout!`](@ref) to anything other than `0`. Only one message can be shown at a time. We can make sure an important message is jumped to the front of the queue by setting its priority using [` set_is_high_priority!`](@ref). This is enough for a simple notification, but `PopupMessage` also supports interactivity beyond just the close button. A `PopupMessage` has one optional button. To show this button, we need to choose a title using [`set_button_label!`](@ref). To react to the user pressing this button, we can either assign it an action using [`set_button_action!`](@ref), or we can connect to `PopupMessages` signals, `button_clicked` and `dismissed`, which are emitted when the optional button and close button are pressed, respectively. Both signals expect a signal handler with signature `(::PopupMessage, [::Data_t]) -> Nothing`. For example, to trigger a function when the user clicks the "OK" button of our `PopupMessage`, we could do: ```julia popup_message = PopupMessage("Message Text") # connect to button press set_button_label!(popup_message, "OK") connect_signal_button_clicked!(popup_message) do self::PopupMessage println("OK clicked") end show_message!(message_overlay, popup_message) ``` ## Popup Messages Mousetrap offers a less intrusive way of showing a message to a user, facilitated by [`PopupMessageOverlay`](@ref). This widget is a container that has a single child. It adds no graphical element to its child, instead, we can present a small popup message to the user, which will be shown above the chosen child: ![](../assets/popup_message.png) !!! details "How to generate this image" ```julia using Mousetrap main() do app::Application window = Window(app) child = Separator() message_overlay = PopupMessageOverlay() set_child!(message_overlay, child) show_message_action = Action("example.show_message", app) set_function!(show_message_action, message_overlay) do self::Action, message_overlay::PopupMessageOverlay message = PopupMessage("This is a message") set_button_label!(message, "OK") set_button_action!(message, self) show_message!(message_overlay, message) end button = Button() set_opacity!(button, 0) set_action!(button, show_message_action) push_front!(get_header_bar(window), button) set_child!(window, message_overlay) present!(window) end ``` To send a message, we first need to instance an object of type [`PopupMessage`](@ref), which, unlike `PopupMessageOverlay`, is **not** a widget, though it is a `SignalEmitter`. A popup message always has a title, which is supplied to its constructor. It will furthermore always have a close button the user can use to hide the message. After instancing the `PopupMessage`, we push it to the overlay using [`show_message!`](@ref): ```julia message_overlay = PopupMessageOverlay() set_child!(message_overlay, widget) popup_message = PopupMessage("Message Text") show_message!(message_overlay, popup_message) ``` We can set the message to hide itself automatically by setting [`set_timeout!`](@ref) to anything other than `0`. Only one message can be shown at a time. We can make sure an important message is put to the front of the queue by setting marking it as high priority using [`set_is_high_priority!`](@ref). This is enough for a simple notification, but `PopupMessage` also supports interactivity beyond just the close button. A `PopupMessage` can have one optional button. To show this button, we need to choose the button's label using [`set_button_label!`](@ref). To react to the user pressing this button, we can either assign it an action using [`set_button_action!`](@ref), or we can connect to one of `PopupMessages` signals, `button_clicked` and `dismissed`, which are emitted when the optional button and close button are pressed, respectively. Both signals expect a signal handler with signature `(::PopupMessage, [::Data_t]) -> Nothing`. For example, to trigger a function when the user clicks the "OK" button of a `PopupMessage`, we could do: ```julia popup_message = PopupMessage("Message Text") # connect to button press set_button_label!(popup_message, "OK") connect_signal_button_clicked!(popup_message) do self::PopupMessage println("OK clicked") end show_message!(message_overlay, popup_message) ``` `PopupMessage` should be used for relatively insignificant messages that do not require immediate user action. Otherwise, we should `AlertDialog`, which is modal by default and will only go away once the user chooses to dismiss the dialog. --- ## GLib Keyfiles For many objects like images, Mousetrap [offers ways to store them on the disk](@ref save_to_file). For custom objects, such as the state of our application, we have no such option. While it may sometimes be necessary, for most purposes we do not need to create a custom file type, instead, we can use the [**GLib KeyFile**](https://docs.gtk.org/glib/struct.KeyFile.html), whose syntax is heavily inspired by Windows `.ini` settings files. Keyfiles are human-readable and easy to edit, which makes them better suited for certain purposes when compared to [json](https://docs.fileformat.com/web/json/) or [xml](https://docs.fileformat.com/web/xml/) files. Thanks to [`Mousetrap.KeyFile`](@ref), loading, saving, and modifying key files is easy and convenient. ### GKib Keyfile Syntax In a keyfile, every line is one of four types: + **Empty**, it has no characters or only control characters and spaces + **Comment**, it begins with `#` + **Group**, has the form `[group_name]`, where `group_name` is any name not containing a space + **Key**, has the form `key=value`, where `key` is any name and `value` is of a format discussed below For example, the following is a valid key file: ```txt # keybindings [image_view.key_bindings] # store current file save_file=s # miscellanous config [image_view.window] # default window size width=400 height=300 # default background color default_color_rgba=0.1;0.7;0.2;1 ``` Key-value pairs belong to the group that was last opened. Groups cannot be nested, they always have a depth of 1 and every key-value pair has to be inside exactly one group. ### Accessing Values If the above key file is stored at `assets/example_key_file.txt`, we can access the values of the above-named keys like so: ```julia # load file file = KeyFile() load_from_file!(file, "assets/example_key_file.txt") # retrieve value as String save_file_keybinding = get_value(file, "image_view.key_binding", "save_file", String) # retrieve values as int width = get_value(file, "image_view.window", "width", Int32) height = get_value(file, "image_view.window", "height", Int32) # retrieve value as RGBA default_color = get_value(file, "image_view.window", "default_color_rgba", RGBA) ``` We see that the general syntax to access a `KeyFile` value is ``` get_value(, , , ) ``` where `type` is one of the following: | Type | Example Value | Format | |---------------------------|----------------------------------------------|-----------------------------------| | Bool | `true` | `true` | | Vector{Bool} | `[true, false, true]` | `true;false;true` | | Cint (Int32) | `32` | `32` | | Vector{Cint} | `[12, 34, 56]` | `12;34;56` | | Csize_t (UInt64) | `984` | `948` | | Vector{Csize_t} | `[124, 123, 192]` | `124;123;192` | | Cfloat (Float32) | `3.14` | `3.14` | | Vector{Cfloat} | `[1.2, 3.4, 5.6]` | `1.2;3.4;5.6` | | Cdouble (Float64) | `3.14569` | `3.14569` | | Vector{Cdouble} | `[1.123, 0.151, 3.121]` | `1.123;0.151;3.121` | | String | `"foo"` | `foo` | | Vector{String} | `["foo", "lib", "bar"]` | `foo;lib;bar` | | RGBA | `RGBA(0.1, 0.9, 0, 1)` | `0.1;0.9;0.0;1.0` | | Image | `RGBA(1, 0, 1, 1), RGBA(0.5, 0.5, 0.7, 0.0)` | `1.0;0.0;1.0;1.0;0.5;0.5;0.7;0.0` | We can also interact with comments from Julia. To access the comment above a group ID or key-value pair, we use [`get_comment_above`](@ref) which takes either just a group name for comments above groups or a group- and key-name for comments above key-value pairs. ### Storing Values We use [`set_value!`](@ref) to modify the value of a `KeyFile` entry. Thanks to method dispatch, we do not have to specify the type of value in Julia for `set_value!`. ```julia set_value!(file, "image_view.window", "default_color_rgba", RGBA(1, 0, 1, 1)); ``` To modify comments, we use [`set_comment_above!`](@ref), which, just like before, takes only a group ID to modify the comment above a group declaration, or both a group ID and key to modify the comment above a key-value pair. When writing to an instance of `KeyFile`, only the file in memory is modified, **not the file on the disk**. To update the actual stored file, we need to call [`save_to_file`](@ref). --- ## Icons We've seen before how to load and display an image using `Image`. One of the most common applications for this is to use the resulting picture as the label of a `Button`. In common language, this picture is often called an **icon**. In modern desktop applications, we may have dozens of these images used as visual labels for widgets. While it is possible to simply store all these images as `.png`s and load them manually with `Image`, this is hardly very scalable. Furthermore, this method does not allow us to modify all pictures at the same time, which may be necessary to, for example, increase icon size to aid visually impaired users. A better way to handle images in a context like this is provided by [`Icon`](@ref). `Icon` is similar to an image file, though it usually does not contain pixel data. Instead, it points to a file on disk. `Icon`s can be loaded from `.png` files, but also allows `.svg` (vector graphics), and `.ico` (web browser icons). ### Creating and Viewing Icons The simplest way to create an `Icon` is to load it as if it were an `Image`. If we have a vector graphics file at `assets/save_icon.svg`, we would load it like this: ```cpp icon = Icon() create_from_file!(icon, "assets/save_icon.svg", 48); ``` Where `48` means the icon will be initialized with a resolution of 48x48 pixels. `Icon`s can only be square. If we want to display this icon using a widget, we can use `ImageDisplay`s [`create_from_icon!`](@ref). This is similar to images, however, because `Icon` will usually not be raster-based, the `ImageDisplay` will scale much more smoothly to any resolution as no interpolation has to take place. Other than with `ImageDisplay`, we have other ways to use icons in graphical widgets: We can directly make the icon a child of a `Button` or `ToggleButton` using [`set_icon!`](@ref). Icons can furthermore be inserted to the left or right of an entry using [`set_primary_icon!`](@ref), and [`set_secondary_icon!`](@ref), and they can be used in menus, which we will learn about in the next chapter. ## Icon Themes !!! compat This section is not yet complete, see the documentation for [`IconTheme`](@ref), and the [Freedesktop Icon Theme Specification](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html) instead. ================================================ FILE: docs/src/01_manual/08_menus.md ================================================ # Chapter 8: Menus In this chapter, we will learn: + How to create complex, nested menus + How to display menus using `PopoverMenu` and `MenuBar` + Best-practice style guides for menus --- In the [chapter on actions](./03_actions.md) we learned that we can trigger an action using [`Button`](@ref), by assigning an action to it using [`set_action!`](@ref). This works if we want to have a GUI element that has one or maybe a few actions. In practice, an application can have hundreds of different actions. Asking users to trigger these using an equal number of buttons would be unsustainable. For situations like these, we should instead turn to **menus**. ## MenuModel Architecture In Mousetrap, menus follow the [model-view architectural pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). To create a menu, we need the menu model, which holds information about how the menu is structured, along with a view, which takes the models and transforms them into an interactable widget users can manipulate. Changing the model changes the view. !!! details "Running Snippets from this Section" To follow along with code snippets from this section, we can use the following `main.jl` file: ```julia using Mousetrap main() do app::Application window = Window(app) # create dummy action action = Action("dummy.action", app) do x println("triggered.") end # model that we will be modifying in the snippet root = MenuModel() # snippet goes here # display menu add_submenu!(root, "Title", model) view = PopoverButton(PopoverMenu(root)) set_child!(window, view) present!(window) end ``` ## Menu Items [`MenuModel`](@ref), the model component of Mousetrap menus, is a list of **menu items** in a specific order. If item `A` is added before item `B` at runtime, then `A` will appear above item `B`. There are multiple different types of menu items, all with their own purpose. The function we choose to add an item to the model determines the item's type. There are four types of menu items, which we will go over in this section. ### Item Type #1: Action The first and most simple item type is called "action". Added via [`add_action!`](@ref), which takes both a label and an [`Action`](@ref) instance, this item is a simple button with text. If the user clicks the button, the action is executed. Similar to `Button`, if the action is disabled (via [`set_enabled!`](@ref)) or does not yet have a callback registered, the menu item will appear "greyed out" and cannot be activated. ```cpp add_action!(model, "Action Item #1", action) add_action!(model, "Action item #2", action) add_action!(model, "Action item #3", action) ``` ![](../assets/menu_model_actions.png) ### Item Type #2: Widgets Secondly, we have perhaps the most powerful type of item: A custom widget. We add an item of this type using [`add_widget!`](@ref), which only takes a single argument, the widget itself. This widget can be arbitrarily complex, though it is usually not advisable to insert an entire `ColumnView` into a tiny menu. Good-practice examples include `Button`, `Entry`, `CheckButton`, `ToggleButton`, `Switch`, and `SpinButton`, all of which are intractable. We do not supply an `Action` instance with this item, if we want the user interacting with the menu item to trigger behavior, we will have to connect that behavior to the signals of the widget we inserted into the menu, or any of its event controllers. ```cpp add_widget!(model, hbox(Label("Choose Value: "), SpinButton(0, 1, 0.01))) add_widget!(model, Scale(0, 1, 0.01, ORIENTATION_HORIZONTAL)) add_widget!(model, hbox(Label("Enter Text: "), Entry())) ``` ![](../assets/menu_model_widgets.png) Widgets are the most flexible type of menu item. They should be used with caution, and only if absolutely necessary. It is often better to create an action that opens a separate [`Window`](@ref) that contains the widget, as opposed to directly adding the widget to the menu. ### Item Type #3: Submenu `MenuModel` can be **nested**, which means we can insert a `MenuModel` into another `MenuModel`. This is similar to `ListView` or file-system tree: a folder can contain regular files (menu items), or it can contain other folders (menu models), which in turn can also contain another file or folder, etc. `MenuModel` can similarly be infinitely nested, though it is usually not recommended to go deeper than two or three levels. We call a `MenuModel` that is nested within another model a **submenu**. It will show up as a button with a label, along with an indicator that it is a submenu, usually a `>` shape. Clicking this item will reveal the submenu. To add this type of submenu item, we use [`add_submenu!`](@ref), which takes a title and another menu model: ```julia submenu_01 = MenuModel() add_widget!(submenu_01, submenu_content()) add_submenu!(model, "Submenu #1", submenu_01) submenu_02 = MenuModel() add_widget!(submenu_02, submenu_content()) add_submenu!(model, "Submenu #2", submenu_02) ``` ![](../assets/menu_model_submenu_outer.png) Clicking on of these items will reveal the submenu content: ![](../assets/menu_model_submenu_inner.png) Where `submenu_content()` returns a simple place-holder widget, in reality, this will be many other menu items or other submenus. ### Item Type #4: Icons A prettier analog to the "action"-type menu item, an "icon"-type item. Added via [`add_icon!`](@ref), it takes an [`Icon`](@ref), along with an action. The entire menu item will be just the icon, it may not have any text. If the icon is clicked, the action is executed. ```julia add_icon!(model, Icon(#=...=#), action) add_icon!(model, Icon(#=...=#), action) add_icon!(model, Icon(#=...=#), action) add_icon!(model, Icon(#=...=#), action) ``` ![](../assets/menu_model_icons.png) Where we used the default Gnome icons for weather indicators as placeholders. When creating a menu item with an action, we have to decide whether to use a simple text label or an icon. We may not have both. ### Item Type #5: Sections Lastly, we have **sections**. Sections are like submenus, in that they are a menu inside another menu. The difference is in the way this inner menu is displayed. When adding a submenu with `add_submenu!`, a single new item will appear in the outer menu. When clicked, the menu "slides" to reveal the submenu. With sections, the inner menu is instead inserted directly into the outer menu; both are shown at the same time. We add a "section"-type item using [`add_section!`](@ref), which takes another menu model and a title, which will be used as a header for the section: ```julia section = MenuModel() add_action!(section, "Section Item #01", #= action =#) add_action!(section, "Section Item #02", #= action =#) add_action!(section, "Section Item #03", #= action =#) add_section!(model, "Section Label", section) ``` ![](../assets/menu_model_section.png) We see that the section label, `"Section Label"` in this case, is displayed above all its items, which are inserted into the outer menu. In this way, sections can be helpful to group multiple menu items, which makes a menu easier to parse without adding another nested level via a submenu. #### Section Formats [`add_section!`](@ref) takes one additional, optional argument, which is a [`SectionFormat`](@ref). This enum has a number of values that govern how the section is displayed: + `SECTION_FORMAT_CIRCULAR_BUTTONS` displays all its elements in circular buttons + `SECTION_FORMAT_HORIZONTAL_BUTTONS`: display its elements as a row of rectangular buttons + `SECTION_FORMAT_INLINE_BUTTONS`: display all buttons right of the section heading The following shows these section formats: ```julia # generate a menu model with the 4 weather icons, then add as section with given format function add_icon_section(title::String, format::SectionFormat) section = MenuModel() add_icon!(section, Icon(#=...=#), action) add_icon!(section, Icon(#=...=#), action) add_icon!(section, Icon(#=...=#), action) add_icon!(section, Icon(#=...=#), action) add_section!(icon_model, title, section, format) end add_icon_section("Normal", SECTION_FORMAT_NORMAL) add_icon_section("Horizontal Buttons", SECTION_FORMAT_HORIZONTAL_BUTTONS) add_icon_section("Inline Buttons: ", SECTION_FORMAT_INLINE_BUTTONS) add_icon_section("Circular Buttons", SECTION_FORMAT_CIRCULAR_BUTTONS) ``` ![](../assets/menu_model_section_formats.png) Using `SectionFormat` and mixing several menu item types, we can construct arbitrarily complex menus. We should realize that the highest priority when constructing menu items is the **user experience**. Presenting users with a giant, deeply nested mess of buttons may be very functional, but it may not be very usable to anyone but the developers themself. --- ## Displaying Menus Now that we have learned to construct the menu **model**, we should turn our attention to the **view**, a widget displaying a `MenuModel`. ### PopoverMenu [`PopoverMenu`](@ref) is a sibling of [`Popover`](@ref). It consists of a small dialog that is only displayed when we ask it to. While `Popover` displays an arbitrary widget, `PopoverMenu` displays a menu model. For ease of use, it's easiest to connect the `PopoverMenu` to a [`PopoverButton`](@ref), just like we did with `Popover`: ```julia model = MenuModel() # fill `model` here popover_menu = PopoverMenu(model) popover_button = PopoverButton(popover_menu) # add the button to the window so we can click it set_child!(window, popover_button) ``` The `PopoverMenu`-`PopoverButton` combo should be reserved for **context menus**, which are menus that act on some local part of the application. For a menu that affects the entire application and should be accessible at all times, we should use the next menu model view instead. ### MenuBar Familiar to any user of a modern desktop GUI, [`MenuBar`](@ref) is a widget that is usually displayed at the top of the main application window: ![](../assets/menu_bar.png) We see that `MenuBar` is a horizontal list of items. When the user clicks on one of the items, a nested menu opens. Just like before, menus can be nested a theoretically infinite number of times. Unlike `PopoverMenu`, `MenuBar` requires its `MenuModel` to have a certain structure: **all top-level items have to be submenus**. What does this mean? Let's work through the menu shown in the image above. We created it using the following snippet: ```julia root = MenuModel() file_submenu = MenuModel() add_action!(file_submenu, "Open", #= action =#) file_recent_submenu = MenuModel() add_action!(file_recent_submenu, "Project 01", #= action =#) add_action!(file_recent_submenu, "Project 02", #= action =#) add_action!(file_recent_submenu, "Other...", #= action =#) add_submenu!(file_submenu, "Recent...", file_recent_submenu) add_action!(file_submenu, "Save", #= action =#) add_action!(file_submenu, "Save As...", #= action =#) add_action!(file_submenu, "Exit", #= action =#) help_submenu = MenuModel() add_submenu!(root, "File", file_submenu) add_submenu!(root, "Help", help_submenu) menubar = MenuBar(model) ``` Where in a real application, each item will have a different action. This code can be quite hard to read. To make the nesting easier to understand, we'll write it out as if it were a file system folder structure: ``` model \ File \ Open Recent... \ Project 01 Project 02 Other... Save Save As Exit Help \ (...) ``` Where any line suffixed with a `\` is a submenu. We see that we have four models in total. The top-most menu model is called `root`, it is what `MenuBar` will be initialized with. Next, we have the model called `file_submenu`, which has the title `File`. It contains five items, titled `Open`, `Recent...`, `Save`, `Save As`, and `Exit`. `Recent...` is a submenu-type item, created from a `MenuModel` called `file_recent_submenu` in the above code. This model, in turn, has three items. On the same level as `File`, we have a second menu `Help`. The **top-level** menu is `root`. It is used as the argument for the constructor of `MenuBar`. We see that all direct children of `root` (`File` and `Help`) **are themselves submenus** (they were added using `add_submenu!`). No direct child of `root` is an "action"-, "widget"-, "icon"- or "section"-type item. This is what is required for `MenuBar`. All top-level items have to be submenus. !!! Warning Due to a bug in the backend, as of `v0.3.0`, a menu model used for a `MenuBar` **may not have a "widget"-type item in a submenu of a submenu**. This means we *can* add a widget to any submenu of `root`, but we may not add a widget to any submenu that is nested any deeper than a direct child of `root`. This bug does not affect `PopoverMenu`, for whom we can put a widget at any depth. `PopoverMenu` has no requirement as to the structure of its menu model, while `MenuBar` requires that all top-level items are submenus and that no submenu of a submenu may have a "widget"-type item. --- ## Style End Note Menus are extremely powerful and complex to construct. With practice and good software / UI design, we can create deep, complex menus that are still easy to understand and use. We, as developers, should make this our priority. Some additional notes: ### Ellipses Some may be curious as to why some menu items have `...` added at the end of their labels, while others do not. This is not a universal standard, but it is common for `...` to indicate that clicking this item will open another window, submenu, or dialog. If clicking an item simply triggers an action (such as `Save` or `Exit`), `...` is omitted. If the item opens a window, widget, or submenu, `...` is appended to the menu label, as is the case with `Recent...` above. ### Maximum Menu Depth Regarding menu depth, the best practice is to never go deeper than three levels. The above example with `File > Recent... > Project 01` shows a good situation in which a 3-level-deep menu may be justified. Going any deeper is rarely a good course of action. Adding a section should always be considered *before* deciding to add a submenu. ### Section Grouping Lastly, some schools of UI design believe that **every menu item should be inside a section**. For example, if we were to follow this philosophy for our above `MenuBar` example, we would redesign it like this: ![](../assets/menu_bar_with_sections.png) This adds considerable complexity to the code (adding 4 models, one for each section, making our total 8). In return, items are grouped logically, and each item gets a "heading", which helps make long menus easier to understand. For this small example, this is most likely unnecessary, but it will be more attractive for a menubar with dozens of items. In the end, each UI designer should decide for themselves which technique to use. What all should agree on, however, is that ease of use for the end-user is the most important thing. It should trump ease of development in every case. If something is harder for us developers but makes it easier for our users, we should go through the effort of doing it. ================================================ FILE: docs/src/01_manual/09_native_rendering.md ================================================ # Chapter 9: Native Rendering In this chapter we will learn: + How to use `RenderArea` + How to draw any shape + How to efficiently show an image as a texture + How to change the currently used blend mode + How to apply a 3D transform to a shape GPU-side + How to compile a GLSL Shader and set its uniforms !!! compat In `v0.2.0` and earlier, features from this chapter were not available on MacOS. Since `v0.3.0`, 100% of Mousetrap is portable, meaning MacOS is now fully supported. --- !!! details "Native Rendering on Linux Wayland" Linux users using the Wayland windowing system may encounter the following error message when starting Mousetrap: ``` In gdk_window_create_gl_context: Failed to create EGL display ``` This means we would be unable to use the `RenderArea` widget, which this chapter centers around. To address this, we need to [locate](https://linuxize.com/post/locate-command-in-linux/) the directory `egl_vendor.d`, which has to be non-empty and contain a JSON file ``` locate egl_vendor.d ``` ``` /etc/glvnd/egl_vendor.d /usr/share/glvnd/egl_vendor.d /usr/share/glvnd/egl_vendor.d/50_mesa.json ``` Here we choose `/usr/share/glvnd/egl_vendor.d` instead of `/etc/glvnd/egl_vendor.d`, because only the former contains a JSON file, `50_mesa.json` in this case. Armed with this path, we then execute, in a terminal, **before** Julia is started: ``` export __EGL_VENDOR_LIBRARY_DIRS=/usr/share/glvnd/egl_vendor.d ``` After which any OpenGL-related features from this chapter will become available. To make this change permanent, we can paste the above line into the `~/.bashrc` text file, which will be loaded automatically anytime a terminal starts. !!! details "Manually Disabling the OpenGL Component" We can disable all features from this chapter by setting the environment variable `MOUSETRAP_DISABLE_OPENGL_COMPONENT` to `TRUE`. This may be necessary for machines that do not have an OpenGL 3.3-compatible graphics card driver. See [here](https://github.com/Clemapfel/Mousetrap.jl/issues/25#issuecomment-1731349366) for more information. --- In the [chapter on widgets](04_widgets.md), we learned that we can create new widgets by combining already predefined widgets as a *compound widget*. We can create a new widget that has a `Scale`, but we cannot render our own scale with, for example, a square knob. In this chapter, this will change. By using the native rendering facilities Mousetrap provides, we are free to create any shape we want, assembling new widgets pixel-by-pixel, line-by-line, then adding interactivity using the [event controller system](./05_event_handling.md). ## RenderArea The central widget of this chapter is [`RenderArea`](@ref), which is a canvas used to display native graphics. At first, it may seem very simple: ```julia render_area = RenderAre() ``` This will render as a transparent area, because `RenderArea` has no graphic properties of its own. Instead, we need to create separate **shapes**, then **bind them for rendering**, after which `RenderArea` will display the shapes for us. Still, `RenderArea` will follow its properties just like any other widget, for example, it will have an allocated size that follows size-hinting and the expansion property. ## Shapes In general, shapes are defined by a number of **vertices**. A vertex has a position in 2D space, a color, and a texture coordinate. In this chapter, we will learn what each of these properties does and how they coalesce to form a shape. ### Vertex Coordinate System A shape's vertices define where inside the `RenderArea` it will be drawn. The coordinate system used by shapes is different from the one we use for widgets. OpenGL, on which the native rendering component of Mousetrap is based, uses the **right-hand coordinate system**, which is familiar from traditional math: ![](https://learnopengl.com/img/getting-started/coordinate_systems_right_handed.png) (source: [learnopengl.com](https://learnopengl.com/img/getting-started)) We assume the z-coordinate for any vertex is set to 0, reducing the coordinate system to a 2D plane. We will refer to this coordinate system as **GL coordinates**, while the widget coordinate system is used for `ClickEventController` and the like as **widget space coordinates**. To further illustrate the difference between GL and widget space coordinates, consider this table, where `w` is the widgets' width, `h` is the widgets' height, in pixels: | Conceptual Position | GL Coordinates | Widget Space Coordinates | |---------------------|---------------|-------------------------| | top left | `(-1, +1)` | `(0, 0)` | | top | `( 0, +1)` | `(w / 2, 0)`| | top right | `(+1, +1)` | `(w, 0)`| | left | `(-1, 0)` | `(0, y / 2)`| | center | `( 0, 0)` | `(w / 2, y / 2)`| | right | `(+1, 0)` | `(w, y / 2)`| | bottom left | `(-1, -1)` | `(0, y)`| | bottom | `( 0, -1)` | `(w / 2, y)`| | bottom right | `(+1, -1)` | `(w, y)`| We see that the OpenGL coordinate system is **normalized**, meaning the values of each coordinate are inside `[-1, 1]`, while the widget-space coordinate system is **absolute**, meaning the values of each coordinate take the allocated size of the widget into account, being inside `[0, w]` and `[0, h]` for the x- and y-coordinates, respectively, in pixels. The gl coordinate system is anchored at the center of the render area's allocated area, while widget space is anchored at the top left. At any point, we can convert between the coordinate systems using [`from_gl_coordinates`](@ref) and [`to_gl_coordinates`](@ref), which take the `RenderArea` as their first argument. These functions convert gl-to-widget-space and widget-space-to-gl coordinates, respectively. Of course, the widget space coordinates depend on the current size of the `RenderArea`. When it is resized, the old coordinates may be out of date, which is why using the *normalized* GL system is preferred in an application where the canvas can change size frequently. ### Rendering Shapes We'll now create our first shape, which is a point. A point is always exactly one pixel in size. ```julia shape = Shape() as_point!(shape, Vector2f(0, 0)) ``` Where we use `Vector2f(0, 0)` as the point's position, meaning it will appear at the origin of the `RenderArea`, its center. The above is directly equivalent to the following: ```julia shape = Point(Vector2f(0, 0)) ``` We see that ```julia typeof(shape) ``` ``` Mousetrap.Shape ``` The variable `shape` is still of type `Shape`. [`Point`](@ref) is simply a convenience function for initializing a shape, then calling [`as_point!`](@ref) on that instance. For this shape to show up on screen, we need to **bind it for rendering**. To do this, we create a [`RenderTask`](@ref) which wraps the shape, then use [`add_render_task!`](@ref) to add it to the scheduled tasks of our `RenderArea`. From this point onwards, anytime the `RenderArea` goes through a render cycle, it will also draw all its tasks, including our point: ```julia shape = Point(Vector2f(0, 0)) add_render_task!(render_area, RenderTask(shape)) ``` ![](../assets/shape_hello_world.png) !!! details "How to generate this Image" ```julia using Mousetrap main() do app::Application set_current_theme!(app, THEME_DEFAULT_DARK) window = Window(app) set_title!(window, "Mousetrap.jl") render_area = RenderArea() shape = Point(Vector2f(0, 0)) add_render_task!(render_area, RenderTask(shape)) frame = AspectFrame(1.0, render_area) set_size_request!(frame, Vector2f(150, 150)) set_margin!(frame, 10) set_child!(window, frame) present!(window) end ``` If we want to remove a task from our render area, we need to call [`clear_render_tasks!`](@ref), then add all other render tasks again. ### Shape Types Mousetrap offers a wide variety of pre-defined shape types. Thanks to this, we don't have to manually adjust each vertex position. #### Point As we've seen, [`Point`](@ref) is always exactly one pixel in size. Its constructor takes a single `Vector2f`: ```julia point = Point(Vector2f(0, 0)) ``` ![](../assets/shape_point.png) #### Points [`Points`](@ref) is a number of points. Instead of taking a single `Vector2f`, its constructor takes `Vector{Vector2f}`: ```julia points = Points([ Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5), Vector2f(0.0, -0.5) ]) ``` ![](../assets/shape_points.png) Rendering several points using `Points` is much more performant, a four-vertex `Points` is much faster than rendering four `Point` with one vertex each. #### Line A [`Line`](@ref) is defined by two points, between which a 1-pixel thick line will be drawn: ```julia line = Line( Vector2f(-0.5, +0.5), Vector2f(+0.5, -0.5) ) ``` ![](../assets/shape_line.png) #### Lines [`Lines`](@ref) will draw unconnected lines. It takes a vector of point pairs. For each of these, a 1-pixel thick line will be drawn between them. ```julia lines = Lines([ Vector2f(-0.5, 0.5) => Vector2f(0.5, -0.5), Vector2f(-0.5, -0.5) => Vector2f(0.5, 0.5) ]) ``` ![](../assets/shape_lines.png) #### LineStrip [`LineStrip`](@ref), not to be confused with `Lines`, is a *connected* series of lines. Thus, it takes a vector of points, as opposed to a vector of point pairs. A line will be drawn between each successive pair of coordinates, meaning the last vertex of the previous line will be used as the first vertex of the current line. If the supplied vector of points is `{a1, a2, a3, ..., a(n)}` then `LineStrip` will render as a series of lines with coordinate pairs `{a1, a2}, {a2, a3}, ..., {a(n-1), a(n)}` ```julia line_strip = LineStrip([ Vector2f(-0.5, +0.5), Vector2f(+0.5, +0.5), Vector2f(+0.5, -0.5), Vector2f(-0.5, -0.5) ]) ``` ![](../assets/shape_line_strip.png) #### Wireframe [`Wireframe`](@ref) is similar to a `LineStrip`, except that it also connects the last and first vertex. For a supplied vector of points `{a1, a2, a3, ..., an}`, the series of lines will be `{a1, a2}, {a2, a3}, ..., {a(n-1), a(n)}, {a(n), a1}`, the last vertex-coordinate pair is what distinguishes it from a `LineStrip`. As such, `Wireframe` is sometimes also called a **line loop**. ```julia wireframe = Wireframe([ Vector2f(-0.5, +0.5), Vector2f(+0.5, +0.5), Vector2f(+0.5, -0.5), Vector2f(-0.5, -0.5) ]) ``` ![](../assets/shape_wireframe.png) Note how this shape takes the same coordinates as `LineStrip`, but draws one more line, connecting the last to the first vertex. #### Triangle A [`Triangle`](@ref) is constructed as one would expect, using three `Vector2f`, one for each of its vertices: ```julia triangle = Triangle( Vector2f(-0.5, 0.5), Vector2f(+0.5, 0.5), Vector2f(0.0, -0.5) ) ``` ![](../assets/shape_triangle.png) #### Rectangle A [`Rectangle`](@ref) has four vertices. It is defined by its top-left point and its width and height. As such, it is always axis-aligned. ```julia rectangle = Rectangle( Vector2f(-0.5, 0.5), # top left Vector2f(1, 1) # width, height ) ``` ![](../assets/shape_rectangle.png) #### Circle A [`Circle`](@ref) is constructed from a center point and radius. We also need to specify the number of outer vertices used for the circle. This number will determine how "smooth" the outline is. For example, a circle with 3 outer vertices is an equilateral triangle; a circle with 4 outer vertices is a square; a circle with 5 is a pentagon, etc. As the number of outer vertices increases, the shape approaches a mathematical circle, but will also require more processing power. ```julia circle = Circle( Vector2f(0, 0), # center 0.5, # radius 32 # n outer vertices ) ``` ![](../assets/shape_circle.png) #### Ellipse An [`Ellipse`](@ref) is a more generalized form of a `Circle`. It has two radii, the x- and y-radius: ```julia ellipse = Ellipse( Vector2f(0, 0), # center 0.6, # x-radius 0.4, # y-radius 32 # n outer vertices ) ``` ![](../assets/shape_ellipse.png) #### Polygon The most general form of convex shapes, [`Polygon`](@ref) is constructed using a vector of vertices, which will be sorted clockwise, then their [outer hull](https://en.wikipedia.org/wiki/Convex_hull) will be calculated, which results in the final convex polygon: ```julia polygon = Polygon([ Vector2f(0.0, 0.75), Vector2f(0.75, 0.25), Vector2f(0.5, -0.75), Vector2f(-0.5, -0.5), Vector2f(-0.75, 0.0) ]) ``` ![](../assets/shape_polygon.png) We note that a 4-vertex polygon is a rectangle. Therefore, if we want to render a non-axis-aligned rectangle, we should instead use `Polygon` with four vertices. #### Rectangular Frame A [`RectangularFrame`](@ref) takes a top-left vertex, a width, a height, and the x- and y-width, the latter of which is the thickness of the frame along the x- and y-axes: ```julia rectangular_frame = RectangularFrame( Vector2f(-0.5, 0.5), # top-left Vector2f(1, 1), # width, height 0.15, # x-thickness 0.15, # y-thickness ) ``` ![](../assets/shape_rectangular_frame.png) Note how the top left and size govern the position and size of the outer perimeter of the rectangular frame. #### Circular Ring For the round equivalent of a rectangular frame, we have [`CircularRing`](@ref), which takes a center, the radius of the outer perimeter, as well as the ring's thickness. Like `Circle` and `Ellipse`, we have to specify the number of outer vertices, which decides the smoothness of the ring: ```julia circular_ring = CircularRing( Vector2f(0, 0), # center 0.5, # radius of outer circle 0.15, # thickness 32 # n outer vertices ) ``` ![](../assets/shape_circular_ring.png) As before, the center and radius determine the position and size of the outer perimeter. #### Elliptical Ring A generalization of `CircularRing`, [`EllipticalRing`](@ref) has an ellipse as its outer shape. Its thickness along the horizontal and vertical dimensions are supplied separately, making it more flexible than `CircularRing`. ```julia elliptcal_ring = EllipticalRing( Vector2f(0, 0), # center 0.6, # x-radius 0.4, # y-radius 0.15, # x-thickness 0.15, # y-thickness 32 # n outer vertices ) ``` ![](../assets/shape_elliptical_ring.png) #### Outline Lastly, we have a special shape. [`Outline`](@ref) does not take any vertex positions for its constructor. Instead, we construct an `Outline` shape from **another shape**. It will then generate a wireframe for the outer perimeters of the original shape. As the name suggests, this is useful for generating outlines of another shape. By rendering the `Outline` on top of the original shape, we can make it so it better stands out from the background. ```julia outline = Outline(triangle) ``` ![](../assets/shape_triangle_outline.png) ```julia outline = Outline(rectangle) ``` ![](../assets/shape_rectangle_outline.png) ```julia outline = Outline(circle) ``` ![](../assets/shape_circle_outline.png) ```julia outline = Outline(ellipse) ``` ![](../assets/shape_ellipse_outline.png) ```julia outline = Outline(polygon) ``` ![](../assets/shape_polygon_outline.png) ```julia outline = Outline(rectangular_frame) ``` ![](../assets/shape_rectangular_frame_outline.png) ```julia outline = Outline(circular_ring) ``` ![](../assets/shape_circular_ring_outline.png) ```julia outline = Outline(elliptical_ring) ``` ![](../assets/shape_elliptical_ring_outline.png) While possible, it doesn't make much sense to create an `Outline` from a shape that does not have a volume, such as `Point` or `Wireframe`. Rendering white outlines on top of a white shape would make little sense, of course. To achieve the desired effect, we need to make the outline another color, which brings us to the additional properties of `Shape`. ### Shape Properties #### Vertex Properties Shapes are made up of vertices, whose properties we can manually edit. To set the property of a single vertex of a shape, we use [`set_vertex_color!`](@ref), [`set_vertex_position!`](@ref), and [`set_vertex_texture_coordinate!`](@ref). Each of these takes an index, which is the index of the vertex in clockwise order, 1-based. To know how many vertices a shape actually has, we use [`get_n_vertices`](@ref). We will rarely need to modify individual vertices, as working on the `Shape` as a whole is much more convenient. ##### Centroid The **centroid** of a shape is the intuitive "center of mass". In mathematical terms, it is the component-wise mean of all vertex coordinates. In practice, for many symmetrical shapes such as rectangles, triangles, circles, and ellipses, the centroid will be the "center" of the shape, as it is defined in common language. We can access the centroid using [`get_centroid`](@ref). To move a shape a certain distance, we move its centroid by that distance by calling [`set_centroid!`](@ref), which will automatically move all other vertices of the shape such that its new centroid is as specified. #### Rotation We can rotate all of a `Shape`'s vertices around a point in GL coordinates by calling [`rotate!`](@ref), which takes an `Angle` as its first argument: ```julia # rotate shape around its center rotate!(shape, degrees(90), get_centroid(shape)) ``` #### Color To change the color of a shape as a whole, we use [`set_color!`](@ref). This simply calls `set_vertex_color!` on all of a shape's vertices. By default, a shape's color will be `RBGA(1, 1, 1, 1)`, white. #### Visibility We can prevent a shape from being rendered by setting [`set_is_visible!`](@ref) to `false`. This is different from making all vertices of a shape have an opacity of `0`. `is_visible` directly hooks into the shape's render function and **prevents it from being called**, as opposed to it completing rendering and not being visible on screen. This means making a shape invisible completely removes any performance penalty that would have been incurred during the render step, which is also called [culling](https://en.wikipedia.org/wiki/Hidden-surface_determination). #### Bounding Box We can access the [axis-aligned bounding box](https://en.wikipedia.org/wiki/Bounding_volume) of a shape with [`get_bounding_box`](@ref), which returns an [`AxisAlignedRectangle`](@ref). This is the smallest axis-aligned rectangle that still contains all of a shape's vertices. Using this, we can query the top-left coordinate and size of the bounding box. --- Lastly, each shape has an optional **texture**, which is what the texture coordinate properties of each vertex are used for. If a shape does not have a texture, it will be rendered as a solid color. If it does, the color of each pixel in the texture will be multiplied by the shape's color. ## Textures In the chapter on widgets, we learned that we can use the `ImageDisplay` widget to display static images. This works, but has a number of disadvantages: + Image data is costly to update + Downloading image data is impossible + Scaling the image will always trigger linear interpolation + The image is always shown in full, as a rectangle If we need the additional flexibility, we should instead use a `Shape` along with a [`Texture`](@ref), which represents an image living on the graphics card. We create a texture from an `Image` like so: ```julia image = Image() load_from_file!(image, "path/to/image.png") texture = Texture() create_from_image!(texture, image) ``` Once `create_from_image!` is called, the image data is uploaded to the graphics cards' RAM, so we can safely discard the `Image` instance. To display the texture on screen, we need to bind it to a shape, and then render that shape: ```julia texture_shape::Shape = Rectangle(Vector2f(-1, 1), Vector2f(2, 2)) set_texture!(texture_shape, texture) add_render_task!(render_area, RenderTask(texture_shape)) ``` How and where the texture is displayed depends on the shape's vertices **texture coordinate**. These coordinates are in `([0, 1], [0, 1])`, meaning the x- and y- component are in `[0, 1]` each. We will call this coordinate system **texture space**. | Conceptual Position | Texture Space Coordinates | |---------------------|---------------| | top left | `(0, 0)` | | top | `(0.5, 0)` | | top right | `(1, 0)` | | left | `(0, 0.5)` | | center | `(0.5, 0.5)` | | right | `(1, 0.5)` | | bottom left | `(0, 1)` | | bottom | `(0.5, 1)` | | bottom right | `(1, 1)` | We see that, due to the normalized nature of this coordinate system, a texture coordinate is unable to reference a specific pixel. Instead, we use a floating-point coordinate, for which the graphics card will return an **interpolated color**. This is the color any specific pixel on the monitor should assume when the shape is displayed. #### Scale Mode Similar to `Image`s [`as_scaled`](@ref), we have options as to how we want the texture to behave when scaled to a size other than its native resolution. Mousetrap offers the following texture scale modes, which are represented by the enum [`TextureScaleMode`](@ref): | `TextureScaleMode` | Meaning | Equivalent `InterpolationType` | |--------------------|---------|-----| | `TEXTURE_SCALE_MODE_NEAREST` | Nearest Neighbor Scaling | `INTERPOLATION_TYPE_NEAREST` | | `TEXTURE_SCALE_MODE_LINEAR` | Linear Interpolation | `INTERPOLATION_TYPE_BILINEAR` | While the resulting image behaves similarly to how `InterpolationType` will result in the final image, operating on a texture is much, much more performant. Rescaling a texture is essentially free when done by the graphics card, which is in stark contrast to the capabilities of a CPU, as would be needed for `Image`s `as_scaled`. #### Wrap Mode Wrap mode governs how the texture behaves when a vertice's texture coordinate components are outside `[0, 1]`. Mousetrap offers the following wrap modes, which are all part of the enum [`TextureWrapMode`](@ref): | `TextureWrapMode` | Pixel will be filled with | |-------------------|--------| | `TEXTURE_WRAP_MODE_ZERO` | `RGBA(0, 0, 0, 0)` | | `TEXTURE_WRAP_MODE_ONE`| `RGBA(1, 1, 1, 1)` | | `TEXTURE_WRAP_MODE_STRETCH` | Nearest outer Edge | | `TEXTURE_WRAP_MODE_REPEAT` | Equivalent pixel in `([0, 1], [0, 1]) ` | | `TEXTURE_WRAP_MODE_MIRROR` | Equivalent pixel in `(1 - [0, 1], 1 - [0, 1])`| ![](../assets/texture_wrap_modes.png) !!! details "How to generate this Image" ```julia using Mousetrap # compound widget that displays a texture with a label struct TexturePage <: Widget center_box::CenterBox label::Label render_area::RenderArea texture::Texture shape::Shape function TexturePage(label::String, image::Image, wrap_mode::TextureWrapMode) out = new( CenterBox(ORIENTATION_VERTICAL), Label("" * label * ""), RenderArea(), Texture(), Rectangle(Vector2f(-1, 1), Vector2f(2, 2)) ) set_expand!(out.render_area, true) set_size_request!(out.render_area, Vector2f(150, 150)) set_start_child!(out.center_box, AspectFrame(1.0, Frame(out.render_area))) set_end_child!(out.center_box, out.label) set_margin!(out.label, 10) create_from_image!(out.texture, image) set_wrap_mode!(out.texture, wrap_mode) set_texture!(out.shape, out.texture) # zoom out texture coordinates by 1 unit set_vertex_texture_coordinate!(out.shape, 1, Vector2f(-1, -1)) set_vertex_texture_coordinate!(out.shape, 2, Vector2f(2, -1)) set_vertex_texture_coordinate!(out.shape, 3, Vector2f(2, 2)) set_vertex_texture_coordinate!(out.shape, 4, Vector2f(-1, 2)) add_render_task!(out.render_area, RenderTask(out.shape)) return out end end Mousetrap.get_top_level_widget(x::TexturePage) = x.center_box main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") render_area = RenderArea() image = Image() create_from_file!(image, "docs/src/assets/logo.png") # this assumes the script is run in `Mousetrap.jl` root # replace RGBA(0, 0, 0, 0) pixels with rainbow color size = get_size(image) hue_step = 1 / size.x for i in 1:size.y for j in 1:size.x if get_pixel(image, i, j).a == 0 set_pixel!(image, i, j, HSVA(j * hue_step, 1, 1, 1)) end end end box = Box(ORIENTATION_HORIZONTAL) set_spacing!(box, 10) set_margin!(box, 10) push_back!(box, TexturePage("ZERO", image, TEXTURE_WRAP_MODE_ZERO)) push_back!(box, TexturePage("ONE", image, TEXTURE_WRAP_MODE_ONE)) push_back!(box, TexturePage("STRETCH", image, TEXTURE_WRAP_MODE_STRETCH)) push_back!(box, TexturePage("REPEAT", image, TEXTURE_WRAP_MODE_REPEAT)) push_back!(box, TexturePage("MIRROR", image, TEXTURE_WRAP_MODE_MIRROR)) set_child!(window, box) present!(window) end ``` Where the default wrap mode is `TEXTURE_WRAP_MODE_REPEAT`. By being able to modify the vertex coordinates for any of a shape's vertices, we have much more control over how image data is displayed on screen. Only the part of the texture that conceptually overlaps a shape will be displayed, which is governed by that shape's texture coordinates. --- ## RenderArea Size Because shapes do not take into account the size and aspect ratio of their `RenderArea`, we, as developers, should take care that shapes are displayed correctly when this size changes. Consider the following example: ```julia render_area = RenderArea() shape = Ellipse(Vector2f(0, 0), 0.5, 0.5, 32) add_render_task!(render_area, RenderTask(shape)) set_child!(window, render_area) ``` ![](../assets/render_area_stretched.png) Where an ellipse with identical x- and y-radii is a circle. Despite defining the shape as a circle, on screen, it appears stretched. This is because the shape's vertices use the GL coordinate system, which is **normalized**. Thus, how long the x- and y-radii of a circle are depends on the width and height of the `RenderArea` canvas. By widening the window, the render area expands, increasing its width and thus stretching our circle. We have two ways to correct this. The easiest of which is putting the render area inside an `AspectFrame`, which forces it to always maintain the correct aspect ratio, square in this case: ```julia render_area = RenderArea() shape = Circle(Vector2f(0, 0), 0.5, 32) add_render_task!(render_area, RenderTask(shape)) set_child!(window, AspectFrame(1.0, render_area)) # force 1:1 aspect ratio ``` While this corrects our circle, the entire `RenderArea` is now restrained in size, making this solution unviable for applications where we need a `RenderArea` to fill its entire area regardless of its aspect ratio. The other way to correct the issue is to modify our circle when `RenderArea` changes shape. This is made possible by the `resize` signal of `RenderArea`, which is emitted whenever its allocated area changes: `resize` requires a signal handler with the following signature: ```julia (::RenderArea, width::Integer, height::Integer, [::Data_t]) -> void ``` Where `width` and `height` are the new sizes of the `RenderArea` widget, in pixels. Using this information and some simple geometry, we can change the x- and y-radius dynamically whenever the `RenderArea` changes aspect ratio: ```julia # define resize callback function on_resize(::RenderArea, width::Integer, height::Integer, shape::Shape) # calculate y-to-x-ratio new_ratio = height / width # resize the shape by adjusting x-radius as_ellipse!(shape, Vector2f(0, 0), # old center 0.5 * new_ratio, # new x-radius 0.5, # old y-radius 32 # n vertices ) end main() do app::Application window = Window(app) render_area = RenderArea() shape = Ellipse(Vector2f(0, 0), 0.5, 0.5, 32) add_render_task!(render_area, RenderTask(shape)) # connect callback, providing our shape as `Data_t` argument connect_signal_resize!(on_resize, render_area, shape) set_child!(window, render_area) present!(window) end ``` ![](../assets/render_area_destretched.png) Here, the `RenderArea` has a non-square aspect ratio, yet the shape is still displayed as a proper circle. Using signal `resize` like this, we can protect ourselves against side effects from the normalized nature of GL coordinates. --- ## Anti Aliasing When shapes are drawn to the screen, they are *rasterized*, which is when the graphics card takes the mathematical shape in memory and transforms it such that it can be displayed using a limited number of pixels. This process is imperfect, as no number of pixels will be able to draw a perfect curve. One artifact that can appear during this process is **aliasing**, which, in non-technical terms, is when lines appear "jagged": ![](https://learnopengl.com/img/advanced/anti_aliasing_zoomed.png) (Source: [learnopengl.com](https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing)) To address the unsightly nature of this issue, several remedies are available, the most appropriate of which is called [multi-sampled anti-aliasing (MSAA)](https://www.khronos.org/opengl/wiki/Multisampling). Users of Mousetrap are not required to understand the algorithm behind it, only that it causes jagged edges to appear smoother. To enable MSAA, we provide an enum value of type [`AntiAliasingQuality`](@ref) to `RenderArea`s constructor: ```julia msaa_on = RenderArea(ANTI_ALIASING_QUALITY_BETTER) msaa_off = RenderArea(ANTI_ALIASING_QUALITY_OFF) ``` | `AntiAliasingQuality` Value | # MSAA Samples | |-----------------------------|------------| | `ANTI_ALIASING_QUALITY_OFF` | 0 (no MSAA) | | `ANTI_ALIASING_QUALITY_MINIMAL` | 2 | | `ANTI_ALIASING_QUALITY_GOOD` | 4 | | `ANTI_ALIASING_QUALITY_BETTER` | 8 | | `ANTI_ALIASING_QUALITY_BEST` | 16 | Where `ANTI_ALIASING_QUALITY_OFF` will be used when calling the `RenderArea` constructor with no arguments, as we have so far. The higher the number of samples, the better the smoothing will be. MSAA comes at a cost: any quality other than `OFF` will induce the `RenderArea` to take about twice as much space in the graphic card's memory. Furthermore, the higher the number of samples, the more time each render step will take. It's difficult to convey the result of MSAA using just pictures on a web page due to compression. Instead, readers are encouraged to run the following `main.jl`, which will show off the anti-aliasing in high resolution on the screen: ![](../assets/msaa_comparison.png) !!! details "How to generate this Image" ```julia main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") # create render areas with different MSAA modes left_area = RenderArea(ANTI_ALIASING_QUALITY_OFF) right_area = RenderArea(ANTI_ALIASING_QUALITY_BEST) # paned that will hold both areas paned = Paned(ORIENTATION_HORIZONTAL) # create singular shape, which will be shared between areas shape = Rectangle(Vector2f(-0.5, 0.5), Vector2f(1, 1)) add_render_task!(left_area, RenderTask(shape)) add_render_task!(right_area, RenderTask(shape)) # rotate shape 1° per frame set_tick_callback!(paned) do clock::FrameClock # rotate shape rotate!(shape, degrees(1), get_centroid(shape)) # force redraw for both areas queue_render(left_area) queue_render(right_area) # continue callback indefinitely return TICK_CALLBACK_RESULT_CONTINUE end # setup window layout for viewing for area in [left_area, right_area] set_size_request!(area, Vector2f(150, 150)) end # caption labels left_label = Label("OFF") right_label = Label("BEST") for label in [left_label, right_label] set_margin!(label, 10) end # format paned set_start_child_shrinkable!(paned, false) set_end_child_shrinkable!(paned, false) set_start_child!(paned, vbox(AspectFrame(1.0, left_area), left_label)) set_end_child!(paned, vbox(AspectFrame(1.0, right_area), right_label)) # present set_child!(window, paned) present!(window) end ``` --- ## Render Task !!! tip The rest of this chapter will assume that readers are familiar with the basics of OpenGL, how to write GLSL shaders, what a shader uniform is, how blending works, and how a linear transform allows us to move a point in 3D space. With what we have learned so far in this chapter, we are already well-equipped to be able to accomplish most tasks that require the native rendering component, such as displaying static images or rendering shapes. **Any information after this point should be considered optional to learn**. So far, we have registered render tasks using `add_render_task!(render_area, RenderTask(shape))`. Sometimes, we will have to deal with [`RenderTask`](@ref) on its own. Its constructor actually has the following signature: ```julia RenderTask(::Shape ; [shader::Shader, transform::GLTransform, blend_mode::BlendMode]) ``` Where any name after the `;` are [keyword arguments](https://docs.julialang.org/en/v1/devdocs/functions/#Keyword-arguments), which are optional. We see that a `RenderTask` actually bundles the following objects: + a `Shape`, which is the shape being rendered + a `Shader`, which is a shader program containing a vertex- and fragment-shader + a `GLTransform`, which is a spatial transform that will be applied to the shape using the vertex shader + a `BlendMode`, which governs which type of blending will take place during the [blit step](https://en.wikipedia.org/wiki/Bit_blit) Using these four components, `RenderTask` gathers all objects necessary to render a shape to the screen. All components except for the `Shape` are *optional*. If not specified, a default value will be used instead. This is what allows less experienced users to fully ignore shaders, transforms, and blend modes, simply calling `RenderTask(shape)` will take care of everything for us. --- ## Transforms [`GLTransform`](@ref) is an object representing a spatial transform. It is called **GL**Transform, because it **uses the GL coordinate system**. Applying a `GLTransform` to a vector in widget- or texture-space will produce incorrect results. They should only be applied to the position attribute of a `Shape`'s vertices. Internally, a `GLTransform` is a 4x4 matrix of 32-bit floats. It is of size 4x4 because it's intended to be applied to OpenGL positions, which are vectors in 3D space. In Mousetrap, the last coordinate of a spatial position is assumed to be `0`, but it is still part of each vector's data. At any time, we can directly access the underlying matrix of a `GLtransform` using `getindex` or `setindex!`: ```julia transform = GLTransform() for i in 1:4 for j in 1:4 print(transform[i, j], " ") end print("\n") end ``` ``` 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ``` We see that after construction, `GLTransform` is initialized as the identity transform. No matter the current state of the transform, we can reset it back to this identity matrix by calling [`reset!`](@ref). `GLTransform` has many common spatial transforms already available as convenient functions, which means we rarely have to modify its values manually. It provides the following transformations, which behave identically to those familiar from linear algebra: + [`translate!`](@ref), translates in 3D space + [`scale!`](@ref), scales along the x- and y-axis + [`rotate!`](@ref), rotates around a point in 3D space We can combine two transforms using [`combine_with`](@ref). If we wish to apply the transform CPU-side to a `Vector2f` or `Vector3f`, we can use [`apply_to`](@ref). While we could apply the transform to each vertex of a `Shape` manually, then render the shape, it is much more performant to do this kind of math GPU-side. By registering the transform with a `RenderTask`, the transform will be forwarded to the vertex shaders, which, for the default vertex shader, is then applied to the shape's vertices automatically: ```julia shape = Shape() transform = Transform() translate!(transform, Vector2f(-0.5, 0.1)) rotate!(transform, degrees(180)) task = RenderTask(shape; transform = transform) ``` Where we used the `transform` keyword argument to specify the transform while leaving the other render task component unspecified. This means the transform is applied automatically during rendering, allowing us to take advantage of the increased performance gained from the GPU architecture. --- ## Blend Mode As the third component of a render task, we have the **blend mode**. This governs how two colors behave when rendered on top of each other. Let the color currently in the frame buffer be `destination`, while the newly added color will be `origin`. Each [`BlendMode`](@ref), then, behaves as follows: | `BlendMode` | Resulting Color | |--------------------|-------------------------------------| | `BLEND_MODE_NONE` | `origin.rgb + 0 * destination.rgb` | | `BLEND_MODE_NORMAL` | [traditional alpha-blending](https://en.wikipedia.org/wiki/Alpha_compositing) | | `BLEND_MODE_ADD` | `origin.rgba + destination.rgba` | | `BLEND_MODE_SUBTRACT` | `origin.rgba - destination.rgba` | | `BLEND_MODE_REVERSE_SUBTRACT` | `destination.rgba - origin.rgba` | | `BLEND_MODE_MULTIPLY` | `origin.rgba * destination.rgba` | | `BLEND_MODE_MIN` | `min(origin.rgba, destination.rgba)` | | `BLEND_MODE_MAX` | `max(origin.rgba, destination.rgba)` | Where `+`, `*`, and `-` are component-wise operations. Users may be familiar with the names of the blend modes from traditional image editors such as GIMP or Photoshop. If left unspecified, `RenderTask` will use `BLEND_MODE_NORMAL`. --- ## Shaders As the last component of a `RenderTask`, we have [`Shader`](@ref), which represents an OpenGL shader program that contains already compiled **fragment** and **vertex** shaders. These shaders are written in [GLSL](https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL), which will not be taught in this manual. ### Compiling Shaders To create a shader, we first instantiate a [`Shader`](@ref), then use [`create_from_file!`](@ref) or [`create_from_string!`](@ref) to compile the shader from GLSL code. After which the component is automatically bound it to the shader program, overriding whatever shader component was there before. We use `SHADER_TYPE_FRAGMENT` or `SHADER_TYPE_VERTEX` to specify which of the two shader types we are targeting. ```julia shader = Shader() # compile fragment shader create_from_string!(shader, SHADER_TYPE_FRAGMENT, """ #version 330 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; void main() { vec2 pos = _vertex_position.xy; _fragment_color = vec4(pos.y, dot(pos.x, pos.y), pos.x, 1); } """) # bind with render task, which will automatically apply it to each fragment of `shape` task = RenderTask(shape; shader = shader) add_render_task!(render_area, task) ``` ![](../assets/shader_hello_world.png) !!! details "How to generate this Image" ```julia using Mousetrap main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") render_area = RenderArea() shape = Rectangle(Vector2f(-1, 1), Vector2f(2, 2)) shader = Shader() create_from_string!(shader, SHADER_TYPE_FRAGMENT, """ #version 330 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; void main() { vec2 pos = _vertex_position.xy; _fragment_color = vec4(pos.y, dot(pos.x, pos.y), pos.x, 1); } """) task = RenderTask(shape; shader = shader) add_render_task!(render_area, task) frame = AspectFrame(1.0, Frame(render_area)) set_size_request!(frame, Vector2f(150, 150)) set_margin!(frame, 10) set_child!(window, frame) present!(window) end ``` If we do not initialize the vertex- or fragment shader, the **default shader component will be used**. It may be instructive to see how the default shaders are defined, as any user-defined shader should build upon them. ### Default Vertex Shader This is the default vertex shader, used whenever we do not supply a custom vertex shader for a `Shader` instance: ```glsl #version 330 layout (location = 0) in vec3 _vertex_position_in; layout (location = 1) in vec4 _vertex_color_in; layout (location = 2) in vec2 _vertex_texture_coordinates_in; uniform mat4 _transform; out vec4 _vertex_color; out vec3 _vertex_position; out vec2 _texture_coordinates; void main() { gl_Position = _transform * vec4(_vertex_position_in, 1.0); _vertex_color = _vertex_color_in; _vertex_position = _vertex_position_in; _texture_coordinates = _vertex_texture_coordinates_in; } ``` Where any variable name prefixed with `_` signals that it was defined outside of `main`. We see that it requires OpenGL 3.3 due to the `location` syntax. In terms of behavior, this shader simply forwards the interpolated vertex attributes to the fragment shader. The current vertices' position is supplied via `_vertex_position_in`, the vertices' texture coordinates as `_vertex_color_in`, and the vertices' texture coordinates are `_vertex_texture_coordinates`. These values will contain the data from the Julia-side `Shape`. We should take care that the `location` attribute exactly matches this order, `0` for vertex position, `1` for vertex color, `2` for texture coordinate. The output variables of the vertex shader are `_vertex_color`, `_texture_coordinates`, and `_vertex_position`, which need to be assigned with results gained from within the vertex shader. The shader has furthermore access to the uniform `_transform`, which holds the `GLTransform` the current `RenderTask` associates with the current `Shape`. ### Default Fragment Shader ```glsl #version 330 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; uniform int _texture_set; uniform sampler2D _texture; void main() { if (_texture_set != 1) _fragment_color = _vertex_color; else _fragment_color = texture2D(_texture, _texture_coordinates) * _vertex_color; } ``` The fragment shader is handed `_vertex_color`, `_texture_coordinate`, and `_vertex_position`, which we recognize as the output of the vertex shader. The output of the fragment shader is `_fragment_color`. The default fragment shader respects two uniforms, `_texture`, which is the texture of the shape we are currently rendering, and `_texture_set`, which is `1` if we have called `set_texture!` on the current `Shape` instance, `0` otherwise. Users aiming to experiment with shaders should take care to not modify the name or `location` of any of the `in` or `out` variables of either shader. These names, along with the required shader version, should not be altered. ### Binding Uniforms Both the vertex and fragment shaders make use of **uniforms**. We've seen that `_transform`, `_texture`, and `_texture_set` are assigned automatically. Mousetrap users can furthermore freely add new uniforms, conveniently assigning them using `RenderTask`. To bind a uniform, we first need a CPU-side value that should be uploaded to the graphics card. Let's say we want to use a certain color in our fragment shader, replacing the shape's fragment color with that color. We would write the fragment shader as follows: ```glsl #version 330 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; uniform vec4 _color_rgba; // new uniform void main() { _fragment_color = _color_rgba; // set fragment color to uniform } ``` To set the value of `_color_rgba`, we use [`set_uniform_rgba!`](@ref), which is called on the **render task**, not the shader itself. This will make the render task store the CPU-side value as long as it is needed, automatically forwarding it to the shader during rendering. `set_uniform_rgba!` is one of many `set_uniform_*!` functions that allow us to bind Julia-side values to OpenGL shader uniforms: The following types can be assigned this way: | Julia Type | `RenderTask` function | GLSL Uniform Type | |---------------|-------------------------|-------------------| | `Cfloat` | `set_uniform_float` | `float` | | `Cint` | `set_uniform_int` | `int` | | `Cuint` | `set_uniform_uint` | `uint` | | `Vector2f` | `set_uniform_vec2` | `vec2` | | `Vector3f` | `set_uniform_vec3` | `vec3` | | `Vector4f` | `set_uniform_vec4` | `vec4` | | `GLTransform` | `set_uniform_transform` | `mat4x4` | | `RGBA` | `set_uniform_rgba` | `vec4` | | `HSVA` | `set_uniform_hsva` | `vec4` | We would therefore set the `_color_rgba` uniform value like so: ```julia # create shader shader = Shader() create_from_string!(shader, SHADER_TYPE_FRAGMENT, """ #version 330 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; uniform vec4 _color_rgba; // uniform we want to assign void main() { _fragment_color = _color_rgba; } """) # create shape and task shape = Shape() task = RenderTask(shape; shader = shader) # shader bound to `shader` keyword argument # set uniform set_uniform_rgba!(task, "_color_rgba", RGBA(1, 0, 1, 1)) ``` ![](../assets/shader_rgba_uniform.png) !!! details "How to generate this Image" ```julia using Mousetrap main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") render_area = RenderArea() shape = Rectangle(Vector2f(-1, 1), Vector2f(2, 2)) shader = Shader() create_from_string!(shader, SHADER_TYPE_FRAGMENT, """ #version 330 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; uniform vec4 _color_rgba; void main() { _fragment_color = _color_rgba; } """) task = RenderTask(shape; shader = shader) set_uniform_rgba!(task, "_color_rgba", RGBA(1, 0, 1, 1)) add_render_task!(render_area, task) frame = AspectFrame(1.0, Frame(render_area)) set_size_request!(frame, Vector2f(150, 150)) set_margin!(frame, 10) set_child!(window, frame) present!(window) end ``` Where the name used in `set_uniform_*!` has to exactly match the variable name in GLSL. With this, we have a convenient way to specify shader uniforms without having to manually update the shader each time it is bound for rendering. `RenderTask` does this for us. --- ## Rendering to a Texture !!! compat This feature is not yet implemented, this section is incomplete. ================================================ FILE: docs/src/01_manual/10_theme_customization.md ================================================ # Chapter 10: Theme & Widget Customization In this chapter, we will learn: + How to swap between light- and dark- mode + How to create custom animations + How to apply a spatial transform to any widget + How to change the look of a widget using CSS + How to change the default colors used for the global theme + How to animate widgets using CSS !!! compat These features are only available in Mousetrap v0.2.0 or newer --- As our app grows and becomes closer to what we envisioned for our project, we want to not only customize the layout and functionality of widgets but also how each widget looks. This can range from small changes such as changing something that is blue by default to green, or large sweeping changes that affect the entire application, such as moving to a light- or dark-, low- or high-contrast theme, or even using a completely custom theme. Mousetrap allows for all of these options. Using its very powerful theme customization component, we can customize our app to a point where most will not be able to tell it was ever Mousetrap- / GTK4-based at all. ## Switching between Dark- and Light Mode The most common task that almost any app will want to offer is for the user to be able to swap between light and dark mode. This is a ubiquitous feature of modern UI, as such, Mousetrap offers a very simple way of changing the global theme. Mousetrap supports four default application-wide themes, which are values of enum [`Theme`](@ref): + `THEME_DEFAULT_LIGHT` + `THEME_DEFAULT_DARK` + `THEME_HIGH_CONTRAST_LIGHT` + `THEME_HIGH_CONTRAST_DARK` At any point after the back-end has been initialized, we can swap the global theme using [`set_current_theme!`](@ref). This will immediately change the look of all widgets and windows, allowing apps to change the entire GUI with just one function call at runtime. For example, to create a window that has a button to switch between light and dark themes in its header bar, we could do the following: ```julia main() do app::Application window = Window(app) # add theme swap button to windows header bar header_bar = get_header_bar(window) swap_button = Button() set_tooltip_text!(swap_button, "Click to Swap Themes") connect_signal_clicked!(swap_button, app) do self::Button, app::Application # get currently used theme current = get_current_theme(app) # swap light with dark, preservng whether the theme is high contrast if current == THEME_DEFAULT_DARK next = THEME_DEFAULT_LIGHT elseif current == THEME_DEFAULT_LIGHT next = THEME_DEFAULT_DARK elseif current == THEME_HIGH_CONTRAST_DARK next = THEME_HIGH_CONTRAST_LIGHT elseif current == THEME_HIGH_CONTRAST_LIGHT next = THEME_HIGH_CONTRAST_DARK end # set new theme set_current_theme!(app, next) end push_front!(header_bar, swap_button) present!(window) end ``` --- ## Animation We've seen in the chapter on widgets that certain kind of widgets animate their children. For example, when switching between two pages of a [`Stack`](@ref), or when a [`Revealer`](@ref) reveals its child, an animation is played depending on which enum value was chosen using [`set_transition_type!`](@ref). Mousetrap offers a convenient mechanism for implementing animations like this from scratch, which this section will demonstrate. If we want to animate a widget "fading out" over 1 second, that is, its opacity changes from 1 to 0 over that period of time, we should decrease the opacity by a specified amount each frame. Tying the amount to the frame rate of our window is ill-advised, many things can influence the frame rate and fluctuations would cause fluctuations in the speed of the fade-out. To address this, Mousetrap offers [`Animation`](@ref), which acts as a *stable clock*, an object that outputs a value over a specified amount of time in a way that is independent of the frame rate. Continuing with our fade-out example, we first need to instance the widget we want to fade out, a `Button`. We then create an instance of `Animation`, which takes for its constructor the widget we want to animate, along with the target duration of the animation: ```julia to_animate = Button(Label("Fade Out")) animation = Animation(to_animate, seconds(1)) ``` By tying the `Animation` to the widget we will target, Mousetrap will automatically preserve the animation while that widget is visible, as well as tie the animation clock to the render cycle of that specific widget, meaning the `Animation` will not play if the widget is not visible. To start the animation, we call [`play!`](@ref). Of course, we have not yet implemented the behavior of the widgets' opacity decreasing. To do this, we register a callback using `Animation`'s [`on_tick!`](@ref), which requires a function with the signature: ```julia (::Animation, value::Float64) -> Nothing ``` Where `value` is the animations output value. By default, this will be in `[0, 1]`, though we can freely choose the upper and lower bound using [`set_lower!`](@ref) and [`set_upper!`](@ref). Once the animation is finished, the callback registered using [`on_done!`](@ref) is invoked. Since a widget's opacity is already in `[0, 1]`, we can use the animations value directly: ```julia to_animate = Button(Label("Fade Out")) animation = Animation(to_animate, seconds(1)) on_tick!(animation, button) do self::Animation, value::Float64, target::Button set_opacity!(target, 1 - value) end ``` Where we used `1 - value` to invert the range, such that the widget starts fully opaque and decreases in opacity. We can then start the animation using `play!`, for example, by clicking the button: ![](../assets/animation_fade_out.webm) !!! details "How to generate this Image" ```julia using Mousetrap main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") button = Button(Label("Fade Out")) aspect_frame = AspectFrame(1.0, button) set_margin!(aspect_frame, 10) animation = Animation(button, seconds(1)) on_tick!(animation, button) do self::Animation, value::Float64, target::Button set_opacity!(target, 1 - value) end on_done!(animation, button) do self::Animation, target::Button set_is_visible!(target, false) end connect_signal_clicked!(button, animation) do self::Button, animation::Animation play!(animation) end set_child!(window, aspect_frame) present!(window); end ``` For cyclical animations, we can use [`set_repeat_count!`](@ref) to specify the number of times the animation should loop, or `0` to loop infinitely. We can easily reverse an animation by setting [`set_is_reversed!`](@ref) to `true`. Attentive readers may remember that pre-made animations for `Stack` and `Revealer` also include widgets spinning or moving around the screen. So far, we have no good way of implementing motion like this. This is about to change. ## TransformBin In the chapter on rendering, we learned that we can apply a `GLTransform` to a `Shape`, a non-widget, in order to move that shape **without** changing its vertex data. [`TransformBin`](@ref) offers similar functionality to this, except it only applies to widgets. `TransformBin` is a widget that does not add any visual elements to its singular child. It furthermore always assumes the same size as its child. Instead, it offers a number of functions that allow us to apply a spatial transformation: | Function | Argument(s) | Effect | |----------------------|-----------|--------| | [`translate!`](@ref) | `Vector2f` | Move widget by number of pixels | | [`rotate!`](@ref) | `Angle` | Rotate around widgets' centroid | | [`scale!`](@ref) | `Number, Number` | Scale height and width by given factor | | [`skew!`](@ref) | `Number, Number` | [Skew](https://en.wikipedia.org/wiki/Shear_mapping) widget along x- and y-axis | | [`reset!`](@ref) | `(none)` | Reset transform to identity | !!! tip "Rotate around a Point" To rotate a widget around a fixed point `p`, we can `translate!` such that the widget's new center is at `p`, `rotate!`, then `translate!` back to the widget's initial position. These functions are called on the `TransformBin` instance directly, we do not use a separate transform object. The arguments for these functions operate in absolute widget space, with `(0, 0)` being the top left corner of the `TransformBin`s size allocation, in pixels. For example, to make a button spin one time when it is clicked, we can use `TransformBin` and `Animation` as follows: ```julia # animation target to_animate = Button(Label("Spin")) # transform bin transform_bin = TransformBin() set_child!(transform_bin, to_animate) # animation animation = Animation(to_animate, seconds(1)) on_tick!(animation, transform_bin) do self::Animation, value::Float64, transform_bin::TransformBin # set the transform angle to value in [0, 360°] reset!(transform_bin) rotate!(transform_bin, degrees(value * 360)) end # trigger animation when button is clicked connect_signal_clicked!(to_animate, animation) do self::Button, animation::Animation play!(animation) end ``` ![](../assets/animation_spin.webm) Note that applying a transform using `TransformBin` does not change the size allocation of the widget, it only applies the effect visually, similarly to how a `GLTransform` is only applied to the rendered image, not the `Shape` itself. By default, the function used to map the elapsed duration of the `Animation` to the output value of `on_tick!` is linear in shape (`f(x) = x`). Mousetrap offers additional functions with different shapes, allowing users to more easily implement animations that appear to speed up or slow down at certain points. Using [`set_timing_function!`](@ref), which takes a value of the enum [`AnimationTimingFunction`](@ref), we can choose from multiple presets. See its documentation for more information. --- # Widget Themes & Style Classes Mousetrap uses [Cascading Style Sheets (CSS)](https://developer.mozilla.org/en-US/docs/Web/CSS) to define the exact look of a widget. A UI theme is nothing more than a huge CSS file from which all widgets take information about how they should be rendered. !!! Warning "CSS" The rest of this chapter will assume that readers are familiar with the basics of CSS. Readers are encouraged to consult [this documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference) for CSS-related questions. ## Applying CSS Properties to a Widget We can define a CSS modifier class as a string, then compile that string using [`add_css!`](@ref), which makes that modifier class globally available: ```julia # define modifier class `sharp-corners` add_css!(""" .sharp-corners { border-radius: 0%; } """) ``` We can then apply this class to any widget using [`add_css_class!`](@ref), at which point the widgets' appearance will change accordingly. To remove the modifier, we call [`remove_css_class!`](@ref). A widget can have more than one modifier class. To list all applied CSS classes, we use [`get_css_classes`](@ref). For a list of CSS properties supported by Mousetrap, see [here](https://docs.gtk.org/gtk4/css-properties.html). For example, the following defines a `ToggleButton` that, when toggled, applies the following CSS class to both the `Window` (which is a Widget), and its `HeaderBar`: ```julia using Mousetrap add_css!(""" .custom { background-color: hotpink; font-family: monospace; border-radius: 0%; } """) main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") button = ToggleButton() connect_signal_toggled!(button, window) do self::ToggleButton, window::Window if get_is_active(self) add_css_class!(window, "custom") add_css_class!(get_header_bar(window), "custom") else remove_css_class!(window, "custom") remove_css_class!(get_header_bar(window), "custom") end end set_child!(window, button) present!(window) end ``` ![](../assets/css_style_pink_window.png) While a pink window is usually not recommended, making small adjustments to interactive widgets, or changing which font a widget uses, can be very useful when polishing our application's look and feel. For a more practical example, after defining the following CSS modifier class: ```css .monospaced { font-family: monospace; } ``` We can make an `Entry` or `TextView` use monospaced text by calling `add_css_class!(widget, "monospaced")` on each widget instance. --- ## Changing a Widgets Color The following implements `set_accent_color!`, which is not part of Mousetrap. `set_accent_color!` takes a widget, one of the below constants, as well as a boolean indicating whether the window should be opaque, as its arguments. When applied to a widget, this function changes that widgets color to one of the 5 pre-defined UI colors, such that their look fits well with the default UI theme: ```julia using Mousetrap # define widget colors const WidgetColor = String const WIDGET_COLOR_DEFAULT = "default" const WIDGET_COLOR_ACCENT = "accent" const WIDGET_COLOR_SUCCESS = "success" const WIDGET_COLOR_WARNING = "warning" const WIDGET_COLOR_ERROR = "error" # create CSS classes for all of the widget colors for name in [WIDGET_COLOR_DEFAULT, WIDGET_COLOR_ACCENT, WIDGET_COLOR_SUCCESS, WIDGET_COLOR_WARNING, WIDGET_COLOR_ERROR] # compile CSS and append it to the global CSS style provider state add_css!(""" $name:not(.opaque) { background-color: @$(name)_fg_color; } .$name.opaque { background-color: @$(name)_bg_color; color: @$(name)_fg_color; } """) end # function to set the accent color of a widget function set_accent_color!(widget::Widget, color, opaque = true) if !(color in [WIDGET_COLOR_DEFAULT, WIDGET_COLOR_ACCENT, WIDGET_COLOR_SUCCESS, WIDGET_COLOR_WARNING, WIDGET_COLOR_ERROR]) log_critical("In set_color!: Color ID `" * color * "` is not supported") end add_css_class!(widget, color) if opaque add_css_class!(widget, "opaque") end end ``` Users are encouraged to just copy the above code into their own project, for `set_accent_color!` to become available. We can use this function like so: ```julia # widget factory create_widget() = Button(Label("TEST")) # create column view column_view = ColumnView() # column 1: whether `opaque` was set to true column = push_back_column!(column_view, " ") set_widget_at!(column_view, column, 1, Label("!opaque")) set_widget_at!(column_view, column, 2, Label("opaque")) for color in [ WIDGET_COLOR_DEFAULT, # column 2: default look of a widget WIDGET_COLOR_ACCENT, # column 3: accented, usually blue WIDGET_COLOR_SUCCESS, # column 4: marked successful, usually green WIDGET_COLOR_WARNING, # column 5: marked as warning, usually yellow WIDGET_COLOR_ERROR] # column 6: marked as destructive action, usually red column = push_back_column!(column_view, color) # row 1: accented widget, not opaque widget = create_widget() set_accent_color!(widget, color, false) set_widget_at!(column_view, column, 1, widget) # row 2: accented widget, opaque widget = create_widget() set_accent_color!(widget, color, true) set_widget_at!(column_view, column, 2, widget) end ``` Here, we created a column view that shows all permutations of the arguments of `set_accent_color!`. We defined `create_widget() = Button(Label("TEST"))`, therefore each cell will have a button with a label: ![](../assets/css_style_column_view_buttons.png) `set_accent_color!` allows us to change the color of each button. It can be applied to any widget, however: ![](../assets/css_style_column_view_headerbar.png) Where we use a `HeaderBar` instead of `Button`. CSS modifiers can be applied to any widget, even windows. ## Changing the Themes Palette To change a color used by the global theme, we need to redefine one of the themes [color constants](https://docs.gtk.org/gtk4/css-properties.html#colors) using CSS. For example, the global accent color, blue by default, can be redefined to any other color using the following function, which is also not part of Mousetrap: ```julia set_accent_color!(color::RGBA) = add_css!("@define-color accent_bg_color $(serialize(color));") ``` For example, calling `set_accent_color!(RGBA(1, 0, 1, 1))` changes the accent color to magenta, which is applied to all widgets globally: ![](../assets/css_style_magenta_accent.png) For names of palette colors other than `accent_bg_color`, see [here](https://gitlab.gnome.org/GNOME/libadwaita/-/blob/main/src/stylesheet/_colors.scss?ref_type=heads). ## CSS Animations `Animation` offers an in-engine way to do animations, which is usually preferred. However, [CSS animations](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) are supported as well, and are defined as they would be in pure CSS: ```julia # define CSS animation and modifier add_css!(""" @keyframes spin-animation { 0% { transform: rotate(0turn) scale(1); } 50% { transform: rotate(0.5turn) scale(2); } 100% { transform: rotate(1turn) scale(1); } } .spinning { animation: spin-animation; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: ease-in-out; } """) main() do app::Application window = Window(app) button = Button() # apply modifier, this sets the animation-related properties add_css_class!(button, "spinning") set_child!(window, AspectFrame(1.0, button)) present!(window) end ``` ![](../assets/css_style_animation_spin.webm) While we could create this animation using `Mousetrap.Animation` and `TransformBin`, using CSS means that we do not have to instance these two objects for every widget we want to animate, we can instead just apply the CSS modifier class to any instance. In return, the CSS-based animation cannot depend on any external variables and is thus only suited for animations that remain the same each time they are played. ================================================ FILE: docs/src/01_manual/11_app_distribution.md ================================================ # Chapter 11: App Distribution In this chapter, we will learn: + How to load and store assets + How to bundle our app for distribution + How to install our app on a user's machine --- !!! compat These features are not yet implemented, this section is incomplete. As of version 0.3.0, there is no unified way to bundle and distribute a Mousetrap app. See [here](https://github.com/users/Clemapfel/projects/2?pane=issue&itemId=33978204#) for more information. ================================================ FILE: docs/src/01_manual/12_opengl_integration.md ================================================ # Chapter 12: OpenGL Integration & Makie Support In this chapter, we will learn: + How to use `GLArea` to allow foreign OpenGL libraries to render to a Mousetrap widget + How to display a `GLMakie` plot in a Mousetrap window !!! compat Features from this chapter are only available in Mousetrap `v0.3.0` or newer. --- # Introduction: Use Case If we want to render shapes or images using `OpenGL`, Mousetraps `RenderArea` offers a convenient interface for this. In some applications, we may want to use [OpenGL](https://github.com/JuliaGL/ModernGL.jl) itself. One common use case for this is the integration of another, separate library that is unrelated to Mousetrap and shares no interface with it, except for the fact that both use OpenGL for rendering. For situations like this, Mousetrap offers a low-level, generic widget, [`GLArea`](@ref), which provides an OpenGL context and render surface, thus allowing OpenGL-based graphics to be displayed inside a Mousetrap application. # GLArea `GLarea` is a very simple widget, it acts exactly as `RenderArea` in terms of appearance and behavior, though there is no in-Mousetrap way to display graphics. We create it like so: ```julia area = GLArea() ``` After which it can be used just like any other widget, meaning it has a size request, opacity, adheres to CSS, etc. `GLArea` holds its own OpenGL context, which is **initialized after `realize` has been emitted**. This is important to understand, we cannot call any OpenGL code before the widget is fully realized and displayed on screen. To delay execution, we should connect to one of `GLArea`s signals. !!! danger "OpenGL Context is only available after Realization" To reiterate, Mousetrap will only be able to provide an OpenGL context after `GLArea` is realized, which usually happens when the window it is contained within is shown for the first time. If we try to interact with the context before this point, a critical log message will be printed and the OpenGL operation will fail. ## Signals `GLArea` has two unique signals, `render` and `resize`. Alongside these, in most situations, we will also want to connect to the `realize` signal, which all widgets share. We recognize `resize` from `RenderArea`. Just as then, it requires a signal handler with the signature ```julia (::GLArea, width::Integer, height::Integer, [::Data_t]) -> Nothing ``` `resize` is emitted anytime `GLArea` changes size, according to its widget properties. Crucially, this also means its [default framebuffer](https://www.khronos.org/opengl/wiki/Default_Framebuffer) and [viewport](https://www.khronos.org/opengl/wiki/GLAPI/glViewport) are resized accordingly. **We cannot change either size ourselves**, Mousetrap's back-end handles this for us. For each `resize` invocation, we can assume that the default frame buffer has a size of `width * height` pixels. Signal `render` is usually emitted once per frame, whenever the widget is drawn on screen. This signal requires a callback with the signature ```julia (::GLArea, gdk_gl_context::Ptr{Cvoid}, [::Data_t]) -> Bool ``` Where `gdk_gl_context` is a C-pointer to the internally held [OpenGL context](https://docs.gtk.org/gdk4/class.GLContext.html). We usually do not have to interact with this context, though any `render` signal handler still requires including this argument to conform to the above signature. We note that signal `render` requires its callback to return a boolean. This return value notifies Mousetrap whether the `GLArea`s framebuffer was updated during the draw step. If we have modified the image we want to appear on screen, we should return `true`, if no drawing has taken place and the `GLArea` does not need to be updated, `false` should be returned. In the signal handler of `render`, we should make sure to bind the current `GLArea`s OpenGL context as the active one using [`make_current`](@ref) (see below). This is to make sure that we are rendering to the buffer associated with the specific `GLArea` emitting the signal, not another instance of the same widget type, or a completely separate OpenGL context from another library. !!! warning "GLAreas do not share a Context" Unlike `RenderArea`, which all share a singular global OpenGL context, each `GLArea` instance has its own OpenGL context, meaning objects cannot be transmitted between `GLArea`s, and, if one is unrealized, all objects associated with that context will be inaccessible (but not freed). For performance optimization reasons, `GLArea` will only be drawn when necessary, as is the case for all objects subtyping `Widget`. We can manually request `GLArea` to update the frame after this one, by calling [`queue_render`](@ref). General usage of `GLarea` with another OpenGL-based library will have the following structure: ```julia glarea = GLArea() # realize callback connect_signal_realize!(glarea) do self::GLArea make_current(self) # do initialization here, only after this callback was invoked is the # internal OpenGL context fully initialized and ready to be used queue_render(self) return nothing end # render callback connect_signal_render!(glarea) do self::GLArea, _::Ptr{Cvoid} make_current(self) # do opengl renderloop here return true end # resize callback connect_signal_resize!(glarea) do self::GLArea, width, height make_current(self) # handle new size here queue_render(self) return nothing end # destroy callback connect_signal_destroy!(glarea) do self::GLArea make_current(self) # do shutdown here return nothing end ``` We see that we should make sure to bind the context using `make_current` before doing any OpenGL-related work and to manually request a redraw after the area was initialized or resized. # Example: GLMakie [`GLMakie`](https://docs.makie.org/stable/explanations/backends/glmakie/index.html) is one backend for the hugely popular [`Makie`](https://github.com/MakieOrg/Makie.jl) plotting library. As its name suggests, `GLMakie` uses OpenGL for rendering, which means it is possible to allow Makie to render to a Mousetrap `GLArea`, allowing us to integrate plots and graphics into our Mousetrap application. Given here will be a minimum working example that displays a scatter plot inside a `Mousetrap.Window` by creating a `GLArea`-based widget that can be used to create a `GLMakie.Screen`. Note that this example is incomplete and does not support all of Makies features. One conflict that Mousetrap users will have to resolve for themselves is how to handle input events. In the following, all of Makies input-related behavior was suppressed, making it so users will have to handle input events and window behavior using only the Mousetrap event model. !!! details "Note from the Author: Makie Interface" The example here most likely does not implement enough of Makies interface to be fully ready for usage. Most of the code was based on [`Gtk4GLMakie`](https://github.com/JuliaGtk/Gtk4Makie.jl), which itself is still rough. I'm not that familiar with Makie in general usage, and fully implementing an interface requires knowledge of Makies internals on top of that. If you or your project is very familiar with Makie and would like to improve this code, feel free to [open a PR](https://github.com/Clemapfel/Mousetrap.jl/pulls) that modifies [`test/makie_test.jl`](https://github.com/Clemapfel/Mousetrap.jl/blob/main/test/makie_test.jl), which ideally will become its own Julia package in the future, similar to `Gtk4GLMakie`. Any contributor will be credited as an author. Thank you for your consideration. C. !!! details "MousetrapMakie: Click to expand" ```julia """ Minimum working example showing how to display a GLMakie plot using Mousetrap `GLArea` """ module MousetrapMakie export GLMakieArea, create_glmakie_screen using Mousetrap using ModernGL, GLMakie, Colors, GeometryBasics, ShaderAbstractions using GLMakie: empty_postprocessor, fxaa_postprocessor, OIT_postprocessor, to_screen_postprocessor using GLMakie.GLAbstraction using GLMakie.Makie """ ## GLMakieArea <: Widget `GLArea` wrapper that automatically connects all necessary callbacks to be used as a GLMakie render target. Use `create_glmakie_screen` to initialize a screen you can render to using Makie from this widget. Note that `create_glmakie_screen` needs to be called **after** `GLMakieArea` has been realized, as only then will the internal OpenGL context be available. See the example below. ## Constructors `GLMakieArea()` ## Signals (no unique signals) ## Fields (no public fields) ## Example using Mousetrap, MousetrapMakie main() do app::Application window = Window(app) canvas = GLMakieArea() set_size_request!(canvas, Vector2f(200, 200)) set_child!(window, canvas) # use optional ref to delay screen allocation after `realize` screen = Ref{Union{Nothing, GLMakie.Screen{GLMakieArea}}}(nothing) connect_signal_realize!(canvas) do self screen[] = create_glmakie_screen(canvas) display(screen[], scatter(1:4)) return nothing end present!(window) end """ mutable struct GLMakieArea <: Widget glarea::GLArea # wrapped native widget framebuffer_id::Ref{Int} # set by render callback, used in MousetrapMakie.create_glmakie_screen framebuffer_size::Vector2i # set by resize callback, used in GLMakie.framebuffer_size function GLMakieArea() glarea = GLArea() set_auto_render!(glarea, false) # should `render` be emitted everytime the widget is drawn connect_signal_render!(on_makie_area_render, glarea) connect_signal_resize!(on_makie_area_resize, glarea) return new(glarea, Ref{Int}(0), Vector2i(0, 0)) end end Mousetrap.get_top_level_widget(x::GLMakieArea) = x.glarea # maps hash(GLMakieArea) to GLMakie.Screen const screens = Dict{UInt64, GLMakie.Screen}() # maps hash(GLMakieArea) to Scene, used in `on_makie_area_resize` const scenes = Dict{UInt64, GLMakie.Scene}() # render callback: if screen is open, render frame to `GLMakieArea`s OpenGL context function on_makie_area_render(self, context) key = Base.hash(self) if haskey(screens, key) screen = screens[key] if !isopen(screen) return false end screen.render_tick[] = nothing glarea = screen.glscreen glarea.framebuffer_id[] = glGetIntegerv(GL_FRAMEBUFFER_BINDING) GLMakie.render_frame(screen) end return true end # resize callback: update framebuffer size, necessary for `GLMakie.framebuffer_size` function on_makie_area_resize(self, w, h) key = Base.hash(self) if haskey(screens, key) screen = screens[key] glarea = screen.glscreen glarea.framebuffer_size.x = w glarea.framebuffer_size.y = h queue_render(glarea.glarea) end if haskey(scenes, key) scene = scenes[key] scene.events.window_area[] = Recti(0, 0, glarea.framebuffer_size.x, glarea.framebuffer_size.y) scene.events.window_dpi[] = Mousetrap.calculate_monitor_dpi(glarea) end return nothing end # resolution of `GLMakieArea` OpenGL framebuffer GLMakie.framebuffer_size(self::GLMakieArea) = (self.framebuffer_size.x, self.framebuffer_size.y) # forward retina scale factor from GTK4 back-end GLMakie.retina_scaling_factor(w::GLMakieArea) = Mousetrap.get_scale_factor(w) # resolution of `GLMakieArea` widget itself` function GLMakie.window_size(w::GLMakieArea) size = get_natural_size(w) size.x = size.x * GLMakie.retina_scaling_factor(w) size.y = size.y * GLMakie.retina_scaling_factor(w) return (size.x, size.y) end # calculate screen size and dpi function Makie.window_area(scene::Scene, screen::GLMakie.Screen{GLMakieArea}) glarea = screen.glscreen scenes[hash(glarea)] = scene end # resize request by makie will be ignored function GLMakie.resize_native!(native::GLMakieArea, resolution...) # noop end # bind `GLMakieArea` OpenGL context ShaderAbstractions.native_switch_context!(a::GLMakieArea) = make_current(a.glarea) # check if `GLMakieArea` OpenGL context is still valid, it is while `GLMakieArea` widget stays realized ShaderAbstractions.native_context_alive(x::GLMakieArea) = get_is_realized(x) # destruction callback ignored, lifetime is managed by Mousetrap instead function GLMakie.destroy!(w::GLMakieArea) # noop end # check if canvas is still realized GLMakie.was_destroyed(window::GLMakieArea) = !get_is_realized(window) # check if canvas should signal it is open Base.isopen(w::GLMakieArea) = !GLMakie.was_destroyed(w) # react to makie screen visibility request GLMakie.set_screen_visibility!(screen::GLMakieArea, bool) = bool ? show(screen.glarea) : hide!(screen.glarea) # apply glmakie config function GLMakie.apply_config!(screen::GLMakie.Screen{GLMakieArea}, config::GLMakie.ScreenConfig; start_renderloop=true) @warn "In MousetrapMakie: GLMakie.apply_config!: This feature is not yet implemented, ignoring config" # cf https://github.com/JuliaGtk/Gtk4Makie.jl/blob/main/src/screen.jl#L111 return screen end # screenshot framebuffer function Makie.colorbuffer(screen::GLMakie.Screen{GLMakieArea}, format::Makie.ImageStorageFormat = Makie.JuliaNative) @warn "In MousetrapMakie: GLMakie.colorbuffer: This feature is not yet implemented, returning framecache" # cf https://github.com/JuliaGtk/Gtk4Makie.jl/blob/main/src/screen.jl#L147 return screen.framecache end # ignore makie event model, use Mousetrap event controllers instead Makie.window_open(::Scene, ::GLMakieArea) = nothing Makie.disconnect!(::GLMakieArea, f) = nothing GLMakie.pollevents(::GLMakie.Screen{GLMakieArea}) = nothing Makie.mouse_buttons(::Scene, ::GLMakieArea) = nothing Makie.keyboard_buttons(::Scene, ::GLMakieArea) = nothing Makie.dropped_files(::Scene, ::GLMakieArea) = nothing Makie.unicode_input(::Scene, ::GLMakieArea) = nothing Makie.mouse_position(::Scene, ::GLMakie.Screen{GLMakieArea}) = nothing Makie.scroll(::Scene, ::GLMakieArea) = nothing Makie.hasfocus(::Scene, ::GLMakieArea) = nothing Makie.entered_window(::Scene, ::GLMakieArea) = nothing """ create_gl_makie_screen(::GLMakieArea; screen_config...) -> GLMakie.Screen{GLMakieArea} For a `GLMakieArea`, create a `GLMakie.Screen` that can be used to display makie graphics """ function create_glmakie_screen(area::GLMakieArea; screen_config...) if !get_is_realized(area) log_critical("MousetrapMakie", "In MousetrapMakie.create_glmakie_screen: GLMakieArea is not yet realized, it's internal OpenGL context cannot yet be accessed") end config = Makie.merge_screen_config(GLMakie.ScreenConfig, screen_config) set_is_visible!(area, config.visible) set_expand!(area, true) # quote from https://github.com/JuliaGtk/Gtk4Makie.jl/blob/main/src/screen.jl#L342 shader_cache = GLAbstraction.ShaderCache(area) ShaderAbstractions.switch_context!(area) fb = GLMakie.GLFramebuffer((1, 1)) # resized on GLMakieArea realization later postprocessors = [ config.ssao ? ssao_postprocessor(fb, shader_cache) : empty_postprocessor(), OIT_postprocessor(fb, shader_cache), config.fxaa ? fxaa_postprocessor(fb, shader_cache) : empty_postprocessor(), to_screen_postprocessor(fb, shader_cache, area.framebuffer_id) ] screen = GLMakie.Screen( area, shader_cache, fb, config, false, nothing, Dict{WeakRef, GLMakie.ScreenID}(), GLMakie.ScreenArea[], Tuple{GLMakie.ZIndex, GLMakie.ScreenID, GLMakie.RenderObject}[], postprocessors, Dict{UInt64, GLMakie.RenderObject}(), Dict{UInt32, Makie.AbstractPlot}(), false, ) # end quote hash = Base.hash(area.glarea) screens[hash] = screen set_tick_callback!(area.glarea) do clock::FrameClock if GLMakie.requires_update(screen) queue_render(area.glarea) end if GLMakie.was_destroyed(area) return TICK_CALLBACK_RESULT_DISCONTINUE else return TICK_CALLBACK_RESULT_CONTINUE end end return screen end end ``` We can test the above using: ```julia using Mousetrap, .MousetrapMakie, GLMakie main() do app::Application window = Window(app) set_title!(window, "Mousetrap x Makie") canvas = GLMakieArea() set_size_request!(canvas, Vector2f(200, 200)) set_child!(window, canvas) # use optional ref to delay screen allocation after `realize` screen = Ref{Union{Nothing, GLMakie.Screen{GLMakieArea}}}(nothing) connect_signal_realize!(canvas) do self # initialize GLMakie.Screen screen[] = create_glmakie_screen(canvas) # use screen to display plot display(screen[], scatter(rand(123))) return nothing end present!(window) end ``` ![](../assets/makie_scatter.png) Where we delayed the call to `create_gl_makie_screen` to *after* `realize` was emitted for reasons discussed [earlier in this chapter](#glarea). Since we still need to reference the created screen outside the `realize` signal handler, we used the **optional pattern**: ```julia optional = Ref{Union{Nothing, T}}(nothing) ``` Which initializes a reference with `nothing`, such that the reference value can later be assigned with a value of `T`, `GLMakie:Screen{GLMakieArea}` in our example above. After `realize` was emitted, we can access the screen using `screen[]`. ================================================ FILE: docs/src/index.md ================================================ # Mousetrap Welcome to the documentation of Mousetrap.jl, a GUI engine for Julia. Mousetrap was created and designed by [C. Cords](https://clemens-cords.com). This page contains a [manual and tutorial](#manual), as well as an [index](#index) of all [functions](./02_library/functions.md), [classes](./02_library/classes.md), and [enums](./02_library/enums.md). To download and install mousetrap, follow the instructions on the [official GitHub page](https://github.com/Clemapfel/mousetrap.jl#installation). Mousetrap.jl, the non-Julia components of mousetrap, this documentation, and all its original assets, are licensed under [lGPL3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html#license-text), meaning they can be used in both free, open-source, as well as for-profit, proprietary projects. For frequently asked questions, see [here](#FAQ). Use the navigation area on the left side of this page or the table of contents below to navigate to the appropriate chapter of this manual: ## Manual ```@contents Pages = [ "01_manual/01_installation.md" "01_manual/02_signals.md" "01_manual/03_actions.md" "01_manual/04_widgets.md" "01_manual/05_event_handling.md" "01_manual/06_image.md" "01_manual/07_os_interface.md" "01_manual/08_menus.md" "01_manual/09_native_rendering.md" "01_manual/10_theme_customization.md" "01_manual/11_app_distribution.md" "01_manual/12_opengl_integration.md" ] Depth = 5 ``` --- ## Index + [index of classes](./02_library/classes.md) + [index of functions](./02_library/functions.md) + [index of enums](./02_library/enums.md) --- ## FAQ ### Why is there a C++ Component at all? The C++ version of mousetrap was originally created for an unrelated commercial, closed-source project in 2022. When the project failed due to funding issues in 2023, I decided that, instead of throwing away all my C++ work, I would instead create a Julia wrapper around it, so it can at least contribute to the Julia ecosysem. Even though Julia-Mousetrap is the face of the project now, C++-Mousetrap existed and was finished *before* Julia-Mousetrap was ever conceived. A less valid reason is that mousetrap, in order to extend the GObject type system, [makes extensive use of C-macros](https://github.com/Clemapfel/mousetrap/blob/main/include/mousetrap/signal_component.hpp#L24), which are quite hard to emulate in Julia, as there is no way to `ccall` a macro, to my knowledge. ### What is the difference between mousetrap and GTK4.jl? Mousetrap is unaffiliated with [GTK.jl](https://github.com/JuliaGraphics/Gtk.jl) and [GTK4.jl](https://github.com/JuliaGtk/Gtk4.jl). The only connection these projects share is that both use the GTK4 C library as their basis. Mousetrap is not endorsed by [GNOME](https://gnome.org) and has no connection to that organization or any of its contributors. ### What advantages does mousetrap have over pure GTK4? While based on GTK4 and usually calling native GTK4 under the hood, mousetraps interface, that is, the actual architecture of the library, including syntax and names, was reworked from the ground up. Heavy editorializing has taken place, renaming or completely removing certain parts of GTK4 in an effort to make mousetrap more friendly to newcomers and people with no GUI experience, easier to use, and less susceptible to developer error. Furthermore, mousetrap contains an all-new OpenGL-based rendering engine, which aims to replace the cairo component of native GTK4 to allow for faster, more easily integrated native rendering. ### What advantages does pure GTK4 have over mousetrap? Speaking about the GTK4 C library specifically, not GTK4.jl, GTK4 is much bigger and has many features that went unused in mousetrap, or, if used, were made opaque such that a user of mousetrap cannot interact with these features: + Removed Modules: [GDK](https://docs.gtk.org/gdk4/), [ATK](https://gitlab.gnome.org/GNOME/atk), [gobject](https://docs.gtk.org/gobject/), [GLib](https://docs.gtk.org/glib/), [adwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/) + Widgets with no mousetrap equivalent: `GtkListBox`, `PasswordEntry`, `GtkTextView`, `GtkSourceView`, `AdwAboutWindow` + Removed Features: Mnemonics, Stateful Actions, [GtkBuilder Interface](https://docs.gtk.org/gtk4/class.Builder.html), `GTK_DEBUG`, `GtkInspector` Furthermore, any classes marked as deprecated in GKT4.10, such as `GtkTreeView`, where removed completely. When possible, the adwaita version of a widget was preferred over the native GTK4 version, for example, using `AdwWindow` instead of `GtkWindow`, or `AdwMessageDialog` over `GtkAlertDialog`. Julia does not use a [build system such as meson](https://mesonbuild.com/) and is thus incompatible with distribution through [flatpak](https://flatpak.org/) or similar services. ### How do I ship my app? JuliaComputing and many more contributors are currently working on [`PackageCompiler`](https://github.com/JuliaLang/PackageCompiler.jl), which is supposed to compile a Julia package into a stand-alone binary. Compatibility of mousetrap with `PackageCompiler` remains untested and may be impossible until further work has been put into either `PackageCompiler`, or mousetrap itself. [There is evidence](https://www.reddit.com/r/Julia/comments/14kfyx7/comment/jpuofyg/) that static compilation is one of Julias next big goals. Ideally, when static compilation is working 100% of the time, mousetrap will be polished enough to be considered fully stable and easily distributable, therefore making it usable in production. Until then, the only way to ship a stand-alone Julia app is to bundle the entire Julia runtime along with the app-specific Julia code and resources, which will usually be a folder with a size of 2 GB or more. Alternatively, developers have to force end users to [install Julia on their machine globally](https://github.com/JuliaLang/juliaup), after which launching the app is as simple as calling (`julia main.jl`) from a shell script. ================================================ FILE: jll/build_tarballs.jl ================================================ # Note that this script can accept some limited command-line arguments, run # `julia build_tarballs.jl --help` to see a usage message. using BinaryBuilder, Pkg name = "libmousetrap" version = v"0.3.0" # Collection of sources required to complete build sources = [ GitSource("https://github.com/Clemapfel/mousetrap.git", "ffa28d0bd569118320a6bb063286edfb35fc0429"), GitSource("https://github.com/Clemapfel/mousetrap_julia_binding.git", "6b838ea238e118d694ffc1b3a1e0225441c8cfb3") ] # Bash recipe for building across all platforms script = raw""" cd $WORKSPACE/srcdir echo -e "[binaries]\ncmake='/usr/bin/cmake'" >> cmake_toolchain_patch.ini cd mousetrap mkdir ${prefix}/share/licenses/mousetrap cp LICENSE ${prefix}/share/licenses/mousetrap/LICENSE meson setup build --cross-file=$MESON_TARGET_TOOLCHAIN --cross-file=../cmake_toolchain_patch.ini meson install -C build cd ../mousetrap_julia_binding meson setup build --cross-file=$MESON_TARGET_TOOLCHAIN --cross-file=../cmake_toolchain_patch.ini -DJulia_INCLUDE_DIRS=$prefix/include/julia meson install -C build cd .. rm cmake_toolchain_patch.ini """ # These are the platforms we will build for by default, unless further # platforms are passed in on the command line platforms = filter(p -> nbits(p) == 64, supported_platforms()) # The products that we will ensure are always built products = [ LibraryProduct("libmousetrap", :mousetrap), LibraryProduct("libmousetrap_julia_binding", :mousetrap_julia_binding), FileProduct("") ] x11_platforms = filter(p -> Sys.islinux(p) || Sys.isfreebsd(p), platforms) # Dependencies that must be installed before this package can be built dependencies = [ Dependency("GLEW_jll") Dependency("GLU_jll"; platforms = x11_platforms) Dependency("GTK4_jll") Dependency("libadwaita_jll") Dependency("OpenGLMathematics_jll") Dependency("libcxxwrap_julia_jll") BuildDependency("libjulia_jll") BuildDependency("Xorg_xorgproto_jll"; platforms = x11_platforms) ] # Build the tarballs, and possibly a `build.jl` as well. build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6", preferred_gcc_version = v"12.1.0") ================================================ FILE: jll/build_tarballs.jl.in ================================================ # Note that this script can accept some limited command-line arguments, run # `julia build_tarballs.jl --help` to see a usage message. using BinaryBuilder, Pkg name = "libmousetrap" version = v"@VERSION@" # Collection of sources required to complete build sources = [ GitSource("https://github.com/Clemapfel/mousetrap.git", "@MOUSETRAP_COMMIT@"), GitSource("https://github.com/Clemapfel/mousetrap_julia_binding.git", "@MOUSETRAP_JULIA_BINDING_COMMIT@") ] # Bash recipe for building across all platforms script = raw""" cd $WORKSPACE/srcdir echo -e "[binaries]\ncmake='/usr/bin/cmake'" >> cmake_toolchain_patch.ini cd mousetrap mkdir ${prefix}/share/licenses/mousetrap cp LICENSE ${prefix}/share/licenses/mousetrap/LICENSE meson setup build --cross-file=$MESON_TARGET_TOOLCHAIN --cross-file=../cmake_toolchain_patch.ini meson install -C build cd ../mousetrap_julia_binding meson setup build --cross-file=$MESON_TARGET_TOOLCHAIN --cross-file=../cmake_toolchain_patch.ini -DJulia_INCLUDE_DIRS=$prefix/include/julia meson install -C build cd .. rm cmake_toolchain_patch.ini """ # These are the platforms we will build for by default, unless further # platforms are passed in on the command line platforms = filter(p -> nbits(p) == 64, supported_platforms()) # The products that we will ensure are always built products = [ LibraryProduct("libmousetrap", :mousetrap), LibraryProduct("libmousetrap_julia_binding", :mousetrap_julia_binding) ] x11_platforms = filter(p -> Sys.islinux(p) || Sys.isfreebsd(p), platforms) # Dependencies that must be installed before this package can be built dependencies = [ Dependency("GLEW_jll") Dependency("GLU_jll"; platforms = x11_platforms) Dependency("GTK4_jll") Dependency("libadwaita_jll") Dependency("OpenGLMathematics_jll") Dependency("libcxxwrap_julia_jll") BuildDependency("libjulia_jll") BuildDependency("Xorg_xorgproto_jll"; platforms = x11_platforms) ] # Build the tarballs, and possibly a `build.jl` as well. build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6", preferred_gcc_version = v"12.1.0") ================================================ FILE: jll/deploy.jl ================================================ #throw(AssertionError("In Mousetrap/jll/deploy.jl: This script is meant for internal use only and should not be tampered with by the general public. Do not run this file.")) const VERSION = "0.3.0" function get_most_recent_commit(folder::String) current = pwd() cd(folder) io = IOBuffer() run(pipeline(`git rev-parse HEAD`; stdout=io, stderr=devnull)) cd(current) return replace(String(take!(io)), "\n" => "") end const mousetrap_commit = get_most_recent_commit("../../mousetrap") const mousetrap_julia_binding_commit = get_most_recent_commit("../../mousetrap_julia_binding") # if local, files will be written to ~/.julia/dev/mousetrap_jll const deploy_local = false const skip_build = true if deploy_local @info "Deployment: local" repo = "local" else @info "Deployment: github" repo = "Clemapfel/mousetrap_jll" end ## Configure function configure_file(path_in::String, path_out::String) file_in = open(path_in, "r") file_out = open(path_out, "w+") for line in eachline(file_in) write(file_out, replace(line, "@MOUSETRAP_COMMIT@" => mousetrap_commit, "@MOUSETRAP_JULIA_BINDING_COMMIT@" => mousetrap_julia_binding_commit, "@VERSION@" => VERSION ) * "\n") end close(file_in) close(file_out) end @info "Configuring `build_tarballs.jl.in`" configure_file("./build_tarballs.jl.in", "./build_tarballs.jl") path = "/home/clem/.julia/dev/mousetrap_jll" if isfile(path) run(`rm -r $path`) end run(`julia -t 8 build_tarballs.jl --debug --verbose --skip-build --deploy=$repo`) ================================================ FILE: src/Mousetrap.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # GitHub: https://github.com/clemapfel/mousetrap.jl # Documentation: https://clemens-cords.com/mousetrap # # Copyright © 2023, Licensed under lGPL-3.0 # """ # Mousetrap GUI Engine ($(Mousetrap.VERSION)) GitHub: https://github.com/clemapfel/mousetrap.jl Documentation: http://clemens-cords.com/mousetrap/ Copyright © 2023 C.Cords, Licensed under lGPL-3.0 """ module Mousetrap const VERSION = v"0.3.1" ####### detail.jl module detail using CxxWrap import GTK4_jll, Glib_jll function try_update_gsettings_schema_dir() # request to use GTK4_jll-supplied settings schema if none are available on the machine if !Sys.islinux() && (!haskey(ENV, "GSETTINGS_SCHEMA_DIR") || isempty(ENV["GSETTINGS_SCHEMA_DIR"])) ENV["GSETTINGS_SCHEMA_DIR"] = normpath(joinpath(GTK4_jll.libgtk4, "../../share/glib-2.0/schemas")) end end function __init__() try_update_gsettings_schema_dir() # executed on `using Mousetrap`, env needs to be set each time before `adw_init` is called @initcxx() end using mousetrap_jll function get_mousetrap_julia_binding() return mousetrap_jll.mousetrap_julia_binding end try_update_gsettings_schema_dir() # executed on `precompile Mousetrap`, but not on using, silences warning during installation @wrapmodule(get_mousetrap_julia_binding) end const MOUSETRAP_ENABLE_OPENGL_COMPONENT = convert(Bool, detail.MOUSETRAP_ENABLE_OPENGL_COMPONENT) ####### typed_function.jl mutable struct TypedFunction _apply::Function _return_t::Type _arg_ts::Tuple function TypedFunction(f::Function, return_t::Type, arg_ts::Tuple) precompile(f, arg_ts) arg_ts_string = "(" for i in 1:length(arg_ts) arg_ts_string = arg_ts_string * string(arg_ts[i]) * ((i < length(arg_ts)) ? ", " : ")") end signature = arg_ts_string * " -> $return_t" actual_return_ts = Base.return_types(f, arg_ts) if isempty(actual_return_ts) throw(AssertionError("Object `$f` is not invokable as function with signature `$signature`, because it does not have a method with argument type(s) `$arg_ts_string`")) end match_found = false for type in actual_return_ts if type <: return_t #|| return_t == Nothing match_found = true break end end if !match_found throw(AssertionError("Object `$f` is not invokable as function with signature `$signature`, because its return type is not `$return_t`")) end return new(f, return_t, arg_ts) end end export TypedFunction function (instance::TypedFunction)(args...) return Base.convert(instance._return_t, instance._apply([Base.convert(instance._arg_ts[i], args[i]) for i in 1:length(args)]...)) end ####### types.jl abstract type SignalEmitter end export SignalEmitter abstract type Widget <: SignalEmitter end export Widget ####### log.jl const LogDomain = String; export LogDomain const MOUSETRAP_DOMAIN = detail.MOUSETRAP_DOMAIN * ".jl" # no export """ See [`log_debug`](@ref). """ macro log_debug(domain, message) return :(Mousetrap.detail.log_debug($message, $domain)) end log_debug(domain::LogDomain, message::String) = detail.log_debug(message, domain) export @log_debug, log_debug """ See [`log_info`](@ref). """ macro log_info(domain, message) return :(Mousetrap.detail.log_info($message, $domain)) end log_info(domain::LogDomain, message::String) = detail.log_info(message, domain) export @log_info, log_info """ See [`log_warning`](@ref). """ macro log_warning(domain, message) return :(Mousetrap.detail.log_warning($message, $domain)) end log_warning(domain::LogDomain, message::String) = detail.log_warning(message, domain) export @log_warning, log_warning """ See [`log_critical`](@ref). """ macro log_critical(domain, message) return :(Mousetrap.detail.log_critical($message, $domain)) end log_critical(domain::LogDomain, message::String) = detail.log_critical(message, domain) export @log_critical, log_critical """ See [`log_fatal`](@ref). """ macro log_fatal(domain, message) return :(Mousetrap.detail.log_fatal($message, $domain)) end log_fatal(domain::LogDomain, message::String) = detail.log_fatal(message, domain) export @log_fatal, log_fatal set_surpress_debug!(domain::LogDomain, b::Bool) = detail.log_set_surpress_debug(domain, b) export set_surpress_debug! set_surpress_info!(domain::LogDomain, b::Bool) = detail.log_set_surpress_info(domain, b) export set_surpress_info! get_surpress_debug(domain::LogDomain) ::Bool = detail.log_get_surpress_debug(domain) export get_surpress_debug get_surpress_info(domain::LogDomain) ::Bool = detail.log_get_surpress_info(domain) export get_surpress_info set_log_file!(path::String) ::Bool = detail.log_set_file(path) export set_log_file! ####### common.jl macro do_not_compile(args...) return :() end import Base: * *(x::Symbol, y::Symbol) = Symbol(string(x) * string(y)) function from_julia_index(x::Integer) ::UInt64 if x <= 0 throw(AssertionError("Index $x < 1; Indices in Julia are 1-based.")) end return x - 1 end function to_julia_index(x::Integer) ::Int64 return x + 1 end function safe_call(scope::String, f, args...) try return f(args...) catch exception printstyled(stderr, "[ERROR] "; bold = true, color = :red) printstyled(stderr, "In " * scope * ": "; bold = true) Base.showerror(stderr, exception, catch_backtrace()) print(stderr, "\n") throw(exception) # this causes jl_call to return nullptr C-side, as opposed to jl_nothing end end macro export_function(type, name, return_t) return_t = esc(return_t) Mousetrap.eval(:(export $name)) return :($name(x::$type) = Base.convert($return_t, detail.$name(x._internal))) end macro export_function(type, name, return_t, arg1_type, arg1_name) return_t = esc(return_t) if arg1_type isa Expr arg1_origin_type = arg1_type.args[2] arg1_destination_type = arg1_type.args[3] else arg1_origin_type = arg1_type arg1_destination_type = arg1_type end arg1_name = esc(arg1_name) Mousetrap.eval(:(export $name)) return :($name( x::$type, $arg1_name::$arg1_origin_type ) = Base.convert($return_t, detail.$name(x._internal, convert($arg1_destination_type, $arg1_name) ))) end macro export_function(type, name, return_t, arg1_type, arg1_name, arg2_type, arg2_name) return_t = esc(return_t) if arg1_type isa Expr arg1_origin_type = arg1_type.args[2] arg1_destination_type = arg1_type.args[3] else arg1_origin_type = arg1_type arg1_destination_type = arg1_type end arg1_name = esc(arg1_name) if arg2_type isa Expr arg2_origin_type = arg2_type.args[2] arg2_destination_type = arg2_type.args[3] elseif arg2_type isa Symbol arg2_origin_type = arg2_type arg2_destination_type = arg2_type end arg2_name = esc(arg2_name) Mousetrap.eval(:(export $name)) return :($name( x::$type, $arg1_name::$arg1_origin_type, $arg2_name::$arg2_origin_type ) = Base.convert($return_t, detail.$name(x._internal, convert($arg1_destination_type, $arg1_name), convert($arg2_destination_type, $arg2_name) ))) end macro export_function(type, name, return_t, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name) return_t = esc(return_t) if arg1_type isa Expr arg1_origin_type = arg1_type.args[2] arg1_destination_type = arg1_type.args[3] else arg1_origin_type = arg1_type arg1_destination_type = arg1_type end arg1_name = esc(arg1_name) if arg2_type isa Expr arg2_origin_type = arg2_type.args[2] arg2_destination_type = arg2_type.args[3] elseif arg2_type isa Symbol arg2_origin_type = arg2_type arg2_destination_type = arg2_type end arg2_name = esc(arg2_name) if arg3_type isa Expr arg3_origin_type = arg3_type.args[2] arg3_destination_type = arg3_type.args[3] else arg3_origin_type = arg3_type arg3_destination_type = arg3_type end arg3_name = esc(arg3_name) Mousetrap.eval(:(export $name)) return :($name( x::$type, $arg1_name::$arg1_origin_type, $arg2_name::$arg2_origin_type, $arg3_name::$arg3_origin_type ) = Base.convert($return_t, detail.$name(x._internal, convert($arg1_destination_type, $arg1_name), convert($arg2_destination_type, $arg2_name), convert($arg3_destination_type, $arg3_name), ))) end macro export_function(type, name, return_t, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name) return_t = esc(return_t) if arg1_type isa Expr arg1_origin_type = arg1_type.args[2] arg1_destination_type = arg1_type.args[3] else arg1_origin_type = arg1_type arg1_destination_type = arg1_type end arg1_name = esc(arg1_name) if arg2_type isa Expr arg2_origin_type = arg2_type.args[2] arg2_destination_type = arg2_type.args[3] elseif arg2_type isa Symbol arg2_origin_type = arg2_type arg2_destination_type = arg2_type end arg2_name = esc(arg2_name) if arg3_type isa Expr arg3_origin_type = arg3_type.args[2] arg3_destination_type = arg3_type.args[3] else arg3_origin_type = arg3_type arg3_destination_type = arg3_type end arg3_name = esc(arg3_name) if arg4_type isa Expr arg4_origin_type = arg4_type.args[2] arg4_destination_type = arg4_type.args[3] else arg4_origin_type = arg4_type arg4_destination_type = arg4_type end arg4_name = esc(arg4_name) Mousetrap.eval(:(export $name)) return :($name( x::$type, $arg1_name::$arg1_origin_type, $arg2_name::$arg2_origin_type, $arg3_name::$arg3_origin_type, $arg4_name::$arg4_origin_type ) = Base.convert($return_t, detail.$name(x._internal, convert($arg1_destination_type, $arg1_name), convert($arg2_destination_type, $arg2_name), convert($arg3_destination_type, $arg3_name), convert($arg4_destination_type, $arg4_name) ))) end @generated function get_top_level_widget(x) ::Widget return :( throw(AssertionError("Object of type $(typeof(x)) does not fulfill the widget interface. In order for it to be able to be treated as a widget, you need to subtype `Mousetrap.Widget` **and** add a method with signature `(::$(typeof(x))) -> Widget` to `Mousetrap.get_top_level_widget`, which should map an instance of $(typeof(x)) to its top-level widget component.")) ) end export get_top_level_widget @generated function is_native_widget(x::Any) :(return false) end # no export macro declare_native_widget(Type) out = Expr(:block) push!(out.args, esc( :(is_native_widget(::$Type) = return true) )) #= push!(out.args, esc( :(Mousetrap.get_top_level_widget(x::$Type) = return x) )) =# return out end macro export_type(name, super) super = esc(super) internal_name = Symbol("_" * "$name") if !isdefined(detail, :($internal_name)) throw(AssertionError("In Mousetrap.@export_type: detail.$internal_name undefined")) end out = Expr(:toplevel) Mousetrap.eval(:(export $name)) push!(out.args, :( mutable struct $name <: $super _internal::detail.$internal_name end )) return out end macro export_type(name) internal_name = Symbol("_" * "$name") if !isdefined(detail, :($internal_name)) throw(AssertionError("In Mousetrap.@export_type: detail.$internal_name undefined")) end out = Expr(:toplevel) Mousetrap.eval(:(export $name)) push!(out.args, :( struct $name _internal::detail.$internal_name end )) return out end macro export_enum(enum, block) @assert block isa Expr out = Expr(:toplevel) push!(out.args, (:(export $enum))) detail_enum_name = :_ * enum @assert isdefined(detail, detail_enum_name) names = Symbol[] push!(out.args, :(const $(esc(enum)) = Mousetrap.detail.$detail_enum_name)) for name in block.args if !(name isa Symbol) continue end push!(out.args, :(const $(esc(name)) = detail.$name)) push!(out.args, :(export $name)) push!(names, name) end enum_str = string(enum) enum_sym = QuoteNode(enum) to_int_name = Symbol(enum) * :_to_int push!(out.args, :(Base.string(x::$enum) = string(Mousetrap.detail.$to_int_name(x)))) push!(out.args, :(Base.convert(::Type{Integer}, x::$enum) = Integer(Mousetrap.detail.to_int_name(x)))) push!(out.args, :(Base.instances(x::Type{$enum}) = [$(names...)])) push!(out.args, :(Base.show(io::IO, x::Type{$enum}) = print(io, (isdefined(Main, $enum_sym) ? "" : "Mousetrap.") * $enum_str))) push!(out.args, :(Base.show(io::IO, x::$enum) = print(io, string($enum) * "(" * string(convert(Int64, x)) * ")"))) return out end function show_aux(io::IO, x::T, fields::Symbol...) where T out = string(typeof(x)) * "(" for i in 1:length(fields) field = fields[i] get_field = :get_ * field value = eval(:($get_field($x))) if typeof(value) == String out *= "$field = \"$value\"" else out *= "$field = $value" end if i != length(fields) out *= ", " end end out *= ")" print(io, out) end ###### vector.jl mutable struct Vector2{T <: Number} x::T y::T Vector2{T}(all::Number) where T = new{T}(convert(T, all), convert(T, all)) Vector2{T}(x::Number, y::Number) where T = new{T}(convert(T, x), convert(T, y)) end export Vector2 Base.:(+)(a::Vector2{T}, b::Vector2{T}) where T = Vector2{T}(a.x + b.x, a.y + b.y) Base.:(-)(a::Vector2{T}, b::Vector2{T}) where T = Vector2{T}(a.x - b.x, a.y - b.y) Base.:(*)(a::Vector2{T}, b::Vector2{T}) where T = Vector2{T}(a.x * b.x, a.y * b.y) Base.:(/)(a::Vector2{T}, b::Vector2{T}) where T = Vector2{T}(a.x / b.x, a.y / b.y) Base.:(==)(a::Vector2{T}, b::Vector2{T}) where T = a.x == b.x && a.y == b.y Base.:(!=)(a::Vector2{T}, b::Vector2{T}) where T = !(a == b) const Vector2f = Vector2{Cfloat} const Vector2i = Vector2{Cint} const Vector2ui = Vector2{Csize_t} export Vector2f, Vector2i, Vector2ui mutable struct Vector3{T <: Number} x::T y::T z::T Vector3{T}(all::Number) where T = new{T}(convert(T, all), convert(T, all), convert(T, all)) Vector3{T}(x::Number, y::Number, z::Number) where T = new{T}(convert(T, x), convert(T, y), convert(T, z)) end export Vector3 Base.:(+)(a::Vector3{T}, b::Vector3{T}) where T = Vector3{T}(a.x + b.x, a.y + b.y, a.z + b.z) Base.:(-)(a::Vector3{T}, b::Vector3{T}) where T = Vector3{T}(a.x - b.x, a.y - b.y, a.z - b.z) Base.:(*)(a::Vector3{T}, b::Vector3{T}) where T = Vector3{T}(a.x * b.x, a.y * b.y, a.z * b.z) Base.:(/)(a::Vector3{T}, b::Vector3{T}) where T = Vector3{T}(a.x / b.x, a.y / b.y, a.z / b.z) Base.:(==)(a::Vector3{T}, b::Vector3{T}) where T = a.x == b.x && a.y == b.y && a.z == b.z Base.:(!=)(a::Vector3{T}, b::Vector3{T}) where T = !(a == b) const Vector3f = Vector3{Cfloat} const Vector3i = Vector3{Cint} const Vector3ui = Vector3{Csize_t} export Vector3f, Vector3i, Vector3ui mutable struct Vector4{T <: Number} x::T y::T z::T w::T Vector4{T}(all::Number) where T = new{T}(convert(T, all), convert(T, all), convert(T, all), convert(T, all)) Vector4{T}(x::Number, y::Number, z::Number, w::Number) where T = new{T}(convert(T, x), convert(T, y), convert(T, z), convert(T, w)) end export Vector4 Base.:(+)(a::Vector4{T}, b::Vector4{T}) where T = Vector4{T}(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w) Base.:(-)(a::Vector4{T}, b::Vector4{T}) where T = Vector4{T}(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w) Base.:(*)(a::Vector4{T}, b::Vector4{T}) where T = Vector4{T}(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w) Base.:(/)(a::Vector4{T}, b::Vector4{T}) where T = Vector4{T}(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w) Base.:(==)(a::Vector4{T}, b::Vector4{T}) where T = a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w Base.:(!=)(a::Vector4{T}, b::Vector4{T}) where T = !(a == b) const Vector4f = Vector4{Cfloat} const Vector4i = Vector4{Cint} const Vector4ui = Vector4{Csize_t} export Vector4f, Vector4i, Vector4ui Base.show(io::IO, x::Vector2{T}) where T = print(io, "Vector2{" * string(T) * "}(" * string(x.x) * ", " * string(x.y) * ")") Base.show(io::IO, x::Vector3{T}) where T = print(io, "Vector3{" * string(T) * "}(" * string(x.x) * ", " * string(x.y) * ", " * string(x.z) * ")") Base.show(io::IO, x::Vector4{T}) where T = print(io, "Vector4{" * string(T) * "}(" * string(x.x) * ", " * string(x.y) * ", " * string(x.z) * ", " * string(x.w) * ")") ####### time.jl struct Time _ns::Int64 end export Time as_minutes(time::Time) ::Cdouble = detail.ns_to_minutes(time._ns) export as_minutes as_seconds(time::Time) ::Cdouble = detail.ns_to_seconds(time._ns) export as_seconds as_milliseconds(time::Time) ::Cdouble = detail.ns_to_milliseconds(time._ns) export as_milliseconds as_microseconds(time::Time) ::Cdouble = detail.ns_to_microseconds(time._ns) export as_microseconds as_nanoseconds(time::Time) ::Int64 = time._ns export as_nanoseconds minutes(n::Number) = Time(detail.minutes_to_ns(convert(Cdouble, n))) export minutes seconds(n::Number) = Time(detail.seconds_to_ns(convert(Cdouble, n))) export seconds milliseconds(n::Number) = Time(detail.milliseconds_to_ns(convert(Cdouble, n))) export milliseconds microseconds(n::Number) = Time(detail.microseconds_to_ns(convert(Cdouble, n))) export microseconds nanoseconds(n::Integer) = Time(n) export nanoseconds Base.:(+)(a::Time, b::Time) = Time(a._ns + b._ns) Base.:(-)(a::Time, b::Time) = Time(a._ns - b._ns) Base.:(==)(a::Time, b::Time) = a._ns == b._ns Base.:(!=)(a::Time, b::Time) = !(a == b) Base.:(<)(a::Time, b::Time) = a._ns < b._ns Base.:(>)(a::Time, b::Time) = a._ns > b._ns Base.show(io::IO, x::Time) = print(io, "Time($(as_seconds(x))s)") @export_type Clock SignalEmitter Clock() = Clock(detail._Clock()) restart!(clock::Clock) ::Time = microseconds(detail.restart!(clock._internal)) export restart! elapsed(clock::Clock) ::Time = microseconds(detail.elapsed(clock._internal)) export elapsed Base.show(io::IO, x::Clock) = print(io, "Clock($(elapsed(x))s)") ####### angle.jl struct Angle _rads::Cfloat end export Angle degrees(x::Number) = Angle(convert(Cfloat, deg2rad(x))) export degrees radians(x::Number) = Angle(convert(Cfloat, x)) export radians as_degrees(angle::Angle) ::Cdouble = rad2deg(angle._rads) export as_degrees as_radians(angle::Angle) ::Cdouble = angle._rads export as_radians Base.:(+)(a::Angle, b::Angle) = Angle(a._rads + b._rads) Base.:(-)(a::Angle, b::Angle) = Angle(a._rads - b._rads) Base.:(*)(a::Angle, b::Angle) = Angle(a._rads * b._rads) Base.:(/)(a::Angle, b::Angle) = Angle(a._rads / b._rads) Base.:(==)(a::Angle, b::Angle) = a._rads == b._rads Base.:(!=)(a::Angle, b::Angle) = !(a._rads == b._rads) Base.show(io::IO, angle::Angle) = print(io, "Angle($(as_degrees(angle))°)") ####### signal_components.jl macro add_signal(T, snake_case, Return_t) out = Expr(:block) connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T,)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][])) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal(T, snake_case, Return_t, Arg1_t, arg1_name) out = Expr(:block) connect_signal_name = :connect_signal_ * snake_case * :! Arg1_t = esc(Arg1_t) arg1_name = esc(arg1_name) push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2]) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, $arg1_name::$Arg1_t) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, $arg1_name)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal(T, snake_case, Return_t, Arg1_t, arg1_name, Arg2_t, arg2_name) out = Expr(:block) connect_signal_name = :connect_signal_ * snake_case * :! Arg1_t = esc(Arg1_t) Arg2_t = esc(Arg2_t) arg1_name = esc(arg1_name) arg2_name = esc(arg2_name) push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg2_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], x[3]) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg2_t, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], x[3], data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, $arg1_name::$Arg1_t, $arg2_name::$Arg2_t) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, $arg1_name, $arg2_name)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal(T, snake_case, Return_t, Arg1_t, arg1_name, Arg2_t, arg2_name, Arg3_t, arg3_name) out = Expr(:block) connect_signal_name = :connect_signal_ * snake_case * :! Arg1_t = esc(Arg1_t) Arg2_t = esc(Arg2_t) Arg3_t = esc(Arg3_t) arg1_name = esc(arg1_name) arg2_name = esc(arg2_name) arg3_name = esc(arg3_name) push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg2_t, $Arg3_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], x[3], x[4]) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg2_t, $Arg3_t, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], x[3], x[4], data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, $arg1_name::$Arg1_t, $arg2_name::$Arg2_t, $arg3_name::$Arg3_t) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, $arg1_name, $arg2_name, $arg3_name)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal_realize(x) return :(@add_signal $x realize Cvoid) end macro add_signal_unrealize(x) return :(@add_signal $x unrealize Cvoid) end macro add_signal_destroy(x) return :(@add_signal $x destroy Cvoid) end macro add_signal_hide(x) return :(@add_signal $x hide Cvoid) end macro add_signal_show(x) return :(@add_signal $x show Cvoid) end macro add_signal_map(x) return :(@add_signal $x map Cvoid) end macro add_signal_unmap(x) return :(@add_signal $x unmap Cvoid) end macro add_widget_signals(x) return quote @add_signal_realize($x) @add_signal_unrealize($x) @add_signal_destroy($x) @add_signal_hide($x) @add_signal_show($x) @add_signal_map($x) @add_signal_unmap($x) end end macro add_signal_activate(x) return :(@add_signal $x activate Cvoid) end macro add_signal_shutdown(x) return :(@add_signal $x shutdown Cvoid) end macro add_signal_clicked(x) return :(@add_signal $x clicked Cvoid) end macro add_signal_toggled(x) return :(@add_signal $x toggled Cvoid) end macro add_signal_dismissed(x) return :(@add_signal $x dismissed Cvoid) end macro add_signal_button_clicked(x) return :(@add_signal $x button_clicked Cvoid) end macro add_signal_activate_default_widget(x) return :(@add_signal $x activate_default_widget Cvoid) end macro add_signal_activate_focused_widget(x) return :(@add_signal $x activate_focused_widget Cvoid) end macro add_signal_close_request(x) return :(@add_signal $x close_request WindowCloseRequestResult) end macro add_signal_items_changed(x) return :(@add_signal $x items_changed Cvoid Integer position Integer n_removed Integer n_added) end macro add_signal_closed(x) return :(@add_signal $x closed Cvoid) end macro add_signal_text_changed(x) return :(@add_signal $x text_changed Cvoid) end macro add_signal_drag_begin(x) return :(@add_signal $x drag_begin Cvoid AbstractFloat start_x AbstractFloat start_y) end macro add_signal_drag(x) return :(@add_signal $x drag Cvoid AbstractFloat offset_x AbstractFloat offset_y) end macro add_signal_drag_end(x) return :(@add_signal $x drag_end Cvoid AbstractFloat offset_x AbstractFloat offset_y) end macro add_signal_click_pressed(x) return :(@add_signal $x click_pressed Cvoid Integer n_press AbstractFloat x AbstractFloat y) end macro add_signal_click_released(x) return :(@add_signal $x click_released Cvoid Integer n_press AbstractFloat x AbstractFloat y) end macro add_signal_click_stopped(x) return :(@add_signal $x click_stopped Cvoid) end macro add_signal_focus_gained(x) return :(@add_signal $x focus_gained Cvoid) end macro add_signal_focus_lost(x) return :(@add_signal $x focus_lost Cvoid) end macro add_signal_pressed(x) return :(@add_signal $x pressed Cvoid AbstractFloat x AbstractFloat y) end macro add_signal_press_cancelled(x) return :(@add_signal $x press_cancelled Cvoid) end macro add_signal_motion_enter(x) return :(@add_signal $x motion_enter Cvoid AbstractFloat x AbstractFloat y) end macro add_signal_motion(x) return :(@add_signal $x motion Cvoid AbstractFloat x AbstractFloat y) end macro add_signal_motion_leave(x) return :(@add_signal $x motion_leave Cvoid) end macro add_signal_scale_changed(x) return :(@add_signal $x scale_changed Cvoid AbstractFloat scale) end macro add_signal_rotation_changed(x) return :(@add_signal $x rotation_changed Cvoid AbstractFloat angle_absolute_rads AbstractFloat angle_delta_rads) end macro add_signal_kinetic_scroll_decelerate(x) return :(@add_signal $x kinetic_scroll_decelerate Cvoid AbstractFloat x_velocity AbstractFloat y_velocity) end macro add_signal_scroll_begin(x) return :(@add_signal $x scroll_begin Cvoid) end macro add_signal_scroll(x) return :(@add_signal $x scroll Cvoid AbstractFloat delta_x AbstractFloat delta_y) end # sic, jl_unbox_bool(jl_nothing) == true macro add_signal_scroll_end(x) return :(@add_signal $x scroll_end Cvoid) end macro add_signal_stylus_up(x) return :(@add_signal $x stylus_up Cvoid AbstractFloat x AbstractFloat y) end macro add_signal_stylus_down(x) return :(@add_signal $x stylus_down Cvoid AbstractFloat x AbstractFloat y) end macro add_signal_proximity(x) return :(@add_signal $x proximity Cvoid AbstractFloat x AbstractFloat y) end macro add_signal_swipe(x) return :(@add_signal $x swipe Cvoid AbstractFloat x_velocity AbstractFloat y_velocity) end macro add_signal_pan(x) return :(@add_signal $x pan Cvoid PanDirection direction AbstractFloat offset) end macro add_signal_paint(x) return :(@add_signal $x paint Cvoid) end macro add_signal_update(x) return :(@add_signal $x update Cvoid) end macro add_signal_value_changed(x) return :(@add_signal $x value_changed Cvoid) end macro add_signal_properties_changed(x) return :(@add_signal $x properties_changed Cvoid) end macro add_signal_wrapped(x) return :(@add_signal $x wrapped Cvoid) end macro add_signal_scroll_child(x) return :(@add_signal $x scroll_child Cvoid ScrollType type Bool is_horizontal) end macro add_signal_resize(x) return :(@add_signal $x resize Cvoid Integer width Integer height) end macro add_signal_render(x) return :(@add_signal $x render Bool Ptr{Cvoid} gdk_gl_context_ptr) end macro add_signal_modifiers_changed(x) return :(@add_signal $x modifiers_changed Cvoid ModifierState state) end macro add_signal_activate_item(T) snake_case = :activate_item Return_t = Cvoid Arg1_t = Integer arg1_name = :index out = Expr(:block) connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), to_julia_index(x[2])) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), to_julia_index(x[2]), data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, $arg1_name::$Arg1_t) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, from_julia_index($arg1_name))) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal_activated(T) out = Expr(:block) snake_case = :activated Return_t = Cvoid connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T,)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][])) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, nothing)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal_revealed(T) out = Expr(:block) snake_case = :revealed Return_t = Cvoid connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T,)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][])) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, nothing)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal_switched(T) out = Expr(:block) snake_case = :switched Return_t = Cvoid connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T,)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][])) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, nothing)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_signal_selection_changed(T) snake_case = :selection_changed Arg1_t = Int64 arg1_name = :position Arg2_t = Int64 arg2_name = :n_items Return_t = Cvoid out = Expr(:block) connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg2_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), to_julia_index(x[2]), x[3]) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg2_t, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), to_julia_index(x[2]), x[3], data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, $arg1_name::$Arg1_t, $arg2_name::$Arg2_t) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, from_julia_index($arg1_name), $arg2_name)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_key_event_controller_signal(T, name, Return_t) out = Expr(:block) snake_case = name connect_signal_name = :connect_signal_ * snake_case * :! Arg1_t = Cuint Arg2_t = Cuint Arg3_t = detail._ModifierState arg1_name = :key_code arg2_name = :key_value arg3_name = :modifiers push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg3_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], x[4]) return false end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, $Arg1_t, $Arg3_t, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), x[2], x[4], data) return false end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, $arg1_name::$Arg1_t, $arg3_name::$Arg3_t) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, $arg1_name, $arg3_name)) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end macro add_notebook_signal(T, snake_case) out = Expr(:block) Return_t = Cvoid connect_signal_name = :connect_signal_ * snake_case * :! push!(out.args, esc(:( function $connect_signal_name(f, x::$T) typed_f = TypedFunction(f, $Return_t, ($T, Integer)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), to_julia_index(x[3])) end) end ))) push!(out.args, esc(:( function $connect_signal_name(f, x::$T, data::Data_t) where Data_t typed_f = TypedFunction(f, $Return_t, ($T, Integer, Data_t)) detail.$connect_signal_name(x._internal, function(x) typed_f($T(x[1][]), to_julia_index(x[3]), data) end) end ))) emit_signal_name = :emit_signal_ * snake_case push!(out.args, esc(:( function $emit_signal_name(x::$T, page_index::Integer) ::$Return_t return convert($Return_t, detail.$emit_signal_name(x._internal, from_julia_index(page_index))) end ))) disconnect_signal_name = :disconnect_signal_ * snake_case * :! push!(out.args, esc(:( function $disconnect_signal_name(x::$T) detail.$disconnect_signal_name(x._internal) end ))) set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::$T, b) detail.$set_signal_blocked_name(x._internal, b) end ))) get_signal_blocked_name = :get_signal_ * snake_case * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::$T) return detail.$get_signal_blocked_name(x._internal) end ))) push!(out.args, esc(:(export $connect_signal_name))) push!(out.args, esc(:(export $disconnect_signal_name))) push!(out.args, esc(:(export $set_signal_blocked_name))) push!(out.args, esc(:(export $get_signal_blocked_name))) push!(out.args, esc(:(export $emit_signal_name))) return out end ####### theme.jl @export_enum Theme begin THEME_DEFAULT_LIGHT THEME_DEFAULT_DARK THEME_HIGH_CONTRAST_LIGHT THEME_HIGH_CONTRAST_DARK end ####### application.jl @export_type Application SignalEmitter @export_type Action SignalEmitter const ApplicationID = String; export ApplicationID Application(id::String; allow_multiple_instances = false) = Application(detail._Application(id, allow_multiple_instances)) run!(app::Application) ::Cint = Mousetrap.detail.run!(app._internal) export run! @export_function Application quit! Cvoid @export_function Application hold! Cvoid @export_function Application release! Cvoid @export_function Application get_is_holding Bool @export_function Application mark_as_busy! Cvoid @export_function Application unmark_as_busy! Cvoid @export_function Application get_is_marked_as_busy Bool @export_function Application get_id String @export_function Application get_current_theme Theme @export_function Application set_current_theme! Cvoid Theme theme add_action!(app::Application, action::Action) = detail.add_action!(app._internal, action._internal) export add_action! get_action(app::Application, id::String) ::Action = Action(detail.get_action(app._internal, id)) export get_action @export_function Application remove_action! Cvoid String id @export_function Application has_action Bool String id @add_signal_activate Application @add_signal_shutdown Application function main(f, application_id::String = "com.julia.mousetrap") app = Application(application_id) typed_f = TypedFunction(f, Any, (Application,)) connect_signal_activate!(app) do app::Application try typed_f(app) catch(exception) printstyled(stderr, "[ERROR] "; bold = true, color = :red) printstyled(stderr, "In Mousetrap.main: "; bold = true) Base.showerror(stderr, exception, catch_backtrace()) print(stderr, "\n") quit!(app) end return nothing end return run!(app) end export main Base.show(io::IO, x::Application) = show_aux(io, x, :is_holding, :is_marked_as_busy) ####### window.jl @export_enum WindowCloseRequestResult begin WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE WINDOW_CLOSE_REQUEST_RESULT_PREVENT_CLOSE end @export_type Window Widget @declare_native_widget Window Window(app::Application) = Window(detail._Window(app._internal)) function set_application!(window::Window, app::Application) detail.set_application!(window._internal, app._internal) end export set_application! @export_function Window set_maximized! Cvoid Bool b @export_function Window set_fullscreen! Cvoid Bool b @export_function Window set_minimized! Cvoid Bool b @export_function Window present! Cvoid @export_function Window set_hide_on_close! Cvoid Bool b @export_function Window get_hide_on_close Bool @export_function Window close! Cvoid @export_function Window destroy! Cvoid @export_function Window get_is_closed Bool function set_child!(window::Window, child::Widget) detail.set_child!(window._internal, as_widget_pointer(child)) end export set_child! @export_function Window remove_child! Cvoid @export_function Window set_destroy_with_parent! Cvoid Bool n @export_function Window get_destroy_with_parent Bool @export_function Window set_title! Cvoid String title @export_function Window get_title String function get_header_bar(window::Window) ::HeaderBar return HeaderBar(detail.get_header_bar(window._internal)) end export get_header_bar @export_function Window set_is_modal! Cvoid Bool b @export_function Window get_is_modal Bool function set_transient_for!(self::Window, other::Window) detail.set_transient_for!(self._internal, other._internal) end export set_transient_for! @export_function Window set_is_decorated! Cvoid Bool b @export_function Window get_is_decorated Bool @export_function Window set_has_close_button! Cvoid Bool b @export_function Window get_has_close_button Bool @export_function Window set_startup_notification_identifier! Cvoid String id @export_function Window set_focus_visible! Cvoid Bool b @export_function Window get_focus_visible Bool function set_default_widget!(window::Window, widget::Widget) detail.set_default_widget!(window._internal, as_widget_pointer(widget)) end export set_default_widget! @add_widget_signals Window @add_signal_close_request Window @add_signal_activate_default_widget Window @add_signal_activate_focused_widget Window Base.show(io::IO, x::Window) = show_aux(io, x, :title) ####### action.jl const ShortcutTrigger = String export ShortcutTrigger Action(id::String, app::Application) = Action(detail._Action(id, app._internal.cpp_object)) function Action(f, id::String, app::Application) out = Action(id, app) set_function!(f, out) return out end @export_function Action get_id String @export_function Action activate! Cvoid @export_function Action add_shortcut! Cvoid ShortcutTrigger shortcut get_shortcuts(action::Action) ::Vector{String} = detail.get_shortcuts(action._internal)[] export get_shortcuts @export_function Action clear_shortcuts! Cvoid @export_function Action set_enabled! Cvoid Bool b @export_function Action get_enabled Bool function set_function!(f, action::Action, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (Action, Data_t)) detail.set_function!(action._internal, function (internal_ref) typed_f(Action(internal_ref[]), data) end) end function set_function!(f, action::Action) typed_f = TypedFunction(f, Cvoid, (Action,)) detail.set_function!(action._internal, function (internal_ref) typed_f(Action(internal_ref[])) end) end export set_function! @add_signal_activated Action Base.show(io::IO, x::Action) = show_aux(io, x, :id, :enabled) ####### adjustment.jl @export_type Adjustment SignalEmitter function Adjustment(value::Number, lower::Number, upper::Number, increment::Number) return Adjustment(detail._Adjustment( convert(Cfloat, value), convert(Cfloat, lower), convert(Cfloat, upper), convert(Cfloat, increment) )); end Adjustment(internal::Ptr{Cvoid}) = Adjustment(detail._Adjustment(internal)) @export_function Adjustment get_lower Cfloat @export_function Adjustment get_upper Cfloat @export_function Adjustment get_value Cfloat @export_function Adjustment get_step_increment Cfloat @export_function Adjustment set_lower! Cvoid Number => Cfloat value @export_function Adjustment set_upper! Cvoid Number => Cfloat value @export_function Adjustment set_value! Cvoid Number => Cfloat value @export_function Adjustment set_step_increment! Cvoid Number => Cfloat value @add_signal_value_changed Adjustment @add_signal_properties_changed Adjustment Base.show(io::IO, x::Adjustment) = show_aux(io, x, :value, :lower, :upper, :step_increment) ####### alignment.jl @export_enum Alignment begin ALIGNMENT_START ALIGNMENT_CENTER ALIGNMENT_END end ####### orientation.jl @export_enum Orientation begin ORIENTATION_HORIZONTAL ORIENTATION_VERTICAL end ####### cursor_type.jl @export_enum CursorType begin CURSOR_TYPE_NONE CURSOR_TYPE_DEFAULT CURSOR_TYPE_HELP CURSOR_TYPE_POINTER CURSOR_TYPE_CONTEXT_MENU CURSOR_TYPE_PROGRESS CURSOR_TYPE_WAIT CURSOR_TYPE_CELL CURSOR_TYPE_CROSSHAIR CURSOR_TYPE_TEXT CURSOR_TYPE_MOVE CURSOR_TYPE_NOT_ALLOWED CURSOR_TYPE_GRAB CURSOR_TYPE_GRABBING CURSOR_TYPE_ALL_SCROLL CURSOR_TYPE_ZOOM_IN CURSOR_TYPE_ZOOM_OUT CURSOR_TYPE_COLUMN_RESIZE CURSOR_TYPE_ROW_RESIZE CURSOR_TYPE_NORTH_RESIZE CURSOR_TYPE_NORTH_EAST_RESIZE CURSOR_TYPE_EAST_RESIZE CURSOR_TYPE_SOUTH_EAST_RESIZE CURSOR_TYPE_SOUTH_RESIZE CURSOR_TYPE_SOUTH_WEST_RESIZE CURSOR_TYPE_WEST_RESIZE CURSOR_TYPE_NORTH_WEST_RESIZE end const CURSOR_TYPE_HORIZONTAL_RESIZE = CURSOR_TYPE_ROW_RESIZE const CURSOR_TYPE_VERTICAL_RESIZE = CURSOR_TYPE_COLUMN_RESIZE ####### color.jl abstract type Color end mutable struct RGBA <: Color r::Cfloat g::Cfloat b::Cfloat a::Cfloat end export RGBA function RGBA(r::AbstractFloat, g::AbstractFloat, b::AbstractFloat, a::AbstractFloat) return RBGA( convert(Cfloat, r), convert(Cfloat, g), convert(Cfloat, b), convert(Cfloat, a) ) end mutable struct HSVA <: Color h::Cfloat s::Cfloat v::Cfloat a::Cfloat end export HSVA function HSVA(h::AbstractFloat, s::AbstractFloat, v::AbstractFloat, a::AbstractFloat) return HSVA( convert(Cfloat, h), convert(Cfloat, s), convert(Cfloat, v), convert(Cfloat, a) ) end import Base.== ==(x::RGBA, y::RGBA) = x.r == y.r && x.g == y.g && x.b == y.b && x.a == y.a function ==(x::HSVA, y::HSVA) hue_equal = x.h == y.h || abs(x.h - y.h) == 1 return hue_equal && x.s == y.s && x.v == y.v && x.a == y.a end import Base.!= !=(x::RGBA, y::RGBA) = !(x == y) !=(x::HSVA, y::HSVA) = !(x == y) rgba_to_hsva(rgba::RGBA) ::HSVA = detail.rgba_to_hsva(rgba) export rgba_to_hsva hsva_to_rgba(hsva::HSVA) ::RGBA = detail.hsva_to_rgba(hsva) export hsva_to_rgba Base.convert(::Type{HSVA}, rgba::RGBA) = rgba_to_hsva(rbga) Base.convert(::Type{RGBA}, hsva::HSVA) = hsva_to_rgba(hsva) rgba_to_html_code(rgba::RGBA) = convert(String, detail.rgba_to_html_code(rgba)) export rgba_to_html_code html_code_to_rgba(code::String) ::RGBA = detail.html_code_to_rgba(code) export html_code_to_rgba is_valid_html_code(code::String) ::Bool = detail.is_valid_html_code(code) export is_valid_html_code Base.show(io::IO, x::RGBA) = print(io, "RGBA($(x.r), $(x.g), $(x.b), $(x.a))") Base.show(io::IO, x::HSVA) = print(io, "HSVA($(x.h), $(x.s), $(x.v), $(x.a))") ####### icon.jl @export_type IconTheme IconTheme(window::Window) = IconTheme(detail._IconTheme(window._internal)) const IconID = String export IconID @export_type Icon Icon() = Icon(detail._Icon()) function Icon(path::String) out = Icon() create_from_file!(out, path) return out end function Icon(theme::IconTheme, id::IconID, square_resolution::Integer) out = Icon() create_from_theme!(out, theme, id, square_resolution) return out end # Icon @export_function Icon create_from_file! Bool String path function create_from_theme!(icon::Icon, theme::IconTheme, id::IconID, square_resolution::Integer, scale::Integer = 1) ::Bool detail.create_from_theme!(icon._internal, theme._internal.cpp_object, id, UInt64(square_resolution), UInt64(scale)) end export create_from_theme! @export_function Icon get_name IconID import Base.== ==(a::Icon, b::Icon) = detail.compare_icons(a._internal, b_internal) import Base.!= !=(a::Icon, b::Icon) = !(a == b) @export_function Icon get_size Vector2i Base.show(io::IO, x::Icon) = show_aux(io, x, :name) # IconTheme function get_icon_names(theme::IconTheme) ::Vector{String} return detail.get_icon_names(theme._internal) end export get_icon_names has_icon(theme::IconTheme, icon::Icon) = detail.has_icon_icon(theme._internal, icon._internal) has_icon(theme::IconTheme, id::IconID) = detail.has_icon_id(theme._internal, id) export has_icon @export_function IconTheme add_resource_path! Cvoid String path @export_function IconTheme set_resource_path! Cvoid String path Base.show(io::IO, x::IconTheme) = show_aux(io, x) ####### image.jl @export_enum InterpolationType begin INTERPOLATION_TYPE_NEAREST INTERPOLATION_TYPE_TILES INTERPOLATION_TYPE_BILINEAR INTERPOLATION_TYPE_HYPERBOLIC end @export_type Image Image() = Image(detail._Image()) Image(path::String) = Image(detail._Image(path)) Image(width::Integer, height::Integer, color::RGBA = RGBA(0, 0, 0, 1)) = Image(detail._Image(UInt64(width), UInt64(height), color)) function create!(image::Image, width::Integer, height::Integer, color::RGBA = RGBA(0, 0, 0, 1)) detail.create!(image._internal, UInt64(width), UInt64(height), color) end export create! @export_function Image create_from_file! Bool String path @export_function Image save_to_file Bool String path @export_function Image get_n_pixels Int64 @export_function Image get_size Vector2i function as_scaled(image::Image, size_x::Integer, size_y::Integer, interpolation::InterpolationType) return Image(detail.as_scaled(image._internal, UInt64(size_x), UInt64(size_y), interpolation)) end export as_scaled function as_cropped(image::Image, offset_x::Signed, offset_y::Signed, new_width::Integer, new_height::Integer) return Image(detail.as_cropped(image._internal, offset_x, offset_y, UInt64(new_width), UInt64(new_height))) end export as_cropped function as_flipped(image::Image, flip_horizontally::Bool, flip_vertically::Bool) return Image(detail.as_flipped(image._internal, flip_horizontally, flip_vertically)) end export as_flipped function set_pixel!(image::Image, x::Integer, y::Integer, color::RGBA) detail.set_pixel_rgba!(image._internal, from_julia_index(x), from_julia_index(y), color) end function set_pixel!(image::Image, x::Integer, y::Integer, color::HSVA) detail.set_pixel_hsva!(image._internal, from_julia_index(x), from_julia_index(y), color) end export set_pixel! function get_pixel(image::Image, x::Integer, y::Integer) ::RGBA return detail.get_pixel(image._internal, from_julia_index(x), from_julia_index(y)) end export get_pixel Base.show(io::IO, x::Image) = show_aux(io, x, :size) ####### key_file.jl @export_type KeyFile SignalEmitter KeyFile() = KeyFile(detail._KeyFile()) KeyFile(path::String) = KeyFile(detail._KeyFile(path)) const GroupID = String export GroupID const KeyID = String export KeyID @export_function KeyFile as_string String @export_function KeyFile create_from_file! Bool String path @export_function KeyFile create_from_string! Bool String file @export_function KeyFile save_to_file Bool String path @export_function KeyFile get_groups Vector{GroupID} @export_function KeyFile get_keys Vector{KeyID} GroupID group @export_function KeyFile has_key Bool GroupID group KeyID key @export_function KeyFile has_group Bool GroupID group set_comment_above!(file::KeyFile, group::GroupID, key::KeyID, comment::String) = detail.set_comment_above_key!(file._internal, group, key, comment) set_comment_above!(file::KeyFile, group::GroupID, comment::String) = detail.set_comment_above_group!(file._internal, group, comment) export set_comment_above! get_comment_above(file::KeyFile, group::GroupID) ::String = detail.get_comment_above_group(file._internal, group) get_comment_above(file::KeyFile, group::GroupID, key::KeyID) ::String = detail.get_comment_above_key(file._internal, group, key) export get_comment_above export set_value! export get_value function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Bool) detail.set_value_as_bool!(file._internal, group, key, value) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::AbstractFloat) detail.set_value_as_double!(file._internal, group, key, convert(Cdouble, value)) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Signed) detail.set_value_as_int!(file._internal, group, key, convert(Cint, value)) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Unsigned) detail.set_value_as_uint!(file._internal, group, key, convert(Cuint, value)) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::String) detail.set_value_as_string!(file._internal, group, key, value) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::RGBA) detail.set_value_as_rgba!(file._internal, group, key, value) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::HSVA) detail.set_value_as_hsva!(file._internal, group, key, value) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Image) detail.set_value_as_image!(file._internal, group, key, value._internal) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Vector{Bool}) detail.set_value_as_bool_list!(file._internal, group, key, value) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Vector{<: AbstractFloat}) vec::Vector{Cdouble} = [] for x in value push!(vec, convert(Cdouble, x)) end detail.set_value_as_double_list!(file._internal, group, key, vec) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Vector{<: Signed}) vec::Vector{Cint} = [] for x in value push!(vec, convert(Cint, x)) end detail.set_value_as_int_list!(file._internal, group, key, vec) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Vector{<: Unsigned}) vec::Vector{Cuint} = [] for x in value push!(vec, convert(Cuint, x)) end detail.set_value_as_uint_list!(file._internal, group, key, vec) end function set_value!(file::KeyFile, group::GroupID, key::KeyID, value::Vector{String}) detail.set_value_as_string_list!(file._internal, group, key, value) end ## function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Bool}) return detail.get_value_as_bool(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{<: AbstractFloat}) return detail.get_value_as_double(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{<: Signed}) return detail.get_value_as_int(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{<: Unsigned}) return detail.get_value_as_uint(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{String}) return detail.get_value_as_string(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{RGBA}) return detail.get_value_as_rgba(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{HSVA}) return detail.get_value_as_hsva(file._internal, group, key) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Image}) return Image(detail.get_value_as_image(file._internal, group, key)) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Vector{Bool}}) return convert(Vector{Bool}, detail.get_value_as_bool_list(file._internal, group, key)) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Vector{T}}) where T <: AbstractFloat return convert(Vector{T}, detail.get_value_as_double_list(file._internal, group, key)) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Vector{T}}) where T <: Signed return convert(Vector{T}, detail.get_value_as_int_list(file._internal, group, key)) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Vector{T}}) where T <: Unsigned return convert(Vector{T}, detail.get_value_as_uint_list(file._internal, group, key)) end function get_value(file::KeyFile, group::GroupID, key::KeyID, ::Type{Vector{String}}) return convert(Vector{String}, detail.get_value_as_string_list(file._internal, group, key)) end Base.show(io::IO, x::KeyFile) = show_aux(io, x, :groups) ####### file_descriptor.jl @export_type FileMonitor SignalEmitter @export_type FileDescriptor SignalEmitter # Monitor @export_enum FileMonitorEvent begin FILE_MONITOR_EVENT_CHANGED FILE_MONITOR_EVENT_CHANGES_DONE_HINT FILE_MONITOR_EVENT_DELETED FILE_MONITOR_EVENT_CREATED FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED FILE_MONITOR_EVENT_RENAMED FILE_MONITOR_EVENT_MOVED_IN FILE_MONITOR_EVENT_MOVED_OUT end @export_function FileMonitor cancel! Cvoid @export_function FileMonitor is_cancelled Bool function on_file_changed!(f, monitor::FileMonitor, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (FileMonitor, FileMonitorEvent, FileDescriptor, FileDescriptor, Data_t)) detail.on_file_changed!(monitor._internal, function(monitor_ref, event, self_descriptor_internal::Ptr{Cvoid}, other_descriptor_internal::Ptr{Cvoid}) typed_f(FileMonitor(monitor_ref[]), event, FileDescriptor(self_descriptor_internal), FileDescriptor(other_descriptor_internal), data) end) end function on_file_changed!(f, monitor::FileMonitor) typed_f = TypedFunction(f, Cvoid, (FileMonitor, FileMonitorEvent, FileDescriptor, FileDescriptor)) detail.on_file_changed!(monitor._internal, function(monitor_ref, event, self_descriptor_internal::Ptr{Cvoid}, other_descriptor_internal::Ptr{Cvoid}) typed_f(FileMonitor(monitor_ref[]), event, FileDescriptor(self_descriptor_internal), FileDescriptor(other_descriptor_internal)) end) end export on_file_changed! Base.show(io::IO, x::FileMonitor) = print(io, "FileMonitor(cancelled = $(is_cancelled(x)))") # Descriptor FileDescriptor(internal::Ptr{Cvoid}) = FileDescriptor(detail._FileDescriptor(internal)) FileDescriptor(path::String) = FileDescriptor(detail._FileDescriptor(path)) import Base.== ==(a::FileDescriptor, b::FileDescriptor) = detail.file_descriptor_equal(a._internal, b._internal) import Base.!= !=(a::FileDescriptor, b::FileDescriptor) = !(a == b) @export_function FileDescriptor create_from_path! Bool String path @export_function FileDescriptor create_from_uri! Bool String uri @export_function FileDescriptor get_name String @export_function FileDescriptor get_path String @export_function FileDescriptor get_uri String get_path_relative_to(self::FileDescriptor, other::FileDescriptor) = detail.get_path_relative_to(self._internal, other._internal) export get_path_relative_to get_parent(self::FileDescriptor) = FileDescriptor(detail.get_parent(self._internal)) export get_parent @export_function FileDescriptor get_file_extension String @export_function FileDescriptor exists Bool @export_function FileDescriptor is_folder Bool @export_function FileDescriptor is_file Bool @export_function FileDescriptor is_symlink Bool read_symlink(self::FileDescriptor) = FileDescriptor(detail.read_symlink(self._internal)) export read_symlink @export_function FileDescriptor is_executable Bool @export_function FileDescriptor get_content_type String @export_function FileDescriptor query_info String String info_id create_monitor(descriptor::FileDescriptor) ::FileMonitor = FileMonitor(detail._FileMonitor(detail.create_monitor(descriptor._internal))) export create_monitor function get_children(descriptor::FileDescriptor; recursive::Bool = false) ::Vector{FileDescriptor} children::Vector{Ptr{Cvoid}} = detail.get_children(descriptor._internal, recursive) return FileDescriptor[FileDescriptor(ptr) for ptr in children] end export get_children # File System create_file_at!(destination::FileDescriptor; replace::Bool = false) ::Bool = detail.create_file_at!(destination._internal, replace) export create_file_at! create_directory_at!(destination::FileDescriptor) ::Bool = detail.create_directory_at!(destination._internal) export create_directory_at! delete_at!(file::FileDescriptor) ::Bool = detail.delete_at!(file._internal) export delete_at! function copy!(from::FileDescriptor, to::FileDescriptor, allow_overwrite::Bool; make_backup::Bool = false, follow_symlink::Bool = false) ::Bool detail.copy!(from._internal, to._internal, allow_overwrite, make_backup, follow_symlink) end export copy! function move!(from::FileDescriptor, to::FileDescriptor, allow_overwrite::Bool; make_backup::Bool = false, follow_symlink::Bool = false) ::Bool detail.move!(from._internal, to._internal, allow_overwrite, make_backup, follow_symlink) end export move! move_to_trash!(file::FileDescriptor) ::Bool = detail.move_to_trash!(file._internal) export move_to_trash! open_file(file::FileDescriptor) ::Cvoid = detail.open_file(file._internal) export open_file show_in_file_explorer(file::FileDescriptor) ::Cvoid = detail.show_in_file_explorer(file._internal) export show_in_file_explorer open_url(file::FileDescriptor) ::Cvoid = detail.open_url(file._internal) export open_url Base.show(io::IO, x::FileDescriptor) = show_aux(io, x, :path) ####### file_chooser.jl @export_type FileFilter SignalEmitter FileFilter(name::String) = FileFilter(detail._FileFilter(name)) get_name(filter::FileFilter) ::String = detail.get_name(filter._internal) export get_name @export_function FileFilter add_allowed_pattern! Cvoid String pattern @export_function FileFilter add_allow_all_supported_image_formats! Cvoid @export_function FileFilter add_allowed_suffix! Cvoid String suffix @export_function FileFilter add_allowed_mime_type! Cvoid String mime_type_id @export_enum FileChooserAction begin FILE_CHOOSER_ACTION_OPEN_FILE FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES FILE_CHOOSER_ACTION_SAVE FILE_CHOOSER_ACTION_SELECT_FOLDER FILE_CHOOSER_ACTION_SELECT_MULTIPLE_FOLDERS end @export_type FileChooser SignalEmitter FileChooser(action::FileChooserAction = FILE_CHOOSER_ACTION_OPEN_FILE, title::String = "") = FileChooser(detail._FileChooser(action, title)) @export_function FileChooser set_accept_label! Cvoid String label @export_function FileChooser get_accept_label String @export_function FileChooser present! Cvoid @export_function FileChooser cancel! Cvoid @export_function FileChooser set_is_modal! Cvoid Bool modal @export_function FileChooser get_is_modal Bool @export_function FileChooser set_title! Cvoid String title @export_function FileChooser get_title String function on_accept!(f, chooser::FileChooser, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (FileChooser, Vector{FileDescriptor}, Data_t)) detail.on_accept!(chooser._internal, function(file_chooser_ref, descriptor_ptrs::Vector{Ptr{Cvoid}}) descriptors = FileDescriptor[FileDescriptor(ptr) for ptr in descriptor_ptrs] typed_f(FileChooser(file_chooser_ref[]), descriptors, data) end) end function on_accept!(f, chooser::FileChooser) typed_f = TypedFunction(f, Cvoid, (FileChooser, Vector{FileDescriptor})) detail.on_accept!(chooser._internal, function(file_chooser_ref, descriptor_ptrs::Vector{Ptr{Cvoid}}) descriptors = FileDescriptor[FileDescriptor(ptr) for ptr in descriptor_ptrs] typed_f(FileChooser(file_chooser_ref[]), descriptors) end) end export on_accept! function on_cancel!(f, chooser::FileChooser, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (FileChooser, Data_t)) detail.on_cancel!(chooser._internal, function(file_chooser_ref) typed_f(FileChooser(file_chooser_ref[]), data) end) end function on_cancel!(f, chooser::FileChooser) typed_f = TypedFunction(f, Cvoid, (FileChooser,)) detail.on_cancel!(chooser._internal, function(file_chooser_ref) typed_f(FileChooser(file_chooser_ref[])) end) end export on_cancel! @export_function FileChooser set_file_chooser_action! Cvoid FileChooserAction action @export_function FileChooser get_file_chooser_action FileChooserAction add_filter!(chooser::FileChooser, filter::FileFilter) = detail.add_filter!(chooser._internal, filter._internal) export add_filter! clear_filters!(chooser::FileChooser) = detail.clear_filters!(chooser._internal) export clear_filters! set_initial_filter!(chooser::FileChooser, filter::FileFilter) = detail.set_initial_filter!(chooser._internal, filter._internal) export set_initial_filter! set_initial_file!(chooser::FileChooser, file::FileDescriptor) = detail.set_initial_file!(chooser._internal, file._internal) export set_initial_file! set_initial_folder!(chooser::FileChooser, folder::FileDescriptor) = detail.set_initial_file!(chooser._internal, folder._internal) export set_initial_folder! set_initial_name!(chooser::FileChooser, name::String) = detail.set_initial_name!(chooser._internal, name) export set_initial_name! Base.show(io::IO, x::FileChooser) = show_aux(io, x) ####### alert_dialog.jl @export_type AlertDialog SignalEmitter AlertDialog(message::String, detailed_message::String = "") = AlertDialog(detail._AlertDialog(message, detailed_message)) function add_button!(alert_dialog::AlertDialog, label::String) ::Integer return to_julia_index(detail.add_button!(alert_dialog._internal, label)) end export add_button! function set_default_button!(alert_dialog::AlertDialog, id::Integer) detail.set_default_button!(alert_dialog.AlertDialog, from_julia_index(id)) end export set_default_button! function set_button_label!(alert_dialog::AlertDialog, id::Integer, label::String) detail.set_button_label!(alert_dialog._internal, from_julia_index(id), label) end export set_button_label! function get_button_label(alert_dialog::AlertDialog, id::Integer) ::String return detail.get_button_label(alert_dialog._internal, from_julia_index(id)) end export get_button_label function set_extra_widget!(alert_dialog::AlertDialog, widget::Widget) detail.set_extra_widget!(alert_dialog._internal, as_widget_pointer(widget)) end export set_extra_widget! @export_function AlertDialog remove_extra_widget! Cvoid @export_function AlertDialog get_n_buttons Integer @export_function AlertDialog get_message String @export_function AlertDialog set_message! Cvoid String message @export_function AlertDialog get_detailed_description String @export_function AlertDialog set_detailed_description! Cvoid String message @export_function AlertDialog set_is_modal! Cvoid Bool b @export_function AlertDialog get_is_modal Bool @export_function AlertDialog present! Cvoid @export_function AlertDialog close! Cvoid function on_selection!(f, dialog::AlertDialog, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (AlertDialog, Integer, Data_t)) detail.on_selection!(dialog._internal, function(dialog_ref, index) typed_f(AlertDialog(dialog_ref[]), convert(UInt32, to_julia_index(index)), data) end) end function on_selection!(f, dialog::AlertDialog) typed_f = TypedFunction(f, Cvoid, (AlertDialog, Signed)) detail.on_selection!(dialog._internal, function(dialog_ref, index) typed_f(AlertDialog(dialog_ref[]), convert(UInt32, to_julia_index(index))) end) end export on_selection! Base.show(io::IO, x::AlertDialog) = show_aux(io, x) ####### popup_message.jl @export_type PopupMessage SignalEmitter PopupMessage(title::String) = PopupMessage(detail._PopupMessage(title, "")) PopupMessage(title::String, button_label::String) = PopupMessage(detail._PopupMessage(title, button_label)) @export_function PopupMessage set_title! Cvoid String title @export_function PopupMessage get_title String @export_function PopupMessage set_button_label! Cvoid String title @export_function PopupMessage get_button_label String set_button_action!(popup_message::PopupMessage, action::Action) = detail.set_button_action!(popup_message._internal, action._internal) export set_button_action! @export_function PopupMessage get_button_action_id String @export_function PopupMessage set_is_high_priority! Cvoid Bool b @export_function PopupMessage get_is_high_priority Bool set_timeout!(popup_message::PopupMessage, duration::Time) = detail.set_timeout!(popup_message._internal, convert(Cfloat, as_microseconds(duration))) export set_timeout! get_timeout(popup_message::PopupMessage) ::Time = microseconds(detail.get_timeout(popup_message._internal)) export get_timeout @add_signal_dismissed PopupMessage @add_signal_button_clicked PopupMessage Base.show(io::IO, x::PopupMessage) = show_aux(io, x, :title, :button_label, :is_high_priority, :timeout) ####### popup_message_overlay.jl @export_type PopupMessageOverlay Widget @declare_native_widget PopupMessageOverlay PopupMessageOverlay() = PopupMessageOverlay(detail._PopupMessageOverlay()) function set_child!(overlay::PopupMessageOverlay, child::Widget) detail.set_child!(overlay._internal, as_widget_pointer(child)) end export set_child! @export_function PopupMessageOverlay remove_child! Cvoid function show_message!(overlay::PopupMessageOverlay, popup_message::PopupMessage) detail.show_message!(overlay._internal, popup_message._internal) end export show_message! @add_widget_signals PopupMessageOverlay Base.show(io::IO, x::PopupMessageOverlay) = show_aux(io, x) ####### color_chooser.jl if detail.GTK_MINOR_VERSION >= 10 @export_type ColorChooser SignalEmitter ColorChooser(title::String = "") = ColorChooser(detail._ColorChooser(title)) get_color(color_chooser::ColorChooser) ::RGBA = detail.get_color(color_chooser._internal) export get_color present!(color_chooser::ColorChooser) = detail.present!(color_chooser._internal) export present! @export_function ColorChooser set_is_modal! Cvoid Bool b @export_function ColorChooser get_is_modal Bool @export_function ColorChooser set_title! Cvoid String title @export_function ColorChooser get_title String function on_accept!(f, chooser::ColorChooser, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (ColorChooser, RGBA, Data_t)) detail.on_accept!(chooser._internal, function(color_chooser_ref, rgba::RGBA) typed_f(ColorChooser(color_chooser_ref[]), rgba, data) end) end function on_accept!(f, chooser::ColorChooser) typed_f = TypedFunction(f, Cvoid, (ColorChooser, RGBA)) detail.on_accept!(chooser._internal, function(color_chooser_ref, rgba::RGBA) typed_f(ColorChooser(color_chooser_ref[]), rgba) end) end export on_accept! function on_cancel!(f, chooser::ColorChooser, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (ColorChooser, Data_t)) detail.on_cancel!(chooser._internal, function(color_chooser_ref) typed_f(ColorChooser(color_chooser_ref[]), data) end) end function on_cancel!(f, chooser::ColorChooser) typed_f = TypedFunction(f, Cvoid, (ColorChooser,)) detail.on_cancel!(chooser._internal, function(color_chooser_ref) typed_f(ColorChooser(color_chooser_ref[])) end) end export on_cancel! Base.show(io::IO, x::ColorChooser) = show_aux(io, x, :color) end # GTK_MINOR_VERSION >= 10 ####### image_display.jl @export_type ImageDisplay Widget @declare_native_widget ImageDisplay ImageDisplay() = ImageDisplay(detail._ImageDisplay()) ImageDisplay(path::String) = ImageDisplay(detail._ImageDisplay(path)) ImageDisplay(image::Image) = ImageDisplay(detail._ImageDisplay(image._internal)) ImageDisplay(icon::Icon) = ImageDisplay(detail._ImageDisplay(icon._internal)) @export_function ImageDisplay create_from_file! Bool String path create_from_image!(image_display::ImageDisplay, image::Image) = detail.create_from_image!(image_display._internal, image._internal) export create_from_image! create_from_icon!(image_display::ImageDisplay, icon::Icon) = detail.create_from_icon!(image_display._internal, icon._internal) export create_from_icon! create_as_file_preview!(image_display::ImageDisplay, file::FileDescriptor) ::Bool = detail.create_as_file_preview!(image_display._internal, file._internal) export create_as_file_preview! @export_function ImageDisplay clear! Cvoid @export_function ImageDisplay set_scale! Cvoid Integer scale @export_function ImageDisplay get_scale Int64 @export_function ImageDisplay get_size Vector2i @add_widget_signals ImageDisplay Base.show(io::IO, x::ImageDisplay) = show_aux(io, x, :size) ####### aspect_frame.jl @export_type AspectFrame Widget @declare_native_widget AspectFrame function AspectFrame(ratio::Number; child_x_alignment::AbstractFloat = 0.5, child_y_alignment::AbstractFloat = 0.5) return AspectFrame(detail._AspectFrame(convert(Cfloat, ratio), convert(Cfloat, child_x_alignment), convert(Cfloat, child_y_alignment))) end function AspectFrame(ratio::Number, child::Widget) out = AspectFrame(ratio) set_child!(out, child) return out end @export_function AspectFrame set_ratio! Cvoid AbstractFloat => Cfloat ratio @export_function AspectFrame get_ratio Cfloat @export_function AspectFrame set_child_x_alignment! Cvoid AbstractFloat => Cfloat alignment @export_function AspectFrame set_child_y_alignment! Cvoid AbstractFloat => Cfloat alignment @export_function AspectFrame get_child_x_alignment Cfloat @export_function AspectFrame get_child_y_alignment Cfloat @export_function AspectFrame remove_child! Cvoid set_child!(aspect_frame::AspectFrame, child::Widget) = detail.set_child!(aspect_frame._internal, as_widget_pointer(child)) export set_child! Base.show(io::IO, x::AspectFrame) = show_aux(io, x, :ratio) ####### clamp_frame.jl @export_type ClampFrame Widget @declare_native_widget ClampFrame function ClampFrame(size::Number, orientation::Orientation = ORIENTATION_HORIZONTAL) out = ClampFrame(detail._ClampFrame(orientation)) set_maximum_size!(out, size) return out end @export_function ClampFrame set_orientation! Cvoid Orientation orientation @export_function ClampFrame get_orientation Orientation @export_function ClampFrame set_maximum_size! Cvoid Number => Cfloat px @export_function ClampFrame get_maximum_size Cfloat @export_function ClampFrame remove_child! Cvoid set_child!(clamp_frame::ClampFrame, child::Widget) = detail.set_child!(clamp_frame._internal, as_widget_pointer(child)) export set_child! Base.show(io::IO, x::ClampFrame) = show_aux(io, x, :maximum_size) ####### box.jl @export_type Box Widget @declare_native_widget Box Box(orientation::Orientation) = Box(detail._Box(orientation)) function hbox(widgets::Widget...) out = Box(ORIENTATION_HORIZONTAL) for widget in widgets push_back!(out, widget) end return out end export hbox function vbox(widgets::Widget...) out = Box(ORIENTATION_VERTICAL) for widget in widgets push_back!(out, widget) end return out end export vbox function push_back!(box::Box, widget::Widget) detail.push_back!(box._internal, as_widget_pointer(widget)) end export push_back! function push_front!(box::Box, widget::Widget) detail.push_front!(box._internal, as_widget_pointer(widget)) end export push_front! function insert_after!(box::Box, to_append::Widget, after::Widget) detail.insert_after!(box._internal, as_widget_pointer(to_append), as_widget_pointer(after)) end export insert_after! function remove!(box::Box, widget::Widget) detail.remove!(box._internal, as_widget_pointer(widget)) end export remove! @export_function Box clear! Cvoid @export_function Box set_homogeneous! Cvoid Bool b @export_function Box get_homogeneous Bool function set_spacing!(box::Box, spacing::Number) detail.set_spacing!(box._internal, convert(Cfloat, spacing)) end export set_spacing! @export_function Box get_spacing Cfloat @export_function Box get_n_items Cint @export_function Box get_orientation Orientation @export_function Box set_orientation! Cvoid Orientation orientation @add_widget_signals Box Base.show(io::IO, x::Box) = show_aux(io, x, :n_items) ####### flow_box.jl @export_type FlowBox Widget @declare_native_widget FlowBox FlowBox(orientation::Orientation) = FlowBox(detail._FlowBox(orientation)) function push_back!(box::FlowBox, widget::Widget) detail.push_back!(box._internal, as_widget_pointer(widget)) end export push_back! function push_front!(box::FlowBox, widget::Widget) detail.push_front!(box._internal, as_widget_pointer(widget)) end export push_front! insert_at!(box::FlowBox, index::Integer, widget::Widget) = detail.insert!(box._internal, from_julia_index(index), as_widget_pointer(widget)) export insert_at! function remove!(box::FlowBox, widget::Widget) detail.remove!(box._internal, as_widget_pointer(widget)) end export remove! @export_function FlowBox clear! Cvoid @export_function FlowBox set_homogeneous! Cvoid Bool b @export_function FlowBox get_homogeneous Bool function set_row_spacing!(box::FlowBox, spacing::Number) detail.set_row_spacing!(box._internal, convert(Cfloat, spacing)) end export set_row_spacing! @export_function FlowBox get_row_spacing Cfloat function set_column_spacing!(box::FlowBox, spacing::Number) detail.set_column_spacing!(box._internal, convert(Cfloat, spacing)) end export set_column_spacing! @export_function FlowBox get_column_spacing Cfloat @export_function FlowBox get_n_items Cint @export_function FlowBox get_orientation Orientation @export_function FlowBox set_orientation! Cvoid Orientation orientation @add_widget_signals FlowBox Base.show(io::IO, x::FlowBox) = show_aux(io, x, :n_items) ####### button.jl @export_type Button Widget @declare_native_widget Button Button() = Button(detail._Button()) function Button(label::Widget) out = Button() set_child!(out, label) return out end function Button(icon::Icon) out = Button() set_icon!(out, icon) return out end @export_function Button set_has_frame! Cvoid Bool b @export_function Button get_has_frame Bool @export_function Button set_is_circular! Cvoid Bool b @export_function Button get_is_circular Bool function set_child!(button::Button, child::Widget) detail.set_child!(button._internal, as_widget_pointer(child)) end export set_child! function set_icon!(button::Button, icon::Icon) detail.set_icon!(button._internal, icon._internal) end export set_icon! @export_function Button remove_child! Cvoid function set_action!(button::Button, action::Action) detail.set_action!(button._internal, action._internal) end export set_action! @add_widget_signals Button @add_signal_clicked Button Base.show(io::IO, x::Button) = show_aux(io, x) ####### center_box.jl @export_type CenterBox Widget @declare_native_widget CenterBox CenterBox(orientation::Orientation) = CenterBox(detail._CenterBox(orientation)) function CenterBox(orientation::Orientation, left::Widget, center::Widget, right::Widget) ::CenterBox out = CenterBox(orientation) set_start_child!(out, left) set_center_child!(out, center) set_end_child!(out, right) return out end function set_start_child!(center_box::CenterBox, child::Widget) detail.set_start_child!(center_box._internal, as_widget_pointer(child)) end export set_start_child! function set_center_child!(center_box::CenterBox, child::Widget) detail.set_center_child!(center_box._internal, as_widget_pointer(child)) end export set_center_child! function set_end_child!(center_box::CenterBox, child::Widget) detail.set_end_child!(center_box._internal, as_widget_pointer(child)) end export set_end_child! @export_function CenterBox remove_start_child! Cvoid @export_function CenterBox remove_center_child! Cvoid @export_function CenterBox remove_end_child! Cvoid @export_function CenterBox get_orientation Orientation @export_function CenterBox set_orientation! Cvoid Orientation orientation @add_widget_signals CenterBox Base.show(io::IO, x::CenterBox) = show_aux(io, x) ####### check_button.jl @export_enum CheckButtonState begin CHECK_BUTTON_STATE_ACTIVE CHECK_BUTTON_STATE_INCONSISTENT CHECK_BUTTON_STATE_INACTIVE end @export_type CheckButton Widget @declare_native_widget CheckButton CheckButton() = CheckButton(detail._CheckButton()) @export_function CheckButton set_state! Cvoid CheckButtonState state @export_function CheckButton get_state CheckButtonState @export_function CheckButton get_is_active Bool set_is_active!(button::CheckButton, b::Bool) = set_state!(button, b ? CHECK_BUTTON_STATE_ACTIVE : CHECK_BUTTON_STATE_INACTIVE) export set_is_active! if detail.GTK_MINOR_VERSION >= 8 function set_child!(check_button::CheckButton, child::Widget) detail.set_child!(check_button._internal, as_widget_pointer(child)) end export set_child! @export_function CheckButton remove_child! Cvoid end @add_widget_signals CheckButton @add_signal_toggled CheckButton Base.show(io::IO, x::CheckButton) = show_aux(io, x, :state) ####### switch.jl @export_type Switch Widget @declare_native_widget Switch Switch() = Switch(detail._Switch()) @export_function Switch get_is_active Bool @export_function Switch set_is_active! Cvoid Bool b @add_widget_signals Switch @add_signal_switched Switch Base.show(io::IO, x::Switch) = show_aux(io, x, :is_active) ####### toggle_button.jl @export_type ToggleButton Widget @declare_native_widget ToggleButton ToggleButton() = ToggleButton(detail._ToggleButton()) function ToggleButton(label::Widget) out = ToggleButton() set_child!(out, label) return out end function ToggleButton(icon::Icon) out = ToggleButton() set_icon!(out, icon) return out end @export_function ToggleButton set_is_active! Cvoid Bool b @export_function ToggleButton get_is_active Bool @export_function ToggleButton set_is_circular! Cvoid Bool b @export_function ToggleButton get_is_circular Bool function set_child!(toggle_button::ToggleButton, child::Widget) detail.set_child!(toggle_button._internal, as_widget_pointer(child)) end export set_child! function set_icon!(toggle_button::ToggleButton, icon::Icon) detail.set_icon!(toggle_button._internal, icon._internal) end export set_icon! @export_function ToggleButton remove_child! Cvoid @add_widget_signals ToggleButton @add_signal_clicked ToggleButton @add_signal_toggled ToggleButton Base.show(io::IO, x::ToggleButton) = show_aux(io, x, :is_active) ####### viewport.jl @export_enum ScrollbarVisibilityPolicy begin SCROLLBAR_VISIBILITY_POLICY_NEVER SCROLLBAR_VISIBILITY_POLICY_ALWAYS SCROLLBAR_VISIBILITY_POLICY_AUTOMATIC end @export_enum CornerPlacement begin CORNER_PLACEMENT_TOP_LEFT CORNER_PLACEMENT_TOP_RIGHT CORNER_PLACEMENT_BOTTOM_LEFT CORNER_PLACEMENT_BOTTOM_RIGHT end @export_type Viewport Widget @declare_native_widget Viewport Viewport() = Viewport(detail._Viewport()) function Viewport(child::Widget) ::Viewport out = Viewport() set_child!(out, child) return out end @export_function Viewport set_propagate_natural_height! Cvoid Bool b @export_function Viewport get_propagate_natural_height Bool @export_function Viewport set_propagate_natural_width! Cvoid Bool b @export_function Viewport get_propagate_natural_width Bool @export_function Viewport set_horizontal_scrollbar_policy! Cvoid ScrollbarVisibilityPolicy policy @export_function Viewport set_vertical_scrollbar_policy! Cvoid ScrollbarVisibilityPolicy policy @export_function Viewport get_horizontal_scrollbar_policy ScrollbarVisibilityPolicy @export_function Viewport get_vertical_scrollbar_policy ScrollbarVisibilityPolicy @export_function Viewport set_scrollbar_placement! Cvoid CornerPlacement placement @export_function Viewport get_scrollbar_placement CornerPlacement @export_function Viewport set_has_frame! Cvoid Bool b @export_function Viewport get_has_frame Bool @export_function Viewport set_kinetic_scrolling_enabled! Cvoid Bool b @export_function Viewport get_kinetic_scrolling_enabled Bool get_horizontal_adjustment(viewport::Viewport) ::Adjustment = Adjustment(detail.get_horizontal_adjustment(viewport._internal)) export get_horizontal_adjustment get_vertical_adjustment(viewport::Viewport) ::Adjustment = Adjustment(detail.get_vertical_adjustment(viewport._internal)) export get_vertical_adjustment set_child!(viewport::Viewport, child::Widget) = detail.set_child!(viewport._internal, as_widget_pointer(child)) export set_child! @export_function Viewport remove_child! Cvoid @export_enum ScrollType begin SCROLL_TYPE_NONE SCROLL_TYPE_JUMP SCROLL_TYPE_STEP_BACKWARD SCROLL_TYPE_STEP_FORWARD SCROLL_TYPE_STEP_UP SCROLL_TYPE_STEP_DOWN SCROLL_TYPE_STEP_LEFT SCROLL_TYPE_STEP_RIGHT SCROLL_TYPE_PAGE_BACKWARD SCROLL_TYPE_PAGE_FORWARD SCROLL_TYPE_PAGE_UP SCROLL_TYPE_PAGE_DOWN SCROLL_TYPE_PAGE_LEFT SCROLL_TYPE_PAGE_RIGHT SCROLL_TYPE_SCROLL_START SCROLL_TYPE_SCROLL_END end @add_widget_signals Viewport @add_signal_scroll_child Viewport Base.show(io::IO, x::Viewport) = show_aux(io, x, :propagate_natural_height, :propagate_natural_width ) ####### entry.jl @export_type Entry Widget @declare_native_widget Entry Entry() = Entry(detail._Entry()) @export_function Entry get_text String @export_function Entry set_text! Cvoid String text @export_function Entry set_max_width_chars! Cvoid Integer n @export_function Entry get_max_width_chars Signed @export_function Entry set_has_frame! Cvoid Bool b @export_function Entry get_has_frame Bool @export_function Entry set_text_visible! Cvoid Bool b @export_function Entry get_text_visible Bool function set_primary_icon!(entry::Entry, icon::Icon) detail.set_primary_icon!(entry._internal, icon._internal) end export set_primary_icon! @export_function Entry remove_primary_icon! Cvoid function set_secondary_icon!(entry::Entry, icon::Icon) detail.set_secondary_icon!(entry._internal, icon._internal) end export set_secondary_icon! @export_function Entry remove_secondary_icon! Cvoid @add_widget_signals Entry @add_signal_text_changed Entry @add_signal_activate Entry Base.show(io::IO, x::Entry) = show_aux(io, x, :text) ####### expander.jl @export_type Expander Widget @declare_native_widget Expander Expander() = Expander(detail._Expander()) function Expander(child::Widget, label::Widget) ::Expander out = Expander() set_child!(out, child) set_label_widget!(out, label) return out end function set_child!(expander::Expander, child::Widget) detail.set_child!(expander._internal, as_widget_pointer(child)) end export set_child! @export_function Expander remove_child! Cvoid function set_label_widget!(expander::Expander, child::Widget) detail.set_label_widget!(expander._internal, as_widget_pointer(child)) end export set_label_widget! @export_function Expander remove_label_widget! Cvoid @export_function Expander set_is_expanded! Cvoid Bool b @export_function Expander get_is_expanded Bool @add_widget_signals Expander @add_signal_activate Expander Base.show(io::IO, x::Expander) = show_aux(io, x, :is_expanded) ####### fixed.jl @export_type Fixed Widget @declare_native_widget Fixed Fixed() = Fixed(detail._Fixed()) add_child!(fixed::Fixed, child::Widget, position::Vector2f) = detail.add_child!(fixed._internal, as_widget_pointer(child), position) export add_child! remove_child!(fixed::Fixed, child::Widget) = detail.remove_child!(fixed._internal, as_widget_pointer(child)) export remove_child! set_child_position!(fixed::Fixed, child::Widget, position::Vector2f) = detail.set_child_position!(fixed._internal, as_widget_pointer(child), position) export set_child_position! @add_widget_signals Fixed Base.show(io::IO, x::Fixed) = show_aux(io, x) ####### level_bar.jl @export_enum LevelBarMode begin LEVEL_BAR_MODE_CONTINUOUS LEVEL_BAR_MODE_DISCRETE end @export_type LevelBar Widget @declare_native_widget LevelBar LevelBar(min::Number, max::Number) = LevelBar(detail._LevelBar(convert(Cfloat, min), convert(Cfloat, max))) @export_function LevelBar add_marker! Cvoid String name Number => Cfloat value @export_function LevelBar remove_marker! Cvoid String name @export_function LevelBar set_inverted! Cvoid Bool b @export_function LevelBar get_inverted Bool @export_function LevelBar set_mode! Cvoid LevelBarMode mode @export_function LevelBar get_mode LevelBarMode @export_function LevelBar set_min_value! Cvoid Number => Cfloat value @export_function LevelBar get_min_value Cfloat @export_function LevelBar set_max_value! Cvoid Number => Cfloat value @export_function LevelBar get_max_value Cfloat @export_function LevelBar set_value! Cvoid Number => Cfloat value @export_function LevelBar get_value Cfloat @export_function LevelBar set_orientation! Cvoid Orientation orientation @export_function LevelBar get_orientation Orientation @add_widget_signals LevelBar Base.show(io::IO, x::LevelBar) = show_aux(io, x, :orientation, :value, :min_value, :max_value) ####### label.jl @export_enum JustifyMode begin JUSTIFY_MODE_LEFT JUSTIFY_MODE_RIGHT JUSTIFY_MODE_CENTER JUSTIFY_MODE_FILL end @export_enum EllipsizeMode begin ELLIPSIZE_MODE_NONE ELLIPSIZE_MODE_START ELLIPSIZE_MODE_MIDDLE ELLIPSIZE_MODE_END end @export_enum LabelWrapMode begin LABEL_WRAP_MODE_NONE LABEL_WRAP_MODE_ONLY_ON_WORD LABEL_WRAP_MODE_ONLY_ON_CHAR LABEL_WRAP_MODE_WORD_OR_CHAR end @export_type Label Widget @declare_native_widget Label Label() = Label(detail._Label()) Label(formatted_string::String) = Label(detail._Label(formatted_string)) @export_function Label set_text! Cvoid String text @export_function Label get_text String @export_function Label set_use_markup! Cvoid Bool b @export_function Label get_use_markup Bool @export_function Label set_ellipsize_mode! Cvoid EllipsizeMode mode @export_function Label get_ellipsize_mode EllipsizeMode @export_function Label set_wrap_mode! Cvoid LabelWrapMode mode @export_function Label get_wrap_mode LabelWrapMode @export_function Label set_justify_mode! Cvoid JustifyMode mode @export_function Label get_justify_mode JustifyMode @export_function Label set_max_width_chars! Cvoid Integer n @export_function Label get_max_width_chars Int64 @export_function Label set_x_alignment! Cvoid AbstractFloat => Cfloat x @export_function Label get_x_alignment Cfloat @export_function Label set_y_alignment! Cvoid AbstractFloat => Cfloat x @export_function Label get_y_alignment Cfloat @export_function Label set_is_selectable! Cvoid Bool b @export_function Label get_is_selectable Bool @add_widget_signals Label Base.show(io::IO, x::Label) = show_aux(io, x, :text, :ellipsize_mode, :wrap_mode, :justify_mode ) ####### text_view.jl @export_type TextView Widget @declare_native_widget TextView TextView() = TextView(detail._TextView()) @export_function TextView get_text String @export_function TextView set_text! Cvoid String text @export_function TextView set_cursor_visible! Cvoid Bool b @export_function TextView get_cursor_visible Bool @export_function TextView undo! Cvoid @export_function TextView redo! Cvoid @export_function TextView set_was_modified! Cvoid Bool b @export_function TextView get_was_modified Bool @export_function TextView set_editable! Cvoid Bool b @export_function TextView get_editable Bool @export_function TextView set_justify_mode! Cvoid JustifyMode mode @export_function TextView get_justify_mode JustifyMode @export_function TextView set_left_margin! Cvoid Number => Cfloat margin @export_function TextView get_left_margin Cfloat @export_function TextView set_right_margin! Cvoid Number => Cfloat margin @export_function TextView get_right_margin Cfloat @export_function TextView set_top_margin! Cvoid Number => Cfloat margin @export_function TextView get_top_margin Cfloat @export_function TextView set_bottom_margin! Cvoid Number => Cfloat margin @export_function TextView get_bottom_margin Cfloat @add_widget_signals TextView @add_signal_text_changed TextView Base.show(io::IO, x::TextView) = show_aux(io, x, :text, :was_modified) ####### frame.jl @export_type Frame Widget @declare_native_widget Frame Frame() = Frame(detail._Frame()) function Frame(child::Widget) ::Frame out = Frame() set_child!(out, child) return out end set_child!(frame::Frame, child::Widget) = detail.set_child!(frame._internal, as_widget_pointer(child)) export set_child! set_label_widget!(frame::Frame, label::Widget) = detail.set_label_widget!(frame._internal, as_widget_pointer(label)) export set_label_widget! @export_function Frame remove_child! Cvoid @export_function Frame remove_label_widget! Cvoid @export_function Frame set_label_x_alignment! Cvoid AbstractFloat => Cfloat x @export_function Frame get_label_x_alignment Cfloat @add_widget_signals Frame Base.show(io::IO, x::Frame) = show_aux(io, x) ####### overlay.jl @export_type Overlay Widget @declare_native_widget Overlay Overlay() = Overlay(detail._Overlay()) function Overlay(base::Widget, overlays::Widget...) ::Overlay out = Overlay() set_child!(out, base) for overlay in overlays add_overlay!(out, overlay) end return out end set_child!(overlay::Overlay, child::Widget) = detail.set_child!(overlay._internal, as_widget_pointer(child)) export set_child! remove_child!(overlay::Overlay) = detail.remove_child!(overlay._internal) export remove_child! function add_overlay!(overlay::Overlay, child::Widget; include_in_measurement::Bool = true, clip::Bool = false) detail.add_overlay!(overlay._internal, as_widget_pointer(child), include_in_measurement, clip) end export add_overlay! remove_overlay!(overlay::Overlay, child::Widget) = detail.remove_overlay!(overlay._internal, as_widget_pointer(child)) export remove_overlay! @add_widget_signals Overlay Base.show(io::IO, x::Overlay) = show_aux(io, x) ####### relative_position.jl @export_enum RelativePosition begin RELATIVE_POSITION_ABOVE RELATIVE_POSITION_BELOW RELATIVE_POSITION_LEFT_OF RELATIVE_POSITION_RIGHT_OF end ####### menu_model.jl @export_type MenuModel SignalEmitter MenuModel() = MenuModel(detail._MenuModel()) add_action!(model::MenuModel, label::String, action::Action) = detail.add_action!(model._internal, label, action._internal) export add_action! add_widget!(model::MenuModel, widget::Widget) = detail.add_widget!(model._internal, as_widget_pointer(widget)) export add_widget! @export_enum SectionFormat begin SECTION_FORMAT_NORMAL SECTION_FORMAT_HORIZONTAL_BUTTONS SECTION_FORMAT_HORIZONTAL_BUTTONS_LEFT_TO_RIGHT SECTION_FORMAT_HORIZONTAL_BUTTONS_RIGHT_TO_LEFT SECTION_FORMAT_CIRCULAR_BUTTONS SECTION_FORMAT_INLINE_BUTTONS end function add_section!(model::MenuModel, title::String, to_add::MenuModel, section_format::SectionFormat = SECTION_FORMAT_NORMAL) detail.add_section!(model._internal, title, to_add._internal, section_format) end export add_section! add_submenu!(model::MenuModel, label::String, to_add::MenuModel) = detail.add_submenu!(model._internal, label, to_add._internal) export add_submenu! add_icon!(model::MenuModel, icon::Icon, action::Action) = detail.add_icon!(model._internal, icon._internal, action._internal) export add_icon! @add_signal_items_changed MenuModel Base.show(io::IO, x::MenuModel) = show_aux(io, x) ###### menubar.jl @export_type MenuBar Widget @declare_native_widget MenuBar MenuBar(model::MenuModel) = MenuBar(detail._MenuBar(model._internal)) @add_widget_signals MenuBar Base.show(io::IO, x::MenuBar) = show_aux(io, x) ####### popover_menu.jl @export_type PopoverMenu Widget @declare_native_widget PopoverMenu PopoverMenu(model::MenuModel) = PopoverMenu(detail._PopoverMenu(model._internal)) @add_widget_signals PopoverMenu @add_signal_closed PopoverMenu Base.show(io::IO, x::PopoverMenu) = show_aux(io, x) ###### popover.jl @export_type Popover Widget @declare_native_widget Popover Popover() = Popover(detail._Popover()) @export_function Popover popup! Cvoid @export_function Popover popdown! Cvoid function set_child!(popover::Popover, child::Widget) detail.set_child!(popover._internal, as_widget_pointer(child)) end export set_child! @export_function Popover remove_child! Cvoid @export_function Popover set_relative_position! Cvoid RelativePosition position @export_function Popover get_relative_position RelativePosition @export_function Popover set_autohide! Cvoid Bool b @export_function Popover get_autohide Bool @export_function Popover set_has_base_arrow! Cvoid Bool b @export_function Popover get_has_base_arrow Bool @add_widget_signals Popover @add_signal_closed Popover Base.show(io::IO, x::Popover) = show_aux(io, x) ###### popover_button.jl @export_type PopoverButton Widget @declare_native_widget PopoverButton PopoverButton(popover::Popover) = PopoverButton(detail._PopoverButton(popover._internal)) PopoverButton(popover_menu::PopoverMenu) = PopoverButton(detail._PopoverButton(popover_menu._internal)) set_child!(popover_button::PopoverButton, child::Widget) = detail.set_child!(popover_button._internal, as_widget_pointer(child)) export set_child! set_icon!(popover_button::PopoverButton, icon::Icon) = detail.set_icon!(popover_button._internal, icon._internal) export set_icon! @export_function PopoverButton remove_child! Cvoid function set_popover!(popover_button::PopoverButton, popover::Popover) detail.set_popover!(popover_button._internal, popover._internal) end export set_popover! function set_popover_menu!(popover_button::PopoverButton, popover_menu::PopoverMenu) detail.set_popover_menu!(popover_button._internal, popover_menu._internal) end export set_popover_menu! @export_function PopoverButton remove_popover! Cvoid @export_function PopoverButton set_relative_position! Cvoid RelativePosition position @export_function PopoverButton get_relative_position RelativePosition @export_function PopoverButton set_always_show_arrow! Cvoid Bool b @export_function PopoverButton get_always_show_arrow Bool @export_function PopoverButton set_has_frame! Cvoid Bool b @export_function PopoverButton get_has_frame Bool @export_function PopoverButton popup! Cvoid @export_function PopoverButton popdown! Cvoid @export_function PopoverButton set_is_circular! Cvoid Bool b @export_function PopoverButton get_is_circular Bool @add_widget_signals PopoverButton @add_signal_activate PopoverButton Base.show(io::IO, x::PopoverButton) = show_aux(io, x) ###### drop_down.jl @export_type DropDown Widget @declare_native_widget DropDown DropDown() = DropDown(detail._DropDown()) const DropDownItemID = UInt64 export DropDownItemID @export_function DropDown remove! Cvoid DropDownItemID id @export_function DropDown set_always_show_arrow! Cvoid Bool b @export_function DropDown get_always_show_arrow Bool @export_function DropDown set_selected! Cvoid DropDownItemID id @export_function DropDown get_selected DropDownItemID get_item_at(drop_down::DropDown, i::Integer) = detail.get_item_at(drop_down._internal, from_julia_index(i)) export get_item_at function push_back!(f, drop_down::DropDown, list_widget::Widget, label_widget::Widget, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (DropDown, Data_t)) return detail.push_back!(drop_down._internal, as_widget_pointer(list_widget), as_widget_pointer(label_widget), function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[]), data) end) end function push_back!(f, drop_down::DropDown, list_widget::Widget, label_widget::Widget) typed_f = TypedFunction(f, Cvoid, (DropDown,)) return detail.push_back!(drop_down._internal, as_widget_pointer(list_widget), as_widget_pointer(label_widget), function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[])) end) end function push_back!(f, drop_down::DropDown, label::String, data::Data_t) where Data_t return detail.push_back!(drop_down._internal, detail._Label(label).cpp_object, detail._Label(label).cpp_object, function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[]), data) end) end function push_back!(f, drop_down::DropDown, label::String) typed_f = TypedFunction(f, Cvoid, (DropDown,)) return detail.push_back!(drop_down._internal, detail._Label(label).cpp_object, detail._Label(label).cpp_object, function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[])) end) end push_back!(drop_down::DropDown, list_widget::Widget, label_widget::Widget) = push_back!((_::DropDown) -> nothing, drop_down, list_widget, label_widget) push_back!(drop_down::DropDown, label::String) = push_back!((_::DropDown) -> nothing, drop_down, label) export push_back! function push_front!(f, drop_down::DropDown, list_widget::Widget, label_widget::Widget, data::Data_t) where Data_t return detail.push_front!(drop_down._internal, as_widget_pointer(list_widget), as_widget_pointer(label_widget), function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[]), data) end) end function push_front!(f, drop_down::DropDown, list_widget::Widget, label_widget::Widget) typed_f = TypedFunction(f, Cvoid, (DropDown,)) return detail.push_front!(drop_down._internal, as_widget_pointer(list_widget), as_widget_pointer(label_widget), function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[])) end) end function push_front!(f, drop_down::DropDown, label::String, data::Data_t) where Data_t return detail.push_front!(drop_down._internal, detail._Label(label).cpp_object, detail._Label(label).cpp_object, function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[]), data) end) end function push_front!(f, drop_down::DropDown, label::String) typed_f = TypedFunction(f, Cvoid, (DropDown,)) return detail.push_front!(drop_down._internal, detail._Label(label).cpp_object, detail._Label(label).cpp_object, function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[])) end) end push_front!(drop_down::DropDown, list_widget::Widget, label_widget::Widget) = push_front!((_::DropDown) -> nothing, drop_down, list_widget, label_widget) push_front!(drop_down::DropDown, label::String) = push_front!((_::DropDown) -> nothing, drop_down, label) export push_front! function insert_at!(f, drop_down::DropDown, index::Integer, list_widget::Widget, label_widget::Widget, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (DropDown, Data_t)) return detail.insert!(drop_down._internal, from_julia_index(index), as_widget_pointer(list_widget), as_widget_pointer(label_widget), function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[]), data) end) end function insert_at!(f, drop_down::DropDown, index::Integer, list_widget::Widget, label_widget::Widget) typed_f = TypedFunction(f, Cvoid, (DropDown,)) return detail.insert!(drop_down._internal, from_julia_index(index), as_widget_pointer(list_widget), as_widget_pointer(label_widget), function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[])) end) end function insert_at!(f, drop_down::DropDown, index::Integer, label::String, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (DropDown, Data_t)) return detail.insert!(drop_down._internal, from_julia_index(index), detail._Label(label).cpp_object, detail._Label(label).cpp_object, function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[]), data) end) end function insert_at!(f, drop_down::DropDown, index::Integer, label::String) typed_f = TypedFunction(f, Cvoid, (DropDown,)) return detail.insert!(drop_down._internal, from_julia_index(index), detail._Label(label).cpp_object, detail._Label(label).cpp_object, function (drop_down_internal_ref) typed_f(DropDown(drop_down_internal_ref[])) end) end insert_at!(drop_down::DropDown, index::Integer, list_widget::Widget, label_widget::Widget) = insert_at!((_::DropDown) -> nothing, drop_down, index, list_widget, label_widget) insert_at!(drop_down::DropDown, index::Integer, label::String) = insert_at!((_::DropDown) -> nothing, drop_down, index, label) export insert_at! @add_widget_signals DropDown Base.show(io::IO, x::DropDown) = show_aux(io, x, :selected) ###### event_controller.jl abstract type EventController <: SignalEmitter end export EventController abstract type SingleClickGesture <: EventController end export SingleClickGesture @export_enum PropagationPhase begin PROPAGATION_PHASE_NONE PROPAGATION_PHASE_CAPTURE PROPAGATION_PHASE_BUBBLE PROPAGATION_PHASE_TARGET end set_propagation_phase!(controller::EventController, phase::PropagationPhase) = detail.set_propagation_phase!(controller._internal.cpp_object, phase) export set_propagation_phase! get_propagation_phase(controller::EventController) ::PropagationPhase = detail.get_propagation_phase(controller._internal.cpp_object) export get_propagation_phase @export_enum ButtonID begin BUTTON_ID_NONE BUTTON_ID_ANY BUTTON_ID_BUTTON_01 BUTTON_ID_BUTTON_02 BUTTON_ID_BUTTON_03 BUTTON_ID_BUTTON_04 BUTTON_ID_BUTTON_05 BUTTON_ID_BUTTON_06 BUTTON_ID_BUTTON_07 BUTTON_ID_BUTTON_08 BUTTON_ID_BUTTON_09 end get_current_button(gesture::SingleClickGesture) ::ButtonID = detail.get_current_button(gesture._internal.cpp_object) export get_current_button set_only_listens_to_button!(gesture::SingleClickGesture, button::ButtonID) = detail.set_only_listens_to_button!(gesture._internal.cpp_object, button) export set_only_listens_to_button! get_only_listens_to_button(gesture::SingleClickGesture) = detail.get_only_listens_to_button(gesture._internal.cpp_object) export get_only_listens_to_button set_touch_only!(gesture::SingleClickGesture, b::Bool) = detail.set_touch_only!(gesture._internal.cpp_object, b) export set_touch_only! get_touch_only(gesture::SingleClickGesture) = detail.get_touch_only(gesture._internal.cpp_object) export get_touch_only ###### drag_event_controller.jl @export_type DragEventController SingleClickGesture DragEventController() = DragEventController(detail._DragEventController()) get_start_position(controller::DragEventController) ::Vector2f = detail.get_start_position(controller._internal) export get_start_position get_current_offset(controller::DragEventController) ::Vector2f = detail.get_current_offset(controller._internal) export get_current_offset @add_signal_drag_begin DragEventController @add_signal_drag DragEventController @add_signal_drag_end DragEventController Base.show(io::IO, x::DragEventController) = show_aux(io, x) ###### click_event_controller.jl @export_type ClickEventController SingleClickGesture ClickEventController() = ClickEventController(detail._ClickEventController()) @add_signal_click_pressed ClickEventController @add_signal_click_released ClickEventController @add_signal_click_stopped ClickEventController Base.show(io::IO, x::ClickEventController) = show_aux(io, x) ###### focus_event_controller.jl @export_type FocusEventController EventController FocusEventController() = FocusEventController(detail._FocusEventController()) @export_function FocusEventController self_or_child_is_focused Bool @export_function FocusEventController self_is_focused Bool @add_signal_focus_gained FocusEventController @add_signal_focus_lost FocusEventController Base.show(io::IO, x::FocusEventController) = show_aux(io, x) ###### key_event_controller.jl const KeyCode = Cuint export KeyCode const ModifierState = detail._ModifierState export ModifierState @export_type KeyEventController EventController KeyEventController() = KeyEventController(detail._KeyEventController()) @export_function KeyEventController should_shortcut_trigger_trigger Bool String trigger @add_key_event_controller_signal KeyEventController key_pressed Cvoid @add_key_event_controller_signal KeyEventController key_released Cvoid @add_signal_modifiers_changed KeyEventController shift_pressed(modifier_state::ModifierState) ::Bool = detail.shift_pressed(modifier_state); export shift_pressed control_pressed(modifier_state::ModifierState) ::Bool = detail.control_pressed(modifier_state); export control_pressed alt_pressed(modifier_state::ModifierState) ::Bool = detail.alt_pressed(modifier_state); export alt_pressed mouse_button_01_pressed(modifier_state::ModifierState) ::Bool = detail.mouse_button_01_pressed(modifier_state); export mouse_button_01_pressed mouse_button_02_pressed(modifier_state::ModifierState) ::Bool = detail.mouse_button_02_pressed(modifier_state); export mouse_button_02_pressed Base.show(io::IO, x::KeyEventController) = show_aux(io, x) ###### long_press_event_controller.jl @export_type LongPressEventController SingleClickGesture LongPressEventController() = LongPressEventController(detail._LongPressEventController()) @export_function LongPressEventController set_delay_factor! Cvoid Number => Cfloat factor @export_function LongPressEventController get_delay_factor Cfloat @add_signal_pressed LongPressEventController @add_signal_press_cancelled LongPressEventController Base.show(io::IO, x::LongPressEventController) = show_aux(io, x) ###### motion_event_controller.jl @export_type MotionEventController EventController MotionEventController() = MotionEventController(detail._MotionEventController()) @add_signal_motion_enter MotionEventController @add_signal_motion MotionEventController @add_signal_motion_leave MotionEventController Base.show(io::IO, x::MotionEventController) = show_aux(io, x) ###### pinch_zoom_event_controller.jl @export_type PinchZoomEventController EventController PinchZoomEventController() = PinchZoomEventController(detail._PinchZoomEventController()) @export_function PinchZoomEventController get_scale_delta Cfloat @add_signal_scale_changed PinchZoomEventController Base.show(io::IO, x::PinchZoomEventController) = show_aux(io, x) ###### rotate_event_controller.jl @export_type RotateEventController EventController RotateEventController() = RotateEventController(detail._RotateEventController()) get_angle_delta(controller::RotateEventController) ::Angle = radians(detail.get_angle_delta(controller._internal)) export get_angle_delta @add_signal_rotation_changed RotateEventController Base.show(io::IO, x::RotateEventController) = show_aux(io, x) ###### scroll_event_controller.jl @export_type ScrollEventController EventController ScrollEventController(kinetic_scrolling_enabled::Bool = false) = ScrollEventController(detail._ScrollEventController(kinetic_scrolling_enabled)) @export_function ScrollEventController get_kinetic_scrolling_enabled Bool @export_function ScrollEventController set_kinetic_scrolling_enabled! Cvoid Bool b @add_signal_kinetic_scroll_decelerate ScrollEventController @add_signal_scroll_begin ScrollEventController @add_signal_scroll ScrollEventController @add_signal_scroll_end ScrollEventController Base.show(io::IO, x::ScrollEventController) = show_aux(io, x) ###### shortcut_event_controller.jl @export_type ShortcutEventController EventController ShortcutEventController() = ShortcutEventController(detail._ShortcutEventController()) add_action!(shortcut_controller::ShortcutEventController, action::Action) = detail.add_action!(shortcut_controller._internal, action._internal) export add_action! remove_action!(shortcut_controller::ShortcutEventController, action::Action) = detail.remove_action!(shortcut_controller._internal, action._internal) export remove_action! @export_enum ShortcutScope begin SHORTCUT_SCOPE_LOCAL SHORTCUT_SCOPE_GLOBAL #SHORTCUT_SCOPE_MANAGED end set_scope!(controller::ShortcutEventController, scope::ShortcutScope) = detail.set_scope!(controller._internal, scope) export set_scope! get_scope(controller::ShortcutEventController) ::ShortcutScope = detail.get_scope(controller._internal) export get_scope Base.show(io::IO, x::ShortcutEventController) = show_aux(io, x, :scope) ###### stylus_event_controller.jl @export_enum ToolType begin TOOL_TYPE_UNKNOWN TOOL_TYPE_PEN TOOL_TYPE_ERASER TOOL_TYPE_BRUSH TOOL_TYPE_PENCIL TOOL_TYPE_AIRBRUSH TOOL_TYPE_LENS TOOL_TYPE_MOUSE end @export_enum DeviceAxis begin DEVICE_AXIS_X DEVICE_AXIS_Y DEVICE_AXIS_DELTA_X DEVICE_AXIS_DELTA_Y DEVICE_AXIS_PRESSURE DEVICE_AXIS_X_TILT DEVICE_AXIS_Y_TILT DEVICE_AXIS_WHEEL DEVICE_AXIS_DISTANCE DEVICE_AXIS_ROTATION DEVICE_AXIS_SLIDER end device_axis_to_string(axis::DeviceAxis) ::String = detail.device_axis_to_string(axis) export device_axis_to_string @export_type StylusEventController SingleClickGesture StylusEventController() = StylusEventController(detail._StylusEventController()) @export_function StylusEventController get_hardware_id Csize_t @export_function StylusEventController get_tool_type ToolType @export_function StylusEventController has_axis Bool DeviceAxis axis @export_function StylusEventController get_axis_value Float64 DeviceAxis axis @add_signal_stylus_up StylusEventController @add_signal_stylus_down StylusEventController @add_signal_proximity StylusEventController @add_signal_motion StylusEventController Base.show(io::IO, x::StylusEventController) = show_aux(io, x, :hardware_id) ###### swipe_event_controller.jl @export_type SwipeEventController SingleClickGesture SwipeEventController() = SwipeEventController(detail._SwipeEventController()) get_velocity(swipe_controller::SwipeEventController) ::Vector2f = detail.get_velocity(swipe_controller._internal) export get_velocity @add_signal_swipe SwipeEventController Base.show(io::IO, x::SwipeEventController) = show_aux(io, x) ###### pan_event_controller.jl @export_enum PanDirection begin PAN_DIRECTION_LEFT PAN_DIRECTION_RIGHT PAN_DIRECTION_UP PAN_DIRECTION_DOWN end @export_type PanEventController SingleClickGesture PanEventController(orientation::Orientation) = PanEventController(detail._PanEventController(orientation)) set_orientation!(pan_controller::PanEventController, orientation::Orientation) = detail.set_orientation!(pan_controller._internal, orientation) export set_orientation! get_orientation(pan_controller::PanEventController) ::Orientation = detail.get_orientation(pan_controller._internal) export get_orientation @add_signal_pan PanEventController Base.show(io::IO, x::PanEventController) = show_aux(io, x, :orientation) ###### selection_model.jl @export_enum SelectionMode begin SELECTION_MODE_NONE SELECTION_MODE_SINGLE SELECTION_MODE_MULTIPLE end @export_type SelectionModel SignalEmitter SelectionModel(internal::Ptr{Cvoid}) = SelectionModel(detail._SelectionModel(internal)) function get_selection(model::SelectionModel) ::Vector{Int64} selection = detail.get_selection(model._internal) return Int64[to_julia_index(x) for x in selection] end export get_selection @export_function SelectionModel select_all! Cvoid @export_function SelectionModel unselect_all! Cvoid @export_function SelectionModel get_n_items Int64 @export_function SelectionModel get_selection_mode SelectionMode select!(model::SelectionModel, i::Integer, unselect_others::Bool = true) = detail.select!(model._internal, from_julia_index(i), unselect_others) export select! unselect!(model::SelectionModel, i::Integer) = detail.unselect!(model._internal, from_julia_index(i)) export unselect! @add_signal_selection_changed SelectionModel Base.show(io::IO, x::SelectionModel) = show_aux(io, x, :selection_mode) ###### list_view.jl @export_type ListView Widget @declare_native_widget ListView ListView(orientation::Orientation, selection_mode::SelectionMode = SELECTION_MODE_NONE) = ListView(detail._ListView(orientation, selection_mode)) struct ListViewIterator _internal::Ptr{Cvoid} end export ListViewIterator push_back!(list_view::ListView, widget::Widget) = ListViewIterator(detail.push_back!(list_view._internal, as_widget_pointer(widget), Ptr{Cvoid}())) push_back!(list_view::ListView, widget::Widget, iterator::ListViewIterator) = ListViewIterator(detail.push_back!(list_view._internal, as_widget_pointer(widget), iterator._internal)) export push_back! push_front!(list_view::ListView, widget::Widget) = ListViewIterator(detail.push_front!(list_view._internal, as_widget_pointer(widget), Ptr{Cvoid}())) push_front!(list_view::ListView, widget::Widget, iterator::ListViewIterator) = ListViewIterator(detail.push_front!(list_view._internal, as_widget_pointer(widget), iterator._internal)) export push_front! insert_at!(list_view::ListView, index::Integer, widget::Widget) = ListViewIterator(detail.insert!(list_view._internal, from_julia_index(index), as_widget_pointer(widget), Ptr{Cvoid}())) insert_at!(list_view::ListView, index::Integer, widget::Widget, iterator::ListViewIterator) = ListViewIterator(detail.insert!(list_view._internal, from_julia_index(index), as_widget_pointer(widget), iterator._internal)) export insert_at! remove!(list_view::ListView, index::Integer) = detail.remove!(list_view._internal, from_julia_index(index), Ptr{Cvoid}()) remove!(list_view::ListView, index::Integer, iterator::ListViewIterator) = detail.remove!(list_view._internal, from_julia_index(index), iterator._internal) export remove! clear!(list_view::ListView) = detail.clear!(list_view._internal,Ptr{Cvoid}()) clear!(list_view::ListView, iterator::ListViewIterator) = detail.clear!(list_view._internal, iterator._internal) export clear! set_widget_at!(list_view::ListView, index::Integer, widget::Widget) = detail.set_widget_at!(list_view._internal, from_julia_index(index), as_widget_pointer(widget), Ptr{Cvoid}()) set_widget_at!(list_view::ListView, index::Integer, widget::Widget, iterator::ListViewIterator) = detail.set_widget_at!(list_view._internal, from_julia_index(index), as_widget_pointer(widget), iterator._internal) export set_widget_at! function find(list_view::ListView, widget::Widget, iterator::ListViewIterator) ::Signed i = detail.find(list_view._internal, as_widget_pointer(widget), iterator._internal) return i == -1 ? -1 : to_julia_index(i) end function find(list_view::ListView, widget::Widget) ::Signed i = detail.find(list_view._internal, as_widget_pointer(widget), Ptr{Cvoid}()) return i == -1 ? -1 : to_julia_index(i) end export find get_selection_model(list_view::ListView) ::SelectionModel = SelectionModel(detail.get_selection_model(list_view._internal)) export get_selection_model @export_function ListView set_enable_rubberband_selection! Cvoid Bool b @export_function ListView get_enable_rubberband_selection Bool @export_function ListView set_show_separators! Cvoid Bool b @export_function ListView get_show_separators Bool @export_function ListView set_single_click_activate! Cvoid Bool b @export_function ListView get_single_click_activate Bool @export_function ListView get_n_items Int64 @export_function ListView set_orientation! Cvoid Orientation orientation @export_function ListView get_orientation Orientation @add_widget_signals ListView @add_signal_activate_item ListView Base.show(io::IO, x::ListView) = show_aux(io, x, :selection_model, :orientation) ###### grid_view.jl @export_type GridView Widget @declare_native_widget GridView GridView(orientation::Orientation = ORIENTATION_VERTICAL, selection_mode::SelectionMode = SELECTION_MODE_NONE) = GridView(detail._GridView(orientation, selection_mode)) GridView(selection_mode::SelectionMode) = GridView(ORIENTATION_VERTICAL, selection_mode) push_back!(grid_view::GridView, widget::Widget) = detail.push_back!(grid_view._internal, as_widget_pointer(widget)) export push_back! push_front!(grid_view::GridView, widget::Widget) = detail.push_front!(grid_view._internal, as_widget_pointer(widget)) export push_front! insert_at!(grid_view::GridView, index::Integer, widget::Widget) = detail.insert!(grid_view._internal, from_julia_index(index), as_widget_pointer(widget)) export insert_at! remove!(grid_view::GridView, index::Integer) = detail.remove!(grid_view._internal, from_julia_index(index)) export remove! clear!(grid_view::GridView) = detail.clear!(grid_view._internal) export clear! function find(grid_view::GridView, widget::Widget) ::Signed i = detail.find(grid_view._internal, as_widget_pointer(widget)) return i == -1 ? -1 : to_julia_index(i) end export find @export_function GridView get_n_items Int64 @export_function GridView set_enable_rubberband_selection! Cvoid Bool b @export_function GridView get_enable_rubberband_selection Bool @export_function GridView get_single_click_activate Bool @export_function GridView set_single_click_activate! Cvoid Bool b set_max_n_columns!(grid_view::GridView, n::Integer) = detail.set_max_n_columns!(grid_view._internal, UInt64(n)) export set_max_n_columns! get_max_n_columns(grid_view::GridView) ::Int64 = detail.get_max_n_columns(grid_view._internal) export get_max_n_columns set_min_n_columns!(grid_view::GridView, n::Integer) = detail.set_min_n_columns!(grid_view._internal, UInt64(n)) export set_min_n_columns! get_min_n_columns(grid_view::GridView) ::Int64 = detail.get_min_n_columns(grid_view._internal) export get_min_n_columns @export_function GridView set_orientation! Cvoid Orientation orientation @export_function GridView get_orientation Orientation get_selection_model(grid_view::GridView) ::SelectionModel = SelectionModel(detail.get_selection_model(grid_view._internal)) export get_selection_model @add_widget_signals GridView @add_signal_activate_item GridView Base.show(io::IO, x::GridView) = show_aux(io, x, :selection_model) ###### grid.jl @export_type Grid Widget @declare_native_widget Grid Grid() = Grid(detail._Grid()) function insert_at!(grid::Grid, widget::Widget, row_i::Signed, column_i::Signed, n_horizontal_cells::Integer = 1, n_vertical_cells::Integer = 1) detail.insert!(grid._internal, as_widget_pointer(widget), row_i - 1, column_i - 1, n_horizontal_cells, n_vertical_cells) end export insert_at! function insert_next_to!(grid::Grid, to_insert::Widget, already_in_grid::Widget, position::RelativePosition, n_horizontal_cells::Integer = 1, n_vertical_cells::Integer = 1) detail.insert_next_to!(grid._internal, as_widget_pointer(to_insert), as_widget_pointer(already_in_grid), position, n_horizontal_cells, n_vertical_cells) end export insert_next_to! remove!(grid::Grid, widget::Widget) = detail.remove!(grid._internal, as_widget_pointer(widget)) export remove! function get_position(grid::Grid, widget::Widget) ::Vector2i native_pos::Vector2i = detail.get_position(grid._internal, as_widget_pointer(widget)) return Vector2i(native_pos.x + 1, native_pos.y + 1) end export get_position get_size(grid::Grid, widget::Widget) ::Vector2i = detail.get_size(grid._internal, as_widget_pointer(widget)) export get_size insert_row_at!(grid::Grid, row_i::Signed) = detail.insert_row_at!(grid._internal, row_i -1) export insert_row_at! remove_row_at!(grid::Grid, row_i::Signed) = detail.remove_row_at!(grid._internal, row_i -1) export remove_row_at! insert_column_at!(grid::Grid, column_i::Signed) = detail.insert_column_at!(grid._internal, column_i -1) export insert_column_at! remove_column_at!(grid::Grid, column_i::Signed) = detail.remove_column_at!(grid._internal, column_i -1) export remove_column_at! @export_function Grid get_column_spacing Cfloat @export_function Grid set_column_spacing! Cvoid Number => Cfloat spacing @export_function Grid get_row_spacing Cfloat @export_function Grid set_row_spacing! Cvoid Number => Cfloat spacing @export_function Grid set_rows_homogeneous! Cvoid Bool b @export_function Grid get_rows_homogeneous Bool @export_function Grid set_columns_homogeneous! Cvoid Bool b @export_function Grid get_columns_homogeneous Bool @export_function Grid set_orientation! Cvoid Orientation orientation @export_function Grid get_orientation Orientation @add_widget_signals Grid Base.show(io::IO, x::Grid) = show_aux(io, x, :orientation) ###### stack.jl @export_type Stack Widget @declare_native_widget Stack Stack() = Stack(detail._Stack()) @export_type StackSidebar Widget @declare_native_widget StackSidebar StackSidebar(stack::Stack) = StackSidebar(detail._StackSidebar(stack._internal)) @export_type StackSwitcher Widget @declare_native_widget StackSwitcher StackSwitcher(stack::Stack) = StackSwitcher(detail._StackSwitcher(stack._internal)) @export_enum StackTransitionType begin STACK_TRANSITION_TYPE_NONE STACK_TRANSITION_TYPE_CROSSFADE STACK_TRANSITION_TYPE_SLIDE_RIGHT STACK_TRANSITION_TYPE_SLIDE_LEFT STACK_TRANSITION_TYPE_SLIDE_UP STACK_TRANSITION_TYPE_SLIDE_DOWN STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT STACK_TRANSITION_TYPE_SLIDE_UP_DOWN STACK_TRANSITION_TYPE_OVER_UP STACK_TRANSITION_TYPE_OVER_DOWN STACK_TRANSITION_TYPE_OVER_LEFT STACK_TRANSITION_TYPE_OVER_RIGHT STACK_TRANSITION_TYPE_UNDER_UP STACK_TRANSITION_TYPE_UNDER_DOWN STACK_TRANSITION_TYPE_UNDER_LEFT STACK_TRANSITION_TYPE_UNDER_RIGHT STACK_TRANSITION_TYPE_OVER_UP_DOWN STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT STACK_TRANSITION_TYPE_ROTATE_LEFT STACK_TRANSITION_TYPE_ROTATE_RIGHT STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT end get_selection_model(stack::Stack) ::SelectionModel = SelectionModel(detail.get_selection_model(stack._internal)) export get_selection_model const StackID = String export StackID add_child!(stack::Stack, child::Widget, title::String) ::StackID = detail.add_child!(stack._internal, as_widget_pointer(child), title) export add_child! remove_child!(stack::Stack, id::StackID) = detail.remove_child!(stack._internal, id) export remove_child! set_visible_child!(stack::Stack, id::StackID) = detail.set_visible_child!(stack._internal, id) export set_visible_child! get_visible_child(stack::Stack) ::StackID = detail.get_visible_child(stack._internal) export get_visible_child get_child_at(stack::Stack, index::Integer) ::StackID = detail.get_child_at(stack._internal, from_julia_index(convert(Csize_t, index))) export get_child_at @export_function Stack set_transition_type! Cvoid StackTransitionType transition @export_function Stack get_transition_type StackTransitionType set_transition_duration!(stack::Stack, duration::Time) = detail.set_transition_duration!(stack._internal, convert(Cfloat, as_microseconds(duration))) export set_transition_duration! get_transition_duration(stack::Stack) ::Time = microseconds(detail.get_transition_duration(stack._internal)) export get_transition_duration @export_function Stack set_is_horizontally_homogeneous! Cvoid Bool b @export_function Stack get_is_horizontally_homogeneous Bool @export_function Stack set_is_vertically_homogeneous! Cvoid Bool b @export_function Stack get_is_vertically_homogeneous Bool @export_function Stack set_should_interpolate_size! Cvoid Bool b @export_function Stack get_should_interpolate_size Bool @add_widget_signals Stack @add_widget_signals StackSidebar @add_widget_signals StackSwitcher Base.show(io::IO, x::Stack) = show_aux(io, x, :selection_model, :transition_type) ###### notebook.jl @export_type Notebook Widget @declare_native_widget Notebook Notebook() = Notebook(detail._Notebook()) function push_front!(notebook::Notebook, child_widget::Widget, label_widget::Widget) ::Int64 return detail.push_front!(notebook._internal, as_widget_pointer(child_widget), as_widget_pointer(label_widget)) end export push_front! function push_back!(notebook::Notebook, child_widget::Widget, label_widget::Widget) ::Int64 return detail.push_back!(notebook._internal, as_widget_pointer(child_widget), as_widget_pointer(label_widget)) end export push_back! function insert_at!(notebook::Notebook, index::Integer, child_widget::Widget, label_widget::Widget) ::Int64 return detail.insert!(notebook._internal, from_julia_index(index), as_widget_pointer(child_widget), as_widget_pointer(label_widget)) end export insert_at! function move_page_to!(notebook::Notebook, current_index::Integer, new_index::Integer) ::Cvoid detail.move_page_to!(notebook._internal, from_julia_index(current_index), from_julia_index(new_index)) end export move_page_to! remove!(notebook::Notebook, index::Integer) = detail.remove!(notebook._internal, from_julia_index(index)) export remove! @export_function Notebook next_page! Cvoid @export_function Notebook previous_page! Cvoid goto_page!(notebook::Notebook, index::Integer) = detail.goto_page!(notebook._internal, from_julia_index(index)) export goto_page! get_current_page(notebook::Notebook) ::Int64 = to_julia_index(detail.get_current_page(notebook._internal)) export get_current_page @export_function Notebook get_n_pages Int64 @export_function Notebook set_is_scrollable! Cvoid Bool b @export_function Notebook get_is_scrollable Bool @export_function Notebook set_has_border! Cvoid Bool b @export_function Notebook get_has_border Bool @export_function Notebook set_tabs_visible! Cvoid Bool b @export_function Notebook get_tabs_visible Bool @export_function Notebook set_quick_change_menu_enabled! Cvoid Bool b @export_function Notebook get_quick_change_menu_enabled Bool @export_function Notebook set_tab_position! Cvoid RelativePosition relative_position @export_function Notebook get_tab_position RelativePosition @export_function Notebook set_tabs_reorderable! Cvoid Bool b @export_function Notebook get_tabs_reorderable Bool @add_widget_signals Notebook @add_notebook_signal Notebook page_added @add_notebook_signal Notebook page_reordered @add_notebook_signal Notebook page_removed @add_notebook_signal Notebook page_selection_changed Base.show(io::IO, x::Notebook) = show_aux(io, x, :current_page, :n_pages) ###### column_view.jl @export_type ColumnViewColumn SignalEmitter ColumnViewColumn(internal::Ptr{Cvoid}) = ColumnViewColumn(detail._ColumnViewColumn(internal)) @export_function ColumnViewColumn set_title! Cvoid String title @export_function ColumnViewColumn get_title String @export_function ColumnViewColumn set_fixed_width! Cvoid Number => Cfloat width @export_function ColumnViewColumn get_fixed_width Cfloat set_header_menu!(column::ColumnViewColumn, model::MenuModel) = detail.set_header_menu!(column._internal, model._internal) export set_header_menu! @export_function ColumnViewColumn set_is_visible! Cvoid Bool b @export_function ColumnViewColumn get_is_visible Bool @export_function ColumnViewColumn set_is_resizable! Cvoid Bool b @export_function ColumnViewColumn get_is_resizable Bool @export_type ColumnView Widget @declare_native_widget ColumnView ColumnView(selection_mode::SelectionMode = SELECTION_MODE_NONE) = ColumnView(detail._ColumnView(selection_mode)) function push_back_column!(column_view::ColumnView, title::String) ::ColumnViewColumn return ColumnViewColumn(detail.push_back_column!(column_view._internal, title)) end export push_back_column! function push_front_column!(column_view::ColumnView, title::String) ::ColumnViewColumn return ColumnViewColumn(detail.push_front_column!(column_view._internal, title)) end export push_front_column! function insert_column_at!(column_view::ColumnView, index::Integer, title::String) ::ColumnViewColumn return ColumnViewColumn(detail.insert_column!(column_view._internal, from_julia_index(index), title)) end export insert_column_at! remove_column!(column_view::ColumnView, column::ColumnViewColumn) = detail.remove_column!(column_view._internal, column._internal) export remove_column! function get_column_at(column_view::ColumnView, index::Integer) ::ColumnViewColumn return ColumnViewColumn(detail.get_column_at(column_view._internal, from_julia_index(index))) end export get_column_at function get_column_with_title(column_view::ColumnView, title::String) ::ColumnViewColumn return ColumnViewColumn(detail.get_column_with_title(column_view._internal, title)) end export get_column_with_title has_column_with_title(column_view::ColumnView, title::String) ::Bool = detail.has_column_with_title(column_view._internal, title) export has_column_with_title function set_widget_at!(column_view::ColumnView, column::ColumnViewColumn, row_i::Integer, widget::Widget) detail.set_widget_at!(column_view._internal, column._internal, from_julia_index(row_i), as_widget_pointer(widget)) end export set_widget_at! function push_back_row!(column_view::ColumnView, widgets::Widget...) if length(widgets) > get_n_columns(column_view) @log_warning MOUSETRAP_DOMAIN "In ColumnView.push_back_row!: Attempting to push $(length(widgets)) widgets, but ColumnView only has $(get_n_columns(column_view)) columns" end row_i = get_n_rows(column_view) + 1 insert_row_at!(column_view, row_i, widgets...) end export push_back_row! function push_front_row!(column_view::ColumnView, widgets::Widget...) @log_critical MOUSETRAP_DOMAIN "In ColumnView.push_front_row!: This method was deprecated in v0.3.2, use `insert_row_at!` instead" if length(widgets) > get_n_columns(column_view) @log_warning MOUSETRAP_DOMAIN "In ColumnView.push_front_row!: Attempting to push $(length(widgets)) widgets, but ColumnView only has $(get_n_columns(column_view)) columns" end row_i = 1 for i in 1:get_n_columns(column_view) column = get_column_at(column_view, i) detail.set_widget_at!(column_view._internal, column._internal, 0, as_widget_pointer(widgets[i])) end end export push_front_row! function insert_row_at!(column_view::ColumnView, index::Integer, widgets::Widget...) if length(widgets) > get_n_columns(column_view) @log_warning MOUSETRAP_DOMAIN "In ColumnView.insert_row_at!: Attempting to push $(length(widgets)) widgets, but ColumnView only has $(get_n_columns(column_view)) columns" end row_i = index for i in 1:get_n_columns(column_view) column = get_column_at(column_view, i) set_widget_at!(column_view, column, row_i, widgets[i]) end end export push_front_row! get_selection_model(column_view::ColumnView) ::SelectionModel = SelectionModel(detail.get_selection_model(column_view._internal)) export get_selection_model @export_function ColumnView set_enable_rubberband_selection! Cvoid Bool b @export_function ColumnView get_enable_rubberband_selection Bool @export_function ColumnView set_show_row_separators Cvoid Bool b @export_function ColumnView get_show_row_separators Bool @export_function ColumnView set_show_column_separators Cvoid Bool b @export_function ColumnView get_show_column_separators Bool @export_function ColumnView set_single_click_activate! Cvoid Bool b @export_function ColumnView get_single_click_activate Bool @export_function ColumnView get_n_rows Int64 @export_function ColumnView get_n_columns Int64 @add_widget_signals ColumnView @add_signal_activate ColumnView Base.show(io::IO, x::ColumnView) = show_aux(io, x, :n_rows, :n_columns) ###### header_bar.jl @export_type HeaderBar Widget @declare_native_widget HeaderBar HeaderBar() = HeaderBar(detail._HeaderBar()) HeaderBar(internal::Ptr{Cvoid}) = HeaderBar(detail._HeaderBar(internal)) HeaderBar(layout::String) = HeaderBar(detail._HeaderBar(layout)) @export_function HeaderBar set_layout! Cvoid String layout @export_function HeaderBar get_layout String @export_function HeaderBar set_show_title_buttons! Cvoid Bool b @export_function HeaderBar get_show_title_buttons Bool set_title_widget!(header_bar::HeaderBar, widget::Widget) = detail.set_title_widget!(header_bar._internal, as_widget_pointer(widget)) export set_title_widget! @export_function HeaderBar remove_title_widget! Cvoid push_front!(header_bar::HeaderBar, widget::Widget) = detail.push_front!(header_bar._internal, as_widget_pointer(widget)) export push_front! push_back!(header_bar::HeaderBar, widget::Widget) = detail.push_back!(header_bar._internal, as_widget_pointer(widget)) export push_back! remove!(header_bar::HeaderBar, widget::Widget) = detail.remove!(header_bar._internal, as_widget_pointer(widget)) export remove! @add_widget_signals HeaderBar Base.show(io::IO, x::HeaderBar) = show_aux(io, x, :layout) ###### paned.jl @export_type Paned Widget @declare_native_widget Paned Paned(orientation::Orientation) = Paned(detail._Paned(orientation)) function Paned(orientation::Orientation, start_child::Widget, end_child::Widget) ::Paned out = Paned(orientation) set_start_child!(out, start_child) set_end_child!(out, end_child) return out end @export_function Paned get_position Cint @export_function Paned set_position! Cvoid Integer position @export_function Paned set_has_wide_handle! Cvoid Bool b @export_function Paned get_has_wide_handle Bool @export_function Paned set_orientation! Cvoid Orientation orientation @export_function Paned get_orientation Orientation @export_function Paned set_start_child_resizable! Cvoid Bool b @export_function Paned get_start_child_resizable Bool @export_function Paned set_start_child_shrinkable! Cvoid Bool b @export_function Paned get_start_child_shrinkable Bool set_start_child!(paned::Paned, child::Widget) = detail.set_start_child!(paned._internal, as_widget_pointer(child)) export set_start_child! @export_function Paned remove_start_child! Cvoid @export_function Paned set_end_child_resizable! Cvoid Bool b @export_function Paned get_end_child_resizable Bool @export_function Paned set_end_child_shrinkable! Cvoid Bool b @export_function Paned get_end_child_shrinkable Bool set_end_child!(paned::Paned, child::Widget) = detail.set_end_child!(paned._internal, as_widget_pointer(child)) export set_end_child! @export_function Paned remove_end_child! Cvoid Base.show(io::IO, x::Paned) = show_aux(io, x, :start_child_resizable, :start_child_shrinkable, :end_child_resizable, :end_child_shrinkable) ###### progress_bar.jl @export_type ProgressBar Widget @declare_native_widget ProgressBar ProgressBar() = ProgressBar(detail._ProgressBar()) @export_function ProgressBar pulse Cvoid @export_function ProgressBar set_fraction! Cvoid AbstractFloat => Cfloat zero_to_one @export_function ProgressBar get_fraction Cfloat @export_function ProgressBar set_is_inverted! Cvoid Bool b @export_function ProgressBar get_is_inverted Bool @export_function ProgressBar set_text! Cvoid String text @export_function ProgressBar get_text String @export_function ProgressBar set_show_text! Cvoid Bool b @export_function ProgressBar get_show_text Bool @export_function ProgressBar set_orientation! Cvoid Orientation orientation @export_function ProgressBar get_orientation Orientation Base.show(io::IO, x::ProgressBar) = show_aux(io, x, :fraction, :orientation, :show_text, :text) ###### spinner.jl @export_type Spinner Widget @declare_native_widget Spinner Spinner() = Spinner(detail._Spinner()) @export_function Spinner set_is_spinning! Cvoid Bool b @export_function Spinner get_is_spinning Bool @export_function Spinner start! Cvoid @export_function Spinner stop! Cvoid Base.show(io::IO, x::Spinner) = show_aux(io, x) ###### revealer.jl @export_enum RevealerTransitionType begin REVEALER_TRANSITION_TYPE_NONE REVEALER_TRANSITION_TYPE_CROSSFADE REVEALER_TRANSITION_TYPE_SLIDE_RIGHT REVEALER_TRANSITION_TYPE_SLIDE_LEFT REVEALER_TRANSITION_TYPE_SLIDE_UP REVEALER_TRANSITION_TYPE_SLIDE_DOWN REVEALER_TRANSITION_TYPE_SWING_RIGHT REVEALER_TRANSITION_TYPE_SWING_LEFT REVEALER_TRANSITION_TYPE_SWING_UP REVEALER_TRANSITION_TYPE_SWING_DOWN end @export_type Revealer Widget @declare_native_widget Revealer Revealer(transition_type::RevealerTransitionType = REVEALER_TRANSITION_TYPE_CROSSFADE) = Revealer(detail._Revealer(transition_type)) function Revealer(widget::Widget, transition_type::RevealerTransitionType = REVEALER_TRANSITION_TYPE_CROSSFADE) :: Revealer out = Revealer(transition_type) set_child!(out, widget) return out end set_child!(revealer::Revealer, child::Widget) = detail.set_child!(revealer._internal, as_widget_pointer(child)) export set_child! @export_function Revealer remove_child! Cvoid @export_function Revealer set_is_revealed! Cvoid Bool child_visible @export_function Revealer get_is_revealed Bool @export_function Revealer set_transition_type! Cvoid RevealerTransitionType type @export_function Revealer get_transition_type RevealerTransitionType set_transition_duration!(revealer::Revealer, duration::Time) = detail.set_transition_duration!(revealer._internal, as_microseconds(duration)) export set_transition_duration! get_transition_duration(revealer::Revealer) ::Time = microseconds(detail.get_transition_duration(revealer._internal)) export get_transition_duration @add_widget_signals Revealer @add_signal_revealed Revealer Base.show(io::IO, x::Revealer) = show_aux(io, x, :is_revealed, :transition_type) ###### action_bar.jl @export_type ActionBar Widget @declare_native_widget ActionBar function push_back!(action_bar::ActionBar, widget::Widget) detail.push_back!(action_bar._internal, as_widget_pointer(widget)) end export push_back! function push_front!(action_bar::ActionBar, widget::Widget) detail.push_front!(action_bar._internal, as_widget_pointer(widget)) end export push_front! function set_center_widget(action_bar::ActionBar, widget::Widget) detail.set_center_widget!(action_bar._internal, as_widget_pointer(widget)) end export insert_after! function remove!(action_bar::ActionBar, widget::Widget) detail.remove!(action_bar._internal, as_widget_pointer(widget)) end export remove! @export_function ActionBar remove_center_child! Cvoid @export_function ActionBar set_is_revealed! Cvoid Bool b @export_function ActionBar get_is_revealed Bool @add_widget_signals ActionBar Base.show(io::IO, x::ActionBar) = show_aux(io, x, :is_revealed) ###### scale.jl @export_type Scale Widget @declare_native_widget Scale function Scale(lower::Number, upper::Number, step_increment::Number, orientation::Orientation = ORIENTATION_HORIZONTAL) return Scale(detail._Scale( convert(Cfloat, lower), convert(Cfloat, upper), convert(Cfloat, step_increment), orientation )) end get_adjustment(scale::Scale) ::Adjustment = Adjustment(detail.get_adjustment(scale._internal)) export get_adjustment @export_function Scale get_lower Cfloat @export_function Scale get_upper Cfloat @export_function Scale get_step_increment Cfloat @export_function Scale get_value Cfloat @export_function Scale set_lower! Cvoid Number => Cfloat value @export_function Scale set_upper! Cvoid Number => Cfloat value @export_function Scale set_step_increment! Cvoid Number => Cfloat value @export_function Scale set_value! Cvoid Number => Cfloat value @export_function Scale set_should_draw_value! Cvoid Bool b @export_function Scale get_should_draw_value Bool @export_function Scale set_has_origin! Cvoid Bool b @export_function Scale get_has_origin Bool @export_function Scale set_orientation! Cvoid Orientation orientation @export_function Scale get_orientation Orientation function add_mark!(scale::Scale, value::Number, position::RelativePosition, label::String = "") detail.add_mark!(scale._internal, convert(Cfloat, value), position, label) end export add_mark! @export_function Scale clear_marks! Cvoid @add_widget_signals Scale @add_signal_value_changed Scale Base.show(io::IO, x::Scale) = show_aux(io, x, :value, :lower, :upper, :step_increment) ###### spin_button.jl @export_type SpinButton Widget @declare_native_widget SpinButton function SpinButton(lower::Number, upper::Number, step_increment::Number, orientation::Orientation = ORIENTATION_HORIZONTAL) return SpinButton(detail._SpinButton( convert(Cfloat, lower), convert(Cfloat, upper), convert(Cfloat, step_increment), orientation )) end get_adjustment(spin_button::SpinButton) ::Adjustment = Adjustment(detail.get_adjustment(spin_button._internal)) export get_adjustment @export_function SpinButton get_lower Cfloat @export_function SpinButton get_upper Cfloat @export_function SpinButton get_step_increment Cfloat @export_function SpinButton get_value Cfloat @export_function SpinButton set_lower! Cvoid Number => Cfloat value @export_function SpinButton set_upper! Cvoid Number => Cfloat value @export_function SpinButton set_step_increment! Cvoid Number => Cfloat value @export_function SpinButton set_value! Cvoid Number => Cfloat value @export_function SpinButton set_n_digits! Cvoid Integer n @export_function SpinButton get_n_digits Int64 @export_function SpinButton set_should_wrap! Cvoid Bool b @export_function SpinButton get_should_wrap Bool @export_function SpinButton set_acceleration_rate! Cvoid Number => Cfloat factor @export_function SpinButton get_acceleration_rate Cfloat @export_function SpinButton set_should_snap_to_ticks! Cvoid Bool b @export_function SpinButton get_should_snap_to_ticks Bool @export_function SpinButton set_allow_only_numeric! Cvoid Bool b @export_function SpinButton get_allow_only_numeric Bool @export_function SpinButton set_orientation! Cvoid Orientation orientation @export_function SpinButton get_orientation Orientation function set_text_to_value_function!(f, spin_button::SpinButton, data::Data_t) where Data_t set_allow_only_numeric!(spin_button, false) typed_f = TypedFunction(f, AbstractFloat, (SpinButton, String, Data_t)) detail.set_text_to_value_function!(spin_button._internal, function (spin_button_ref, text::String) return typed_f(SpinButton(spin_button_ref[]), text, data) end) end function set_text_to_value_function!(f, spin_button::SpinButton) set_allow_only_numeric!(spin_button, false) typed_f = TypedFunction(f, AbstractFloat, (SpinButton, String)) detail.set_text_to_value_function!(spin_button._internal, function (spin_button_ref, text::String) return typed_f(SpinButton(spin_button_ref[]), text) end) end export set_text_to_value_function! @export_function SpinButton reset_value_to_text_function! Cvoid function set_value_to_text_function!(f, spin_button::SpinButton, data::Data_t) where Data_t set_allow_only_numeric!(spin_button, false) typed_f = TypedFunction(f, String, (SpinButton, AbstractFloat, Data_t)) detail.set_value_to_text_function!(spin_button._internal, function (spin_button_ref, value::Cfloat) return typed_f(SpinButton(spin_button_ref[]), value, data) end) end function set_value_to_text_function!(f, spin_button::SpinButton) set_allow_only_numeric!(spin_button, false) typed_f = TypedFunction(f, String, (SpinButton, AbstractFloat)) detail.set_value_to_text_function!(spin_button._internal, function (spin_button_ref, value::Cfloat) return typed_f(SpinButton(spin_button_ref[]), value) end) end export set_value_to_text_function! @export_function SpinButton reset_text_to_value_function! Cvoid @add_widget_signals SpinButton @add_signal_value_changed SpinButton @add_signal_wrapped SpinButton Base.show(io::IO, x::SpinButton) = show_aux(io, x, :value, :lower, :upper, :step_increment, :orientation) ###### scrollbar.jl @export_type Scrollbar Widget @declare_native_widget Scrollbar Scrollbar(orientation::Orientation, adjustment::Adjustment) = Scrollbar(detail._Scrollbar(orientation, adjustment._internal)) get_adjustment(scrollbar::Scrollbar) ::Adjustment = Adjustment(detail.get_adjustment(scrollbar._internal)) export get_adjustment @export_function Scrollbar set_orientation! Cvoid Orientation orientation @export_function Scrollbar get_orientation Orientation @add_widget_signals Scrollbar Base.show(io::IO, x::Scrollbar) = show_aux(io, x, :orientation, :adjustment) ###### separator.jl @export_type Separator Widget @declare_native_widget Separator Separator(orientation::Orientation = ORIENTATION_HORIZONTAL) = Separator(detail._Separator(orientation)) @export_function Separator set_orientation! Cvoid Orientation orientation @export_function Separator get_orientation Orientation @add_widget_signals Separator Base.show(io::IO, x::Separator) = show_aux(io, x) ####### frame_clock.jl @export_type FrameClock SignalEmitter FrameClock(internal::Ptr{Cvoid}) = FrameClock(detail._FrameClock(internal)) get_time_since_last_frame(frame_clock::FrameClock) ::Time = microseconds(detail.get_time_since_last_frame(frame_clock._internal)) export get_time_since_last_frame get_target_frame_duration(frame_clock::FrameClock) ::Time = microseconds(detail.get_target_frame_duration(frame_clock._internal)) export get_target_frame_duration @add_signal_update FrameClock @add_signal_paint FrameClock Base.show(io::IO, x::FrameClock) = show_aux(io, x, :time_since_last_frame) ####### widget.jl function as_widget_pointer(widget::Widget) as_native::Widget = widget seen = Set{Type}() while !is_native_widget(as_native) if typeof(as_native) in seen detail.log_critical("In as_widget_pointer: Type `$(typeof(as_native))`` has a malformed `as_widget` definition, this usually means `get_top_level_widget(x)` returns x itself, as opposed to the top-level widget component of x.", MOUSETRAP_DOMAIN) return Separator(opacity = 0.0)._internal.cpp_object # return placeholder to prevent segfault else push!(seen, typeof(as_native)) end as_native = get_top_level_widget(as_native) end return as_native._internal.cpp_object end # no export macro export_widget_function(name, return_t) return_t = esc(return_t) Mousetrap.eval(:(export $name)) return :(function $name(widget::Widget) return Base.convert($return_t, detail.$name(as_widget_pointer(widget))) end) return out end macro export_widget_function(name, return_t, arg1_type, arg1_name) return_t = esc(return_t) if arg1_type isa Expr arg1_origin_type = arg1_type.args[2] arg1_destination_type = arg1_type.args[3] else arg1_origin_type = arg1_type arg1_destination_type = arg1_type end arg1_name = esc(arg1_name) Mousetrap.eval(:(export $name)) out = Expr(:toplevel) return :(function $name(widget::Widget, $arg1_name::$arg1_origin_type) return Base.convert($return_t, detail.$name(as_widget_pointer(widget), convert($arg1_destination_type, $arg1_name))) end) return out end @export_enum TickCallbackResult begin TICK_CALLBACK_RESULT_CONTINUE TICK_CALLBACK_RESULT_DISCONTINUE end @export_widget_function activate! Cvoid @export_widget_function set_size_request! Cvoid Vector2f size @export_widget_function get_size_request Vector2f @export_widget_function set_margin_top! Cvoid Number => Cfloat margin @export_widget_function get_margin_top Cfloat @export_widget_function set_margin_bottom! Cvoid Number => Cfloat margin @export_widget_function get_margin_bottom Cfloat @export_widget_function set_margin_start! Cvoid Number => Cfloat margin @export_widget_function get_margin_start Cfloat @export_widget_function set_margin_end! Cvoid Number => Cfloat margin @export_widget_function get_margin_end Cfloat @export_widget_function set_margin_horizontal! Cvoid Number => Cfloat margin @export_widget_function set_margin_vertical! Cvoid Number => Cfloat margin @export_widget_function set_margin! Cvoid Number => Cfloat margin @export_widget_function set_expand_horizontally! Cvoid Bool b @export_widget_function get_expand_horizontally Bool @export_widget_function set_expand_vertically! Cvoid Bool b @export_widget_function get_expand_vertically Bool @export_widget_function set_expand! Cvoid Bool b @export_widget_function set_horizontal_alignment! Cvoid Alignment alignment @export_widget_function get_horizontal_alignment Alignment @export_widget_function set_vertical_alignment! Cvoid Alignment alignment @export_widget_function get_vertical_alignment Alignment @export_widget_function set_alignment! Cvoid Alignment both @export_widget_function set_opacity! Cvoid Number => Cfloat opacity @export_widget_function get_opacity Cfloat @export_widget_function set_is_visible! Cvoid Bool b @export_widget_function get_is_visible Bool @export_widget_function set_tooltip_text! Cvoid String text set_tooltip_widget!(widget::Widget, tooltip::Widget) = detail.set_tooltip_widget!(as_widget_pointer(widget), as_widget_pointer(tooltip)) export set_tooltip_widget! @export_widget_function remove_tooltip_widget! Cvoid @export_widget_function set_cursor! Cvoid CursorType cursor function set_cursor_from_image!(widget::Widget, image::Image, offset::Vector2i = Vector2i(0, 0)) detail.set_cursor_from_image!(as_widget_pointer(widget), image._internal, offset.x, offset.y) end export set_cursor_from_image! @export_widget_function hide! Cvoid @export_widget_function show! Cvoid function add_controller!(widget::Widget, controller::EventController) detail.add_controller!(as_widget_pointer(widget), controller._internal.cpp_object) end export add_controller! function remove_controller!(widget::Widget, controller::EventController) detail.remove_controller!(as_widget_pointer(widget), controller._internal.cpp_object) end export remove_controller! @export_widget_function set_is_focusable! Cvoid Bool b @export_widget_function get_is_focusable Bool @export_widget_function grab_focus! Cvoid @export_widget_function get_has_focus Bool @export_widget_function set_focus_on_click! Cvoid Bool b @export_widget_function get_focus_on_click Bool @export_widget_function set_can_respond_to_input! Cvoid Bool b @export_widget_function get_can_respond_to_input Bool @export_widget_function get_is_realized Bool @export_widget_function get_minimum_size Vector2f @export_widget_function get_natural_size Vector2f @export_widget_function get_position Vector2f @export_widget_function get_allocated_size Vector2f @export_widget_function calculate_monitor_dpi Cfloat @export_widget_function get_scale_factor Cfloat @export_widget_function unparent! Cvoid @export_widget_function set_hide_on_overflow! Cvoid Bool b @export_widget_function get_hide_on_overflow Bool function set_tick_callback!(f, widget::Widget, data::Data_t) where Data_t typed_f = TypedFunction(f, TickCallbackResult, (FrameClock, Data_t)) detail.set_tick_callback!(as_widget_pointer(widget), function(frame_clock_ref) typed_f(FrameClock(frame_clock_ref[]), data) end) end function set_tick_callback!(f, widget::Widget) typed_f = TypedFunction(f, TickCallbackResult, (FrameClock,)) detail.set_tick_callback!(as_widget_pointer(widget), function(frame_clock_ref) typed_f(FrameClock(frame_clock_ref[])) end) end export set_tick_callback! @export_widget_function remove_tick_callback! Cvoid function set_listens_for_shortcut_action!(widget::Widget, action::Action) ::Cvoid detail.set_listens_for_shortcut_action!(as_widget_pointer(widget), action._internal) end export set_listens_for_shortcut_action! Base.hash(x::Widget) = UInt64(Mousetrap.detail.as_native_widget(Mousetrap.as_widget_pointer(x))) ####### clipboard.jl @export_type Clipboard SignalEmitter function Clipboard(internal::Ptr{Cvoid}) out = Clipboard(detail._Clipboard(internal)) end @export_function Clipboard get_is_local Bool @export_function Clipboard contains_string Bool set_string!(clipboard::Clipboard, string::String) = detail.set_string!(clipboard._internal, string) export set_string! function get_string(f, clipboard::Clipboard, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (Clipboard, String, Data_t)) detail.get_string(clipboard._internal, function(internal_ref, string) typed_f(Clipboard(internal_ref[]), String(string), data) end) end function get_string(f, clipboard::Clipboard) typed_f = TypedFunction(f, Cvoid, (Clipboard, String)) detail.get_string(clipboard._internal, function(internal_ref, string) typed_f(Clipboard(internal_ref[]), String(string)) end) end export get_string @export_function Clipboard contains_image Bool set_image!(clipboard::Clipboard, image::Image) = detail.set_image!(clipboard._internal, image._internal) export set_image! function get_image(f, clipboard::Clipboard, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (Clipboard, Image, Data_t)) detail.get_image(clipboard._internal, function(internal_ref, image_ref) typed_f(Clipboard(internal_ref[]), Image(image_ref[], data)) end) end function get_image(f, clipboard::Clipboard) typed_f = TypedFunction(f, Cvoid, (Clipboard, Image)) detail.get_image(clipboard._internal, function(internal_ref, image_ref) typed_f(Clipboard(internal_ref[]), Image(image_ref[])) end) end export get_image @export_function Clipboard contains_file Bool set_file!(clipboard::Clipboard, file::FileDescriptor) = detail.set_file!(clipboard._internal, file._internal) export set_file! get_clipboard(widget::Widget) ::Clipboard = Clipboard(detail.get_clipboard(as_widget_pointer(widget))) export get_clipboard Base.show(io::IO, x::Clipboard) = show_aux(io, x) ### opengl_common.jl macro define_opengl_error_type(name) message = "In Mousetrap::$name(): `$name` cannot be instantiated, because the Mousetrap OpenGL component is disabled for MacOS. It and any function operating on it cannot be used in any way.\n\nSee the manual chapter on native rendering for more information." return :(struct $name function $name() Mousetrap.log_fatal(Mousetrap.MOUSETRAP_DOMAIN, $message) return new() end end) end @static if MOUSETRAP_ENABLE_OPENGL_COMPONENT ####### blend_mode.jl @export_enum BlendMode begin BLEND_MODE_NONE BLEND_MODE_NORMAL BLEND_MODE_ADD BLEND_MODE_SUBTRACT BLEND_MODE_REVERSE_SUBTRACT BLEND_MODE_MULTIPLY BLEND_MODE_MIN BLEND_MODE_MAX end function set_current_blend_mode(blend_mode::BlendMode; allow_alpha_blending = true) detail.set_current_blend_mode(blend_mode, allow_alpha_blending) end export set_current_blend_mode ####### gl_transform.jl @export_type GLTransform SignalEmitter GLTransform() = GLTransform(detail._GLTransform()) import Base.setindex! function Base.setindex!(transform::GLTransform, x::Integer, y::Integer, value::Number) if x == 0 || x > 4 || y == 0 || y > 4 throw(BoundsError(transform, (x, y))) end detail.setindex!(transform._internal, from_julia_index(x), from_julia_index(y), convert(Float32, value)) end import Base.getindex function Base.getindex(transform::GLTransform, x::Integer, y::Integer) ::Cfloat if x == 0 || x > 4 || y == 0 || y > 4 throw(BoundsError(transform, (x, y))) end return detail.getindex(transform._internal, from_julia_index(x), from_julia_index(y)) end apply_to(transform::GLTransform, v::Vector2f) ::Vector2f = detail.apply_to_2f(transform, v.x, v.y) apply_to(transform::GLTransform, v::Vector3f) ::Vector3f = detail.apply_to_3f(transform, v.x, v.y, v.z) export apply_to combine_with(self::GLTransform, other::GLTransform) = GLTransform(detail.combine_with(self._internal, other._internal)) export combine_with function rotate!(transform::GLTransform, angle::Angle, origin::Vector2f = Vector2f(0, 0)) detail.rotate!(transform._internal, convert(Float32, as_radians(angle)), origin.x, origin.y) end export rotate! function translate!(transform::GLTransform, offset::Vector2f) detail.translate!(transform._internal, offset.x, offset.y) end export translate! function scale!(transform::GLTransform, x_scale::AbstractFloat, y_scale::AbstractFloat) detail.scale!(transform._internal, convert(Float32, x_scale), convert(Float32, y_scale)) end export scale! @export_function GLTransform reset! Cvoid function Base.:(==)(a::GLTransform, b::GLTransform) for i in 1:4 for j in 1:4 if a[i, j] != b[i, j] return false end end end return true end Base.:(!=)(a::GLTransform, b::GLTransform) = !(a == b) Base.show(io::IO, x::GLTransform) = show_aux(io, x) ###### shader.jl @export_type Shader SignalEmitter Shader() = Shader(detail._Shader()) @export_enum ShaderType begin SHADER_TYPE_FRAGMENT SHADER_TYPE_VERTEX end @export_function Shader get_program_id Cuint @export_function Shader get_fragment_shader_id Cuint @export_function Shader get_vertex_shader_id Cuint function create_from_string!(shader::Shader, type::ShaderType, glsl_code::String) ::Bool return detail.create_from_string!(shader._internal, type, glsl_code) end export create_from_string! function create_from_file!(shader::Shader, type::ShaderType, path::String) ::Bool return detail.create_from_file!(shader._internal, type, path) end export create_from_file! @export_function Shader get_uniform_location Cint String name @export_function Shader set_uniform_float! Cvoid String name Cfloat float @export_function Shader set_uniform_int! Cvoid String name Cint float @export_function Shader set_uniform_uint! Cvoid String name Cuint float set_uniform_vec2!(shader::Shader, name::String, vec2::Vector2f) = detail.set_uniform_vec2!(shader._internal, name, vec2) export set_uniform_vec2! set_uniform_vec3!(shader::Shader, name::String, vec3::Vector3f) = detail.set_uniform_vec3!(shader._internal, name, vec3) export set_uniform_vec3! set_uniform_vec4!(shader::Shader, name::String, vec4::Vector4f) = detail.set_uniform_vec4!(shader._internal, name, vec4) export set_uniform_vec4! set_uniform_transform!(shader::Shader, name::String, transform::GLTransform) = detail.set_uniform_transform!(shader._internal, name, transform._internal) export set_uniform_transform! get_vertex_position_location() = detail.shader_get_vertex_position_location() export get_vertex_position_location get_vertex_color_location() = detail.shader_get_vertex_color_location() export get_vertex_color_location get_vertex_texture_coordinate_location() = detail.shader_get_vertex_texture_coordinate_location() export get_vertex_texture_coordinate_location Base.show(io::IO, x::Shader) = show_aux(io, x) ###### texture.jl abstract type TextureObject <: SignalEmitter end export TextureObject @export_enum TextureWrapMode begin TEXTURE_WRAP_MODE_ZERO TEXTURE_WRAP_MODE_ONE TEXTURE_WRAP_MODE_REPEAT TEXTURE_WRAP_MODE_MIRROR TEXTURE_WRAP_MODE_STRETCH end @export_enum TextureScaleMode begin TEXTURE_SCALE_MODE_NEAREST TEXTURE_SCALE_MODE_LINEAR end @export_type Texture TextureObject Texture() = Texture(detail._Texture()) @export_type RenderTexture TextureObject RenderTexture() = RenderTexture(detail._RenderTexture()) download(texture::TextureObject) ::Image = Image(detail.texture_download(texture._internal.cpp_object)) export download bind(texture::TextureObject) = detail.texture_bind(texture._internal.cpp_object) export bind unbind(texture::TextureObject) = detail.texture_unbind(texture._internal.cpp_object) export unbind create!(texture::TextureObject, width::Integer, height::Integer) = detail.texture_create!(texture._internal.cpp_object, width, height) export create! create_from_image!(texture::TextureObject, image::Image) = detail.texture_create_from_image!(texture._internal.cpp_object, image._internal) export create_from_image! set_wrap_mode!(texture::TextureObject, mode::TextureWrapMode) = detail.texture_set_wrap_mode!(texture._internal.cpp_object, mode) export set_wrap_mode! set_scale_mode!(texture::TextureObject, mode::TextureScaleMode) = detail.texture_set_scale_mode!(texture._internal.cpp_object, mode) export set_scale_mode! get_wrap_mode(texture::TextureObject) ::TextureWrapMode = detail.texture_get_wrap_mode(texture._internal.cpp_object) export get_wrap_mode get_scale_mode(texture::TextureObject) ::TextureScaleMode = detail.texture_get_scale_mode(texture._internal.cpp_object) export get_scale_mode get_size(texture::TextureObject) ::Vector2i = detail.texture_get_size(texture._internal.cpp_object) export get_size get_native_handle(texture::TextureObject) ::Cuint = detail.texture_get_native_handle(texture._internal.cpp_object) export get_native_handle bind_as_render_target(render_texture::RenderTexture) = detail.render_texture_bind_as_render_target(render_texture._internal.cpp_object) export bind_as_render_target unbind_as_render_target(render_texture::RenderTexture) = detail.render_texture_unbind_as_render_target(render_texture._internal.cpp_object) export unbind_as_render_target Base.show(io::IO, x::TextureObject) = show_aux(io, x, :native_handle) ###### shape.jl @export_type Shape SignalEmitter Shape() = Shape(detail._Shape()) @export_function Shape get_native_handle Cuint as_point!(shape::Shape, position::Vector2f) = detail.as_point!(shape._internal, position) export as_point! function Point(position::Vector2f) ::Shape out = Shape() as_point!(out, position) return out end export Point function as_points!(shape::Shape, positions::Vector{Vector2f}) if isempty(positions) @log_critical MOUSETRAP_DOMAIN "In as_points!: Vector `positions` is empty." end return detail.as_points!(shape._internal, positions) end export as_points! function Points(positions::Vector{Vector2f}) ::Shape out = Shape() as_points!(out, positions) return out end export Points as_triangle!(shape::Shape, a::Vector2f, b::Vector2f, c::Vector2f) = detail.as_triangle!(shape._internal, a, b, c) export as_triangle! function Triangle(a::Vector2f, b::Vector2f, c::Vector2f) ::Shape out = Shape() as_triangle!(out, a, b, c) return out end export Triangle as_rectangle!(shape::Shape, top_left::Vector2f, size::Vector2f) = detail.as_rectangle!(shape._internal, top_left, size) export as_rectangle! function Rectangle(top_left::Vector2f, size::Vector2f) ::Shape out = Shape() as_rectangle!(out, top_left, size) return out end export Rectangle as_circle!(shape::Shape, center::Vector2f, radius::Number, n_outer_vertices::Integer) = detail.as_circle!(shape._internal, center, convert(Cfloat, radius), n_outer_vertices) export as_circle! function Circle(center::Vector2f, radius::Number, n_outer_vertices::Integer) ::Shape out = Shape() as_circle!(out, center, radius, n_outer_vertices) return out end export Circle as_ellipse!(shape::Shape, center::Vector2f, x_radius::Number, y_radius::Number, n_outer_vertices::Integer) = detail.as_ellipse!(shape._internal, center, convert(Cfloat, x_radius), convert(Cfloat, y_radius), n_outer_vertices) export as_ellipse! function Ellipse(center::Vector2f, x_radius::Number, y_radius::Number, n_outer_vertices::Integer) ::Shape out = Shape() as_ellipse!(out, center, x_radius, y_radius, n_outer_vertices) return out end export Ellipse as_line!(shape::Shape, a::Vector2f, b::Vector2f) = detail.as_line!(shape._internal, a, b) export as_line! function Line(a::Vector2f, b::Vector2f) out = Shape() as_line!(out, a, b) return out end export Line as_lines!(shape::Shape, points::Vector{Pair{Vector2f, Vector2f}}) = detail.as_lines!(shape._internal, points) export as_lines! function Lines(points::Vector{Pair{Vector2f, Vector2f}}) out = Shape() as_lines!(out, points) return out end export Lines as_line_strip!(shape::Shape, points::Vector{Vector2f}) = detail.as_line_strip!(shape._internal, points) export as_line_strip! function LineStrip(points::Vector{Vector2f}) out = Shape() as_line_strip!(out, points) return out end export LineStrip as_polygon!(shape::Shape, points::Vector{Vector2f}) = detail.as_polygon!(shape._internal, points) export as_polygon! function Polygon(points::Vector{Vector2f}) out = Shape() as_polygon!(out, points) return out end export Polygon function as_rectangular_frame!(shape::Shape, top_left::Vector2f, outer_size::Vector2f, x_width::Number, y_width::Number) detail.as_rectangular_frame!(shape._internal, top_left, outer_size, convert(Cfloat, x_width), convert(Cfloat, y_width)) end export as_rectangular_frame! function RectangularFrame(top_left::Vector2f, outer_size::Vector2f, x_width::Number, y_width::Number) out = Shape() as_rectangular_frame!(out, top_left, outer_size, x_width, y_width) return out end export RectangularFrame function as_circular_ring!(shape::Shape, center::Vector2f, outer_radius::Number, thickness::Number, n_outer_vertices::Integer) detail.as_circular_ring!(shape._internal, center, convert(Cfloat, outer_radius), convert(Cfloat, thickness), n_outer_vertices) end export as_circular_ring! function CircularRing(center::Vector2f, outer_radius::Number, thickness::Number, n_outer_vertices::Integer) out = Shape() as_circular_ring!(out, center, outer_radius, thickness, n_outer_vertices) return out end export CircularRing function as_elliptical_ring!(shape::Shape, center::Vector2f, outer_x_radius::Number, outer_y_radius::Number, x_thickness::Number, y_thickness::Number, n_outer_vertices::Integer) detail.as_elliptical_ring!(shape._internal, center, convert(Cfloat, outer_x_radius), convert(Cfloat, outer_y_radius), convert(Cfloat, x_thickness), convert(Cfloat, y_thickness), n_outer_vertices) end export as_elliptical_ring! function EllipticalRing(center::Vector2f, outer_x_radius::Number, outer_y_radius::Number, x_thickness::Number, y_thickness::Number, n_outer_vertices::Integer) ::Shape out = Shape() as_elliptical_ring!(out, center, outer_x_radius, outer_y_radius, x_thickness, y_thickness, n_outer_vertices) return out end export EllipticalRing as_wireframe!(shape::Shape, points::Vector{Vector2f}) = detail.as_wireframe!(shape._internal, points) export as_wireframe! function Wireframe(points::Vector{Vector2f}) out = Shape() as_wireframe!(out, points) return out end export Wireframe as_outline!(self::Shape, other::Shape) = detail.as_outline!(self._internal, other._internal) export as_outline! function Outline(other::Shape) out = Shape() as_outline!(out, other) return out end export Outline render(shape::Shape, shader::Shader, transform::GLTransform) = detail.render(shape._internal, shader._internal, transform._internal) export render function get_vertex_color(shape::Shape, index::Integer) ::RGBA return detail.get_vertex_color(shape._internal, from_julia_index(index)) end export get_vertex_color function set_vertex_color!(shape::Shape, index::Integer, color::RGBA) detail.set_vertex_color!(shape._internal, from_julia_index(index), color) end export set_vertex_color! function get_vertex_texture_coordinate(shape::Shape, index::Integer) ::Vector2f return detail.get_vertex_texture_coordinate(shape._internal, from_julia_index(index)) end export get_vertex_texture_coordinate function set_vertex_texture_coordinate!(shape::Shape, index::Integer, coordinate::Vector2f) detail.set_vertex_texture_coordinate!(shape._internal, from_julia_index(index), coordinate) end export set_vertex_texture_coordinate! function get_vertex_position(shape::Shape, index::Integer) ::Vector3f detail.get_vertex_position(shape._internal, from_julia_index(index)) end export get_vertex_position function set_vertex_position!(shape::Shape, index::Integer, coordinate::Vector3f) detail.set_vertex_position!(shape._internal, from_julia_index(index), coordinate) end export set_vertex_position! @export_function Shape get_n_vertices Int64 @export_function Shape set_is_visible! Cvoid Bool b @export_function Shape get_is_visible Bool struct AxisAlignedRectangle top_left::Vector2f size::Vector2f end export AxisAlignedRectangle @export_function Shape get_bounding_box AxisAlignedRectangle @export_function Shape get_size Vector2f @export_function Shape set_centroid! Cvoid Vector2f centroid @export_function Shape get_centroid Vector2f @export_function Shape set_top_left! Cvoid Vector2f top_left @export_function Shape get_top_left Vector2f function rotate!(shape::Shape, angle::Angle, origin::Vector2f = Vector2f(0, 0)) detail.rotate!(shape._internal, convert(Cfloat, as_radians(angle)), origin.x, origin.y) end export rotate! set_texture!(shape::Shape, texture::TextureObject) = detail.set_texture!(shape._internal, texture._internal.cpp_object) export set_texture! remove_texture!(shape::Shape) = detail.set_texture!(shape._internal, Ptr{Cvoid}()) export remove_texture! set_color!(shape::Shape, color::RGBA) = detail.set_color!(shape._internal, color) set_color!(shape::Shape, color::HSVA) = detail.set_color!(shape._internal, hsva_to_rgba(color)) export set_color! Base.show(io::IO, x::Shape) = show_aux(io, x, :native_handle) ###### render_task.jl @export_type RenderTask SignalEmitter function RenderTask(shape::Shape; shader::Union{Shader, Nothing} = nothing, transform::Union{GLTransform, Nothing} = nothing, blend_mode::BlendMode = BLEND_MODE_NORMAL ) shader_ptr = isnothing(shader) ? Ptr{Cvoid}(0) : shader._internal.cpp_object transform_ptr = isnothing(transform) ? Ptr{Cvoid}(0) : transform._internal.cpp_object return RenderTask(detail._RenderTask(shape._internal, shader_ptr, transform_ptr, blend_mode)) end export RenderTask @export_function RenderTask render Cvoid @export_function RenderTask set_uniform_float! Cvoid String name Cfloat v @export_function RenderTask get_uniform_float Cfloat String name @export_function RenderTask set_uniform_int! Cvoid String name Cint v @export_function RenderTask get_uniform_int Cint String name @export_function RenderTask set_uniform_uint! Cvoid String name Cuint v @export_function RenderTask get_uniform_uint Cuint String name set_uniform_vec2!(task::RenderTask, name::String, v::Vector2f) = detail.set_uniform_vec2!(task._internal, name, v) export set_uniform_vec2! get_uniform_vec2(task::RenderTask, name::String) ::Vector2f = detail.get_uniform_vec2(task._internal, name) export get_uniform_vec2 set_uniform_vec3!(task::RenderTask, name::String, v::Vector3f) = detail.set_uniform_vec3!(task._internal, name, v) export set_uniform_vec3! get_uniform_vec3(task::RenderTask, name::String) ::Vector3f = detail.get_uniform_vec3(task._internal, name) export get_uniform_vec3 set_uniform_vec4!(task::RenderTask, name::String, v::Vector4f) = detail.set_uniform_vec4!(task._internal, name, v) export set_uniform_vec4! get_uniform_vec4(task::RenderTask, name::String) ::Vector4f = detail.get_uniform_vec4(task._internal, name) export get_uniform_vec4 set_uniform_rgba!(task::RenderTask, name::String, rgba::RGBA) = detail.set_uniform_rgba!(task._internal, name, rgba) export set_uniform_rgba! get_uniform_rgba(task::RenderTask, name::String) ::RGBA = detail.get_uniform_rgba(task._internal, name) export get_uniform_rgba set_uniform_hsva!(task::RenderTask, name::String, hsva::HSVA) = detail.set_uniform_hsva!(task._internal, name, hsva) export set_uniform_hsva! get_uniform_hsva(task::RenderTask, name::String) ::HSVA = detail.get_uniform_hsva(task._internal, name) export get_uniform_hsva set_uniform_transform!(task::RenderTask, name::String, transform::GLTransform) = detail.set_uniform_transform!(task._internal, name, transform._internal) export set_uniform_transform! get_uniform_transform(task::RenderTask, name::String) ::GLTransform = GLTransform(detail.get_uniform_transform(task._internal, name)) export get_uniform_transform Base.show(io::IO, x::RenderTask) = show_aux(io, x) ###### render_area.jl @export_enum AntiAliasingQuality begin ANTI_ALIASING_QUALITY_OFF ANTI_ALIASING_QUALITY_MINIMAL ANTI_ALIASING_QUALITY_GOOD ANTI_ALIASING_QUALITY_BETTER ANTI_ALIASING_QUALITY_BEST end @export_type RenderArea Widget @declare_native_widget RenderArea RenderArea(msaa_quality::AntiAliasingQuality = ANTI_ALIASING_QUALITY_OFF) = RenderArea(detail._RenderArea(msaa_quality)) add_render_task!(area::RenderArea, task::RenderTask) = detail.add_render_task!(area._internal, task._internal) export add_render_task! @export_function RenderArea clear_render_tasks! Cvoid @export_function RenderArea make_current Cvoid @export_function RenderArea queue_render Cvoid @export_function RenderArea clear Cvoid @export_function RenderArea render_render_tasks Cvoid @export_function RenderArea flush Cvoid function from_gl_coordinates(area::RenderArea, gl_coordinates::Vector2f) ::Vector2f return detail.from_gl_coordinates(area._internal, gl_coordinates) end export from_gl_coordinates function to_gl_coordinates(area::RenderArea, absolute_widget_space_coordinates::Vector2f) ::Vector2f return detail.to_gl_coordinates(area._internal, absolute_widget_space_coordinates) end export to_gl_coordinates @add_widget_signals RenderArea @add_signal_resize RenderArea @add_signal_render RenderArea Base.show(io::IO, x::RenderArea) = show_aux(io, x) else # if MOUSETRAP_ENABLE_OPENGL_COMPONENT @define_opengl_error_type BlendMode export BlendMode @define_opengl_error_type GLTransform export GLTransform @define_opengl_error_type Shape export Shape @define_opengl_error_type Shader export Shader @define_opengl_error_type ShaderType export ShaderType @define_opengl_error_type TextureWrapMode export TextureWrapMode @define_opengl_error_type TextureScaleMode export TextureScaleMode @define_opengl_error_type Texture export Texture @define_opengl_error_type RenderTexture export RenderTexture @define_opengl_error_type RenderTask export RenderTask @define_opengl_error_type RenderArea export RenderArea end # else MOUSETRAP_ENABLE_OPENGL_COMPONENT ###### animation.jl @export_enum AnimationState begin ANIMATION_STATE_IDLE ANIMATION_STATE_PAUSED ANIMATION_STATE_PLAYING ANIMATION_STATE_DONE end @export_enum AnimationTimingFunction begin ANIMATION_TIMING_FUNCTION_LINEAR ANIMATION_TIMING_FUNCTION_EXPONENTIAL_EASE_IN ANIMATION_TIMING_FUNCTION_EXPONENTIAL_EASE_OUT ANIMATION_TIMING_FUNCTION_EXPONENTIAL_SIGMOID ANIMATION_TIMING_FUNCTION_SINE_EASE_IN ANIMATION_TIMING_FUNCTION_SINE_EASE_OUT ANIMATION_TIMING_FUNCTION_SINE_SIGMOID ANIMATION_TIMING_FUNCTION_CIRCULAR_EASE_IN ANIMATION_TIMING_FUNCTION_CIRCULAR_EASE_OUT ANIMATION_TIMING_FUNCTION_CIRCULAR_SIGMOID ANIMATION_TIMING_FUNCTION_OVERSHOOT_EASE_IN ANIMATION_TIMING_FUNCTION_OVERSHOOT_EASE_OUT ANIMATION_TIMING_FUNCTION_OVERSHOOT_SIGMOID ANIMATION_TIMING_FUNCTION_ELASTIC_EASE_IN ANIMATION_TIMING_FUNCTION_ELASTIC_EASE_OUT ANIMATION_TIMING_FUNCTION_ELASTIC_SIGMOID ANIMATION_TIMING_FUNCTION_BOUNCE_EASE_IN ANIMATION_TIMING_FUNCTION_BOUNCE_EASE_OUT ANIMATION_TIMING_FUNCTION_BOUNCE_SIGMOID end @export_type Animation SignalEmitter function Animation(target::Widget, duration::Time) return Animation(detail._Animation(as_widget_pointer(target), convert(Float32, as_microseconds(duration)))) end @export_function Animation get_state AnimationState @export_function Animation play! Cvoid @export_function Animation pause! Cvoid @export_function Animation reset! Cvoid set_duration!(animation::Animation, duration::Time) = detail.set_duration(animation._internal, as_microseconds(duration)) export set_duration! get_duration(animation::Animation) ::Time = microseconds(detail.get_duration(animation._internal)) export get_duration @export_function Animation set_lower! Cvoid Number => Cdouble lower @export_function Animation get_lower Cdouble @export_function Animation set_upper! Cvoid Number => Cdouble upper @export_function Animation get_upper Cdouble @export_function Animation get_value Cdouble @export_function Animation get_repeat_count Csize_t @export_function Animation set_repeat_count! Cvoid Integer => Csize_t n @export_function Animation get_is_reversed Bool @export_function Animation set_is_reversed! Cvoid Bool is_reversed @export_function Animation set_timing_function! Cvoid AnimationTimingFunction tweening_mode @export_function Animation get_timing_function AnimationTimingFunction function on_tick!(f, animation::Animation, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (Animation, AbstractFloat, Data_t)) detail.on_tick!(animation._internal, function(animation_ref, value::Cdouble ) typed_f(Animation(animation_ref[]), value, data) end) end function on_tick!(f, animation::Animation) typed_f = TypedFunction(f, Cvoid, (Animation, AbstractFloat)) detail.on_tick!(animation._internal, function(animation_ref, value::Cdouble) typed_f(Animation(animation_ref[]), value) end) end export on_tick! function on_done!(f, animation::Animation, data::Data_t) where Data_t typed_f = TypedFunction(f, Cvoid, (Animation, Data_t)) detail.on_done!(animation._internal, function(animation_ref) typed_f(Animation(animation_ref[]), data) end) end function on_done!(f, animation::Animation) typed_f = TypedFunction(f, Cvoid, (Animation,)) detail.on_done!(animation._internal, function(animation_ref) typed_f(Animation(animation_ref[])) end) end export on_done! Base.show(io::IO, x::Animation) = show_aux(io, x, :value, :lower, :upper, :state, :timing_function) ###### transform_bin.jl @export_type TransformBin Widget @declare_native_widget TransformBin TransformBin() = TransformBin(detail._TransformBin()) function TransformBin(child::Widget) out = TransformBin() set_child!(out, child) return out end set_child!(transform_bin::TransformBin, child::Widget) = detail.set_child!(transform_bin._internal, as_widget_pointer(child)) export set_child! @export_function TransformBin remove_child! Cvoid @export_function TransformBin reset! Cvoid rotate!(bin::TransformBin, angle::Angle) = detail.rotate!(bin._internal, convert(Float32, as_degrees(angle))) export rotate! translate!(bin::TransformBin, offset::Vector2f) = detail.translate!(bin._internal, convert(Float32, offset.x), convert(Float32, offset.y)) export translate! @export_function TransformBin scale! Cvoid Number => Cfloat x Number => Cfloat y scale!(bin::TransformBin, both::Number) = scale!(bin, both, both) @export_function TransformBin skew! Cvoid Number => Cfloat x Number => Cfloat y @add_widget_signals TransformBin Base.show(io::IO, x::TransformBin) = show_aux(io, x) ###### style.jl add_css_class!(widget::Widget, class::String) = detail.add_css_class!(as_widget_pointer(widget), class) export add_css_class! remove_css_class!(widget::Widget, class::String) = detail.remove_css_class!(as_widget_pointer(widget), class) export remove_css_class! get_css_classes(widget::Widget) ::Vector{String} = detail.get_css_classes(as_widget_pointer(widget)) export get_css_classes add_css!(code::String) = detail.style_manager_add_css!(code) export add_css! serialize(color::RGBA) ::String = detail.style_manager_color_to_css_rgba(color.r, color.g, color.b, color.a) serialize(color::HSVA) ::String = detail.style_manager_color_to_css_hsva(color.h, color.s, color.v, color.a) export serialize ###### gl_canvas.jl @export_type GLArea Widget @declare_native_widget GLArea GLArea() = GLArea(detail._GLArea()) @add_widget_signals GLArea @add_signal_resize GLArea @add_signal_render GLArea @export_function GLArea make_current Cvoid @export_function GLArea queue_render Cvoid @export_function GLArea get_auto_render Bool @export_function GLArea set_auto_render! Cvoid Bool b Base.show(io::IO, x::GLArea) = show_aux(io, x) ###### key_codes.jl include("./key_codes.jl") ###### documentation include("./docs.jl") ###### compound_widget.jl macro define_compound_widget_signals() out = Expr(:block) for (signal, _) in Mousetrap.signal_descriptors connect_signal_name = :connect_signal_ * signal * :! push!(out.args, esc(:( function Mousetrap.$connect_signal_name(f, x::Widget) $connect_signal_name(f, Mousetrap.get_top_level_widget(x)) end ))) push!(out.args, esc(:( function Mousetrap.$connect_signal_name(f, x::Widget, data::Data_t) where Data_t $connect_signal_name(f, Mousetrap.get_top_level_widget(x), data) end ))) emit_signal_name = :emit_signal_ * signal push!(out.args, esc(:( function $emit_signal_name(x::Widget, args...) $emit_signal_name(Mousetrap.get_top_level_widget(x), args) end ))) disconnect_signal_name = :disconnect_signal_ * signal * :! push!(out.args, esc(:( function $disconnect_signal_name(x::Widget) $disconnect_signal_name(Mousetrap.get_top_level_widget(x)) end ))) set_signal_blocked_name = :set_signal_ * signal * :_blocked * :! push!(out.args, esc(:( function $set_signal_blocked_name(x::Widget, b) $set_signal_blocked_name(Mousetrap.get_top_level_widget(x), b) end ))) get_signal_blocked_name = :get_signal_ * signal * :_blocked push!(out.args, esc(:( function $get_signal_blocked_name(x::Widget) return $get_signal_blocked_name(Mousetrap.get_top_level_widget(x)) end ))) end return out end @define_compound_widget_signals() end ================================================ FILE: src/docgen/enums.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # GitHub: https://github.com/clemapfel/mousetrap.jl # Documentation: https://clemens-cords.com/mousetrap # # Copyright © 2023, Licensed under lGPL-3.0 # @document Alignment enum_docs(:Alignment, "Determines alignment of widgets along the horizontal or vertical axis.", [ :ALIGNMENT_CENTER, :ALIGNMENT_END, :ALIGNMENT_START ]) @document ALIGNMENT_START "Aligned left if horizontal, top if vertical" @document ALIGNMENT_CENTER "Aligned centered, regardless of orientation" @document ALIGNMENT_END "Aligned right if horizontal, bottom if vertical" @document AnimationState enum_docs(:AnimationState, "Current state of the animation.", [ :ANIMATION_STATE_IDLE, :ANIMATION_STATE_PAUSED, :ANIMATION_STATE_PLAYING, :ANIMATION_STATE_DONE, ]) @document ANIMATION_STATE_IDLE "Initial state of the animation" @document ANIMATION_STATE_PAUSED "Playing was started but `pause!` was called" @document ANIMATION_STATE_PLAYING "Playing was started using `play!`" @document ANIMATION_STATE_DONE "Animation went through all of its cycles and is now done" @document AnimationTimingFunction enum_docs(:AnimationTimingFunction, "Shape of the mathematical function that will be used to generate the `Animation`s value over its duration.", [ :ANIMATION_TIMING_FUNCTION_LINEAR, :ANIMATION_TIMING_FUNCTION_EXPONENTIAL_EASE_IN, :ANIMATION_TIMING_FUNCTION_EXPONENTIAL_EASE_OUT, :ANIMATION_TIMING_FUNCTION_EXPONENTIAL_SIGMOID, :ANIMATION_TIMING_FUNCTION_SINE_EASE_IN, :ANIMATION_TIMING_FUNCTION_SINE_EASE_OUT, :ANIMATION_TIMING_FUNCTION_SINE_SIGMOID, :ANIMATION_TIMING_FUNCTION_CIRCULAR_EASE_IN, :ANIMATION_TIMING_FUNCTION_CIRCULAR_EASE_OUT, :ANIMATION_TIMING_FUNCTION_CIRCULAR_SIGMOID, :ANIMATION_TIMING_FUNCTION_OVERSHOOT_EASE_IN, :ANIMATION_TIMING_FUNCTION_OVERSHOOT_EASE_OUT, :ANIMATION_TIMING_FUNCTION_OVERSHOOT_SIGMOID, :ANIMATION_TIMING_FUNCTION_ELASTIC_EASE_IN, :ANIMATION_TIMING_FUNCTION_ELASTIC_EASE_OUT, :ANIMATION_TIMING_FUNCTION_ELASTIC_SIGMOID, :ANIMATION_TIMING_FUNCTION_BOUNCE_EASE_IN, :ANIMATION_TIMING_FUNCTION_BOUNCE_EASE_OUT, :ANIMATION_TIMING_FUNCTION_BOUNCE_SIGMOID ]) @document ANIMATION_TIMING_FUNCTION_LINEAR "strictly increasing, linear shape" @document ANIMATION_TIMING_FUNCTION_EXPONENTIAL_EASE_IN "strictly increasing, exponential ease in" @document ANIMATION_TIMING_FUNCTION_EXPONENTIAL_EASE_OUT "strictly decreasing, exponential ease out" @document ANIMATION_TIMING_FUNCTION_EXPONENTIAL_SIGMOID "strictly increasing, exponential east in and ease out" @document ANIMATION_TIMING_FUNCTION_SINE_EASE_IN "strictly increasing, sinusoid ease in" @document ANIMATION_TIMING_FUNCTION_SINE_EASE_OUT "strictly decreasing, sinusoid ease out" @document ANIMATION_TIMING_FUNCTION_SINE_SIGMOID "strictly increasing, sinusoid ease in and ease out" @document ANIMATION_TIMING_FUNCTION_CIRCULAR_EASE_IN "strictly increasing, circular ease in" @document ANIMATION_TIMING_FUNCTION_CIRCULAR_EASE_OUT "strictly decreasing, circular ease out" @document ANIMATION_TIMING_FUNCTION_CIRCULAR_SIGMOID "strictly increasing, circular east in and ease out" @document ANIMATION_TIMING_FUNCTION_OVERSHOOT_EASE_IN "undershoots `lower`, then increases, ease in" @document ANIMATION_TIMING_FUNCTION_OVERSHOOT_EASE_OUT "increases, then overshoots above `higher`, ease out" @document ANIMATION_TIMING_FUNCTION_OVERSHOOT_SIGMOID "undershoots `lower`, increases, then overshoots `higher`, ease in and ease out" @document ANIMATION_TIMING_FUNCTION_ELASTIC_EASE_IN "simulates stretching of a spring, ease in" @document ANIMATION_TIMING_FUNCTION_ELASTIC_EASE_OUT "simulates stretching of a spring, ease out" @document ANIMATION_TIMING_FUNCTION_ELASTIC_SIGMOID "simulates stretching of a spring, ease in and ease out" @document ANIMATION_TIMING_FUNCTION_BOUNCE_EASE_IN "simulates bouncing of a ball under gravity, ease in" @document ANIMATION_TIMING_FUNCTION_BOUNCE_EASE_OUT "simulates bouncing of a ball under gravity, ease out" @document ANIMATION_TIMING_FUNCTION_BOUNCE_SIGMOID "simulates bouncing of a ball under gravity, ease in and ease out" @document AntiAliasingQuality enum_docs(:AntiAliasingQuality, "Number of samples when performing multi-sampled anti-aliasing (MSAA).", [ :ANTI_ALIASING_QUALITY_OFF, :ANTI_ALIASING_QUALITY_MINIMAL, :ANTI_ALIASING_QUALITY_GOOD, :ANTI_ALIASING_QUALITY_BETTER, :ANTI_ALIASING_QUALITY_BEST ]) @document ANTI_ALIASING_QUALITY_OFF "0 MSAA Samples, optimal speed" @document ANTI_ALIASING_QUALITY_MINIMAL "2 MSAA Samples" @document ANTI_ALIASING_QUALITY_GOOD "4 MSAA Samples" @document ANTI_ALIASING_QUALITY_BETTER "8 MSAA Samples" @document ANTI_ALIASING_QUALITY_BEST "16 MSAA Samples, optimal quality" @document BlendMode enum_docs(:BlendMode, "Governs how colors are mixed when two fragments are rendered on top of each other.", [ :BLEND_MODE_ADD, :BLEND_MODE_MAX, :BLEND_MODE_MIN, :BLEND_MODE_MULTIPLY, :BLEND_MODE_NONE, :BLEND_MODE_REVERSE_SUBTRACT, :BLEND_MODE_SUBTRACT ]) @document BLEND_MODE_NORMAL "Traditional alpha blending, alpha component of both colors is treated as emission." @document BLEND_MODE_ADD "result = source.rgb + destination.rgb" @document BLEND_MODE_MAX "result = max(source.rgb, destination.rgb)" @document BLEND_MODE_MIN "result = min(source.rgb, destination.rgb)" @document BLEND_MODE_MULTIPLY "result = source.rgb * destination.rgb" @document BLEND_MODE_NONE "result = destination.rgba" @document BLEND_MODE_REVERSE_SUBTRACT "result = source.rgb - destination.rgb" @document BLEND_MODE_SUBTRACT "result = destination.rgb - source.rgb" @document ButtonID enum_docs(:ButtonID, "ID of a mouse button, manufacturer-specific.", [ :BUTTON_ID_ANY, :BUTTON_ID_BUTTON_01, :BUTTON_ID_BUTTON_02, :BUTTON_ID_BUTTON_03, :BUTTON_ID_BUTTON_04, :BUTTON_ID_BUTTON_05, :BUTTON_ID_BUTTON_06, :BUTTON_ID_BUTTON_07, :BUTTON_ID_BUTTON_08, :BUTTON_ID_BUTTON_09, :BUTTON_ID_NONE ]) @document BUTTON_ID_ANY "Any button, regardless of ID" @document BUTTON_ID_BUTTON_01 "Button #1, usually the left mouse button, or a touchscreen press" @document BUTTON_ID_BUTTON_02 "Button #2, usually the right mouse button" @document BUTTON_ID_BUTTON_03 "Button #3, manufacturer specific" @document BUTTON_ID_BUTTON_04 "Button #4, manufacturer specific" @document BUTTON_ID_BUTTON_05 "Button #5, manufacturer specific" @document BUTTON_ID_BUTTON_06 "Button #6, manufacturer specific" @document BUTTON_ID_BUTTON_07 "Button #7, manufacturer specific" @document BUTTON_ID_BUTTON_08 "Button #8, manufacturer specific" @document BUTTON_ID_BUTTON_09 "Button #9, manufacturer specific" @document BUTTON_ID_NONE "No button, regardless of ID" @document CheckButtonState enum_docs(:CheckButtonState, "State of a [`CheckButton`](@ref)", [ :CHECK_BUTTON_STATE_ACTIVE, :CHECK_BUTTON_STATE_INACTIVE, :CHECK_BUTTON_STATE_INCONSISTENT ]) @document CHECK_BUTTON_STATE_ACTIVE "Active, usually displayed as a checkmark" @document CHECK_BUTTON_STATE_INACTIVE "Inactive, usually displayed as no check mark" @document CHECK_BUTTON_STATE_INCONSISTENT "Neither active nor inactive, usually displayed with a tilde `~`" @document CornerPlacement enum_docs(:CornerPlacement, "Placement of both scrollbars relative to the center of a `Viewport`.", [ :CORNER_PLACEMENT_BOTTOM_LEFT, :CORNER_PLACEMENT_BOTTOM_RIGHT, :CORNER_PLACEMENT_TOP_LEFT, :CORNER_PLACEMENT_TOP_RIGHT ]) @document CORNER_PLACEMENT_BOTTOM_LEFT "Horizontal scrollbar bottom, vertical scrollbar left" @document CORNER_PLACEMENT_BOTTOM_RIGHT "Horizontal scrollbar bottom, vertical scrollbar right" @document CORNER_PLACEMENT_TOP_LEFT "Horizontal scrollbar top, vertical scrollbar left" @document CORNER_PLACEMENT_TOP_RIGHT "Horizontal scrollbar top, vertical scrollbar right" @document CursorType enum_docs(:CursorType, "Determines what the user cursor will look like while it is inside the allocated area of the widget.", [ :CURSOR_TYPE_ALL_SCROLL, :CURSOR_TYPE_CELL, :CURSOR_TYPE_COLUMN_RESIZE, :CURSOR_TYPE_CONTEXT_MENU, :CURSOR_TYPE_CROSSHAIR, :CURSOR_TYPE_DEFAULT, :CURSOR_TYPE_EAST_RESIZE, :CURSOR_TYPE_GRAB, :CURSOR_TYPE_GRABBING, :CURSOR_TYPE_HELP, :CURSOR_TYPE_MOVE, :CURSOR_TYPE_NONE, :CURSOR_TYPE_NORTH_EAST_RESIZE, :CURSOR_TYPE_NORTH_RESIZE, :CURSOR_TYPE_NORTH_WEST_RESIZE, :CURSOR_TYPE_NOT_ALLOWED, :CURSOR_TYPE_POINTER, :CURSOR_TYPE_PROGRESS, :CURSOR_TYPE_ROW_RESIZE, :CURSOR_TYPE_SOUTH_EAST_RESIZE, :CURSOR_TYPE_SOUTH_RESIZE, :CURSOR_TYPE_SOUTH_WEST_RESIZE, :CURSOR_TYPE_TEXT, :CURSOR_TYPE_WAIT, :CURSOR_TYPE_WEST_RESIZE, :CURSOR_TYPE_ZOOM_IN, :CURSOR_TYPE_ZOOM_OUT ]) @document CURSOR_TYPE_ALL_SCROLL "Omni-directional scrolling" @document CURSOR_TYPE_CELL "Cross, used for selecting cells from a table" @document CURSOR_TYPE_COLUMN_RESIZE "Left-right arrow" @document CURSOR_TYPE_CONTEXT_MENU "Questionmark, instructs the user that clicking will open a context menu" @document CURSOR_TYPE_CROSSHAIR "Crosshair, used for making pixel-perfect selections" @document CURSOR_TYPE_DEFAULT "Default arrow pointer" @document CURSOR_TYPE_EAST_RESIZE "Left arrow" @document CURSOR_TYPE_GRAB "Hand, not yet grabbing" @document CURSOR_TYPE_GRABBING "Hand, currently grabbing" @document CURSOR_TYPE_HELP "Questionmark, instructs the user that clicking or hovering above this element will open a help menu" @document CURSOR_TYPE_MOVE "4-directional arrow" @document CURSOR_TYPE_NONE "Invisible cursor" @document CURSOR_TYPE_NORTH_EAST_RESIZE "Up-left arrow" @document CURSOR_TYPE_NORTH_RESIZE "Up-arrow" @document CURSOR_TYPE_NORTH_WEST_RESIZE "Up-right arrow" @document CURSOR_TYPE_NOT_ALLOWED "Instructs the user that this action is currently disabled" @document CURSOR_TYPE_POINTER "Hand pointing" @document CURSOR_TYPE_PROGRESS "Spinning animation, signifies that the object is currently busy" @document CURSOR_TYPE_ROW_RESIZE "Up-down arrow" @document CURSOR_TYPE_SOUTH_EAST_RESIZE "Down-left arrow" @document CURSOR_TYPE_SOUTH_RESIZE "Down arrow" @document CURSOR_TYPE_SOUTH_WEST_RESIZE "Down-right arrow" @document CURSOR_TYPE_TEXT "Caret" @document CURSOR_TYPE_WAIT "Loading animation, Instructs the user that an action will become available soon" @document CURSOR_TYPE_WEST_RESIZE "Right arrow" @document CURSOR_TYPE_ZOOM_IN "Lens, usually with a plus icon" @document CURSOR_TYPE_ZOOM_OUT "Lens, usually with a minus icon" @document DeviceAxis enum_docs(:DeviceAxis, "Axes of stylus- and touchpad device, captured by `StylusEventController`. Not all manufacturers support all or even any of these.", [ :DEVICE_AXIS_DELTA_X, :DEVICE_AXIS_DELTA_Y, :DEVICE_AXIS_DISTANCE, :DEVICE_AXIS_PRESSURE, :DEVICE_AXIS_ROTATION, :DEVICE_AXIS_SLIDER, :DEVICE_AXIS_WHEEL, :DEVICE_AXIS_X, :DEVICE_AXIS_X_TILT, :DEVICE_AXIS_Y, :DEVICE_AXIS_Y_TILT ]) @document DEVICE_AXIS_DELTA_X "Horizontal offset" @document DEVICE_AXIS_DELTA_Y "Vertical offset" @document DEVICE_AXIS_DISTANCE "Distance between the stylus' tip and the touchpad" @document DEVICE_AXIS_PRESSURE "Current pressure of the stylus" @document DEVICE_AXIS_ROTATION "Rotation of the stylus, usually in radians" @document DEVICE_AXIS_SLIDER "State of the stylus slider" @document DEVICE_AXIS_WHEEL "State of the stylus scroll wheel" @document DEVICE_AXIS_X "X-position of the stylus" @document DEVICE_AXIS_X_TILT "Tilt along the horizontal axis" @document DEVICE_AXIS_Y "Y-position of the stylus" @document DEVICE_AXIS_Y_TILT "Tilt along the vertical axis" @document EllipsizeMode enum_docs(:EllipsizeMode, "Determines how ellipses are inserted when a `Label`s allocated area exceeds the space it is allowed to allocated.", [ :ELLIPSIZE_MODE_END, :ELLIPSIZE_MODE_MIDDLE, :ELLIPSIZE_MODE_NONE, :ELLIPSIZE_MODE_START ]) @document ELLIPSIZE_MODE_END "Inserted at the end: `text...`" @document ELLIPSIZE_MODE_MIDDLE "Inserted in the middle: `te...xt`" @document ELLIPSIZE_MODE_NONE "No eclipsing will take place" @document ELLIPSIZE_MODE_START "Insert at the start: `...text`" @document FileChooserAction enum_docs(:FileChooserAction, "Determines layout, which, and how many files/folders a user can select when using [`FileChooser`](@ref).", [ :FILE_CHOOSER_ACTION_OPEN_FILE, :FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES, :FILE_CHOOSER_ACTION_SAVE, :FILE_CHOOSER_ACTION_SELECT_FOLDER, :FILE_CHOOSER_ACTION_SELECT_MULTIPLE_FOLDERS ]) @document FILE_CHOOSER_ACTION_OPEN_FILE "Select exactly one file" @document FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES "Select one or more files" @document FILE_CHOOSER_ACTION_SAVE "Choose a name and location" @document FILE_CHOOSER_ACTION_SELECT_FOLDER "Select exactly one folder" @document FILE_CHOOSER_ACTION_SELECT_MULTIPLE_FOLDERS "Select one or more folders" @document FileMonitorEvent enum_docs(:FileMonitorEvent, "Classifies user behavior that triggered the callback of [`FileMonitor`](@ref).", [ :FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, :FILE_MONITOR_EVENT_CHANGED, :FILE_MONITOR_EVENT_CHANGES_DONE_HINT, :FILE_MONITOR_EVENT_CREATED, :FILE_MONITOR_EVENT_DELETED, :FILE_MONITOR_EVENT_MOVED_IN, :FILE_MONITOR_EVENT_MOVED_OUT, :FILE_MONITOR_EVENT_RENAMED ]) @document FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED "Metadata about monitored file changed" @document FILE_MONITOR_EVENT_CHANGED "Content of monitored file changed" @document FILE_MONITOR_EVENT_CHANGES_DONE_HINT "Emitted to signal the end of a series of changes" @document FILE_MONITOR_EVENT_CREATED "A new file was created inside the monitored folder" @document FILE_MONITOR_EVENT_DELETED "File, Folder, or file inside the monitored folder was deleted" @document FILE_MONITOR_EVENT_MOVED_IN "File was moved into the monitored folder" @document FILE_MONITOR_EVENT_MOVED_OUT "File was moved out of the monitored folder" @document FILE_MONITOR_EVENT_RENAMED "File or folder was renamed" @document InterpolationType enum_docs(:InterpolationType, "Determines interpolation algorithm used when scaling [`Image`](@ref).", [ :INTERPOLATION_TYPE_BILINEAR, :INTERPOLATION_TYPE_HYPERBOLIC, :INTERPOLATION_TYPE_NEAREST, :INTERPOLATION_TYPE_TILES ]) @document INTERPOLATION_TYPE_BILINEAR "Linear interpolation, adequate speed and quality" @document INTERPOLATION_TYPE_HYPERBOLIC "Cubic interpolation, slow speed, high quality" @document INTERPOLATION_TYPE_NEAREST "Nearest neighbor interpolation, fastest but no filtering takes place" @document INTERPOLATION_TYPE_TILES "Linear when scaling down, nearest neighbor when scaling up." @document JustifyMode enum_docs(:JustifyMode, "Determines how words are arranged along the horizontal axis of a [`Label`](@ref) or [`TextView`](@ref).", [ :JUSTIFY_MODE_CENTER, :JUSTIFY_MODE_FILL, :JUSTIFY_MODE_LEFT, :JUSTIFY_MODE_RIGHT ]) @document JUSTIFY_MODE_CENTER "Push towards the center" @document JUSTIFY_MODE_FILL "Expand such that the entire width is filled" @document JUSTIFY_MODE_LEFT "Push towards left" @document JUSTIFY_MODE_RIGHT "Push towards right" @document LabelWrapMode enum_docs(:LabelWrapMode, "Determines at which point in a `Label`s contents a linebreak will be inserted.", [ :LABEL_WRAP_MODE_NONE, :LABEL_WRAP_MODE_ONLY_ON_CHAR, :LABEL_WRAP_MODE_ONLY_ON_WORD, :LABEL_WRAP_MODE_WORD_OR_CHAR ]) @document LABEL_WRAP_MODE_NONE "Never wrap, will always be exactly one line" @document LABEL_WRAP_MODE_ONLY_ON_CHAR "Insert linebreaks after a character" @document LABEL_WRAP_MODE_ONLY_ON_WORD "Insert linebreaks before a space between two words" @document LABEL_WRAP_MODE_WORD_OR_CHAR "Insert linebreak after a character or before the space between two words" @document LevelBarMode enum_docs(:LevelBarMode, "Determines how a [`LevelBar`](@ref) displays its fraction.", [ :LEVEL_BAR_MODE_CONTINUOUS, :LEVEL_BAR_MODE_DISCRETE ]) @document LEVEL_BAR_MODE_CONTINUOUS "Continuous bar, displays floating point value" @document LEVEL_BAR_MODE_DISCRETE "Segmented bar, displays integer value" @document Orientation enum_docs(:Orientation, "Determines orientation of a widget.", [ :ORIENTATION_HORIZONTAL, :ORIENTATION_VERTICAL ]) @document ORIENTATION_HORIZONTAL "Align left-to-right along the x-axis" @document ORIENTATION_VERTICAL "Align top-to-bottom along the y-axis" @document PanDirection enum_docs(:PanDirection, "Direction of a pan gesture recognized by [`PanEventController`](@ref).", [ :PAN_DIRECTION_DOWN, :PAN_DIRECTION_LEFT, :PAN_DIRECTION_RIGHT, :PAN_DIRECTION_UP ]) @document PAN_DIRECTION_DOWN "Pan up-down" @document PAN_DIRECTION_LEFT "Pan left-right" @document PAN_DIRECTION_RIGHT "Pan right-left" @document PAN_DIRECTION_UP "Pen down-up" @document PropagationPhase enum_docs(:PropagationPhase, "Determines at which part during the main loop event propagation an event controller will consume the event, cf. https://developer-old.gnome.org/gtk4/stable/event-propagation.html", [ :PROPAGATION_PHASE_BUBBLE, :PROPAGATION_PHASE_CAPTURE, :PROPAGATION_PHASE_NONE, :PROPAGATION_PHASE_TARGET ]) @document PROPAGATION_PHASE_BUBBLE "Consume event during propagation \"upwards\", from child to parent" @document PROPAGATION_PHASE_CAPTURE "Consume event during propagation \"downwards\", from parent to child" @document PROPAGATION_PHASE_NONE "Do not capture events" @document PROPAGATION_PHASE_TARGET "Consume events when the widget targets its event controllers with events" @document RelativePosition enum_docs(:RelativePosition, "Relative position of one object to another.", [ :RELATIVE_POSITION_ABOVE, :RELATIVE_POSITION_BELOW, :RELATIVE_POSITION_LEFT_OF, :RELATIVE_POSITION_RIGHT_OF ]) @document RELATIVE_POSITION_ABOVE "Object is above another" @document RELATIVE_POSITION_BELOW "Object is below another" @document RELATIVE_POSITION_LEFT_OF "Object is left of another" @document RELATIVE_POSITION_RIGHT_OF "Object is right of another" @document RevealerTransitionType enum_docs(:RevealerTransitionType, "Determines animation type when of [`Revealer`] showing or hiding its child.", [ :REVEALER_TRANSITION_TYPE_CROSSFADE, :REVEALER_TRANSITION_TYPE_NONE, :REVEALER_TRANSITION_TYPE_SLIDE_DOWN, :REVEALER_TRANSITION_TYPE_SLIDE_LEFT, :REVEALER_TRANSITION_TYPE_SLIDE_RIGHT, :REVEALER_TRANSITION_TYPE_SLIDE_UP, :REVEALER_TRANSITION_TYPE_SWING_DOWN, :REVEALER_TRANSITION_TYPE_SWING_LEFT, :REVEALER_TRANSITION_TYPE_SWING_RIGHT, :REVEALER_TRANSITION_TYPE_SWING_UP ]) @document REVEALER_TRANSITION_TYPE_CROSSFADE "Crossfade, slowly increasing / decreasing opacity" @document REVEALER_TRANSITION_TYPE_NONE "Instantly reveal the widget" @document REVEALER_TRANSITION_TYPE_SLIDE_DOWN "Slide from top to bottom" @document REVEALER_TRANSITION_TYPE_SLIDE_LEFT "Slide from right to left" @document REVEALER_TRANSITION_TYPE_SLIDE_RIGHT "Slide from left to right" @document REVEALER_TRANSITION_TYPE_SLIDE_UP "Slide from bottom to top" @document REVEALER_TRANSITION_TYPE_SWING_DOWN "Swing from top to bottom" @document REVEALER_TRANSITION_TYPE_SWING_LEFT "Swing from right to left" @document REVEALER_TRANSITION_TYPE_SWING_RIGHT "Swing from left to right" @document REVEALER_TRANSITION_TYPE_SWING_UP "Swing from bottom to top" @document ScrollType enum_docs(:ScrollType, "Classification of keyboard event that triggered the `scroll_child` event of a [`Viewport`](@ref).", [ :SCROLL_TYPE_JUMP, :SCROLL_TYPE_NONE, :SCROLL_TYPE_PAGE_BACKWARD, :SCROLL_TYPE_PAGE_DOWN, :SCROLL_TYPE_PAGE_FORWARD, :SCROLL_TYPE_PAGE_LEFT, :SCROLL_TYPE_PAGE_RIGHT, :SCROLL_TYPE_PAGE_UP, :SCROLL_TYPE_SCROLL_END, :SCROLL_TYPE_SCROLL_START, :SCROLL_TYPE_STEP_BACKWARD, :SCROLL_TYPE_STEP_DOWN, :SCROLL_TYPE_STEP_FORWARD, :SCROLL_TYPE_STEP_LEFT, :SCROLL_TYPE_STEP_RIGHT, :SCROLL_TYPE_STEP_UP ]) @document SCROLL_TYPE_JUMP "Jump keybinding, if present" @document SCROLL_TYPE_NONE "No keybinding was used" @document SCROLL_TYPE_PAGE_BACKWARD "Move one page backward" @document SCROLL_TYPE_PAGE_DOWN "Move one page vertically down" @document SCROLL_TYPE_PAGE_FORWARD "Move one page forward" @document SCROLL_TYPE_PAGE_LEFT "Move one page vertically left" @document SCROLL_TYPE_PAGE_RIGHT "Move one page horizontally right" @document SCROLL_TYPE_PAGE_UP "Move one page vertically up" @document SCROLL_TYPE_SCROLL_END "Jump to the end" @document SCROLL_TYPE_SCROLL_START "Jump to the start" @document SCROLL_TYPE_STEP_BACKWARD "Move one scroll step backward" @document SCROLL_TYPE_STEP_DOWN "Move one scroll step vertically down" @document SCROLL_TYPE_STEP_FORWARD "Move on scroll step forward" @document SCROLL_TYPE_STEP_LEFT "Move one scroll step horizontally left" @document SCROLL_TYPE_STEP_RIGHT "Move one scroll step horizontally right" @document SCROLL_TYPE_STEP_UP "Move on scroll step vertically up" @document ScrollbarVisibilityPolicy enum_docs(:ScrollbarVisibilityPolicy, "Determines when / if a scrollbar of a [`Viewport`](@ref) reveals itself.", [ :SCROLLBAR_VISIBILITY_POLICY_ALWAYS, :SCROLLBAR_VISIBILITY_POLICY_AUTOMATIC, :SCROLLBAR_VISIBILITY_POLICY_NEVER ]) @document SCROLLBAR_VISIBILITY_POLICY_ALWAYS "Stay revealed at all times" @document SCROLLBAR_VISIBILITY_POLICY_AUTOMATIC "Reveal when the user's cursor enters the [`Viewport`](@ref), hide when it exits" @document SCROLLBAR_VISIBILITY_POLICY_NEVER "Stay hidden at all times" @document SectionFormat enum_docs(:SectionFormat, "Visual layout of a [`MenuModel`](@ref) \"section\"-type item.", [ :SECTION_FORMAT_CIRCULAR_BUTTONS, :SECTION_FORMAT_HORIZONTAL_BUTTONS, :SECTION_FORMAT_HORIZONTAL_BUTTONS_LEFT_TO_RIGHT, :SECTION_FORMAT_HORIZONTAL_BUTTONS_RIGHT_TO_LEFT, :SECTION_FORMAT_INLINE_BUTTONS, :SECTION_FORMAT_NORMAL ]) @document SECTION_FORMAT_CIRCULAR_BUTTONS "Circular buttons" @document SECTION_FORMAT_HORIZONTAL_BUTTONS "Rectangular buttons" @document SECTION_FORMAT_HORIZONTAL_BUTTONS_LEFT_TO_RIGHT "Rectangular buttons, pushed to the left" @document SECTION_FORMAT_HORIZONTAL_BUTTONS_RIGHT_TO_LEFT "Rectangular buttons, pushed to the right" @document SECTION_FORMAT_INLINE_BUTTONS "Buttons are appended right of the section title" @document SECTION_FORMAT_NORMAL "Default layout" @document SelectionMode enum_docs(:SelectionMode, "Governs if and how many elements can be selected.", [ :SELECTION_MODE_MULTIPLE, :SELECTION_MODE_NONE, :SELECTION_MODE_SINGLE ]) @document SELECTION_MODE_MULTIPLE "Zero or more widgets can be selected" @document SELECTION_MODE_NONE "Exactly zero widgets can be selected" @document SELECTION_MODE_SINGLE "Exactly one widget can be selected" @document ShaderType enum_docs(:ShaderType, "Type of OpenGL shaderprogram component.", [ :SHADER_TYPE_FRAGMENT, :SHADER_TYPE_VERTEX ]) @document SHADER_TYPE_FRAGMENT "Fragment shader" @document SHADER_TYPE_VERTEX "Vertex shader" @document ShortcutScope enum_docs(:ShortcutScope, "Determines at which scope a shortcut will be captured.", [ :SHORTCUT_SCOPE_GLOBAL, :SHORTCUT_SCOPE_LOCAL #:SHORTCUT_SCOPE_MANAGED ]) @document SHORTCUT_SCOPE_GLOBAL "If the most top-level parent of the widget holds focus, the shortcut is captured" @document SHORTCUT_SCOPE_LOCAL "If the widget the event controller was added to holds focus, the shortcut is captured" # @document SHORTCUT_SCOPE_MANAGED "" @document StackTransitionType enum_docs(:StackTransitionType, "Determines animation that plays when a [`Stack`](@ref) switches from one of its pages to another.", [ :STACK_TRANSITION_TYPE_CROSSFADE, :STACK_TRANSITION_TYPE_NONE, :STACK_TRANSITION_TYPE_OVER_DOWN, :STACK_TRANSITION_TYPE_OVER_LEFT, :STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT, :STACK_TRANSITION_TYPE_OVER_RIGHT, :STACK_TRANSITION_TYPE_OVER_UP, :STACK_TRANSITION_TYPE_OVER_UP_DOWN, :STACK_TRANSITION_TYPE_ROTATE_LEFT, :STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT, :STACK_TRANSITION_TYPE_ROTATE_RIGHT, :STACK_TRANSITION_TYPE_SLIDE_DOWN, :STACK_TRANSITION_TYPE_SLIDE_LEFT, :STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT, :STACK_TRANSITION_TYPE_SLIDE_RIGHT, :STACK_TRANSITION_TYPE_SLIDE_UP, :STACK_TRANSITION_TYPE_SLIDE_UP_DOWN, :STACK_TRANSITION_TYPE_UNDER_DOWN, :STACK_TRANSITION_TYPE_UNDER_LEFT, :STACK_TRANSITION_TYPE_UNDER_RIGHT, :STACK_TRANSITION_TYPE_UNDER_UP ]) @document STACK_TRANSITION_TYPE_CROSSFADE "Crossfade, slowly increasing opacity" @document STACK_TRANSITION_TYPE_NONE "Instantly transition" @document STACK_TRANSITION_TYPE_OVER_DOWN "Slide next page over current, from top to bottom" @document STACK_TRANSITION_TYPE_OVER_LEFT "Slide next page over current, from right to left" @document STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT "Slide next page over current, exiting left and entering right" @document STACK_TRANSITION_TYPE_OVER_RIGHT "Slide next page over current, from left to right" @document STACK_TRANSITION_TYPE_OVER_UP "Slide next page over current, from bottom to top" @document STACK_TRANSITION_TYPE_OVER_UP_DOWN "Slide next page over current, exiting up and entering bottom" @document STACK_TRANSITION_TYPE_ROTATE_LEFT "Rotate the previous page from right to left, then enter the next page from right to left" @document STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT "Rotate the previous page to the right to left, then enter next next page from left to right" @document STACK_TRANSITION_TYPE_ROTATE_RIGHT "Rotate the previous page from left to right, then enter the next page from left to right" @document STACK_TRANSITION_TYPE_SLIDE_DOWN "Slide the current page top to bottom, enter the next page from top to bottom" @document STACK_TRANSITION_TYPE_SLIDE_LEFT "Slide the current page right to left, enter the next page from right to left" @document STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT "Slide the current page left, enter the next page from left to right" @document STACK_TRANSITION_TYPE_SLIDE_RIGHT "Slide the current page left to right, enter the next page left to right" @document STACK_TRANSITION_TYPE_SLIDE_UP "Slide the current page bottom to top, enter the next page bottom to top" @document STACK_TRANSITION_TYPE_SLIDE_UP_DOWN "Slide the current page bottom top, enter next page top to bottom" @document STACK_TRANSITION_TYPE_UNDER_DOWN "Slide next page under current, from top to bottom" @document STACK_TRANSITION_TYPE_UNDER_LEFT "Slide next page under current, from left to right" @document STACK_TRANSITION_TYPE_UNDER_RIGHT "Slide next page under current, from right to left" @document STACK_TRANSITION_TYPE_UNDER_UP "Slide next page under current, from bottom to top" @document TextureScaleMode enum_docs(:TextureScaleMode, "Determines how [`Texture`](@ref) filters when scaled.", [ :TEXTURE_SCALE_MODE_LINEAR, :TEXTURE_SCALE_MODE_NEAREST ]) @document TEXTURE_SCALE_MODE_LINEAR "Linear interpolation" @document TEXTURE_SCALE_MODE_NEAREST "Nearest-neighbor interpolation" @document TextureWrapMode enum_docs(:TextureWrapMode, "Determines color of fragments with a texture coordinate outside of `[0, 1]`.", [ :TEXTURE_WRAP_MODE_MIRROR, :TEXTURE_WRAP_MODE_ONE, :TEXTURE_WRAP_MODE_REPEAT, :TEXTURE_WRAP_MODE_STRETCH, :TEXTURE_WRAP_MODE_ZERO ]) @document TEXTURE_WRAP_MODE_MIRROR "Mirror along the closest edge" @document TEXTURE_WRAP_MODE_ONE "RGBA(1, 1, 1, 1)" @document TEXTURE_WRAP_MODE_REPEAT "Repeat along the closest edge" @document TEXTURE_WRAP_MODE_STRETCH "Stretch the outermost fragment of the closest edge" @document TEXTURE_WRAP_MODE_ZERO "RGBA(0, 0, 0, 0)" @document Theme enum_docs(:Theme, "Determines the look of all widgets when made active using `Application`s `set_current_theme!`.", [ :THEME_DEFAULT_LIGHT, :THEME_DEFAULT_DARK, :THEME_HIGH_CONTRAST_LIGHT, :THEME_HIGH_CONTRAST_DARK ]) @document THEME_DEFAULT_LIGHT "Default light theme, this theme is available for all operating systems." @document THEME_DEFAULT_DARK "Default dark theme, this theme is available for all operating systems." @document THEME_HIGH_CONTRAST_LIGHT "Default high contrast theme, light variant. Not all operating systems support this." @document THEME_HIGH_CONTRAST_DARK "Default high contrast theme, dark variant. Not all operating systems support this." @document TickCallbackResult enum_docs(:TickCallbackResult, "Return value of a callback registered via [`set_tick_callback!`](@ref). Determines whether the callback should be removed.", [ :TICK_CALLBACK_RESULT_CONTINUE, :TICK_CALLBACK_RESULT_DISCONTINUE ]) @document TICK_CALLBACK_RESULT_CONTINUE "Continue the callback, it will be invoked the next frame" @document TICK_CALLBACK_RESULT_DISCONTINUE "Remove the callback, it will no longer be invoked" @document ToolType enum_docs(:ToolType, "Tool type classification of a stylus, not all manufactures support all or even any of these.", [ :TOOL_TYPE_AIRBRUSH, :TOOL_TYPE_BRUSH, :TOOL_TYPE_ERASER, :TOOL_TYPE_LENS, :TOOL_TYPE_MOUSE, :TOOL_TYPE_PEN, :TOOL_TYPE_PENCIL, :TOOL_TYPE_UNKNOWN ]) @document TOOL_TYPE_AIRBRUSH "Airbrush tool" @document TOOL_TYPE_BRUSH "Variable-width brush" @document TOOL_TYPE_ERASER "Erase tool" @document TOOL_TYPE_LENS "Zoom tool" @document TOOL_TYPE_MOUSE "Cursor tol" @document TOOL_TYPE_PEN "Basic pen tool" @document TOOL_TYPE_PENCIL "Fixed-width brush" @document TOOL_TYPE_UNKNOWN "None of the other values of `ToolType`" @document WindowCloseRequestResult enum_docs(:WindowCloseRequestResult, "Return value of signal `close_request` of [`Window`](@ref). Determines whether the window should close when requested to.", [ :WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE, :WINDOW_CLOSE_REQUEST_RESULT_PREVENT_CLOSE ]) @document WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE "Allow invocation of the default handler, the window will close." @document WINDOW_CLOSE_REQUEST_RESULT_PREVENT_CLOSE "Prevent the window from closing." ================================================ FILE: src/docgen/functions.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # GitHub: https://github.com/clemapfel/mousetrap.jl # Documentation: https://clemens-cords.com/mousetrap # # Copyright © 2023, Licensed under lGPL-3.0 # @document Circle """ ``` Circle(center::Vector2f, radius::Number, n_outer_vertices::Integer) -> Shape ``` Create a shape as a circle, defined by its center and radius. In OpenGL coordinates. """ @document CircularRing """ ``` CircularRing(center::Vector2f, outer_radius::Number, thickness::Number, n_outer_vertices::Integer) -> Shape ``` Create a shape as a circular ring, defined by its center, `outer_radius`, which is the distance to the out perimeter, and `thickness` which is the distance between the inner and outer perimeter. """ @document Ellipse """ ``` Ellipse(center::Vector2f, x_radius::Number, y_radius::Number, n_outer_vertices::Integer) -> Shape ``` Create a shape as an ellipse, defined by its center and radii along the x- and y-dimension, in OpenGL coordinates. """ @document EllipticalRing """ ``` EllipticalRing(center::Vector2f, outer_x_radius::Number, outer_y_radius::Number, x_thickness::Number, y_thickness::Number, n_outer_vertices::Unsigned) -> Shape ``` Create a shape as an elliptical ring, where + `center`: center of the ring + `outer_x_radius`: distance between the center and the outer perimeter along the x-axis + `outer_y_radius`: distance between the center and the outer perimeter along the y-axis + `x_thickness`: distance between the outer and inner perimeter along the x-axis + `y_thickness``: distance between the outer and inner perimeter along the y-axis In OpenGL coordinates. """ @document Line """ ``` Line(a::Vector2f, b::Vector2f) -> Shape ``` Create a shape as a 1-fragment thick line between two points, in OpenGL coordinates. """ @document Lines """ ``` Lines(::Vector{Pair{Vector2f, Vector2f}) -> Shape ``` Create a shape as a set of unconnected lines, vertex positions in OpenGL coordinates. """ @document LineStrip """ ``` LineStrip(points::Vector{Vector2f}) -> Shape ``` Create a shape as a line strip. For points `{a1, a2, ..., an}`, this will be a set of connected lines `{a1, a2}, {a2, a3}, ..., {an-1, an}`, in OpenGL coordinates. """ @document Outline """ ``` Outline(other::Shape) -> Shape ``` Create a shape as an outline of another shape. """ @document Point """ ``` Point(position::Vector2f) -> Shape ``` Create a shape as 1-fragment point, in OpenGL coordinates. """ @document Points """ ``` Point(positions::Vector{Vector2f}) -> Shape ``` Create a shape as a set of unconnected points, in OpenGL coordinates. """ @document Polygon """ ``` Polygon(points::Vector{Vector2f}) -> Shape ``` Create a shape as a convex polygon, the outer hull of `points` will be computed, but no vertex elimination will take place. In OpenGL coordiantes. """ @document Rectangle """ ``` Rectangle(top_left::Vector2f, size::Vector2f) -> Shape ``` Create a shape as an axis-aligned rectangle, in OpenGL coordinates. """ @document RectangularFrame """ ``` RectangularFrame(top_left::Vector2f, outer_size::Vector2f, x_width::Number, y_width::Number) -> Shape ``` Create a shape as a rectangular frame, where `x_width`, `y_width` are the "thickness" of the frames filled area. In OpenGL coordinates. """ @document Triangle """ ``` Triangle(a::Vector2f, b::Vector2f, c::Vector2f) -> Shape ``` Create a shape as a triangle defined by three points, in OpenGL coordinates. """ @document Wireframe """ ``` Wireframe(points::Vector{Vector2f}) -> Shape ``` Create a shape as a wireframe. For points `{a1, a2, a3, ..., an}`, the shape will be a connected series of lines `{a1, a2}, {a2, a3}, ..., {an-1, an}, {an, a1}`. In OpenGL coordinates. """ @document activate! """ ``` activate!(::Widget) ``` If the widget is activatable, trigger it. Depending on the widget, this may not necessarily emit signal `activate`. Use [`emit_signal_activate`](@ref) to manually emit the signal instead. --- ``` activate!(::Action) ``` Trigger the action's callback. This will also emit signal `activated`. """ @document add_action! """ ``` add_action!(app::Application, action::Action) ``` Register an action with the application. This is usually done automatically. --- ``` add_action!(model::MenuModel, label::String, action::Action) ``` Add an "action"-type item to the menu model --- ``` add_action!(shortcut_controller::ShortcutEventController, action::Action) ``` Register an action with the shortcut controller. Once connected to a widget, the controller will listen for keyboard events associated with any registered action, triggering that action when the shortcut is recognized. """ @document add_allow_all_supported_image_formats! """ ``` add_allow_all_supported_image_formats!(::FileFilter) ``` Let all file formats pass through the filter that can be loaded `Image`, `ImageDisplay`, or `Icon`. """ @document add_allowed_mime_type! """ ``` add_allowed_mime_type!(::FileFilter, mime_type_id::String) ``` Let all files pass through the filter whose MIME type is equal to the given string. """ @document add_allowed_pattern! """ ``` add_allowed_pattern!(::FileFilter, pattern::String) ``` Let all files pass through the filter whose name match the given shell-style glob. ## Example ```julia filter = FilterFilter("pass_julia_files") add_allowed_pattern!(filter, "*.jl") ``` """ @document add_allowed_suffix! """ ``` add_allowed_suffix!(::FileFilter, suffix::String) ``` Let all files pass through the filter whose file extension is equal to the given string, where `suffix` should **not** contain a dot. ## Example ```julia filter = FilterFilter("pass_julia_files") add_allowed_suffix!(filter, "jl") # "jl", not ".jl" ``` ```` """ @document add_button! """ ``` add_button!(::AlertDialog, label::String) -> Integer ``` Add a new button to the dialog. If the button is clicked, the dialog closes automatically. The return value of this function is the button's ID, which should be stored to later reference the button. """ @document add_child! """ ``` add_child!(stack::Stack, ::Widget, title::String) ``` Add a new stack page that will be uniquely identified by `title`. --- ``` add_child!(fixed::Fixed, ::Widget, position::Vector2f) ``` Add a widget at given position, in absolute widget-space coordinates. """ @document add_css! """ ``` add_css!(code::String) -> Cvoid ``` Execute CSS code and add it to the global style manager. If compiled successfully, any class defined will be available to be applied to a widget using `add_css_class!`. ## Example ```julia mousetrap.add_css!(\"\"\" .custom { color: green; border-radius: 10%; } \"\"\") add_css_class!(window, ".custom") ``` """ @document add_css_class! """ ``` add_css_class!(::Widget, class::String) ``` Apply a custom style class to the widget. Use `add_css!` to define a CSS class. ## Example ```julia mousetrap.add_css!(\"\"\" .custom { color: green; border-radius: 10%; } \"\"\") add_css_class!(window, ".custom") ``` """ @document add_controller! """ ``` add_controller!(::Widget, controller::EventController) ``` Add an event controller to the widget. Once that widget is realized, the controller will start listening for events. """ @document add_filter! """ ``` add_filter!(::FileChooser, ::FileFilter) ``` Add a filter to the selection of available file filters. Use [`set_initial_filter!`](@ref) to make it the currently selected filter. """ @document add_icon! """ ``` add_icon!(model::MenuModel, icon::Icon, action::Action) ``` Add an "icon"-type item to the menu model. """ @document add_mark! """ ``` add_mark!(::Scale, value::Number, position::RelativePosition, [label::String]) ``` Add a mark with an option label. `position` determines where the mark is shown relative to the scales center. """ @document add_marker! """ ``` add_marker!(::LevelBar, name::String, value::AbstractFloat) ``` Add a marker with label to the level bar. """ @document add_overlay! """ ``` add_overlay!(overlay::Overlay, child::Widget ; [include_in_measurement::Bool = true, clip::Bool = false]) ``` Add an additional overlay widget. It will be display "on top" of previously added widgets. If `include_in_measurement` is `true`, the overlaid widget will be included in size-allocation of the entire `Overlay`. If `clip` is `true`, if part of a widget goes outside the overlays allocated area, it will be truncated. """ @document add_render_task! """ ``` add_render_task!(area::RenderArea, task::RenderTask) ``` Register a new render task with the area. Unless a custom handle was connected to the `RenderArea` using `connect_signal_render!`, the render task will be drawn every frame. """ @document add_resource_path! """ ``` add_resource_path!(::IconTheme, path::String) ``` Add a folder that the `IconTheme` should lookup icons from. This is in addition to the default search path for icons. The folder has to adhere to the [Freedesktop icon theme specificatins](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html). """ @document add_section! """ ``` add_section!(self::MenuModel, title::String, to_add::MenuModel, [::SectionFormat]) ``` Add a "section"-type menu item to the model, which will be constructed based on `to_add`. """ @document add_shortcut! """ ``` add_shortcut!(::Action, shortcut::ShortcutTrigger) ``` Add a shortcut trigger to the list of shortcuts. To make a widget listen for action shortcuts, use [`set_listens_for_shortcut_action!`](@ref), or use a [`ShortcutEventController`](@ref). """ @document add_submenu! """ ``` add_submenu!(model::MenuModel, label::String, to_add::MenuModel) ``` Add a "submenu"-type menu item to the model, which will be constructed based on `to_add`. """ @document add_widget! """ ``` add_widget!(model::MenuModel, ::Widget) ``` Add a "widget"-type item to the model. This widget should be interactable. """ @document alt_pressed """ ``` alt_pressed(::ModifierState) -> Bool ``` Check if modifier state indicates that the `Alt` key is currently pressed. """ @document apply_to """ ``` apply_to(::GLTransform, ::Vector2f) -> Vector2f apply_to(::GLTransform, ::Vector3f) -> Vector3f ``` Apply transform to a vector, both operate in OpenGL coordinates. """ @document as_circle! """ ``` as_circle!(::Shape, center::Vector2f, radius::Number, n_outer_vertices::Integer) ``` Create a shape as a circle, defined by its center and radius. In OpenGL coordinates. """ @document as_circular_ring! """ ``` as_circular_ring!(::Shape, center::Vector2f, outer_radius::Number, thickness::Number, n_outer_vertices::Integer) ``` Create a shape as a circular ring, defined by its center, `outer_radius`, which is the distance to the out perimeter, and `thickness` which is the distance between the inner and outer perimeter. """ @document as_cropped """ ``` as_cropped(image::Image, offset_x::Signed, offset_y::Signed, new_width::Integer, new_height::Integer) -> Image ``` Crop the image, this is similar to the "resize canvas" operation in many image manipulation programs. `offset_x` and `offset_y` is the offset between the top-left pixel of the image and the top-left pixel of the newly allocated area, where an offset of 0 means the new image is anchored at the same pixel as the original. Offsets can be negative. This function does not modify the original image. """ @document as_degrees """ ``` as_degrees(angle::Angle) -> Float64 ``` Convert the angle to degrees, in [0°, 360°]. """ @document as_ellipse! """ ``` as_ellipse!(::Shape, center::Vector2f, x_radius::Number, y_radius::Number, n_outer_vertices::Integer) ``` Create a shape as an ellipse, defined by its center and radii along the x- and y-dimension, in OpenGL coordinates. """ @document as_elliptical_ring! """ ``` as_elliptical_ring!(::Shape, center::Vector2f, outer_x_radius::Number, outer_y_radius::Number, x_thickness::Number, y_thickness::Number, n_outer_vertices::Unsigned) ``` Create a shape as an elliptical ring, where + `center`: center of the ring + `outer_x_radius`: distance between the center and the outer perimeter along the x-axis + `outer_y_radius`: distance between the center and the outer perimeter along the y-axis + `x_thickness`: distance between the outer and inner perimeter along the x-axis + `y_thickness``: distance between the outer and inner perimeter along the y-axis In OpenGL coordinates. """ @document as_flipped """ ``` as_flipped(::Image, flip_horizontally::Bool, flip_vertically::Bool) -> Image ``` Create a new image that is a horizontally and/or vertically mirrored. This function does not modify the original image. """ @document as_line! """ ``` as_line!(::Shape, a::Vector2f, b::Vector2f) ``` Create a shape as a 1-fragment thick line between two points, in OpenGL coordinates. """ @document as_line_strip! """ ``` as_line_strip!(::Shape, points::Vector{Vector2f}) ``` Create a shape as a line strip. For points `{a1, a2, ..., an}`, this will be a set of connected lines `{a1, a2}, {a2, a3}, ..., {an-1, an}`, in OpenGL coordinates. """ @document as_lines! """ ``` as_lines!(::Shape, points::Vector{Pair{Vector2f, Vector2f}}) ``` Create a shape as a set of unconnected lines, vertex positions in OpenGL coordinates. """ @document as_microseconds """ ``` as_microseconds(time::Time) -> Float64 ``` Convert to microseconds. """ @document as_milliseconds """ ``` as_milliseconds(time::Time) -> Float64 ``` Convert to milliseconds. """ @document as_minutes """ ``` as_minutes(time::Time) -> Float64 ``` Convert to minutes. """ @document as_nanoseconds """ ``` as_nanoseconds(time::Time) -> UInt64 ``` Convert to number of nanoseconds. """ @document as_outline! """ ``` as_outline!(self::Shape, other::Shape) ``` Create a shape as an outline of another shape. """ @document as_point! """ ``` as_point!(::Shape, position::Vector2f) ``` Create a shape as 1-fragment point, in OpenGL coordinates. """ @document as_points! """ ``` as_points!(::Shape, positions::Vector{Vector2f}) ``` Create a shape as a set of unconnected points, in OpenGL coordinates. """ @document as_polygon! """ ``` as_polygon!(::Shape, points::Vector{Vector2f}) ``` Create a shape as a convex polygon, the outer hull of `points` will be computed, but no vertex elimination will take place. In OpenGL coordiantes. """ @document as_radians """ ``` as_radians(angle::Angle) ``` Convert to radians, in [0, 2π]. """ @document as_rectangle! """ ``` as_rectangle!(::Shape, top_left::Vector2f, size::Vector2f) ``` Create a shape as an axis-aligned rectangle, in OpenGL coordinates. """ @document as_rectangular_frame! """ ``` as_rectangular_frame!(::Shape, top_left::Vector2f, outer_size::Vector2f, x_width::Number, y_width::Number) ``` Create a shape as a rectangular frame, where `x_width`, `y_width` are the "thickness" of the frames filled area. In OpenGL coordinates. """ @document as_scaled """ ``` as_scaled(::Image, size_x::Integer, size_y::Integer, ::InterpolationType) -> Image ``` Scale the image to a new size. This is similar to the "scale image" option in many image manipulation programs. Note that this does not modify the original image. """ @document as_seconds """ ``` as_seconds(time::Time) -> Float64 ``` Convert to seconds. """ @document as_string """ ``` as_string(::KeyFile) -> String ``` Serialize file into a string. """ @document as_triangle! """ ``` as_triangle!(::Shape, a::Vector2f, b::Vector2f, c::Vector2f) ``` Create a shape as a triangle defined by three points, in OpenGL coordinates. """ @document as_wireframe! """ ``` as_wireframe!(::Shape, points::Vector{Vector2f}) ``` Create a shape as a wire-frame. For points `{a1, a2, a3, ..., an}`, the shape will be a connected series of lines `{a1, a2}, {a2, a3}, ..., {an-1, an}, {an, a1}`. In OpenGL coordinates. """ @document attach_to! """ ``` attach_to!(popover::Popover, attachment::Widget) ``` Attach a popover to a widget, which anchors the graphical element of the popover such that it points to the widget. """ @document bind """ ``` bind(::TextureObject) ``` Bind a texture for rendering, which will make it available at `GL_TEXTURE_UNIT_0`. This is usually done automatically when a texture was registered with a shape using [`set_texture!`](@ref). """ @document bind_as_render_target """ ``` bind_as_render_target(render_texture::RenderTexture) ``` Bind a render texture as the current frame buffer. This should be done inside the signal handler of `RenderArea`'s signal `render`. Use [`unbind_as_render_target`](@ref) to restore the previously bound frame buffer. """ @document cancel! """ ``` cancel!(::FileChooser) ``` Cancel the file chooser, this will behave identically to the user clicking the cancel button. --- ``` cancel!(::FileMonitor) ``` Cancel the file monitor. It will no longer monitor the file. """ @document calculate_monitor_dpi """ ``` calculate_monitor_dpi(::Widget) -> Float32 ``` Calculate the dpi (dots per inch) of the monitor that is currently displaying the widgets associated window. """ @document clear """ ``` clear(::RenderArea) ``` Clear the current framebuffer, this will reset `GL_COLOR_BUFFER_BIT` and replace the contents of the currently bound framebuffer with `RGBA(0, 0, 0, 0)`. """ @document clear! """ ``` clear!(::Box) clear!(::FlowBox) clear!(::ImageDisplay) clear!(::ListView) clear!(::ListView, [iterator::ListViewIterator]) clear!(::GridView) ``` Remove all children from the widget. """ @document clear_filters! """ ``` clear_filters!(::FileChooser) ``` Remove all registered file filters. """ @document clear_marks! """ ``` clear_marks!(::Scale) ``` Remove all marks added via [`add_mark!`](@ref). """ @document clear_render_tasks! """ ``` clear_render_tasks!(::RenderArea) ``` Remove all registered render tasks. """ @document clear_shortcuts! """ ``` clear_shortcuts!(::Action) ``` Remove all registered shortcut triggers. """ @document close! """ ``` close!(::Window) ``` Attempt to close the window, this will emit signal `close_request`. """ @document combine_with """ ``` combine_with(self::GLTransform, other::GLTransform) -> GLTransform ``` Perform matrix-multiplication and return the resulting transform, in OpenGL coordinates. """ @document contains_file """ ``` contains_file(::Clipboard) -> Bool ``` Check whether the clipboard contains a file path. """ @document contains_image """ ``` contains_image(::Clipboard) -> Bool ``` Check whether the clipboard contains an image. """ @document contains_string """ ``` contains_string(::Clipboard) -> Bool ``` Check whether the clipboard contains a string. """ @document control_pressed """ ``` control_pressed(modifier_state::ModifierState) -> Bool ``` Check whether the modifier state indicates that `Control` is currently pressed. """ @document copy! """ ``` copy!(from::FileDescriptor, to::FileDescriptor, allow_overwrite::Bool ; make_backup::Bool = false, follow_symlink::Bool = false) -> Bool ``` Copy a file from one location to another. Returns `true` if the operation was successful. """ @document create! """ ``` create!(::Image, width::Integer, height::Integer, [color::RGBA]) create!(::TextureObject, width::Integer, height::Integer) ``` Clear the current pixeldata and reinitialize it at given size. Will fille each pixel with `RGBA(0, 0, 0, 0)`, unless otherwise specified. """ @document create_as_file_preview! """ ``` create_as_file_preview!(image_display::ImageDisplay, file::FileDescriptor) ``` If the `file` points to an image file, create a preview for that image, otherwise create from the files default icon. """ @document create_directory_at! """ ``` create_directory_at!(destination::FileDescriptor) -> Bool ``` Create folder at given location, returns `true` if the operation was successful. """ @document create_file_at! """ ``` create_file_at!(destination::FileDescriptor, should_replace::Bool) -> Bool ``` Create file at given location, returns `true` if the operation was successful """ @document create_from_file! """ ``` create_from_file!(::Icon, path::String) -> Bool create_from_file!(::Image, path::String) -> Bool create_from_file!(::ImageDisplay, path::String) -> Bool create_from_file!(::KeyFile, path::String) -> Bool create_from_file!(::Shader, type::ShaderType, file::String) -> Bool ``` Initialize the object from a file. Returns `true` if the operation was successful. """ @document create_from_icon! """ ``` create_from_icon!(::ImageDisplay, icon::Icon) ``` Create as preview of an icon. """ @document create_from_image! """ ``` create_from_image!(::TextureObject, ::Image) create_from_image!(::ImageDisplay, ::Image) ``` Initialize from an image. This will replace the current pixel data and size. """ @document create_from_path! """ ``` create_from_path!(::FileDescriptor, path::String) ``` Create as file descriptor pointing to given path. Use [`exists`](@ref) to check if this location is valid and contains a file or folde. """ @document create_from_string! """ ``` create_from_string!(::KeyFile, file::String) ``` De-serialize from a string. If you are loading a `KeyFile` from a file on disk, [`create_from_file!`](@ref) should be preferred. --- ``` create_from_string!(::Shader, type::ShaderType, glsl_code::String) ``` Create from GLSL code. See the manual chapter on native rendering for more information. """ @document create_from_theme! """ ``` create_from_theme!(::Icon, theme::IconTheme, id::String, square_resolution::Integer, [scale::Integer = 1]) ``` Create an icon from the icon theme. """ @document create_from_uri! """ ``` create_from_uri!(::FileDescriptor, uri::String) ``` Create as file descriptor pointing to given URI. There is no guarantee that the location exists or points to a valid file or folder. """ @document create_monitor """ ``` create_monitor(descriptor::FileDescriptor) -> FileMonitor ``` Create a `FileMonitor` monitoring the current file or folder. This may fail if the location does not contain a valid file. """ @document degrees """ ``` degrees(::Number) -> Angle ``` Create angle from degrees, automatically clamped to [0°, 360°]. """ @document delete_at! """ ``` delete_at!(::FileDescriptor) -> Bool ``` Irreversibly delete file at given location, returns `true` if the operation was successful. """ @document device_axis_to_string """ ``` device_axis_to_string(axis::DeviceAxis) -> String ``` Serialize the axes identification. """ @document destroy! """ ``` destroy!(::Window) -> Cvoid ``` Free the internally held reference, causing the window to be deallocated. This is only necessary if [`set_hide_on_close!`](@ref) was set to `true`. """ @document download """ ``` download(texture::TextureObject) -> Image ``` Retrieve the pixel data from the graphics card and return it as an image. This is an extremely costly operation. """ @document elapsed """ ``` elapsed(clock::Clock) -> Time ``` Get time since the clock was last restarted. """ @document exists """ ``` exists(::FileDescriptor) -> Bool ``` Check if file location contains a valid file or folder. """ @document find """ ``` find(::ListView, ::Widget, [::ListViewIterator]) -> Signed find(::GridView, ::Widget) -> Signed ``` Get index of widget in view, or -1 if widget is not part of view. """ @document flush """ ``` flush(::RenderArea) -> Cvoid ``` Equivalent to `glFlush`, requests for the bound framebuffer to be pushed to the screen. This may not immediately update the `RenderArea`. """ @document from_gl_coordinates """ ``` from_gl_coordinates(area::RenderArea, gl_coordinates::Vector2f) -> Vector2f ``` Convert OpenGL coordinates to absolute widget-space coordinates. This will take into account the `RenderArea`s currently allocated size on screen. """ @document get_acceleration_rate """ ``` get_acceleration_rate(::SpinButton) -> Float64 ``` Get the current rate at which the spin button accelerates when one of the buttons is held down. """ @document get_accept_label """ ``` get_accept_label(::FileChooser) -> String ``` Get the label used for the "accept" button. """ @document get_action """ ``` get_action(app::Application, id::String) -> Action ``` Retrieve an action registered with the application. """ @document get_adjustment """ ``` get_adjustment(::Scale) -> Adjustment get_adjustment(::SpinButton) -> Adjustment get_adjustment(::Scrollbar) -> Adjustment ``` Retrieve the adjustment of the widget. Modifying the adjustment will modify the widget, and vice-versa. """ @document get_allocated_size """ ``` get_allocated_size(::Widget) -> Vector2f ``` Get the size the widget currently occupies on screen, in pixels. If the widget is not currently shown, this function will return `(0, 0)`. """ @document get_allow_only_numeric """ ``` get_allow_only_numeric(::SpinButton) -> Bool ``` Get whether the spin button only accepts numerical strings for its text-entry. """ @document get_angle_delta """ ``` get_angle_delta(::RotateEventController) -> Angle ``` Get the difference between the current angle and the angle recognized when the gesture started. """ @document get_autohide """ ``` get_autohide(::Popover) -> Bool ``` Get whether the popover should automatically hide when it loses focus. """ @document get_auto_render """ ``` get_auto_render(::GLArea) -> Bool ``` Get whether the `render` signal is emitted any time the widget is drawn. """ @document get_axis_value """ ``` get_axis_value(::StylusEventController, ::DeviceAxis) -> Float32 ``` Get value for the devices axis, or 0 if no such axis is present. This value will usually be in `[0, 1]`. """ @document get_button_action_id """ ``` get_button_action_id(::PopupMessage) -> ActionID ``` Get the ID of the messages button, or `""` if no button is present. """ @document get_button_label """ ``` get_button_label!(::AlertDialog, index::Integer) -> String ``` Get label of the button at given ID, obtained when calling `add_button!`. -- ``` get_button_label!(::PopupMessage) -> String ``` Get label of the singular button, or `""` if no button is present. """ @document get_bottom_margin """ ``` get_bottom_margin(::TextView) -> Float32 ``` Get the distance between the bottom of the text and the bottom of the text views frame. """ @document get_bounding_box """ ``` get_bounding_box(::Shape) -> AxisAlignedRectangle ``` Get the axis-aligned bounding box of the shape. This is the smallest rectangle that contains all vertices, in OpenGL coordinates. """ @document get_can_respond_to_input """ ``` get_can_respond_to_input(::Widget) -> Bool ``` Get whether the widget can receive and capture input events. """ @document get_centroid """ ``` get_centroid(::Shape) -> Vector2f ``` Get the centroid of the shape, this is the mathematical average of all its vertices positions, in OpenGL coordinates. """ @document get_child_at """ ``` get_child_at(::Stack, index::Integer) -> StackID ``` Retrieve the ID of the stack page at given position, or `""` if the index is out of bounds. """ @document get_child_x_alignment """ ``` get_child_x_alignment(::AspectFrame) -> Float32 ``` Get the horizontal alignment of the aspect frames child, in `[0, 1]`. """ @document get_child_y_alignment """ ``` get_child_y_alignment(::AspectFrame) ``` Get the vertical alignment of the aspect frames child, in `[0, 1]`. """ @document get_children """ ``` get_children(descriptor::FileDescriptor ; [recursive = false]) -> Vector{FileDescriptor} ``` Get all children of a folder. If the location pointed to by `descriptor` is a file or does not exist, the resulting vector will be empty. """ @document get_clipboard """ ``` get_clipboard(::Widget) -> Clipboard ``` Retrieve the clipboard from a widget, which should usually be the top-level window. """ @document get_color """ ``` get_color(::ColorChooser) -> RGBA ``` Get the currently selected color. """ @document get_column_at """ ``` get_column_at(column_view::ColumnView, index::Integer) -> ColumnViewColumn ``` Get column at specified position, 1-based. """ @document get_column_spacing """ ``` get_column_spacing!(::FlowBox) -> Float32 get_column_spacing(::Grid) -> Float32 ``` Get spacing between columns, in pixels. """ @document get_column_with_title """ ``` get_column_with_title(column_view::ColumnView, title::String) -> ColumnViewColumn ``` Get column with specified title. """ @document get_columns_homogeneous """ ``` get_columns_homogeneous(::Grid) -> Bool ``` Get whether all columns should allocate the same width. """ @document get_comment_above """ ``` get_comment_above(::KeyFile, group::GroupID) -> String get_comment_above(::KeyFile, group::GroupID, key::KeyID) -> String ``` Get the singular comment above a group or key declaration. """ @document get_content_type """ ``` get_content_type(::FileDescriptor) -> String ``` Get the file type as a MIME identification string. """ @document get_css_classes """ ``` get_css_classes(::Widget) -> Vector{String} ``` Get all CSS classes currently applied to that widget. """ @document get_current_button """ ``` get_current_button(gesture::SingleClickGesture) -> ButtonID ``` Get the ID of the button that triggered the current event. """ @document get_current_offset """ ``` get_current_offset(::DragEventController) -> Vecto2f ``` Get the distance between the current cursor position and the point of origin for the drag-gestured, in absolute widget-space coordinates. """ @document get_current_theme """ ``` get_current_theme(::Application) -> Theme ``` Get the currently used theme, or `THEME_DEFAULT_LIGHT` if the application is uninitialized and no theme was chosen yet. """ @document get_current_page """ ``` get_current_page(::Notebook) -> Int64 ``` Get index of currently active page. """ @document get_cursor_visible """ ``` get_cursor_visible(::TextView) -> Bool ``` Get whether the text caret should be visible. """ @document get_delay_factor """ ``` get_delay_factor(::LongPressEventController) -> Float32 ``` Get multiplier that determines after how much time a long press gesture is recognized, where `1` is no change as compared to the default, `2` is twice as long, `0.5` is half as long. """ @document get_destroy_with_parent """ ``` get_destroy_with_parent(::Window) -> Bool ``` Get whether the window should be closed and deallocated when its parent window is. """ @document get_detailed_description """ ``` get_detailed_description(::AlertDIalog) -> String ``` Get detailed message, this is the text shown below the dialogs title. """ @document get_duration """ ``` get_duration(::Animation) -> Time ``` Get the target duration of the animation. """ @document get_editable """ ``` get_editable(::TextView) -> Bool ``` Get whether the user can edit the text. """ @document get_ellipsize_mode """ ``` get_ellipsize_mode(::Label) -> EllipsizeMode ``` Get the ellipsize mode of a label, `ELLIPSIZE_MODE_NONE` by default. """ @document get_enabled """ ``` get_enabled(::Action) -> Bool ``` Get whether the action is enabled. A disabled action cannot be activated and all its connected widgets are disabled. """ @document get_enable_rubberband_selection """ ``` get_enable_rubberband_selection(::ListView) -> Bool get_enable_rubberband_selection(::GridView) -> Bool get_enable_rubberband_selection(::ColumnView) -> Bool ``` Get whether the user can select multiple children by click-dragging with the cursor. The selectable widgets selection mode has to be `SELECTION_MODE_MULTIPLE` in order for this to be possible. """ @document get_end_child_resizable """ ``` get_end_child_resizable(::Paned) -> Bool ``` Get whether the end child should resize when the `Paned` is resized. """ @document get_end_child_shrinkable """ ``` get_end_child_shrinkable(::Paned) -> Bool ``` Get whether the user can resize the end child such that its allocated area inside the `Paned` is smaller than the natural size of the child. """ @document get_expand_horizontally """ ``` get_expand_horizontally(::Widget) -> Bool ``` Get whether the widget can expand along the x-axis. """ @document get_expand_vertically """ ``` get_expand_vertically(::Widget) -> Bool ``` Get whether the widget can expand along the y-axis. """ @document get_is_expanded """ ``` get_is_expanded(::Expander) -> Bool ``` Get whether the `Expander`'s child is currently visible. """ @document get_file_chooser_action """ ``` get_file_chooser_action(::FileChooser) -> FileChooserAction ``` Get the file chooser action type. """ @document get_file_extension """ ``` get_file_extension(::FileDescriptor) -> String ``` Get the file extension of the file. This will be any characters after the last `.`. """ @document get_fixed_width """ ``` get_fixed_width(::ColumnViewColumn) -> Float32 ``` Get the target width of the column, in pixels. """ @document get_focus_on_click """ ``` get_focus_on_click(::Widget) -> Bool ``` Get whether the widget should grab focus when it is clicked. """ @document get_focus_visible """ ``` get_focus_visible(::Window) -> Bool ``` Get whether which widget currently holds input focus should be highlighted using a border. """ @document get_fraction """ ``` get_fraction(::ProgressBar) -> Float32 ``` Get the currently displayed fraction of the `ProgressBar`, in `[0, 1]`. """ @document get_fragment_shader_id """ ``` get_fragment_shader_id(::Shader) -> Cuint ``` Get the native OpenGL handle of the shader programs fragment shader component. """ @document get_groups """ ``` get_groups(::KeyFile) -> Vector{GroupID} ``` Get all group IDs currently present in the key file. """ @document get_hardware_id """ ``` get_hardware_id(::StylusEventController) -> Cuint ``` Get the native ID of the stylus-device that caused the current event. """ @document get_has_base_arrow """ ``` get_has_base_arrow(::Popover) -> Bool ``` Get whether the arrow-shaped "tail" of the popover is visible. """ @document get_has_border """ ``` get_has_border(::Notebook) -> Bool ``` Get whether a border should be drawn around the notebooks perimeter. """ @document get_has_close_button """ ``` get_has_close_button(::Window) -> Bool ``` Get whether the "x" button is present. """ @document get_has_focus """ ``` get_has_focus(::Widget) -> Bool ``` Check whether the input currently holds input fcus. """ @document get_has_frame """ ``` get_has_frame(::Button) -> Bool get_has_frame(::Viewport) -> Bool get_has_frame(::Entry) -> Bool get_has_frame(::PopoverButton) -> Bool ``` Get whether the widget's outline should be displayed, it will remain interactable. """ @document get_has_origin """ ``` get_has_origin(::Scale) -> Bool ``` Get whether the area between the origin of the scales trough and the current value should be fille with a solid color. """ @document get_has_wide_handle """ ``` get_has_wide_handle(::Paned) -> Bool ``` Get whether the barrier in between the `Paned`'s two children is wide or thin, wide by default. """ @document get_header_bar """ ``` get_header_bar(::Window) -> header_bar ``` Access the `HeaderBar` instance used as the window's titlebar widget. """ @document get_hide_on_close """ ``` get_hide_on_close(::Window) -> Bool ``` Get whether the window will be hidden when it is closed, as opposed to destroyed. """ @document get_hide_on_overflow """ ``` get_hide_on_overflow(::Widget) -> Bool ``` Get whether the entire widget should be hidden if its allocated area is smaller than its natural size. If `false`, the overflow part of the widget will be truncated. """ @document get_homogeneous """ ``` get_homogeneous(::Box) -> Bool ``` Get whether all of the `Box`'s children should be allocated the same width (or height, if orientation is `ORIENTATION_VERTICAL`). """ @document get_horizontal_adjustment """ ``` get_horizontal_adjustment(viewport::Viewport) -> Adjustment ``` Get the adjustment controlling the horizontal scrollbar. """ @document get_horizontal_alignment """ ``` get_horizontal_alignemtn(::Widget) -> Alignment ``` Get alignment along the x-axis. """ @document get_horizontal_scrollbar_policy """ ``` get_horizontal_scrollbar_policy(::Viewport) -> ScrollbarVisibilityPolicy ``` Get the policy governing how and if the horizontal scrollbar is revealed / hidden. """ @document get_icon_names """ ``` get_icon_names(theme::IconTheme) -> Vector{String} ``` Get the ID of all icons available in the icon theme. """ @document get_id """ ``` get_id(::Application) -> ApplicationID get_id(::Action) -> ActionID ``` Access the ID specified during the object's construction. """ @document get_image """ ``` get_image(f, ::Clipboard, [::Data_t]) ``` Register a callback to read an image from the clipboad. Once the clipboard is ready, the callback will be invoked. `f` is required to be invocable as a function with signature ```julia (::Clipboard, ::Image, [::Data_t]) -> Nothing ``` ## Example ```julia clipboard = get_clipboard(window) if contains_image(clipboard) get_image(clipboard) do x::Clipboard, image::Image # use image here end end ``` """ @document get_inverted """ ``` get_inverted(::LevelBar) -> Bool ``` Get whether the level bar should be mirrored along the horizontal or vertical axis, depending on orientation. """ @document get_is_active """ ``` get_is_active(::CheckButton) -> Bool get_is_active(::Switch) -> Bool get_is_active(::ToggleButton) -> Bool ``` Get whether the internal state of the widget is active. """ @document get_is_circular """ ``` get_is_circular(::Button) -> Bool get_is_circular(::ToggleButton) -> Bool get_is_circular(::PopoverButton) -> Bool ``` Get whether the button is circular (as opposed to rectangular, the default). """ @document get_is_closed """ ``` get_is_closed(::Window) -> Bool ``` Returns `false` if the window is currently active and visible to the user, `true` otherwise. """ @document get_is_decorated """ ``` get_is_decorated(::Window) -> Bool ``` Get whether the header bar area of the window is visible. """ @document is_executable """ ``` is_executable(::FileDescriptor) -> Bool ``` Get whether the file location contains an executable. """ @document get_is_focusable """ ``` get_is_focusable(::Widget) -> Bool ``` Get whether the widget can grab input focus. """ @document get_is_high_priority """ ``` get_is_high_priority(::PopupMessage) -> Bool ``` Get whether this message has a high priority. High priority messages will be shown before non-high-priority if queued with `PopupMessageOverlay`. """ @document get_is_holding """ ``` get_is_holding(::Application) -> Bool ``` Get whether [`hold!`](@ref) was called and the application currently blocks attempts at exiting. """ @document get_is_horizontally_homogeneous """ ``` get_is_horizontally_homogeneous(::Stack) -> Bool ``` Get whether all pages of the stack should allocate the same width. """ @document get_is_inverted """ ``` get_is_inverted(::ProgressBar) -> Bool ``` Get whether the progress bar should fill from right-to-left, instead of left-to-right """ @document get_is_modal """ ``` get_is_modal(::Window) -> Bool get_is_modal(::FileChooser) -> Bool get_is_modal(::ColorChooser) -> Bool get_is_modal(::AlertDialog) -> Bool ``` Get whether all other windows should be paused while this window is active. """ @document get_is_marked_as_busy """ ``` get_is_marked_as_busy(::Application) -> Bool ``` Returns `true` if [`mark_as_busy!`](@ref) was called before. """ @document get_is_realized """ ``` get_is_realized(::Widget) -> Bool ``` Get whether the widget was initialized and is now shown on screen. """ @document get_is_resizable """ ``` get_is_resizable(::ColumnViewColumn) -> Bool ``` Get whether the user can choose the width of this column by click-dragging. """ @document get_is_reversed """ ``` get_is_reversed(::Animation) -> Bool ``` If `false`, the animation will interpolate its value from the lower to upper bound, or the other way around if `true`. """ @document get_is_scrollable """ ``` get_is_scrollable(::Notebook) -> Bool ``` Get whether the user can scroll between pages using the mouse scroll wheel or touchscreen. """ @document get_is_spinning """ ``` get_is_spinning(::Spinner) -> Bool ``` Get whether the `Spinner`s animation is currently playing. """ @document get_is_vertically_homogeneous """ ``` get_is_vertically_homogeneous(::Stack) -> Bool ``` Get whether the stack should allocate the same height for all its pages. """ @document get_is_visible """ ``` get_is_visible(::Widget) -> Bool get_is_visible(::ColumnViewColumn) -> Bool ``` Get whether the object is currently shown on screen. --- ``` get_is_visible(::Shape) -> Bool ``` Get whether the shape should be omitted from rendering, where `false` means it will be ommitted. """ @document get_item_at """ ``` get_item_at(::DropDown, i::Integer) -> DropDownID ``` Get ID of the item at given position. """ @document get_justify_mode """ ``` get_justify_mode(::Label) -> JustifyMode get_justify_mode(::TextView) -> JustifyMode ``` Get the currently used justify mode. """ @document get_keys """ ``` get_keys(::KeyFile, group::GroupID) -> Vector{KeyID} ``` Get all keys in this group, or an empty vector if the group does not exist. """ @document get_kinetic_scrolling_enabled """ ``` get_kinetic_scrolling_enabled(::Viewport) -> Bool get_kinetic_scrolling_enabled(::ScrollEventController) -> Bool ``` Get whether scrolling should continue once the user stops operating the mouse wheel or touchscreen, simulating "inertia". """ @document get_label_x_alignment """ ``` get_label_x_alignment(::Frame) -> Float32 ``` Get the horizontal alignment of the `Frame`'s optional label widget, in `[0, 1]`. """ @document get_layout """ ``` get_layout(::HeaderBar) -> String ``` Get the layout string of the header bar. See the manual section on `HeaderBar` in the chapter on widgets for how layout-syntax works. """ @document get_left_margin """ ``` get_left_margin(::TextView) -> Float32 ``` Get distance between the left side of the text and the `TextView`'s frame. """ @document get_lower """ ``` get_lower(::Adjustment) -> Float32 get_lower(::Scale) -> Float32 get_lower(::SpinButton) -> Float32 get_lower(::Animation) -> Float64 ``` Get the lower bound of the underlying range. """ @document get_margin_bottom """ ``` get_margin_bottom(::Widget) -> Float32 ``` Get the bottom margin of the widget, in pixels. """ @document get_margin_end """ ``` get_margin_end(::Widget) -> Float32 ``` Get the right margin of the widget, in pixels. """ @document get_margin_start """ ``` get_margin_start(::Widget) -> Float32 ``` Get the left margin of the widget, in pixels. """ @document get_margin_top """ ``` get_margin_top(::Widget) -> Float32 ``` Get the top margin of the widget, in pixels. """ @document get_maximum_size """ ``` get_maximum_size(::ClampFrame) -> Float32 ``` Get the maximum width (or height, if vertical) the frame should constrain its child to, in pixels. """ @document get_max_n_columns """ ``` get_max_n_columns(grid_view::GridView) -> Signed ``` Get the maximum number of columns, (or rows if orientation is vertical), or `-1` if unlimited. """ @document get_max_value """ ``` get_max_value(::LevelBar) -> Float32 ``` Get the upper bound of the underlying range. """ @document get_max_width_chars """ ``` get_max_width_chars(::Entry) -> Signed get_max_width_chars(::Label) -> Signed ``` Get the maximum number of characters for which the label should allocate horizontal space, or `-1` if unlimited. """ @document get_message """ ``` get_message(::AlertDialog) -> String ``` Get the current message, this is the title of the dialog. """ @document get_min_n_columns """ ``` get_min_n_columns(grid_view::GridView) -> Signed ``` Get the minimum number of columns, or `-1` if unlimited. """ @document get_min_value """ ``` get_min_value(::LevelBar) -> Float32 ``` Get the lower bound of the underlying range. """ @document get_minimum_size """ ``` get_minimum_size(::Widget) -> Vector2f ``` Get the minimum possible size the widget would have to allocate in order for it to be fully visible. """ @document get_mode """ ``` get_mode(::LevelBar) -> LevelBarMode ``` Get whether the `LevelBar` should display its value as a continuous bar, or segmented. """ @document get_n_buttons """ ``` get_n_buttons(::AlertDialog) -> Int64 ``` Get the number of buttons the dialog currently has. """ @document get_n_columns """ ``` get_n_columns(::ColumnView) -> Unsigned ``` Get the current number of columns. """ @document get_n_digits """ ``` get_n_digits(::SpinButton) -> Signed ``` Get the number of digits the spin button should display. """ @document get_n_items """ ``` get_n_items(::Box) -> Unsigned get_n_items(::FlowBox) -> Unsigned get_n_items(::ListView) -> Unsigned get_n_items(::GridView) -> Unsigned ``` Get the number of children. """ @document get_n_pages """ ``` get_n_pages(::Notebook) -> Unsigned ``` Get the number of pages. """ @document get_n_pixels """ ``` get_n_pixels(::Image) -> Unsigned ``` Get the number of pixels, equal to `width * height`. """ @document get_n_rows """ ``` get_n_rows(::ColumnView) -> Unsigned ``` Get the current number of rows. """ @document get_n_vertices """ ``` get_n_vertices(::Shape) -> Unsigned ``` Get the number of OpenGL vertices. """ @document get_name """ ``` get_name(::Icon) -> String get_name(::FileDescriptor) -> String get_name(::FileFilter) -> String ``` Get a cleartext identifier for the object. """ @document get_native_handle """ ``` get_native_handle(::TextureObject) -> Cuint get_native_handle(::Shape) -> Cuint ``` Get the native OpenGL handle of the texture- or vertex buffer. """ @document get_natural_size """ ``` get_natural_size(::Widget) -> Vector2f ``` Get the size the widget would prefer to display at, if given infinite space and no expansion. """ @document get_only_listens_to_button """ ``` get_only_listens_to_button(::SingleClickGesture) -> Bool ``` Get whether the event controller should not capture events send by a touch device. """ @document get_opacity """ ``` get_opacity(::Widget) -> Float32 ``` Get the widget's current opacity, in `[0, 1]`. """ @document get_orientation """ ``` get_orientation(::Box) -> Orientation get_orientation(::FlowBox) -> Orientation get_orientation(::CenterBox) -> Orientation get_orientation(::ClampFrame) -> Orientation get_orientation(::LevelBar) -> Orientation get_orientation(::ListView) -> Orientation get_orientation(::GridView) -> Orientation get_orientation(::Grid) -> Orientation get_orientation(::Paned) -> Orientation get_orientation(::ProgressBar) -> Orientation get_orientation(::Scrollbar) -> Orientation get_orientation(::Separator) -> Orientation get_orientation(::SpinButton) -> Orientation get_orientation(::SpinButton) -> Orientation ``` Get whether the widget is oriented horizontally or vertically. --- ``` get_orientation(::PanEventController) -> Orientation ``` Get along which axis the event controller should recognize pan gestures. """ @document get_parent """ ``` get_parent(self::FileDescriptor) -> FileDescriptor ``` Get the files parent folder. If `self` points to root or a location that does not exist, the result may be invalid. """ @document get_path """ ``` get_path(::FileDescriptor) -> String ``` Get the absolute path to the file location. """ @document get_path_relative_to """ ``` get_path_relative_to(self::FileDescriptor, other::FileDescriptor) -> String ``` Get the relative path from `self` to `other`. """ @document get_pixel """ ``` get_pixel(image::Image, x::Integer, y::Integer) -> RGBA ``` Get the color of the pixel at given position, 1-indexed. """ @document get_position """ ``` get_position(::Widget) -> Vector2f ``` Get the current position on screen, relative to the toplevel window's origin, in pixels. --- ``` get_position(::Grid, ::Widget) -> Vector2i ``` Get row- and column-index of the widget, 1-indexed. --- ``` get_position(::Paned) -> Int32 ``` Get the offset of the draggable handle, in absolute widget-space coordinates. """ @document get_program_id """ ``` get_program_id(::Shader) -> Cuint ``` Get the native handle of the OpenGL shader program. """ @document get_propagate_natural_height """ ``` get_propagate_natural_height(::Viewport) -> Bool ``` Get whether the viewport should assume the natural height of its child. """ @document get_propagate_natural_width """ ``` get_propagate_natural_width(::Viewport) -> Bool ``` Get whether the viewport should assume the natural width of its child. """ @document get_propagation_phase """ ``` get_propagation_phase(::EventController) -> PropagationPhase ``` Get the phase at which the event controller will capture events, see [here](https://developer-old.gnome.org/gtk4/stable/event-propagation.html) for more information. """ @document get_quick_change_menu_enabled """ ``` get_quick_change_menu_enabled(::Notebook) -> Bool ``` Get whether the user can open a menu that lets them skip to any page of the notebook. """ @document get_ratio """ ``` get_ratio(::AspectFrame) -> Float32 ``` Get width-to-height aspect ratio. """ @document get_relative_position """ ``` get_relative_position(::Popover) -> RelativePosition get_relative_position(::PopoverButton) -> RelativePosition ``` Get the position of the popover relative to the widget it is attached to. """ @document get_repeat_count """ ``` get_repeat_count(::Animation) -> Unsigned ``` Get the number of cycles the animation will perform, or `0` if the animation loops endlessly. """ @document get_is_revealed """ ``` get_is_revealed(::Revealer) -> Bool get_is_revealed(::ActionBar) -> Bool ``` Get whether the widget's child is currently visible. """ @document get_right_margin """ ``` get_right_margin(::TextView) -> Float32 ``` Get distance between the right end of the text and the `TextView`'s frame. """ @document get_row_spacing """ ``` get_row_spacing(::Grid) -> Float32 get_row_spacing(::FlowBox) -> Float32 ``` Get the margin between two rows, in pixels. """ @document get_rows_homogeneous """ ``` get_rows_homogeneous(::Grid) -> Bool ``` Get whether all rows should allocate the same height. """ @document get_scale """ ``` get_scale(::ImageDisplay) -> Signed ``` Get scale factor, in `{1, 2, 3, ...}``. """ @document get_scale_delta """ ``` get_scale_delta(::PinchZoomEventController) -> Float32 ``` Get the difference between the current scale of the pinch-zoom-gesture and the scale at the point the gesture started, in absolute widget-space coordinates. """ @document get_scale_factor """ ``` get_scale_factor(::Widget) -> Float32 ``` Retrieves the internal scale factor that maps from window coordinates to the actual device pixels. On traditional systems this is 1, on high density outputs, it can be a higher value (typically 2). Quoted from: https://docs.gtk.org/gtk4/method.Widget.get_scale_factor.html """ @document get_scale_mode """ ``` get_scale_mode(::TextureObject) -> ScaleMode ``` Get the OpenGL scale mode the texture uses. """ @document get_scope """ ``` get_scope(::ShortcutEventController) -> ShortcutScope ``` Get the scope in which the controller listens for shortcut events, see [here](https://docs.gtk.org/gtk4/method.ShortcutController.set_scope.html) for more information. """ @document get_scrollbar_placement """ ``` get_scrollbar_placement(::Viewport) -> CornerPlacement ``` Get the position of both scrollbars relative to the `Viewport`'s center. """ @document get_is_selectable """ ``` get_is_selectable(::Label) -> Bool ``` Get whether the user can select part of the label, as would be needed to copy its text. """ @document get_selected """ ``` get_selected(::DropDown) -> DropDownID ``` Get the ID of the currently selected item. """ @document get_selection """ ``` get_selection(::SelectionModel) -> Vector{Int64} ``` Get all currently selected items' indices, 1-based. """ @document get_selection_model """ ``` get_selection_model(::ListView) -> SelectionModel get_selection_model(::GridView) -> SelectionModel get_selection_model(::Stack) -> SelectionModel get_selection_model(::ColumnView) -> SelectionModel ``` Get the underlying selection model of the selectable widget. """ @document get_selection_mode """ ``` get_selection_mode(::SelectionModel) -> SelectionMode ``` Get the underlying selection mode of the model. """ @document get_shortcuts """ ``` get_shortcuts(action::Action) -> Vector{ShortcutTrigger} ``` Get all registered shortcuts for the action. """ @document get_should_draw_value """ ``` get_should_draw_value(::Scale) -> Bool ``` Get whether the value of the `Scale`'s `Adjustment` is drawn as a label next to the slider. """ @document get_should_interpolate_size """ ``` get_should_interpolate_size(::Stack) -> Bool ``` Get whether the stack should slowly transition its size when switching from one page to another. """ @document get_should_snap_to_ticks """ ``` get_should_snap_to_ticks(::SpinButton) -> Bool ``` Get whether when the user enters a value using the `SpinButton`'s text entry, that value should be clamped to the nearest tick. """ @document get_should_wrap """ ``` get_should_wrap(::SpinButton) -> Bool ``` Get whether the spin button should over- or underflow when reaching the upper or lower end of its range. This needs to be set to `true` in order for `SpinButton` to emit its signal `wrapped`. """ @document get_always_show_arrow """ ``` get_always_show_arrow(::DropDown) -> Bool get_always_show_arrow(::PopoverButton) -> Bool ``` Get whether an arrow should be drawn next to the label. """ @document get_show_column_separators """ ``` get_show_column_separators(::ColumnView) -> Bool ``` Get whether a separator should be drawn between two columns. """ @document get_show_row_separators """ ``` get_show_row_separators(::ColumnView) -> Bool ``` Get whether a separator should be drawn between two rows. """ @document get_show_separators """ ``` get_show_separators(::ListView) -> Bool ``` Get whether a separator should be drawn between two items. """ @document get_show_text """ ``` get_show_text(::ProgressBar) - Bool ``` Get whether a percentage or custom label should be displayed above the `ProgressBar`. User [`set_text!`](@ref) to choose the custom label. """ @document get_show_title_buttons """ ``` get_show_title_buttons(::HeaderBar) -> Bool ``` Get whether the "close", "minimize", and / or "maximize" button should be visible. """ @document get_single_click_activate """ ``` get_single_click_activate(::ListView) -> Bool get_single_click_activate(::GridView) -> Bool get_single_click_activate(::ColumnView) -> Bool ``` Get whether simply hovering over an item will select it. """ @document get_size """ ``` get_size(::TextureObject) -> Vector2i get_size(::Icon) -> Vector2i get_size(::Image) -> Vector2i ``` Get resolution of the underlying image. --- ``` get_size(::Grid, ::Widget) -> Vector2i ``` Get the number of rows and columns. --- ``` get_size(::Shape) -> Vector2f ``` Get width and height of the axis-aligned bounding box, in OpenGL coordinates. """ @document get_size_request """ ``` get_size_request(::Widget) -> Vector2f ``` Get the size request, where a `0` for either width or height indicates that no size request was made. """ @document get_spacing """ ``` get_spacing(::Box) -> Float32 ``` Get spacing drawn between any two items, in pixels. """ @document get_start_child_resizable """ ``` get_start_child_resizable(::Paned) -> Bool ``` Get whether the start child should resize itself when the `Paned` is resized. """ @document get_start_child_shrinkable """ ``` get_start_child_shrinkable(::Paned) -> Bool ``` Get whether the start child can be resized such that its allocated area in the paned is less than its minimum size. """ @document get_start_position """ ``` get_start_position(controller::DragEventController) -> Vector2f ``` Get position at which the drag gesture was first recognized, in absolute widget-space coordinates. """ @document get_state """ ``` get_state(::CheckButton) -> CheckButtonState ``` Get current state of the check button. --- ``` get_state(::Animation) -> AnimationState ``` Get current state of the animation. """ @document get_step_increment """ ``` get_step_increment(::Adjustment) -> Float32 get_step_increment(::Scale) -> Float32 get_step_increment(::SpinButton) -> Float32 ``` Get minimum step increment of the underlying adjustment. """ @document get_string """ ``` get_string(f, clipboard::Clipboard, [::Data_t]) -> Cvoid ``` Register a callback with the signature: ``` (::Clipboad, ::String, [::Data_t]) -> Cvoid ``` When a string is read from the clipboard, the callback will be invoked and the string will be provided as the second argument for the callback. ## Example ```julia clipboard = get_clipboard(window) if contains_string(clipboard) get_string(clipboard) do x::Clipboard, string::String # use string here end end ``` """ @document get_surpress_debug """ ``` get_surpress_debug(::LogDomain) -> Bool ``` Get whether log messages of level "DEBUG" will be omitted from the console output. """ @document get_surpress_info """ ``` get_surpress_info(::LogDomain) -> Bool ``` Get whether log message of level "INFO" will be omitted from the console output. """ @document get_tab_position """ ``` get_tab_position(::Notebook) -> RelativePosition ``` Get position of the tab bar relative to the center of the notebook. """ @document get_tabs_reorderable """ ``` get_tabs_reorderable(::Notebook) -> Bool ``` Get whether the user can reorder tabs. """ @document get_tabs_visible """ ``` get_tabs_visible(::Notebook) -> Bool ``` Get whether the tab bar is visible. """ @document get_target_frame_duration """ ``` get_target_frame_duration(::FrameClock) -> Time ``` Get the intended duration of a frame. For example, if the monitor has a refresh rate of 60hz, the target frame duration is `1/60s`. """ @document get_text """ ``` get_text(::Entry) -> String get_text(::Label) -> String get_text(::TextView) -> String ``` Get the content of the underlying text buffer. --- ``` get_text(::ProgressBar) -> String ``` Get text currently displayed by the `ProgressBar`, or `""` if the percentage is displayed instead. """ @document get_text_visible """ ``` get_text_visible(::Entry) -> Bool ``` Get whether the text entry is in "password mode". """ @document get_timeout """ ``` get_timeout(::PopupMessage) -> Time ``` Get the duration after which the message should hide itself, or `0` for it to never hide on its own. Microsecond precision. """ @document get_time_since_last_frame """ ``` get_time_since_last_frame(::FrameClock) -> Time ``` Get the actual duration of the last rendered frame. """ @document get_timing_function """ ``` get_timing_function(::Animation) -> AnimationTimingFunction ``` Get the shape of the function used to interpolate the animation's underlying value over time. """ @document get_title """ ``` get_title(::Window) -> String get_title(::FileChooser) -> String get_title(::ColorChooser) -> String ``` Get the window title. --- ``` get_title(::ColumnViewColumn) -> String ``` Get the title for this column, which uniquely identifies it. --- ``` get_title!(::PopupMessage) -> String ```` Get the `PopupMessage`s text. """ @document get_tool_type """ ``` get_tool_type(::StylusEventController) -> ToolType ``` Get the currently set tool type of the stylus device, or `TOOL_TYPE_UNKNOWN` if tool types are not supported. """ @document get_top_left """ ``` get_top_left(::Shape) -> Vector2f ``` Get the position of the top left corner of the axis-aligned bounding box, in OpenGL coordinates. """ @document get_top_margin """ ``` get_top_margin(::TextView) -> Float32 ``` Get distance between the top of the text and the `TextView`'s frame. """ @document get_touch_only """ ``` get_touch_only(::SingleClickGesture) -> Bool ``` Get whether the event controller should exclusively react to events from touch devices. """ @document get_top_level_widget """ ``` get_top_level_widget(::Widget) -> Widget ``` Function that maps a non-native compound widget (subtyping `Widget`) to its top-level widget component. See the manual section on compound widgets in the chapter on widgets for more information. ## Example ```julia struct CompoundWidget <: Widget box::Box CompoundWidget() = new(hbox(Label("this is a compound widget"))) end Mousetrap.get_top_level_widget(x::CompoundWidget) = x.box # after this definition, `CompoundWidget` can be used like any native Mousetrap widget """ @document get_transition_duration """ ``` get_transition_duration(::Stack) -> Time get_transition_duration(::Revealer) -> Time ``` Get the duration of the transition animation. """ @document get_transition_type """ ``` get_transition_type(::Stack) -> StackTransitionType get_transition_type(::Revealer) -> RevealerTransitionType ``` Get type of animation used for the transition animation. """ @document get_uniform_float """ ``` get_uniform_float(::RenderTask, name::String) -> Cfloat ``` Get a registered uniform `float`, or `0.0` if no such uniform was registered. """ @document get_uniform_hsva """ ``` get_uniform_hsva(task::RenderTask, name::String) -> HSVA ``` Get uniform `vec4`, or `HSVA(0, 0, 0, 0)` if no such uniform exists. """ @document get_uniform_int """ ``` get_uniform_int(::RenderTask, name::String) -> Cint ``` Get a registered uniform `int`, or `0` if no such uniform was registered. """ @document get_uniform_location """ ``` get_uniform_location(::Shader, name::String) -> Cuint ``` Get the OpenGL shader program uniform location for the given uniform name, or `-1` if no such uniform exists. Note that uniform names may be optimized away by the GLSL compiler if they go unused. """ @document get_uniform_rgba """ ``` get_uniform_rgba(task::RenderTask, name::String) -> RGBA ``` Get uniform `vec4`, or `RGBA(0, 0, 0, 0)` if no such uniform exists. """ @document get_uniform_transform """ ``` get_uniform_transform(task::RenderTask, name::String) -> GLTransform ``` Get uniform `mat4x4`, or the identity transform if no such uniform exists. """ @document get_uniform_uint """ ``` get_uniform_uint(::RenderTask, name::String) -> Cuint ``` Get uniform `uint`, or `0` if no such uniform exists. """ @document get_uniform_vec2 """ ``` get_uniform_vec2(task::RenderTask, name::String) -> Vector2f ``` Get uniform `vec2`, or `Vector2f(0, 0)` if no such uniform exists. """ @document get_uniform_vec3 """ ``` get_uniform_vec3(task::RenderTask, name::String) ``` Get uniform `vec3`, or `Vector3f(0, 0, 0)` if no such uniform exists. """ @document get_uniform_vec4 """ ``` get_uniform_vec4(task::RenderTask, name::String) ``` Get uniform `vec4`, or `Vector4f(0, 0, 0, 0)` if no such uniform exists. """ @document get_upper """ ``` get_upper(::Adjustment) -> Float32 get_upper(::Scale) -> Float32 get_upper(::SpinButton) -> Float32 get_upper(::Animation) -> Float64 ``` Get upper bound of the underlying range. """ @document get_uri """ ``` get_uri(::FileDescriptor) -> String ``` Transform the descriptor's path to URI format. """ @document get_use_markup """ ``` get_use_markup(::Label) -> Bool ``` Set whether the label should respect [pango markup syntax](https://docs.gtk.org/Pango/pango_markup.html), `true` by default. """ @document get_value """ ``` get_value(::Adjustment) -> Float32 get_value(::SpinButton) -> Float32 get_value(::Scale) -> Float32 get_value(::LevelBar) -> Float32 get_value(::Animation) -> Float64 ``` Get current value of the underlying adjustment. --- ``` get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{<:AbstractFloat}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Vector{T}}) where T <: AbstractFloat get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{<:Signed}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Vector{T}}) where T <: Signed get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{<:Unsigned}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Vector{T}}) where T <: Unsigned get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Bool}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Vector{Bool}}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{String}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Vector{String}}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{RGBA}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{HSVA}) get_value(file::KeyFile, ::GroupID, ::KeyID, ::Type{Image}) ``` Deserialize a value from the keyfile, then return it as a the specfied type. Returns a default value if the key-value pair or group does not exist, or it cannot be converted to the given type. """ @document get_velocity """ ``` get_velocity(SwipeEventController) -> Vector2f ``` Get the swipe's current velocity, in absolute widget-space coordinates. """ @document get_vertex_color """ ``` get_vertex_color(::Shape, index::Integer) -> RGBA ``` Get color of the vertex at given index, or `RGBA(0, 0, 0, 0)` if `index` is out of bounds. """ @document get_vertex_color_location """ ``` get_vertex_color_location() -> Cuint ``` Get the native uniform location for the `_vertex_color` input value of all vertex shaders. """ @document get_vertex_position """ ``` get_vertex_position(::Shape, ::Integer) -> Vector3f ``` Get the position of vertex at given index, in 3D OpenGL coordinates. """ @document get_vertex_position_location """ ``` get_vertex_position_location() -> Cuint ``` Get the native uniform location for `_vertex_position` input value of all vertex shaders. """ @document get_vertex_shader_id """ ``` get_vertex_shader_id(::Shader) -> Cuint ``` Get the native OpenGL handle of vertex shader component of a shader program. """ @document get_vertex_texture_coordinate """ ``` get_vertex_texture_coordinate(::Shape, index::Integer) -> Vector2f ``` Get the texture coordinate of a vertex at given index, in relative texture-space coordinates. """ @document get_vertex_texture_coordinate_location """ ``` get_vertex_texture_coordinate_location() -> Cuint ``` Get the native uniform location for `_vertex_texture_coordinate` input value of all vertex shaders. """ @document get_vertical_adjustment """ ``` get_vertical_adjustment(::Viewport) -> Adjustment ``` Get the underlying adjustment of the vertical scrollbar. """ @document get_vertical_alignment """ ``` get_vertical_alignment(::Widget) -> Alignment ``` Get widget alignment along the y-axis. """ @document get_vertical_scrollbar_policy """ ``` get_vertical_scrollbar_policy(::Viewport) -> ScrollbarVisibilityPolicy ``` Get the policy governing how and if the vertical scrollbar is revealed / hidden. """ @document get_visible_child """ ``` get_visible_child(stack::Stack) -> StackID ``` Get the ID of currently selected child. """ @document get_was_modified """ ``` get_was_modified(::TextView) -> Bool ``` Get whether the "was modified" flag of a `TextView` was set to `true`. """ @document get_wrap_mode """ ``` get_wrap_mode(::Label) -> LabelWrapMode ``` Get the mode used to determine at which point in a line a linebreak will be inserted. --- ``` get_wrap_mode(::TextureObject) -> TextureWrapMode ``` Get the OpenGL texture wrap mode. """ @document get_x_alignment """ ``` get_x_alignment(::Label) -> Float32 ``` Get the horizontal alignment of the label's text. """ @document get_y_alignment """ ``` get_y_alignment(::Label) -> Float32 ``` Get the vertical alignment of the label's text. """ @document goto_page! """ ``` goto_page!(::Notebook, position::Integer) ``` Jump to page at given index, or the last page if `position` is out of bounds. """ @document grab_focus! """ ``` grab_focus!(::Widget) ``` Attempt to grab input focus. This may fail. """ @document has_action """ ``` has_action(::Application, id::String) -> Bool ``` Check whether an action with the given ID is registered. """ @document has_axis """ ``` has_axis(::StylusEventController, ::DeviceAxis) -> Bool ``` Check whether a stylus device supports the given axis. """ @document has_column_with_title """ ``` has_column_with_title(::ColumnView, title::String) -> Bool ``` Check whether the `ColumnVIew` has a column with the given title. """ @document has_group """ ``` has_group(::KeyFile, group::GroupID) -> Bool ``` Check if the `KeyFile` has a group with given ID. """ @document has_icon """ ``` has_icon(::IconTheme, icon::Icon) -> Bool has_icon(::IconTheme, id::String) -> Bool ``` Check whether icon theme has an icon. """ @document has_key """ ``` has_key(::KeyFile, group::GroupID, key::KeyID) -> Bool ``` Check whether the key file has a group with the given ID and whether that group has a key with given ID. """ @document hbox """ ``` hbox(::Widget...) -> Box ``` Convenience function that wraps list of a widget in a horizontally oriented box. """ @document hide! """ ``` hide!(::Widget) ``` Hide the widget, this means its allocated size will become `0` and all of its children will be hidden. """ @document hold! """ ``` hold!(::Application) ``` Prevent the application from closing. Use [`release!`](@ref) to undo this. """ @document hsva_to_rgba """ ``` hsva_to_rgba(hsva::HSVA) -> RGBA ``` Convert HSVA to RGBA. """ @document html_code_to_rgba """ ``` html_code_to_rgba(code::String) -> RGBA ``` Read an html color code of the form `#RRGGBB` or `#RRGGBBAA`, in hexadecimal. """ @document insert_at! """ ``` insert_at!(::FlowBox, ::Widget, index::Integer) -> Cvoid insert_at!(::ListView, ::Widget, index::Integer, [::ListViewIterator]) -> ListViewIterator insert_at!(::GridView, inde::Integer, ::Widget) -> Cvoid insert_at!(::Grid, ::Widget, row_i::Signed, column_i::Signed, [n_horizontal_cells:Unsigned = 1, n_vertical_cells::Unsigned = 1]) -> Cvoid ``` Insert a widget at the given position. --- ``` insert_at!(::DropDown, index::Integer, label_for_both::String) -> DropDownItemID insert_at!(::DropDown, index::Integer, list_widget::Widget, label_widget::Widget) -> DropDownItemID insert_at!(f, ::DropDown, index::Integer, list_widget::Widget, label_widget::Widget, [::Data_t]) -> DropDownItemID insert_at!(f, ::DropDown, index::Integer, label_for_both::String, [::Data_t]) -> DropDownItemID ``` Add an item to the `DropDown` at given index. When it is selected `label_widget` will appear as the child of the `DropDown`, while `list_widget` will be used as the widget displayed when the `DropDown` menu is open. `f` is called when the corresponding item is selected. `f` is required to be invocable as a function with the signature ``` (::DropDown, [::Data_t]) -> Cvoid ``` Returns a unique ID identifying the inserted item. See the manual section on `DropDown` in the chapter on widgets for more information. --- ``` insert_at!(::Notebook, index::Integer, child_widget::Widget, label_widget::Widget) ``` Insert a page at the given position, where `child_widget` is the widget used as the page, and `label_widget` is the widget displayed in the tab bar. """ @document insert_after! """ ``` insert_after!(::Box, to_append::Widget, after::Widget) ``` Insert `to_append` such that it comes right after `after`. """ @document insert_column_at! """ ``` insert_column_at!(grid::Grid, column_i::Signed) ``` Insert an empty column after the given index (may be negative). --- ``` insert_column:at!(column_view::ColumnView, index::Integer, title::String) ``` Insert a column at the given index. Each row of the column will be empty. """ @document insert_next_to! """ ``` insert_next_to!(::Grid, to_insert::Widget, already_in_grid::Widget, position::RelativePosition, [n_horizontal_cells:Unsigned = 1, n_vertical_cells::Unsigned = 1]) -> Cvoid ``` Insert widget into a grid such that it is now next to `already_in_grid`, where `position` describes the relative position of `already_in_grid` to `to_insert`. """ @document insert_row_at! """ ``` insert_row_at!(grid::Grid, row_i::Signed) ``` Insert an empty row after the given index (may be negative). --- ``` insert_row_at!(::ColumnView, index::Integer, widgets::Widget...) ``` Insert several widgets as a row, inserting them into the corresponding column. If the number of widgets is lower than the number of columns, the left-over columns will contain an empty cell in that row. """ @document is_cancelled """ ``` is_cancelled(::FileMonitor) -> Bool ``` Check whether the file monitor has been cancelled. """ @document is_file """ ``` is_file(::FileDescriptor) -> Bool ``` Check whether the location on disk contains points to a valid file (not folder). """ @document is_folder """ ``` is_folder(::FileDescriptor) -> Bool ``` Check whether the location on disk contains points to a valid folder (not file). """ @document get_is_local """ ``` get_is_local(::Clipboard) -> Bool ``` Check whether the content of the clipboard was set from within the currently active Mousetrap application. """ @document is_symlink """ ``` is_symlink(::FileDescriptor) -> Bool ``` Get whether the location on disk is a valid symbolic link. """ @document is_valid_html_code """ ``` is_valid_html_code(code::String) -> Bool ``` Check whether `code` is a string that can be converted to a color using [`html_code_to_rgba`](@ref). """ @document main """ ``` main(f; application_id::ApplicationID) ``` Run `f`, which is required to be invocable as a function with signature ``` (::Application, [::Data_t]) -> Cvoid ``` This function automatically creates an application with given ID and starts the main loop. If an error occurs during `f`, the application safely exits. ## Example ```julia using Mousetrap main() do app::Application window = Window(app) present!(window) end ``` """ @document log_debug """ ``` log_debug(::LogDomain, message::Sting) @log_debug(::LogDomain, message::Sting) ``` Display a log message with level `DEBUG`. Messages of this level will only be displayed once `set_surpress_debug!` is set to `false` for this log domain. If [`set_log_file!`](@ref) was called before, the message will also be appended to that file. """ @document log_info """ ``` log_info(::LogDomain, message::Sting) @log_info(::LogDomain, message::Sting) ``` Display a log message with level `INFO`. Messages of this level can be surpressed by setting [`set_surpress_info!`](@ref) for this log domain to `true`. If [`set_log_file!`](@ref) was called before, the message will also be appended to that file. """ @document log_warning """ ``` log_warning(::LogDomain, message::Sting) @log_warning(::LogDomain, message::Sting) ``` Display a log message with level `WARNING`. If [`set_log_file!`](@ref) was called before, the message will also be appended to that file. """ @document log_critical """ ``` log_critical(::LogDomain, message::Sting) @log_critical(::LogDomain, message::Sting) ``` Display a log message with level `CRITICAL`. If [`set_log_file!`](@ref) was called before, the message will also be appended to that file. """ @document log_fatal """ ``` log_fatal(::LogDomain, message::Sting) @log_fatal(::LogDomain, message::Sting) ``` Display a log message with level `FATAL`. Immediately after, runtime ends. If [`set_log_file!`](@ref) was called before, the message will also be appended to that file. """ @document make_current """ ``` make_current(::RenderArea) make_current(::GLArea) ``` Bind the associated frame buffer as the one currently being rendered to. This is usually not necessary. """ @document mark_as_busy! """ ``` mark_as_busy!(::Application) ``` Mark the application as busy, this will tell the OS that the application is currently processing something. The caller of this function is reponsible for calling [`unmark_as_busy!`](@ref) to undo this action. """ @document microseconds """ ``` microseconds(n::Number) -> Time ``` Create time from number of microseconds. """ @document milliseconds """ ``` milliseconds(n::Number) -> Time ``` Create time from number of milliseconds. """ @document minutes """ ``` minutes(n::Number) -> Time ``` Create time from number of minutes. """ @document mouse_button_01_pressed """ ``` mouse_button_01_pressed(::ModifierState) -> Bool ``` Check whether the modifier state indicates that the left mouse button is currently pressed. """ @document mouse_button_02_pressed """ ``` mouse_button_02_pressed(::ModifierState) -> Bool ``` Check whether the modifier state indicates that the right mouse button is currently pressed. """ @document move! """ ``` move!(from::FileDescriptor, to::FileDescriptor, allow_overwrite::Bool ; [make_backup::Bool = false, follow_symlink::Bool = true]) -> Bool ``` Move a file to a different location. Returns `true` if the operation was successful. """ @document move_page_to! """ ``` move_page_to!(::Notebook, current_index::Integer, new_index::Integer) ``` Move notebook page at position `current_index` to position `new_index`, 1-based. This will emit signal `page_reordered`. """ @document move_to_trash! """ ``` move_to_trash!(file::FileDescriptor) ::Bool ``` Safely move the file to the operating system garbage bin. This operation can be undone by the user. Returns `true` if the operation was successful. """ @document nanoseconds """ ``` nanoseconds(n::Integer) -> Time ``` Create time from number of nanoseconds. """ @document next_page! """ ``` next_page!(::Notebook) ``` Go to the next page, if possible. """ @document on_accept! """ ``` on_accept!(f, chooser::FileChooser, [::Data_t]) ``` Register a callback to be called when the user clicks the "accept" button. `f` is required to be invocable as a function with signature ``` (::FileChooser, ::Vector{FileDescriptor}, [::Data_t]) -> Cvoid ``` ## Example ```julia file_chooser = FileChooser(FILE_CHOOSER_ACTION_OPEN_FILE) on_accept!(file_chooser) do x::FileChooser, files::Vector{FileDescriptor} # use `files` here end ```` --- ``` on_accept!(f, chooser::ColorChooser, [::Data_t]) ``` Register a callback to be called when the user makes a color selection. `f` is required to be invocable as a function with signature: ``` (::FileChooser, ::RGBA, [::Data_t]) -> Cvoid ``` ## Example ```julia color_chooser = ColorChooser() on_accept!(color_chooser) do self::ColorChooser, color::RGBA # use `color` here end ``` """ @document on_cancel! """ ``` on_cancel!(f, chooser::FileChooser, [::Data_t]) ``` Register a callback to be called when the user clicks the "cancel" button of the file chooser. `f` is required to be invocable as a function with signature ``` (::FileChooser, [::Data_t]) -> Cvoid ``` ## Example ```julia file_chooser = FileChooser(FILE_CHOOSER_ACTION_OPEN_FILE) on_cancel!(file_chooser) do x::FileChooser println("file selection cancelled.") end ``` --- ``` on_cancel!(f, chooser::ColorChooser, [::Data_t]) ``` Register a callback to be called when the user cancels color selection or otherwise closes the dialog. `f` is required to be invocable as a function with signature: ``` (::FileChooser, [::Data_t]) -> Cvoid ``` ## Example ```julia color_chooser = ColorChooser() on_cancel!(color_chooser) do self::ColorChooser println("color selection cancelled") end ```` """ @document on_done! """ ``` on_done!(f, ::Animation, [::Data_t]) ``` Register a callback called when the animations state changes from `ANIMATION_STATE_PLAYING` to `ANIMATION_STATE_DONE`. `f` is required to be invocable as a function with signature ``` (::Animation, [::Data_t]) -> Cvoid ``` ## Example ```julia animation = Animation(widget, seconds(1)) on_done!(animation) do self::Animation println("done") end play!(animation) ``` """ @document on_file_changed! """ ``` on_file_changed!(f, monitor::FileMonitor, [::Data_t]) ``` Register a callback to be called when the monitored file is modified. `f` is required to be invocable as a function with signature ``` (::FileMonitor, ::FileMonitorEvent, self::FileDescriptor, other::FileDescriptor) -> Cvoid ``` Where `other` may not point to a valid file, depending on the event type. ## Example ```julia file = FileDescriptor("path/to/file.jl") @assert(exists(file)) monitor = create_monitor(file) on_file_changed!(monitor) do x::FileMonitor, event_type::FileMonitorEvent, self::FileDescriptor, other::FileDescriptor if event_type == FILE_MONITOR_EVENT_CHANGED println("File at " * get_path(self) * " was modified.") end end ``` """ @document on_selection! """ ``` on_selection!(f, ::AlertDialog, [::Data_t]) ``` Register a callback to be called when the user clicks one of the dialog's buttons or dismisses the dialog. `f` is required to be invocable as a function with signature ``` (::AlertDialog, button_index::Signed, [::Data_t]) -> Cvoid ``` Where `button_index` is the index of the current button (1-based), or `0` if the dialog was dismissed. ## Example ```julia alert_dialog = AlertDialog(["Yes", "No"], "Is this a dialog?") on_selection!(alert_dialog) do self::AlertDialog, button_index::Signed if button_index == 0 println("User dismissed the dialog") else println("User chose \$(get_button_label(self, button_index))") end end present!(alert_dialog) ``` """ @document on_tick! """ ``` on_tick!(f, ::Animation, [::Data_t]) ``` Register a callback called every frame while the animation is active. `f` is required to be invocable as a function with signature ``` (::Animation, value::AbstractFloat, [::Data_t]) -> Cvoid ``` Where `value` is the currently interpolated value. ## Example ```julia animation = Animation(widget, seconds(1)) on_tick!(animation) do self::Animation, value::AbstractFloat # use `value` here end play!(animation) ``` """ @document open_file """ ``` open_file(::FileDescriptor) -> Cvoid ``` Asynchronously launch the default application to open the file or folder. May present the users with a list of applications they can choose from. """ @document open_url """ ``` open_url(uri::String) -> Cvoid ``` Asynchronously launch the default application to open the URI. This will usually be the user's web browser """ @document pause! """ ``` pause!(::Animation) ``` If the animation is playing, pause it, otherwise does nothing. """ @document play! """ ``` play!(::Animation) ``` If the animation is currently paused, resume playing, otherwise restart the animation from the beginning. """ @document popdown! """ ``` popdown!(::Popover) popdown!(::PopoverButton) ``` Close the popover window. """ @document popup! """ ``` popup!(::Popover) popup!(::PopoverButton) ``` Present the popover window. """ @document present! """ ``` present!(::Window) present!(::Popover) present!(::FileChooser) present!(::ColorChooser) present!(::AlertDialog) ``` Show the window to the user. """ @document previous_page! """ ``` previous_page!(::Notebook) ``` Go to the previous page, if possible. """ @document pulse """ ``` pulse(::ProgressBar) ``` Trigger an animation that signifies to the user that progress has been made. This does not increase the displayed ratio of the progress bar. """ @document push_back! """ ``` push_back!(::Box, ::Widget) -> Cvoid push_back!(::FlowBox, ::Widget) -> Cvoid push_back!(::ListView, ::Widget, [::ListViewIterator]) -> ListViewIterator push_back!(::GridView, ::Widget) -> Cvoid push_back!(::HeaderBar, ::Widget) -> Cvoid push_back!(::ActionBar, ::Widget) -> Cvoid ``` Add a widget to the end of the container. --- ``` push_back!(::DropDown, label_for_both::String) -> DropDownItemID push_back!(::DropDown, list_widget::Widget, label_widget::Widget) -> DropDownItemID push_back!(f, drop_down::DropDown, list_widget::Widget, label_widget::Widget, [::Data_t]) -> DropDownItemID push_back!(f, drop_down::DropDown, label_for_both::String, [::Data_t]) -> DropDownItemID ``` Add an item to the end of the `DropDown`. When it is selected `label_widget` will appear as the child of the `DropDown`, while `list_widget` will be used as the widget displayed when the `DropDown` menu is open. `f` is called when the corresponding item is selected. `f` is required to be invocable as a function with the signature ``` (::DropDown, [::Data_t]) -> Cvoid ``` Returns a unique ID identifying the inserted item. See the manual section on `DropDown` in the chapter on widgets for more information. --- ``` push_back!(::Notebook, inde::Integer, child_widget::Widget, label_widget::Widget) ``` Insert a page at the end of the notebook, where `child_widget` is the widget used as the page, and `label_widget` is the widget displayed in the tab bar. """ @document push_back_column! """ ``` push_back_column!(::ColumnView, title::String) -> ColumnViewColumn ``` Add a column to the end of the column view. """ @document push_back_row! """ ``` push_back_row!(column_view::ColumnView, widgets::Widget...) -> Cvoid ``` Add widgets to the end of the rows, inserting them into the corresponding column. If the number of widgets is lower than the number of columns, the left-over columns will contain an empty cell in that row. """ @document push_front! """ ``` push_front!(::Box, ::Widget) -> Cvoid push_front!(::FlowBox, ::Widget) -> Cvoid push_front!(::ListView, ::Widget, [::ListViewIterator]) -> ListViewIterator push_front!(::GridView, ::Widget) -> Cvoid push_front!(::HeaderBar, ::Widget) -> Cvoid push_front!(::ActionBar, ::Widget) -> Cvoid ``` Add a widget to the start of the container. --- ``` push_front!(::DropDown, label_for_both::String) -> DropDownItemID push_front!(::DropDown, list_widget::Widget, label_widget::Widget) -> DropDownItemID push_front!(f, drop_down::DropDown, list_widget::Widget, label_widget::Widget, [::Data_t]) -> DropDownItemID push_front!(f, drop_down::DropDown, label_for_both::String, [::Data_t]) -> DropDownItemID ``` Add an item to the start of the `DropDown`. When it is selected `label_widget` will appear as the child of the `DropDown`, while `list_widget` will be used as the widget displayed when the `DropDown` menu is open. `f` is called when the corresponding item is selected. `f` is required to be invocable as a function with the signature ``` (::DropDown, [::Data_t]) -> Cvoid ``` Returns a unique ID identifying the inserted item. See the manual section on `DropDown` in the chapter on widgets for more information. --- ``` push_front!(::Notebook, inde::Integer, child_widget::Widget, label_widget::Widget) ``` Insert a page at the start of the notebook, where `child_widget` is the widget used as the page, and `label_widget` is the widget displayed in the tab bar. """ @document push_front_column! """ ``` push_front_column!(column_view::ColumnView, title::String) -> ColumnViewColumn ``` Add a column to the start of the column view. """ @document push_front_row! """ ``` push_front_row!(column_view::ColumnView, widgets::Widget...) -> Cvoid ``` !!! compat This function was deprecated in v0.3.2, use [`insert_row_at!`](@ref) instead Add widgets to the start of the rows, inserting them into the corresponding column. If the number of widgets is lower than the number of columns, the leftover columns will contain an empty cell in that row. """ @document query_info """ ``` query_info(::FileDescriptor, attribute_id::String) -> String ``` Access metadata info about a file. A list of attribute IDs can be found [here](https://gitlab.gnome.org/GNOME/glib/-/blob/main/gio/gfileinfo.h#L46). Note that there is no guarantee that a file will contain a value for any of these attributes. """ @document queue_render """ ``` queue_render(::RenderArea) queue_render(::GLArea) ``` Request for the `RendeArea` to performa a render cycle and flush the current framebuffer to the screen. There is no guarantee that this will happen immediately. """ @document quit! """ ``` quit!(::Application) ``` Exit the application. """ @document radians """ ``` radians(::Number) -> Angle ``` Construct angle from radians. """ @document read_symlink """ ``` read_symlink(self::FileDescriptor) -> FileDescriptor ``` If the file location is a valid symlink, follow that symlink and return the resulting file. """ @document redo! """ ``` redo!(::TextView) ``` Invoke the "redo!" keybinding signal. If there is no action on the undo stack, this function does nothing. """ @document release! """ ``` release!(::Application) ``` Release an application that is currently being prevented from exiting because [`hold!`](@ref) was called before. """ @document remove! """ ``` remove!(::Box, ::Widget) -> Cvoid remove!(::FlowBox, ::WIdget) -> Cvoid remove!(::ActionBar, ::Widget) -> Cvoid remove!(::ListView, index::Integer, [::ListViewIterator]) -> Cvoid remove!(::GridView, ::Widget) -> Cvoid remove!(::Grid, ::Widget) -> Cvoid remove!(::HeaderBar, ::Widget) -> Cvoid remove!(::DropDown, item_id::DropDownItemID) -> Cvoid remove!(::Notebook, position::Integer) -> Cvoid ``` Remove an item from the container. """ @document remove_action! """ ``` remove_action!(::Application, id::String) ``` Unregister an action from the application. Any connected widgets such as `Button` or `MenuModel` will be disabled. --- ``` remove_action!(::ShortcutEventController, ::Action) ``` Remove an action, such that the controller will not longer trigger it if any of its shortcuts are recognized. """ @document remove_button! """ ``` remove_button!(::AlertDialog, index::Signed) ``` Remove the button at given position (1-based, left-to-right), this means all buttons after it have their index shifted by 1. """ @document remove_center_child! """ ``` remove_center_child!(::CenterBox) remove_center_child!(::ActionBar) ``` Remove the middle child of the center box. """ @document remove_child! """ ``` remove_child!(::Fixed, child::Widget) remove_child!(::Window) remove_child!(::AspectFrame) remove_child!(::Button) remove_child!(::CheckButton) remove_child!(::ToggleButton) remove_child!(::Expander) remove_child!(::Frame) remove_child!(::Overlay) remove_child!(::PopupMessageOverlay) remove_child!(::Popover) remove_child!(::PopoverButton) remove_child!(::Stack, id::String) remove_child!(::Revealer) remove_child!(::Viewport) remove_child!(::TransformBin) ``` Remove the widget's singular child, such that it is now empty. """ @document remove_css_class! """ ``` remove_css_class!(::Widget, class::String) ``` Undo the effects of `add_css_class!`. """ @document remove_column! """ ``` remove_column!(::ColumnView, column::ColumnViewColumn) ``` Remove a column from the column view, this also frees all of its rows. """ @document remove_column_at! """ ``` remove_column_at!(::Grid, column_i::Signed) ``` Remove column at given index (1-based, may be negative). Any following columns will be shifted to the left. """ @document remove_controller! """ ``` remove_controller!(::Widget, controller::EventController) ``` Disconnect an event controller. """ @document remove_end_child! """ ``` remove_end_child!(::CenterBox) remove_end_child!(::Paned) ``` Remove the latter child of the widget. """ @document remove_extra_widget! """ ``` remove_extra_widget!(::AlertDialog) ``` Remove the widget set using `set_extra_widget!`. """ @document remove_label_widget! """ ``` remove_label_widget!(::Expander) remove_label_widget!(::Frame) ``` Remove the label widget of the widget, such that it now has no label widget. """ @document remove_marker! """ ``` remove_marker!(::LevelBar, name::String) ``` Remove a marker with the given label from the level bar. """ @document remove_overlay! """ ``` remove_overlay!(overlay::Overlay, overlayed::Widget) ``` Remove an overlaid widget added via [`add_overlay!`](@ref). """ @document remove_popover! """ ``` remove_popover!(::PopoverButton) ``` Remove the connected `Popover` or `PopoverMenu`. """ @document remove_primary_icon! """ ``` remove_primary_icon!(::Entry) ``` Remove the left icon of the entry. """ @document remove_row_at! """ ``` remove_row_at!(grid::Grid, row_i::Signed) ``` Remove a row at specified position (1-based). """ @document remove_secondary_icon! """ ``` remove_secondary_icon!(::Entry) ``` Remove the right icon of the entry. """ @document remove_start_child! """ ``` remove_start_child!(::Paned) remove_start_child!(::CenterBox) ``` Remove the start child such that the widget is now empty at that position """ @document remove_texture! """ ``` remove_texture!(::Shape) ``` Make it such that shape no longer has a texture, meaning it will be rendered as a solid color. """ @document remove_tick_callback! """ ``` remove_tick_callback!(::Widget) ``` Remove a registered tick callback. Usually, this should be done by returning `TICK_CALLBACK_RESULT_DISCONTINUE` from within the tick callback function. """ @document remove_title_widget! """ ``` remove_title_widget!(::HeaderBar) ``` Remove widget that is currently used as the title element, and instead use the default window title. To completely hide the title, use `set_title!(window, "")` on the associated window instance. """ @document remove_tooltip_widget! """ ``` remove_tooltip_widget!(::Widget) ``` Remove the tooltip widget. The widget will no longer display a tooltip. """ @document render """ ``` render(::RenderTask) render(shape::Shape, shader::Shader, transform::GLTransform) ``` Draw a shape to the currently bound framebuffer, forwarding the transform to the vertex shader and applying the fragment shader to all fragments of the shape. Note that calling this function is usually not necessary, instead, register a `RenderTask` with a `RenderArea` using `add_render_task!`, after which the task will be automatically rendered every frame, unless a custom signal handler was connected to `RenderArea`s signal `render`. """ @document render_render_tasks """ ``` render_render_tasks(::RenderArea) ``` Render all registered render tasks. This is only necessary when a custom signal handler is connected to the areas signal `render`. """ @document reset! """ ``` reset!(::Animation) ``` Reset animation's state to idle. --- ``` reset!(::GLTransform) reset!(::TransformBin) ``` Override the transform such that it is now the identity transform. """ @document reset_style! """ ``` reset_style!(::Widget) ``` Remove all style classes from a widget. """ @document reset_text_to_value_function! """ ``` reset_text_to_value_function!(::SpinButton) ``` Reset the function registered using `set_text_to_value_function!`, using the default handler instead. """ @document reset_value_to_text_function! """ ``` reset_value_to_text_function!(::SpinButton) ``` Reset the function registered using `set_value_to_text_function!`, using the default handler instead. """ @document restart! """ ``` restart!(clock::Clock) -> Time ``` Restart the clock and return the elapsed time since the last restart. """ @document rgba_to_hsva """ ``` rgba_to_hsva(rgba::RGBA) -> HSVA ``` Convert RGBA to HSVA. """ @document rgba_to_html_code """ ``` rgba_to_html_code(rgba::RGBA) -> String ``` Convert the color to an html-style hexadecimal string of the form `#RRGGBB`. The alpha component is ignored. """ @document rotate! """ ``` rotate!(::TransformBin, angle::Angle) ``` Rotate child widget around its center. --- ``` rotate!(::GLTransform, angle::Angle, [origin::Vector2f]) rotate!(::Shape, angle::Angle, [origin::Vector2f]) ``` Rotate around a point, in OpenGL coordinates. """ @document run! """ ``` run!(app::Application) -> Cint ``` Start the main loop, initializing the internal state and triggering `Application`s signal `activate`. Note that no part of Mousetrap should be used or initialized before this function is called. Usually, users are encouraged to use [`main`](@ref) instead, which does this automatically. ## Example ```julia app = Application("example.app") connect_signal_activate!(app) app::app # all initialization should happen here end run(app) # start the main loop ``` """ @document save_to_file """ ``` save_to_file(::Image, path::String) -> Bool ``` Save the image to a file, the file format is automatically determined based on the extension of the given path. Returns `true` if the operation was successful. --- ``` save_to_file(::KeyFile, path::String) -> Bool ``` Serialize the key file to a string and save that string to a file. Usually, the extension for this file should be `.ini`. Returns `true` if the operation was successful """ @document scale! """ ``` scale!(::TransformBin, x_scale::Number, y_scale::Number) scale!(::GLTransform, x_scale::AbstractFloat, y_scale::AbstractFloat) ``` Combine the transform with a scale transform. To scale around a point, first `translate!` the transform to that point, then apply `scale!`, then `translate!` the transform back to origin. Uses relative scale, where `2` is twice as big, `0.5` is half as big, `1` is no change compared to the original size. """ @document seconds """ ``` seconds(n::Number) -> Time ``` Create from number of seconds. """ @document select! """ ``` select!(::SelectionModel, i::Integer, [unselect_others::Bool = true]) ``` Select item at given position, this will emit signal `selection_changed`. """ @document select_all! """ ``` select_all!(::SelectionModel) ``` Select all items, triggering emission of signal `selection_changed`. This is only possible if the selection mode is `SELECTION_MODE_MULTIPLE`. """ @document self_is_focused """ ``` self_is_focused(::FocusEventController) -> Bool ``` Check if the widget the controller was added to currently holds focus. """ @document self_or_child_is_focused """ ``` self_or_child_is_focused(::FocusEventController) -> Bool ``` Check if the widget the controller was added to, or any of the widgets children currently hold focus. """ @document serialize """ ``` serialize(::RGBA) -> String serialize(::HSVA) -> String ``` Convert the object to its CSS representation. """ @document set_acceleration_rate! """ ``` set_acceleration_rate!(::SpinButton, factor::AbstractFloat) ``` Set the rate at which the `SpinButton`'s value accelerates when a button is held down, where `0.0` is the default rate, `0.1` is 10%, `1.0` is 100%, etc. May not be negative. """ @document set_accept_label! """ ``` set_accept_label!(::FileChooser, label::String) ``` Set the label used for the "accept" button of the file chooser. """ @document set_action! """ ``` set_action!(::Button, ::Action) ``` Connect an action to the button. When the button is clicked, the action is activated. When the action is disabled, the button is also disabled. Note that a button can have both an action and a signal handler connected. If this is the case and the button is clicked, both are triggered. """ @document set_active! """ ``` set_active!(::CheckButton, ::Bool) ``` If `true`, set the check button state to [`CHECK_BUTTON_STATE_ACTIVE`](@ref), otherwise set to [`CHECK_BUTTON_STATE_INACTIVE`](@ref). """ @document set_alignment! """ ``` set_alignment!(::Widget, both::Alignment) ``` Set both the horizontal and vertical alignment of a widget at the same time. """ @document set_allow_only_numeric! """ ``` set_allow_only_numeric!(::SpinButton, ::Bool) ``` Set whether the spin button should only accept numeric text entry, as opposed to alphanumeric. """ @document set_always_show_arrow! """ ``` set_always_show_arrow!(::DropDown, ::Bool) set_always_show_arrow!(::PopoverButton, ::Bool) ``` Set whether an arrow should be drawn next to the label. """ @document set_application! """ ``` set_application!(window::Window, app::Application) ``` Register the window with the application. This is usually done automatically. """ @document set_autohide! """ ``` set_autohide!(::Popover, ::Bool) ``` Set whether the popover should hide itself when the attached widget loses focus. """ @document set_auto_render! """ ``` set_auto_render(::GLArea, ::Bool) ``` Set whether the `render` signal should be emitted anytime the widget is drawn to the screen, `true` by default. """ @document set_button_action! """ ``` set_button_action!(::PopupMessage, ::Action) ``` Connect an action to the singular button of the message. Note that the messages button label has to be set to anything other than `""` for the button to be visible. """ @document set_button_label! """ ``` set_button_label!(::AlertDialog, id::Integer, label::String) ``` Replace the label of the button with given ID, obtained when calling `add_button!`. --- ``` set_button_label!(::PopupMessage, label::String) ``` If `label` is not empty, add a singular button to the message with the given label. When that button is clicked, the `PopupMessage` will emit signal `button_clicked`. """ @document set_bottom_margin! """ ``` set_bottom_margin!(::TextView, margin::AbstractFloat) ``` Set margin between the bottom of the text and the `TextView`'s frame. """ @document set_can_respond_to_input! """ ``` set_can_respond_to_input!(::Widget, ::Bool) ``` Set whether the widget can receive input events. If set to `false`, the widget may appear "greyed out", signaling to the user that it is inactive. """ @document set_center_child! """ ``` set_center_child!(::CenterBox, ::Widget) set_center_child!(::ActionBar, ::Widget) ``` Set the middle child of the center box. """ @document set_centroid! """ ``` set_centroid!(::Shape, centroid::Vector2f) ``` Move the shape such that its centroid is now at given position, in OpenGL coordinates. """ @document set_child! """ ``` set_child!(::Window, child::Widget) set_child!(::AspectFrame, child::Widget) set_child!(::Button, child::Widget) set_child!(::CheckButton, child::Widget) set_child!(::ToggleButton, child::Widget) set_child!(::Viewport, child::Widget) set_child!(::Expander, child::Widget) set_child!(::Frame, child::Widget) set_child!(::Overlay, child::Widget) set_child!(::PopupMessageOverlay, child::Widget) set_child!(::Popover, child::Widget) set_child!(::PopoverButton, child::Widget) set_child!(::Revealer, child::Widget) set_child!(::TransformBin, child::Widget) ``` Set the widget's singular child. """ @document set_child_position! """ ``` set_child_position!(::Fixed, child::Widget, position::Vector2f) ``` Set fixed position of the child, in absolute widget-space coordinates. """ @document set_child_x_alignment! """ ``` set_child_x_alignment!(::AspectFrame, alignment::AbstractFloat) ``` Set horizontal alignment of the `AspectFrame`'s child. `0.5` by default. """ @document set_child_y_alignment! """ ``` set_child_y_alignment!(::AspectFrame, alignment::AbstractFloat) ``` Set vertical alignment of the `AspectFrame`'s child. `0.5` by default. """ @document set_color! """ ``` set_color!(::Shape, ::RGBA) set_color!(::Shape, ::HSVA) ``` Set the color of all vertices of the shape. """ @document set_column_spacing! """ ``` set_column_spacing!(::FlowBox, spacing::AbstractFloat) set_column_spacing!(::Grid, spacing::AbstractFloat) ``` Set spacing between two columns of the grid, in pixels. """ @document set_columns_homogeneous! """ ``` set_columns_homogeneous!(::Grid, ::Bool) ``` Set whether all columns of the grid should be the same width. """ @document set_comment_above! """ ``` set_comment_above!(::KeyFile, ::GroupID, comment::String) set_comment_above!(::KeyFile, ::GroupID, ::KeyID, comment::String) ``` Set the singular comment above a group or key-value pair in the file. """ @document set_current_blend_mode """ ``` set_current_blend_mode(::BlendMode; allow_alpha_blending::Bool = true) ``` Enable GPU-side blending and set the current OpenGL blend mode. If `allow_alpha_blending` is set to `false`, only the RGB components of a fragment's color will participate in blending. """ @document set_current_theme! """ ``` set_current_theme!(::Application, ::Theme) ``` Swap the global theme used to determine the look and color of all widgets. This function is only available after the backend has been initialized. """ @document set_cursor! """ ``` set_cursor!(::Widget, cursor::CursorType) ``` Set which cursor shape should be used when the cursor is over the allocated area of the widget. """ @document set_cursor_from_image! """ ``` set_cursor_from_image!(::Widget, image::Image, [offset::Vector2i = Vector2i(0, 0)]) ``` Set which image should be displayed when the cursor is over the allocated area of the widget. `offset` determines the vertical and horizontal offset of the center of the image, relative to the cursor position, in pixels. """ @document set_cursor_visible! """ ``` set_cursor_visible!(::TextView, ::Bool) ``` Set whether the caret is visible. """ @document set_default_button! """ ``` set_default_button!(::AlertDialog, id::Integer) ``` Mark a button as the default response using the id obtained when calling `add_button!`. This will change the buttons look and make it such that that button becomes the default widget of the dialogs window. """ @document set_default_widget! """ ``` set_default_widget!(window::Window, ::Widget) ``` Designate a widget as the default widget. When the window is activated, for example by the user pressing the enter key, the default widget is activated. """ @document set_delay_factor! """ ``` set_delay_factor!(::LongPressEventController, factor::AbstractFloat) ``` Set a factor that multiplies the default delay after which a longpress gesture is recognized. """ @document set_destroy_with_parent! """ ``` set_destroy_with_parent!(::Window, ::Bool) ``` Set whether the window should close and be destroyed when the top-level window is closed. """ @document set_detailed_description! """ ``` set_detailed_description(::AlertDialog, message::String) ``` Set the detailed message, this is the text shown below the dialog's title. """ @document set_duration! """ ``` set_duration!(::Animation, ::Time) ``` Set the duration of the animation, microseconds precision. """ @document set_editable! """ ``` set_editable!(::TextView, ::Bool) ``` Set whether the user can edit the text in the text view. """ @document set_ellipsize_mode! """ ``` set_ellipsize_mode!(::Label, mode::EllipsizeMode) ``` Set the ellipsize mode of a label, `ELLIPSIZE_MODE_NONE` by default. """ @document set_enable_rubberband_selection! """ ``` set_enable_rubberband_selection!(::ListView, ::Bool) set_enable_rubberband_selection!(::GridView, ::Bool) set_enable_rubberband_selection!(::ColumnView, ::Bool) ``` Set whether the user can select multiple children by holding down the mouse button and click-dragging. The selectable widgets selection mode has to be `SELECTION_MODE_MULTIPLE` in order for this to be possible. """ @document set_enabled! """ ``` set_enabled!(::Action, ::Bool) ``` Set whether the action is enabled. If set to `false`, all connected buttons and menu items will be disabled as well. """ @document set_end_child! """ ``` set_end_child!(::CenterBox, child::Widget) set_end_child!(::Paned, child::Widget) ``` Set the latter child of the widget. """ @document set_end_child_resizable! """ ``` set_end_child_resizable!(::Paned, ::Bool) ``` Set whether the end child should resize when the `Paned` is resized. """ @document set_end_child_shrinkable! """ ``` set_end_child_shrinkable!(::Paned, ::Bool) ``` Set whether the user can resize the end child such that its allocated area inside the paned is smaller than the natural size of the child. """ @document set_expand! """ ``` set_expand!(::Widget, ::Bool) ``` Set whether the widget should expand along both the horizontal and vertical axis. """ @document set_is_expanded! """ ``` set_is_expanded(::Expander, ::Bool) ``` Automatically expand or hide the `Expander`'s child. """ @document set_expand_horizontally! """ ``` set_expand_horizontally!(::Widget, ::Bool) ``` Set whether the widget should expand along the x-axis. """ @document set_expand_vertically! """ ``` set_expand_vertically!(::Widget, ::Bool) ``` Set whether the widget should expand along the y-axis. """ @document set_extra_widget! """ ``` set_extra_widget!(::AlertDialog, ::Widget) ``` Insert a widget into the dialog's content area, it will be displayed underneath the detailed message. """ @document set_file! """ ``` set_file!(::Clipboard, file::FileDescriptor) ``` Override the content of the clipboard with a path to a file. Use [`get_string`](@ref) to retrieve it. """ @document set_file_chooser_action! """ ``` set_file_chooser_action!(::FileChooser, ::FileChooserAction) ``` Override the current file chooser action type, this may not change the layout until the dialog is hidden, then shown again. """ @document set_fixed_width! """ ``` set_fixed_width!(::ColumnViewColumn, width::AbstractFloat) ``` Set the fixed width of the column, in pixels, or `-1` if unlimited. """ @document set_focus_on_click! """ ``` set_focus_on_click!(::Widget, ::Bool) ``` Set whether the widget should attempt to grap focus when it is clicked. """ @document set_focus_visible! """ ``` set_focus_visible!(::Window, ::Bool) ``` Set whether which widget currently holds input focus should be highlighted using a border. """ @document set_fraction! """ ``` set_fraction!(::ProgressBar, zero_to_one::AbstractFloat) ``` Set the currently displayed fraction of the `ProgressBar`, in `[0, 1]`. """ @document set_fullscreen! """ ``` set_fullscreen!(::Window) ``` Request for the window manager to enter fullscreen mode. This may fail. """ @document set_function! """ ``` set_function!(f, action::Action, [::Data_t]) ``` Register a callback that should be called when the action is activated. ## Example ```julia action = Action("example.action") set_function!(action) do x::Action println(get_id(x) * " called") end ``` """ @document set_has_base_arrow! """ ``` set_has_base_arrow!(::Popover, ::Bool) ``` Set whether the "tail" of the popover pointing to widget it is attached to should be visible. """ @document set_has_border! """ ``` set_has_border!(::Notebook, ::Bool) ``` Set whether a border should be drawn around the notebook's perimeter. """ @document set_has_close_button! """ ``` set_has_close_button!(::Window, ::Bool) ``` Set whether the "x" button is present. """ @document set_has_frame! """ ``` set_has_frame!(::Button, ::Bool) set_has_frame!(::Viewport, ::Bool) set_has_frame!(::Entry, ::Bool) set_has_frame!(::PopoverButton, ::Bool) ``` Set whether the widget's outline should be displayed. This does not impact the widget's interactability. """ @document set_has_origin! """ ``` set_has_origin!(::Scale, ::Bool) ``` Set whether the area of the slider between the start of the range and the current value should be filled with a color. """ @document set_has_wide_handle! """ ``` set_has_wide_handle!(::Paned, ::Bool) ``` Set whether the barrier in-between the `Paned`s two children is wide or thin, wide by default. """ @document set_header_menu! """ ``` set_header_menu!(::ColumnViewColumn, model::MenuModel) ``` Add a menu model to be used as the column's header menu, which the user can access by clicking the column's title. """ @document set_hide_on_close! """ ``` set_hide_on_close!(::Window, ::Bool) ``` If set to to `true`, the window will be hidden when it is closed, if set to `false`, the window is destroyed when closed. `false` by default. If set to `true`, the caller of this function is responsible for deallocating the window by calling [`destroy!`](@ref). """ @document set_hide_on_overflow! """ ``` set_hide_on_overflow!(::Widget, ::Bool) ``` Set whether the entire widget should be hidden if its allocated area is smaller than its natural size. If `false`, the overflow part of the widget will be truncated. """ @document set_homogeneous! """ ``` set_homogeneous!(::Box, ::Bool) ``` Set whether the box allocates the same space for all of its children. """ @document set_horizontal_alignment! """ ``` set_horizontal_alignment!(::Widget, ::Alignment) ``` Set alignment along the x-axis. """ @document set_horizontal_scrollbar_policy! """ ``` set_horizontal_scrollbar_policy!(::Viewport, policy::ScrollbarVisibilityPolicy) ``` Set the policy governing when the horizontal scrollbar is revealed / hidden. """ @document set_icon! """ ``` set_icon!(::Button, ::Icon) set_icon!(::ToggleButton, ::Icon) set_icon!(::PopoverButton, ::Icon) ``` Replace the button's label with an icon. """ @document set_image! """ ``` set_image!(::Clipboard, image::Image) ``` Override the clipboard's content with an image. Use [`get_image`](@ref) to retrieve it. """ @document set_initial_file! """ ``` set_initial_file!(::FileChooser, ::FileDescriptor) ``` For `FILE_CHOOSER_ACTION_OPEN_FILE` or `FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FILES`, choose the file that is selected when the dialog is first shown. """ @document set_initial_filter! """ ``` set_initial_filter!(::FileChooser, ::FileFilter) ``` Set currently selected filter. If the filter was not yet added with [`add_filter!`](@ref), it will still be made the active filter, but the user will be unable to change the filter selection. """ @document set_initial_folder! """ ``` set_initial_folder(::FileChooser, ::FileDescriptor) ``` For `FILE_CHOOSER_ACTION_SELECT_FOLDER` or `FILE_CHOOSER_ACTION_OPEN_MULTIPLE_FOLDERS`, choose the folder that is selected when the dialog is first shown. """ @document set_initial_name! """ ``` set_initial_name!(::FileChooser, ::String) ``` For `FILE_CHOOSER_ACTION_SAVE`, set the name field that will be used to determine the saved file's name. """ @document set_inverted! """ ``` set_inverted!(::LevelBar, ::Bool) ``` Set whether the level bar should be mirrored along the horizontal or vertical axis, depending on orientation. """ @document set_is_active! """ ``` set_is_active!(::Switch, ::Bool) set_is_active!(::ToggleButton, ::Bool) set_is_active!(::CheckMark, ::Bool) ``` Set the internal state of the widget. """ @document set_is_circular! """ ``` set_is_circular!(::Button, ::Bool) set_is_circular!(::ToggleButton, ::Bool) set_is_circular!(::PopoverButton, ::Bool) ``` Set whether the button should be circular, as opposed to rectangular. """ @document set_is_decorated! """ ``` set_is_decorated!(::Window, ::Bool) ``` Set whether the header bar area of the window is visible. """ @document set_is_focusable! """ ``` set_is_focusable!(::Widget, ::Bool) ``` Set whether the widget can retrieve input focus. Most widgets that support interaction by default are already focusable. """ @document set_is_high_priority! """ ``` set_is_high_priority!(::PopupMessage, high_priority::Bool) ``` Set whether this message should be shown before any other non-high-priority messages already queue with the `PopupMessageOverlay`. `false` by default. """ @document set_is_horizontally_homogeneous! """ ``` set_is_horizontally_homogeneous!(::Stack, ::Bool) ``` Set whether the stack should allocate the same width for all of its pages. """ @document set_is_inverted! """ ``` set_is_inverted!(::ProgressBar, ::Bool) ``` Set whether the `ProgressBar` should be mirrored. """ @document set_is_modal! """ ``` set_is_modal!(::Window, ::Bool) set_is_modal!(::FileChooser, ::Bool) set_is_modal!(::ColorChooser, ::Bool) set_is_modal!(::AlertDialog, ::Bool) ``` Set whether all others windows should be paused while this window is active. """ @document set_is_resizable! """ ``` set_is_resizable!(::ColumnViewColumn, ::Bool) ``` Set whether the column can be resized. If set to `false`, the size set via [`set_fixed_width!`](@ref) will be used. """ @document set_is_reversed! """ ``` set_is_reversed!(::Animation, is_reversed::Bool) ``` If set to `false`, the animation will interpolate its value from the lower to upper bound, or the other way around if `true`. `false` by default. """ @document set_is_scrollable! """ ``` set_is_scrollable!(::Notebook, ::Bool) ``` Set whether the user can scroll between pages using the mouse scroll wheel or touchscreen. """ @document set_is_spinning! """ ``` set_is_spinning!(::Spinner, ::Bool) ``` Set whether the spinner is currently playing its animation. """ @document set_is_vertically_homogeneous! """ ``` set_is_vertically_homogeneous!(::Stack, ::Bool) ``` Set whether all pages of the stack should allocate the same height. """ @document set_is_visible! """ ``` set_is_visible!(::Widget, ::Bool) ``` Set whether the widget is hidden. --- ``` set_is_visible!(::Shape, ::Bool) ``` Set whether the shape and any associated render tasks should be rendered. --- ``` set_is_visible!(::ColumnViewColumn, ::Bool) ``` Temporarily remove the column and all its rows from the column view. """ @document set_justify_mode! """ ``` set_justify_mode!(::Label, mode::JustifyMode) set_justify_mode!(::TextView, mode:JustifyMode) ``` Set the text justification mode. """ @document set_kinetic_scrolling_enabled! """ ``` set_kinetic_scrolling_enabled!(::Viewport, ::Bool) set_kinetic_scrolling_enabled!(::ScrollEventController, ::Bool) ``` Set whether scrolling should continue once the user stopped operating the mouse wheel or touchscreen, simulating "inertia". """ @document set_label_widget! """ ``` set_label_widget!(::Expander, label::Widget) set_label_widget!(::Frame, label::Widget) ``` Choose a widget as the label. """ @document set_label_x_alignment! """ ``` set_label_x_alignment!(::Frame, ::AbstractFloat) ``` Set horizontal alignment of the label widget (if present), in `[0, 1]` """ @document set_layout! """ ``` set_layout!(::HeaderBar, layout::String) ``` Set layout string of the header bar. This is a list of button IDs. Valid IDs are limited to: + `maximize`: Maximize Button + `minimize`: Minimize Button + `close`: Close Button Any object left of `:` will be placed left of the title, any after `:` will be place right of the title. Object are delimited by `,`. ## Example ```julia header_bar = HeaderBar() set_layout!(header_bar, "close:maximize,minimize") """ @document set_left_margin! """ ``` set_left_margin!(::TextView, margin::AbstractFloat) ``` Set distance between the left end of the text and the `TextView`'s frame. """ @document set_log_file! """ ``` set_log_file!(path::String) -> Bool ``` Set file at `path` as the log file. Any logging will be pushed to the file as opposed to being printed to the console. The file will be created if it does not exist. If it does exist, the file will be appended to, as opposed to being overwritten. Returns `true` if the file was successfuly opened. """ @document set_lower! """ ``` set_lower!(::Adjustment, ::Number) set_lower!(::Scale, ::Number) set_lower!(::SpinButton, ::Number) set_lower!(::Animation, ::Number) ``` Set lower bound of the underlying range. """ @document set_listens_for_shortcut_action! """ ``` set_listens_for_shortcut_action!(::Widget, ::Action) ``` Adds the action to the widget's internal `ShortcutEventController`. While the widget holds focus, if the user presses the action's associated shortcut, the action will trigger. """ @document set_margin_bottom! """ ``` set_margin_bottom!(::Widget, margin::AbstractFloat) ``` Set distance between the bottom of the text and the `TextView`'s frame, in pixels. """ @document set_margin_end! """ ``` set_margin_end!(::Widget, margin::AbstractFloat) ``` Set right margin of the widget, in pixels. """ @document set_margin_horizontal! """ ``` set_margin_horizontal!(::Widget, margin::AbstractFloat) ``` Set both the left and right margin of the widget, in pixels. """ @document set_margin! """ ``` set_margin!(::Widget, margin::AbstractFloat) ``` Set both the left, right, top, and bottom margin of the widget, in pixels. """ @document set_margin_start! """ ``` set_margin_start!(::Widget, margin::AbstractFloat) ``` Set left margin of the widget, in pixels. """ @document set_margin_top! """ ``` set_margin_top!(::Widget, margin::AbstractFloat) ``` Set top margin of the widget, in pixels. """ @document set_margin_vertical! """ ``` set_margin_vertical!(::Widget, margin::AbstractFloat) ``` Set both the top and bottom margin of the widget, in pixels. """ @document set_maximum_size! """ ``` set_maximum_size!(::ClampFrame, size::AbstractFloat) ``` Set the maximum width (or height, if vertical) the frame should constrain its child to, in pixels. """ @document set_max_n_columns! """ ``` set_max_n_columns!(grid_view::GridView, n::Integer) ``` Limit the number of columns (or rows, if horizontal) to given number, or `-1` if unlimited. """ @document set_max_value! """ ``` set_max_value!(::LevelBar, value::AbstractFloat) ``` Set upper limit of the underlying range. """ @document set_max_width_chars! """ ``` set_max_width_chars!(::Label, n::Integer) set_max_width_chars!(::Entry, n::Integer) ``` Set the number of characters that the widget should make space for, or `-1` if unlimited. """ @document set_maximized! """ ``` set_maximized!(::Window, ::Bool) ``` Attempt to maximize or unmaximize the window. """ @document set_message! """ ``` set_message!(::AlertDialog, ::String) ``` Set the main message of the dialog, this will be used as the dialogs title. """ @document set_minimized! """ ``` set_minimized!(::Window, ::Bool) ``` Attempt to (un-)minimize the window. """ @document set_min_n_columns! """ ``` set_min_n_columns!(grid_view::GridView, n::Integer) ``` Limit the minimum number of columns, or unlimited if `-1`. """ @document set_min_value! """ ``` set_min_value!(::LevelBar, value::AbstractFloat) ``` Set the lower bound of the underlying range. """ @document set_mode! """ ``` set_mode!(::LevelBar, mode::LevelBarMode) ``` Set whether the level bar should display its value continuous or segmented. """ @document set_n_digits! """ ``` set_n_digits!(::SpinButton, n::Integer) ``` Set number of digits after the decimal point, up to a maximum of `20`. This only affects the visuals of the `SpinButton`, the internal value of the underlying adjustment is unaffected. """ @document set_only_listens_to_button! """ ``` set_only_listens_to_button!(::SingleClickGesture, button::ButtonID) ``` Set which mouse buttons the event controller should listen to, or `BUTTON_ID_ANY` to listen to all buttons. """ @document set_opacity! """ ``` set_opacity!(::Widget, opacity::AbstractFloat) ``` Set the opacity of the widget, in `[0, 1]`. """ @document set_orientation! """ ``` set_orientation!(::Box, ::Orientation) set_orientation!(::FlowBox, ::Orientation) set_orientation!(::CenterBox, ::Orientation) set_orientation!(::ClampFrame, ::Orientation) set_orientation!(::LevelBar, ::Orientation) set_orientation!(::Grid, ::Orientation) set_orientation!(::ProgressBar, ::Orientation) set_orientation!(::Scrollbar, ::Orientation) set_orientation!(::Separator, ::Orientation) set_orientation!(::ListView, ::Orientation) set_orientation!(::GridView, ::Orientation) set_orientation!(::Paned, ::Orientation) set_orientation!(::SpinButton, ::Orientation) set_orientation!(::Scale, ::Orientation) ``` Set orientation of the widget, this governs along which axis it aligns itself and its children. --- ``` set_orientation!(::PanEventController) ``` Set along which axis the event controller should listen for pan gestures. """ @document set_pixel! """ ``` set_pixel!(image::Image, x::Integer, y::Integer, color::RGBA) set_pixel!(image::Image, x::Integer, y::Integer, color::HSVA) ``` Override the color of a pixel, 1-based indexing. """ @document set_popover! """ ``` set_popover!(::PopoverButton, popover::Popover) ``` Attach a [`Popover`](@ref) to the popover button. This will detach any already attached `Popover` or `PopoverMenu`. """ @document set_popover_menu! """ ``` set_popover_menu!(popover_button::PopoverButton, popover_menu::PopoverMenu) ``` Attach a [`PopoverMenu`](@ref) to the popover button. This will detach any already attached `Popover` or `PopoverMenu`. """ @document set_position! """ ``` set_position!(::Paned, position::Integer) ``` Set position of the paned handle, relative to the `Paned`'s origin, in pixels. """ @document set_primary_icon! """ ``` set_primary_icon!(::Entry, icon::Icon) ``` Set left icon of the entry. """ @document set_propagate_natural_height! """ ``` set_propagate_natural_height!(::Viewport, ::Bool) ``` Set whether the viewport should assume the width of its child. This will usually hide the vertical scrollbar. """ @document set_propagate_natural_width! """ ``` set_propagate_natural_width!(::Viewport, ::Bool) ``` Set whether the viewport should assume the height of its child. This will usually hide the horizontal scrollbar. """ @document set_propagation_phase! """ ``` set_propagation_phase!(controller::EventController, ::PropagationPhase) ``` Set the phase at which the event controller will capture events, see [here](https://developer-old.gnome.org/gtk4/stable/event-propagation.html) for more information. """ @document set_quick_change_menu_enabled! """ ``` set_quick_change_menu_enabled!(::Notebook, ::Bool) ``` Set whether the user can click any of the tabs to open a menu that allows them to jump to a page. """ @document set_ratio! """ ``` set_ratio!(::AspectFrame, ratio::AbstractFloat) ``` Set width-to-height aspect ratio. """ @document set_relative_position! """ ``` set_relative_position!(::Popover, position::RelativePosition) set_relative_position!(::PopoverButton, position::RelativePosition) ``` Set position of the popover relative to the widget it is attached to. """ @document set_repeat_count! """ ``` set_repeat_count!(::Animation, ::Unsigned) ``` Set the number of cycles the animation should perform, or `0` if the animation loops endlessly. `1` by default. """ @document set_resource_path! """ ``` set_resource_path!(::IconTheme, path::String) ``` Override all resource paths with the given path. The pointed-to folder has to adhere to the [Freedesktop icon theme specificatins](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html). """ @document set_is_revealed! """ ``` set_is_revealed!(::Revealer, child_visible::Bool) set_is_revealed!(::ActionBar, widget_visible::Bool) ``` Set whether widget or its children should be visible. If the visibility changes, an animation is played. """ @document set_right_margin! """ ``` set_right_margin!(::TextView, margin::AbstractFloat) ``` Set margin between the right end of the text and the `TextView`'s frame. """ @document set_row_spacing! """ ``` set_row_spacing!(::Grid, spacing::Number) set_row_spacing!(::FLowBox, spacing::Number) ``` Set spacing between rows of the grid, in pixels. """ @document set_rows_homogeneous! """ ``` set_rows_homogeneous!(::Grid, ::Bool) ``` Set whether all rows should allocate the same height. """ @document set_scale! """ ``` set_scale!(::ImageDisplay, scale::Integer) ``` Scale image by a constant factor, in `{1, 2, 3, ...}`. """ @document set_scale_mode! """ ``` set_scale_mode!(texture:TextureObject, ::TextureScaleMode) ``` Set the OpenGL scale mode the texture uses, `TEXTURE_SCALE_MODE_NEAREST` by default. """ @document set_scope! """ ``` set_scope!(::ShortcutEventController, scope::ShortcutScope) ``` Set the scope in which the controller listens for shortcut events, see [here](https://docs.gtk.org/gtk4/method.ShortcutController.set_scope.html) for more information. """ @document set_scrollbar_placement! """ ``` set_scrollbar_placement!(::Viewport, placement::CornerPlacement) ``` Set placement of both scrollbars relative to the viewport's center. """ @document set_secondary_icon! """ ``` set_secondary_icon!(entry::Entry, icon::Icon) ``` Set the right icon of the entry. """ @document set_is_selectable! """ ``` set_is_selectable!(::Label, ::Bool) ``` Set whether the user can select part of the label, as would be needed to copy its text. `false` by default. """ @document set_selected! """ ``` set_selected!(::DropDown, id::DropDownItemID) ``` Make the item identified by the given ID the currently selected item. This will invoke its associated callback. """ @document set_should_draw_value! """ ``` set_should_draw_value!(::Scale, ::Bool) ``` Set whether the current value of the scales internal adjustment should be drawn next to the knob. """ @document set_should_interpolate_size! """ ``` set_should_interpolate_size!(::Stack, ::Bool) ``` Set whether the stack should slowly transition its size when transitioning from one page to another. """ @document set_should_snap_to_ticks! """ ``` set_should_snap_to_ticks!(::SpinButton, ::Bool) ``` Set whether when the user enters a value using the spin buttons text entry, that value should be clamped to the nearest tick. """ @document set_should_wrap! """ ``` set_should_wrap!(::SpinButton, ::Bool) ``` Set whether the spin button should over- / underflow when reaching the upper or lower end of its range. """ @document set_show_column_separators """ ``` set_show_column_separators(::ColumnView, ::Bool) ``` Set whether separators should be drawn between each column. """ @document set_show_row_separators """ ``` set_show_row_separators(::ColumnView, ::Bool) ``` Set whether separators should be drawn between each row. """ @document set_show_separators! """ ``` set_show_separators!(::ListView, ::Bool) ``` Set whether separators should be drawn between two items. """ @document set_show_text! """ ``` set_show_text!(::ProgressBar, ::Bool) ``` Set whether a percentage or custom text should be displayed above the `ProgressBar`. Use [`set_text!`](@ref) to specify the custom text. """ @document set_show_title_buttons! """ ``` set_show_title_buttons!(::HeaderBar, ::Bool) ``` If set to `false`, the "close", "minimize" and "maximize" buttons will be hidden. """ @document set_single_click_activate! """ ``` set_single_click_activate!(::ListView, ::Bool) set_single_click_activate!(::ColumnView, ::Bool) set_single_click_activate!(::GridView, ::Bool) ``` Set whether simply hovering about an item selects it. """ @document set_size_request! """ ``` set_size_request!(::Widget, size::Vector2f) ``` Set the size request, where a `0` for either width or height indicates that no size request was made. """ @document set_spacing! """ ``` set_spacing!(::Box, spacing::Number) ``` Set the space between two items, in pixels. """ @document set_start_child! """ ``` set_start_child!(::CenterBox, child::Widget) set_start_child!(::Paned, child::Widget) ``` Set first child of the container. """ @document set_start_child_resizable! """ ``` set_start_child_resizable!(::Paned, ::Bool) ``` Set whether the first child should resize when the `Paned` is resized. """ @document set_start_child_shrinkable! """ ``` set_start_child_shrinkable!(::Paned, ::Bool) ``` Set whether the user can resize the first child such that its allocated area inside the paned is smaller than the natural size of the child. """ @document set_startup_notification_identifier! """ ``` set_startup_notification_identifier!(::Window, id::String) ``` Register an ID to be used to send a notification when the window is first shown, which will usually be `"\$id is ready.". There is no guarantee that the users operating system supports this feature. """ @document set_state! """ ``` set_state!(::CheckButton, state::CheckButtonState) ``` Set the state of the check button, this will change its visual element and emit the `toggled` signal. """ @document set_step_increment! """ ``` set_step_increment!(::Adjustment, value::Number) set_step_increment!(::Scale, value::Number) set_step_increment!(::SpinButton, value::Number) ``` Set minimum distance between two discrete values of the underlying range. """ @document set_string! """ ``` set_string!(::Clipboard, string::String) ``` Override the clipboards contents with a string. Use [`get_string`](@ref) to retrieve it. """ @document set_surpress_debug! """ ``` set_surpress_debug!(domain::String, ::Bool) ``` If set to `false`, log messages with log-level `DEBUG` will now be printed to console or the log file. `true` by default. """ @document set_surpress_info! """ ``` set_surpress_info!(domain::String, ::Bool) ``` If set to `false`, log message with log-level `INFO` will now be printed to console or the log file. `true` by default. """ @document set_tab_position! """ ``` set_tab_position!(::Notebook, relative_position::RelativePosition) ``` Set position of the tab bar, relative to the notebook's center. """ @document set_tabs_reorderable! """ ``` set_tabs_reorderable!(::Notebook, ::Bool) ``` Set whether the user can reorder tabs by dragging them. """ @document set_tabs_visible! """ ``` set_tabs_visible!(::Notebook, ::Bool) ``` Set whether the tab bar should be displayed. """ @document set_text! """ ``` set_text!(::Entry, text::String) set_text!(::Label, text::String) set_text!(::TextView, text::String) ``` Override the content of the internal text buffer. --- ``` set_text!(::ProgressBar, text::String) ``` Set text that will be displayed instead of the percentage when [`set_show_text!`](@ref) was set to true. """ @document set_text_to_value_function! """ ``` set_text_to_value_function!(f, spin_button::SpinButton, [::Data_t]) ``` Set function that converts the text in the `SpinButton`'s text entry to a value. `f` is required to be invocable as a function with signature ``` (::SpinButton, text::String) -> Float32 ``` ## Example ``` spin_button = SpinButton(0, 1, 0.001) set_text_to_value_function!(spin_button) do self::SpinButton, text::String value::Float32 = 0 # process string here return value end ``` """ @document set_text_visible! """ ``` set_text_visible!(::Entry, ::Bool) ``` Set whether the entry should enter password mode. """ @document set_texture! """ ``` set_texture!(::Shape, texture::TextureObject) ``` Set the texture of the shape. It will be automatically bound when rendering. """ @document set_tick_callback! """ ``` set_tick_callback!(f, ::Widget, [::Data_t]) ``` Register a function that will be invoked exactly once per frame while the widget is shown. `f` is required to be invocable as a function with signature ``` (::FrameClock, [::Data_t]) -> TickCallbackResult ``` The callback will be removed when `f` returns `TICK_CALLBACK_RESULT_DISCONTINUE`. ## Example ```julia set_tick_callback!(widget) do clock::FrameClock frame_duration = as_seconds(get_time_since_last_frame(clock)) println("It has been \$frame_duration seconds since widget was last rendered") return TICK_CALLBACK_RESULT_CONTINUE end ``` """ @document set_timeout! """ ``` set_timeout!(::PopupMessage, duration::Time) ``` Set the duration after which the message should hide itself, or `0` for it to never hide on its own, forcing the user to close it. Microsecond precision, `0` by default. `PopupMessage` will not hide if it is clicked or currently holds input focus. """ @document set_timing_function! """ ``` set_timing_function!(::Animation, ::AnimationTimingFunction) ``` Sets the shape of the function used to interpolate the animations underlying value over time. """ @document set_title! """ ``` set_title!(::Window, title::String) set_title!(::FileChooser, title::String) set_title!(::ColorChooser, title::String) ``` Set the window's title, which will be shown in its title bar. --- ``` set_title!(::ColumnViewColumn, title::String) ``` Set the column's title, which will uniquely identify that column. --- ``` set_title!(::PopupMessage, title::String) ``` Set the `PopupMessage`'s text, does not support Pango markup. """ @document set_title_widget! """ ``` set_title_widget!(header_bar::HeaderBar, ::Widget) ``` Replace the default header bar with a custom widget. """ @document set_tooltip_text! """ ``` set_tooltip_text!(::Widget, text::String) ``` Create a simple text tooltip. It will be automatically shown when the user hovers above the widget's allocated area for a certain duration. """ @document set_tooltip_widget! """ ``` set_tooltip_widget!(::Widget, tooltip::Widget) ``` Set a custom widget as the widget's tooltip. It will be automatically shown when the user hovers above the widget's allocated area for a certain duration. This widget should not be interactable. """ @document set_top_left! """ ``` set_top_left!(::Shape, top_left::Vector2f) ``` Move the shape such that the top left corner of its axis-aligned bounding box is at given position, in OpenGL coordinates. """ @document set_top_margin! """ ``` set_top_margin!(::TextView, margin::AbstractFloat) ``` Set margin between the top of the text and the `TextView`'s frame, in pixels. """ @document set_touch_only! """ ``` set_touch_only!(::SingleClickGesture) ``` Make it such that the event controller will exclusively listen to events emitted by touch devices. """ @document set_transient_for! """ ``` set_transient_for!(self::Window, other::Window) ``` Make it such that if `self` and `other` overlap, `self` will always be shown on top. """ @document set_transition_duration! """ ``` set_transition_duration!(::Stack, duration::Time) set_transition_duration!(::Revealer, duration::Time) ``` Choose the duration of the transition animation. """ @document set_transition_type! """ ``` set_transition_type!(::Stack, transition::StackTransitionType) set_transition_type!(::Revealer, type::RevealerTransitionType) ``` Set the type of transition animation. """ @document set_uniform_float! """ ``` set_uniform_float!(::Shader, name::String, ::Cfloat) set_uniform_float!(::RenderTask, name::String, ::Cfloat) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `float` in GLSL. """ @document set_uniform_hsva! """ ``` set_uniform_hsva!(task::RenderTask, name::String, ::RGBA) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `vec4` in GLSL. """ @document set_uniform_int! """ ``` set_uniform_int!(::Shader, name::String, ::Cint) set_uniform_int!(::RenderTask, name::String, ::Cint) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `int` in GLSL. """ @document set_uniform_rgba! """ ``` set_uniform_rgba!(::RenderTask, name::String, ::RGBA) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `vec4` in GLSL. """ @document set_uniform_transform! """ ``` set_uniform_transform!(::Shader, name::String, ::GLTransform) set_uniform_transform!(::RenderTask, name::String, ::GLTransform) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `mat4x4` in GLSL. """ @document set_uniform_uint! """ ``` set_uniform_uint!(::Shader, name::String, ::Cuint) set_uniform_uint!(::RenderTask, name::String, ::Cuint) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `uint` in GLSL. """ @document set_uniform_vec2! """ ``` set_uniform_vec2!(::Shader, name::String, ::Vector2f) set_uniform_vec2!(::RenderTask, name::String, ::Vector2f) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `vec2` in GLSL. """ @document set_uniform_vec3! """ ``` set_uniform_vec3!(::Shader, name::String, ::Vector3f) set_uniform_vec3!(::RenderTask, name::String, ::Vector3f) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `vec3` in GLSL. """ @document set_uniform_vec4! """ ``` set_uniform_vec4!(::Shader, name::String, ::Vector4f) set_uniform_vec4!(::RenderTask, name::String, ::Vector4f) ``` Assign a value to a uniform in the shader program, whose variable name exactly matches `name`. This value will be a `vec4` in GLSL. """ @document set_upper! """ ``` set_upper!(::Adjustment, ::Number) set_upper!(::Scale, ::Number) set_upper!(::SpinButton, ::Number) set_upper!(::Animation, ::Number) ``` Set upper bound of the underlying range. """ @document set_use_markup! """ ``` set_use_markup!(::Label, ::Bool) ``` Set whether the label should respect [pango markup syntax](https://docs.gtk.org/Pango/pango_markup.html). `true` by default. """ @document set_value! """ ``` set_value!(::SpinButton, value::Number) set_value!(::Scale, value::Number) set_value!(adjustment::Adjustment, ::Number) ``` Set the current value of the underlying range, clamped to `[lower, upper]`. --- ``` set_value!(file::KeyFile, ::GroupID, ::KeyID, ::AbstractFloat) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Vector{<:AbstractFloat}) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Signed) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Vector{<:Signed}) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Unsigned) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Vector{<:Unsigned}) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Bool) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Vector{Bool}) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::String) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Vector{String}) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::RGBA) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::HSVA) set_value!(file::KeyFile, ::GroupID, ::KeyID, ::Image) ``` Serialize a value and save it to a key-value pair in given group. If the group or key does not yet exist, it is created. """ @document set_value_to_text_function! """ ``` set_value_to_text_function!(f, spin_button::SpinButton) set_value_to_text_function!(f, spin_button::SpinButton, data::Data_t) where Data_t ``` Register a function that converts the value of the underlying range to the text displayed in the text-entry area of the spin button. `f` is required to be invocable as a function with signature ``` (::SpinButton, ::AbstractFloat) -> String ``` ## Example ```julia spin_button = SpinButton(0, 1, 0.001) set_value_to_text_function!(spin_button) do self::SpinButton, value::AbstractFloat result = "" # generate string here return result end ``` """ @document set_vertex_color! """ ``` set_vertex_color!(::Shape, index::Integer, color::RGBA) ``` Set the color of a specific vertex. """ @document set_vertex_position! """ ``` set_vertex_position!(::Shape, index::Integer, position::Vector3f) ``` Set position of a specific vertex, in 3D OpenGL coordinates. """ @document set_vertex_texture_coordinate! """ ``` set_vertex_texture_coordinate!(::Shape, inde::Integer, coordinate::Vector2f) ``` Set texture coordinate of a specific vertex. """ @document set_vertical_alignment! """ ``` set_vertical_alignment!(::Widget, alignment::Alignment) ``` Set widget alignment along the y-axis. """ @document set_vertical_scrollbar_policy! """ ``` set_vertical_scrollbar_policy!(::Viewport, policy::ScrollbarVisibilityPolicy) ``` Set policy of vertical scrollbar, this determines when/if the scrollbar is shown. """ @document set_visible_child! """ ``` set_visible_child!(stack::Stack, id::StackID) ``` Make the current page of the stack the one identified by ID. """ @document set_was_modified! """ ``` set_was_modified!(::TextView, ::Bool) ``` Override the flag indicating that a text buffer was modified. """ @document set_widget_at! """ ``` set_widget_at!(::ListView, index::Integer, ::Widget, [::ListViewIterator]) set_widget_at!(::ColumnView, ::ColumnViewColumn, row_i::Integer, ::Widget) ``` Replace the widget at given position. """ @document set_wrap_mode! """ ``` set_wrap_mode!(::Label, mode::LabelWrapMode) ``` Set wrap mode, this determines at which point of a line a linebreak will be inserted. --- ``` set_wrap_mode!(::TextureObject, mode::TextureWrapMode) ``` Set OpenGL texture wrap mode, `TEXTURE_WRAP_MODE_REPEAT` by default. """ @document set_x_alignment! """ ``` set_x_alignment!(::Label, ::AbstractFloat) ``` Set horizontal alignment of the label, in `[0, 1]`. """ @document set_y_alignment! """ ``` set_y_alignment!(::Label, ::AbstractFloat) ``` Set vertical alignment of the label, in `[0, 1]`. """ @document shift_pressed """ ``` shift_pressed(modifier_state::ModifierState) -> Bool ``` Check whether the modifier state indicates that the shift or caps key is currently down. """ @document should_shortcut_trigger_trigger """ ``` should_shortcut_trigger_trigger(::KeyEventController, trigger::String) -> Bool ``` Test whether the currently active event should trigger the shortcut trigger. This function should only be called from within signal `key_pressed` or `modifiers_changed`. This is usually not necessary, use `ShortcutEventController` to listen for shortcuts instead. """ @document show! """ ``` show!(::Widget) ``` Reveal the widget if it is currently hidden. This will emit signal `show`. """ @document show_message! """ ``` show_message!(::PopupMessageOverlay, ::PopupMessage) ``` Queue a message to be shown above the `PopupMessageOverlay`s child. Note that only one message can be shown at a time. """ @document show_in_file_explorer """ ``` show_in_file_explorer(::FileDescriptor) -> Cvoid ``` Asynchronously open the users file explorer application to show the folder containing the given file. """ @document skew! """ ``` skew!(::TransformBin, x::Number, y::Number) ``` Apply a skew transform to the child widget, where `x` skews along the horizontal, `y` along the vertical axis. """ @document start! """ ``` start!(::Spinner) ``` Start the spinning animation if it is currently stopped. """ @document stop! """ ``` stop!(::Spinner) ``` Stop the spinning animation if it is currently playing. """ @document to_gl_coordinates """ ``` to_gl_coordinates(area::RenderArea, absolute_widget_space_coordinates::Vector2f) -> Vector2f ``` Convert absolute widget-space coordinates to OpenGL coordinates. This will take into account the `RenderArea`s currently allocated size on screen. """ @document translate! """ ``` translate!(::TransformBin, offset::Vector2f) ``` Move the child widget by given offset, widget space coordinates, in pixels. ``` translate!(transform::GLTransform, offset::Vector2f) ``` Translate the transform by the given offset, in OpenGL coordinates. """ @document unbind """ ``` unbind(::TextureObject) ``` Unbind a texture from rendering. This is usually done automatically. """ @document unbind_as_render_target """ ``` unbind_as_render_target(render_texture::RenderTexture) ``` Make it such that the render texture is no longer the current render target. This will restore the framebuffer that was active when `bind_as_render_target` was called. """ @document undo! """ ``` undo!(::TextView) ``` Trigger an undo step. """ @document unmark_as_busy! """ ``` unmark_as_busy!(::Application) ``` Undo the effect of [`mark_as_busy!`](@ref). """ @document unparent! """ ``` unparent!(::Widget) ``` Remove the widget from its parent. """ @document unselect! """ ``` unselect!(model::SelectionModel, i::Integer) ``` Make item at given position no longer selected. """ @document unselect_all! """ ``` unselect_all!(::SelectionModel) ``` Make it such that no item is selected if the selection mode allows for that. """ @document vbox """ ``` vbox(::Widget...) -> Box ``` Convenience function that wraps list of widgets in a vertically oriented box. """ ================================================ FILE: src/docgen/signals.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # GitHub: https://github.com/clemapfel/mousetrap.jl # Documentation: https://clemens-cords.com/mousetrap # # Copyright © 2023, Licensed under lGPL-3.0 # void_signature = "(::T, [::Data_t]) -> Nothing" const signal_descriptors = Dict([ :activate => ( void_signature, "Emitted when an activatable widget is activated by the user, usually by pressing the enter key." ), :activate_item => ( "(::T, index::Integer, [::Data_t]) -> Nothing", "Emitted when the user presses the enter key while one or more items are selected." ), :shutdown => ( void_signature, "Emitted when an `Application` is exiting the main loop and attempting to shut down." ), :update => ( void_signature, "Emitted exactly once per frame, when the widget associated with the `FrameClock` is about to be drawn." ), :paint => ( void_signature, "Emitted exactly once per frame, when the widget associated with the `FrameClock` is was drawn." ), :realize => ( void_signature, "Emitted when the widget is shown for the first time after being initialized." ), :unrealize => ( void_signature, "Emitted when the widget was hidden and no longer has an allocated area on screen." ), :destroy => ( void_signature, "Emitted when the widgets reference count reaches 0, it will be finalized and deleted permanently." ), :hide => ( void_signature, "Emitted when the widget is hidden, for example by calling `hide!`, being removed from its parent, or its parent being hidden." ), :show => ( void_signature, "Emitted when the widget is shown, for example by calling `show!` or its parent being shown." ), :map => ( void_signature, "Emitted when the widget has chosen its final size allocation and is assuming its size and position on screen." ), :unmap => ( void_signature, "Emitted when the widget loses its current size allocation, usually in the process of being hidden or destroyed." ), :close_request => ( "(::T, [::Data_t]) -> WindowCloseRequestResult", "Emitted when the window is requested to be closed, for example by the user pressing the close button. Depending on whether the return value of the signal handler is `WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE` or `WINDOW_CLOSE_REQUEST_RESULT_PREVENT_CLOSE`, closing of the window will be permitted or prevented." ), :closed => ( void_signature, "Emitted when a popover-window is closed, for example by calling `popdown!`, or it losing focus while `set_autohide!` is set to `true`." ), :activate_default_widget => ( void_signature, "Emitted when the child widget designated via `set_default_widget!` was activated." ), :activate_focused_widget => ( void_signature, "Emitted when the child widget that currently holds input focus is activated." ), :clicked => ( void_signature, "Emitted when the user clicks the widget using a mouse or touchscreen." ), :toggled => ( void_signature, "Emitted when the widgets internal state changes from active to inactive, or inactive to active." ), :text_changed => ( void_signature, "Emitted when underlying text buffer is modified in any way." ), :selection_changed => ( "(::T, position::Integer, n_items::Integer, [::Data_t]) -> Nothing", "The number or position of one or more selected items has changed, where `position` is the item index that was modified, `n_items` is the number of items currently selected." ), :key_pressed => ( "(::T, code::KeyCode, modifiers::ModifierState, [::Data_t]) -> Nothing", "Emitted when the user presses a non-modifier key (which is currently not pressed), while the controller's associated widget holds input focus." ), :key_released => ( "(::T, code::KeyCode, modifiers::ModifierState, [::Data_t]) -> Nothing", "Emitted when the user releases a non-modifier key (which is currently pressed), while the controller's associated widget holds input focus." ), :modifiers_changed => ( "(::T, modifiers::ModifierState, [::Data_t]) -> Nothing", "Emitted when the user presses or releases a modifier key, while the controller's associated widget holds input focus." ), :drag_begin => ( "(::T, start_x::AbstractFloat, start_y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted exactly once, on the first frame a drag gesture is recognized, where `start_y` and `start_x` are the initial position of the cursor, in pixels." ), :drag => ( "(::T, x_offset::AbstractFloat, y_offset::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once per frame while a drag gesture is active, where `x_offset` and `y_offset` are the distance between the current position of the cursor, and the position at the start of the gesture, in pixels." ), :drag_end => ( "(::T, x_offset::AbstractFloat, y_offset::AbstractFloat, [::Data_t]) -> Nothing", "Emitted exactly once, when the drag gesture seizes to be active, where `x_offset` and `y_offset` are the distance between the current position of the cursor, and the position at the start of the gesture, in pixels." ), :click_pressed => ( "(::T, n_presses::Integer, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "User presses a mouse button (which is currently not pressed), while the controller's associated widget holds input focus. Where `n_presses` are the current number of clicks in the sequence, `x`, `y` are the current cursor position, in pixels." ), :click_released => ( "(::T, n_presses::Integer, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "User releases a mouse button (which is currently pressed), while the controller's associated widget holds input focus. Where `n_presses` are the current number of clicks in the sequence, `x`, `y` are the current cursor position, in pixels." ), :click_stopped => ( void_signature, "Emitted exactly once to signal that a series of clicks ended." ), :focus_gained => ( void_signature, "Emitted when the widget that is currently not focused becomes focus." ), :focus_lost => ( void_signature, "Emitted when the widget that is currently focused loses focus." ), :pressed => ( "(::T, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once when a long-press gesture is recognized, where `x` and `y` are the current position of the cursor, in pixels." ), :press_cancelled => ( void_signature, "Emitted once when the user releases the button of a long-press gesture." ), :motion_enter => ( "(::T, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once when the user's cursor first enters the allocated area of the widget on screen, where `x` and `y` are the current position of the cursor, in pixels." ), :motion => ( "(::T, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once per frame, while the cursor is inside the allocated area of the widget, where `x` and `y` are the current cursor position, in pixels." ), :motion_leave => ( void_signature, "Emitted exactly once when the cursor leaves the allocated area of the widget." ), :scale_changed => ( "(::T, scale::AbstractFloat, [::Data_t]) -> Nothing", "Emitted any time the distance between two fingers of a pinch-zoom-gesture change, where `scale` is the factor of the current distance between the two fingers, compared to the distance at the start of the gesture." ), :rotation_changed => ( "(::T, angle_absolute::AbstractFloat, angle_delta::AbstractFloat, [::Data_t]) -> Nothing", "Emitted when the angle between the two fingers of a rotate-gesture changes, where `angle_delta` is the offset, `angle_absolute`the current angle, in radians." ), :scroll_begin => ( void_signature, "Emitted once when a scroll gesture is first recognized." ), :scroll => ( "(::T, x_delta::AbstractFloat, y_delta::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once per frame while a scroll gesture is active, where `x_delta` and `y_delta` are the offset along the horizontal and vertical axis, in pixels." ), :scroll_end => ( void_signature, "Emitted to signal the end of a scroll gesture." ), :kinetic_scroll_decelerate => ( "(::T, x_velocity::AbstractFloat, y_velocity::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once per frame while kinetic scrolling is active, see the manual on `ScrollEventController` for more information." ), :stylus_down => ( "(::T, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once when the stylus makes contact with the touchpad, where `x` and `y` are the cursor position, in pixels." ), :stylus_up => ( "(::T, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once when the stylus seizes to make contact with the touchpad, where `x` and `y` is the cursor position, in pixels." ), :proximity => ( "(::T, x::AbstractFloat, y::AbstractFloat, [::Data_t]) -> Nothing", "Emitted when the stylus enters or exits the proximity distance recognized by the touchpad. This will usually precede a `stylus_down` or `stylus_up` signal." ), :swipe => ( "(::T, x_velocity::AbstractFloat, y_velocity::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once per frame while a swipe gesture is active, where `x_velocity` and `y_velocity` are the current velocity of the swipe, in pixels." ), :pan => ( "(::T, ::PanDirection, offset::AbstractFloat, [::Data_t]) -> Nothing", "Emitted once per frame while a pan gesture is active, where `offset` is the horizontal (or vertical) distance between the current position of the cursor, and the position at the start of the gesture, in pixels." ), :value_changed => ( void_signature, "Emitted when the `value` property of the `Adjustment` changes." ), :properties_changed => ( void_signature, "Emitted when any property of the `Adjustment` other than `value` changes." ), :wrapped => ( void_signature, "Emitted when a `SpinButton`s value wraps from the minimum to the maximum, or vice-versa, while `set_should_wrap!` was set to `true`." ), :scroll_child => ( "(::T, scroll_type::ScrollType, is_horizontal::Bool, [::Data_t]) -> Nothing", "Emitted any time a user triggers a scroll action that moves one or both of the `Viewport`s scroll bars, where `scroll_type` identifies the type of action that triggered the scrolling, while `is_horizontal` determines along which axis the scrolling took place." ), :render => ( "(::T, gdk_gl_context::Ptr{Cvoid}, [::Data_t]) -> Bool", "Emitted once per frame before the GL frame buffer is flushed to the screen. The `gdk_gl_context` argument is for internal use only and can be ignored." ), :resize => ( "(::T, width::Integer, height::Integer, [::Data_t]) -> Nothing", "Emitted whenever the allocated area of a `RenderArea` changes, where `width` and `height` are the new size, in pixels." ), :activated => ( void_signature, "Emitted when `activate!` is called, or the `Action` is otherwise activated." ), :revealed => ( void_signature, "Emitted once when a `Revealer`s child goes from hidden to shown (or shown to hidden) and the corresponding animation has finished playing." ), :switched => ( void_signature, "Emitted whenever the internal state of a `Switch` changes, for example by `set_is_active!`, or by the user operating the `Switch`." ), :page_reordered => ( "(::T, page_index::Integer, [::Data_t]) -> Nothing", "Emitted when a page changes position, where `page_index` is the new position of the page." ), :page_added => ( "(::T, page_index::Integer, [::Data_t]) -> Nothing", "Emitted when the total number of pages increases, where `page_index` is the position of the page that was inserted." ), :page_removed => ( "(::T, page_index::Integer, [::Data_t]) -> Nothing", "Emitted when a page is removed, where `page_index` is the old index of the page that was removed." ), :page_selection_changed => ( "(::T, page_index::Integer, [::Data_t]) -> Nothing", "Emitted when the currently active page changes by any means, where `page_index` is the index of the now visible page." ), :items_changed => ( "(::T, position::Integer, n_removed::Integer, n_added::Integer, [::Data_t]) -> Nothing", "Emitted when the number of menu items, or any of their properties, changes." ), :dismissed => ( "(::T, [::Data_t]) -> Nothing", "Emitted when the user clicks the close button of the `PopupMessage`" ), :button_clicked => ( "(::T, [::Data_t]) -> Nothing", "Emitted when the user clicks the button of a `PopupMessage`. Note that the button is only visible if `set_button_label!` was set to anything but `\"\"`" ) ]) import Latexify macro signal_table(T, signals...) ids = ["ID"] signatures = ["Signature"] descriptions = ["Description"] for signal_id in signals push!(ids, string(signal_id)) signature = signal_descriptors[signal_id][1] push!(signatures, replace(signature, "T" => string(T))) push!(descriptions, signal_descriptors[signal_id][2]) end return Latexify.mdtable(ids, signatures, descriptions; latex=false) end macro type_signals(T) return """ ## Signals (no unique signals) """ end macro type_signals(T, signals...) out = String["## Signals\n"] n_signals = length(signals) for i in 1:n_signals id = signals[i] signature = replace(signal_descriptors[id][1], "::T" => "::" * string(T)) description = signal_descriptors[id][2] push!(out, """ > #### `$id` > > ``` > > $signature > > ``` > $description """) end return join(out) end ================================================ FILE: src/docgen/types.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # GitHub: https://github.com/clemapfel/mousetrap.jl # Documentation: https://clemens-cords.com/mousetrap # # Copyright © 2023, Licensed under lGPL-3.0 # @document Action """ # Action <: SignalEmitter Memory-managed object that wraps a function. Each action has a unique ID and is registered with the [`Application`](@ref). It can furthermore have any number of shortcut triggers. Use `set_function!` to register a callback to be called when the action is activated in any way. This function is required to have the signature: ``` (::Action, [::Data_t]) -> Nothing ``` Each action can be enabled or disabled. If an action is disabled, all associated widgets, keybindings and menu items will be disabled automatically. See the manual chapter on actions for more information. $(@type_constructors( Action(::ActionID, ::Application), Action(stateless_f, ::ActionID, ::Application) )) $(@type_signals(Action, activated )) $(@type_fields()) ## Example ```julia action = Action("example.action", app) set_function!(action) do self::Action println(get_id(self) * " activated.") end activate!(action) ``` """ @document Adjustment """ # Adjustment <: SignalEmitter Object that represents a range of discrete values. Modifying the underlying adjustment of a widget will modify the widget, and vice-versa. See [`get_adjustment`](@ref) to see which widgets are available to be controlled this way. $(@type_constructors( Adjustment(value::Number, lower::Number, upper::Number, increment::Number) )) $(@type_signals(Adjustment, value_changed, properties_changed )) $(@type_fields()) """ @document AlertDialog """ # AlertDialog <: SignalEmitter Simple dialog with a message, detailed description, space for a single widget, and one or more labeled buttons. Use `on_selection!` to register a function with the signature ``` (::AlertDialog, button_index::Integer, [::Data_t]) -> Nothing ``` which is called when the user makes a selection or closes the dialog. $(@type_constructors( AlertDialog(message::String, [detailed_message::String]) )) $(@type_signals(AlertDialog, )) $(@type_fields()) ## Example ```julia alert_dialog = AlertDialog("Is this is a dialog?") add_button!(alert_dialog, "yes") add_button!(alert_dialog, "no") on_selection!(alert_dialog) do self::AlertDialog, button_index::Signed if button_index == 1 println("User chose `Yes`") elseif button_index == 2 println("User chose `No`") elseif button_index == 0 println("User dismissed the dialog") end end present!(alert_dialog) ``` """ @document ActionBar """ # ActionBar <: Widget Horizontal bar, has an area for widgets at the start and end, along with a singular centered widget, set via `set_center_widget!`. `ActionBar` can be hidden / shown using `set_is_revealed!`. It is always horizontal. $(@type_constructors( ActionBar() )) $(@type_signals(ActionBar, )) $(@type_fields()) """ @document Angle """ # Angle Represents an angle in a unit-agnostic way. Use [`radians`](@ref) or [`degrees`](@ref) to construct an object of this type. [`as_radians`](@ref) and [`as_degrees`](@ref) allow for converting an angle to the respective unit. $(@type_constructors()) $(@type_fields()) """ @document Application """ # Application <: SignalEmitter Used to register an application with the user's OS. The application's ID is required to contain at least one `.`, and it should be unique, meaning no other application on the user's operating system shares this ID. When all windows of an application are closed, or [`quit!`](@ref) is called, the application exits. This can be prevented with [`hold!`](@ref), which has to be undone later by calling [`release!`](@ref). When exiting, the application will emit signal `shutdown`, which should be used to safely free resources. When creating a new application, `allow_multiple_instances` governs whether resources are shared between two instances with the same ID. If set to `false`, the most recently created instance will be the primary instance. If set to `true` (default) the instance created **first** will be the primary instance. See [here](https://docs.gtk.org/gio/class.Application.html) for more information. $(@type_constructors( Application(::ApplicationID ; [allow_multiple_instances::Bool = true]) )) $(@type_signals(Application, activate, shutdown )) $(@type_fields()) ## Example ```julia app = Application("example.app") connect_signal_activate!(app) app::Application # all initialization has to happen here end connect_signal_shutdown!(app) app::Application # safely free all resources end run!(app) ``` """ @document ApplicationID """ # ApplicationID Application name as a string, in reverse domain name syntax. For example, if the app's homepage is `Foo.julia.org`, an appropriate application ID would be `"org.julia.foo"` """ @document Animation """ # Animation <: SignalEmitter Object that provides a steady timing function which is synched to a widget's render cycle. It can be used as the basis of implementing animations. Use `on_tick!` to register a callback with the signature ``` (::Animation, value::Float64, [::Data_t]) -> Nothing ``` Which will be called once per frame while the widget is visible. By default, the animation's `value` will be in [0, 1], this can be changed with `set_lower!` and `set_upper!`. The shape of the function interpolating the value over time can be set using `set_timing_function!`. $(@type_constructors( Animation(target::Widget, duration::Time) )) $(@type_signals(Application, )) $(@type_fields()) ## Example ```julia # animate a gradual fade-out to_animate = Button(Label("Click Me")) animation = Animation(to_animate, seconds(1)) on_tick!(animation, to_animate) do self::Animation, value::AbstractFloat, target::Widget # value will be in [0, 1] set_opacity!(target, 1 - value) end on_done!(animation, to_animate) do self::Animation, target::Widget set_is_visible!(target, false) end # start animation when button is clicked connect_signal_clicked!(to_animate, animation) do self::Button, animation::Animation play!(animation) end set_child!(window, to_animate) ``` """ @document AspectFrame """ # AspectFrame <: Widget Container widget with a single child, makes sure that the size of its child will always be at the specified width-to-height ratio. $(@type_constructors( AspectFrame(width_to_height::AbstractFloat; [child_x_alignment::AbstractFloat, child_y_alignment::AbstractFloat]), AspectFrame(Width_to_height::AbstractFloat, child::Widget) )) $(@type_signals(AspectFrame, )) $(@type_fields()) """ @document AxisAlignedRectangle """ # AxisAlignedRectangle Axis-aligned bounding box. Defined by its top-left corner and size. $(@type_constructors( AxisAlignedRectangle(top_left::Vector2f, size::Vector2f) )) $(@type_fields( top_left::Vectorf, size::Vector2f )) """ @document Box """ # Box <: Widget Widget that aligns its children in a row or column, depending on orientation. $(@type_constructors( Box(::Orientation) )) $(@type_signals(Box, )) $(@type_fields()) ## Example ```julia box = Box(ORIENTATION_HORIZONTAL) push_back!(box, Label("01")) push_back!(box, Button()) push_back!(box, Label("03")) # equivalent to box = hbox(Label("01"), Button(), Label("02")) ``` """ @document Button """ # Button <: Widget Button with a label. Connect to signal `clicked` or specify an action via [`set_action!`](@ref) to react to the user clicking the button. $(@type_constructors( Button(), Button(label::Widget), Button(::Icon) )) $(@type_signals(Button, clicked )) $(@type_fields()) ## Example ```julia button = Button() set_child!(button, Label("Click Me")) connect_signal_clicked!(button) do x::Button println("clicked!") end set_child!(window, button) ``` """ @document CenterBox """ # CenterBox <: Widget Widget that aligns exactly 3 widgets in a row (or column), prioritizing keeping the middle widget centered at all times. $(@type_constructors( CenterBox(::Orientation), CenterBox(::Orientation, left::Widget, center::Widget, right::Widget) )) $(@type_signals(CenterBox, )) $(@type_fields()) ## Example ```julia center_box = CenterBox(ORIENTATION_HORIZONTAL) set_start_child!(center_box, Label("Left")) set_center_child!(center_box, Button()) set_end_child!(center_box, Label("Right")) ``` """ @document CheckButton """ # CheckButton <: Widget Rectangle that displays a checkmark and an optional label. Connect to signal `toggled` to react to the user changing the `CheckButton`'s state by clicking it. $(@type_constructors( CheckButton() )) $(@type_signals(CheckButton, toggled )) $(@type_fields()) ## Example ```julia check_button = CheckButton() set_child!(check_button, Label("Click Me")) connect_signal_toggled!(check_button) do self::CheckButton state = get_state(self) print("CheckButton is now: ") if state == CHECK_BUTTON_STATE_ACTIVE println("active!") elseif state == CHECK_BUTTON_STATE_INACTIVE println("inactive") else # state == CHECK_BUTTON_STATE_INCONSISTENT println("inconsistent") end end set_child!(window, check_button) ``` """ @document ClampFrame """ # ClampFrame <: Widget Constrains its single child such that the child's width (or height, if vertically orientated) cannot exceed the size set using `set_maximum_size!`. $(@type_constructors( ClampFrame(size_px::AbstractFloat, [::Orientation = ORIENTATION_HORIZONTAL]) )) $(@type_signals(ClampFrame)) $(@type_fields()) """ @document ClickEventController """ # ClickEventController <: SingleClickGesture <: EventController Event controller that reacts to a series of one or more mouse-button or touchscreen presses. $(@type_constructors( ClickEventController() )) $(@type_signals(ClickEventController, click_pressed, click_released, click_stopped )) $(@type_fields()) ## Example ```julia click_controller = ClickEventController() connect_signal_click_pressed!(click_controller) do self::ClickEventController, n_presses::Integer, x::Float64, y::Float64 if n_presses == 2 println("double click registered at position (\$(Int64(x)), \$(Int64(y)))") end end add_controller!(window, click_controller) ``` """ @document Clipboard """ # Clipboard <: SignalEmitter Allows for accessing and overwriting the data in the user's OS-wide clipboard. Construct an instance of this type by calling [`get_clipboard`](@ref) on the top-level window. If the clipboard contains an image, use [`get_image`](@ref) to access it, any other kind of data needs to be accessed with [`get_string`](@ref). $(@type_constructors( )) $(@type_signals(Clipboard, )) $(@type_fields()) ## Example ```julia clipboard = get_clipboard(window) get_string(clipboard) do self::Clipboard, value::String println("Value in clipboard: " * value) end ``` """ @document Clock """ # Clock <: SignalEmitter Object used to keep track of time. Nanosecond precision. $(@type_constructors( Clock() )) $(@type_signals(Clock, )) $(@type_fields()) ## Example ```julia clock = Clock() current = restart!(clock) sleep(1) now = elapsed(clock) println("time delta: \$(as_seconds(now - current))") ``` """ @document ColorChooser """ # ColorChooser <: SignalEmitter Dialog that allows a user to choose a color. $(@type_constructors( ColorChooser([title::String, modal::Bool]) )) $(@type_signals(ColorChooser, )) $(@type_fields()) ## Example ```julia color_chooser = ColorChooser() on_accept!(color_chooser) do self::ColorChooser, color::RGBA println("Selected \$color") end on_cancel!(color_chooser) do self::ColorChooser println("color selection cancelled") end present!(color_chooser) ``` """ @document ColumnView """ # ColumnView <: Widget Selectable widget that arranges its children as a table with rows and named columns. $(@type_constructors( ColumnView([::SelectionMode]) )) $(@type_signals(ColumnView, activate )) $(@type_fields()) ## Example ```julia column_view = ColumnView(SELECTION_MODE_NONE) for column_i in 1:4 column = push_back_column!(column_view, "Column #\$column_i") for row_i in 1:3 set_widget!(column_view, column, row_i, Label("\$row_i | \$column_i")) end end # or push an entire row at once push_back_row!(column_view, Button(), CheckButton(), Entry(), Separator()) set_child!(window, column_view) ``` """ @document ColumnViewColumn """ # ColumnViewColumn <: SignalEmitter Class representing a column of [`ColumnView`](@ref). Has a label, any number of children which represented that column's rows, and an optional header menu. $(@type_constructors( )) $(@type_signals(ColumnViewColumn, )) $(@type_fields()) ## Example ```julia # create a new column column = push_back_column!(column_view) # set widget in 4th row, automatically backfills rows 1 - 3 set_widget!(column, 4, Label("4")) ``` """ @document DragEventController """ # DragEventController <: SingleClickGesture <: EventController Event controller that recogizes drag-gestures by both a mouse or touch device. $(@type_constructors( DragEventController() )) $(@type_signals(DragEventController, drag_begin, drag, drag_end )) $(@type_fields()) ## Example ```julia drag_controller = DragEventController() connect_signal_drag!(drag_controller) do self::DragEventController, x_offset, y_offset start::Vector2f = get_start_position(self) current = start + Vector2f(x_offset, y_offset) println("Current cursor position: \$current") end add_controller!(window, drag_controller) ``` """ @document DropDown """ # DropDown <: Widget Presents the user with a collapsible list of items. If one of its items is clicked, that item's callback will be invoked. $(@type_constructors( DropDown() )) $(@type_signals(DropDown, )) $(@type_fields()) ## Example ```julia drop_down = DropDown() push_back!(drop_down, "Item 01") do x::DropDown println("Item 01 selected") end push_back!(drop_down, "Item 02") do x::DropDown println("Item 02 selected") end set_child!(window, drop_down) ``` """ @document DropDownItemID """ # DropDownItemID ID of a dropdown item, returned when adding a new item to the drop down. Keep track of this in order to identify items in a position-independent manner. $(@type_constructors( )) $(@type_fields( )) """ @document Entry """ # Entry <: Widget Single-line text entry, supports "password mode", as well as inserting an icon to the left and/or right of the text area. $(@type_constructors( Entry() )) $(@type_signals(Entry, activate, text_changed )) $(@type_fields()) ## Example ```julia entry = Entry() set_text!(entry, "Write here") connect_signal_text_changed!(entry) do self::Entry println("text is now: \$(get_text(self))") end ``` """ @document EventController abstract_type_docs(EventController, Any, """ # EventController <: SignalEmitter Superclass of all event controllers. Use [`add_controller!`](@ref) to connect an event controller to any widget, after which it starts receiving input events. Connect to the unique signals of each event controller in order to react to these events. """) @document Expander """ # Expander <: Widget Collapsible item which has a label and child. If the label is clicked, the child is shown (or hidden, if it is already shown). !!! note This widget should not be used to create collapsible lists, use [`ListView`](@ref) for this purpose instead. $(@type_constructors( Expander(), Expander(child::Widget, label::Widget) )) $(@type_signals(Expander, activate )) $(@type_fields()) """ @document FileChooser """ # FileChooser <: SignalEmitter Pre-made dialog that allows users to pick a file or folder on the local disk. Connect a function with the signature ``` (::FileChooser, files::Vector{FileDescriptor}, [::Data_t]) -> Nothing ``` using [`on_accept!`](@ref). When the user makes a selection, this function will be invoked and `files` will contain one or more selected files. The file choosers layout depends on the [`FileChooserAction`](@ref) specified on construction. $(@type_constructors( FileChooser(::FileChooserAction, [title::String]) )) $(@type_signals(FileChooser, )) $(@type_fields()) ## Example ```julia file_chooser = FileChooser(FILE_CHOOSER_ACTION_OPEN_FILE) on_accept!(file_chooser) do x::FileChooser, files::Vector{FileDescriptor} if !isempty(files) println("Selected file at ", get_path(files[1])) end end on_cancel!(file_chooser) do x::FileChooser println("Canceled.") end present!(file_chooser) ``` """ @document FileDescriptor """ # FileDescriptor <: SignalEmitter Read-only object that points to a specific location on disk. There is no guarantee that this location contains a valid file or folder. $(@type_constructors( FileDescriptor(path::String) )) $(@type_signals(FileDescriptor, )) $(@type_fields()) ## Example ```julia current_dir = FileDescriptor(".") for file in get_children(current_dir) println(get_name(file) * ":\t" * get_content_type(file)) end ``` """ @document FileFilter """ # FileFilter <: SignalEmitter Filter used by [`FileChooser`](@ref). Only files that pass the filter will be available to be selected when the file chooser is active. If multiple filters are supplied, the user can select between them using a dropdown that is automatically added to the `FileChooser` dialog. $(@type_constructors( FileFilter(name::String) )) $(@type_signals(FileFilter, )) $(@type_fields()) ## Example ```julia filter = FileFilter() add_allowed_suffix!(filter, "jl") # without the `.` ```` """ @document FileMonitor """ # FileMonitor <: SignalEmitter Object that monitors a location on disk. If anything about the object at that location changes, it invoke the callback registered using [`on_file_changed!`](@ref), which requires a function with signature ``` (::FileMonitor, event::FileMonitorEvent, self::FileDescriptor, other::FileDescriptor, [::Data_t]) -> Nothing ``` Where `event` classifies the type of change, `self` is the file being monitored. $(@type_constructors( )) $(@type_signals(FileMonitor, )) $(@type_fields()) ## Example ```julia file = FileDescriptor("path/to/file.jl") @assert(exists(file)) monitor = create_monitor(file) on_file_changed!(monitor) do x::FileMonitor, event_type::FileMonitorEvent, self::FileDescriptor, other::FileDescriptor if event_type == FILE_MONITOR_EVENT_CHANGED println("File at " * get_path(self) * " was modified.") end end ``` """ @document Fixed """ # Fixed <: Widget Container widget that places its children at a specified pixel position relative to the `Fixed`s top-left corner. Use of this widget is usually discouraged, it does not allow for automatic expansion or alignment. $(@type_constructors( Fixed() )) $(@type_signals(Fixed, )) $(@type_fields()) """ @document FlowBox """ # FlowBox <: Widget `Box`-like widget that dynamically rearranges its children into multiple rows (or columns), as the widget's width (or height) changes. $(@type_constructors( FlowBox(Orientation) )) $(@type_signals(Fixed, )) $(@type_fields()) """ @document FocusEventController """ # FocusEventController <: EventController Reacts to a widget gaining or losing input focus. $(@type_constructors( FocusEventController() )) $(@type_signals(FocusEventController, focus_gained, focus_lost )) $(@type_fields()) ## Example ```julia focus_controller = FocusEventController() connect_signal_focus_gained!(focus_controller) do self::FocusController println("Gained Focus") end add_controller!(widget, focus_controller) ``` """ @document Frame """ # Frame <: Widget Widget that draws a black outline with rounded corners around its singular child. $(@type_constructors( Frame(), Frame(child::Widget) )) $(@type_signals(Frame, )) $(@type_fields()) """ @document FrameClock """ # FrameClock <: SignalEmitter Clock that is synched with a widget's render cycle. Connect to its signals to trigger behavior once per frame. $(@type_constructors( )) $(@type_signals(FrameClock, )) $(@type_fields()) ## Example ```julia frame_clock = get_frame_clock(widget) connect_signal_paint!(frame_clock) do x::FrameClock println("Widget was drawn.") end ``` """ @document GLArea """ # GLArea <: Widget Canvas that can be used as an OpenGL render target. This widget is intended to be used by third libraries, if you want to render using OpenGL using only Mousetrap, use `RenderArea` instead. $(@type_constructors( GLArea() )) $(@type_signals(GLArea, render, resize )) $(@type_fields()) ## Example ```julia canvas = GLArea() connect_signal_resize!(canvas) do self::GLArea, x, y # viewport was resized to x, y (in pixels) end connect_signal_render!(canvas) do self::GLArea, gl_context::Ptr{Cvoid} make_current!(canvas) # do OpenGL rendering here return true end ``` """ @document GLTransform """ # GLTransform <: SignalEmitter Transform in 3D spaces. Uses OpenGL coordinates, it should therefore only be used to modify vertices of a [`Shape`](@ref). Can be indexed and modified as a 4x4 matrix of `Float32`. $(@type_constructors( GLTransform() )) $(@type_signals(GLTransform, )) $(@type_fields( )) """ @document Grid """ # Grid <: Widget Selectable container that arranges its children in a non-uniform grid. Each child has a row- and column index, as well as a width and height, measured in number of cells. $(@type_constructors( Grid() )) $(@type_signals(Grid, )) $(@type_fields()) ## Example ```julia grid = Grid() insert_at!(grid, Label("Label"), 1, 1, 1, 1) insert_at!(grid, Button(), 1, 2, 1, 1) insert_at!(grid, Separator, 2, 1, 2, 1) ``` """ @document GridView """ # GridView <: Widget Selectable widget container that arranges its children in a uniform grid. $(@type_constructors( GridView(::Orientation, [::SelectionMode]) )) $(@type_signals(GridView, activate_item )) $(@type_fields()) """ @document GroupID """ # GroupID ID of a group inside a `KeyFile`. May not start with a number, and can only roman letters, 0-9, `_`, `-`, and `.`. Use `.` to deliminate nested groups, as each key-value pair has to belong to exactly one group. $(@type_constructors( )) $(@type_fields( )) """ @document HSVA """ # HSVA Color in hsva format, all components are `Float32` in `[0, 1]`. $(@type_constructors( HSVA(::AbstractFloat, ::AbstractFloat, ::AbstractFloat, ::AbstractFloat) )) $(@type_fields( h::Float32, s::Float32, v::Float32, a::Float32 )) """ @document HeaderBar """ # HeaderBar <: Widget Widget that is usually used as the title bar of a window. It contains a title, close-, maximize-, minimize buttons, as well as an area for widgets on both sides of the title. $(@type_constructors( HeaderBar(), HeaderBar(layout::String) )) $(@type_signals(HeaderBar, )) $(@type_fields()) ## Example ```julia header_bar = HeaderBar("close:title,minimize,maximize") push_front!(header_bar, Button()) set_titlebar_widget!(window, header_bar) ``` """ @document Icon """ # Icon Allows loading of icons from an image file or icon theme. $(@type_constructors( Icon(), Icon(path::String), Icon(theme::IconTheme, id::IconID, square_resolution::Integer) )) $(@type_fields( )) """ @document IconID """ # IconID ID of an icon, used by [`IconTheme`](@ref) to refer to icons in a file-agnostic way. $(@type_constructors( )) $(@type_fields())) """ @document IconTheme """ # IconTheme <: Any Allows loading of items from a folder if that folder strictly adheres to the [freedesktop icon theme specifications](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html). A [`Window`](@ref) is required to construct the icon theme, at which point the default icons for that window's display are also loaded. $(@type_constructors( IconTheme(::Window) )) $(@type_fields()) ## Example ```julia theme = IconTheme() add_resource_path!(theme, "/path/to/freedesktop_icon_theme_directory") icon = Icon() create_from_theme!(icon, theme, "custom-icon-id", 64) ``` """ @document Image """ # Image <: Any 2D image data, in RGBA. $(@type_constructors( Image(), Image(path::String), Image(width::Integer, height::Integer, [::RGBA]) )) $(@type_fields()) """ @document ImageDisplay """ # ImageDisplay <: Widget Widget that displays an [`Image`](@ref), [`Icon`](@ref), or image file. $(@type_constructors( ImageDisplay(path::String), ImageDisplay(::Image), ImageDisplay(::Icon) )) $(@type_signals(ImageDisplay, )) $(@type_fields()) """ @document KeyCode """ # KeyCode Identifier of a key. Used for [`ShortcutTrigger`](@ref) syntax. $(@type_constructors( )) $(@type_fields( )) """ @document KeyEventController """ # KeyEventController <: EventController Event controller that recognizes keyboard keystrokes. $(@type_constructors( KeyEventController() )) $(@type_signals(KeyEventController, key_pressed, key_released, modifiers_changed )) $(@type_fields()) ## Example ```julia key_controller = KeyEventController() connect_signal_key_pressed!(key_controller) do self::KeyEventController, key::KeyCode, modifier::ModifierState if key == KEY_space println("space bar pressed") end end add_controller!(window, key_controller) ``` """ @document KeyFile """ # KeyFile <: SignalEmitter GLib keyfile, ordered into groups with key-value pairs. Allows (de-)serializing of the following types: + `Bool`, `Vector{Bool}` + `AbstractFloat`, `Vector{AbstractFloat}` + `Signed`, `Vector{Signed}` + `Unsigned`, `Vector{Unsigned}` + `String`, `Vector{String}` + `RGBA` + `HSVA` + `Image` All key-value pairs have to be in exactly one group. $(@type_constructors( KeyFile(), KeyFile(path::String) )) $(@type_signals(KeyFile, )) $(@type_fields()) ## Example ```julia key_file = KeyFile() set_value!(key_file, "group_id", "key_id", [123, 456, 789]) set_comment_above!(key_file, "group_id", "key_id", "An example key-value pair") println(as_string(key_file)) ``` ``` [group_id] # An example key-value pair key_id=123;456;789; ```` """ @document KeyID """ # KeyID ID of [`KeyFile`](@ref) key-value-pair. Contains only Roman letters, 0-9, and '_'. $(@type_constructors( )) $(@type_fields( )) """ @document Label """ # Label <: Widget Static text, multi- or single-line. Use [`set_ellipsize_mode!`](@ref), [`set_wrap_mode!`](@ref), and [`set_justify_mode!`](@ref) to influence how text is displayed. Supports [pango markup](https://docs.gtk.org/Pango/pango_markup.html), see the manual section on labels for more information. $(@type_constructors( Label(), Label(markup_string::String) )) $(@type_signals(Label, )) $(@type_fields()) """ @document LevelBar """ # LevelBar <: Widget Non-interactive widget that displays the value of a range as a fraction. $(@type_constructors( LevelBar(min::AbstractFloat, max::AbstractFloat) )) $(@type_signals(LevelBar, )) $(@type_fields()) """ @document ListView """ # ListView <: Widget Selectable widget container that arranges its children in a (nested) list. $(@type_constructors( ListView(::Orientation, [::SelectionMode]) )) $(@type_signals(ListView, activate_item )) $(@type_fields()) ## Example ```julia list_view = ListView(ORIENTATION_VERTICAL) item_01_iterator = push_back!(list_view, Label("Item 01")) push_back!(list_view, Label("Item 02")) push_back!(list_view, Label("Imte 03")) push_back!(list_view, Label("Nested Item 01"), item_01_iterator) push_back!(list_view, Label("Nested Item 02"), item_01_iterator) set_child!(window, list_view) ``` """ @document ListViewIterator """ # ListViewIterator Iterator returned when inserting an item into a [`ListView`](@ref). Use this iterator as an additional argument to `push_back!`, `push_front!`, or `insert`, in order to create a nested list at that item position. $(@type_constructors( )) $(@type_fields( )) """ @document LogDomain """ # LogDomain Domain of log messages, this will be used to associate log message with a specific application or library. May only contain roman letters, `_`, `.` and `-`. $(@type_constructors( LogDomain(::String) )) $(@type_fields( )) """ @document LongPressEventController """ # LongPressEventController <: SingleClickGesture <: EventController Event controller that recognizes long-press-gestures from a mouse or touch device. $(@type_constructors( LongPressEventController() )) $(@type_signals(LongPressEventController, pressed, press_cancelled )) $(@type_fields()) ## Example ```julia long_press_controller = LongPressEventController() connect_signal_pressed!(long_press_controller) do self::LongPressEventController, x::AbstractFloat, y::AbstractFloat println("long press recognized at (\$x, \$y)") end add_controller!(window, long_press_controller) ``` """ @document MenuBar """ # MenuBar <: Widget View that displays a [`MenuModel`](@ref) as a horizontal bar. The `MenuModel` has to have the following structure: + all top-level items have to be "submenu"-type items + no submenu or section of another submenu may have a "widget"-type item $(@type_constructors( HeaderBar(::MenuModel) )) $(@type_signals(MenuBar, )) $(@type_fields()) ## Example ```julia action = Action("example.action", app) set_function!(action) do action::Action println(get_id(action), " activate.") end outer_model = MenuModel() inner_model = MenuModel() add_action!(inner_model, "Trigger Action", action) add_submenu!(outer_model, "Submenu", inner_model) menu_bar = MenuBar(outer_model) set_child!(window, menu_bar) ``` """ @document MenuModel """ # MenuModel <: SignalEmitter Model that holds information about how to construct a menu. Use [`MenuBar`](@ref) or [`PopoverMenu`](@ref) to display the menu to the user. The following types of menu items are available | Type | Function | |-----------|-------------| | "action" | [`add_action!(::MenuModel, label::String, ::Action)`](@ref) | | "icon" | [`add_icon!(::MenuModel, ::Icon, ::Action)`](@ref) | | "submenu" | [`add_submenu!(::MenuModel, label::String, other::MenuModel)`](@ref) | | "section" | [`add_section!(::MenuModel, label::String, other::MenuModel)`](@ref) | | "widget" | [`add_widget!(::MenuModel, ::Widget)`](@ref) | See the manual section on menus for more information. $(@type_constructors( MenuModel() )) $(@type_signals(MenuModel, items_changed )) $(@type_fields()) """ @document ModifierState """ # ModifierState Holds information about which modifiers are currently pressed See also: + [`control_pressed`](@ref) + [`alt_pressed`](@ref) + [`shift_pressed`](@ref) + [`mouse_button_01_pressed`](@ref) + [`mouse_button_02_pressed`](@ref) $(@type_constructors( )) $(@type_fields( )) ## Example ```julia key_controller = KeyEventController() connect_signal_modifiers_changed!(key_controller) do self::KeyEventController, modifiers::ModifierState if shift_pressed(modifiers) println("Shift was pressed") end end add_controller!(window, key_controller) ``` """ @document MotionEventController """ # MotionEventController <: EventController Captures cursor motion events while the cursor is inside the allocated area of the associated widget. $(@type_constructors( MotionEventController() )) $(@type_signals(MotionEventController, motion_enter, motion, motion_leave )) $(@type_fields()) ## Example ```julia motion_controller = MotionEventController() connect_signal_motion!(motion_controller) do self::MotionEventController, x::AbstractFloat, y::AbstractFloat println("recognized motion at (\$(Int64(round(x))), \$(Int64(round(y))))") end add_controller!(window, motion_controller) ``` """ @document Notebook """ # Notebook <: Widget Widget that arranges its children as a list of pages. Each page has exactly one child widget, as well as an optional label widget. Pages can be freely reordered by the user if [`set_tabs_reorderable!`](@ref) is set to true. It furthermore supports a quick-change menu, in which the user can quickly jump to another tab. To enable this, `set_quick_change_menu_enabled!` needs to be set to `true`. $(@type_constructors( Notebook() )) $(@type_signals(Notebook, page_added, page_removed, page_reordered, page_selection_changed )) $(@type_fields()) ## Example ```julia notebook = Notebook() push_back!(notebook, Separator(), Label("Page 01")) push_back!(notebook, Separator(), Label("Page 02")) connect_signal_page_selection_changed!(notebook) do x::Notebook, index::Integer println("Page #\$index is now selected") end set_child!(window, notebook) ``` """ @document Overlay """ # Overlay <: Widget Widget that has exactly one "base" child, and any number of "overlay" children. If two interactable widgets overlap, only the top-most widget will be interactable. $(@type_constructors( Overlay(), Overlay(base::Widget, overlays::Widget...) )) $(@type_signals(Overlay, )) $(@type_fields()) ## Example ```julia overlay = Overlay() set_child!(overlay, Separator()) add_overlay!(overlay, Label("On Top")) set_child!(window, overlay) ``` """ @document PanEventController """ # PanEventController <: SingleClickGesture <: EventController Recognizes pan gestures along exactly one axis. $(@type_constructors( PanEventController(axis::Orientation) )) $(@type_signals(PanEventController, pan )) $(@type_fields()) ## Example ```julia connect_signal_pan!(pan_controller) do self::PanEventController, direction::PanDirection, offset::AbstractFloat if direction == PAN_DIRECTION_LEFT println("panning left") elseif direction == PAN_DIRECTION_RIGHT println("panning right") end println("x-offset from start position: \$offset") end add_controller!(window, pan_controller) ``` """ @document Paned """ # Paned <: Widget Widget with exactly two children. Draws a solid border between the two, which the user can drag to one side or the other to control the size of both widgets at the same time. $(@type_constructors( Paned(orientation::Orientation), Paned(orientation::Orientation, start_child::Widget, end_child::Widget) )) $(@type_signals(Paned, )) $(@type_fields()) ## Example ```julia paned = Paned(ORIENTATION_HORIZONTAL) set_start_child!(paned, Label("Left")) set_end_child!(paned, Label("Right")) ``` """ @document PinchZoomEventController """ # PinchZoomEventController <: EventController Controller recognizing 2-finger pinch-zoom gestures (touch-only). $(@type_constructors( PinchZoomEventController() )) $(@type_signals(PinchZoomEventController, scale_changed )) $(@type_fields()) ## Example ```julia pinch_zoom_controller = PinchZoomEventController() connect_signal_scale_changed!(pinch_zoom_controller) do self::PinchZoomEventController, scale::AbstractFloat println("current scale: \$scale") end add_controller!(window, pinch_zoom_controller) ``` """ @document PopupMessage """ # PopupMessage <: SignalEmitter Popup message, always has a title and a close button. Additionally, a singular optional button can be placed next to the title. When clicked, the `PopupMessage` emits signal `button_clicked`, or calls the `Action` connected to the button using `set_button_action!`. Use `PopupMessageOverlay` to display the message above a widget. $(@type_constructors( PopupMessage(title::String), PopupMessage(title::String, button_label::String) )) $(@type_signals(PopupMessage, dismissed, button_clicked )) $(@type_fields()) ## Example ```julia message_overlay = PopupMessageOverlay() set_child!(message_overlay, Separator()) message = PopupMessage("Is this a message?", "Yes") connect_signal_button_clicked!(message) do self::PopupMessage println("button clicked") end connect_signal_dismissed!(message) do self::PopupMessage println("message closed") end show_message!(message_overlay, message) ``` """ @document PopupMessageOverlay """ # PopupMessageOverlay <: SignalEmitter Widget that can display a `PopupMessage` above the `PopupMessageOverlay`'s singular child. Only one message can be shown at a time. $(@type_constructors( PopupMessageOverlay() )) $(@type_signals(PopupMessageOverlay, )) $(@type_fields()) ## Example ```julia overlay = PopupMessageOverlay() set_child!(overlay, widget) message = PopupMessage("This example works!", "ok") connect_signal_button_clicked!(message) do self::PopupMessage println("button clicked") end connect_signal_dismissed!(message) do self::PopupMessage println("message closed") end show_message!(overlay, message) ``` """ @document Popover """ # Popover <: Widget Window-type widget with exactly one child, has to be attached to another widget to be visible. Use [`PopoverButton`](@ref) to automatically show / hide the popover. $(@type_constructors( Popover() )) $(@type_signals(Popover, closed )) $(@type_fields()) ## Example ```julia popover = Popover() set_child!(popover, Label("Popover!")) popover_button = PopoverButton() set_popover!(popover_button, popover) set_child!(window, popover_button) ``` """ @document PopoverButton """ # PopoverButton <: Widget Button that automatically shows or hides its associated [`Popover`](@ref) or [`PopoverMenu`](@ref) when clicked. $(@type_constructors( PopoverButton(::Popover), PopoverButton(::PopoverMenu) )) $(@type_signals(PopoverButton, )) $(@type_fields()) ## Example ```julia popover = Popover() set_child!(popover, Label("Popover!")) popover_button = PopoverButton() set_popover!(popover_button, popover) set_child!(window, popover_button) ``` """ @document PopoverMenu """ # PopoverMenu <: Widget Menu view that displays a [`MenuModel`](@ref) in a popover window. Use [`PopoverButton`](@ref) to automatically show / hide the popover. $(@type_constructors( PopoverMenu(::MenuModel) )) $(@type_signals(PopoverMenu, closed )) $(@type_fields()) ## Example ```julia action = Action("example.action", app) set_function!(action) do x::Action println("Action activated") end model = MenuModel() add_action!(model, "Trigger Example", action) popover_menu = PopoverMenu(model) popover_button = PopoverButton() set_popover_menu!(popover_button, popover_menu) set_child!(window, popover_button) ``` """ @document ProgressBar """ # ProgressBar <: Widget Bar that displays a fraction in `[0, 1]`. Use `set_fraction!` to change the current value. $(@type_constructors( ProgressBar() )) $(@type_signals(ProgressBar, )) $(@type_fields()) """ @document RGBA """ # RGBA Color representation in rgba. All components are `Float32` in `[0, 1]`. $(@type_constructors( RGBA(r::AbstractFloat, g::AbstractFloat, b::AbstractFloat, a::AbstractFloat) )) $(@type_fields( r::Float32, g::Float32, b::Float32, a::Flota32 )) """ @document RenderArea """ # RenderArea <: Widget Canvas for rendering custom shapes. See the manual chapter on native rendering for more information. $(@type_constructors( RenderArea([AntiAliasingQuality = ANTI_ALIASING_QUALITY_OFF]) )) $(@type_signals(RenderArea, #render, resize )) $(@type_fields()) ## Example ```julia render_area = RenderArea() rectangle = Rectangle(Vector2f(-0.5, -0.5), Vector2f(1, 1)) add_render_task!(render_area, RenderTask(rectangle)) set_size_request!(render_area, Vector2f(150, 150)) set_child!(window, render_area) ``` """ @document RenderTask """ # RenderTask <: SignalEmitter Task that groups a [`Shape`](@ref), [`Shader`](@ref), [`GLTransform`]@ref, and [`BlendMode`](@ref), allowing them to be bound for rendering. If no shader, transform, and/or blend mode is specified, the default shader, identity transform, and [`BLEND_MODE_NORMAL`](@ref) will be used, respectively. See the manual chapter on native rendering for more information. $(@type_constructors( RenderTask(::Shape ; [shader::Union{Shader, Nothing}, transform::Union{GLTransform, Nothing}, blend_mode::BlendMode]) )) $(@type_signals(RenderTask, )) $(@type_fields()) ## Example ```julia shape = Rectangle(Vector2f(-0.5, -0.5), Vector2f(1, 1)) task = RenderTask(shape) # euivalent to task = RenderTask(shape; shader = nothing, transform = nothing, blend_mode = BLEND_MODE_NORMAL ) ``` """ @document RenderTexture """ # RenderTexture <: TextureObject <: SignalEmitter Texture that can be bound as a render target. This object is for internal use only. $(@type_constructors( RenderTexture() )) $(@type_signals(RenderTexture, )) $(@type_fields()) """ @document Revealer """ # Revealer <: Widget Container that plays an animation to reveal or hide its singular child. $(@type_constructors( Revealer([::RevealerTransitionType]), Revealer(child::Widget, [::RevealerTransitionType]) )) $(@type_signals(Revealer, revealed )) $(@type_fields()) """ @document RotateEventController """ # RotateEventController <: EventController Recognizes 2-finger rotate gestures (touch-only). $(@type_constructors( RotateEventController() )) $(@type_signals(RotateEventController, rotation_changed )) $(@type_fields()) ## Example ```julia rotate_controller = RotateEventController() connect_signal_rotation_changed!(rotate_controller) do self::RotateEventController, angle_absolute::AbstractFloat, angle_dela::AbstractFloat println("angle is now: " * as_degrees(radians(angle_absolute)) * "°") end add_controller!(window, rotate_controller) ``` """ @document Scale """ # Scale <: Widget Allows users to select a value from a range. $(@type_constructors( Scale(lower::AbstractFloat, upper::AbstractFloat, step_increment::AbstractFloat, [::Orientation]) )) $(@type_signals(Scale, value_changed )) $(@type_fields()) ## Example ```julia scale = Scale(0, 1, 0.01) connect_signal_value_changed!(scale) self::Scale println("Current value: \$(get_value(scale))") end ``` """ @document Scrollbar """ # Scrollbar <: Widget GUI element typically used to scroll another widget. Connect to the signals of the underlying adjustment to react to the user scrolling. $(@type_constructors( Scrollbar(::Orientation, ::Adjustment) )) $(@type_signals(Scrollbar, )) $(@type_fields()) ## Example ```julia scrollbar = Scrollbar(ORIENTATION_HORIZONTAL, Adjustment(0, 0, 1, 0.01)) connect_signal_value_changed!(get_adjustment(scrollbar)) do self::Adjustment println("value is now \$(get_value(self))") end """ @document ScrollEventController """ # ScrollEventController <: EventController Controller able to recognize scrolling gestures by a mouse scroll wheel or touch device. $(@type_constructors( ScrollEventController([kinetic_scrolling_enabled::Bool = false]) )) $(@type_signals(ScrollEventController, scroll_begin, scroll, scroll_end, kinetic_scroll_decelerate )) $(@type_fields()) ## Example ```julia scroll_controller = ScrollEventController() connect_signal_scroll!(scroll_controller) do self::ScrollEventController, delta_x::AbstractFloat, delta_y::AbstractFloat println("current scroll offset: (\$delta_x, \$delta_y)") end add_controller!(window, scroll_controller) ``` """ @document SelectionModel """ # SelectionModel <: SignalEmitter Model that governs the current selection of a selectable widget, such as [`GridView`](@ref), [`ListView`](@ref), or [`Stack`](@ref). Only if the selection mode is set to anything other than [`SELECTION_MODE_NONE`](@ref) will the selection model emit its signals. Use [`get_selection_model`](@ref) to retrieve the model from a selectable widget. $(@type_constructors( )) $(@type_signals(SelectionModel, selection_changed )) $(@type_fields()) ## Example ```julia grid_view = GridView(SELECTION_MODE_SINGLE) for i in 1:4 push_back!(grid_view, Label("0\$i")) end selection_model = get_selection_model(grid_view) connect_signal_selection_changed!(selection_model) do x::SelectionModel, position::Integer, n_items::Integer println("selected item is now: \$position") end set_child!(window, grid_view) ``` """ @document Separator """ # Separator <: Widget Simple spacer, fills its allocated area with a solid color. $(@type_constructors( Separator([::Orientation, opacity::AbstractFloat = 1.0]) )) $(@type_signals(Separator, )) $(@type_fields()) """ @document Shader """ # Shader <: SignalEmitter OpenGL shader program, contains a fragment and vertex shader. See the manual chapter on native rendering for more information. $(@type_constructors( Shader() )) $(@type_signals(Shader, )) $(@type_fields()) """ @document Shape """ # Shape <: SignalEmitter OpenGL vertex buffer, pre-initialized as one of various shape types. See the manual chapter on native rendering for more information. $(@type_constructors( Shape(), Point(::Vector2f), Points(::Vector{Vector2f}), Triangle(::Vector2f, ::Vector2f, ::Vector2f), Rectangle(top_left::Vecto2f, size::Vector2f), Circle(center::Vector2f, radius::AbstractFloat, n_outer_vertices::Integer), Ellipse(center::Vector2f, x_radius::AbstractFloat, y_radius::AbstractFloat, n_outer_vertices), Line(::Vector2f, ::Vector2f), Lines(::Vector{Pair{Vector2f, Vector2f}}), LineStrip(::Vector2{Vector2f}), Polygon(::Vector{Vector2f}), RectangularFrame(top_left::Vector2f, outer_size::Vector2f, x_width::AbstractFloat, y_width::AbstractFloat), CircularRing(center::Vector2f, outer_radius::AbstractFloat, thickness::AbstractFloat, n_outer_vertices::Integer), EllipticalRing(center::Vector2f, outer_x_radius::AbstractFloat, outer_y_radius::AbstractFloat, x_thickness::AbstractFloat, y_thickness::AbstractFloat, n_outer_vertices::Unsigned), Wireframe(::Vector{Vector2f}), Outline(other_shape::Shape) )) $(@type_signals(Shape, )) $(@type_fields()) """ @document ShortcutEventController """ # ShortcutEventController <: EventController Triggers actions if their associate shortcuts are recognized. Call [`add_action!`](@ref) to specify which actions the controller should manage. $(@type_constructors( ShortcutEventController() )) $(@type_signals(ShortcutEventController, )) $(@type_fields()) ## Example ```julia action = Action("example.action", app) set_function!(action) do x::Action println("example.action activated") end add_shortcut!(action, "space") # activate action when the user presses Control + Space shortcut_controller = ShortcutEventController() add_action!(shortcut_controller, action) add_controller!(window, shortcut_controller) ``` """ @document ShortcutTrigger """ # ShortcutTrigger String expressing a combination of zero or more modifier keys, enclosed in `<>`, followed by exactly one non-modifier key. See the section on `ShortctuEventController` in the manual chapter on event handling for more information. $(@type_constructors( ShortcutTrigger(::String) )) $(@type_fields( )) """ @document SignalEmitter abstract_type_docs(SignalEmitter, Any, """ # SignalEmitter <: Any Object that can emit signals. Any signal emitter is memory-managed independently of Julia, once its internal reference counter reaches zero, it is safely deallocated. Julia users do not have to worry about keeping any signal emitters in scope, it is kept alive automatically. """) @document SingleClickGesture abstract_type_docs(SingleClickGesture, Any, """ # SingleClickGesture <: EventController Specialized type of `EventController` that provides the following functions: + [`get_current_button`](@ref) + [`get_only_listens_to_button`](@ref) + [`set_only_listens_to_button!`](@ref) + [`set_touch_only!`](@ref) """) @document SpinButton """ # SpinButton <: Widget Widget with a value-entry and two buttons. $(@type_constructors( SpinButton(lower::Number, upper::Number, step_increment::Number, [orientation::Orientation]) )) $(@type_signals(SpinButton, value_changed )) $(@type_fields()) """ @document Spinner """ # Spinner <: Widget Graphical widget that signifies that a process is busy. Set [`set_is_spinning!`](@ref) to `true` to start the spinning animation. $(@type_constructors( Spinner() )) $(@type_signals(Spinner, )) $(@type_fields()) """ @document Stack """ # Stack <: Widget Selectable widget that always shows exactly one of its children. Use [`StackSwitcher`](@ref) or [`StackSidebar`](@ref) to provide a way for users to choose the page of the stack. Connect to the signals of the [`SelectionModel`](@ref) provided by [`get_selection_model`](@ref) to track which stack page is currently selected. $(@type_constructors( Stack() )) $(@type_signals(Stack, )) $(@type_fields()) ## Example ```julia stack = Stack() add_child!(stack, Label("Page 01"), "Page 01") add_child!(stack, Label("Page 02"), "Page 02") add_child!(stack, Label("Page 03"), "Page 03") stack_switcher = StackSwitcher(stack) box = Box(ORIENTATION_VERTICAL) push_back!(box, stack) push_back!(box, stack_switcher) set_child!(window, box) ``` """ @document StackID """ # StackID ID that uniquely identifies a page of a [`Stack`](@ref). Will be used as the page title for [`StackSwitcher`](@ref) and [`StackSidebar`](@ref). $(@type_constructors( )) $(@type_fields( )) """ @document StackSidebar """ # StackSidebar <: Widget Widget that allows users to select a page of a [`Stack`](@ref). $(@type_constructors( StackSidebar(::Stack) )) $(@type_signals(StackSidebar, )) $(@type_fields()) """ @document StackSwitcher """ # StackSwitcher <: Widget Widget that allows users to select a page of a [`Stack`](@ref). $(@type_constructors( StackSwitcher(::Stack) )) $(@type_signals(StackSwitcher, )) $(@type_fields()) """ @document StylusEventController """ # StylusEventController <: SingleClickGesture <: EventController Controller handling events from a stylus devices, such as drawing tablets. Has access to many manufacturer-specific sensors, see the section on `StylusEventController` in the manual chapter on event handling for more information. $(@type_constructors( StylusEventController() )) $(@type_signals(StylusEventController, stylus_up, stylus_down, proximity, motion )) $(@type_fields()) ## Example ```julia stylus_controller = StylusEventController() connect_signal_motion!(stylus_controller) do self::StylusEventController, x::AbstractFloat, y::AbstractFloat println("stylus position detected at (\$x, \$y)") end add_controller!(window, stylus_controller) ``` """ @document SwipeEventController """ # SwipeEventController <: SingleClickGesture <: EventController Recognizes swipe gestures (touch-only). $(@type_constructors( SwipeEventController()) )) $(@type_signals(SwipeEventController, swipe )) $(@type_fields()) ## Example ```julia swipe_controller = SwipeEventController() connect_signal_swipe!(swipe_controller) do self::SwipeEventController, x_velocity::AbstractFloat, y_velocity::AbstractFloat print("swiping ") if (y_velocity < 0) print("up ") elseif (y_velocity > 0) print("down ") end if (x_velocity < 0) println("left") elseif (x_velocity > 0) println("right") end end add_controller!(window, swipe_controller) ``` """ @document Switch """ # Switch <: Widget Widget with a binary state, emits signal `switched` when triggered. $(@type_constructors( Switch() )) $(@type_signals(Switch, switched )) $(@type_fields()) """ @document TextView """ # TextView <: Widget Multi-line text entry. $(@type_constructors( TextVew() )) $(@type_signals(TextView, text_changed )) $(@type_fields()) ## Example ```julia text_view = TextView() set_text!(text_view, "Write here") connect_signal_text_changed!(text_view) do self::TextView println("text is now: \$(get_text(self))") end ``` """ @document Texture """ # Texture <: TextureObject <: SignalEmitter OpenGL Texture. See the manual chapter on native rendering for more information. $(@type_constructors( Texture() )) $(@type_signals(Texture, )) $(@type_fields()) """ @document TextureObject """ # TextureObject Object that can be bound as a texture. Use [`set_texture!`](@ref) to associate it with a [`Shape`](@ref). See the manual chapter on native rendering for more information. """ @document Time """ # Time Object representing a duration of time, nanoseconds precision, may be negative. $(@type_constructors( nanoseconds(::Int64), microseconds(::Number), milliseconds(::Number), seconds(::Number), minutes(::Number) )) $(@type_fields( )) ## Example ```julia # convert seconds to microseconds println(as_microseconds(seconds(3.14159))) ``` """ @document ToggleButton """ # ToggleButton <: Widget Button with a boolean state. Emits signal `toggled` when its state changes. $(@type_constructors( ToggleButton(), ToggleButton(label::Widget), ToggleButton(::Icon) )) $(@type_signals(ToggleButton, toggled, clicked )) $(@type_fields()) ## Example ```julia toggle_button = ToggleButton() connect_signal_toggled!(toggle_button) do self::ToggleButton println("state is now: " get_is_active(self)) end set_child!(window, toggle_button) ``` """ @document TransformBin """ # TransformBin <: Widget Container with a singular child, allows applying spatial transform operations to its child widget. $(@type_constructors( TransformBin(), TransformBin(child::Widget) )) $(@type_signals(TransformBin, )) $(@type_fields()) ## Example ```julia # continuously rotate a widget widget = Button() bin = TransformBin() set_child!(widget) animation = Animation(bin, seconds(1)) set_repeat_count!(animation, 0) # infinite repeats on_tick!(animation, bin) do self::Animation, bin::TransformBin rotate!(bin, degrees(1)) end play!(animation) set_child!(window, button) ``` """ @document TypedFunction """ # TypedFunction Object used to invoke an arbitrary function using the given signature. This wrapper will automatically convert any arguments and return values to the types specified as the signature, unless impossible, at which point an assertion error will be thrown on instantiation. In this way, it can be used to assert a functions signature at compile time. $(@type_constructors( )) $(@type_fields( )) ## Example ```julia as_typed = TypedFunction(Int64, (Integer,)) do(x::Integer) return string(x) end as_typed(12) # returns 12, because "12" will be converted to given return type, Int64 ``` """ @document Vector2 """ # Vector2{T} Vector with two components, all operations are component-wise, just like in GLSL. $(@type_constructors( Vector2{T}(::T, ::T), Vector2{T}(both::T) )) $(@type_fields( x::T, y::T )) """ @document Vector3 """ # Vector3{T} Vector with 4 components, all operations are component-wise, just like in GLSL. $(@type_constructors( Vector3{T}(::T, ::T, ::T), Vector3{T}(all::T) )) $(@type_fields( x::T, y::T, z::T )) """ @document Vector4 """ # Vector4{T} Vector with 4 components, all operations are component-wise, just like in GLSL. $(@type_constructors( Vector4{T}(::T, ::T, ::T, ::T), Vector4{T}(all::T) )) $(@type_fields( x::T, y::T, z::T, w::T )) """ @document Viewport """ # Viewport <: Widget Container that displays part of its singular child. The allocated size of the `Viewport` is independent of that of its child. The user can control which part is shown by operating two scrollbars. These will automatically hide or show themself when the user's cursor enters the viewport. This behavior can be influenced by setting the [`ScrollbarVisibilityPolicy`](@ref) for one or both of the scrollbars. `Viewport` can be forced to obey the width and/or height of its child by setting [`set_propagate_natural_width!`](@ref) or [`set_propagate_natural_height!`](@ref) to `true`. The placement of both scrollbars at the same time can be set with [`set_scrollbar_placement!`](@ref). Connect to the `value_changed` signal of each of the scrollbars [`Adjustment`](@ref) to react to the user scrolling the `Viewport`. $(@type_constructors( Viewport() )) $(@type_signals(Viewport, scroll_child )) $(@type_fields()) """ @document Widget abstract_type_docs(Widget, Any, """ # Widget <: SignalEmitter Superclass of all renderable entities in Mousetrap. Like all [`SignalEmitter`](@ref)s, a widget's lifetime is managed automatically. Widgets have a large number of properties that influence their size and position on screen. See the manual chapter on widgets for more information. In order for an object to be treated as a widget, it needs to subtype this abstract type and define [`Mousetrap.get_top_level_widget`](@ref). See the manual section on compound widgets in the chapter on widgets. All widgets share the following signals, where `T` is the subclass of `Widget`. For example, signal `realize` of class `Label` has the signature `(::Label, [::Data_t]) -> Nothing`: $(@type_signals(T, realize, unrealize, destroy, hide, show, map, unmap )) $(@type_fields()) """) @document Window """ # Window <: Widget Top-level window, associated with an [`Application`](@ref). Has exactly one child, as well as a titlebar widget, which will usually be a [`HeaderBar`](@ref). When the user's window manager requests for a window to close, signal `close_request` will be emitted, whose return value can prevent the window from closing. $(@type_constructors( Window(app::Application) )) $(@type_signals(Window, close_request, activate_default_widget, activate_focused_widget )) $(@type_fields()) ## Example ```julia main() do app::Application window = Window(app) present!(window) end ``` """ ================================================ FILE: src/docs.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # GitHub: https://github.com/clemapfel/mousetrap.jl # Documentation: https://clemens-cords.com/mousetrap # # Copyright © 2023, Licensed under lGPL-3.0 # ## Classification const widgets = Symbol[] const event_controllers = Symbol[] const signal_emitters = Symbol[] const abstract_types = Symbol[] const types = Symbol[] const functions = Symbol[] const enums = Symbol[] const enum_values = Symbol[] const key_codes = Symbol[] const other = Symbol[] const style_properties = Symbol[] const style_targets = Symbol[] const style_classes = Symbol[] for n in names(Mousetrap) binding = getproperty(Mousetrap, n) if binding isa Type if isabstracttype(binding) push!(abstract_types, n) elseif binding <: Widget push!(widgets, n) elseif binding <: EventController push!(event_controllers, n) elseif binding <: SignalEmitter push!(signal_emitters, n) elseif binding <: Int64 push!(enums, n) else push!(types, n) end elseif typeof(binding) <: Function # if isnothing(match(r".*_signal_.*", string(binding))) # filter autogenerated signal functions if string(n)[1] != `@` # filter macros push!(functions, n) end #end elseif occursin("STYLE_PROPERTY_", string(n)) push!(style_properties, n) elseif occursin("STYLE_TARGET_", string(n)) push!(style_targets, n) elseif occursin("STYLE_CLASS_", string(n)) push!(style_classes, n) elseif occursin("KEY_", string(n)) push!(key_codes, Symbol(string(n)[5:end])) # omit `KEY_` elseif typeof(binding) <: Int64 push!(enum_values, n) else push!(other, n) end end ## Docs Common macro document(name, string) :(@doc $string $name) end function enum_docs(name, brief, values) out = "# $(name)\n" out *= "$(brief)\n" out *= "## Enum Values\n" for value in values out *= "+ `$value`\n" end return out end macro type_constructors(constructors...) out = "## Constructors\n" if !isempty(constructors) out *= "```\n" for constructor in constructors out *= string(constructor) * "\n" end out *= "```\n" else out *= "(no public constructors)\n" end return out end macro type_fields() out = "## Fields\n" out *= "(no public fields)\n" return out end macro type_fields(fields...) out = "## Fields\n" if !isempty(fields) for field in fields out *= "+ `$field`\n" end else out *= "(no public fields)\n" end return out end using InteractiveUtils function abstract_type_docs(type_in, super_type, brief) type = string(type_in) out = "$brief\n" out *= "## Supertype\n`$super_type`\n" out *= "## Subtypes\n" # get all subtypes and subtypes of subtypes seen = Set() subtypes = [] function aux(type, subtypes) if type in seen return else push!(seen, type) end if isabstracttype(type) for t in InteractiveUtils.subtypes(type) aux(t, subtypes) end else push!(subtypes, string(type)) end end aux(type_in, subtypes) for type in sort(subtypes) to_append = "+ [`$type`](@ref)\n" out *= replace(to_append, "Mousetrap." => "") end return out end ## include include("docgen/signals.jl") include("docgen/functions.jl") include("docgen/types.jl") include("docgen/enums.jl") macro generate_signal_function_docs(snake_case) out = Expr(:toplevel) id = snake_case signature = signal_descriptors[snake_case][1] connect_signal_name = :connect_signal_ * snake_case * :! connect_signal_string = """ ``` $connect_signal_name(f, ::T, [::Data_t]) -> Cvoid ``` Connect to signal `$id`, where `T` is a signal emitter instance that supports this signal `Data_t` is an optional argument, which, if specified, will be forwarded to the signal handler. `f` is required to be invocable as a function with signature: ``` $signature ``` Where `T` is the type of the signal emitter instance. """ push!(out.args, :(@document $connect_signal_name $connect_signal_string)) ### emit_signal_name = :emit_signal_ * snake_case return_t = match(r" -> .*", signature).match arg_ts = signature[(match(r"::T, ", signature).offset + 5):(match(r", \[::Data_t\]", signature).offset - 1)] emit_signal_string = """ ``` $emit_signal_name(::T, $arg_ts)$return_t ``` Manually emit signal `$id`, where `T` is a signal emitter that supports this signal. The arguments will forwarded to the signal handler. """ emit_signal_name = :emit_signal_ * snake_case push!(out.args, :(@document $emit_signal_name $emit_signal_string)) ### disconnect_signal_name = :disconnect_signal_ * snake_case * :! disconnect_signal_string = """ ``` $disconnect_signal_name(::T) ``` Permanently disconnect a signal, where `T` is a signal emitter that supports signal `$id`. """ push!(out.args, :(@document $disconnect_signal_name $disconnect_signal_string)) ### set_signal_blocked_name = :set_signal_ * snake_case * :_blocked * :! set_signal_blocked_string = """ ``` $set_signal_blocked_name(::T, ::Bool) ``` If set to `true`, blocks emission of this signal until turned back on, where `T` is a signal emitter that supports signal `$id`. """ push!(out.args, :(@document $set_signal_blocked_name $set_signal_blocked_string)) ### get_signal_blocked_name = :get_signal_ * snake_case * :_blocked get_signal_blocked_string = """ ``` $get_signal_blocked_name(::T) -> Bool ``` Get whether the signal is currently blocked, where `T` is a signal emitter that supports signal `$id`. """ push!(out.args, :(@document $get_signal_blocked_name $get_signal_blocked_string)) return out end for pair in signal_descriptors id = pair[1] eval(:(@generate_signal_function_docs $id)) end @do_not_compile const _generate_function_docs = quote for name in Mousetrap.functions method_list = "" method_table = methods(getproperty(Mousetrap, name)) for i in eachindex(method_table) as_string = string(method_table[i]) method_list *= as_string[1:match(r" \@.*", as_string).offset] if i != length(method_table) method_list *= "\n" end end println(""" @document $name \"\"\" ``` $method_list ``` TODO \"\"\" """) end end @do_not_compile const _generate_type_docs = quote for name in sort(union( Mousetrap.types, Mousetrap.signal_emitters, Mousetrap.widgets, Mousetrap.event_controllers, Mousetrap.abstract_types )) if name in Mousetrap.types println(""" @document $name \"\"\" ## $name TODO \$(@type_constructors( )) \$(@type_fields( )) \"\"\" """) elseif name in Mousetrap.abstract_types println(""" @document $name abstract_type_docs($name, Any, \"\"\" TODO \"\"\") """) else super = "" if name in Mousetrap.event_controllers super = "EventController" elseif name in Mousetrap.widgets super = "Widget" elseif name in Mousetrap.signal_emitters super = "SignalEmitter" else continue end println(""" @document $name \"\"\" ## $name <: $super TODO \$(@type_constructors( )) \$(@type_signals($name, )) \$(@type_fields()) \"\"\" """) end end end @do_not_compile const _generate_enum_docs = quote for enum_name in Mousetrap.enums enum = getproperty(Mousetrap, enum_name) values = [] for value_name in Mousetrap.enum_values if typeof(getproperty(Mousetrap, value_name)) <: enum push!(values, value_name) end end value_string = "" for i in 1:length(values) value_string *= " :" * string(values[i]) if i != length(values) value_string *= "," end value_string *= "\n" end println(""" @document $enum_name enum_docs(:$enum_name, "TODO", [ $value_string])""") for value in values println("@document $value \"TODO\"") end println() end end ================================================ FILE: src/key_codes.jl ================================================ const KEY_VoidSymbol = detail.KEY_VoidSymbol export KEY_VoidSymbol const KEY_BackSpace = detail.KEY_BackSpace export KEY_BackSpace const KEY_Tab = detail.KEY_Tab export KEY_Tab const KEY_Linefeed = detail.KEY_Linefeed export KEY_Linefeed const KEY_Clear = detail.KEY_Clear export KEY_Clear const KEY_Return = detail.KEY_Return export KEY_Return const KEY_Pause = detail.KEY_Pause export KEY_Pause const KEY_Scroll_Lock = detail.KEY_Scroll_Lock export KEY_Scroll_Lock const KEY_Sys_Req = detail.KEY_Sys_Req export KEY_Sys_Req const KEY_Escape = detail.KEY_Escape export KEY_Escape const KEY_Delete = detail.KEY_Delete export KEY_Delete const KEY_Multi_key = detail.KEY_Multi_key export KEY_Multi_key const KEY_Codeinput = detail.KEY_Codeinput export KEY_Codeinput const KEY_SingleCandidate = detail.KEY_SingleCandidate export KEY_SingleCandidate const KEY_MultipleCandidate = detail.KEY_MultipleCandidate export KEY_MultipleCandidate const KEY_PreviousCandidate = detail.KEY_PreviousCandidate export KEY_PreviousCandidate const KEY_Kanji = detail.KEY_Kanji export KEY_Kanji const KEY_Muhenkan = detail.KEY_Muhenkan export KEY_Muhenkan const KEY_Henkan_Mode = detail.KEY_Henkan_Mode export KEY_Henkan_Mode const KEY_Henkan = detail.KEY_Henkan export KEY_Henkan const KEY_Romaji = detail.KEY_Romaji export KEY_Romaji const KEY_Hiragana = detail.KEY_Hiragana export KEY_Hiragana const KEY_Katakana = detail.KEY_Katakana export KEY_Katakana const KEY_Hiragana_Katakana = detail.KEY_Hiragana_Katakana export KEY_Hiragana_Katakana const KEY_Zenkaku = detail.KEY_Zenkaku export KEY_Zenkaku const KEY_Hankaku = detail.KEY_Hankaku export KEY_Hankaku const KEY_Zenkaku_Hankaku = detail.KEY_Zenkaku_Hankaku export KEY_Zenkaku_Hankaku const KEY_Touroku = detail.KEY_Touroku export KEY_Touroku const KEY_Massyo = detail.KEY_Massyo export KEY_Massyo const KEY_Kana_Lock = detail.KEY_Kana_Lock export KEY_Kana_Lock const KEY_Kana_Shift = detail.KEY_Kana_Shift export KEY_Kana_Shift const KEY_Eisu_Shift = detail.KEY_Eisu_Shift export KEY_Eisu_Shift const KEY_Eisu_toggle = detail.KEY_Eisu_toggle export KEY_Eisu_toggle const KEY_Kanji_Bangou = detail.KEY_Kanji_Bangou export KEY_Kanji_Bangou const KEY_Zen_Koho = detail.KEY_Zen_Koho export KEY_Zen_Koho const KEY_Mae_Koho = detail.KEY_Mae_Koho export KEY_Mae_Koho const KEY_Home = detail.KEY_Home export KEY_Home const KEY_Left = detail.KEY_Left export KEY_Left const KEY_Up = detail.KEY_Up export KEY_Up const KEY_Right = detail.KEY_Right export KEY_Right const KEY_Down = detail.KEY_Down export KEY_Down const KEY_Prior = detail.KEY_Prior export KEY_Prior const KEY_Page_Up = detail.KEY_Page_Up export KEY_Page_Up const KEY_Next = detail.KEY_Next export KEY_Next const KEY_Page_Down = detail.KEY_Page_Down export KEY_Page_Down const KEY_End = detail.KEY_End export KEY_End const KEY_Begin = detail.KEY_Begin export KEY_Begin const KEY_Select = detail.KEY_Select export KEY_Select const KEY_Print = detail.KEY_Print export KEY_Print const KEY_Execute = detail.KEY_Execute export KEY_Execute const KEY_Insert = detail.KEY_Insert export KEY_Insert const KEY_Undo = detail.KEY_Undo export KEY_Undo const KEY_Redo = detail.KEY_Redo export KEY_Redo const KEY_Menu = detail.KEY_Menu export KEY_Menu const KEY_Find = detail.KEY_Find export KEY_Find const KEY_Cancel = detail.KEY_Cancel export KEY_Cancel const KEY_Help = detail.KEY_Help export KEY_Help const KEY_Break = detail.KEY_Break export KEY_Break const KEY_Mode_switch = detail.KEY_Mode_switch export KEY_Mode_switch const KEY_script_switch = detail.KEY_script_switch export KEY_script_switch const KEY_Num_Lock = detail.KEY_Num_Lock export KEY_Num_Lock const KEY_KP_Space = detail.KEY_KP_Space export KEY_KP_Space const KEY_KP_Tab = detail.KEY_KP_Tab export KEY_KP_Tab const KEY_KP_Enter = detail.KEY_KP_Enter export KEY_KP_Enter const KEY_KP_F1 = detail.KEY_KP_F1 export KEY_KP_F1 const KEY_KP_F2 = detail.KEY_KP_F2 export KEY_KP_F2 const KEY_KP_F3 = detail.KEY_KP_F3 export KEY_KP_F3 const KEY_KP_F4 = detail.KEY_KP_F4 export KEY_KP_F4 const KEY_KP_Home = detail.KEY_KP_Home export KEY_KP_Home const KEY_KP_Left = detail.KEY_KP_Left export KEY_KP_Left const KEY_KP_Up = detail.KEY_KP_Up export KEY_KP_Up const KEY_KP_Right = detail.KEY_KP_Right export KEY_KP_Right const KEY_KP_Down = detail.KEY_KP_Down export KEY_KP_Down const KEY_KP_Prior = detail.KEY_KP_Prior export KEY_KP_Prior const KEY_KP_Page_Up = detail.KEY_KP_Page_Up export KEY_KP_Page_Up const KEY_KP_Next = detail.KEY_KP_Next export KEY_KP_Next const KEY_KP_Page_Down = detail.KEY_KP_Page_Down export KEY_KP_Page_Down const KEY_KP_End = detail.KEY_KP_End export KEY_KP_End const KEY_KP_Begin = detail.KEY_KP_Begin export KEY_KP_Begin const KEY_KP_Insert = detail.KEY_KP_Insert export KEY_KP_Insert const KEY_KP_Delete = detail.KEY_KP_Delete export KEY_KP_Delete const KEY_KP_Equal = detail.KEY_KP_Equal export KEY_KP_Equal const KEY_KP_Multiply = detail.KEY_KP_Multiply export KEY_KP_Multiply const KEY_KP_Add = detail.KEY_KP_Add export KEY_KP_Add const KEY_KP_Separator = detail.KEY_KP_Separator export KEY_KP_Separator const KEY_KP_Subtract = detail.KEY_KP_Subtract export KEY_KP_Subtract const KEY_KP_Decimal = detail.KEY_KP_Decimal export KEY_KP_Decimal const KEY_KP_Divide = detail.KEY_KP_Divide export KEY_KP_Divide const KEY_KP_0 = detail.KEY_KP_0 export KEY_KP_0 const KEY_KP_1 = detail.KEY_KP_1 export KEY_KP_1 const KEY_KP_2 = detail.KEY_KP_2 export KEY_KP_2 const KEY_KP_3 = detail.KEY_KP_3 export KEY_KP_3 const KEY_KP_4 = detail.KEY_KP_4 export KEY_KP_4 const KEY_KP_5 = detail.KEY_KP_5 export KEY_KP_5 const KEY_KP_6 = detail.KEY_KP_6 export KEY_KP_6 const KEY_KP_7 = detail.KEY_KP_7 export KEY_KP_7 const KEY_KP_8 = detail.KEY_KP_8 export KEY_KP_8 const KEY_KP_9 = detail.KEY_KP_9 export KEY_KP_9 const KEY_F1 = detail.KEY_F1 export KEY_F1 const KEY_F2 = detail.KEY_F2 export KEY_F2 const KEY_F3 = detail.KEY_F3 export KEY_F3 const KEY_F4 = detail.KEY_F4 export KEY_F4 const KEY_F5 = detail.KEY_F5 export KEY_F5 const KEY_F6 = detail.KEY_F6 export KEY_F6 const KEY_F7 = detail.KEY_F7 export KEY_F7 const KEY_F8 = detail.KEY_F8 export KEY_F8 const KEY_F9 = detail.KEY_F9 export KEY_F9 const KEY_F10 = detail.KEY_F10 export KEY_F10 const KEY_F11 = detail.KEY_F11 export KEY_F11 const KEY_L1 = detail.KEY_L1 export KEY_L1 const KEY_F12 = detail.KEY_F12 export KEY_F12 const KEY_L2 = detail.KEY_L2 export KEY_L2 const KEY_F13 = detail.KEY_F13 export KEY_F13 const KEY_L3 = detail.KEY_L3 export KEY_L3 const KEY_F14 = detail.KEY_F14 export KEY_F14 const KEY_L4 = detail.KEY_L4 export KEY_L4 const KEY_F15 = detail.KEY_F15 export KEY_F15 const KEY_L5 = detail.KEY_L5 export KEY_L5 const KEY_F16 = detail.KEY_F16 export KEY_F16 const KEY_L6 = detail.KEY_L6 export KEY_L6 const KEY_F17 = detail.KEY_F17 export KEY_F17 const KEY_L7 = detail.KEY_L7 export KEY_L7 const KEY_F18 = detail.KEY_F18 export KEY_F18 const KEY_L8 = detail.KEY_L8 export KEY_L8 const KEY_F19 = detail.KEY_F19 export KEY_F19 const KEY_L9 = detail.KEY_L9 export KEY_L9 const KEY_F20 = detail.KEY_F20 export KEY_F20 const KEY_L10 = detail.KEY_L10 export KEY_L10 const KEY_F21 = detail.KEY_F21 export KEY_F21 const KEY_R1 = detail.KEY_R1 export KEY_R1 const KEY_F22 = detail.KEY_F22 export KEY_F22 const KEY_R2 = detail.KEY_R2 export KEY_R2 const KEY_F23 = detail.KEY_F23 export KEY_F23 const KEY_R3 = detail.KEY_R3 export KEY_R3 const KEY_F24 = detail.KEY_F24 export KEY_F24 const KEY_R4 = detail.KEY_R4 export KEY_R4 const KEY_F25 = detail.KEY_F25 export KEY_F25 const KEY_R5 = detail.KEY_R5 export KEY_R5 const KEY_F26 = detail.KEY_F26 export KEY_F26 const KEY_R6 = detail.KEY_R6 export KEY_R6 const KEY_F27 = detail.KEY_F27 export KEY_F27 const KEY_R7 = detail.KEY_R7 export KEY_R7 const KEY_F28 = detail.KEY_F28 export KEY_F28 const KEY_R8 = detail.KEY_R8 export KEY_R8 const KEY_F29 = detail.KEY_F29 export KEY_F29 const KEY_R9 = detail.KEY_R9 export KEY_R9 const KEY_F30 = detail.KEY_F30 export KEY_F30 const KEY_R10 = detail.KEY_R10 export KEY_R10 const KEY_F31 = detail.KEY_F31 export KEY_F31 const KEY_R11 = detail.KEY_R11 export KEY_R11 const KEY_F32 = detail.KEY_F32 export KEY_F32 const KEY_R12 = detail.KEY_R12 export KEY_R12 const KEY_F33 = detail.KEY_F33 export KEY_F33 const KEY_R13 = detail.KEY_R13 export KEY_R13 const KEY_F34 = detail.KEY_F34 export KEY_F34 const KEY_R14 = detail.KEY_R14 export KEY_R14 const KEY_F35 = detail.KEY_F35 export KEY_F35 const KEY_R15 = detail.KEY_R15 export KEY_R15 const KEY_Shift_L = detail.KEY_Shift_L export KEY_Shift_L const KEY_Shift_R = detail.KEY_Shift_R export KEY_Shift_R const KEY_Control_L = detail.KEY_Control_L export KEY_Control_L const KEY_Control_R = detail.KEY_Control_R export KEY_Control_R const KEY_Caps_Lock = detail.KEY_Caps_Lock export KEY_Caps_Lock const KEY_Shift_Lock = detail.KEY_Shift_Lock export KEY_Shift_Lock const KEY_Meta_L = detail.KEY_Meta_L export KEY_Meta_L const KEY_Meta_R = detail.KEY_Meta_R export KEY_Meta_R const KEY_Alt_L = detail.KEY_Alt_L export KEY_Alt_L const KEY_Alt_R = detail.KEY_Alt_R export KEY_Alt_R const KEY_Super_L = detail.KEY_Super_L export KEY_Super_L const KEY_Super_R = detail.KEY_Super_R export KEY_Super_R const KEY_Hyper_L = detail.KEY_Hyper_L export KEY_Hyper_L const KEY_Hyper_R = detail.KEY_Hyper_R export KEY_Hyper_R const KEY_ISO_Lock = detail.KEY_ISO_Lock export KEY_ISO_Lock const KEY_ISO_Level2_Latch = detail.KEY_ISO_Level2_Latch export KEY_ISO_Level2_Latch const KEY_ISO_Level3_Shift = detail.KEY_ISO_Level3_Shift export KEY_ISO_Level3_Shift const KEY_ISO_Level3_Latch = detail.KEY_ISO_Level3_Latch export KEY_ISO_Level3_Latch const KEY_ISO_Level3_Lock = detail.KEY_ISO_Level3_Lock export KEY_ISO_Level3_Lock const KEY_ISO_Level5_Shift = detail.KEY_ISO_Level5_Shift export KEY_ISO_Level5_Shift const KEY_ISO_Level5_Latch = detail.KEY_ISO_Level5_Latch export KEY_ISO_Level5_Latch const KEY_ISO_Level5_Lock = detail.KEY_ISO_Level5_Lock export KEY_ISO_Level5_Lock const KEY_ISO_Group_Shift = detail.KEY_ISO_Group_Shift export KEY_ISO_Group_Shift const KEY_ISO_Group_Latch = detail.KEY_ISO_Group_Latch export KEY_ISO_Group_Latch const KEY_ISO_Group_Lock = detail.KEY_ISO_Group_Lock export KEY_ISO_Group_Lock const KEY_ISO_Next_Group = detail.KEY_ISO_Next_Group export KEY_ISO_Next_Group const KEY_ISO_Next_Group_Lock = detail.KEY_ISO_Next_Group_Lock export KEY_ISO_Next_Group_Lock const KEY_ISO_Prev_Group = detail.KEY_ISO_Prev_Group export KEY_ISO_Prev_Group const KEY_ISO_Prev_Group_Lock = detail.KEY_ISO_Prev_Group_Lock export KEY_ISO_Prev_Group_Lock const KEY_ISO_First_Group = detail.KEY_ISO_First_Group export KEY_ISO_First_Group const KEY_ISO_First_Group_Lock = detail.KEY_ISO_First_Group_Lock export KEY_ISO_First_Group_Lock const KEY_ISO_Last_Group = detail.KEY_ISO_Last_Group export KEY_ISO_Last_Group const KEY_ISO_Last_Group_Lock = detail.KEY_ISO_Last_Group_Lock export KEY_ISO_Last_Group_Lock const KEY_ISO_Left_Tab = detail.KEY_ISO_Left_Tab export KEY_ISO_Left_Tab const KEY_ISO_Move_Line_Up = detail.KEY_ISO_Move_Line_Up export KEY_ISO_Move_Line_Up const KEY_ISO_Move_Line_Down = detail.KEY_ISO_Move_Line_Down export KEY_ISO_Move_Line_Down const KEY_ISO_Partial_Line_Up = detail.KEY_ISO_Partial_Line_Up export KEY_ISO_Partial_Line_Up const KEY_ISO_Partial_Line_Down = detail.KEY_ISO_Partial_Line_Down export KEY_ISO_Partial_Line_Down const KEY_ISO_Partial_Space_Left = detail.KEY_ISO_Partial_Space_Left export KEY_ISO_Partial_Space_Left const KEY_ISO_Partial_Space_Right = detail.KEY_ISO_Partial_Space_Right export KEY_ISO_Partial_Space_Right const KEY_ISO_Set_Margin_Left = detail.KEY_ISO_Set_Margin_Left export KEY_ISO_Set_Margin_Left const KEY_ISO_Set_Margin_Right = detail.KEY_ISO_Set_Margin_Right export KEY_ISO_Set_Margin_Right const KEY_ISO_Release_Margin_Left = detail.KEY_ISO_Release_Margin_Left export KEY_ISO_Release_Margin_Left const KEY_ISO_Release_Margin_Right = detail.KEY_ISO_Release_Margin_Right export KEY_ISO_Release_Margin_Right const KEY_ISO_Release_Both_Margins = detail.KEY_ISO_Release_Both_Margins export KEY_ISO_Release_Both_Margins const KEY_ISO_Fast_Cursor_Left = detail.KEY_ISO_Fast_Cursor_Left export KEY_ISO_Fast_Cursor_Left const KEY_ISO_Fast_Cursor_Right = detail.KEY_ISO_Fast_Cursor_Right export KEY_ISO_Fast_Cursor_Right const KEY_ISO_Fast_Cursor_Up = detail.KEY_ISO_Fast_Cursor_Up export KEY_ISO_Fast_Cursor_Up const KEY_ISO_Fast_Cursor_Down = detail.KEY_ISO_Fast_Cursor_Down export KEY_ISO_Fast_Cursor_Down const KEY_ISO_Continuous_Underline = detail.KEY_ISO_Continuous_Underline export KEY_ISO_Continuous_Underline const KEY_ISO_Discontinuous_Underline = detail.KEY_ISO_Discontinuous_Underline export KEY_ISO_Discontinuous_Underline const KEY_ISO_Emphasize = detail.KEY_ISO_Emphasize export KEY_ISO_Emphasize const KEY_ISO_Center_Object = detail.KEY_ISO_Center_Object export KEY_ISO_Center_Object const KEY_ISO_Enter = detail.KEY_ISO_Enter export KEY_ISO_Enter const KEY_dead_grave = detail.KEY_dead_grave export KEY_dead_grave const KEY_dead_acute = detail.KEY_dead_acute export KEY_dead_acute const KEY_dead_circumflex = detail.KEY_dead_circumflex export KEY_dead_circumflex const KEY_dead_tilde = detail.KEY_dead_tilde export KEY_dead_tilde const KEY_dead_perispomeni = detail.KEY_dead_perispomeni export KEY_dead_perispomeni const KEY_dead_macron = detail.KEY_dead_macron export KEY_dead_macron const KEY_dead_breve = detail.KEY_dead_breve export KEY_dead_breve const KEY_dead_abovedot = detail.KEY_dead_abovedot export KEY_dead_abovedot const KEY_dead_diaeresis = detail.KEY_dead_diaeresis export KEY_dead_diaeresis const KEY_dead_abovering = detail.KEY_dead_abovering export KEY_dead_abovering const KEY_dead_doubleacute = detail.KEY_dead_doubleacute export KEY_dead_doubleacute const KEY_dead_caron = detail.KEY_dead_caron export KEY_dead_caron const KEY_dead_cedilla = detail.KEY_dead_cedilla export KEY_dead_cedilla const KEY_dead_ogonek = detail.KEY_dead_ogonek export KEY_dead_ogonek const KEY_dead_iota = detail.KEY_dead_iota export KEY_dead_iota const KEY_dead_voiced_sound = detail.KEY_dead_voiced_sound export KEY_dead_voiced_sound const KEY_dead_semivoiced_sound = detail.KEY_dead_semivoiced_sound export KEY_dead_semivoiced_sound const KEY_dead_belowdot = detail.KEY_dead_belowdot export KEY_dead_belowdot const KEY_dead_hook = detail.KEY_dead_hook export KEY_dead_hook const KEY_dead_horn = detail.KEY_dead_horn export KEY_dead_horn const KEY_dead_stroke = detail.KEY_dead_stroke export KEY_dead_stroke const KEY_dead_abovecomma = detail.KEY_dead_abovecomma export KEY_dead_abovecomma const KEY_dead_psili = detail.KEY_dead_psili export KEY_dead_psili const KEY_dead_abovereversedcomma = detail.KEY_dead_abovereversedcomma export KEY_dead_abovereversedcomma const KEY_dead_dasia = detail.KEY_dead_dasia export KEY_dead_dasia const KEY_dead_doublegrave = detail.KEY_dead_doublegrave export KEY_dead_doublegrave const KEY_dead_belowring = detail.KEY_dead_belowring export KEY_dead_belowring const KEY_dead_belowmacron = detail.KEY_dead_belowmacron export KEY_dead_belowmacron const KEY_dead_belowcircumflex = detail.KEY_dead_belowcircumflex export KEY_dead_belowcircumflex const KEY_dead_belowtilde = detail.KEY_dead_belowtilde export KEY_dead_belowtilde const KEY_dead_belowbreve = detail.KEY_dead_belowbreve export KEY_dead_belowbreve const KEY_dead_belowdiaeresis = detail.KEY_dead_belowdiaeresis export KEY_dead_belowdiaeresis const KEY_dead_invertedbreve = detail.KEY_dead_invertedbreve export KEY_dead_invertedbreve const KEY_dead_belowcomma = detail.KEY_dead_belowcomma export KEY_dead_belowcomma const KEY_dead_currency = detail.KEY_dead_currency export KEY_dead_currency const KEY_dead_lowline = detail.KEY_dead_lowline export KEY_dead_lowline const KEY_dead_aboveverticalline = detail.KEY_dead_aboveverticalline export KEY_dead_aboveverticalline const KEY_dead_belowverticalline = detail.KEY_dead_belowverticalline export KEY_dead_belowverticalline const KEY_dead_longsolidusoverlay = detail.KEY_dead_longsolidusoverlay export KEY_dead_longsolidusoverlay const KEY_dead_a = detail.KEY_dead_a export KEY_dead_a const KEY_dead_A = detail.KEY_dead_A export KEY_dead_A const KEY_dead_e = detail.KEY_dead_e export KEY_dead_e const KEY_dead_E = detail.KEY_dead_E export KEY_dead_E const KEY_dead_i = detail.KEY_dead_i export KEY_dead_i const KEY_dead_I = detail.KEY_dead_I export KEY_dead_I const KEY_dead_o = detail.KEY_dead_o export KEY_dead_o const KEY_dead_O = detail.KEY_dead_O export KEY_dead_O const KEY_dead_u = detail.KEY_dead_u export KEY_dead_u const KEY_dead_U = detail.KEY_dead_U export KEY_dead_U const KEY_dead_small_schwa = detail.KEY_dead_small_schwa export KEY_dead_small_schwa const KEY_dead_capital_schwa = detail.KEY_dead_capital_schwa export KEY_dead_capital_schwa const KEY_dead_greek = detail.KEY_dead_greek export KEY_dead_greek const KEY_First_Virtual_Screen = detail.KEY_First_Virtual_Screen export KEY_First_Virtual_Screen const KEY_Prev_Virtual_Screen = detail.KEY_Prev_Virtual_Screen export KEY_Prev_Virtual_Screen const KEY_Next_Virtual_Screen = detail.KEY_Next_Virtual_Screen export KEY_Next_Virtual_Screen const KEY_Last_Virtual_Screen = detail.KEY_Last_Virtual_Screen export KEY_Last_Virtual_Screen const KEY_Terminate_Server = detail.KEY_Terminate_Server export KEY_Terminate_Server const KEY_AccessX_Enable = detail.KEY_AccessX_Enable export KEY_AccessX_Enable const KEY_AccessX_Feedback_Enable = detail.KEY_AccessX_Feedback_Enable export KEY_AccessX_Feedback_Enable const KEY_RepeatKeys_Enable = detail.KEY_RepeatKeys_Enable export KEY_RepeatKeys_Enable const KEY_SlowKeys_Enable = detail.KEY_SlowKeys_Enable export KEY_SlowKeys_Enable const KEY_BounceKeys_Enable = detail.KEY_BounceKeys_Enable export KEY_BounceKeys_Enable const KEY_StickyKeys_Enable = detail.KEY_StickyKeys_Enable export KEY_StickyKeys_Enable const KEY_MouseKeys_Enable = detail.KEY_MouseKeys_Enable export KEY_MouseKeys_Enable const KEY_MouseKeys_Accel_Enable = detail.KEY_MouseKeys_Accel_Enable export KEY_MouseKeys_Accel_Enable const KEY_Overlay1_Enable = detail.KEY_Overlay1_Enable export KEY_Overlay1_Enable const KEY_Overlay2_Enable = detail.KEY_Overlay2_Enable export KEY_Overlay2_Enable const KEY_AudibleBell_Enable = detail.KEY_AudibleBell_Enable export KEY_AudibleBell_Enable const KEY_Pointer_Left = detail.KEY_Pointer_Left export KEY_Pointer_Left const KEY_Pointer_Right = detail.KEY_Pointer_Right export KEY_Pointer_Right const KEY_Pointer_Up = detail.KEY_Pointer_Up export KEY_Pointer_Up const KEY_Pointer_Down = detail.KEY_Pointer_Down export KEY_Pointer_Down const KEY_Pointer_UpLeft = detail.KEY_Pointer_UpLeft export KEY_Pointer_UpLeft const KEY_Pointer_UpRight = detail.KEY_Pointer_UpRight export KEY_Pointer_UpRight const KEY_Pointer_DownLeft = detail.KEY_Pointer_DownLeft export KEY_Pointer_DownLeft const KEY_Pointer_DownRight = detail.KEY_Pointer_DownRight export KEY_Pointer_DownRight const KEY_Pointer_Button_Dflt = detail.KEY_Pointer_Button_Dflt export KEY_Pointer_Button_Dflt const KEY_Pointer_Button1 = detail.KEY_Pointer_Button1 export KEY_Pointer_Button1 const KEY_Pointer_Button2 = detail.KEY_Pointer_Button2 export KEY_Pointer_Button2 const KEY_Pointer_Button3 = detail.KEY_Pointer_Button3 export KEY_Pointer_Button3 const KEY_Pointer_Button4 = detail.KEY_Pointer_Button4 export KEY_Pointer_Button4 const KEY_Pointer_Button5 = detail.KEY_Pointer_Button5 export KEY_Pointer_Button5 const KEY_Pointer_DblClick_Dflt = detail.KEY_Pointer_DblClick_Dflt export KEY_Pointer_DblClick_Dflt const KEY_Pointer_DblClick1 = detail.KEY_Pointer_DblClick1 export KEY_Pointer_DblClick1 const KEY_Pointer_DblClick2 = detail.KEY_Pointer_DblClick2 export KEY_Pointer_DblClick2 const KEY_Pointer_DblClick3 = detail.KEY_Pointer_DblClick3 export KEY_Pointer_DblClick3 const KEY_Pointer_DblClick4 = detail.KEY_Pointer_DblClick4 export KEY_Pointer_DblClick4 const KEY_Pointer_DblClick5 = detail.KEY_Pointer_DblClick5 export KEY_Pointer_DblClick5 const KEY_Pointer_Drag_Dflt = detail.KEY_Pointer_Drag_Dflt export KEY_Pointer_Drag_Dflt const KEY_Pointer_Drag1 = detail.KEY_Pointer_Drag1 export KEY_Pointer_Drag1 const KEY_Pointer_Drag2 = detail.KEY_Pointer_Drag2 export KEY_Pointer_Drag2 const KEY_Pointer_Drag3 = detail.KEY_Pointer_Drag3 export KEY_Pointer_Drag3 const KEY_Pointer_Drag4 = detail.KEY_Pointer_Drag4 export KEY_Pointer_Drag4 const KEY_Pointer_Drag5 = detail.KEY_Pointer_Drag5 export KEY_Pointer_Drag5 const KEY_Pointer_EnableKeys = detail.KEY_Pointer_EnableKeys export KEY_Pointer_EnableKeys const KEY_Pointer_Accelerate = detail.KEY_Pointer_Accelerate export KEY_Pointer_Accelerate const KEY_Pointer_DfltBtnNext = detail.KEY_Pointer_DfltBtnNext export KEY_Pointer_DfltBtnNext const KEY_Pointer_DfltBtnPrev = detail.KEY_Pointer_DfltBtnPrev export KEY_Pointer_DfltBtnPrev const KEY_ch = detail.KEY_ch export KEY_ch const KEY_Ch = detail.KEY_Ch export KEY_Ch const KEY_CH = detail.KEY_CH export KEY_CH const KEY_c_h = detail.KEY_c_h export KEY_c_h const KEY_C_h = detail.KEY_C_h export KEY_C_h const KEY_C_H = detail.KEY_C_H export KEY_C_H const KEY_3270_Duplicate = detail.KEY_3270_Duplicate export KEY_3270_Duplicate const KEY_3270_FieldMark = detail.KEY_3270_FieldMark export KEY_3270_FieldMark const KEY_3270_Right2 = detail.KEY_3270_Right2 export KEY_3270_Right2 const KEY_3270_Left2 = detail.KEY_3270_Left2 export KEY_3270_Left2 const KEY_3270_BackTab = detail.KEY_3270_BackTab export KEY_3270_BackTab const KEY_3270_EraseEOF = detail.KEY_3270_EraseEOF export KEY_3270_EraseEOF const KEY_3270_EraseInput = detail.KEY_3270_EraseInput export KEY_3270_EraseInput const KEY_3270_Reset = detail.KEY_3270_Reset export KEY_3270_Reset const KEY_3270_Quit = detail.KEY_3270_Quit export KEY_3270_Quit const KEY_3270_PA1 = detail.KEY_3270_PA1 export KEY_3270_PA1 const KEY_3270_PA2 = detail.KEY_3270_PA2 export KEY_3270_PA2 const KEY_3270_PA3 = detail.KEY_3270_PA3 export KEY_3270_PA3 const KEY_3270_Test = detail.KEY_3270_Test export KEY_3270_Test const KEY_3270_Attn = detail.KEY_3270_Attn export KEY_3270_Attn const KEY_3270_CursorBlink = detail.KEY_3270_CursorBlink export KEY_3270_CursorBlink const KEY_3270_AltCursor = detail.KEY_3270_AltCursor export KEY_3270_AltCursor const KEY_3270_KeyClick = detail.KEY_3270_KeyClick export KEY_3270_KeyClick const KEY_3270_Jump = detail.KEY_3270_Jump export KEY_3270_Jump const KEY_3270_Ident = detail.KEY_3270_Ident export KEY_3270_Ident const KEY_3270_Rule = detail.KEY_3270_Rule export KEY_3270_Rule const KEY_3270_Copy = detail.KEY_3270_Copy export KEY_3270_Copy const KEY_3270_Play = detail.KEY_3270_Play export KEY_3270_Play const KEY_3270_Setup = detail.KEY_3270_Setup export KEY_3270_Setup const KEY_3270_Record = detail.KEY_3270_Record export KEY_3270_Record const KEY_3270_ChangeScreen = detail.KEY_3270_ChangeScreen export KEY_3270_ChangeScreen const KEY_3270_DeleteWord = detail.KEY_3270_DeleteWord export KEY_3270_DeleteWord const KEY_3270_ExSelect = detail.KEY_3270_ExSelect export KEY_3270_ExSelect const KEY_3270_CursorSelect = detail.KEY_3270_CursorSelect export KEY_3270_CursorSelect const KEY_3270_PrintScreen = detail.KEY_3270_PrintScreen export KEY_3270_PrintScreen const KEY_3270_Enter = detail.KEY_3270_Enter export KEY_3270_Enter const KEY_space = detail.KEY_space export KEY_space const KEY_exclam = detail.KEY_exclam export KEY_exclam const KEY_quotedbl = detail.KEY_quotedbl export KEY_quotedbl const KEY_numbersign = detail.KEY_numbersign export KEY_numbersign const KEY_dollar = detail.KEY_dollar export KEY_dollar const KEY_percent = detail.KEY_percent export KEY_percent const KEY_ampersand = detail.KEY_ampersand export KEY_ampersand const KEY_apostrophe = detail.KEY_apostrophe export KEY_apostrophe const KEY_quoteright = detail.KEY_quoteright export KEY_quoteright const KEY_parenleft = detail.KEY_parenleft export KEY_parenleft const KEY_parenright = detail.KEY_parenright export KEY_parenright const KEY_asterisk = detail.KEY_asterisk export KEY_asterisk const KEY_plus = detail.KEY_plus export KEY_plus const KEY_comma = detail.KEY_comma export KEY_comma const KEY_minus = detail.KEY_minus export KEY_minus const KEY_period = detail.KEY_period export KEY_period const KEY_slash = detail.KEY_slash export KEY_slash const KEY_0 = detail.KEY_0 export KEY_0 const KEY_1 = detail.KEY_1 export KEY_1 const KEY_2 = detail.KEY_2 export KEY_2 const KEY_3 = detail.KEY_3 export KEY_3 const KEY_4 = detail.KEY_4 export KEY_4 const KEY_5 = detail.KEY_5 export KEY_5 const KEY_6 = detail.KEY_6 export KEY_6 const KEY_7 = detail.KEY_7 export KEY_7 const KEY_8 = detail.KEY_8 export KEY_8 const KEY_9 = detail.KEY_9 export KEY_9 const KEY_colon = detail.KEY_colon export KEY_colon const KEY_semicolon = detail.KEY_semicolon export KEY_semicolon const KEY_less = detail.KEY_less export KEY_less const KEY_equal = detail.KEY_equal export KEY_equal const KEY_greater = detail.KEY_greater export KEY_greater const KEY_question = detail.KEY_question export KEY_question const KEY_at = detail.KEY_at export KEY_at const KEY_A = detail.KEY_A export KEY_A const KEY_B = detail.KEY_B export KEY_B const KEY_C = detail.KEY_C export KEY_C const KEY_D = detail.KEY_D export KEY_D const KEY_E = detail.KEY_E export KEY_E const KEY_F = detail.KEY_F export KEY_F const KEY_G = detail.KEY_G export KEY_G const KEY_H = detail.KEY_H export KEY_H const KEY_I = detail.KEY_I export KEY_I const KEY_J = detail.KEY_J export KEY_J const KEY_K = detail.KEY_K export KEY_K const KEY_L = detail.KEY_L export KEY_L const KEY_M = detail.KEY_M export KEY_M const KEY_N = detail.KEY_N export KEY_N const KEY_O = detail.KEY_O export KEY_O const KEY_P = detail.KEY_P export KEY_P const KEY_Q = detail.KEY_Q export KEY_Q const KEY_R = detail.KEY_R export KEY_R const KEY_S = detail.KEY_S export KEY_S const KEY_T = detail.KEY_T export KEY_T const KEY_U = detail.KEY_U export KEY_U const KEY_V = detail.KEY_V export KEY_V const KEY_W = detail.KEY_W export KEY_W const KEY_X = detail.KEY_X export KEY_X const KEY_Y = detail.KEY_Y export KEY_Y const KEY_Z = detail.KEY_Z export KEY_Z const KEY_bracketleft = detail.KEY_bracketleft export KEY_bracketleft const KEY_backslash = detail.KEY_backslash export KEY_backslash const KEY_bracketright = detail.KEY_bracketright export KEY_bracketright const KEY_asciicircum = detail.KEY_asciicircum export KEY_asciicircum const KEY_underscore = detail.KEY_underscore export KEY_underscore const KEY_grave = detail.KEY_grave export KEY_grave const KEY_quoteleft = detail.KEY_quoteleft export KEY_quoteleft const KEY_a = detail.KEY_a export KEY_a const KEY_b = detail.KEY_b export KEY_b const KEY_c = detail.KEY_c export KEY_c const KEY_d = detail.KEY_d export KEY_d const KEY_e = detail.KEY_e export KEY_e const KEY_f = detail.KEY_f export KEY_f const KEY_g = detail.KEY_g export KEY_g const KEY_h = detail.KEY_h export KEY_h const KEY_i = detail.KEY_i export KEY_i const KEY_j = detail.KEY_j export KEY_j const KEY_k = detail.KEY_k export KEY_k const KEY_l = detail.KEY_l export KEY_l const KEY_m = detail.KEY_m export KEY_m const KEY_n = detail.KEY_n export KEY_n const KEY_o = detail.KEY_o export KEY_o const KEY_p = detail.KEY_p export KEY_p const KEY_q = detail.KEY_q export KEY_q const KEY_r = detail.KEY_r export KEY_r const KEY_s = detail.KEY_s export KEY_s const KEY_t = detail.KEY_t export KEY_t const KEY_u = detail.KEY_u export KEY_u const KEY_v = detail.KEY_v export KEY_v const KEY_w = detail.KEY_w export KEY_w const KEY_x = detail.KEY_x export KEY_x const KEY_y = detail.KEY_y export KEY_y const KEY_z = detail.KEY_z export KEY_z const KEY_braceleft = detail.KEY_braceleft export KEY_braceleft const KEY_bar = detail.KEY_bar export KEY_bar const KEY_braceright = detail.KEY_braceright export KEY_braceright const KEY_asciitilde = detail.KEY_asciitilde export KEY_asciitilde const KEY_nobreakspace = detail.KEY_nobreakspace export KEY_nobreakspace const KEY_exclamdown = detail.KEY_exclamdown export KEY_exclamdown const KEY_cent = detail.KEY_cent export KEY_cent const KEY_sterling = detail.KEY_sterling export KEY_sterling const KEY_currency = detail.KEY_currency export KEY_currency const KEY_yen = detail.KEY_yen export KEY_yen const KEY_brokenbar = detail.KEY_brokenbar export KEY_brokenbar const KEY_section = detail.KEY_section export KEY_section const KEY_diaeresis = detail.KEY_diaeresis export KEY_diaeresis const KEY_copyright = detail.KEY_copyright export KEY_copyright const KEY_ordfeminine = detail.KEY_ordfeminine export KEY_ordfeminine const KEY_guillemotleft = detail.KEY_guillemotleft export KEY_guillemotleft const KEY_notsign = detail.KEY_notsign export KEY_notsign const KEY_hyphen = detail.KEY_hyphen export KEY_hyphen const KEY_registered = detail.KEY_registered export KEY_registered const KEY_macron = detail.KEY_macron export KEY_macron const KEY_degree = detail.KEY_degree export KEY_degree const KEY_plusminus = detail.KEY_plusminus export KEY_plusminus const KEY_twosuperior = detail.KEY_twosuperior export KEY_twosuperior const KEY_threesuperior = detail.KEY_threesuperior export KEY_threesuperior const KEY_acute = detail.KEY_acute export KEY_acute const KEY_mu = detail.KEY_mu export KEY_mu const KEY_paragraph = detail.KEY_paragraph export KEY_paragraph const KEY_periodcentered = detail.KEY_periodcentered export KEY_periodcentered const KEY_cedilla = detail.KEY_cedilla export KEY_cedilla const KEY_onesuperior = detail.KEY_onesuperior export KEY_onesuperior const KEY_masculine = detail.KEY_masculine export KEY_masculine const KEY_guillemotright = detail.KEY_guillemotright export KEY_guillemotright const KEY_onequarter = detail.KEY_onequarter export KEY_onequarter const KEY_onehalf = detail.KEY_onehalf export KEY_onehalf const KEY_threequarters = detail.KEY_threequarters export KEY_threequarters const KEY_questiondown = detail.KEY_questiondown export KEY_questiondown const KEY_Agrave = detail.KEY_Agrave export KEY_Agrave const KEY_Aacute = detail.KEY_Aacute export KEY_Aacute const KEY_Acircumflex = detail.KEY_Acircumflex export KEY_Acircumflex const KEY_Atilde = detail.KEY_Atilde export KEY_Atilde const KEY_Adiaeresis = detail.KEY_Adiaeresis export KEY_Adiaeresis const KEY_Aring = detail.KEY_Aring export KEY_Aring const KEY_AE = detail.KEY_AE export KEY_AE const KEY_Ccedilla = detail.KEY_Ccedilla export KEY_Ccedilla const KEY_Egrave = detail.KEY_Egrave export KEY_Egrave const KEY_Eacute = detail.KEY_Eacute export KEY_Eacute const KEY_Ecircumflex = detail.KEY_Ecircumflex export KEY_Ecircumflex const KEY_Ediaeresis = detail.KEY_Ediaeresis export KEY_Ediaeresis const KEY_Igrave = detail.KEY_Igrave export KEY_Igrave const KEY_Iacute = detail.KEY_Iacute export KEY_Iacute const KEY_Icircumflex = detail.KEY_Icircumflex export KEY_Icircumflex const KEY_Idiaeresis = detail.KEY_Idiaeresis export KEY_Idiaeresis const KEY_ETH = detail.KEY_ETH export KEY_ETH const KEY_Eth = detail.KEY_Eth export KEY_Eth const KEY_Ntilde = detail.KEY_Ntilde export KEY_Ntilde const KEY_Ograve = detail.KEY_Ograve export KEY_Ograve const KEY_Oacute = detail.KEY_Oacute export KEY_Oacute const KEY_Ocircumflex = detail.KEY_Ocircumflex export KEY_Ocircumflex const KEY_Otilde = detail.KEY_Otilde export KEY_Otilde const KEY_Odiaeresis = detail.KEY_Odiaeresis export KEY_Odiaeresis const KEY_multiply = detail.KEY_multiply export KEY_multiply const KEY_Oslash = detail.KEY_Oslash export KEY_Oslash const KEY_Ooblique = detail.KEY_Ooblique export KEY_Ooblique const KEY_Ugrave = detail.KEY_Ugrave export KEY_Ugrave const KEY_Uacute = detail.KEY_Uacute export KEY_Uacute const KEY_Ucircumflex = detail.KEY_Ucircumflex export KEY_Ucircumflex const KEY_Udiaeresis = detail.KEY_Udiaeresis export KEY_Udiaeresis const KEY_Yacute = detail.KEY_Yacute export KEY_Yacute const KEY_THORN = detail.KEY_THORN export KEY_THORN const KEY_Thorn = detail.KEY_Thorn export KEY_Thorn const KEY_ssharp = detail.KEY_ssharp export KEY_ssharp const KEY_agrave = detail.KEY_agrave export KEY_agrave const KEY_aacute = detail.KEY_aacute export KEY_aacute const KEY_acircumflex = detail.KEY_acircumflex export KEY_acircumflex const KEY_atilde = detail.KEY_atilde export KEY_atilde const KEY_adiaeresis = detail.KEY_adiaeresis export KEY_adiaeresis const KEY_aring = detail.KEY_aring export KEY_aring const KEY_ae = detail.KEY_ae export KEY_ae const KEY_ccedilla = detail.KEY_ccedilla export KEY_ccedilla const KEY_egrave = detail.KEY_egrave export KEY_egrave const KEY_eacute = detail.KEY_eacute export KEY_eacute const KEY_ecircumflex = detail.KEY_ecircumflex export KEY_ecircumflex const KEY_ediaeresis = detail.KEY_ediaeresis export KEY_ediaeresis const KEY_igrave = detail.KEY_igrave export KEY_igrave const KEY_iacute = detail.KEY_iacute export KEY_iacute const KEY_icircumflex = detail.KEY_icircumflex export KEY_icircumflex const KEY_idiaeresis = detail.KEY_idiaeresis export KEY_idiaeresis const KEY_eth = detail.KEY_eth export KEY_eth const KEY_ntilde = detail.KEY_ntilde export KEY_ntilde const KEY_ograve = detail.KEY_ograve export KEY_ograve const KEY_oacute = detail.KEY_oacute export KEY_oacute const KEY_ocircumflex = detail.KEY_ocircumflex export KEY_ocircumflex const KEY_otilde = detail.KEY_otilde export KEY_otilde const KEY_odiaeresis = detail.KEY_odiaeresis export KEY_odiaeresis const KEY_division = detail.KEY_division export KEY_division const KEY_oslash = detail.KEY_oslash export KEY_oslash const KEY_ooblique = detail.KEY_ooblique export KEY_ooblique const KEY_ugrave = detail.KEY_ugrave export KEY_ugrave const KEY_uacute = detail.KEY_uacute export KEY_uacute const KEY_ucircumflex = detail.KEY_ucircumflex export KEY_ucircumflex const KEY_udiaeresis = detail.KEY_udiaeresis export KEY_udiaeresis const KEY_yacute = detail.KEY_yacute export KEY_yacute const KEY_thorn = detail.KEY_thorn export KEY_thorn const KEY_ydiaeresis = detail.KEY_ydiaeresis export KEY_ydiaeresis const KEY_Aogonek = detail.KEY_Aogonek export KEY_Aogonek const KEY_breve = detail.KEY_breve export KEY_breve const KEY_Lstroke = detail.KEY_Lstroke export KEY_Lstroke const KEY_Lcaron = detail.KEY_Lcaron export KEY_Lcaron const KEY_Sacute = detail.KEY_Sacute export KEY_Sacute const KEY_Scaron = detail.KEY_Scaron export KEY_Scaron const KEY_Scedilla = detail.KEY_Scedilla export KEY_Scedilla const KEY_Tcaron = detail.KEY_Tcaron export KEY_Tcaron const KEY_Zacute = detail.KEY_Zacute export KEY_Zacute const KEY_Zcaron = detail.KEY_Zcaron export KEY_Zcaron const KEY_Zabovedot = detail.KEY_Zabovedot export KEY_Zabovedot const KEY_aogonek = detail.KEY_aogonek export KEY_aogonek const KEY_ogonek = detail.KEY_ogonek export KEY_ogonek const KEY_lstroke = detail.KEY_lstroke export KEY_lstroke const KEY_lcaron = detail.KEY_lcaron export KEY_lcaron const KEY_sacute = detail.KEY_sacute export KEY_sacute const KEY_caron = detail.KEY_caron export KEY_caron const KEY_scaron = detail.KEY_scaron export KEY_scaron const KEY_scedilla = detail.KEY_scedilla export KEY_scedilla const KEY_tcaron = detail.KEY_tcaron export KEY_tcaron const KEY_zacute = detail.KEY_zacute export KEY_zacute const KEY_doubleacute = detail.KEY_doubleacute export KEY_doubleacute const KEY_zcaron = detail.KEY_zcaron export KEY_zcaron const KEY_zabovedot = detail.KEY_zabovedot export KEY_zabovedot const KEY_Racute = detail.KEY_Racute export KEY_Racute const KEY_Abreve = detail.KEY_Abreve export KEY_Abreve const KEY_Lacute = detail.KEY_Lacute export KEY_Lacute const KEY_Cacute = detail.KEY_Cacute export KEY_Cacute const KEY_Ccaron = detail.KEY_Ccaron export KEY_Ccaron const KEY_Eogonek = detail.KEY_Eogonek export KEY_Eogonek const KEY_Ecaron = detail.KEY_Ecaron export KEY_Ecaron const KEY_Dcaron = detail.KEY_Dcaron export KEY_Dcaron const KEY_Dstroke = detail.KEY_Dstroke export KEY_Dstroke const KEY_Nacute = detail.KEY_Nacute export KEY_Nacute const KEY_Ncaron = detail.KEY_Ncaron export KEY_Ncaron const KEY_Odoubleacute = detail.KEY_Odoubleacute export KEY_Odoubleacute const KEY_Rcaron = detail.KEY_Rcaron export KEY_Rcaron const KEY_Uring = detail.KEY_Uring export KEY_Uring const KEY_Udoubleacute = detail.KEY_Udoubleacute export KEY_Udoubleacute const KEY_Tcedilla = detail.KEY_Tcedilla export KEY_Tcedilla const KEY_racute = detail.KEY_racute export KEY_racute const KEY_abreve = detail.KEY_abreve export KEY_abreve const KEY_lacute = detail.KEY_lacute export KEY_lacute const KEY_cacute = detail.KEY_cacute export KEY_cacute const KEY_ccaron = detail.KEY_ccaron export KEY_ccaron const KEY_eogonek = detail.KEY_eogonek export KEY_eogonek const KEY_ecaron = detail.KEY_ecaron export KEY_ecaron const KEY_dcaron = detail.KEY_dcaron export KEY_dcaron const KEY_dstroke = detail.KEY_dstroke export KEY_dstroke const KEY_nacute = detail.KEY_nacute export KEY_nacute const KEY_ncaron = detail.KEY_ncaron export KEY_ncaron const KEY_odoubleacute = detail.KEY_odoubleacute export KEY_odoubleacute const KEY_rcaron = detail.KEY_rcaron export KEY_rcaron const KEY_uring = detail.KEY_uring export KEY_uring const KEY_udoubleacute = detail.KEY_udoubleacute export KEY_udoubleacute const KEY_tcedilla = detail.KEY_tcedilla export KEY_tcedilla const KEY_abovedot = detail.KEY_abovedot export KEY_abovedot const KEY_Hstroke = detail.KEY_Hstroke export KEY_Hstroke const KEY_Hcircumflex = detail.KEY_Hcircumflex export KEY_Hcircumflex const KEY_Iabovedot = detail.KEY_Iabovedot export KEY_Iabovedot const KEY_Gbreve = detail.KEY_Gbreve export KEY_Gbreve const KEY_Jcircumflex = detail.KEY_Jcircumflex export KEY_Jcircumflex const KEY_hstroke = detail.KEY_hstroke export KEY_hstroke const KEY_hcircumflex = detail.KEY_hcircumflex export KEY_hcircumflex const KEY_idotless = detail.KEY_idotless export KEY_idotless const KEY_gbreve = detail.KEY_gbreve export KEY_gbreve const KEY_jcircumflex = detail.KEY_jcircumflex export KEY_jcircumflex const KEY_Cabovedot = detail.KEY_Cabovedot export KEY_Cabovedot const KEY_Ccircumflex = detail.KEY_Ccircumflex export KEY_Ccircumflex const KEY_Gabovedot = detail.KEY_Gabovedot export KEY_Gabovedot const KEY_Gcircumflex = detail.KEY_Gcircumflex export KEY_Gcircumflex const KEY_Ubreve = detail.KEY_Ubreve export KEY_Ubreve const KEY_Scircumflex = detail.KEY_Scircumflex export KEY_Scircumflex const KEY_cabovedot = detail.KEY_cabovedot export KEY_cabovedot const KEY_ccircumflex = detail.KEY_ccircumflex export KEY_ccircumflex const KEY_gabovedot = detail.KEY_gabovedot export KEY_gabovedot const KEY_gcircumflex = detail.KEY_gcircumflex export KEY_gcircumflex const KEY_ubreve = detail.KEY_ubreve export KEY_ubreve const KEY_scircumflex = detail.KEY_scircumflex export KEY_scircumflex const KEY_kra = detail.KEY_kra export KEY_kra const KEY_kappa = detail.KEY_kappa export KEY_kappa const KEY_Rcedilla = detail.KEY_Rcedilla export KEY_Rcedilla const KEY_Itilde = detail.KEY_Itilde export KEY_Itilde const KEY_Lcedilla = detail.KEY_Lcedilla export KEY_Lcedilla const KEY_Emacron = detail.KEY_Emacron export KEY_Emacron const KEY_Gcedilla = detail.KEY_Gcedilla export KEY_Gcedilla const KEY_Tslash = detail.KEY_Tslash export KEY_Tslash const KEY_rcedilla = detail.KEY_rcedilla export KEY_rcedilla const KEY_itilde = detail.KEY_itilde export KEY_itilde const KEY_lcedilla = detail.KEY_lcedilla export KEY_lcedilla const KEY_emacron = detail.KEY_emacron export KEY_emacron const KEY_gcedilla = detail.KEY_gcedilla export KEY_gcedilla const KEY_tslash = detail.KEY_tslash export KEY_tslash const KEY_ENG = detail.KEY_ENG export KEY_ENG const KEY_eng = detail.KEY_eng export KEY_eng const KEY_Amacron = detail.KEY_Amacron export KEY_Amacron const KEY_Iogonek = detail.KEY_Iogonek export KEY_Iogonek const KEY_Eabovedot = detail.KEY_Eabovedot export KEY_Eabovedot const KEY_Imacron = detail.KEY_Imacron export KEY_Imacron const KEY_Ncedilla = detail.KEY_Ncedilla export KEY_Ncedilla const KEY_Omacron = detail.KEY_Omacron export KEY_Omacron const KEY_Kcedilla = detail.KEY_Kcedilla export KEY_Kcedilla const KEY_Uogonek = detail.KEY_Uogonek export KEY_Uogonek const KEY_Utilde = detail.KEY_Utilde export KEY_Utilde const KEY_Umacron = detail.KEY_Umacron export KEY_Umacron const KEY_amacron = detail.KEY_amacron export KEY_amacron const KEY_iogonek = detail.KEY_iogonek export KEY_iogonek const KEY_eabovedot = detail.KEY_eabovedot export KEY_eabovedot const KEY_imacron = detail.KEY_imacron export KEY_imacron const KEY_ncedilla = detail.KEY_ncedilla export KEY_ncedilla const KEY_omacron = detail.KEY_omacron export KEY_omacron const KEY_kcedilla = detail.KEY_kcedilla export KEY_kcedilla const KEY_uogonek = detail.KEY_uogonek export KEY_uogonek const KEY_utilde = detail.KEY_utilde export KEY_utilde const KEY_umacron = detail.KEY_umacron export KEY_umacron const KEY_Wcircumflex = detail.KEY_Wcircumflex export KEY_Wcircumflex const KEY_wcircumflex = detail.KEY_wcircumflex export KEY_wcircumflex const KEY_Ycircumflex = detail.KEY_Ycircumflex export KEY_Ycircumflex const KEY_ycircumflex = detail.KEY_ycircumflex export KEY_ycircumflex const KEY_Babovedot = detail.KEY_Babovedot export KEY_Babovedot const KEY_babovedot = detail.KEY_babovedot export KEY_babovedot const KEY_Dabovedot = detail.KEY_Dabovedot export KEY_Dabovedot const KEY_dabovedot = detail.KEY_dabovedot export KEY_dabovedot const KEY_Fabovedot = detail.KEY_Fabovedot export KEY_Fabovedot const KEY_fabovedot = detail.KEY_fabovedot export KEY_fabovedot const KEY_Mabovedot = detail.KEY_Mabovedot export KEY_Mabovedot const KEY_mabovedot = detail.KEY_mabovedot export KEY_mabovedot const KEY_Pabovedot = detail.KEY_Pabovedot export KEY_Pabovedot const KEY_pabovedot = detail.KEY_pabovedot export KEY_pabovedot const KEY_Sabovedot = detail.KEY_Sabovedot export KEY_Sabovedot const KEY_sabovedot = detail.KEY_sabovedot export KEY_sabovedot const KEY_Tabovedot = detail.KEY_Tabovedot export KEY_Tabovedot const KEY_tabovedot = detail.KEY_tabovedot export KEY_tabovedot const KEY_Wgrave = detail.KEY_Wgrave export KEY_Wgrave const KEY_wgrave = detail.KEY_wgrave export KEY_wgrave const KEY_Wacute = detail.KEY_Wacute export KEY_Wacute const KEY_wacute = detail.KEY_wacute export KEY_wacute const KEY_Wdiaeresis = detail.KEY_Wdiaeresis export KEY_Wdiaeresis const KEY_wdiaeresis = detail.KEY_wdiaeresis export KEY_wdiaeresis const KEY_Ygrave = detail.KEY_Ygrave export KEY_Ygrave const KEY_ygrave = detail.KEY_ygrave export KEY_ygrave const KEY_OE = detail.KEY_OE export KEY_OE const KEY_oe = detail.KEY_oe export KEY_oe const KEY_Ydiaeresis = detail.KEY_Ydiaeresis export KEY_Ydiaeresis const KEY_overline = detail.KEY_overline export KEY_overline const KEY_kana_fullstop = detail.KEY_kana_fullstop export KEY_kana_fullstop const KEY_kana_openingbracket = detail.KEY_kana_openingbracket export KEY_kana_openingbracket const KEY_kana_closingbracket = detail.KEY_kana_closingbracket export KEY_kana_closingbracket const KEY_kana_comma = detail.KEY_kana_comma export KEY_kana_comma const KEY_kana_conjunctive = detail.KEY_kana_conjunctive export KEY_kana_conjunctive const KEY_kana_middledot = detail.KEY_kana_middledot export KEY_kana_middledot const KEY_kana_WO = detail.KEY_kana_WO export KEY_kana_WO const KEY_kana_a = detail.KEY_kana_a export KEY_kana_a const KEY_kana_i = detail.KEY_kana_i export KEY_kana_i const KEY_kana_u = detail.KEY_kana_u export KEY_kana_u const KEY_kana_e = detail.KEY_kana_e export KEY_kana_e const KEY_kana_o = detail.KEY_kana_o export KEY_kana_o const KEY_kana_ya = detail.KEY_kana_ya export KEY_kana_ya const KEY_kana_yu = detail.KEY_kana_yu export KEY_kana_yu const KEY_kana_yo = detail.KEY_kana_yo export KEY_kana_yo const KEY_kana_tsu = detail.KEY_kana_tsu export KEY_kana_tsu const KEY_kana_tu = detail.KEY_kana_tu export KEY_kana_tu const KEY_prolongedsound = detail.KEY_prolongedsound export KEY_prolongedsound const KEY_kana_A = detail.KEY_kana_A export KEY_kana_A const KEY_kana_I = detail.KEY_kana_I export KEY_kana_I const KEY_kana_U = detail.KEY_kana_U export KEY_kana_U const KEY_kana_E = detail.KEY_kana_E export KEY_kana_E const KEY_kana_O = detail.KEY_kana_O export KEY_kana_O const KEY_kana_KA = detail.KEY_kana_KA export KEY_kana_KA const KEY_kana_KI = detail.KEY_kana_KI export KEY_kana_KI const KEY_kana_KU = detail.KEY_kana_KU export KEY_kana_KU const KEY_kana_KE = detail.KEY_kana_KE export KEY_kana_KE const KEY_kana_KO = detail.KEY_kana_KO export KEY_kana_KO const KEY_kana_SA = detail.KEY_kana_SA export KEY_kana_SA const KEY_kana_SHI = detail.KEY_kana_SHI export KEY_kana_SHI const KEY_kana_SU = detail.KEY_kana_SU export KEY_kana_SU const KEY_kana_SE = detail.KEY_kana_SE export KEY_kana_SE const KEY_kana_SO = detail.KEY_kana_SO export KEY_kana_SO const KEY_kana_TA = detail.KEY_kana_TA export KEY_kana_TA const KEY_kana_CHI = detail.KEY_kana_CHI export KEY_kana_CHI const KEY_kana_TI = detail.KEY_kana_TI export KEY_kana_TI const KEY_kana_TSU = detail.KEY_kana_TSU export KEY_kana_TSU const KEY_kana_TU = detail.KEY_kana_TU export KEY_kana_TU const KEY_kana_TE = detail.KEY_kana_TE export KEY_kana_TE const KEY_kana_TO = detail.KEY_kana_TO export KEY_kana_TO const KEY_kana_NA = detail.KEY_kana_NA export KEY_kana_NA const KEY_kana_NI = detail.KEY_kana_NI export KEY_kana_NI const KEY_kana_NU = detail.KEY_kana_NU export KEY_kana_NU const KEY_kana_NE = detail.KEY_kana_NE export KEY_kana_NE const KEY_kana_NO = detail.KEY_kana_NO export KEY_kana_NO const KEY_kana_HA = detail.KEY_kana_HA export KEY_kana_HA const KEY_kana_HI = detail.KEY_kana_HI export KEY_kana_HI const KEY_kana_FU = detail.KEY_kana_FU export KEY_kana_FU const KEY_kana_HU = detail.KEY_kana_HU export KEY_kana_HU const KEY_kana_HE = detail.KEY_kana_HE export KEY_kana_HE const KEY_kana_HO = detail.KEY_kana_HO export KEY_kana_HO const KEY_kana_MA = detail.KEY_kana_MA export KEY_kana_MA const KEY_kana_MI = detail.KEY_kana_MI export KEY_kana_MI const KEY_kana_MU = detail.KEY_kana_MU export KEY_kana_MU const KEY_kana_ME = detail.KEY_kana_ME export KEY_kana_ME const KEY_kana_MO = detail.KEY_kana_MO export KEY_kana_MO const KEY_kana_YA = detail.KEY_kana_YA export KEY_kana_YA const KEY_kana_YU = detail.KEY_kana_YU export KEY_kana_YU const KEY_kana_YO = detail.KEY_kana_YO export KEY_kana_YO const KEY_kana_RA = detail.KEY_kana_RA export KEY_kana_RA const KEY_kana_RI = detail.KEY_kana_RI export KEY_kana_RI const KEY_kana_RU = detail.KEY_kana_RU export KEY_kana_RU const KEY_kana_RE = detail.KEY_kana_RE export KEY_kana_RE const KEY_kana_RO = detail.KEY_kana_RO export KEY_kana_RO const KEY_kana_WA = detail.KEY_kana_WA export KEY_kana_WA const KEY_kana_N = detail.KEY_kana_N export KEY_kana_N const KEY_voicedsound = detail.KEY_voicedsound export KEY_voicedsound const KEY_semivoicedsound = detail.KEY_semivoicedsound export KEY_semivoicedsound const KEY_kana_switch = detail.KEY_kana_switch export KEY_kana_switch const KEY_Farsi_0 = detail.KEY_Farsi_0 export KEY_Farsi_0 const KEY_Farsi_1 = detail.KEY_Farsi_1 export KEY_Farsi_1 const KEY_Farsi_2 = detail.KEY_Farsi_2 export KEY_Farsi_2 const KEY_Farsi_3 = detail.KEY_Farsi_3 export KEY_Farsi_3 const KEY_Farsi_4 = detail.KEY_Farsi_4 export KEY_Farsi_4 const KEY_Farsi_5 = detail.KEY_Farsi_5 export KEY_Farsi_5 const KEY_Farsi_6 = detail.KEY_Farsi_6 export KEY_Farsi_6 const KEY_Farsi_7 = detail.KEY_Farsi_7 export KEY_Farsi_7 const KEY_Farsi_8 = detail.KEY_Farsi_8 export KEY_Farsi_8 const KEY_Farsi_9 = detail.KEY_Farsi_9 export KEY_Farsi_9 const KEY_Arabic_percent = detail.KEY_Arabic_percent export KEY_Arabic_percent const KEY_Arabic_superscript_alef = detail.KEY_Arabic_superscript_alef export KEY_Arabic_superscript_alef const KEY_Arabic_tteh = detail.KEY_Arabic_tteh export KEY_Arabic_tteh const KEY_Arabic_peh = detail.KEY_Arabic_peh export KEY_Arabic_peh const KEY_Arabic_tcheh = detail.KEY_Arabic_tcheh export KEY_Arabic_tcheh const KEY_Arabic_ddal = detail.KEY_Arabic_ddal export KEY_Arabic_ddal const KEY_Arabic_rreh = detail.KEY_Arabic_rreh export KEY_Arabic_rreh const KEY_Arabic_comma = detail.KEY_Arabic_comma export KEY_Arabic_comma const KEY_Arabic_fullstop = detail.KEY_Arabic_fullstop export KEY_Arabic_fullstop const KEY_Arabic_0 = detail.KEY_Arabic_0 export KEY_Arabic_0 const KEY_Arabic_1 = detail.KEY_Arabic_1 export KEY_Arabic_1 const KEY_Arabic_2 = detail.KEY_Arabic_2 export KEY_Arabic_2 const KEY_Arabic_3 = detail.KEY_Arabic_3 export KEY_Arabic_3 const KEY_Arabic_4 = detail.KEY_Arabic_4 export KEY_Arabic_4 const KEY_Arabic_5 = detail.KEY_Arabic_5 export KEY_Arabic_5 const KEY_Arabic_6 = detail.KEY_Arabic_6 export KEY_Arabic_6 const KEY_Arabic_7 = detail.KEY_Arabic_7 export KEY_Arabic_7 const KEY_Arabic_8 = detail.KEY_Arabic_8 export KEY_Arabic_8 const KEY_Arabic_9 = detail.KEY_Arabic_9 export KEY_Arabic_9 const KEY_Arabic_semicolon = detail.KEY_Arabic_semicolon export KEY_Arabic_semicolon const KEY_Arabic_question_mark = detail.KEY_Arabic_question_mark export KEY_Arabic_question_mark const KEY_Arabic_hamza = detail.KEY_Arabic_hamza export KEY_Arabic_hamza const KEY_Arabic_maddaonalef = detail.KEY_Arabic_maddaonalef export KEY_Arabic_maddaonalef const KEY_Arabic_hamzaonalef = detail.KEY_Arabic_hamzaonalef export KEY_Arabic_hamzaonalef const KEY_Arabic_hamzaonwaw = detail.KEY_Arabic_hamzaonwaw export KEY_Arabic_hamzaonwaw const KEY_Arabic_hamzaunderalef = detail.KEY_Arabic_hamzaunderalef export KEY_Arabic_hamzaunderalef const KEY_Arabic_hamzaonyeh = detail.KEY_Arabic_hamzaonyeh export KEY_Arabic_hamzaonyeh const KEY_Arabic_alef = detail.KEY_Arabic_alef export KEY_Arabic_alef const KEY_Arabic_beh = detail.KEY_Arabic_beh export KEY_Arabic_beh const KEY_Arabic_tehmarbuta = detail.KEY_Arabic_tehmarbuta export KEY_Arabic_tehmarbuta const KEY_Arabic_teh = detail.KEY_Arabic_teh export KEY_Arabic_teh const KEY_Arabic_theh = detail.KEY_Arabic_theh export KEY_Arabic_theh const KEY_Arabic_jeem = detail.KEY_Arabic_jeem export KEY_Arabic_jeem const KEY_Arabic_hah = detail.KEY_Arabic_hah export KEY_Arabic_hah const KEY_Arabic_khah = detail.KEY_Arabic_khah export KEY_Arabic_khah const KEY_Arabic_dal = detail.KEY_Arabic_dal export KEY_Arabic_dal const KEY_Arabic_thal = detail.KEY_Arabic_thal export KEY_Arabic_thal const KEY_Arabic_ra = detail.KEY_Arabic_ra export KEY_Arabic_ra const KEY_Arabic_zain = detail.KEY_Arabic_zain export KEY_Arabic_zain const KEY_Arabic_seen = detail.KEY_Arabic_seen export KEY_Arabic_seen const KEY_Arabic_sheen = detail.KEY_Arabic_sheen export KEY_Arabic_sheen const KEY_Arabic_sad = detail.KEY_Arabic_sad export KEY_Arabic_sad const KEY_Arabic_dad = detail.KEY_Arabic_dad export KEY_Arabic_dad const KEY_Arabic_tah = detail.KEY_Arabic_tah export KEY_Arabic_tah const KEY_Arabic_zah = detail.KEY_Arabic_zah export KEY_Arabic_zah const KEY_Arabic_ain = detail.KEY_Arabic_ain export KEY_Arabic_ain const KEY_Arabic_ghain = detail.KEY_Arabic_ghain export KEY_Arabic_ghain const KEY_Arabic_tatweel = detail.KEY_Arabic_tatweel export KEY_Arabic_tatweel const KEY_Arabic_feh = detail.KEY_Arabic_feh export KEY_Arabic_feh const KEY_Arabic_qaf = detail.KEY_Arabic_qaf export KEY_Arabic_qaf const KEY_Arabic_kaf = detail.KEY_Arabic_kaf export KEY_Arabic_kaf const KEY_Arabic_lam = detail.KEY_Arabic_lam export KEY_Arabic_lam const KEY_Arabic_meem = detail.KEY_Arabic_meem export KEY_Arabic_meem const KEY_Arabic_noon = detail.KEY_Arabic_noon export KEY_Arabic_noon const KEY_Arabic_ha = detail.KEY_Arabic_ha export KEY_Arabic_ha const KEY_Arabic_heh = detail.KEY_Arabic_heh export KEY_Arabic_heh const KEY_Arabic_waw = detail.KEY_Arabic_waw export KEY_Arabic_waw const KEY_Arabic_alefmaksura = detail.KEY_Arabic_alefmaksura export KEY_Arabic_alefmaksura const KEY_Arabic_yeh = detail.KEY_Arabic_yeh export KEY_Arabic_yeh const KEY_Arabic_fathatan = detail.KEY_Arabic_fathatan export KEY_Arabic_fathatan const KEY_Arabic_dammatan = detail.KEY_Arabic_dammatan export KEY_Arabic_dammatan const KEY_Arabic_kasratan = detail.KEY_Arabic_kasratan export KEY_Arabic_kasratan const KEY_Arabic_fatha = detail.KEY_Arabic_fatha export KEY_Arabic_fatha const KEY_Arabic_damma = detail.KEY_Arabic_damma export KEY_Arabic_damma const KEY_Arabic_kasra = detail.KEY_Arabic_kasra export KEY_Arabic_kasra const KEY_Arabic_shadda = detail.KEY_Arabic_shadda export KEY_Arabic_shadda const KEY_Arabic_sukun = detail.KEY_Arabic_sukun export KEY_Arabic_sukun const KEY_Arabic_madda_above = detail.KEY_Arabic_madda_above export KEY_Arabic_madda_above const KEY_Arabic_hamza_above = detail.KEY_Arabic_hamza_above export KEY_Arabic_hamza_above const KEY_Arabic_hamza_below = detail.KEY_Arabic_hamza_below export KEY_Arabic_hamza_below const KEY_Arabic_jeh = detail.KEY_Arabic_jeh export KEY_Arabic_jeh const KEY_Arabic_veh = detail.KEY_Arabic_veh export KEY_Arabic_veh const KEY_Arabic_keheh = detail.KEY_Arabic_keheh export KEY_Arabic_keheh const KEY_Arabic_gaf = detail.KEY_Arabic_gaf export KEY_Arabic_gaf const KEY_Arabic_noon_ghunna = detail.KEY_Arabic_noon_ghunna export KEY_Arabic_noon_ghunna const KEY_Arabic_heh_doachashmee = detail.KEY_Arabic_heh_doachashmee export KEY_Arabic_heh_doachashmee const KEY_Farsi_yeh = detail.KEY_Farsi_yeh export KEY_Farsi_yeh const KEY_Arabic_farsi_yeh = detail.KEY_Arabic_farsi_yeh export KEY_Arabic_farsi_yeh const KEY_Arabic_yeh_baree = detail.KEY_Arabic_yeh_baree export KEY_Arabic_yeh_baree const KEY_Arabic_heh_goal = detail.KEY_Arabic_heh_goal export KEY_Arabic_heh_goal const KEY_Arabic_switch = detail.KEY_Arabic_switch export KEY_Arabic_switch const KEY_Cyrillic_GHE_bar = detail.KEY_Cyrillic_GHE_bar export KEY_Cyrillic_GHE_bar const KEY_Cyrillic_ghe_bar = detail.KEY_Cyrillic_ghe_bar export KEY_Cyrillic_ghe_bar const KEY_Cyrillic_ZHE_descender = detail.KEY_Cyrillic_ZHE_descender export KEY_Cyrillic_ZHE_descender const KEY_Cyrillic_zhe_descender = detail.KEY_Cyrillic_zhe_descender export KEY_Cyrillic_zhe_descender const KEY_Cyrillic_KA_descender = detail.KEY_Cyrillic_KA_descender export KEY_Cyrillic_KA_descender const KEY_Cyrillic_ka_descender = detail.KEY_Cyrillic_ka_descender export KEY_Cyrillic_ka_descender const KEY_Cyrillic_KA_vertstroke = detail.KEY_Cyrillic_KA_vertstroke export KEY_Cyrillic_KA_vertstroke const KEY_Cyrillic_ka_vertstroke = detail.KEY_Cyrillic_ka_vertstroke export KEY_Cyrillic_ka_vertstroke const KEY_Cyrillic_EN_descender = detail.KEY_Cyrillic_EN_descender export KEY_Cyrillic_EN_descender const KEY_Cyrillic_en_descender = detail.KEY_Cyrillic_en_descender export KEY_Cyrillic_en_descender const KEY_Cyrillic_U_straight = detail.KEY_Cyrillic_U_straight export KEY_Cyrillic_U_straight const KEY_Cyrillic_u_straight = detail.KEY_Cyrillic_u_straight export KEY_Cyrillic_u_straight const KEY_Cyrillic_U_straight_bar = detail.KEY_Cyrillic_U_straight_bar export KEY_Cyrillic_U_straight_bar const KEY_Cyrillic_u_straight_bar = detail.KEY_Cyrillic_u_straight_bar export KEY_Cyrillic_u_straight_bar const KEY_Cyrillic_HA_descender = detail.KEY_Cyrillic_HA_descender export KEY_Cyrillic_HA_descender const KEY_Cyrillic_ha_descender = detail.KEY_Cyrillic_ha_descender export KEY_Cyrillic_ha_descender const KEY_Cyrillic_CHE_descender = detail.KEY_Cyrillic_CHE_descender export KEY_Cyrillic_CHE_descender const KEY_Cyrillic_che_descender = detail.KEY_Cyrillic_che_descender export KEY_Cyrillic_che_descender const KEY_Cyrillic_CHE_vertstroke = detail.KEY_Cyrillic_CHE_vertstroke export KEY_Cyrillic_CHE_vertstroke const KEY_Cyrillic_che_vertstroke = detail.KEY_Cyrillic_che_vertstroke export KEY_Cyrillic_che_vertstroke const KEY_Cyrillic_SHHA = detail.KEY_Cyrillic_SHHA export KEY_Cyrillic_SHHA const KEY_Cyrillic_shha = detail.KEY_Cyrillic_shha export KEY_Cyrillic_shha const KEY_Cyrillic_SCHWA = detail.KEY_Cyrillic_SCHWA export KEY_Cyrillic_SCHWA const KEY_Cyrillic_schwa = detail.KEY_Cyrillic_schwa export KEY_Cyrillic_schwa const KEY_Cyrillic_I_macron = detail.KEY_Cyrillic_I_macron export KEY_Cyrillic_I_macron const KEY_Cyrillic_i_macron = detail.KEY_Cyrillic_i_macron export KEY_Cyrillic_i_macron const KEY_Cyrillic_O_bar = detail.KEY_Cyrillic_O_bar export KEY_Cyrillic_O_bar const KEY_Cyrillic_o_bar = detail.KEY_Cyrillic_o_bar export KEY_Cyrillic_o_bar const KEY_Cyrillic_U_macron = detail.KEY_Cyrillic_U_macron export KEY_Cyrillic_U_macron const KEY_Cyrillic_u_macron = detail.KEY_Cyrillic_u_macron export KEY_Cyrillic_u_macron const KEY_Serbian_dje = detail.KEY_Serbian_dje export KEY_Serbian_dje const KEY_Macedonia_gje = detail.KEY_Macedonia_gje export KEY_Macedonia_gje const KEY_Cyrillic_io = detail.KEY_Cyrillic_io export KEY_Cyrillic_io const KEY_Ukrainian_ie = detail.KEY_Ukrainian_ie export KEY_Ukrainian_ie const KEY_Ukranian_je = detail.KEY_Ukranian_je export KEY_Ukranian_je const KEY_Macedonia_dse = detail.KEY_Macedonia_dse export KEY_Macedonia_dse const KEY_Ukrainian_i = detail.KEY_Ukrainian_i export KEY_Ukrainian_i const KEY_Ukranian_i = detail.KEY_Ukranian_i export KEY_Ukranian_i const KEY_Ukrainian_yi = detail.KEY_Ukrainian_yi export KEY_Ukrainian_yi const KEY_Ukranian_yi = detail.KEY_Ukranian_yi export KEY_Ukranian_yi const KEY_Cyrillic_je = detail.KEY_Cyrillic_je export KEY_Cyrillic_je const KEY_Serbian_je = detail.KEY_Serbian_je export KEY_Serbian_je const KEY_Cyrillic_lje = detail.KEY_Cyrillic_lje export KEY_Cyrillic_lje const KEY_Serbian_lje = detail.KEY_Serbian_lje export KEY_Serbian_lje const KEY_Cyrillic_nje = detail.KEY_Cyrillic_nje export KEY_Cyrillic_nje const KEY_Serbian_nje = detail.KEY_Serbian_nje export KEY_Serbian_nje const KEY_Serbian_tshe = detail.KEY_Serbian_tshe export KEY_Serbian_tshe const KEY_Macedonia_kje = detail.KEY_Macedonia_kje export KEY_Macedonia_kje const KEY_Ukrainian_ghe_with_upturn = detail.KEY_Ukrainian_ghe_with_upturn export KEY_Ukrainian_ghe_with_upturn const KEY_Byelorussian_shortu = detail.KEY_Byelorussian_shortu export KEY_Byelorussian_shortu const KEY_Cyrillic_dzhe = detail.KEY_Cyrillic_dzhe export KEY_Cyrillic_dzhe const KEY_Serbian_dze = detail.KEY_Serbian_dze export KEY_Serbian_dze const KEY_numerosign = detail.KEY_numerosign export KEY_numerosign const KEY_Serbian_DJE = detail.KEY_Serbian_DJE export KEY_Serbian_DJE const KEY_Macedonia_GJE = detail.KEY_Macedonia_GJE export KEY_Macedonia_GJE const KEY_Cyrillic_IO = detail.KEY_Cyrillic_IO export KEY_Cyrillic_IO const KEY_Ukrainian_IE = detail.KEY_Ukrainian_IE export KEY_Ukrainian_IE const KEY_Ukranian_JE = detail.KEY_Ukranian_JE export KEY_Ukranian_JE const KEY_Macedonia_DSE = detail.KEY_Macedonia_DSE export KEY_Macedonia_DSE const KEY_Ukrainian_I = detail.KEY_Ukrainian_I export KEY_Ukrainian_I const KEY_Ukranian_I = detail.KEY_Ukranian_I export KEY_Ukranian_I const KEY_Ukrainian_YI = detail.KEY_Ukrainian_YI export KEY_Ukrainian_YI const KEY_Ukranian_YI = detail.KEY_Ukranian_YI export KEY_Ukranian_YI const KEY_Cyrillic_JE = detail.KEY_Cyrillic_JE export KEY_Cyrillic_JE const KEY_Serbian_JE = detail.KEY_Serbian_JE export KEY_Serbian_JE const KEY_Cyrillic_LJE = detail.KEY_Cyrillic_LJE export KEY_Cyrillic_LJE const KEY_Serbian_LJE = detail.KEY_Serbian_LJE export KEY_Serbian_LJE const KEY_Cyrillic_NJE = detail.KEY_Cyrillic_NJE export KEY_Cyrillic_NJE const KEY_Serbian_NJE = detail.KEY_Serbian_NJE export KEY_Serbian_NJE const KEY_Serbian_TSHE = detail.KEY_Serbian_TSHE export KEY_Serbian_TSHE const KEY_Macedonia_KJE = detail.KEY_Macedonia_KJE export KEY_Macedonia_KJE const KEY_Ukrainian_GHE_WITH_UPTURN = detail.KEY_Ukrainian_GHE_WITH_UPTURN export KEY_Ukrainian_GHE_WITH_UPTURN const KEY_Byelorussian_SHORTU = detail.KEY_Byelorussian_SHORTU export KEY_Byelorussian_SHORTU const KEY_Cyrillic_DZHE = detail.KEY_Cyrillic_DZHE export KEY_Cyrillic_DZHE const KEY_Serbian_DZE = detail.KEY_Serbian_DZE export KEY_Serbian_DZE const KEY_Cyrillic_yu = detail.KEY_Cyrillic_yu export KEY_Cyrillic_yu const KEY_Cyrillic_a = detail.KEY_Cyrillic_a export KEY_Cyrillic_a const KEY_Cyrillic_be = detail.KEY_Cyrillic_be export KEY_Cyrillic_be const KEY_Cyrillic_tse = detail.KEY_Cyrillic_tse export KEY_Cyrillic_tse const KEY_Cyrillic_de = detail.KEY_Cyrillic_de export KEY_Cyrillic_de const KEY_Cyrillic_ie = detail.KEY_Cyrillic_ie export KEY_Cyrillic_ie const KEY_Cyrillic_ef = detail.KEY_Cyrillic_ef export KEY_Cyrillic_ef const KEY_Cyrillic_ghe = detail.KEY_Cyrillic_ghe export KEY_Cyrillic_ghe const KEY_Cyrillic_ha = detail.KEY_Cyrillic_ha export KEY_Cyrillic_ha const KEY_Cyrillic_i = detail.KEY_Cyrillic_i export KEY_Cyrillic_i const KEY_Cyrillic_shorti = detail.KEY_Cyrillic_shorti export KEY_Cyrillic_shorti const KEY_Cyrillic_ka = detail.KEY_Cyrillic_ka export KEY_Cyrillic_ka const KEY_Cyrillic_el = detail.KEY_Cyrillic_el export KEY_Cyrillic_el const KEY_Cyrillic_em = detail.KEY_Cyrillic_em export KEY_Cyrillic_em const KEY_Cyrillic_en = detail.KEY_Cyrillic_en export KEY_Cyrillic_en const KEY_Cyrillic_o = detail.KEY_Cyrillic_o export KEY_Cyrillic_o const KEY_Cyrillic_pe = detail.KEY_Cyrillic_pe export KEY_Cyrillic_pe const KEY_Cyrillic_ya = detail.KEY_Cyrillic_ya export KEY_Cyrillic_ya const KEY_Cyrillic_er = detail.KEY_Cyrillic_er export KEY_Cyrillic_er const KEY_Cyrillic_es = detail.KEY_Cyrillic_es export KEY_Cyrillic_es const KEY_Cyrillic_te = detail.KEY_Cyrillic_te export KEY_Cyrillic_te const KEY_Cyrillic_u = detail.KEY_Cyrillic_u export KEY_Cyrillic_u const KEY_Cyrillic_zhe = detail.KEY_Cyrillic_zhe export KEY_Cyrillic_zhe const KEY_Cyrillic_ve = detail.KEY_Cyrillic_ve export KEY_Cyrillic_ve const KEY_Cyrillic_softsign = detail.KEY_Cyrillic_softsign export KEY_Cyrillic_softsign const KEY_Cyrillic_yeru = detail.KEY_Cyrillic_yeru export KEY_Cyrillic_yeru const KEY_Cyrillic_ze = detail.KEY_Cyrillic_ze export KEY_Cyrillic_ze const KEY_Cyrillic_sha = detail.KEY_Cyrillic_sha export KEY_Cyrillic_sha const KEY_Cyrillic_e = detail.KEY_Cyrillic_e export KEY_Cyrillic_e const KEY_Cyrillic_shcha = detail.KEY_Cyrillic_shcha export KEY_Cyrillic_shcha const KEY_Cyrillic_che = detail.KEY_Cyrillic_che export KEY_Cyrillic_che const KEY_Cyrillic_hardsign = detail.KEY_Cyrillic_hardsign export KEY_Cyrillic_hardsign const KEY_Cyrillic_YU = detail.KEY_Cyrillic_YU export KEY_Cyrillic_YU const KEY_Cyrillic_A = detail.KEY_Cyrillic_A export KEY_Cyrillic_A const KEY_Cyrillic_BE = detail.KEY_Cyrillic_BE export KEY_Cyrillic_BE const KEY_Cyrillic_TSE = detail.KEY_Cyrillic_TSE export KEY_Cyrillic_TSE const KEY_Cyrillic_DE = detail.KEY_Cyrillic_DE export KEY_Cyrillic_DE const KEY_Cyrillic_IE = detail.KEY_Cyrillic_IE export KEY_Cyrillic_IE const KEY_Cyrillic_EF = detail.KEY_Cyrillic_EF export KEY_Cyrillic_EF const KEY_Cyrillic_GHE = detail.KEY_Cyrillic_GHE export KEY_Cyrillic_GHE const KEY_Cyrillic_HA = detail.KEY_Cyrillic_HA export KEY_Cyrillic_HA const KEY_Cyrillic_I = detail.KEY_Cyrillic_I export KEY_Cyrillic_I const KEY_Cyrillic_SHORTI = detail.KEY_Cyrillic_SHORTI export KEY_Cyrillic_SHORTI const KEY_Cyrillic_KA = detail.KEY_Cyrillic_KA export KEY_Cyrillic_KA const KEY_Cyrillic_EL = detail.KEY_Cyrillic_EL export KEY_Cyrillic_EL const KEY_Cyrillic_EM = detail.KEY_Cyrillic_EM export KEY_Cyrillic_EM const KEY_Cyrillic_EN = detail.KEY_Cyrillic_EN export KEY_Cyrillic_EN const KEY_Cyrillic_O = detail.KEY_Cyrillic_O export KEY_Cyrillic_O const KEY_Cyrillic_PE = detail.KEY_Cyrillic_PE export KEY_Cyrillic_PE const KEY_Cyrillic_YA = detail.KEY_Cyrillic_YA export KEY_Cyrillic_YA const KEY_Cyrillic_ER = detail.KEY_Cyrillic_ER export KEY_Cyrillic_ER const KEY_Cyrillic_ES = detail.KEY_Cyrillic_ES export KEY_Cyrillic_ES const KEY_Cyrillic_TE = detail.KEY_Cyrillic_TE export KEY_Cyrillic_TE const KEY_Cyrillic_U = detail.KEY_Cyrillic_U export KEY_Cyrillic_U const KEY_Cyrillic_ZHE = detail.KEY_Cyrillic_ZHE export KEY_Cyrillic_ZHE const KEY_Cyrillic_VE = detail.KEY_Cyrillic_VE export KEY_Cyrillic_VE const KEY_Cyrillic_SOFTSIGN = detail.KEY_Cyrillic_SOFTSIGN export KEY_Cyrillic_SOFTSIGN const KEY_Cyrillic_YERU = detail.KEY_Cyrillic_YERU export KEY_Cyrillic_YERU const KEY_Cyrillic_ZE = detail.KEY_Cyrillic_ZE export KEY_Cyrillic_ZE const KEY_Cyrillic_SHA = detail.KEY_Cyrillic_SHA export KEY_Cyrillic_SHA const KEY_Cyrillic_E = detail.KEY_Cyrillic_E export KEY_Cyrillic_E const KEY_Cyrillic_SHCHA = detail.KEY_Cyrillic_SHCHA export KEY_Cyrillic_SHCHA const KEY_Cyrillic_CHE = detail.KEY_Cyrillic_CHE export KEY_Cyrillic_CHE const KEY_Cyrillic_HARDSIGN = detail.KEY_Cyrillic_HARDSIGN export KEY_Cyrillic_HARDSIGN const KEY_Greek_ALPHAaccent = detail.KEY_Greek_ALPHAaccent export KEY_Greek_ALPHAaccent const KEY_Greek_EPSILONaccent = detail.KEY_Greek_EPSILONaccent export KEY_Greek_EPSILONaccent const KEY_Greek_ETAaccent = detail.KEY_Greek_ETAaccent export KEY_Greek_ETAaccent const KEY_Greek_IOTAaccent = detail.KEY_Greek_IOTAaccent export KEY_Greek_IOTAaccent const KEY_Greek_IOTAdieresis = detail.KEY_Greek_IOTAdieresis export KEY_Greek_IOTAdieresis const KEY_Greek_IOTAdiaeresis = detail.KEY_Greek_IOTAdiaeresis export KEY_Greek_IOTAdiaeresis const KEY_Greek_OMICRONaccent = detail.KEY_Greek_OMICRONaccent export KEY_Greek_OMICRONaccent const KEY_Greek_UPSILONaccent = detail.KEY_Greek_UPSILONaccent export KEY_Greek_UPSILONaccent const KEY_Greek_UPSILONdieresis = detail.KEY_Greek_UPSILONdieresis export KEY_Greek_UPSILONdieresis const KEY_Greek_OMEGAaccent = detail.KEY_Greek_OMEGAaccent export KEY_Greek_OMEGAaccent const KEY_Greek_accentdieresis = detail.KEY_Greek_accentdieresis export KEY_Greek_accentdieresis const KEY_Greek_horizbar = detail.KEY_Greek_horizbar export KEY_Greek_horizbar const KEY_Greek_alphaaccent = detail.KEY_Greek_alphaaccent export KEY_Greek_alphaaccent const KEY_Greek_epsilonaccent = detail.KEY_Greek_epsilonaccent export KEY_Greek_epsilonaccent const KEY_Greek_etaaccent = detail.KEY_Greek_etaaccent export KEY_Greek_etaaccent const KEY_Greek_iotaaccent = detail.KEY_Greek_iotaaccent export KEY_Greek_iotaaccent const KEY_Greek_iotadieresis = detail.KEY_Greek_iotadieresis export KEY_Greek_iotadieresis const KEY_Greek_iotaaccentdieresis = detail.KEY_Greek_iotaaccentdieresis export KEY_Greek_iotaaccentdieresis const KEY_Greek_omicronaccent = detail.KEY_Greek_omicronaccent export KEY_Greek_omicronaccent const KEY_Greek_upsilonaccent = detail.KEY_Greek_upsilonaccent export KEY_Greek_upsilonaccent const KEY_Greek_upsilondieresis = detail.KEY_Greek_upsilondieresis export KEY_Greek_upsilondieresis const KEY_Greek_upsilonaccentdieresis = detail.KEY_Greek_upsilonaccentdieresis export KEY_Greek_upsilonaccentdieresis const KEY_Greek_omegaaccent = detail.KEY_Greek_omegaaccent export KEY_Greek_omegaaccent const KEY_Greek_ALPHA = detail.KEY_Greek_ALPHA export KEY_Greek_ALPHA const KEY_Greek_BETA = detail.KEY_Greek_BETA export KEY_Greek_BETA const KEY_Greek_GAMMA = detail.KEY_Greek_GAMMA export KEY_Greek_GAMMA const KEY_Greek_DELTA = detail.KEY_Greek_DELTA export KEY_Greek_DELTA const KEY_Greek_EPSILON = detail.KEY_Greek_EPSILON export KEY_Greek_EPSILON const KEY_Greek_ZETA = detail.KEY_Greek_ZETA export KEY_Greek_ZETA const KEY_Greek_ETA = detail.KEY_Greek_ETA export KEY_Greek_ETA const KEY_Greek_THETA = detail.KEY_Greek_THETA export KEY_Greek_THETA const KEY_Greek_IOTA = detail.KEY_Greek_IOTA export KEY_Greek_IOTA const KEY_Greek_KAPPA = detail.KEY_Greek_KAPPA export KEY_Greek_KAPPA const KEY_Greek_LAMDA = detail.KEY_Greek_LAMDA export KEY_Greek_LAMDA const KEY_Greek_LAMBDA = detail.KEY_Greek_LAMBDA export KEY_Greek_LAMBDA const KEY_Greek_MU = detail.KEY_Greek_MU export KEY_Greek_MU const KEY_Greek_NU = detail.KEY_Greek_NU export KEY_Greek_NU const KEY_Greek_XI = detail.KEY_Greek_XI export KEY_Greek_XI const KEY_Greek_OMICRON = detail.KEY_Greek_OMICRON export KEY_Greek_OMICRON const KEY_Greek_PI = detail.KEY_Greek_PI export KEY_Greek_PI const KEY_Greek_RHO = detail.KEY_Greek_RHO export KEY_Greek_RHO const KEY_Greek_SIGMA = detail.KEY_Greek_SIGMA export KEY_Greek_SIGMA const KEY_Greek_TAU = detail.KEY_Greek_TAU export KEY_Greek_TAU const KEY_Greek_UPSILON = detail.KEY_Greek_UPSILON export KEY_Greek_UPSILON const KEY_Greek_PHI = detail.KEY_Greek_PHI export KEY_Greek_PHI const KEY_Greek_CHI = detail.KEY_Greek_CHI export KEY_Greek_CHI const KEY_Greek_PSI = detail.KEY_Greek_PSI export KEY_Greek_PSI const KEY_Greek_OMEGA = detail.KEY_Greek_OMEGA export KEY_Greek_OMEGA const KEY_Greek_alpha = detail.KEY_Greek_alpha export KEY_Greek_alpha const KEY_Greek_beta = detail.KEY_Greek_beta export KEY_Greek_beta const KEY_Greek_gamma = detail.KEY_Greek_gamma export KEY_Greek_gamma const KEY_Greek_delta = detail.KEY_Greek_delta export KEY_Greek_delta const KEY_Greek_epsilon = detail.KEY_Greek_epsilon export KEY_Greek_epsilon const KEY_Greek_zeta = detail.KEY_Greek_zeta export KEY_Greek_zeta const KEY_Greek_eta = detail.KEY_Greek_eta export KEY_Greek_eta const KEY_Greek_theta = detail.KEY_Greek_theta export KEY_Greek_theta const KEY_Greek_iota = detail.KEY_Greek_iota export KEY_Greek_iota const KEY_Greek_kappa = detail.KEY_Greek_kappa export KEY_Greek_kappa const KEY_Greek_lamda = detail.KEY_Greek_lamda export KEY_Greek_lamda const KEY_Greek_lambda = detail.KEY_Greek_lambda export KEY_Greek_lambda const KEY_Greek_mu = detail.KEY_Greek_mu export KEY_Greek_mu const KEY_Greek_nu = detail.KEY_Greek_nu export KEY_Greek_nu const KEY_Greek_xi = detail.KEY_Greek_xi export KEY_Greek_xi const KEY_Greek_omicron = detail.KEY_Greek_omicron export KEY_Greek_omicron const KEY_Greek_pi = detail.KEY_Greek_pi export KEY_Greek_pi const KEY_Greek_rho = detail.KEY_Greek_rho export KEY_Greek_rho const KEY_Greek_sigma = detail.KEY_Greek_sigma export KEY_Greek_sigma const KEY_Greek_finalsmallsigma = detail.KEY_Greek_finalsmallsigma export KEY_Greek_finalsmallsigma const KEY_Greek_tau = detail.KEY_Greek_tau export KEY_Greek_tau const KEY_Greek_upsilon = detail.KEY_Greek_upsilon export KEY_Greek_upsilon const KEY_Greek_phi = detail.KEY_Greek_phi export KEY_Greek_phi const KEY_Greek_chi = detail.KEY_Greek_chi export KEY_Greek_chi const KEY_Greek_psi = detail.KEY_Greek_psi export KEY_Greek_psi const KEY_Greek_omega = detail.KEY_Greek_omega export KEY_Greek_omega const KEY_Greek_switch = detail.KEY_Greek_switch export KEY_Greek_switch const KEY_leftradical = detail.KEY_leftradical export KEY_leftradical const KEY_topleftradical = detail.KEY_topleftradical export KEY_topleftradical const KEY_horizconnector = detail.KEY_horizconnector export KEY_horizconnector const KEY_topintegral = detail.KEY_topintegral export KEY_topintegral const KEY_botintegral = detail.KEY_botintegral export KEY_botintegral const KEY_vertconnector = detail.KEY_vertconnector export KEY_vertconnector const KEY_topleftsqbracket = detail.KEY_topleftsqbracket export KEY_topleftsqbracket const KEY_botleftsqbracket = detail.KEY_botleftsqbracket export KEY_botleftsqbracket const KEY_toprightsqbracket = detail.KEY_toprightsqbracket export KEY_toprightsqbracket const KEY_botrightsqbracket = detail.KEY_botrightsqbracket export KEY_botrightsqbracket const KEY_topleftparens = detail.KEY_topleftparens export KEY_topleftparens const KEY_botleftparens = detail.KEY_botleftparens export KEY_botleftparens const KEY_toprightparens = detail.KEY_toprightparens export KEY_toprightparens const KEY_botrightparens = detail.KEY_botrightparens export KEY_botrightparens const KEY_leftmiddlecurlybrace = detail.KEY_leftmiddlecurlybrace export KEY_leftmiddlecurlybrace const KEY_rightmiddlecurlybrace = detail.KEY_rightmiddlecurlybrace export KEY_rightmiddlecurlybrace const KEY_topleftsummation = detail.KEY_topleftsummation export KEY_topleftsummation const KEY_botleftsummation = detail.KEY_botleftsummation export KEY_botleftsummation const KEY_topvertsummationconnector = detail.KEY_topvertsummationconnector export KEY_topvertsummationconnector const KEY_botvertsummationconnector = detail.KEY_botvertsummationconnector export KEY_botvertsummationconnector const KEY_toprightsummation = detail.KEY_toprightsummation export KEY_toprightsummation const KEY_botrightsummation = detail.KEY_botrightsummation export KEY_botrightsummation const KEY_rightmiddlesummation = detail.KEY_rightmiddlesummation export KEY_rightmiddlesummation const KEY_lessthanequal = detail.KEY_lessthanequal export KEY_lessthanequal const KEY_notequal = detail.KEY_notequal export KEY_notequal const KEY_greaterthanequal = detail.KEY_greaterthanequal export KEY_greaterthanequal const KEY_integral = detail.KEY_integral export KEY_integral const KEY_therefore = detail.KEY_therefore export KEY_therefore const KEY_variation = detail.KEY_variation export KEY_variation const KEY_infinity = detail.KEY_infinity export KEY_infinity const KEY_nabla = detail.KEY_nabla export KEY_nabla const KEY_approximate = detail.KEY_approximate export KEY_approximate const KEY_similarequal = detail.KEY_similarequal export KEY_similarequal const KEY_ifonlyif = detail.KEY_ifonlyif export KEY_ifonlyif const KEY_implies = detail.KEY_implies export KEY_implies const KEY_identical = detail.KEY_identical export KEY_identical const KEY_radical = detail.KEY_radical export KEY_radical const KEY_includedin = detail.KEY_includedin export KEY_includedin const KEY_includes = detail.KEY_includes export KEY_includes const KEY_intersection = detail.KEY_intersection export KEY_intersection const KEY_union = detail.KEY_union export KEY_union const KEY_logicaland = detail.KEY_logicaland export KEY_logicaland const KEY_logicalor = detail.KEY_logicalor export KEY_logicalor const KEY_partialderivative = detail.KEY_partialderivative export KEY_partialderivative const KEY_function = detail.KEY_function export KEY_function const KEY_leftarrow = detail.KEY_leftarrow export KEY_leftarrow const KEY_uparrow = detail.KEY_uparrow export KEY_uparrow const KEY_rightarrow = detail.KEY_rightarrow export KEY_rightarrow const KEY_downarrow = detail.KEY_downarrow export KEY_downarrow const KEY_blank = detail.KEY_blank export KEY_blank const KEY_soliddiamond = detail.KEY_soliddiamond export KEY_soliddiamond const KEY_checkerboard = detail.KEY_checkerboard export KEY_checkerboard const KEY_ht = detail.KEY_ht export KEY_ht const KEY_ff = detail.KEY_ff export KEY_ff const KEY_cr = detail.KEY_cr export KEY_cr const KEY_lf = detail.KEY_lf export KEY_lf const KEY_nl = detail.KEY_nl export KEY_nl const KEY_vt = detail.KEY_vt export KEY_vt const KEY_lowrightcorner = detail.KEY_lowrightcorner export KEY_lowrightcorner const KEY_uprightcorner = detail.KEY_uprightcorner export KEY_uprightcorner const KEY_upleftcorner = detail.KEY_upleftcorner export KEY_upleftcorner const KEY_lowleftcorner = detail.KEY_lowleftcorner export KEY_lowleftcorner const KEY_crossinglines = detail.KEY_crossinglines export KEY_crossinglines const KEY_horizlinescan1 = detail.KEY_horizlinescan1 export KEY_horizlinescan1 const KEY_horizlinescan3 = detail.KEY_horizlinescan3 export KEY_horizlinescan3 const KEY_horizlinescan5 = detail.KEY_horizlinescan5 export KEY_horizlinescan5 const KEY_horizlinescan7 = detail.KEY_horizlinescan7 export KEY_horizlinescan7 const KEY_horizlinescan9 = detail.KEY_horizlinescan9 export KEY_horizlinescan9 const KEY_leftt = detail.KEY_leftt export KEY_leftt const KEY_rightt = detail.KEY_rightt export KEY_rightt const KEY_bott = detail.KEY_bott export KEY_bott const KEY_topt = detail.KEY_topt export KEY_topt const KEY_vertbar = detail.KEY_vertbar export KEY_vertbar const KEY_emspace = detail.KEY_emspace export KEY_emspace const KEY_enspace = detail.KEY_enspace export KEY_enspace const KEY_em3space = detail.KEY_em3space export KEY_em3space const KEY_em4space = detail.KEY_em4space export KEY_em4space const KEY_digitspace = detail.KEY_digitspace export KEY_digitspace const KEY_punctspace = detail.KEY_punctspace export KEY_punctspace const KEY_thinspace = detail.KEY_thinspace export KEY_thinspace const KEY_hairspace = detail.KEY_hairspace export KEY_hairspace const KEY_emdash = detail.KEY_emdash export KEY_emdash const KEY_endash = detail.KEY_endash export KEY_endash const KEY_signifblank = detail.KEY_signifblank export KEY_signifblank const KEY_ellipsis = detail.KEY_ellipsis export KEY_ellipsis const KEY_doubbaselinedot = detail.KEY_doubbaselinedot export KEY_doubbaselinedot const KEY_onethird = detail.KEY_onethird export KEY_onethird const KEY_twothirds = detail.KEY_twothirds export KEY_twothirds const KEY_onefifth = detail.KEY_onefifth export KEY_onefifth const KEY_twofifths = detail.KEY_twofifths export KEY_twofifths const KEY_threefifths = detail.KEY_threefifths export KEY_threefifths const KEY_fourfifths = detail.KEY_fourfifths export KEY_fourfifths const KEY_onesixth = detail.KEY_onesixth export KEY_onesixth const KEY_fivesixths = detail.KEY_fivesixths export KEY_fivesixths const KEY_careof = detail.KEY_careof export KEY_careof const KEY_figdash = detail.KEY_figdash export KEY_figdash const KEY_leftanglebracket = detail.KEY_leftanglebracket export KEY_leftanglebracket const KEY_decimalpoint = detail.KEY_decimalpoint export KEY_decimalpoint const KEY_rightanglebracket = detail.KEY_rightanglebracket export KEY_rightanglebracket const KEY_marker = detail.KEY_marker export KEY_marker const KEY_oneeighth = detail.KEY_oneeighth export KEY_oneeighth const KEY_threeeighths = detail.KEY_threeeighths export KEY_threeeighths const KEY_fiveeighths = detail.KEY_fiveeighths export KEY_fiveeighths const KEY_seveneighths = detail.KEY_seveneighths export KEY_seveneighths const KEY_trademark = detail.KEY_trademark export KEY_trademark const KEY_signaturemark = detail.KEY_signaturemark export KEY_signaturemark const KEY_trademarkincircle = detail.KEY_trademarkincircle export KEY_trademarkincircle const KEY_leftopentriangle = detail.KEY_leftopentriangle export KEY_leftopentriangle const KEY_rightopentriangle = detail.KEY_rightopentriangle export KEY_rightopentriangle const KEY_emopencircle = detail.KEY_emopencircle export KEY_emopencircle const KEY_emopenrectangle = detail.KEY_emopenrectangle export KEY_emopenrectangle const KEY_leftsinglequotemark = detail.KEY_leftsinglequotemark export KEY_leftsinglequotemark const KEY_rightsinglequotemark = detail.KEY_rightsinglequotemark export KEY_rightsinglequotemark const KEY_leftdoublequotemark = detail.KEY_leftdoublequotemark export KEY_leftdoublequotemark const KEY_rightdoublequotemark = detail.KEY_rightdoublequotemark export KEY_rightdoublequotemark const KEY_prescription = detail.KEY_prescription export KEY_prescription const KEY_permille = detail.KEY_permille export KEY_permille const KEY_minutes = detail.KEY_minutes export KEY_minutes const KEY_seconds = detail.KEY_seconds export KEY_seconds const KEY_latincross = detail.KEY_latincross export KEY_latincross const KEY_hexagram = detail.KEY_hexagram export KEY_hexagram const KEY_filledrectbullet = detail.KEY_filledrectbullet export KEY_filledrectbullet const KEY_filledlefttribullet = detail.KEY_filledlefttribullet export KEY_filledlefttribullet const KEY_filledrighttribullet = detail.KEY_filledrighttribullet export KEY_filledrighttribullet const KEY_emfilledcircle = detail.KEY_emfilledcircle export KEY_emfilledcircle const KEY_emfilledrect = detail.KEY_emfilledrect export KEY_emfilledrect const KEY_enopencircbullet = detail.KEY_enopencircbullet export KEY_enopencircbullet const KEY_enopensquarebullet = detail.KEY_enopensquarebullet export KEY_enopensquarebullet const KEY_openrectbullet = detail.KEY_openrectbullet export KEY_openrectbullet const KEY_opentribulletup = detail.KEY_opentribulletup export KEY_opentribulletup const KEY_opentribulletdown = detail.KEY_opentribulletdown export KEY_opentribulletdown const KEY_openstar = detail.KEY_openstar export KEY_openstar const KEY_enfilledcircbullet = detail.KEY_enfilledcircbullet export KEY_enfilledcircbullet const KEY_enfilledsqbullet = detail.KEY_enfilledsqbullet export KEY_enfilledsqbullet const KEY_filledtribulletup = detail.KEY_filledtribulletup export KEY_filledtribulletup const KEY_filledtribulletdown = detail.KEY_filledtribulletdown export KEY_filledtribulletdown const KEY_leftpointer = detail.KEY_leftpointer export KEY_leftpointer const KEY_rightpointer = detail.KEY_rightpointer export KEY_rightpointer const KEY_club = detail.KEY_club export KEY_club const KEY_diamond = detail.KEY_diamond export KEY_diamond const KEY_heart = detail.KEY_heart export KEY_heart const KEY_maltesecross = detail.KEY_maltesecross export KEY_maltesecross const KEY_dagger = detail.KEY_dagger export KEY_dagger const KEY_doubledagger = detail.KEY_doubledagger export KEY_doubledagger const KEY_checkmark = detail.KEY_checkmark export KEY_checkmark const KEY_ballotcross = detail.KEY_ballotcross export KEY_ballotcross const KEY_musicalsharp = detail.KEY_musicalsharp export KEY_musicalsharp const KEY_musicalflat = detail.KEY_musicalflat export KEY_musicalflat const KEY_malesymbol = detail.KEY_malesymbol export KEY_malesymbol const KEY_femalesymbol = detail.KEY_femalesymbol export KEY_femalesymbol const KEY_telephone = detail.KEY_telephone export KEY_telephone const KEY_telephonerecorder = detail.KEY_telephonerecorder export KEY_telephonerecorder const KEY_phonographcopyright = detail.KEY_phonographcopyright export KEY_phonographcopyright const KEY_caret = detail.KEY_caret export KEY_caret const KEY_singlelowquotemark = detail.KEY_singlelowquotemark export KEY_singlelowquotemark const KEY_doublelowquotemark = detail.KEY_doublelowquotemark export KEY_doublelowquotemark const KEY_cursor = detail.KEY_cursor export KEY_cursor const KEY_leftcaret = detail.KEY_leftcaret export KEY_leftcaret const KEY_rightcaret = detail.KEY_rightcaret export KEY_rightcaret const KEY_downcaret = detail.KEY_downcaret export KEY_downcaret const KEY_upcaret = detail.KEY_upcaret export KEY_upcaret const KEY_overbar = detail.KEY_overbar export KEY_overbar const KEY_downtack = detail.KEY_downtack export KEY_downtack const KEY_upshoe = detail.KEY_upshoe export KEY_upshoe const KEY_downstile = detail.KEY_downstile export KEY_downstile const KEY_underbar = detail.KEY_underbar export KEY_underbar const KEY_jot = detail.KEY_jot export KEY_jot const KEY_quad = detail.KEY_quad export KEY_quad const KEY_uptack = detail.KEY_uptack export KEY_uptack const KEY_circle = detail.KEY_circle export KEY_circle const KEY_upstile = detail.KEY_upstile export KEY_upstile const KEY_downshoe = detail.KEY_downshoe export KEY_downshoe const KEY_rightshoe = detail.KEY_rightshoe export KEY_rightshoe const KEY_leftshoe = detail.KEY_leftshoe export KEY_leftshoe const KEY_lefttack = detail.KEY_lefttack export KEY_lefttack const KEY_righttack = detail.KEY_righttack export KEY_righttack const KEY_hebrew_doublelowline = detail.KEY_hebrew_doublelowline export KEY_hebrew_doublelowline const KEY_hebrew_aleph = detail.KEY_hebrew_aleph export KEY_hebrew_aleph const KEY_hebrew_bet = detail.KEY_hebrew_bet export KEY_hebrew_bet const KEY_hebrew_beth = detail.KEY_hebrew_beth export KEY_hebrew_beth const KEY_hebrew_gimel = detail.KEY_hebrew_gimel export KEY_hebrew_gimel const KEY_hebrew_gimmel = detail.KEY_hebrew_gimmel export KEY_hebrew_gimmel const KEY_hebrew_dalet = detail.KEY_hebrew_dalet export KEY_hebrew_dalet const KEY_hebrew_daleth = detail.KEY_hebrew_daleth export KEY_hebrew_daleth const KEY_hebrew_he = detail.KEY_hebrew_he export KEY_hebrew_he const KEY_hebrew_waw = detail.KEY_hebrew_waw export KEY_hebrew_waw const KEY_hebrew_zain = detail.KEY_hebrew_zain export KEY_hebrew_zain const KEY_hebrew_zayin = detail.KEY_hebrew_zayin export KEY_hebrew_zayin const KEY_hebrew_chet = detail.KEY_hebrew_chet export KEY_hebrew_chet const KEY_hebrew_het = detail.KEY_hebrew_het export KEY_hebrew_het const KEY_hebrew_tet = detail.KEY_hebrew_tet export KEY_hebrew_tet const KEY_hebrew_teth = detail.KEY_hebrew_teth export KEY_hebrew_teth const KEY_hebrew_yod = detail.KEY_hebrew_yod export KEY_hebrew_yod const KEY_hebrew_finalkaph = detail.KEY_hebrew_finalkaph export KEY_hebrew_finalkaph const KEY_hebrew_kaph = detail.KEY_hebrew_kaph export KEY_hebrew_kaph const KEY_hebrew_lamed = detail.KEY_hebrew_lamed export KEY_hebrew_lamed const KEY_hebrew_finalmem = detail.KEY_hebrew_finalmem export KEY_hebrew_finalmem const KEY_hebrew_mem = detail.KEY_hebrew_mem export KEY_hebrew_mem const KEY_hebrew_finalnun = detail.KEY_hebrew_finalnun export KEY_hebrew_finalnun const KEY_hebrew_nun = detail.KEY_hebrew_nun export KEY_hebrew_nun const KEY_hebrew_samech = detail.KEY_hebrew_samech export KEY_hebrew_samech const KEY_hebrew_samekh = detail.KEY_hebrew_samekh export KEY_hebrew_samekh const KEY_hebrew_ayin = detail.KEY_hebrew_ayin export KEY_hebrew_ayin const KEY_hebrew_finalpe = detail.KEY_hebrew_finalpe export KEY_hebrew_finalpe const KEY_hebrew_pe = detail.KEY_hebrew_pe export KEY_hebrew_pe const KEY_hebrew_finalzade = detail.KEY_hebrew_finalzade export KEY_hebrew_finalzade const KEY_hebrew_finalzadi = detail.KEY_hebrew_finalzadi export KEY_hebrew_finalzadi const KEY_hebrew_zade = detail.KEY_hebrew_zade export KEY_hebrew_zade const KEY_hebrew_zadi = detail.KEY_hebrew_zadi export KEY_hebrew_zadi const KEY_hebrew_qoph = detail.KEY_hebrew_qoph export KEY_hebrew_qoph const KEY_hebrew_kuf = detail.KEY_hebrew_kuf export KEY_hebrew_kuf const KEY_hebrew_resh = detail.KEY_hebrew_resh export KEY_hebrew_resh const KEY_hebrew_shin = detail.KEY_hebrew_shin export KEY_hebrew_shin const KEY_hebrew_taw = detail.KEY_hebrew_taw export KEY_hebrew_taw const KEY_hebrew_taf = detail.KEY_hebrew_taf export KEY_hebrew_taf const KEY_Hebrew_switch = detail.KEY_Hebrew_switch export KEY_Hebrew_switch const KEY_Thai_kokai = detail.KEY_Thai_kokai export KEY_Thai_kokai const KEY_Thai_khokhai = detail.KEY_Thai_khokhai export KEY_Thai_khokhai const KEY_Thai_khokhuat = detail.KEY_Thai_khokhuat export KEY_Thai_khokhuat const KEY_Thai_khokhwai = detail.KEY_Thai_khokhwai export KEY_Thai_khokhwai const KEY_Thai_khokhon = detail.KEY_Thai_khokhon export KEY_Thai_khokhon const KEY_Thai_khorakhang = detail.KEY_Thai_khorakhang export KEY_Thai_khorakhang const KEY_Thai_ngongu = detail.KEY_Thai_ngongu export KEY_Thai_ngongu const KEY_Thai_chochan = detail.KEY_Thai_chochan export KEY_Thai_chochan const KEY_Thai_choching = detail.KEY_Thai_choching export KEY_Thai_choching const KEY_Thai_chochang = detail.KEY_Thai_chochang export KEY_Thai_chochang const KEY_Thai_soso = detail.KEY_Thai_soso export KEY_Thai_soso const KEY_Thai_chochoe = detail.KEY_Thai_chochoe export KEY_Thai_chochoe const KEY_Thai_yoying = detail.KEY_Thai_yoying export KEY_Thai_yoying const KEY_Thai_dochada = detail.KEY_Thai_dochada export KEY_Thai_dochada const KEY_Thai_topatak = detail.KEY_Thai_topatak export KEY_Thai_topatak const KEY_Thai_thothan = detail.KEY_Thai_thothan export KEY_Thai_thothan const KEY_Thai_thonangmontho = detail.KEY_Thai_thonangmontho export KEY_Thai_thonangmontho const KEY_Thai_thophuthao = detail.KEY_Thai_thophuthao export KEY_Thai_thophuthao const KEY_Thai_nonen = detail.KEY_Thai_nonen export KEY_Thai_nonen const KEY_Thai_dodek = detail.KEY_Thai_dodek export KEY_Thai_dodek const KEY_Thai_totao = detail.KEY_Thai_totao export KEY_Thai_totao const KEY_Thai_thothung = detail.KEY_Thai_thothung export KEY_Thai_thothung const KEY_Thai_thothahan = detail.KEY_Thai_thothahan export KEY_Thai_thothahan const KEY_Thai_thothong = detail.KEY_Thai_thothong export KEY_Thai_thothong const KEY_Thai_nonu = detail.KEY_Thai_nonu export KEY_Thai_nonu const KEY_Thai_bobaimai = detail.KEY_Thai_bobaimai export KEY_Thai_bobaimai const KEY_Thai_popla = detail.KEY_Thai_popla export KEY_Thai_popla const KEY_Thai_phophung = detail.KEY_Thai_phophung export KEY_Thai_phophung const KEY_Thai_fofa = detail.KEY_Thai_fofa export KEY_Thai_fofa const KEY_Thai_phophan = detail.KEY_Thai_phophan export KEY_Thai_phophan const KEY_Thai_fofan = detail.KEY_Thai_fofan export KEY_Thai_fofan const KEY_Thai_phosamphao = detail.KEY_Thai_phosamphao export KEY_Thai_phosamphao const KEY_Thai_moma = detail.KEY_Thai_moma export KEY_Thai_moma const KEY_Thai_yoyak = detail.KEY_Thai_yoyak export KEY_Thai_yoyak const KEY_Thai_rorua = detail.KEY_Thai_rorua export KEY_Thai_rorua const KEY_Thai_ru = detail.KEY_Thai_ru export KEY_Thai_ru const KEY_Thai_loling = detail.KEY_Thai_loling export KEY_Thai_loling const KEY_Thai_lu = detail.KEY_Thai_lu export KEY_Thai_lu const KEY_Thai_wowaen = detail.KEY_Thai_wowaen export KEY_Thai_wowaen const KEY_Thai_sosala = detail.KEY_Thai_sosala export KEY_Thai_sosala const KEY_Thai_sorusi = detail.KEY_Thai_sorusi export KEY_Thai_sorusi const KEY_Thai_sosua = detail.KEY_Thai_sosua export KEY_Thai_sosua const KEY_Thai_hohip = detail.KEY_Thai_hohip export KEY_Thai_hohip const KEY_Thai_lochula = detail.KEY_Thai_lochula export KEY_Thai_lochula const KEY_Thai_oang = detail.KEY_Thai_oang export KEY_Thai_oang const KEY_Thai_honokhuk = detail.KEY_Thai_honokhuk export KEY_Thai_honokhuk const KEY_Thai_paiyannoi = detail.KEY_Thai_paiyannoi export KEY_Thai_paiyannoi const KEY_Thai_saraa = detail.KEY_Thai_saraa export KEY_Thai_saraa const KEY_Thai_maihanakat = detail.KEY_Thai_maihanakat export KEY_Thai_maihanakat const KEY_Thai_saraaa = detail.KEY_Thai_saraaa export KEY_Thai_saraaa const KEY_Thai_saraam = detail.KEY_Thai_saraam export KEY_Thai_saraam const KEY_Thai_sarai = detail.KEY_Thai_sarai export KEY_Thai_sarai const KEY_Thai_saraii = detail.KEY_Thai_saraii export KEY_Thai_saraii const KEY_Thai_saraue = detail.KEY_Thai_saraue export KEY_Thai_saraue const KEY_Thai_sarauee = detail.KEY_Thai_sarauee export KEY_Thai_sarauee const KEY_Thai_sarau = detail.KEY_Thai_sarau export KEY_Thai_sarau const KEY_Thai_sarauu = detail.KEY_Thai_sarauu export KEY_Thai_sarauu const KEY_Thai_phinthu = detail.KEY_Thai_phinthu export KEY_Thai_phinthu const KEY_Thai_maihanakat_maitho = detail.KEY_Thai_maihanakat_maitho export KEY_Thai_maihanakat_maitho const KEY_Thai_baht = detail.KEY_Thai_baht export KEY_Thai_baht const KEY_Thai_sarae = detail.KEY_Thai_sarae export KEY_Thai_sarae const KEY_Thai_saraae = detail.KEY_Thai_saraae export KEY_Thai_saraae const KEY_Thai_sarao = detail.KEY_Thai_sarao export KEY_Thai_sarao const KEY_Thai_saraaimaimuan = detail.KEY_Thai_saraaimaimuan export KEY_Thai_saraaimaimuan const KEY_Thai_saraaimaimalai = detail.KEY_Thai_saraaimaimalai export KEY_Thai_saraaimaimalai const KEY_Thai_lakkhangyao = detail.KEY_Thai_lakkhangyao export KEY_Thai_lakkhangyao const KEY_Thai_maiyamok = detail.KEY_Thai_maiyamok export KEY_Thai_maiyamok const KEY_Thai_maitaikhu = detail.KEY_Thai_maitaikhu export KEY_Thai_maitaikhu const KEY_Thai_maiek = detail.KEY_Thai_maiek export KEY_Thai_maiek const KEY_Thai_maitho = detail.KEY_Thai_maitho export KEY_Thai_maitho const KEY_Thai_maitri = detail.KEY_Thai_maitri export KEY_Thai_maitri const KEY_Thai_maichattawa = detail.KEY_Thai_maichattawa export KEY_Thai_maichattawa const KEY_Thai_thanthakhat = detail.KEY_Thai_thanthakhat export KEY_Thai_thanthakhat const KEY_Thai_nikhahit = detail.KEY_Thai_nikhahit export KEY_Thai_nikhahit const KEY_Thai_leksun = detail.KEY_Thai_leksun export KEY_Thai_leksun const KEY_Thai_leknung = detail.KEY_Thai_leknung export KEY_Thai_leknung const KEY_Thai_leksong = detail.KEY_Thai_leksong export KEY_Thai_leksong const KEY_Thai_leksam = detail.KEY_Thai_leksam export KEY_Thai_leksam const KEY_Thai_leksi = detail.KEY_Thai_leksi export KEY_Thai_leksi const KEY_Thai_lekha = detail.KEY_Thai_lekha export KEY_Thai_lekha const KEY_Thai_lekhok = detail.KEY_Thai_lekhok export KEY_Thai_lekhok const KEY_Thai_lekchet = detail.KEY_Thai_lekchet export KEY_Thai_lekchet const KEY_Thai_lekpaet = detail.KEY_Thai_lekpaet export KEY_Thai_lekpaet const KEY_Thai_lekkao = detail.KEY_Thai_lekkao export KEY_Thai_lekkao const KEY_Hangul = detail.KEY_Hangul export KEY_Hangul const KEY_Hangul_Start = detail.KEY_Hangul_Start export KEY_Hangul_Start const KEY_Hangul_End = detail.KEY_Hangul_End export KEY_Hangul_End const KEY_Hangul_Hanja = detail.KEY_Hangul_Hanja export KEY_Hangul_Hanja const KEY_Hangul_Jamo = detail.KEY_Hangul_Jamo export KEY_Hangul_Jamo const KEY_Hangul_Romaja = detail.KEY_Hangul_Romaja export KEY_Hangul_Romaja const KEY_Hangul_Codeinput = detail.KEY_Hangul_Codeinput export KEY_Hangul_Codeinput const KEY_Hangul_Jeonja = detail.KEY_Hangul_Jeonja export KEY_Hangul_Jeonja const KEY_Hangul_Banja = detail.KEY_Hangul_Banja export KEY_Hangul_Banja const KEY_Hangul_PreHanja = detail.KEY_Hangul_PreHanja export KEY_Hangul_PreHanja const KEY_Hangul_PostHanja = detail.KEY_Hangul_PostHanja export KEY_Hangul_PostHanja const KEY_Hangul_SingleCandidate = detail.KEY_Hangul_SingleCandidate export KEY_Hangul_SingleCandidate const KEY_Hangul_MultipleCandidate = detail.KEY_Hangul_MultipleCandidate export KEY_Hangul_MultipleCandidate const KEY_Hangul_PreviousCandidate = detail.KEY_Hangul_PreviousCandidate export KEY_Hangul_PreviousCandidate const KEY_Hangul_Special = detail.KEY_Hangul_Special export KEY_Hangul_Special const KEY_Hangul_switch = detail.KEY_Hangul_switch export KEY_Hangul_switch const KEY_Hangul_Kiyeog = detail.KEY_Hangul_Kiyeog export KEY_Hangul_Kiyeog const KEY_Hangul_SsangKiyeog = detail.KEY_Hangul_SsangKiyeog export KEY_Hangul_SsangKiyeog const KEY_Hangul_KiyeogSios = detail.KEY_Hangul_KiyeogSios export KEY_Hangul_KiyeogSios const KEY_Hangul_Nieun = detail.KEY_Hangul_Nieun export KEY_Hangul_Nieun const KEY_Hangul_NieunJieuj = detail.KEY_Hangul_NieunJieuj export KEY_Hangul_NieunJieuj const KEY_Hangul_NieunHieuh = detail.KEY_Hangul_NieunHieuh export KEY_Hangul_NieunHieuh const KEY_Hangul_Dikeud = detail.KEY_Hangul_Dikeud export KEY_Hangul_Dikeud const KEY_Hangul_SsangDikeud = detail.KEY_Hangul_SsangDikeud export KEY_Hangul_SsangDikeud const KEY_Hangul_Rieul = detail.KEY_Hangul_Rieul export KEY_Hangul_Rieul const KEY_Hangul_RieulKiyeog = detail.KEY_Hangul_RieulKiyeog export KEY_Hangul_RieulKiyeog const KEY_Hangul_RieulMieum = detail.KEY_Hangul_RieulMieum export KEY_Hangul_RieulMieum const KEY_Hangul_RieulPieub = detail.KEY_Hangul_RieulPieub export KEY_Hangul_RieulPieub const KEY_Hangul_RieulSios = detail.KEY_Hangul_RieulSios export KEY_Hangul_RieulSios const KEY_Hangul_RieulTieut = detail.KEY_Hangul_RieulTieut export KEY_Hangul_RieulTieut const KEY_Hangul_RieulPhieuf = detail.KEY_Hangul_RieulPhieuf export KEY_Hangul_RieulPhieuf const KEY_Hangul_RieulHieuh = detail.KEY_Hangul_RieulHieuh export KEY_Hangul_RieulHieuh const KEY_Hangul_Mieum = detail.KEY_Hangul_Mieum export KEY_Hangul_Mieum const KEY_Hangul_Pieub = detail.KEY_Hangul_Pieub export KEY_Hangul_Pieub const KEY_Hangul_SsangPieub = detail.KEY_Hangul_SsangPieub export KEY_Hangul_SsangPieub const KEY_Hangul_PieubSios = detail.KEY_Hangul_PieubSios export KEY_Hangul_PieubSios const KEY_Hangul_Sios = detail.KEY_Hangul_Sios export KEY_Hangul_Sios const KEY_Hangul_SsangSios = detail.KEY_Hangul_SsangSios export KEY_Hangul_SsangSios const KEY_Hangul_Ieung = detail.KEY_Hangul_Ieung export KEY_Hangul_Ieung const KEY_Hangul_Jieuj = detail.KEY_Hangul_Jieuj export KEY_Hangul_Jieuj const KEY_Hangul_SsangJieuj = detail.KEY_Hangul_SsangJieuj export KEY_Hangul_SsangJieuj const KEY_Hangul_Cieuc = detail.KEY_Hangul_Cieuc export KEY_Hangul_Cieuc const KEY_Hangul_Khieuq = detail.KEY_Hangul_Khieuq export KEY_Hangul_Khieuq const KEY_Hangul_Tieut = detail.KEY_Hangul_Tieut export KEY_Hangul_Tieut const KEY_Hangul_Phieuf = detail.KEY_Hangul_Phieuf export KEY_Hangul_Phieuf const KEY_Hangul_Hieuh = detail.KEY_Hangul_Hieuh export KEY_Hangul_Hieuh const KEY_Hangul_A = detail.KEY_Hangul_A export KEY_Hangul_A const KEY_Hangul_AE = detail.KEY_Hangul_AE export KEY_Hangul_AE const KEY_Hangul_YA = detail.KEY_Hangul_YA export KEY_Hangul_YA const KEY_Hangul_YAE = detail.KEY_Hangul_YAE export KEY_Hangul_YAE const KEY_Hangul_EO = detail.KEY_Hangul_EO export KEY_Hangul_EO const KEY_Hangul_E = detail.KEY_Hangul_E export KEY_Hangul_E const KEY_Hangul_YEO = detail.KEY_Hangul_YEO export KEY_Hangul_YEO const KEY_Hangul_YE = detail.KEY_Hangul_YE export KEY_Hangul_YE const KEY_Hangul_O = detail.KEY_Hangul_O export KEY_Hangul_O const KEY_Hangul_WA = detail.KEY_Hangul_WA export KEY_Hangul_WA const KEY_Hangul_WAE = detail.KEY_Hangul_WAE export KEY_Hangul_WAE const KEY_Hangul_OE = detail.KEY_Hangul_OE export KEY_Hangul_OE const KEY_Hangul_YO = detail.KEY_Hangul_YO export KEY_Hangul_YO const KEY_Hangul_U = detail.KEY_Hangul_U export KEY_Hangul_U const KEY_Hangul_WEO = detail.KEY_Hangul_WEO export KEY_Hangul_WEO const KEY_Hangul_WE = detail.KEY_Hangul_WE export KEY_Hangul_WE const KEY_Hangul_WI = detail.KEY_Hangul_WI export KEY_Hangul_WI const KEY_Hangul_YU = detail.KEY_Hangul_YU export KEY_Hangul_YU const KEY_Hangul_EU = detail.KEY_Hangul_EU export KEY_Hangul_EU const KEY_Hangul_YI = detail.KEY_Hangul_YI export KEY_Hangul_YI const KEY_Hangul_I = detail.KEY_Hangul_I export KEY_Hangul_I const KEY_Hangul_J_Kiyeog = detail.KEY_Hangul_J_Kiyeog export KEY_Hangul_J_Kiyeog const KEY_Hangul_J_SsangKiyeog = detail.KEY_Hangul_J_SsangKiyeog export KEY_Hangul_J_SsangKiyeog const KEY_Hangul_J_KiyeogSios = detail.KEY_Hangul_J_KiyeogSios export KEY_Hangul_J_KiyeogSios const KEY_Hangul_J_Nieun = detail.KEY_Hangul_J_Nieun export KEY_Hangul_J_Nieun const KEY_Hangul_J_NieunJieuj = detail.KEY_Hangul_J_NieunJieuj export KEY_Hangul_J_NieunJieuj const KEY_Hangul_J_NieunHieuh = detail.KEY_Hangul_J_NieunHieuh export KEY_Hangul_J_NieunHieuh const KEY_Hangul_J_Dikeud = detail.KEY_Hangul_J_Dikeud export KEY_Hangul_J_Dikeud const KEY_Hangul_J_Rieul = detail.KEY_Hangul_J_Rieul export KEY_Hangul_J_Rieul const KEY_Hangul_J_RieulKiyeog = detail.KEY_Hangul_J_RieulKiyeog export KEY_Hangul_J_RieulKiyeog const KEY_Hangul_J_RieulMieum = detail.KEY_Hangul_J_RieulMieum export KEY_Hangul_J_RieulMieum const KEY_Hangul_J_RieulPieub = detail.KEY_Hangul_J_RieulPieub export KEY_Hangul_J_RieulPieub const KEY_Hangul_J_RieulSios = detail.KEY_Hangul_J_RieulSios export KEY_Hangul_J_RieulSios const KEY_Hangul_J_RieulTieut = detail.KEY_Hangul_J_RieulTieut export KEY_Hangul_J_RieulTieut const KEY_Hangul_J_RieulPhieuf = detail.KEY_Hangul_J_RieulPhieuf export KEY_Hangul_J_RieulPhieuf const KEY_Hangul_J_RieulHieuh = detail.KEY_Hangul_J_RieulHieuh export KEY_Hangul_J_RieulHieuh const KEY_Hangul_J_Mieum = detail.KEY_Hangul_J_Mieum export KEY_Hangul_J_Mieum const KEY_Hangul_J_Pieub = detail.KEY_Hangul_J_Pieub export KEY_Hangul_J_Pieub const KEY_Hangul_J_PieubSios = detail.KEY_Hangul_J_PieubSios export KEY_Hangul_J_PieubSios const KEY_Hangul_J_Sios = detail.KEY_Hangul_J_Sios export KEY_Hangul_J_Sios const KEY_Hangul_J_SsangSios = detail.KEY_Hangul_J_SsangSios export KEY_Hangul_J_SsangSios const KEY_Hangul_J_Ieung = detail.KEY_Hangul_J_Ieung export KEY_Hangul_J_Ieung const KEY_Hangul_J_Jieuj = detail.KEY_Hangul_J_Jieuj export KEY_Hangul_J_Jieuj const KEY_Hangul_J_Cieuc = detail.KEY_Hangul_J_Cieuc export KEY_Hangul_J_Cieuc const KEY_Hangul_J_Khieuq = detail.KEY_Hangul_J_Khieuq export KEY_Hangul_J_Khieuq const KEY_Hangul_J_Tieut = detail.KEY_Hangul_J_Tieut export KEY_Hangul_J_Tieut const KEY_Hangul_J_Phieuf = detail.KEY_Hangul_J_Phieuf export KEY_Hangul_J_Phieuf const KEY_Hangul_J_Hieuh = detail.KEY_Hangul_J_Hieuh export KEY_Hangul_J_Hieuh const KEY_Hangul_RieulYeorinHieuh = detail.KEY_Hangul_RieulYeorinHieuh export KEY_Hangul_RieulYeorinHieuh const KEY_Hangul_SunkyeongeumMieum = detail.KEY_Hangul_SunkyeongeumMieum export KEY_Hangul_SunkyeongeumMieum const KEY_Hangul_SunkyeongeumPieub = detail.KEY_Hangul_SunkyeongeumPieub export KEY_Hangul_SunkyeongeumPieub const KEY_Hangul_PanSios = detail.KEY_Hangul_PanSios export KEY_Hangul_PanSios const KEY_Hangul_KkogjiDalrinIeung = detail.KEY_Hangul_KkogjiDalrinIeung export KEY_Hangul_KkogjiDalrinIeung const KEY_Hangul_SunkyeongeumPhieuf = detail.KEY_Hangul_SunkyeongeumPhieuf export KEY_Hangul_SunkyeongeumPhieuf const KEY_Hangul_YeorinHieuh = detail.KEY_Hangul_YeorinHieuh export KEY_Hangul_YeorinHieuh const KEY_Hangul_AraeA = detail.KEY_Hangul_AraeA export KEY_Hangul_AraeA const KEY_Hangul_AraeAE = detail.KEY_Hangul_AraeAE export KEY_Hangul_AraeAE const KEY_Hangul_J_PanSios = detail.KEY_Hangul_J_PanSios export KEY_Hangul_J_PanSios const KEY_Hangul_J_KkogjiDalrinIeung = detail.KEY_Hangul_J_KkogjiDalrinIeung export KEY_Hangul_J_KkogjiDalrinIeung const KEY_Hangul_J_YeorinHieuh = detail.KEY_Hangul_J_YeorinHieuh export KEY_Hangul_J_YeorinHieuh const KEY_Korean_Won = detail.KEY_Korean_Won export KEY_Korean_Won const KEY_Armenian_ligature_ew = detail.KEY_Armenian_ligature_ew export KEY_Armenian_ligature_ew const KEY_Armenian_full_stop = detail.KEY_Armenian_full_stop export KEY_Armenian_full_stop const KEY_Armenian_verjaket = detail.KEY_Armenian_verjaket export KEY_Armenian_verjaket const KEY_Armenian_separation_mark = detail.KEY_Armenian_separation_mark export KEY_Armenian_separation_mark const KEY_Armenian_but = detail.KEY_Armenian_but export KEY_Armenian_but const KEY_Armenian_hyphen = detail.KEY_Armenian_hyphen export KEY_Armenian_hyphen const KEY_Armenian_yentamna = detail.KEY_Armenian_yentamna export KEY_Armenian_yentamna const KEY_Armenian_exclam = detail.KEY_Armenian_exclam export KEY_Armenian_exclam const KEY_Armenian_amanak = detail.KEY_Armenian_amanak export KEY_Armenian_amanak const KEY_Armenian_accent = detail.KEY_Armenian_accent export KEY_Armenian_accent const KEY_Armenian_shesht = detail.KEY_Armenian_shesht export KEY_Armenian_shesht const KEY_Armenian_question = detail.KEY_Armenian_question export KEY_Armenian_question const KEY_Armenian_paruyk = detail.KEY_Armenian_paruyk export KEY_Armenian_paruyk const KEY_Armenian_AYB = detail.KEY_Armenian_AYB export KEY_Armenian_AYB const KEY_Armenian_ayb = detail.KEY_Armenian_ayb export KEY_Armenian_ayb const KEY_Armenian_BEN = detail.KEY_Armenian_BEN export KEY_Armenian_BEN const KEY_Armenian_ben = detail.KEY_Armenian_ben export KEY_Armenian_ben const KEY_Armenian_GIM = detail.KEY_Armenian_GIM export KEY_Armenian_GIM const KEY_Armenian_gim = detail.KEY_Armenian_gim export KEY_Armenian_gim const KEY_Armenian_DA = detail.KEY_Armenian_DA export KEY_Armenian_DA const KEY_Armenian_da = detail.KEY_Armenian_da export KEY_Armenian_da const KEY_Armenian_YECH = detail.KEY_Armenian_YECH export KEY_Armenian_YECH const KEY_Armenian_yech = detail.KEY_Armenian_yech export KEY_Armenian_yech const KEY_Armenian_ZA = detail.KEY_Armenian_ZA export KEY_Armenian_ZA const KEY_Armenian_za = detail.KEY_Armenian_za export KEY_Armenian_za const KEY_Armenian_E = detail.KEY_Armenian_E export KEY_Armenian_E const KEY_Armenian_e = detail.KEY_Armenian_e export KEY_Armenian_e const KEY_Armenian_AT = detail.KEY_Armenian_AT export KEY_Armenian_AT const KEY_Armenian_at = detail.KEY_Armenian_at export KEY_Armenian_at const KEY_Armenian_TO = detail.KEY_Armenian_TO export KEY_Armenian_TO const KEY_Armenian_to = detail.KEY_Armenian_to export KEY_Armenian_to const KEY_Armenian_ZHE = detail.KEY_Armenian_ZHE export KEY_Armenian_ZHE const KEY_Armenian_zhe = detail.KEY_Armenian_zhe export KEY_Armenian_zhe const KEY_Armenian_INI = detail.KEY_Armenian_INI export KEY_Armenian_INI const KEY_Armenian_ini = detail.KEY_Armenian_ini export KEY_Armenian_ini const KEY_Armenian_LYUN = detail.KEY_Armenian_LYUN export KEY_Armenian_LYUN const KEY_Armenian_lyun = detail.KEY_Armenian_lyun export KEY_Armenian_lyun const KEY_Armenian_KHE = detail.KEY_Armenian_KHE export KEY_Armenian_KHE const KEY_Armenian_khe = detail.KEY_Armenian_khe export KEY_Armenian_khe const KEY_Armenian_TSA = detail.KEY_Armenian_TSA export KEY_Armenian_TSA const KEY_Armenian_tsa = detail.KEY_Armenian_tsa export KEY_Armenian_tsa const KEY_Armenian_KEN = detail.KEY_Armenian_KEN export KEY_Armenian_KEN const KEY_Armenian_ken = detail.KEY_Armenian_ken export KEY_Armenian_ken const KEY_Armenian_HO = detail.KEY_Armenian_HO export KEY_Armenian_HO const KEY_Armenian_ho = detail.KEY_Armenian_ho export KEY_Armenian_ho const KEY_Armenian_DZA = detail.KEY_Armenian_DZA export KEY_Armenian_DZA const KEY_Armenian_dza = detail.KEY_Armenian_dza export KEY_Armenian_dza const KEY_Armenian_GHAT = detail.KEY_Armenian_GHAT export KEY_Armenian_GHAT const KEY_Armenian_ghat = detail.KEY_Armenian_ghat export KEY_Armenian_ghat const KEY_Armenian_TCHE = detail.KEY_Armenian_TCHE export KEY_Armenian_TCHE const KEY_Armenian_tche = detail.KEY_Armenian_tche export KEY_Armenian_tche const KEY_Armenian_MEN = detail.KEY_Armenian_MEN export KEY_Armenian_MEN const KEY_Armenian_men = detail.KEY_Armenian_men export KEY_Armenian_men const KEY_Armenian_HI = detail.KEY_Armenian_HI export KEY_Armenian_HI const KEY_Armenian_hi = detail.KEY_Armenian_hi export KEY_Armenian_hi const KEY_Armenian_NU = detail.KEY_Armenian_NU export KEY_Armenian_NU const KEY_Armenian_nu = detail.KEY_Armenian_nu export KEY_Armenian_nu const KEY_Armenian_SHA = detail.KEY_Armenian_SHA export KEY_Armenian_SHA const KEY_Armenian_sha = detail.KEY_Armenian_sha export KEY_Armenian_sha const KEY_Armenian_VO = detail.KEY_Armenian_VO export KEY_Armenian_VO const KEY_Armenian_vo = detail.KEY_Armenian_vo export KEY_Armenian_vo const KEY_Armenian_CHA = detail.KEY_Armenian_CHA export KEY_Armenian_CHA const KEY_Armenian_cha = detail.KEY_Armenian_cha export KEY_Armenian_cha const KEY_Armenian_PE = detail.KEY_Armenian_PE export KEY_Armenian_PE const KEY_Armenian_pe = detail.KEY_Armenian_pe export KEY_Armenian_pe const KEY_Armenian_JE = detail.KEY_Armenian_JE export KEY_Armenian_JE const KEY_Armenian_je = detail.KEY_Armenian_je export KEY_Armenian_je const KEY_Armenian_RA = detail.KEY_Armenian_RA export KEY_Armenian_RA const KEY_Armenian_ra = detail.KEY_Armenian_ra export KEY_Armenian_ra const KEY_Armenian_SE = detail.KEY_Armenian_SE export KEY_Armenian_SE const KEY_Armenian_se = detail.KEY_Armenian_se export KEY_Armenian_se const KEY_Armenian_VEV = detail.KEY_Armenian_VEV export KEY_Armenian_VEV const KEY_Armenian_vev = detail.KEY_Armenian_vev export KEY_Armenian_vev const KEY_Armenian_TYUN = detail.KEY_Armenian_TYUN export KEY_Armenian_TYUN const KEY_Armenian_tyun = detail.KEY_Armenian_tyun export KEY_Armenian_tyun const KEY_Armenian_RE = detail.KEY_Armenian_RE export KEY_Armenian_RE const KEY_Armenian_re = detail.KEY_Armenian_re export KEY_Armenian_re const KEY_Armenian_TSO = detail.KEY_Armenian_TSO export KEY_Armenian_TSO const KEY_Armenian_tso = detail.KEY_Armenian_tso export KEY_Armenian_tso const KEY_Armenian_VYUN = detail.KEY_Armenian_VYUN export KEY_Armenian_VYUN const KEY_Armenian_vyun = detail.KEY_Armenian_vyun export KEY_Armenian_vyun const KEY_Armenian_PYUR = detail.KEY_Armenian_PYUR export KEY_Armenian_PYUR const KEY_Armenian_pyur = detail.KEY_Armenian_pyur export KEY_Armenian_pyur const KEY_Armenian_KE = detail.KEY_Armenian_KE export KEY_Armenian_KE const KEY_Armenian_ke = detail.KEY_Armenian_ke export KEY_Armenian_ke const KEY_Armenian_O = detail.KEY_Armenian_O export KEY_Armenian_O const KEY_Armenian_o = detail.KEY_Armenian_o export KEY_Armenian_o const KEY_Armenian_FE = detail.KEY_Armenian_FE export KEY_Armenian_FE const KEY_Armenian_fe = detail.KEY_Armenian_fe export KEY_Armenian_fe const KEY_Armenian_apostrophe = detail.KEY_Armenian_apostrophe export KEY_Armenian_apostrophe const KEY_Georgian_an = detail.KEY_Georgian_an export KEY_Georgian_an const KEY_Georgian_ban = detail.KEY_Georgian_ban export KEY_Georgian_ban const KEY_Georgian_gan = detail.KEY_Georgian_gan export KEY_Georgian_gan const KEY_Georgian_don = detail.KEY_Georgian_don export KEY_Georgian_don const KEY_Georgian_en = detail.KEY_Georgian_en export KEY_Georgian_en const KEY_Georgian_vin = detail.KEY_Georgian_vin export KEY_Georgian_vin const KEY_Georgian_zen = detail.KEY_Georgian_zen export KEY_Georgian_zen const KEY_Georgian_tan = detail.KEY_Georgian_tan export KEY_Georgian_tan const KEY_Georgian_in = detail.KEY_Georgian_in export KEY_Georgian_in const KEY_Georgian_kan = detail.KEY_Georgian_kan export KEY_Georgian_kan const KEY_Georgian_las = detail.KEY_Georgian_las export KEY_Georgian_las const KEY_Georgian_man = detail.KEY_Georgian_man export KEY_Georgian_man const KEY_Georgian_nar = detail.KEY_Georgian_nar export KEY_Georgian_nar const KEY_Georgian_on = detail.KEY_Georgian_on export KEY_Georgian_on const KEY_Georgian_par = detail.KEY_Georgian_par export KEY_Georgian_par const KEY_Georgian_zhar = detail.KEY_Georgian_zhar export KEY_Georgian_zhar const KEY_Georgian_rae = detail.KEY_Georgian_rae export KEY_Georgian_rae const KEY_Georgian_san = detail.KEY_Georgian_san export KEY_Georgian_san const KEY_Georgian_tar = detail.KEY_Georgian_tar export KEY_Georgian_tar const KEY_Georgian_un = detail.KEY_Georgian_un export KEY_Georgian_un const KEY_Georgian_phar = detail.KEY_Georgian_phar export KEY_Georgian_phar const KEY_Georgian_khar = detail.KEY_Georgian_khar export KEY_Georgian_khar const KEY_Georgian_ghan = detail.KEY_Georgian_ghan export KEY_Georgian_ghan const KEY_Georgian_qar = detail.KEY_Georgian_qar export KEY_Georgian_qar const KEY_Georgian_shin = detail.KEY_Georgian_shin export KEY_Georgian_shin const KEY_Georgian_chin = detail.KEY_Georgian_chin export KEY_Georgian_chin const KEY_Georgian_can = detail.KEY_Georgian_can export KEY_Georgian_can const KEY_Georgian_jil = detail.KEY_Georgian_jil export KEY_Georgian_jil const KEY_Georgian_cil = detail.KEY_Georgian_cil export KEY_Georgian_cil const KEY_Georgian_char = detail.KEY_Georgian_char export KEY_Georgian_char const KEY_Georgian_xan = detail.KEY_Georgian_xan export KEY_Georgian_xan const KEY_Georgian_jhan = detail.KEY_Georgian_jhan export KEY_Georgian_jhan const KEY_Georgian_hae = detail.KEY_Georgian_hae export KEY_Georgian_hae const KEY_Georgian_he = detail.KEY_Georgian_he export KEY_Georgian_he const KEY_Georgian_hie = detail.KEY_Georgian_hie export KEY_Georgian_hie const KEY_Georgian_we = detail.KEY_Georgian_we export KEY_Georgian_we const KEY_Georgian_har = detail.KEY_Georgian_har export KEY_Georgian_har const KEY_Georgian_hoe = detail.KEY_Georgian_hoe export KEY_Georgian_hoe const KEY_Georgian_fi = detail.KEY_Georgian_fi export KEY_Georgian_fi const KEY_Xabovedot = detail.KEY_Xabovedot export KEY_Xabovedot const KEY_Ibreve = detail.KEY_Ibreve export KEY_Ibreve const KEY_Zstroke = detail.KEY_Zstroke export KEY_Zstroke const KEY_Gcaron = detail.KEY_Gcaron export KEY_Gcaron const KEY_Ocaron = detail.KEY_Ocaron export KEY_Ocaron const KEY_Obarred = detail.KEY_Obarred export KEY_Obarred const KEY_xabovedot = detail.KEY_xabovedot export KEY_xabovedot const KEY_ibreve = detail.KEY_ibreve export KEY_ibreve const KEY_zstroke = detail.KEY_zstroke export KEY_zstroke const KEY_gcaron = detail.KEY_gcaron export KEY_gcaron const KEY_ocaron = detail.KEY_ocaron export KEY_ocaron const KEY_obarred = detail.KEY_obarred export KEY_obarred const KEY_SCHWA = detail.KEY_SCHWA export KEY_SCHWA const KEY_schwa = detail.KEY_schwa export KEY_schwa const KEY_EZH = detail.KEY_EZH export KEY_EZH const KEY_ezh = detail.KEY_ezh export KEY_ezh const KEY_Lbelowdot = detail.KEY_Lbelowdot export KEY_Lbelowdot const KEY_lbelowdot = detail.KEY_lbelowdot export KEY_lbelowdot const KEY_Abelowdot = detail.KEY_Abelowdot export KEY_Abelowdot const KEY_abelowdot = detail.KEY_abelowdot export KEY_abelowdot const KEY_Ahook = detail.KEY_Ahook export KEY_Ahook const KEY_ahook = detail.KEY_ahook export KEY_ahook const KEY_Acircumflexacute = detail.KEY_Acircumflexacute export KEY_Acircumflexacute const KEY_acircumflexacute = detail.KEY_acircumflexacute export KEY_acircumflexacute const KEY_Acircumflexgrave = detail.KEY_Acircumflexgrave export KEY_Acircumflexgrave const KEY_acircumflexgrave = detail.KEY_acircumflexgrave export KEY_acircumflexgrave const KEY_Acircumflexhook = detail.KEY_Acircumflexhook export KEY_Acircumflexhook const KEY_acircumflexhook = detail.KEY_acircumflexhook export KEY_acircumflexhook const KEY_Acircumflextilde = detail.KEY_Acircumflextilde export KEY_Acircumflextilde const KEY_acircumflextilde = detail.KEY_acircumflextilde export KEY_acircumflextilde const KEY_Acircumflexbelowdot = detail.KEY_Acircumflexbelowdot export KEY_Acircumflexbelowdot const KEY_acircumflexbelowdot = detail.KEY_acircumflexbelowdot export KEY_acircumflexbelowdot const KEY_Abreveacute = detail.KEY_Abreveacute export KEY_Abreveacute const KEY_abreveacute = detail.KEY_abreveacute export KEY_abreveacute const KEY_Abrevegrave = detail.KEY_Abrevegrave export KEY_Abrevegrave const KEY_abrevegrave = detail.KEY_abrevegrave export KEY_abrevegrave const KEY_Abrevehook = detail.KEY_Abrevehook export KEY_Abrevehook const KEY_abrevehook = detail.KEY_abrevehook export KEY_abrevehook const KEY_Abrevetilde = detail.KEY_Abrevetilde export KEY_Abrevetilde const KEY_abrevetilde = detail.KEY_abrevetilde export KEY_abrevetilde const KEY_Abrevebelowdot = detail.KEY_Abrevebelowdot export KEY_Abrevebelowdot const KEY_abrevebelowdot = detail.KEY_abrevebelowdot export KEY_abrevebelowdot const KEY_Ebelowdot = detail.KEY_Ebelowdot export KEY_Ebelowdot const KEY_ebelowdot = detail.KEY_ebelowdot export KEY_ebelowdot const KEY_Ehook = detail.KEY_Ehook export KEY_Ehook const KEY_ehook = detail.KEY_ehook export KEY_ehook const KEY_Etilde = detail.KEY_Etilde export KEY_Etilde const KEY_etilde = detail.KEY_etilde export KEY_etilde const KEY_Ecircumflexacute = detail.KEY_Ecircumflexacute export KEY_Ecircumflexacute const KEY_ecircumflexacute = detail.KEY_ecircumflexacute export KEY_ecircumflexacute const KEY_Ecircumflexgrave = detail.KEY_Ecircumflexgrave export KEY_Ecircumflexgrave const KEY_ecircumflexgrave = detail.KEY_ecircumflexgrave export KEY_ecircumflexgrave const KEY_Ecircumflexhook = detail.KEY_Ecircumflexhook export KEY_Ecircumflexhook const KEY_ecircumflexhook = detail.KEY_ecircumflexhook export KEY_ecircumflexhook const KEY_Ecircumflextilde = detail.KEY_Ecircumflextilde export KEY_Ecircumflextilde const KEY_ecircumflextilde = detail.KEY_ecircumflextilde export KEY_ecircumflextilde const KEY_Ecircumflexbelowdot = detail.KEY_Ecircumflexbelowdot export KEY_Ecircumflexbelowdot const KEY_ecircumflexbelowdot = detail.KEY_ecircumflexbelowdot export KEY_ecircumflexbelowdot const KEY_Ihook = detail.KEY_Ihook export KEY_Ihook const KEY_ihook = detail.KEY_ihook export KEY_ihook const KEY_Ibelowdot = detail.KEY_Ibelowdot export KEY_Ibelowdot const KEY_ibelowdot = detail.KEY_ibelowdot export KEY_ibelowdot const KEY_Obelowdot = detail.KEY_Obelowdot export KEY_Obelowdot const KEY_obelowdot = detail.KEY_obelowdot export KEY_obelowdot const KEY_Ohook = detail.KEY_Ohook export KEY_Ohook const KEY_ohook = detail.KEY_ohook export KEY_ohook const KEY_Ocircumflexacute = detail.KEY_Ocircumflexacute export KEY_Ocircumflexacute const KEY_ocircumflexacute = detail.KEY_ocircumflexacute export KEY_ocircumflexacute const KEY_Ocircumflexgrave = detail.KEY_Ocircumflexgrave export KEY_Ocircumflexgrave const KEY_ocircumflexgrave = detail.KEY_ocircumflexgrave export KEY_ocircumflexgrave const KEY_Ocircumflexhook = detail.KEY_Ocircumflexhook export KEY_Ocircumflexhook const KEY_ocircumflexhook = detail.KEY_ocircumflexhook export KEY_ocircumflexhook const KEY_Ocircumflextilde = detail.KEY_Ocircumflextilde export KEY_Ocircumflextilde const KEY_ocircumflextilde = detail.KEY_ocircumflextilde export KEY_ocircumflextilde const KEY_Ocircumflexbelowdot = detail.KEY_Ocircumflexbelowdot export KEY_Ocircumflexbelowdot const KEY_ocircumflexbelowdot = detail.KEY_ocircumflexbelowdot export KEY_ocircumflexbelowdot const KEY_Ohornacute = detail.KEY_Ohornacute export KEY_Ohornacute const KEY_ohornacute = detail.KEY_ohornacute export KEY_ohornacute const KEY_Ohorngrave = detail.KEY_Ohorngrave export KEY_Ohorngrave const KEY_ohorngrave = detail.KEY_ohorngrave export KEY_ohorngrave const KEY_Ohornhook = detail.KEY_Ohornhook export KEY_Ohornhook const KEY_ohornhook = detail.KEY_ohornhook export KEY_ohornhook const KEY_Ohorntilde = detail.KEY_Ohorntilde export KEY_Ohorntilde const KEY_ohorntilde = detail.KEY_ohorntilde export KEY_ohorntilde const KEY_Ohornbelowdot = detail.KEY_Ohornbelowdot export KEY_Ohornbelowdot const KEY_ohornbelowdot = detail.KEY_ohornbelowdot export KEY_ohornbelowdot const KEY_Ubelowdot = detail.KEY_Ubelowdot export KEY_Ubelowdot const KEY_ubelowdot = detail.KEY_ubelowdot export KEY_ubelowdot const KEY_Uhook = detail.KEY_Uhook export KEY_Uhook const KEY_uhook = detail.KEY_uhook export KEY_uhook const KEY_Uhornacute = detail.KEY_Uhornacute export KEY_Uhornacute const KEY_uhornacute = detail.KEY_uhornacute export KEY_uhornacute const KEY_Uhorngrave = detail.KEY_Uhorngrave export KEY_Uhorngrave const KEY_uhorngrave = detail.KEY_uhorngrave export KEY_uhorngrave const KEY_Uhornhook = detail.KEY_Uhornhook export KEY_Uhornhook const KEY_uhornhook = detail.KEY_uhornhook export KEY_uhornhook const KEY_Uhorntilde = detail.KEY_Uhorntilde export KEY_Uhorntilde const KEY_uhorntilde = detail.KEY_uhorntilde export KEY_uhorntilde const KEY_Uhornbelowdot = detail.KEY_Uhornbelowdot export KEY_Uhornbelowdot const KEY_uhornbelowdot = detail.KEY_uhornbelowdot export KEY_uhornbelowdot const KEY_Ybelowdot = detail.KEY_Ybelowdot export KEY_Ybelowdot const KEY_ybelowdot = detail.KEY_ybelowdot export KEY_ybelowdot const KEY_Yhook = detail.KEY_Yhook export KEY_Yhook const KEY_yhook = detail.KEY_yhook export KEY_yhook const KEY_Ytilde = detail.KEY_Ytilde export KEY_Ytilde const KEY_ytilde = detail.KEY_ytilde export KEY_ytilde const KEY_Ohorn = detail.KEY_Ohorn export KEY_Ohorn const KEY_ohorn = detail.KEY_ohorn export KEY_ohorn const KEY_Uhorn = detail.KEY_Uhorn export KEY_Uhorn const KEY_uhorn = detail.KEY_uhorn export KEY_uhorn const KEY_EcuSign = detail.KEY_EcuSign export KEY_EcuSign const KEY_ColonSign = detail.KEY_ColonSign export KEY_ColonSign const KEY_CruzeiroSign = detail.KEY_CruzeiroSign export KEY_CruzeiroSign const KEY_FFrancSign = detail.KEY_FFrancSign export KEY_FFrancSign const KEY_LiraSign = detail.KEY_LiraSign export KEY_LiraSign const KEY_MillSign = detail.KEY_MillSign export KEY_MillSign const KEY_NairaSign = detail.KEY_NairaSign export KEY_NairaSign const KEY_PesetaSign = detail.KEY_PesetaSign export KEY_PesetaSign const KEY_RupeeSign = detail.KEY_RupeeSign export KEY_RupeeSign const KEY_WonSign = detail.KEY_WonSign export KEY_WonSign const KEY_NewSheqelSign = detail.KEY_NewSheqelSign export KEY_NewSheqelSign const KEY_DongSign = detail.KEY_DongSign export KEY_DongSign const KEY_EuroSign = detail.KEY_EuroSign export KEY_EuroSign const KEY_zerosuperior = detail.KEY_zerosuperior export KEY_zerosuperior const KEY_foursuperior = detail.KEY_foursuperior export KEY_foursuperior const KEY_fivesuperior = detail.KEY_fivesuperior export KEY_fivesuperior const KEY_sixsuperior = detail.KEY_sixsuperior export KEY_sixsuperior const KEY_sevensuperior = detail.KEY_sevensuperior export KEY_sevensuperior const KEY_eightsuperior = detail.KEY_eightsuperior export KEY_eightsuperior const KEY_ninesuperior = detail.KEY_ninesuperior export KEY_ninesuperior const KEY_zerosubscript = detail.KEY_zerosubscript export KEY_zerosubscript const KEY_onesubscript = detail.KEY_onesubscript export KEY_onesubscript const KEY_twosubscript = detail.KEY_twosubscript export KEY_twosubscript const KEY_threesubscript = detail.KEY_threesubscript export KEY_threesubscript const KEY_foursubscript = detail.KEY_foursubscript export KEY_foursubscript const KEY_fivesubscript = detail.KEY_fivesubscript export KEY_fivesubscript const KEY_sixsubscript = detail.KEY_sixsubscript export KEY_sixsubscript const KEY_sevensubscript = detail.KEY_sevensubscript export KEY_sevensubscript const KEY_eightsubscript = detail.KEY_eightsubscript export KEY_eightsubscript const KEY_ninesubscript = detail.KEY_ninesubscript export KEY_ninesubscript const KEY_partdifferential = detail.KEY_partdifferential export KEY_partdifferential const KEY_emptyset = detail.KEY_emptyset export KEY_emptyset const KEY_elementof = detail.KEY_elementof export KEY_elementof const KEY_notelementof = detail.KEY_notelementof export KEY_notelementof const KEY_containsas = detail.KEY_containsas export KEY_containsas const KEY_squareroot = detail.KEY_squareroot export KEY_squareroot const KEY_cuberoot = detail.KEY_cuberoot export KEY_cuberoot const KEY_fourthroot = detail.KEY_fourthroot export KEY_fourthroot const KEY_dintegral = detail.KEY_dintegral export KEY_dintegral const KEY_tintegral = detail.KEY_tintegral export KEY_tintegral const KEY_because = detail.KEY_because export KEY_because const KEY_approxeq = detail.KEY_approxeq export KEY_approxeq const KEY_notapproxeq = detail.KEY_notapproxeq export KEY_notapproxeq const KEY_notidentical = detail.KEY_notidentical export KEY_notidentical const KEY_stricteq = detail.KEY_stricteq export KEY_stricteq const KEY_braille_dot_1 = detail.KEY_braille_dot_1 export KEY_braille_dot_1 const KEY_braille_dot_2 = detail.KEY_braille_dot_2 export KEY_braille_dot_2 const KEY_braille_dot_3 = detail.KEY_braille_dot_3 export KEY_braille_dot_3 const KEY_braille_dot_4 = detail.KEY_braille_dot_4 export KEY_braille_dot_4 const KEY_braille_dot_5 = detail.KEY_braille_dot_5 export KEY_braille_dot_5 const KEY_braille_dot_6 = detail.KEY_braille_dot_6 export KEY_braille_dot_6 const KEY_braille_dot_7 = detail.KEY_braille_dot_7 export KEY_braille_dot_7 const KEY_braille_dot_8 = detail.KEY_braille_dot_8 export KEY_braille_dot_8 const KEY_braille_dot_9 = detail.KEY_braille_dot_9 export KEY_braille_dot_9 const KEY_braille_dot_10 = detail.KEY_braille_dot_10 export KEY_braille_dot_10 const KEY_braille_blank = detail.KEY_braille_blank export KEY_braille_blank const KEY_braille_dots_1 = detail.KEY_braille_dots_1 export KEY_braille_dots_1 const KEY_braille_dots_2 = detail.KEY_braille_dots_2 export KEY_braille_dots_2 const KEY_braille_dots_12 = detail.KEY_braille_dots_12 export KEY_braille_dots_12 const KEY_braille_dots_3 = detail.KEY_braille_dots_3 export KEY_braille_dots_3 const KEY_braille_dots_13 = detail.KEY_braille_dots_13 export KEY_braille_dots_13 const KEY_braille_dots_23 = detail.KEY_braille_dots_23 export KEY_braille_dots_23 const KEY_braille_dots_123 = detail.KEY_braille_dots_123 export KEY_braille_dots_123 const KEY_braille_dots_4 = detail.KEY_braille_dots_4 export KEY_braille_dots_4 const KEY_braille_dots_14 = detail.KEY_braille_dots_14 export KEY_braille_dots_14 const KEY_braille_dots_24 = detail.KEY_braille_dots_24 export KEY_braille_dots_24 const KEY_braille_dots_124 = detail.KEY_braille_dots_124 export KEY_braille_dots_124 const KEY_braille_dots_34 = detail.KEY_braille_dots_34 export KEY_braille_dots_34 const KEY_braille_dots_134 = detail.KEY_braille_dots_134 export KEY_braille_dots_134 const KEY_braille_dots_234 = detail.KEY_braille_dots_234 export KEY_braille_dots_234 const KEY_braille_dots_1234 = detail.KEY_braille_dots_1234 export KEY_braille_dots_1234 const KEY_braille_dots_5 = detail.KEY_braille_dots_5 export KEY_braille_dots_5 const KEY_braille_dots_15 = detail.KEY_braille_dots_15 export KEY_braille_dots_15 const KEY_braille_dots_25 = detail.KEY_braille_dots_25 export KEY_braille_dots_25 const KEY_braille_dots_125 = detail.KEY_braille_dots_125 export KEY_braille_dots_125 const KEY_braille_dots_35 = detail.KEY_braille_dots_35 export KEY_braille_dots_35 const KEY_braille_dots_135 = detail.KEY_braille_dots_135 export KEY_braille_dots_135 const KEY_braille_dots_235 = detail.KEY_braille_dots_235 export KEY_braille_dots_235 const KEY_braille_dots_1235 = detail.KEY_braille_dots_1235 export KEY_braille_dots_1235 const KEY_braille_dots_45 = detail.KEY_braille_dots_45 export KEY_braille_dots_45 const KEY_braille_dots_145 = detail.KEY_braille_dots_145 export KEY_braille_dots_145 const KEY_braille_dots_245 = detail.KEY_braille_dots_245 export KEY_braille_dots_245 const KEY_braille_dots_1245 = detail.KEY_braille_dots_1245 export KEY_braille_dots_1245 const KEY_braille_dots_345 = detail.KEY_braille_dots_345 export KEY_braille_dots_345 const KEY_braille_dots_1345 = detail.KEY_braille_dots_1345 export KEY_braille_dots_1345 const KEY_braille_dots_2345 = detail.KEY_braille_dots_2345 export KEY_braille_dots_2345 const KEY_braille_dots_12345 = detail.KEY_braille_dots_12345 export KEY_braille_dots_12345 const KEY_braille_dots_6 = detail.KEY_braille_dots_6 export KEY_braille_dots_6 const KEY_braille_dots_16 = detail.KEY_braille_dots_16 export KEY_braille_dots_16 const KEY_braille_dots_26 = detail.KEY_braille_dots_26 export KEY_braille_dots_26 const KEY_braille_dots_126 = detail.KEY_braille_dots_126 export KEY_braille_dots_126 const KEY_braille_dots_36 = detail.KEY_braille_dots_36 export KEY_braille_dots_36 const KEY_braille_dots_136 = detail.KEY_braille_dots_136 export KEY_braille_dots_136 const KEY_braille_dots_236 = detail.KEY_braille_dots_236 export KEY_braille_dots_236 const KEY_braille_dots_1236 = detail.KEY_braille_dots_1236 export KEY_braille_dots_1236 const KEY_braille_dots_46 = detail.KEY_braille_dots_46 export KEY_braille_dots_46 const KEY_braille_dots_146 = detail.KEY_braille_dots_146 export KEY_braille_dots_146 const KEY_braille_dots_246 = detail.KEY_braille_dots_246 export KEY_braille_dots_246 const KEY_braille_dots_1246 = detail.KEY_braille_dots_1246 export KEY_braille_dots_1246 const KEY_braille_dots_346 = detail.KEY_braille_dots_346 export KEY_braille_dots_346 const KEY_braille_dots_1346 = detail.KEY_braille_dots_1346 export KEY_braille_dots_1346 const KEY_braille_dots_2346 = detail.KEY_braille_dots_2346 export KEY_braille_dots_2346 const KEY_braille_dots_12346 = detail.KEY_braille_dots_12346 export KEY_braille_dots_12346 const KEY_braille_dots_56 = detail.KEY_braille_dots_56 export KEY_braille_dots_56 const KEY_braille_dots_156 = detail.KEY_braille_dots_156 export KEY_braille_dots_156 const KEY_braille_dots_256 = detail.KEY_braille_dots_256 export KEY_braille_dots_256 const KEY_braille_dots_1256 = detail.KEY_braille_dots_1256 export KEY_braille_dots_1256 const KEY_braille_dots_356 = detail.KEY_braille_dots_356 export KEY_braille_dots_356 const KEY_braille_dots_1356 = detail.KEY_braille_dots_1356 export KEY_braille_dots_1356 const KEY_braille_dots_2356 = detail.KEY_braille_dots_2356 export KEY_braille_dots_2356 const KEY_braille_dots_12356 = detail.KEY_braille_dots_12356 export KEY_braille_dots_12356 const KEY_braille_dots_456 = detail.KEY_braille_dots_456 export KEY_braille_dots_456 const KEY_braille_dots_1456 = detail.KEY_braille_dots_1456 export KEY_braille_dots_1456 const KEY_braille_dots_2456 = detail.KEY_braille_dots_2456 export KEY_braille_dots_2456 const KEY_braille_dots_12456 = detail.KEY_braille_dots_12456 export KEY_braille_dots_12456 const KEY_braille_dots_3456 = detail.KEY_braille_dots_3456 export KEY_braille_dots_3456 const KEY_braille_dots_13456 = detail.KEY_braille_dots_13456 export KEY_braille_dots_13456 const KEY_braille_dots_23456 = detail.KEY_braille_dots_23456 export KEY_braille_dots_23456 const KEY_braille_dots_123456 = detail.KEY_braille_dots_123456 export KEY_braille_dots_123456 const KEY_braille_dots_7 = detail.KEY_braille_dots_7 export KEY_braille_dots_7 const KEY_braille_dots_17 = detail.KEY_braille_dots_17 export KEY_braille_dots_17 const KEY_braille_dots_27 = detail.KEY_braille_dots_27 export KEY_braille_dots_27 const KEY_braille_dots_127 = detail.KEY_braille_dots_127 export KEY_braille_dots_127 const KEY_braille_dots_37 = detail.KEY_braille_dots_37 export KEY_braille_dots_37 const KEY_braille_dots_137 = detail.KEY_braille_dots_137 export KEY_braille_dots_137 const KEY_braille_dots_237 = detail.KEY_braille_dots_237 export KEY_braille_dots_237 const KEY_braille_dots_1237 = detail.KEY_braille_dots_1237 export KEY_braille_dots_1237 const KEY_braille_dots_47 = detail.KEY_braille_dots_47 export KEY_braille_dots_47 const KEY_braille_dots_147 = detail.KEY_braille_dots_147 export KEY_braille_dots_147 const KEY_braille_dots_247 = detail.KEY_braille_dots_247 export KEY_braille_dots_247 const KEY_braille_dots_1247 = detail.KEY_braille_dots_1247 export KEY_braille_dots_1247 const KEY_braille_dots_347 = detail.KEY_braille_dots_347 export KEY_braille_dots_347 const KEY_braille_dots_1347 = detail.KEY_braille_dots_1347 export KEY_braille_dots_1347 const KEY_braille_dots_2347 = detail.KEY_braille_dots_2347 export KEY_braille_dots_2347 const KEY_braille_dots_12347 = detail.KEY_braille_dots_12347 export KEY_braille_dots_12347 const KEY_braille_dots_57 = detail.KEY_braille_dots_57 export KEY_braille_dots_57 const KEY_braille_dots_157 = detail.KEY_braille_dots_157 export KEY_braille_dots_157 const KEY_braille_dots_257 = detail.KEY_braille_dots_257 export KEY_braille_dots_257 const KEY_braille_dots_1257 = detail.KEY_braille_dots_1257 export KEY_braille_dots_1257 const KEY_braille_dots_357 = detail.KEY_braille_dots_357 export KEY_braille_dots_357 const KEY_braille_dots_1357 = detail.KEY_braille_dots_1357 export KEY_braille_dots_1357 const KEY_braille_dots_2357 = detail.KEY_braille_dots_2357 export KEY_braille_dots_2357 const KEY_braille_dots_12357 = detail.KEY_braille_dots_12357 export KEY_braille_dots_12357 const KEY_braille_dots_457 = detail.KEY_braille_dots_457 export KEY_braille_dots_457 const KEY_braille_dots_1457 = detail.KEY_braille_dots_1457 export KEY_braille_dots_1457 const KEY_braille_dots_2457 = detail.KEY_braille_dots_2457 export KEY_braille_dots_2457 const KEY_braille_dots_12457 = detail.KEY_braille_dots_12457 export KEY_braille_dots_12457 const KEY_braille_dots_3457 = detail.KEY_braille_dots_3457 export KEY_braille_dots_3457 const KEY_braille_dots_13457 = detail.KEY_braille_dots_13457 export KEY_braille_dots_13457 const KEY_braille_dots_23457 = detail.KEY_braille_dots_23457 export KEY_braille_dots_23457 const KEY_braille_dots_123457 = detail.KEY_braille_dots_123457 export KEY_braille_dots_123457 const KEY_braille_dots_67 = detail.KEY_braille_dots_67 export KEY_braille_dots_67 const KEY_braille_dots_167 = detail.KEY_braille_dots_167 export KEY_braille_dots_167 const KEY_braille_dots_267 = detail.KEY_braille_dots_267 export KEY_braille_dots_267 const KEY_braille_dots_1267 = detail.KEY_braille_dots_1267 export KEY_braille_dots_1267 const KEY_braille_dots_367 = detail.KEY_braille_dots_367 export KEY_braille_dots_367 const KEY_braille_dots_1367 = detail.KEY_braille_dots_1367 export KEY_braille_dots_1367 const KEY_braille_dots_2367 = detail.KEY_braille_dots_2367 export KEY_braille_dots_2367 const KEY_braille_dots_12367 = detail.KEY_braille_dots_12367 export KEY_braille_dots_12367 const KEY_braille_dots_467 = detail.KEY_braille_dots_467 export KEY_braille_dots_467 const KEY_braille_dots_1467 = detail.KEY_braille_dots_1467 export KEY_braille_dots_1467 const KEY_braille_dots_2467 = detail.KEY_braille_dots_2467 export KEY_braille_dots_2467 const KEY_braille_dots_12467 = detail.KEY_braille_dots_12467 export KEY_braille_dots_12467 const KEY_braille_dots_3467 = detail.KEY_braille_dots_3467 export KEY_braille_dots_3467 const KEY_braille_dots_13467 = detail.KEY_braille_dots_13467 export KEY_braille_dots_13467 const KEY_braille_dots_23467 = detail.KEY_braille_dots_23467 export KEY_braille_dots_23467 const KEY_braille_dots_123467 = detail.KEY_braille_dots_123467 export KEY_braille_dots_123467 const KEY_braille_dots_567 = detail.KEY_braille_dots_567 export KEY_braille_dots_567 const KEY_braille_dots_1567 = detail.KEY_braille_dots_1567 export KEY_braille_dots_1567 const KEY_braille_dots_2567 = detail.KEY_braille_dots_2567 export KEY_braille_dots_2567 const KEY_braille_dots_12567 = detail.KEY_braille_dots_12567 export KEY_braille_dots_12567 const KEY_braille_dots_3567 = detail.KEY_braille_dots_3567 export KEY_braille_dots_3567 const KEY_braille_dots_13567 = detail.KEY_braille_dots_13567 export KEY_braille_dots_13567 const KEY_braille_dots_23567 = detail.KEY_braille_dots_23567 export KEY_braille_dots_23567 const KEY_braille_dots_123567 = detail.KEY_braille_dots_123567 export KEY_braille_dots_123567 const KEY_braille_dots_4567 = detail.KEY_braille_dots_4567 export KEY_braille_dots_4567 const KEY_braille_dots_14567 = detail.KEY_braille_dots_14567 export KEY_braille_dots_14567 const KEY_braille_dots_24567 = detail.KEY_braille_dots_24567 export KEY_braille_dots_24567 const KEY_braille_dots_124567 = detail.KEY_braille_dots_124567 export KEY_braille_dots_124567 const KEY_braille_dots_34567 = detail.KEY_braille_dots_34567 export KEY_braille_dots_34567 const KEY_braille_dots_134567 = detail.KEY_braille_dots_134567 export KEY_braille_dots_134567 const KEY_braille_dots_234567 = detail.KEY_braille_dots_234567 export KEY_braille_dots_234567 const KEY_braille_dots_1234567 = detail.KEY_braille_dots_1234567 export KEY_braille_dots_1234567 const KEY_braille_dots_8 = detail.KEY_braille_dots_8 export KEY_braille_dots_8 const KEY_braille_dots_18 = detail.KEY_braille_dots_18 export KEY_braille_dots_18 const KEY_braille_dots_28 = detail.KEY_braille_dots_28 export KEY_braille_dots_28 const KEY_braille_dots_128 = detail.KEY_braille_dots_128 export KEY_braille_dots_128 const KEY_braille_dots_38 = detail.KEY_braille_dots_38 export KEY_braille_dots_38 const KEY_braille_dots_138 = detail.KEY_braille_dots_138 export KEY_braille_dots_138 const KEY_braille_dots_238 = detail.KEY_braille_dots_238 export KEY_braille_dots_238 const KEY_braille_dots_1238 = detail.KEY_braille_dots_1238 export KEY_braille_dots_1238 const KEY_braille_dots_48 = detail.KEY_braille_dots_48 export KEY_braille_dots_48 const KEY_braille_dots_148 = detail.KEY_braille_dots_148 export KEY_braille_dots_148 const KEY_braille_dots_248 = detail.KEY_braille_dots_248 export KEY_braille_dots_248 const KEY_braille_dots_1248 = detail.KEY_braille_dots_1248 export KEY_braille_dots_1248 const KEY_braille_dots_348 = detail.KEY_braille_dots_348 export KEY_braille_dots_348 const KEY_braille_dots_1348 = detail.KEY_braille_dots_1348 export KEY_braille_dots_1348 const KEY_braille_dots_2348 = detail.KEY_braille_dots_2348 export KEY_braille_dots_2348 const KEY_braille_dots_12348 = detail.KEY_braille_dots_12348 export KEY_braille_dots_12348 const KEY_braille_dots_58 = detail.KEY_braille_dots_58 export KEY_braille_dots_58 const KEY_braille_dots_158 = detail.KEY_braille_dots_158 export KEY_braille_dots_158 const KEY_braille_dots_258 = detail.KEY_braille_dots_258 export KEY_braille_dots_258 const KEY_braille_dots_1258 = detail.KEY_braille_dots_1258 export KEY_braille_dots_1258 const KEY_braille_dots_358 = detail.KEY_braille_dots_358 export KEY_braille_dots_358 const KEY_braille_dots_1358 = detail.KEY_braille_dots_1358 export KEY_braille_dots_1358 const KEY_braille_dots_2358 = detail.KEY_braille_dots_2358 export KEY_braille_dots_2358 const KEY_braille_dots_12358 = detail.KEY_braille_dots_12358 export KEY_braille_dots_12358 const KEY_braille_dots_458 = detail.KEY_braille_dots_458 export KEY_braille_dots_458 const KEY_braille_dots_1458 = detail.KEY_braille_dots_1458 export KEY_braille_dots_1458 const KEY_braille_dots_2458 = detail.KEY_braille_dots_2458 export KEY_braille_dots_2458 const KEY_braille_dots_12458 = detail.KEY_braille_dots_12458 export KEY_braille_dots_12458 const KEY_braille_dots_3458 = detail.KEY_braille_dots_3458 export KEY_braille_dots_3458 const KEY_braille_dots_13458 = detail.KEY_braille_dots_13458 export KEY_braille_dots_13458 const KEY_braille_dots_23458 = detail.KEY_braille_dots_23458 export KEY_braille_dots_23458 const KEY_braille_dots_123458 = detail.KEY_braille_dots_123458 export KEY_braille_dots_123458 const KEY_braille_dots_68 = detail.KEY_braille_dots_68 export KEY_braille_dots_68 const KEY_braille_dots_168 = detail.KEY_braille_dots_168 export KEY_braille_dots_168 const KEY_braille_dots_268 = detail.KEY_braille_dots_268 export KEY_braille_dots_268 const KEY_braille_dots_1268 = detail.KEY_braille_dots_1268 export KEY_braille_dots_1268 const KEY_braille_dots_368 = detail.KEY_braille_dots_368 export KEY_braille_dots_368 const KEY_braille_dots_1368 = detail.KEY_braille_dots_1368 export KEY_braille_dots_1368 const KEY_braille_dots_2368 = detail.KEY_braille_dots_2368 export KEY_braille_dots_2368 const KEY_braille_dots_12368 = detail.KEY_braille_dots_12368 export KEY_braille_dots_12368 const KEY_braille_dots_468 = detail.KEY_braille_dots_468 export KEY_braille_dots_468 const KEY_braille_dots_1468 = detail.KEY_braille_dots_1468 export KEY_braille_dots_1468 const KEY_braille_dots_2468 = detail.KEY_braille_dots_2468 export KEY_braille_dots_2468 const KEY_braille_dots_12468 = detail.KEY_braille_dots_12468 export KEY_braille_dots_12468 const KEY_braille_dots_3468 = detail.KEY_braille_dots_3468 export KEY_braille_dots_3468 const KEY_braille_dots_13468 = detail.KEY_braille_dots_13468 export KEY_braille_dots_13468 const KEY_braille_dots_23468 = detail.KEY_braille_dots_23468 export KEY_braille_dots_23468 const KEY_braille_dots_123468 = detail.KEY_braille_dots_123468 export KEY_braille_dots_123468 const KEY_braille_dots_568 = detail.KEY_braille_dots_568 export KEY_braille_dots_568 const KEY_braille_dots_1568 = detail.KEY_braille_dots_1568 export KEY_braille_dots_1568 const KEY_braille_dots_2568 = detail.KEY_braille_dots_2568 export KEY_braille_dots_2568 const KEY_braille_dots_12568 = detail.KEY_braille_dots_12568 export KEY_braille_dots_12568 const KEY_braille_dots_3568 = detail.KEY_braille_dots_3568 export KEY_braille_dots_3568 const KEY_braille_dots_13568 = detail.KEY_braille_dots_13568 export KEY_braille_dots_13568 const KEY_braille_dots_23568 = detail.KEY_braille_dots_23568 export KEY_braille_dots_23568 const KEY_braille_dots_123568 = detail.KEY_braille_dots_123568 export KEY_braille_dots_123568 const KEY_braille_dots_4568 = detail.KEY_braille_dots_4568 export KEY_braille_dots_4568 const KEY_braille_dots_14568 = detail.KEY_braille_dots_14568 export KEY_braille_dots_14568 const KEY_braille_dots_24568 = detail.KEY_braille_dots_24568 export KEY_braille_dots_24568 const KEY_braille_dots_124568 = detail.KEY_braille_dots_124568 export KEY_braille_dots_124568 const KEY_braille_dots_34568 = detail.KEY_braille_dots_34568 export KEY_braille_dots_34568 const KEY_braille_dots_134568 = detail.KEY_braille_dots_134568 export KEY_braille_dots_134568 const KEY_braille_dots_234568 = detail.KEY_braille_dots_234568 export KEY_braille_dots_234568 const KEY_braille_dots_1234568 = detail.KEY_braille_dots_1234568 export KEY_braille_dots_1234568 const KEY_braille_dots_78 = detail.KEY_braille_dots_78 export KEY_braille_dots_78 const KEY_braille_dots_178 = detail.KEY_braille_dots_178 export KEY_braille_dots_178 const KEY_braille_dots_278 = detail.KEY_braille_dots_278 export KEY_braille_dots_278 const KEY_braille_dots_1278 = detail.KEY_braille_dots_1278 export KEY_braille_dots_1278 const KEY_braille_dots_378 = detail.KEY_braille_dots_378 export KEY_braille_dots_378 const KEY_braille_dots_1378 = detail.KEY_braille_dots_1378 export KEY_braille_dots_1378 const KEY_braille_dots_2378 = detail.KEY_braille_dots_2378 export KEY_braille_dots_2378 const KEY_braille_dots_12378 = detail.KEY_braille_dots_12378 export KEY_braille_dots_12378 const KEY_braille_dots_478 = detail.KEY_braille_dots_478 export KEY_braille_dots_478 const KEY_braille_dots_1478 = detail.KEY_braille_dots_1478 export KEY_braille_dots_1478 const KEY_braille_dots_2478 = detail.KEY_braille_dots_2478 export KEY_braille_dots_2478 const KEY_braille_dots_12478 = detail.KEY_braille_dots_12478 export KEY_braille_dots_12478 const KEY_braille_dots_3478 = detail.KEY_braille_dots_3478 export KEY_braille_dots_3478 const KEY_braille_dots_13478 = detail.KEY_braille_dots_13478 export KEY_braille_dots_13478 const KEY_braille_dots_23478 = detail.KEY_braille_dots_23478 export KEY_braille_dots_23478 const KEY_braille_dots_123478 = detail.KEY_braille_dots_123478 export KEY_braille_dots_123478 const KEY_braille_dots_578 = detail.KEY_braille_dots_578 export KEY_braille_dots_578 const KEY_braille_dots_1578 = detail.KEY_braille_dots_1578 export KEY_braille_dots_1578 const KEY_braille_dots_2578 = detail.KEY_braille_dots_2578 export KEY_braille_dots_2578 const KEY_braille_dots_12578 = detail.KEY_braille_dots_12578 export KEY_braille_dots_12578 const KEY_braille_dots_3578 = detail.KEY_braille_dots_3578 export KEY_braille_dots_3578 const KEY_braille_dots_13578 = detail.KEY_braille_dots_13578 export KEY_braille_dots_13578 const KEY_braille_dots_23578 = detail.KEY_braille_dots_23578 export KEY_braille_dots_23578 const KEY_braille_dots_123578 = detail.KEY_braille_dots_123578 export KEY_braille_dots_123578 const KEY_braille_dots_4578 = detail.KEY_braille_dots_4578 export KEY_braille_dots_4578 const KEY_braille_dots_14578 = detail.KEY_braille_dots_14578 export KEY_braille_dots_14578 const KEY_braille_dots_24578 = detail.KEY_braille_dots_24578 export KEY_braille_dots_24578 const KEY_braille_dots_124578 = detail.KEY_braille_dots_124578 export KEY_braille_dots_124578 const KEY_braille_dots_34578 = detail.KEY_braille_dots_34578 export KEY_braille_dots_34578 const KEY_braille_dots_134578 = detail.KEY_braille_dots_134578 export KEY_braille_dots_134578 const KEY_braille_dots_234578 = detail.KEY_braille_dots_234578 export KEY_braille_dots_234578 const KEY_braille_dots_1234578 = detail.KEY_braille_dots_1234578 export KEY_braille_dots_1234578 const KEY_braille_dots_678 = detail.KEY_braille_dots_678 export KEY_braille_dots_678 const KEY_braille_dots_1678 = detail.KEY_braille_dots_1678 export KEY_braille_dots_1678 const KEY_braille_dots_2678 = detail.KEY_braille_dots_2678 export KEY_braille_dots_2678 const KEY_braille_dots_12678 = detail.KEY_braille_dots_12678 export KEY_braille_dots_12678 const KEY_braille_dots_3678 = detail.KEY_braille_dots_3678 export KEY_braille_dots_3678 const KEY_braille_dots_13678 = detail.KEY_braille_dots_13678 export KEY_braille_dots_13678 const KEY_braille_dots_23678 = detail.KEY_braille_dots_23678 export KEY_braille_dots_23678 const KEY_braille_dots_123678 = detail.KEY_braille_dots_123678 export KEY_braille_dots_123678 const KEY_braille_dots_4678 = detail.KEY_braille_dots_4678 export KEY_braille_dots_4678 const KEY_braille_dots_14678 = detail.KEY_braille_dots_14678 export KEY_braille_dots_14678 const KEY_braille_dots_24678 = detail.KEY_braille_dots_24678 export KEY_braille_dots_24678 const KEY_braille_dots_124678 = detail.KEY_braille_dots_124678 export KEY_braille_dots_124678 const KEY_braille_dots_34678 = detail.KEY_braille_dots_34678 export KEY_braille_dots_34678 const KEY_braille_dots_134678 = detail.KEY_braille_dots_134678 export KEY_braille_dots_134678 const KEY_braille_dots_234678 = detail.KEY_braille_dots_234678 export KEY_braille_dots_234678 const KEY_braille_dots_1234678 = detail.KEY_braille_dots_1234678 export KEY_braille_dots_1234678 const KEY_braille_dots_5678 = detail.KEY_braille_dots_5678 export KEY_braille_dots_5678 const KEY_braille_dots_15678 = detail.KEY_braille_dots_15678 export KEY_braille_dots_15678 const KEY_braille_dots_25678 = detail.KEY_braille_dots_25678 export KEY_braille_dots_25678 const KEY_braille_dots_125678 = detail.KEY_braille_dots_125678 export KEY_braille_dots_125678 const KEY_braille_dots_35678 = detail.KEY_braille_dots_35678 export KEY_braille_dots_35678 const KEY_braille_dots_135678 = detail.KEY_braille_dots_135678 export KEY_braille_dots_135678 const KEY_braille_dots_235678 = detail.KEY_braille_dots_235678 export KEY_braille_dots_235678 const KEY_braille_dots_1235678 = detail.KEY_braille_dots_1235678 export KEY_braille_dots_1235678 const KEY_braille_dots_45678 = detail.KEY_braille_dots_45678 export KEY_braille_dots_45678 const KEY_braille_dots_145678 = detail.KEY_braille_dots_145678 export KEY_braille_dots_145678 const KEY_braille_dots_245678 = detail.KEY_braille_dots_245678 export KEY_braille_dots_245678 const KEY_braille_dots_1245678 = detail.KEY_braille_dots_1245678 export KEY_braille_dots_1245678 const KEY_braille_dots_345678 = detail.KEY_braille_dots_345678 export KEY_braille_dots_345678 const KEY_braille_dots_1345678 = detail.KEY_braille_dots_1345678 export KEY_braille_dots_1345678 const KEY_braille_dots_2345678 = detail.KEY_braille_dots_2345678 export KEY_braille_dots_2345678 const KEY_braille_dots_12345678 = detail.KEY_braille_dots_12345678 export KEY_braille_dots_12345678 const KEY_Sinh_ng = detail.KEY_Sinh_ng export KEY_Sinh_ng const KEY_Sinh_h2 = detail.KEY_Sinh_h2 export KEY_Sinh_h2 const KEY_Sinh_a = detail.KEY_Sinh_a export KEY_Sinh_a const KEY_Sinh_aa = detail.KEY_Sinh_aa export KEY_Sinh_aa const KEY_Sinh_ae = detail.KEY_Sinh_ae export KEY_Sinh_ae const KEY_Sinh_aee = detail.KEY_Sinh_aee export KEY_Sinh_aee const KEY_Sinh_i = detail.KEY_Sinh_i export KEY_Sinh_i const KEY_Sinh_ii = detail.KEY_Sinh_ii export KEY_Sinh_ii const KEY_Sinh_u = detail.KEY_Sinh_u export KEY_Sinh_u const KEY_Sinh_uu = detail.KEY_Sinh_uu export KEY_Sinh_uu const KEY_Sinh_ri = detail.KEY_Sinh_ri export KEY_Sinh_ri const KEY_Sinh_rii = detail.KEY_Sinh_rii export KEY_Sinh_rii const KEY_Sinh_lu = detail.KEY_Sinh_lu export KEY_Sinh_lu const KEY_Sinh_luu = detail.KEY_Sinh_luu export KEY_Sinh_luu const KEY_Sinh_e = detail.KEY_Sinh_e export KEY_Sinh_e const KEY_Sinh_ee = detail.KEY_Sinh_ee export KEY_Sinh_ee const KEY_Sinh_ai = detail.KEY_Sinh_ai export KEY_Sinh_ai const KEY_Sinh_o = detail.KEY_Sinh_o export KEY_Sinh_o const KEY_Sinh_oo = detail.KEY_Sinh_oo export KEY_Sinh_oo const KEY_Sinh_au = detail.KEY_Sinh_au export KEY_Sinh_au const KEY_Sinh_ka = detail.KEY_Sinh_ka export KEY_Sinh_ka const KEY_Sinh_kha = detail.KEY_Sinh_kha export KEY_Sinh_kha const KEY_Sinh_ga = detail.KEY_Sinh_ga export KEY_Sinh_ga const KEY_Sinh_gha = detail.KEY_Sinh_gha export KEY_Sinh_gha const KEY_Sinh_ng2 = detail.KEY_Sinh_ng2 export KEY_Sinh_ng2 const KEY_Sinh_nga = detail.KEY_Sinh_nga export KEY_Sinh_nga const KEY_Sinh_ca = detail.KEY_Sinh_ca export KEY_Sinh_ca const KEY_Sinh_cha = detail.KEY_Sinh_cha export KEY_Sinh_cha const KEY_Sinh_ja = detail.KEY_Sinh_ja export KEY_Sinh_ja const KEY_Sinh_jha = detail.KEY_Sinh_jha export KEY_Sinh_jha const KEY_Sinh_nya = detail.KEY_Sinh_nya export KEY_Sinh_nya const KEY_Sinh_jnya = detail.KEY_Sinh_jnya export KEY_Sinh_jnya const KEY_Sinh_nja = detail.KEY_Sinh_nja export KEY_Sinh_nja const KEY_Sinh_tta = detail.KEY_Sinh_tta export KEY_Sinh_tta const KEY_Sinh_ttha = detail.KEY_Sinh_ttha export KEY_Sinh_ttha const KEY_Sinh_dda = detail.KEY_Sinh_dda export KEY_Sinh_dda const KEY_Sinh_ddha = detail.KEY_Sinh_ddha export KEY_Sinh_ddha const KEY_Sinh_nna = detail.KEY_Sinh_nna export KEY_Sinh_nna const KEY_Sinh_ndda = detail.KEY_Sinh_ndda export KEY_Sinh_ndda const KEY_Sinh_tha = detail.KEY_Sinh_tha export KEY_Sinh_tha const KEY_Sinh_thha = detail.KEY_Sinh_thha export KEY_Sinh_thha const KEY_Sinh_dha = detail.KEY_Sinh_dha export KEY_Sinh_dha const KEY_Sinh_dhha = detail.KEY_Sinh_dhha export KEY_Sinh_dhha const KEY_Sinh_na = detail.KEY_Sinh_na export KEY_Sinh_na const KEY_Sinh_ndha = detail.KEY_Sinh_ndha export KEY_Sinh_ndha const KEY_Sinh_pa = detail.KEY_Sinh_pa export KEY_Sinh_pa const KEY_Sinh_pha = detail.KEY_Sinh_pha export KEY_Sinh_pha const KEY_Sinh_ba = detail.KEY_Sinh_ba export KEY_Sinh_ba const KEY_Sinh_bha = detail.KEY_Sinh_bha export KEY_Sinh_bha const KEY_Sinh_ma = detail.KEY_Sinh_ma export KEY_Sinh_ma const KEY_Sinh_mba = detail.KEY_Sinh_mba export KEY_Sinh_mba const KEY_Sinh_ya = detail.KEY_Sinh_ya export KEY_Sinh_ya const KEY_Sinh_ra = detail.KEY_Sinh_ra export KEY_Sinh_ra const KEY_Sinh_la = detail.KEY_Sinh_la export KEY_Sinh_la const KEY_Sinh_va = detail.KEY_Sinh_va export KEY_Sinh_va const KEY_Sinh_sha = detail.KEY_Sinh_sha export KEY_Sinh_sha const KEY_Sinh_ssha = detail.KEY_Sinh_ssha export KEY_Sinh_ssha const KEY_Sinh_sa = detail.KEY_Sinh_sa export KEY_Sinh_sa const KEY_Sinh_ha = detail.KEY_Sinh_ha export KEY_Sinh_ha const KEY_Sinh_lla = detail.KEY_Sinh_lla export KEY_Sinh_lla const KEY_Sinh_fa = detail.KEY_Sinh_fa export KEY_Sinh_fa const KEY_Sinh_al = detail.KEY_Sinh_al export KEY_Sinh_al const KEY_Sinh_aa2 = detail.KEY_Sinh_aa2 export KEY_Sinh_aa2 const KEY_Sinh_ae2 = detail.KEY_Sinh_ae2 export KEY_Sinh_ae2 const KEY_Sinh_aee2 = detail.KEY_Sinh_aee2 export KEY_Sinh_aee2 const KEY_Sinh_i2 = detail.KEY_Sinh_i2 export KEY_Sinh_i2 const KEY_Sinh_ii2 = detail.KEY_Sinh_ii2 export KEY_Sinh_ii2 const KEY_Sinh_u2 = detail.KEY_Sinh_u2 export KEY_Sinh_u2 const KEY_Sinh_uu2 = detail.KEY_Sinh_uu2 export KEY_Sinh_uu2 const KEY_Sinh_ru2 = detail.KEY_Sinh_ru2 export KEY_Sinh_ru2 const KEY_Sinh_e2 = detail.KEY_Sinh_e2 export KEY_Sinh_e2 const KEY_Sinh_ee2 = detail.KEY_Sinh_ee2 export KEY_Sinh_ee2 const KEY_Sinh_ai2 = detail.KEY_Sinh_ai2 export KEY_Sinh_ai2 const KEY_Sinh_o2 = detail.KEY_Sinh_o2 export KEY_Sinh_o2 const KEY_Sinh_oo2 = detail.KEY_Sinh_oo2 export KEY_Sinh_oo2 const KEY_Sinh_au2 = detail.KEY_Sinh_au2 export KEY_Sinh_au2 const KEY_Sinh_lu2 = detail.KEY_Sinh_lu2 export KEY_Sinh_lu2 const KEY_Sinh_ruu2 = detail.KEY_Sinh_ruu2 export KEY_Sinh_ruu2 const KEY_Sinh_luu2 = detail.KEY_Sinh_luu2 export KEY_Sinh_luu2 const KEY_Sinh_kunddaliya = detail.KEY_Sinh_kunddaliya export KEY_Sinh_kunddaliya const KEY_ModeLock = detail.KEY_ModeLock export KEY_ModeLock const KEY_MonBrightnessUp = detail.KEY_MonBrightnessUp export KEY_MonBrightnessUp const KEY_MonBrightnessDown = detail.KEY_MonBrightnessDown export KEY_MonBrightnessDown const KEY_KbdLightOnOff = detail.KEY_KbdLightOnOff export KEY_KbdLightOnOff const KEY_KbdBrightnessUp = detail.KEY_KbdBrightnessUp export KEY_KbdBrightnessUp const KEY_KbdBrightnessDown = detail.KEY_KbdBrightnessDown export KEY_KbdBrightnessDown const KEY_Standby = detail.KEY_Standby export KEY_Standby const KEY_AudioLowerVolume = detail.KEY_AudioLowerVolume export KEY_AudioLowerVolume const KEY_AudioMute = detail.KEY_AudioMute export KEY_AudioMute const KEY_AudioRaiseVolume = detail.KEY_AudioRaiseVolume export KEY_AudioRaiseVolume const KEY_AudioPlay = detail.KEY_AudioPlay export KEY_AudioPlay const KEY_AudioStop = detail.KEY_AudioStop export KEY_AudioStop const KEY_AudioPrev = detail.KEY_AudioPrev export KEY_AudioPrev const KEY_AudioNext = detail.KEY_AudioNext export KEY_AudioNext const KEY_HomePage = detail.KEY_HomePage export KEY_HomePage const KEY_Mail = detail.KEY_Mail export KEY_Mail const KEY_Start = detail.KEY_Start export KEY_Start const KEY_Search = detail.KEY_Search export KEY_Search const KEY_AudioRecord = detail.KEY_AudioRecord export KEY_AudioRecord const KEY_Calculator = detail.KEY_Calculator export KEY_Calculator const KEY_Memo = detail.KEY_Memo export KEY_Memo const KEY_ToDoList = detail.KEY_ToDoList export KEY_ToDoList const KEY_Calendar = detail.KEY_Calendar export KEY_Calendar const KEY_PowerDown = detail.KEY_PowerDown export KEY_PowerDown const KEY_ContrastAdjust = detail.KEY_ContrastAdjust export KEY_ContrastAdjust const KEY_RockerUp = detail.KEY_RockerUp export KEY_RockerUp const KEY_RockerDown = detail.KEY_RockerDown export KEY_RockerDown const KEY_RockerEnter = detail.KEY_RockerEnter export KEY_RockerEnter const KEY_Back = detail.KEY_Back export KEY_Back const KEY_Forward = detail.KEY_Forward export KEY_Forward const KEY_Stop = detail.KEY_Stop export KEY_Stop const KEY_Refresh = detail.KEY_Refresh export KEY_Refresh const KEY_PowerOff = detail.KEY_PowerOff export KEY_PowerOff const KEY_WakeUp = detail.KEY_WakeUp export KEY_WakeUp const KEY_Eject = detail.KEY_Eject export KEY_Eject const KEY_ScreenSaver = detail.KEY_ScreenSaver export KEY_ScreenSaver const KEY_WWW = detail.KEY_WWW export KEY_WWW const KEY_Sleep = detail.KEY_Sleep export KEY_Sleep const KEY_Favorites = detail.KEY_Favorites export KEY_Favorites const KEY_AudioPause = detail.KEY_AudioPause export KEY_AudioPause const KEY_AudioMedia = detail.KEY_AudioMedia export KEY_AudioMedia const KEY_MyComputer = detail.KEY_MyComputer export KEY_MyComputer const KEY_VendorHome = detail.KEY_VendorHome export KEY_VendorHome const KEY_LightBulb = detail.KEY_LightBulb export KEY_LightBulb const KEY_Shop = detail.KEY_Shop export KEY_Shop const KEY_History = detail.KEY_History export KEY_History const KEY_OpenURL = detail.KEY_OpenURL export KEY_OpenURL const KEY_AddFavorite = detail.KEY_AddFavorite export KEY_AddFavorite const KEY_HotLinks = detail.KEY_HotLinks export KEY_HotLinks const KEY_BrightnessAdjust = detail.KEY_BrightnessAdjust export KEY_BrightnessAdjust const KEY_Finance = detail.KEY_Finance export KEY_Finance const KEY_Community = detail.KEY_Community export KEY_Community const KEY_AudioRewind = detail.KEY_AudioRewind export KEY_AudioRewind const KEY_BackForward = detail.KEY_BackForward export KEY_BackForward const KEY_Launch0 = detail.KEY_Launch0 export KEY_Launch0 const KEY_Launch1 = detail.KEY_Launch1 export KEY_Launch1 const KEY_Launch2 = detail.KEY_Launch2 export KEY_Launch2 const KEY_Launch3 = detail.KEY_Launch3 export KEY_Launch3 const KEY_Launch4 = detail.KEY_Launch4 export KEY_Launch4 const KEY_Launch5 = detail.KEY_Launch5 export KEY_Launch5 const KEY_Launch6 = detail.KEY_Launch6 export KEY_Launch6 const KEY_Launch7 = detail.KEY_Launch7 export KEY_Launch7 const KEY_Launch8 = detail.KEY_Launch8 export KEY_Launch8 const KEY_Launch9 = detail.KEY_Launch9 export KEY_Launch9 const KEY_LaunchA = detail.KEY_LaunchA export KEY_LaunchA const KEY_LaunchB = detail.KEY_LaunchB export KEY_LaunchB const KEY_LaunchC = detail.KEY_LaunchC export KEY_LaunchC const KEY_LaunchD = detail.KEY_LaunchD export KEY_LaunchD const KEY_LaunchE = detail.KEY_LaunchE export KEY_LaunchE const KEY_LaunchF = detail.KEY_LaunchF export KEY_LaunchF const KEY_ApplicationLeft = detail.KEY_ApplicationLeft export KEY_ApplicationLeft const KEY_ApplicationRight = detail.KEY_ApplicationRight export KEY_ApplicationRight const KEY_Book = detail.KEY_Book export KEY_Book const KEY_CD = detail.KEY_CD export KEY_CD const KEY_WindowClear = detail.KEY_WindowClear export KEY_WindowClear const KEY_Close = detail.KEY_Close export KEY_Close const KEY_Copy = detail.KEY_Copy export KEY_Copy const KEY_Cut = detail.KEY_Cut export KEY_Cut const KEY_Display = detail.KEY_Display export KEY_Display const KEY_DOS = detail.KEY_DOS export KEY_DOS const KEY_Documents = detail.KEY_Documents export KEY_Documents const KEY_Excel = detail.KEY_Excel export KEY_Excel const KEY_Explorer = detail.KEY_Explorer export KEY_Explorer const KEY_Game = detail.KEY_Game export KEY_Game const KEY_Go = detail.KEY_Go export KEY_Go const KEY_iTouch = detail.KEY_iTouch export KEY_iTouch const KEY_LogOff = detail.KEY_LogOff export KEY_LogOff const KEY_Market = detail.KEY_Market export KEY_Market const KEY_Meeting = detail.KEY_Meeting export KEY_Meeting const KEY_MenuKB = detail.KEY_MenuKB export KEY_MenuKB const KEY_MenuPB = detail.KEY_MenuPB export KEY_MenuPB const KEY_MySites = detail.KEY_MySites export KEY_MySites const KEY_New = detail.KEY_New export KEY_New const KEY_News = detail.KEY_News export KEY_News const KEY_OfficeHome = detail.KEY_OfficeHome export KEY_OfficeHome const KEY_Open = detail.KEY_Open export KEY_Open const KEY_Option = detail.KEY_Option export KEY_Option const KEY_Paste = detail.KEY_Paste export KEY_Paste const KEY_Phone = detail.KEY_Phone export KEY_Phone const KEY_Reply = detail.KEY_Reply export KEY_Reply const KEY_Reload = detail.KEY_Reload export KEY_Reload const KEY_RotateWindows = detail.KEY_RotateWindows export KEY_RotateWindows const KEY_RotationPB = detail.KEY_RotationPB export KEY_RotationPB const KEY_RotationKB = detail.KEY_RotationKB export KEY_RotationKB const KEY_Save = detail.KEY_Save export KEY_Save const KEY_ScrollUp = detail.KEY_ScrollUp export KEY_ScrollUp const KEY_ScrollDown = detail.KEY_ScrollDown export KEY_ScrollDown const KEY_ScrollClick = detail.KEY_ScrollClick export KEY_ScrollClick const KEY_Send = detail.KEY_Send export KEY_Send const KEY_Spell = detail.KEY_Spell export KEY_Spell const KEY_SplitScreen = detail.KEY_SplitScreen export KEY_SplitScreen const KEY_Support = detail.KEY_Support export KEY_Support const KEY_TaskPane = detail.KEY_TaskPane export KEY_TaskPane const KEY_Terminal = detail.KEY_Terminal export KEY_Terminal const KEY_Tools = detail.KEY_Tools export KEY_Tools const KEY_Travel = detail.KEY_Travel export KEY_Travel const KEY_UserPB = detail.KEY_UserPB export KEY_UserPB const KEY_User1KB = detail.KEY_User1KB export KEY_User1KB const KEY_User2KB = detail.KEY_User2KB export KEY_User2KB const KEY_Video = detail.KEY_Video export KEY_Video const KEY_WheelButton = detail.KEY_WheelButton export KEY_WheelButton const KEY_Word = detail.KEY_Word export KEY_Word const KEY_Xfer = detail.KEY_Xfer export KEY_Xfer const KEY_ZoomIn = detail.KEY_ZoomIn export KEY_ZoomIn const KEY_ZoomOut = detail.KEY_ZoomOut export KEY_ZoomOut const KEY_Away = detail.KEY_Away export KEY_Away const KEY_Messenger = detail.KEY_Messenger export KEY_Messenger const KEY_WebCam = detail.KEY_WebCam export KEY_WebCam const KEY_MailForward = detail.KEY_MailForward export KEY_MailForward const KEY_Pictures = detail.KEY_Pictures export KEY_Pictures const KEY_Music = detail.KEY_Music export KEY_Music const KEY_Battery = detail.KEY_Battery export KEY_Battery const KEY_Bluetooth = detail.KEY_Bluetooth export KEY_Bluetooth const KEY_WLAN = detail.KEY_WLAN export KEY_WLAN const KEY_UWB = detail.KEY_UWB export KEY_UWB const KEY_AudioForward = detail.KEY_AudioForward export KEY_AudioForward const KEY_AudioRepeat = detail.KEY_AudioRepeat export KEY_AudioRepeat const KEY_AudioRandomPlay = detail.KEY_AudioRandomPlay export KEY_AudioRandomPlay const KEY_Subtitle = detail.KEY_Subtitle export KEY_Subtitle const KEY_AudioCycleTrack = detail.KEY_AudioCycleTrack export KEY_AudioCycleTrack const KEY_CycleAngle = detail.KEY_CycleAngle export KEY_CycleAngle const KEY_FrameBack = detail.KEY_FrameBack export KEY_FrameBack const KEY_FrameForward = detail.KEY_FrameForward export KEY_FrameForward const KEY_Time = detail.KEY_Time export KEY_Time const KEY_SelectButton = detail.KEY_SelectButton export KEY_SelectButton const KEY_View = detail.KEY_View export KEY_View const KEY_TopMenu = detail.KEY_TopMenu export KEY_TopMenu const KEY_Red = detail.KEY_Red export KEY_Red const KEY_Green = detail.KEY_Green export KEY_Green const KEY_Yellow = detail.KEY_Yellow export KEY_Yellow const KEY_Blue = detail.KEY_Blue export KEY_Blue const KEY_Suspend = detail.KEY_Suspend export KEY_Suspend const KEY_Hibernate = detail.KEY_Hibernate export KEY_Hibernate const KEY_TouchpadToggle = detail.KEY_TouchpadToggle export KEY_TouchpadToggle const KEY_TouchpadOn = detail.KEY_TouchpadOn export KEY_TouchpadOn const KEY_TouchpadOff = detail.KEY_TouchpadOff export KEY_TouchpadOff const KEY_AudioMicMute = detail.KEY_AudioMicMute export KEY_AudioMicMute const KEY_Keyboard = detail.KEY_Keyboard export KEY_Keyboard const KEY_WWAN = detail.KEY_WWAN export KEY_WWAN const KEY_RFKill = detail.KEY_RFKill export KEY_RFKill const KEY_AudioPreset = detail.KEY_AudioPreset export KEY_AudioPreset const KEY_Switch_VT_1 = detail.KEY_Switch_VT_1 export KEY_Switch_VT_1 const KEY_Switch_VT_2 = detail.KEY_Switch_VT_2 export KEY_Switch_VT_2 const KEY_Switch_VT_3 = detail.KEY_Switch_VT_3 export KEY_Switch_VT_3 const KEY_Switch_VT_4 = detail.KEY_Switch_VT_4 export KEY_Switch_VT_4 const KEY_Switch_VT_5 = detail.KEY_Switch_VT_5 export KEY_Switch_VT_5 const KEY_Switch_VT_6 = detail.KEY_Switch_VT_6 export KEY_Switch_VT_6 const KEY_Switch_VT_7 = detail.KEY_Switch_VT_7 export KEY_Switch_VT_7 const KEY_Switch_VT_8 = detail.KEY_Switch_VT_8 export KEY_Switch_VT_8 const KEY_Switch_VT_9 = detail.KEY_Switch_VT_9 export KEY_Switch_VT_9 const KEY_Switch_VT_10 = detail.KEY_Switch_VT_10 export KEY_Switch_VT_10 const KEY_Switch_VT_11 = detail.KEY_Switch_VT_11 export KEY_Switch_VT_11 const KEY_Switch_VT_12 = detail.KEY_Switch_VT_12 export KEY_Switch_VT_12 const KEY_Ungrab = detail.KEY_Ungrab export KEY_Ungrab const KEY_ClearGrab = detail.KEY_ClearGrab export KEY_ClearGrab const KEY_Next_VMode = detail.KEY_Next_VMode export KEY_Next_VMode const KEY_Prev_VMode = detail.KEY_Prev_VMode export KEY_Prev_VMode const KEY_LogWindowTree = detail.KEY_LogWindowTree export KEY_LogWindowTree const KEY_LogGrabInfo = detail.KEY_LogGrabInfo export KEY_LogGrabInfo @do_not_compile const to_generate = quote file = open("in.txt") cpp_out = open("out.cpp", "w") jl_binding_out = open("jl_binding.cpp", "w") jl_out = open("out.jl", "w") lines = String[] while !eof(file) push!(lines, readline(file)) end for i in 1:length(lines) new_name = "" first = true previous_char_is_lowercase = false for c in split(lines[i][1:(end-1)], "GDK_KEY_")[2] new_name *= c previous_char_is_lowercase = islowercase(c) first = false end name = "KEY_" * (new_name) write(cpp_out, replace(lines[i], "TODO" => name) * "\n") write(jl_binding_out, "module.set_const(\"$name\", (guint) mousetrap::$name);\n") write(jl_out, "const $name = detail.$name\nexport $name\n") end end ================================================ FILE: test/example.jl ================================================ # File used for debugging and for dosc examples, why are you snooping through this file? # File used for debugging and for dosc examples, why are you snooping through this file? using Mousetrap main() do app::Application window = Window(app) column_view = ColumnView() row_index = push_back_column!(column_view, " ") count_column = push_back_column!(column_view, "#") name_column = push_back_column!(column_view, "Name") weigt_column = push_back_column!(column_view, "Weight") unit_column = push_back_column!(column_view, "Units") # fill columns with example text for i in 1:100 push_front_row!(column_view, Label(string(i)), # row index Label(string(rand(0:99))), # count Label(rand(["Apple", "Orange", "Banana", "Kumquat", "Durian", "Mangosteen"])), # name Label(string(rand(0:100))), # weight Label(string(rand(["mg", "g", "kg", "ton"]))) # unit ) end scrolled_viewport = Viewport() set_child!(scrolled_viewport, column_view) set_child!(window, scrolled_viewport) present!(window) end if false add_css!(""" @keyframes spin-animation { 0% { transform: rotate(0turn) scale(1); } 50% { transform: rotate(0.5turn) scale(2); } 100% { transform: rotate(1turn) scale(1); } } .spinning { animation: spin-animation; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: ease-in-out; } .monospaced { font-family: monospace; } """) main() do app::Application window = Window(app) set_title!(window, "mousesnap.jl") button = Button() add_css_class!(button, "spinning") frame = AspectFrame(1.0, button) set_margin!(frame, 10) #set_child!(window, frame) text_view = TextView(); add_css_class!(text_view, "monospaced") set_child!(window, text_view) present!(window) end exit(0) add_css!(""" .custom { background-color: hotpink; border-color: darker(hotpink); font-family: monospace; border-radius: 0%; } """) function set_accent_color!(color::RGBA) add_css!(""" @define-color accent_bg_color $(serialize(color)); """) end main() do app::Application window = Window(app) set_title!(window, "mousetrap.jl") box = CenterBox(ORIENTATION_VERTICAL) check_button = CheckButton() set_alignment!(check_button, ALIGNMENT_CENTER) set_expand!(check_button, false) set_start_child!(box, check_button) scale = Scale(0, 1, 0.01) set_expand!(scale, true) set_center_child!(box, scale) switch = Switch() set_expand!(switch, false) set_alignment!(switch, ALIGNMENT_CENTER) set_end_child!(box, switch) action = Action("change_accent_color", app) do self set_accent_color!(RGBA(1, 0, 1, 1)) end add_shortcut!(action, "e") set_listens_for_shortcut_action!(scale, action) set_expand!(box, true) set_margin!(box, 10) set_child!(window, box) present!(window) end exit(0) # define widget colors const WidgetColor = String const WIDGET_COLOR_DEFAULT = "default" const WIDGET_COLOR_ACCENT = "accent" const WIDGET_COLOR_SUCCESS = "success" const WIDGET_COLOR_WARNING = "warning" const WIDGET_COLOR_ERROR = "error" # create a CSS classes for each and load it into the global theme for name in [WIDGET_COLOR_DEFAULT, WIDGET_COLOR_ACCENT, WIDGET_COLOR_SUCCESS, WIDGET_COLOR_WARNING, WIDGET_COLOR_ERROR] add_css!(""" $name:not(.opaque) { background-color: @$(name)_fg_color; } .$name.opaque { background-color: @$(name)_bg_color; color: @$(name)_fg_color; } """) end """ ``` set_color!(::Widget, ::WidgetColor) -> Nothing ``` """ function set_accent_color!(widget::Widget, color, opaque = true) if !(color in [WIDGET_COLOR_DEFAULT, WIDGET_COLOR_ACCENT, WIDGET_COLOR_SUCCESS, WIDGET_COLOR_WARNING, WIDGET_COLOR_ERROR]) log_critical("In set_color!: Color ID `" * color * "` is not supported") end add_css_class!(widget, color) if opaque add_css_class!(widget, "opaque") end end main() do app::Application window = Window(app) set_title!(window, "mousetrap.jl") function create_widget() return Button(Label("TEST")) end column_view = ColumnView() column = push_back_column!(column_view, " ") set_widget_at!(column_view, column, 1, Label("!opaque")) set_widget_at!(column_view, column, 2, Label("opaque")) for color in [WIDGET_COLOR_DEFAULT, WIDGET_COLOR_ACCENT, WIDGET_COLOR_SUCCESS, WIDGET_COLOR_WARNING, WIDGET_COLOR_ERROR] column = push_back_column!(column_view, color) # non-opaque version widget = create_widget() set_accent_color!(widget, color, false) set_widget_at!(column_view, column, 1, widget) # opaque version widget = create_widget() set_accent_color!(widget, color, true) set_widget_at!(column_view, column, 2, widget) end set_child!(window, column_view) present!(window) end exit(0) using Mousetrap main() do app::Application window = Window(app) widget = Button() box = Box(ORIENTATION_HORIZONTAL) for name in ["accent", "success", "warning", "error"] add_css!(""" $name:not(.opaque) { background-color: @$(name)_fg_color; } .$name.opaque { background-color: @$(name)_bg_color; color: @$(name)_fg_color; } """) end names = ["default", "accent", "success", "warning", "error"] column_view = ColumnView() first_column = push_back_column!(column_view, " ") for name in names push_back_column!(column_view, name) end #, Switch, CheckButton, ProgressBar, Spinner, Scale, SpinButton, Entry, Separator j = 1 for create_widget in [ () -> Button(Label("TEST")), () -> Switch(), () -> ProgressBar(), () -> Spinner(), () -> LevelBar(0, 1), () -> Scale(0, 1, 1), () -> Entry(), () -> Separator() ] local default_label = Label("default") local opaque_label = Label("opaque") for label in [default_label, opaque_label] add_css_class!(label, "dimmed") add_css_class!(label, "caption") end set_widget_at!(column_view, first_column, j, default_label) set_widget_at!(column_view, first_column, j + 1, opaque_label) for name in names i = j column = get_column_with_title(column_view, name) local non_opaque = create_widget() add_css_class!(non_opaque, name) set_widget_at!(column_view, column, i, non_opaque) try set_child!(non_opaque, Label("TEST")) catch end i = i + 1 local opaque = create_widget() add_css_class!(opaque, name) add_css_class!(opaque, "opaque") set_widget_at!(column_view, column, i, opaque) try set_child!(opaque, Label("TEST")) catch end for widget in [non_opaque, opaque] #set_alignment!(widget, ALIGNMENT_CENTER) #set_expand!(widget, false) end end j = j + 2 end set_child!(window, Viewport(column_view)) present!(window) end exit(0) main() do app::Application window = Window(app) set_title!(window, "mousetrap.jl") # animate a gradual fade-out button = Button(Label("SPIN")) aspect_frame = AspectFrame(1.0, button) set_margin!(aspect_frame, 10) transform_bin = TransformBin() set_child!(transform_bin, aspect_frame) animation = Animation(transform_bin, seconds(1)) on_tick!(animation, transform_bin) do self::Animation, value::AbstractFloat, transform_bin::TransformBin reset!(transform_bin) rotate!(transform_bin, degrees(value * 360)) scale!(transform_bin, 1 + value, 1 + value) end on_done!(animation, transform_bin) do self::Animation, transform_bin::TransformBin reset!(transform_bin) end # start animation when button is clicked connect_signal_clicked!(button, animation) do self::Button, animation::Animation play!(animation) end set_child!(window, transform_bin) present!(window) end #= @static if false struct TexturePage <: Widget center_box::CenterBox label::Label render_area::RenderArea texture::Texture shape::Shape function TexturePage(label::String, image::Image, wrap_mode::TextureWrapMode) out = new( CenterBox(ORIENTATION_VERTICAL), Label("" * label * ""), RenderArea(), Texture(), Rectangle(Vector2f(-1, 1), Vector2f(2, 2)) ) set_expand!(out.render_area, true) set_size_request!(out.render_area, Vector2f(150, 150)) set_start_child!(out.center_box, AspectFrame(1.0, Frame(out.render_area))) set_end_child!(out.center_box, out.label) set_margin!(out.label, 10) create_from_image!(out.texture, image) set_wrap_mode!(out.texture, wrap_mode) set_texture!(out.shape, out.texture) set_vertex_texture_coordinate!(out.shape, 1, Vector2f(-1, -1)) set_vertex_texture_coordinate!(out.shape, 2, Vector2f(2, -1)) set_vertex_texture_coordinate!(out.shape, 3, Vector2f(2, 2)) set_vertex_texture_coordinate!(out.shape, 4, Vector2f(-1, 2)) add_render_task!(out.render_area, RenderTask(out.shape)) return out end end Mousetrap.get_top_level_widget(x::TexturePage) = x.center_box main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") render_area = RenderArea() image = Image() create_from_file!(image, "docs/src/assets/logo.png") size = get_size(image) hue_step = 1 / size.x for i in 1:size.y for j in 1:size.x if get_pixel(image, i, j).a == 0 set_pixel!(image, i, j, HSVA(j * hue_step, 1, 1, 1)) end end end box = Box(ORIENTATION_HORIZONTAL) set_spacing!(box, 10) set_margin!(box, 10) push_back!(box, TexturePage("ZERO", image, TEXTURE_WRAP_MODE_ZERO)) push_back!(box, TexturePage("ONE", image, TEXTURE_WRAP_MODE_ONE)) push_back!(box, TexturePage("STRETCH", image, TEXTURE_WRAP_MODE_STRETCH)) push_back!(box, TexturePage("REPEAT", image, TEXTURE_WRAP_MODE_REPEAT)) push_back!(box, TexturePage("MIRROR", image, TEXTURE_WRAP_MODE_MIRROR)) set_child!(window, box) present!(window) end end # compound widget that is an entire stack page, render area with the shape + a label below struct ShapePage <: Widget separator::Separator render_area::RenderArea overlay::Overlay frame::Frame aspect_frame::AspectFrame label::Label center_box::CenterBox function ShapePage(title::String, shape::Shape) out = new( Separator(), RenderArea(), Overlay(), Frame(), AspectFrame(1.0), Label(title), CenterBox(ORIENTATION_VERTICAL) ) set_child!(out.overlay, out.separator) add_overlay!(out.overlay, out.render_area) set_child!(out.frame, out.overlay) set_child!(out.aspect_frame, out.frame) set_center_child!(out.center_box, out.aspect_frame) set_end_child!(out.center_box, out.label) set_size_request!(out.aspect_frame, Vector2f(150, 150)) set_expand!(out.aspect_frame, true) set_margin!(out.aspect_frame, 10) set_margin!(out.label, 10) add_render_task!(out.render_area, RenderTask(shape)) radius = 0.001 n_vertices = get_n_vertices(shape) for i in 1:n_vertices pos = get_vertex_position(shape, i) to_add = Circle(Vector2f(pos.x, pos.y), radius, 16) set_color!(to_add, HSVA(i / n_vertices, 1, 1, 1)) add_render_task!(out.render_area, RenderTask(to_add)) end # Widget hierarchy for clarity: # # CenterBox \ # AspectFrame \ # Frame \ # Overlay \ # RenderArea # Separator # Label return out end end Mousetrap.get_top_level_widget(x::ShapePage) = x.center_box main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") shapes = [ "Point" => Point( Vector2f(0, 0) ), "Points" => Points([ Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5), Vector2f(0.0, -0.5) ]), "Line" => Line( Vector2f(-0.5, +0.5), Vector2f(+0.5, -0.5) ), "Lines" =>Lines([ Vector2f(-0.5, 0.5) => Vector2f(0.5, -0.5), Vector2f(-0.5, -0.5) => Vector2f(0.5, 0.5) ]), "LineStrip" => LineStrip([ Vector2f(-0.5, +0.5), Vector2f(+0.5, +0.5), Vector2f(+0.5, -0.5), Vector2f(-0.5, -0.5) ]), "Wireframe" => Wireframe([ Vector2f(-0.5, +0.5), Vector2f(+0.5, +0.5), Vector2f(+0.5, -0.5), Vector2f(-0.5, -0.5) ]), "Triangle" => Triangle( Vector2f(-0.5, 0.5), Vector2f(+0.5, 0.5), Vector2f(0.0, -0.5) ), "Rectangle" => Rectangle( Vector2f(-0.5, 0.5), Vector2f(1, 1) ), "Circle" => Circle( Vector2f(0, 0), 0.5, 32 ), "Ellipse" => Ellipse( Vector2f(0, 0), 0.6, 0.4, 32 ), "Polygon" => Polygon([ Vector2f(0.0, 0.75), Vector2f(0.75, 0.25), Vector2f(0.5, -0.75), Vector2f(-0.5, -0.5), Vector2f(-0.75, 0.0) ]), "RectangularFrame" => RectangularFrame( Vector2f(-0.5, 0.5), Vector2f(1, 1), 0.15, 0.15, ), "CircularRing" => CircularRing( Vector2f(0, 0), 0.5, 0.15, 32 ), "EllipticalRing" => EllipticalRing( Vector2f(0, 0), 0.6, 0.4, 0.15, 0.15, 32 ) ] # add button that allow switching between light and dark theme button = Button() connect_signal_clicked!(button, app) do self::Button, app::Application current = get_current_theme(app) # swap light to dark, dark to light if current == THEME_DEFAULT_DARK next = THEME_DEFAULT_LIGHT elseif current == THEME_DEFAULT_LIGHT next = THEME_DEFAULT_DARK elseif current == THEME_HIGH_CONTRAST_DARK next = THEME_HIGH_CONTRAST_LIGHT elseif current == THEME_HIGH_CONTRAST_LIGHT next = THEME_HIGH_CONTRAST_DARK end set_current_theme!(app, next) # also swap colors to contrast well for pair in shapes shape = pair[2] if next == THEME_DEFAULT_LIGHT || next == THEME_HIGH_CONTRAST_LIGHT set_color!(shape, RGBA(0, 0, 0, 1)) else set_color!(shape, RGBA(1, 1, 1, 1)) end end end set_tooltip_text!(button, "Click to Swap UI Theme") set_has_frame!(button, false) push_front!(get_header_bar(window), button) # add outline shapes for shapes that have a volume for name in ["Triangle", "Rectangle", "Circle", "Ellipse", "Polygon", "RectangularFrame", "CircularRing", "EllipticalRing"] # get shape of pairs whos first element is equal to `name` shape = (shapes[findfirst(x -> x[1] == name, shapes)]).second # add the new outline to shapes push!(shapes, (name * " (Outline)" => Outline(shape))) end # create the stack and fill it with pages, in order stack = Stack() for (name, shape) in shapes add_child!(stack, ShapePage(name, shape), name) end # create side bar to be able to pick the stack page viewport = Viewport(StackSidebar(stack)) set_propagate_natural_width!(viewport, true) set_horizontal_scrollbar_policy!(viewport, SCROLLBAR_VISIBILITY_POLICY_NEVER) # forces witdh of viewport to be equal to width of stack side bar at all time # make it so stack page expands instead of side-bar set_expand_horizontally!(stack, true) set_expand_horizontally!(viewport, false) # add a revealer that can hide the side bar for screenshots revealer = Revealer(viewport) set_transition_type!(revealer, REVEALER_TRANSITION_TYPE_SLIDE_RIGHT) # Allow hiding / showing the sidebar by pressing `Control + H` # To do this, we create an action that triggers the revealer, then add a shortcut revealer_action = Action("trigger_revealer", app) set_function!(revealer_action, revealer) do self::Action, revealer::Revealer set_is_revealed!(revealer, !get_is_revealed(revealer)) end add_shortcut!(revealer_action, "h"); set_listens_for_shortcut_action!(window, revealer_action) set_tooltip_text!(revealer, "press Control + H to hide this element.") key_controller = KeyEventController() connect_signal_key_released!(key_controller, stack) do _::KeyEventController, code::KeyCode, modifiers::ModifierState, stack::Stack stack_model = get_selection_model(stack) current = get_selection(stack_model)[1] if code == KEY_Left || code == KEY_Up && (current > 1) select!(stack_model, current - 1) elseif code == KEY_Right || code == KEY_Down && (current < get_n_items(stack_model)) select!(stack_model, current + 1) end end add_controller!(button, key_controller) # main layout box = hbox(stack, revealer) set_child!(window, box) present!(window) end @static if false main() do app::Application window = Window(app) set_title!(window, "Mousetrap.jl") # create render areas with different MSAA modes left_area = RenderArea(ANTI_ALIASING_QUALITY_OFF) right_area = RenderArea(ANTI_ALIASING_QUALITY_BEST) # paned that will hold both areas paned = Paned(ORIENTATION_HORIZONTAL) # create singular shape, which will be shared between areas shape = Rectangle(Vector2f(-0.5, 0.5), Vector2f(1, 1)) add_render_task!(left_area, RenderTask(shape)) add_render_task!(right_area, RenderTask(shape)) # rotate shape 1° per frame set_tick_callback!(paned) do clock::FrameClock # rotate shape rotate!(shape, degrees(1), get_centroid(shape)) # force redraw for both areas queue_render(left_area) queue_render(right_area) # continue callback indefinitely return TICK_CALLBACK_RESULT_CONTINUE end # setup window layout for viewing for area in [left_area, right_area] set_size_request!(area, Vector2f(150, 150)) end # caption labels left_label = Label("OFF") right_label = Label("BEST") for label in [left_label, right_label] set_margin!(label, 10) end # format paned set_start_child_shrinkable!(paned, false) set_end_child_shrinkable!(paned, false) set_start_child!(paned, vbox(AspectFrame(1.0, left_area), left_label)) set_end_child!(paned, vbox(AspectFrame(1.0, right_area), right_label)) # present set_child!(window, paned) present!(window) end end # @static if =# end ================================================ FILE: test/makie_test.jl ================================================ """ Minimum working example showing how to display a GLMakie plot using Mousetrap `GLArea` """ module MousetrapMakie export GLMakieArea, create_glmakie_screen using Mousetrap using ModernGL, GLMakie, Colors, GeometryBasics, ShaderAbstractions using GLMakie: empty_postprocessor, fxaa_postprocessor, OIT_postprocessor, to_screen_postprocessor using GLMakie.GLAbstraction using GLMakie.Makie """ ## GLMakieArea <: Widget `GLArea` wrapper that automatically connects all necessary callbacks in order for it to be used as a GLMakie render target. Use `create_glmakie_screen` to initialize a screen you can render to using Makie from this widget. Note that `create_glmakie_screen` needs to be called **after** `GLMakieArea` has been realized, as only then will the internal OpenGL context be available. See the example below. ## Constructors `GLMakieArea()` ## Signals (no unique signals) ## Fields (no public fields) ## Example ``` using Mousetrap, MousetrapMakie main() do app::Application window = Window(app) canvas = GLMakieArea() set_size_request!(canvas, Vector2f(200, 200)) set_child!(window, canvas) # use optional ref to delay screen allocation after `realize` screen = Ref{Union{Nothing, GLMakie.Screen{GLMakieArea}}}(nothing) connect_signal_realize!(canvas) do self screen[] = create_glmakie_screen(canvas) display(screen[], scatter(1:4)) return nothing end present!(window) end ``` """ mutable struct GLMakieArea <: Widget glarea::GLArea # wrapped native widget framebuffer_id::Ref{Int} # set by render callback, used in MousetrapMakie.create_glmakie_screen framebuffer_size::Vector2i # set by resize callback, used in GLMakie.framebuffer_size function GLMakieArea() glarea = GLArea() set_auto_render!(glarea, false) # should `render` be emitted everytime the widget is drawn connect_signal_render!(on_makie_area_render, glarea) connect_signal_resize!(on_makie_area_resize, glarea) return new(glarea, Ref{Int}(0), Vector2i(0, 0)) end end Mousetrap.get_top_level_widget(x::GLMakieArea) = x.glarea # maps hash(GLMakieArea) to GLMakie.Screen const screens = Dict{UInt64, GLMakie.Screen}() # maps hash(GLMakieArea) to Scene, used in `on_makie_area_resize` const scenes = Dict{UInt64, GLMakie.Scene}() # render callback: if screen is open, render frame to `GLMakieArea`s OpenGL context function on_makie_area_render(self, context) key = Base.hash(self) if haskey(screens, key) screen = screens[key] if !isopen(screen) return false end screen.render_tick[] = nothing glarea = screen.glscreen glarea.framebuffer_id[] = glGetIntegerv(GL_FRAMEBUFFER_BINDING) GLMakie.render_frame(screen) end return true end # resize callback: update framebuffer size, necessary for `GLMakie.framebuffer_size` function on_makie_area_resize(self, w, h) key = Base.hash(self) if haskey(screens, key) screen = screens[key] glarea = screen.glscreen glarea.framebuffer_size.x = w glarea.framebuffer_size.y = h queue_render(glarea.glarea) end if haskey(scenes, key) scene = scenes[key] scene.events.window_area[] = Recti(0, 0, glarea.framebuffer_size.x, glarea.framebuffer_size.y) scene.events.window_dpi[] = Mousetrap.calculate_monitor_dpi(glarea) end return nothing end # resolution of `GLMakieArea` OpenGL framebuffer GLMakie.framebuffer_size(self::GLMakieArea) = (self.framebuffer_size.x, self.framebuffer_size.y) # forward retina scale factor from GTK4 back-end GLMakie.retina_scaling_factor(w::GLMakieArea) = Mousetrap.get_scale_factor(w) # resolution of `GLMakieArea` widget itself` function GLMakie.window_size(w::GLMakieArea) size = get_natural_size(w) size.x = size.x * GLMakie.retina_scaling_factor(w) size.y = size.y * GLMakie.retina_scaling_factor(w) return (size.x, size.y) end # calculate screen size and dpi function Makie.window_area(scene::Scene, screen::GLMakie.Screen{GLMakieArea}) glarea = screen.glscreen scenes[hash(glarea)] = scene end # resize request by makie will be ignored function GLMakie.resize_native!(native::GLMakieArea, resolution...) # noop end # bind `GLMakieArea` OpenGL context ShaderAbstractions.native_switch_context!(a::GLMakieArea) = make_current(a.glarea) # check if `GLMakieArea` OpenGL context is still valid, it is while `GLMakieArea` widget stays realized ShaderAbstractions.native_context_alive(x::GLMakieArea) = get_is_realized(x) # destruction callback ignored, lifetime is managed by mousetrap instead function GLMakie.destroy!(w::GLMakieArea) # noop end # check if canvas is still realized GLMakie.was_destroyed(window::GLMakieArea) = !get_is_realized(window) # check if canvas should signal it is open Base.isopen(w::GLMakieArea) = !GLMakie.was_destroyed(w) # react to makie screen visibility request GLMakie.set_screen_visibility!(screen::GLMakieArea, bool) = bool ? show(screen.glarea) : hide!(screen.glarea) # apply glmakie config function GLMakie.apply_config!(screen::GLMakie.Screen{GLMakieArea}, config::GLMakie.ScreenConfig; start_renderloop=true) @warn "In MousetrapMakie: GLMakie.apply_config!: This feature is not yet implemented, ignoring config" # cf https://github.com/JuliaGtk/Gtk4Makie.jl/blob/main/src/screen.jl#L111 return screen end # screenshot framebuffer function Makie.colorbuffer(screen::GLMakie.Screen{GLMakieArea}, format::Makie.ImageStorageFormat = Makie.JuliaNative) @warn "In MousetrapMakie: GLMakie.colorbuffer: This feature is not yet implemented, returning framecache" # cf https://github.com/JuliaGtk/Gtk4Makie.jl/blob/main/src/screen.jl#L147 return screen.framecache end # ignore makie event model, use the mousetrap event controllers instead Makie.window_open(::Scene, ::GLMakieArea) = nothing Makie.disconnect!(::GLMakieArea, f) = nothing GLMakie.pollevents(::GLMakie.Screen{GLMakieArea}) = nothing Makie.mouse_buttons(::Scene, ::GLMakieArea) = nothing Makie.keyboard_buttons(::Scene, ::GLMakieArea) = nothing Makie.dropped_files(::Scene, ::GLMakieArea) = nothing Makie.unicode_input(::Scene, ::GLMakieArea) = nothing Makie.mouse_position(::Scene, ::GLMakie.Screen{GLMakieArea}) = nothing Makie.scroll(::Scene, ::GLMakieArea) = nothing Makie.hasfocus(::Scene, ::GLMakieArea) = nothing Makie.entered_window(::Scene, ::GLMakieArea) = nothing """ ``` create_gl_makie_screen(::GLMakieArea; screen_config...) -> GLMakie.Screen{GLMakieArea} ``` For a `GLMakieArea`, create a `GLMakie.Screen` that can be used to display makie graphics """ function create_glmakie_screen(area::GLMakieArea; screen_config...) if !get_is_realized(area) log_critical("MousetrapMakie", "In MousetrapMakie.create_glmakie_screen: GLMakieArea is not yet realized, it's internal OpenGL context cannot yet be accessed") end config = Makie.merge_screen_config(GLMakie.ScreenConfig, screen_config) set_is_visible!(area, config.visible) set_expand!(area, true) # quote from https://github.com/JuliaGtk/Gtk4Makie.jl/blob/main/src/screen.jl#L342 shader_cache = GLAbstraction.ShaderCache(area) ShaderAbstractions.switch_context!(area) fb = GLMakie.GLFramebuffer((1, 1)) # resized on GLMakieArea realization later postprocessors = [ config.ssao ? ssao_postprocessor(fb, shader_cache) : empty_postprocessor(), OIT_postprocessor(fb, shader_cache), config.fxaa ? fxaa_postprocessor(fb, shader_cache) : empty_postprocessor(), to_screen_postprocessor(fb, shader_cache, area.framebuffer_id) ] screen = GLMakie.Screen( area, shader_cache, fb, config, false, nothing, Dict{WeakRef, GLMakie.ScreenID}(), GLMakie.ScreenArea[], Tuple{GLMakie.ZIndex, GLMakie.ScreenID, GLMakie.RenderObject}[], postprocessors, Dict{UInt64, GLMakie.RenderObject}(), Dict{UInt32, Makie.AbstractPlot}(), false, ) # end quote hash = Base.hash(area.glarea) screens[hash] = screen set_tick_callback!(area.glarea) do clock::FrameClock if GLMakie.requires_update(screen) queue_render(area.glarea) end if GLMakie.was_destroyed(area) return TICK_CALLBACK_RESULT_DISCONTINUE else return TICK_CALLBACK_RESULT_CONTINUE end end return screen end end # test using Mousetrap, .MousetrapMakie, GLMakie main() do app::Application window = Window(app) set_title!(window, "Mousetrap x Makie") canvas = GLMakieArea() set_size_request!(canvas, Vector2f(200, 200)) set_child!(window, canvas) # use optional ref to delay screen allocation after `realize` screen = Ref{Union{Nothing, GLMakie.Screen{GLMakieArea}}}(nothing) connect_signal_realize!(canvas) do self screen[] = create_glmakie_screen(canvas) display(screen[], scatter(rand(123))) return nothing end present!(window) end ================================================ FILE: test/runtests.jl ================================================ # # Author: C. Cords (mail@clemens-cords.com) # https://github.com/clemapfel/mousetrap.jl # # Copyright © 2023, Licensed under lGPL3-0 # using Test using Mousetrap ### GLOBALS app_id = "mousetrap.runtests.jl" app = Ref{Union{Application, Nothing}}(nothing) window = Ref{Union{Window, Nothing}}(nothing) icon = Ref{Union{Icon, Nothing}}(nothing) ### MAIN Container = Stack function add_page!(container::Container, title::String, x::Widget) add_child!(container, title, x) end ### function test_action(::Container) @testset "Action" begin action_id = "test.action" action = Action(action_id, Main.app[]) Base.show(devnull, action) triggered = Ref{Bool}(false) function on_activate(::Action, triggered::Ref{Bool}) triggered[] = true return nothing end set_function!(on_activate, action, triggered) connect_signal_activated!(on_activate, action, triggered) activate!(action) @test triggered[] == true @test get_id(action) == action_id @test get_enabled(action) == true set_enabled!(action, false) @test get_enabled(action) == false add_shortcut!(action, "a") add_shortcut!(action, "b") shortcuts = get_shortcuts(action) @test "a" in shortcuts @test "b" in shortcuts clear_shortcuts!(action) @test isempty(get_shortcuts(action)) end end ### ANIMATION function test_animation(widget::Container) @testset "Animation" begin animation = Animation(widget, seconds(1)) Base.show(devnull, animation) @test get_state(animation) == ANIMATION_STATE_IDLE @test get_duration(animation) == seconds(1) set_duration!(animation, seconds(2)) @test get_duration(animation) = seconds(2) @test get_value(animation) == 0 @test get_lower!(animation) == 0 @test get_upper!(animation) == 1 set_lower!(animation, -1) set_upper!(animation, 2) @test get_lower!(animation) == -1 @test get_upper!(animation) == 2 @test get_repeat_count(animation) == 1 set_repeat_count!(animation, 0) @test get_repeat_count(animation) == 0 @test get_is_reversed(animation) == false set_is_reversed!(animation, true) @test get_is_reversed(animation) == true set_timing_function!(animation, ANIMATION_TIMING_FUNCTION_ELASTIC_SIGMOID) @test get_timing_function(animation) == ANIMATION_TIMING_FUNCTION_ELASTIC_SIGMOID on_tick!(animation) do self::Animation, value::AbstractFloat end on_done!(animation) do self::Animation end play!(animation) pause!(animation) reset!(animation) end end ### ADJUSTMENT function test_adjustment(::Container) @testset "Adjustment" begin adjustment = Adjustment(0, -1, 2, 0.05) Base.show(devnull, adjustment) properties_changed_called = Ref{Bool}(false) connect_signal_properties_changed!(adjustment, properties_changed_called) do ::Adjustment, properties_changed_called properties_changed_called[] = true return nothing end value_changed_called = Ref{Bool}(false) connect_signal_value_changed!(adjustment, value_changed_called) do ::Adjustment, value_changed_called value_changed_called[] = true return nothing end @test get_lower(adjustment) == -1.0f0 set_lower!(adjustment, 0) @test get_lower(adjustment) == 0.0f0 @test get_upper(adjustment) == 2.0f0 set_upper!(adjustment, 1) @test get_upper(adjustment) == 1.0f0 @test get_step_increment(adjustment) == 0.05f0 set_step_increment!(adjustment, 0.01) @test get_step_increment(adjustment) == 0.01f0 @test get_value(adjustment) == 0.0f0 set_value!(adjustment, 0.5) @test get_value(adjustment) == 0.5f0 @test properties_changed_called[] == true @test value_changed_called[] == true end end function test_application(::Container) @testset "Application" begin app = Main.app[] Base.show(devnull, app) @test get_id(app) == Main.app_id action_id = "application.test_action" action = Action(action_id, app) do ::Action end add_action!(app, action) @test has_action(app, action_id) == true @test get_id(get_action(app, action_id)) == action_id remove_action!(app, action_id) @test has_action(app, action_id) == false @test get_is_holding(app) == false hold!(app) @test get_is_holding(app) == true release!(app) @test get_is_holding(app) == false @test get_is_marked_as_busy(app) == false mark_as_busy!(app) @test get_is_marked_as_busy(app) == true unmark_as_busy!(app) @test get_is_marked_as_busy(app) == false @test get_current_theme(app) isa Theme set_current_theme!(app, THEME_DEFAULT_LIGHT) @test get_current_theme(app) == THEME_DEFAULT_LIGHT end end function test_alert_dialog(::Container) @testset "AlertDialog" begin message = "message" detailed_message = "detailed message" alert_dialog = AlertDialog(message, detailed_message) Base.show(devnull, alert_dialog) button_label = "Label" id = add_button!(alert_dialog, button_label) @test id == 1 @test get_button_label(alert_dialog, 1) == button_label new_label = "new_label" set_button_label!(alert_dialog, 1, new_label) @test get_button_label(alert_dialog, 1) == new_label set_extra_widget!(alert_dialog, Separator()) remove_extra_widget!(alert_dialog) @test get_n_buttons(alert_dialog) == 1 @test get_message(alert_dialog) == message @test get_detailed_description(alert_dialog) == detailed_message @test get_is_modal(alert_dialog) == true set_is_modal!(alert_dialog, false) @test get_is_modal(alert_dialog) == false on_selection!(alert_dialog) do self::AlertDialog, id::Integer end present!(alert_dialog) close!(alert_dialog) end end function test_angle(::Container) @testset "Angle" begin angle = degrees(90) Base.show(devnull, angle) @test isapprox(as_degrees(radians(as_radians(degrees(90)))), 90.0) end end function test_aspect_frame(::Container) @testset "AspectFrame" begin aspect_frame = AspectFrame(1.0) Base.show(devnull, aspect_frame) @test Mousetrap.is_native_widget(aspect_frame) @test get_child_x_alignment(aspect_frame) == 0.5 @test get_child_y_alignment(aspect_frame) == 0.5 set_child_x_alignment!(aspect_frame, 1.0) set_child_y_alignment!(aspect_frame, 1.0) @test get_child_x_alignment(aspect_frame) == 1.0 @test get_child_y_alignment(aspect_frame) == 1.0 @test get_ratio(aspect_frame) == 1.0 set_ratio!(aspect_frame, 1.5) @test get_ratio(aspect_frame) == 1.5 end end function test_button(::Container) @testset "Button" begin button = Button() Base.show(devnull, button) @test Mousetrap.is_native_widget(button) set_child!(button, Label("Button")) @test get_has_frame(button) set_has_frame!(button, false) @test !get_has_frame(button) @test get_is_circular(button) == false set_is_circular!(button, true) @test get_is_circular(button) == true clicked_called = Ref{Bool}(false) connect_signal_clicked!(button) do ::Button clicked_called[] = true return nothing end activate!(button) emit_signal_clicked(button) @test clicked_called[] == true end end function test_box(::Container) @testset "Box" begin box = Box(ORIENTATION_HORIZONTAL) Base.show(devnull, box) @test Mousetrap.is_native_widget(box) @test get_homogeneous(box) == false set_homogeneous!(box, true) @test get_homogeneous(box) == true @test get_orientation(box) == ORIENTATION_HORIZONTAL set_orientation!(box, ORIENTATION_VERTICAL) @test get_orientation(box) == ORIENTATION_VERTICAL start = Separator() push_front!(box, start) push_back!(box, Separator()) insert_after!(box, Separator(), start) remove!(box, start) @test get_n_items(box) == 2 @test get_spacing(box) == 0 set_spacing!(box, 10) @test get_spacing(box) == 10 end end function test_transform_bin(::Container) @testset "TransformBin" begin bin = TransformBin() @test Mouestrap.is_native_widget(bin) Base.show(devnull, bin) set_child!(bin, Separator()) rotate!(bin, degrees(10)) translate!(bin, Vector2f(10, 10)) scale!(bin, 1.1, 0.9) skew!(bin, 1.1, 0.9) remove_child!(bin) end end function test_center_box(::Container) @testset "CenterBox" begin center_box = CenterBox(ORIENTATION_HORIZONTAL) Base.show(devnull, center_box) @test Mousetrap.is_native_widget(center_box) @test get_orientation(center_box) == ORIENTATION_HORIZONTAL set_orientation!(center_box, ORIENTATION_VERTICAL) @test get_orientation(center_box) == ORIENTATION_VERTICAL set_start_child!(center_box, Separator()) remove_start_child!(center_box) set_center_child!(center_box, Separator()) remove_center_child!(center_box) set_end_child!(center_box, Separator()) remove_end_child!(center_box) end end function test_check_button(::Container) @testset "CheckButton" begin check_button = CheckButton() Base.show(devnull, check_button) @test Mousetrap.is_native_widget(check_button) toggled_called = Ref{Bool}(false) connect_signal_toggled!(check_button, toggled_called) do _, toggled_called toggled_called[] = true return nothing end set_is_active!(check_button, true) @test get_is_active(check_button) == true set_state!(check_button, CHECK_BUTTON_STATE_INACTIVE) @test get_state(check_button) == CHECK_BUTTON_STATE_INACTIVE @test get_is_active(check_button) == false set_state!(check_button, CHECK_BUTTON_STATE_ACTIVE) @test get_state(check_button) == CHECK_BUTTON_STATE_ACTIVE @test get_is_active(check_button) == true set_state!(check_button, CHECK_BUTTON_STATE_INCONSISTENT) @test get_state(check_button) == CHECK_BUTTON_STATE_INCONSISTENT @test get_is_active(check_button) == false set_child!(check_button, Separator()) remove_child!(check_button) end end function test_event_controller(this::Container) area = this function test_single_click_gesture(controller) end function test_event_controller(controller) @test get_propagation_phase(controller) != PROPAGATION_PHASE_NONE set_propagation_phase!(controller, PROPAGATION_PHASE_NONE) @test get_propagation_phase(controller) == PROPAGATION_PHASE_NONE if controller isa SingleClickGesture @test get_current_button(controller) isa ButtonID set_only_listens_to_button!(controller, BUTTON_ID_NONE) @test get_only_listens_to_button(controller) == BUTTON_ID_NONE @test get_touch_only(controller) == false set_touch_only!(controller, true) @test get_touch_only(controller) == true end end let controller = ClickEventController() Base.show(devnull, controller) @testset "ClickEventController" begin test_event_controller(controller) connect_signal_click_pressed!(controller) do self::ClickEventController, n_presses::Integer, x::AbstractFloat, y::AbstractFloat end @test get_signal_click_pressed_blocked(controller) == false connect_signal_click_released!(controller) do self::ClickEventController, n_presses::Integer, x::AbstractFloat, y::AbstractFloat end @test get_signal_click_released_blocked(controller) == false connect_signal_click_stopped!(controller) do self::ClickEventController end @test get_signal_click_stopped_blocked(controller) == false end add_controller!(area, controller) end let controller = DragEventController() Base.show(devnull, controller) @testset "DragEventController" begin test_event_controller(controller) connect_signal_drag_begin!(controller) do self::DragEventController, start_x::AbstractFloat, start_y::AbstractFloat end @test get_signal_drag_begin_blocked(controller) == false connect_signal_drag!(controller) do self::DragEventController, x_offset::AbstractFloat, y_offset::AbstractFloat end @test get_signal_drag_blocked(controller) == false connect_signal_drag_end!(controller) do self::DragEventController, x_offset::AbstractFloat, y_offset::AbstractFloat end @test get_signal_drag_end_blocked(controller) == false end add_controller!(area, controller) end let controller = FocusEventController() Base.show(devnull, controller) @testset "FocusEventController" begin test_event_controller(controller) connect_signal_focus_gained!(controller) do self::FocusEventController end @test get_signal_focus_gained_blocked(controller) == false connect_signal_focus_lost!(controller) do self::FocusEventController end @test get_signal_focus_lost_blocked(controller) == false end add_controller!(area, controller) end let controller = KeyEventController() Base.show(devnull, controller) @testset "KeyEventController" begin test_event_controller(controller) connect_signal_key_pressed!(controller) do self::KeyEventController, key::KeyCode, modifier::ModifierState end @test get_signal_key_pressed_blocked(controller) == false connect_signal_key_released!(controller) do self::KeyEventController, key::KeyCode, modifier::ModifierState end @test get_signal_key_released_blocked(controller) == false connect_signal_modifiers_changed!(controller) do self::KeyEventController, modifier::ModifierState end @test get_signal_modifiers_changed_blocked(controller) == false end add_controller!(area, controller) end let controller = LongPressEventController() Base.show(devnull, controller) @testset "LongPressEventController" begin test_event_controller(controller) connect_signal_pressed!(controller) do self::LongPressEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_pressed_blocked(controller) == false connect_signal_press_cancelled!(controller) do self::LongPressEventController end @test get_signal_press_cancelled_blocked(controller) == false end add_controller!(area, controller) end let controller = MotionEventController() Base.show(devnull, controller) @testset "MotionEventController" begin test_event_controller(controller) connect_signal_motion!(controller) do self::MotionEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_motion_blocked(controller) == false connect_signal_motion_enter!(controller) do self::MotionEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_motion_enter_blocked(controller) == false connect_signal_motion_leave!(controller) do self::MotionEventController end @test get_signal_motion_leave_blocked(controller) == false end add_controller!(area, controller) end let controller = PanEventController(ORIENTATION_HORIZONTAL) Base.show(devnull, controller) @testset "PanEventController" begin test_event_controller(controller) connect_signal_pan!(controller) do self::PanEventController, direction::PanDirection, offset::AbstractFloat end @test get_signal_pan_blocked(controller) == false @test get_orientation(controller) == ORIENTATION_HORIZONTAL set_orientation!(controller, ORIENTATION_VERTICAL) @test get_orientation(controller) == ORIENTATION_VERTICAL end add_controller!(area, controller) end let controller = PinchZoomEventController() Base.show(devnull, controller) @testset "PinchZoomController" begin test_event_controller(controller) connect_signal_scale_changed!(controller) do self::PinchZoomEventController, scale::AbstractFloat end @test get_signal_scale_changed_blocked(controller) == false end add_controller!(area, controller) end let controller = RotateEventController() Base.show(devnull, controller) @testset "RotateEventController" begin test_event_controller(controller) connect_signal_rotation_changed!(controller) do self::RotateEventController, angle_absolute::AbstractFloat, angle_delta::AbstractFloat end @test get_signal_rotation_changed_blocked(controller) == false end add_controller!(area, controller) end let controller = ScrollEventController(false) Base.show(devnull, controller) @testset "ScrollEventController" begin test_event_controller(controller) @test get_kinetic_scrolling_enabled(controller) == false set_kinetic_scrolling_enabled!(controller, true) @test get_kinetic_scrolling_enabled(controller) == true connect_signal_scroll_begin!(controller) do self::ScrollEventController end @test get_signal_scroll_begin_blocked(controller) == false connect_signal_scroll!(controller) do self::ScrollEventController, x_delta::AbstractFloat, y_delta::AbstractFloat end @test get_signal_scroll_blocked(controller) == false connect_signal_scroll_end!(controller) do self::ScrollEventController end @test get_signal_scroll_end_blocked(controller) == false connect_signal_kinetic_scroll_decelerate!(controller) do self::ScrollEventController, x_velocity::AbstractFloat, y_velocity::AbstractFloat end @test get_signal_kinetic_scroll_decelerate_blocked(controller) == false end add_controller!(area, controller) end let controller = ShortcutEventController() Base.show(devnull, controller) @testset "ShortcutEventController" begin test_event_controller(controller) @test get_scope(controller) == SHORTCUT_SCOPE_LOCAL set_scope!(controller, SHORTCUT_SCOPE_GLOBAL) @test get_scope(controller) == SHORTCUT_SCOPE_GLOBAL action = Action("test.action", Main.app[]) do self end add_action!(controller, action) remove_action!(controller, action) end add_controller!(area, controller) end let controller = StylusEventController() Base.show(devnull, controller) @testset "StylusEventController" begin test_event_controller(controller) connect_signal_stylus_up!(controller) do self::StylusEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_stylus_up_blocked(controller) == false connect_signal_stylus_down!(controller) do self::StylusEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_stylus_down_blocked(controller) == false connect_signal_proximity!(controller) do self::StylusEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_proximity_blocked(controller) == false connect_signal_motion!(controller) do self::StylusEventController, x::AbstractFloat, y::AbstractFloat end @test get_signal_motion_blocked(controller) == false @test get_hardware_id(controller) isa Csize_t @test get_tool_type(controller) isa ToolType @test has_axis(controller, DEVICE_AXIS_Y) isa Bool end add_controller!(area, controller) end let controller = SwipeEventController() Base.show(devnull, controller) @testset "SwipeEventController" begin test_event_controller(controller) connect_signal_swipe!(controller) do self::SwipeEventController, x_velocity::AbstractFloat, y_velocity::AbstractFloat end @test get_signal_swipe_blocked(controller) == false end add_controller!(area, controller) end end function test_clipboard(::Container) @testset "Clipboard" begin clipboard = get_clipboard(Main.window[]) Base.show(devnull, clipboard) set_string!(clipboard, "test") @test contains_string(clipboard) == true @test get_is_local(clipboard) == true get_string(clipboard) do self::Clipboard, value::String @test true return nothing end set_file!(clipboard, FileDescriptor(".")) @test contains_file(clipboard) == true @test get_is_local(clipboard) == true get_string(clipboard) do self::Clipboard, value::String @test true return nothing end set_image!(clipboard, Image(1, 1, RGBA(1, 0, 0, 1))) @test contains_image(clipboard) == true @test get_is_local(clipboard) == true get_image(clipboard) do self::Clipboard, value::Image @test true return nothing end end end function test_clamp_frame(::Container) @testset "ClampFrame" begin frame = ClampFrame(150) Base.show(devnull, frame) set_child!(frame, Separator()) remove_child!(frame) @test get_orientation(frame) == ORIENTATION_HORIZONTAL set_orientation!(frame, ORIENTATION_VERTICAL) @test get_orientation(frame) == ORIENTATION_VERTICAL @test get_maximum_size(frame) == 150 set_maximum_size!(frame, 50) @test get_maximum_size(frame) == 50 end end function test_time(::Container) @testset "Time" begin value = 1234 @test as_minutes(minutes(value)) == value @test as_seconds(seconds(value)) == value @test as_milliseconds(milliseconds(value)) == value @test as_microseconds(microseconds(value)) == value @test as_nanoseconds(nanoseconds(value)) == value Base.show(devnull, milliseconds(1234)) end @testset "Clock" begin clock = Clock() sleep(0.1) @test as_seconds(elapsed(clock)) > 0.0 @test as_seconds(restart!(clock)) > 0.0 Base.show(devnull, clock) end end function test_color_chooser(::Container) @testset "ColorChooser" begin color_chooser = ColorChooser() Base.show(devnull, color_chooser) on_accept!(color_chooser) do self::ColorChooser, color::RGBA end on_cancel!(color_chooser) do self::ColorChooser end @test get_color(color_chooser) isa RGBA @test get_is_modal(color_chooser) == true set_is_modal!(color_chooser, false) @test get_is_modal(color_chooser) == false @test get_title(color_chooser) == "" set_title!(color_chooser, "TEST") @test get_title(color_chooser) == "TEST" end end function test_column_view(::Container) @testset "ColumnViewTest" begin column_view = ColumnView() Base.show(devnull, column_view) @test Mousetrap.is_native_widget(column_view) push_back_column!(column_view, "column 01") push_front_column!(column_view, "column 03") column_name = "column 02" column = insert_column_at!(column_view, 1, column_name) push_back_row!(column_view, Label(""), Label(""), Label("")) push_front_row!(column_view, Label(""), Label(""), Label("")) insert_row_at!(column_view, 2, Label(""), Label(""), Label("")) @test get_title(column) == column_name new_title = "new title" set_title!(column, new_title) @test get_title(column) == new_title @test get_fixed_width(column) isa Float32 set_fixed_width!(column, 100) @test get_fixed_width(column) == 100 model = MenuModel() set_header_menu!(column, model) @test get_is_visible(column) == true set_is_visible!(column, false) @test get_is_visible(column) == false set_is_resizable!(column, true) @test get_is_resizable(column) == true @test has_column_with_title(column_view, new_title) == true other_column = get_column_with_title(column_view, new_title) @test get_title(other_column) == new_title remove_column!(column_view, get_column_at(column_view, 1)) end end function test_drop_down(::Container) @testset "DropDown" begin drop_down = DropDown() Base.show(devnull, drop_down) @test Mousetrap.is_native_widget(drop_down) label = "Label"; id_02 = push_back!(drop_down, label, label) do self::DropDown end @test get_item_at(drop_down, 1) == id_02 id_01 = push_front!(drop_down, label, label) do self::DropDown end id_03 = insert_at!(drop_down, 1, label) do self::DropDown end remove!(drop_down, id_03) set_selected!(drop_down, id_01) @test get_selected(drop_down) == id_01 @test get_always_show_arrow(drop_down) == true set_always_show_arrow!(drop_down, false) @test get_always_show_arrow(drop_down) == false end end function test_entry(::Container) @testset "Entry" begin entry = Entry() Base.show(devnull, entry) @test Mousetrap.is_native_widget(entry) activate_called = Ref{Bool}(false) connect_signal_activate!(entry, activate_called) do entry::Entry, activate_called activate_called[] = true return nothing end text_changed_called = Ref{Bool}(false) connect_signal_text_changed!(entry, text_changed_called) do entry::Entry, text_changed_called text_changed_called[] = true return nothing end @test get_has_frame(entry) == true set_has_frame!(entry, false) @test get_has_frame(entry) == false @test get_max_width_chars(entry) == 0 set_max_width_chars!(entry, 64) @test get_max_width_chars(entry) == 64 @test get_text(entry) == "" set_text!(entry, "text") @test get_text(entry) == "text" @test get_text_visible(entry) == true set_text_visible!(entry, false) @test get_text_visible(entry) == false set_primary_icon!(entry, Main.icon[]) set_secondary_icon!(entry, Main.icon[]) remove_primary_icon!(entry) remove_secondary_icon!(entry) # TODO: activate! does not cause `activate` signal to be emitted, see gtk_widget_set_activate_signal emit_signal_activate(entry) @test activate_called[] == true @test text_changed_called[] == true end end function test_expander(::Container) @testset "Expander" begin expander = Expander() Base.show(devnull, expander) @test Mousetrap.is_native_widget(expander) activate_called = Ref{Bool}(false) connect_signal_activate!(expander, activate_called) do self::Expander, activate_called activate_called[] = true return nothing end activate!(expander) @test activate_called[] == true set_child!(expander, Separator()) set_label_widget!(expander, Separator()) set_is_expanded!(expander, true) @test get_is_expanded(expander) == true remove_child!(expander) remove_label_widget!(expander) end end function test_file_chooser(::Container) filter = FileFilter("test") @testset "FileFilter" begin Base.show(devnull, filter) add_allow_all_supported_image_formats!(filter) add_allowed_mime_type!(filter, "text/plain") add_allowed_pattern!(filter, "*.jl") add_allowed_suffix!(filter, "jl") @test get_name(filter) == "test" end @testset "FileChooser" begin for action in instances(FileChooserAction) file_chooser = FileChooser(action) Base.show(devnull, file_chooser) add_filter!(file_chooser, filter) set_initial_filter!(file_chooser, filter) set_initial_file!(file_chooser, FileDescriptor(".")) set_initial_folder!(file_chooser, FileDescriptor(".")) set_initial_name!(file_chooser, "name"); set_accept_label!(file_chooser, "accept") @test get_accept_label(file_chooser) == "accept" @test get_is_modal(file_chooser) == true set_is_modal!(file_chooser, false) @test get_is_modal(file_chooser) == false @test get_title(file_chooser) == "" set_title!(file_chooser, "TEST") @test get_title(file_chooser) == "TEST" on_accept!(file_chooser) do x::FileChooser, files::Vector{FileDescriptor} end on_cancel!(file_chooser) do x::FileChooser end #present!(file_chooser) cancel!(file_chooser) end end end function test_file_descriptor(::Container) @testset "FileDescriptor" begin name = tempname() path = name * ".txt" file = open(path, "w+") write(file, "test\n") close(file) descriptor = FileDescriptor(".") Base.show(devnull, descriptor) create_from_path!(descriptor, path) @test exists(descriptor) @test get_name(descriptor)[end-3:end] == ".txt" @test get_path(descriptor) == path @test get_uri(descriptor) isa String @test get_path_relative_to(descriptor, descriptor) == "" @test exists(get_parent(descriptor)) @test is_file(descriptor) == true @test is_folder(descriptor) == false @test is_symlink(descriptor) == false # todo read_symlink @test is_executable(descriptor) == false @test get_content_type(descriptor) isa String # type is OS-specific @test query_info(descriptor, "standard::name") == get_name(descriptor) monitor = create_monitor(descriptor) on_file_changed!(monitor) do ::FileMonitor, ::FileMonitorEvent, ::FileDescriptor, ::FileDescriptor end cancel!(monitor) @test is_cancelled(monitor) gtk_file = FileDescriptor(tempname() * ".txt") create_file_at!(gtk_file) delete_at!(gtk_file) create_directory_at!(gtk_file) #move_to_trash!(gtk_file) # silenced because /tmp files can't be moved to the trash close(file) end end function test_fixed(::Container) @testset "Fixed" begin fixed = Fixed() Base.show(devnull, fixed) @test Mousetrap.is_native_widget(fixed) child = Label("(32, 32)") add_child!(fixed, child, Vector2f(32, 32)) set_child_position!(fixed, child, Vector2f(64, 64)) remove_child!(fixed, child) end end function test_flow_box(::Container) @testset "FlowBox" begin box = FlowBox(ORIENTATION_HORIZONTAL) Base.show(devnull, box) @test Mousetrap.is_native_widget(box) @test get_homogeneous(box) == false set_homogeneous!(box, true) @test get_homogeneous(box) == true @test get_orientation(box) == ORIENTATION_HORIZONTAL set_orientation!(box, ORIENTATION_VERTICAL) @test get_orientation(box) == ORIENTATION_VERTICAL start = Separator() push_front!(box, start) push_back!(box, Separator()) insert_at!(box, 1, Separator()) remove!(box, start) @test get_n_items(box) == 2 @test get_row_spacing(box) == 0 set_row_spacing!(box, 10) @test get_row_spacing(box) == 10 @test get_column_spacing(box) == 0 set_column_spacing!(box, 10) @test get_column_spacing(box) == 10 end end function test_frame(::Container) @testset "Frame" begin frame = Frame() Base.show(devnull, frame) @test Mousetrap.is_native_widget(frame) set_child!(frame, Separator()) @test get_label_x_alignment(frame) == 0.0 set_label_x_alignment!(frame, 0.5) @test get_label_x_alignment(frame) == 0.5 set_child!(frame, Separator()) set_label_widget!(frame, Label()) remove_child!(frame) remove_label_widget!(frame) end end function test_gl_transform(::Container) @testset "GLTransform" begin if !Mousetrap.MOUSETRAP_ENABLE_OPENGL_COMPONENT return end transform = GLTransform() Base.show(devnull, transform) rotate!(transform, degrees(90)) scale!(transform, 1.5, 1.5) translate!(transform, Vector2f(0.5, 0.5)) new_transform = combine_with(transform, GLTransform()) for x in 1:3 for y in 1:3 @test transform[x, y] == new_transform[x, y] end end reset!(new_transform) end end function test_gl_area(::Container) @testset "GLArea" begin area = GLArea() Base.show(devnull, area) connect_signal_render!(area) do self::GLArea, context::Ptr{Cvoid} return true end connect_signal_resize!(area) do self::GLArea, w, h end #make_current(area) # would print gtk warning because area is not yet realized queue_render(area) @test get_auto_render(area) isa Bool set_auto_render!(area, false) @test get_auto_render(area) == false end end function test_grid(::Container) @testset "Grid" begin grid = Grid() Base.show(devnull, grid) @test Mousetrap.is_native_widget(grid) @test get_column_spacing(grid) == 0.0 set_column_spacing!(grid, 2.0) @test get_column_spacing(grid) == 2.0 @test get_row_spacing(grid) == 0.0 set_row_spacing!(grid, 2.0) @test get_row_spacing(grid) == 2.0 @test get_orientation(grid) == ORIENTATION_HORIZONTAL set_orientation!(grid, ORIENTATION_VERTICAL) @test get_orientation(grid) == ORIENTATION_VERTICAL @test get_rows_homogeneous(grid) == false set_rows_homogeneous!(grid, true) @test get_rows_homogeneous(grid) == true @test get_columns_homogeneous(grid) == false set_columns_homogeneous!(grid, true) @test get_columns_homogeneous(grid) == true widget_01 = Separator() widget_02 = Separator() insert_at!(grid, widget_01, 1, 2, 3, 4) insert_next_to!(grid, widget_02, widget_01, RELATIVE_POSITION_RIGHT_OF, 3, 4) @test get_position(grid, widget_01) == Vector2i(1, 2) @test get_size(grid, widget_01) == Vector2i(3, 4) @test get_position(grid, widget_02) == Vector2i(4, 2) @test get_size(grid, widget_02) == Vector2i(3, 4) insert_row_at!(grid, 1) insert_column_at!(grid, 1) remove_row_at!(grid, 1) remove_column_at!(grid, 1) end end ### GRID_VIEW function test_grid_view(::Container) @testset "GridView" begin grid_view = GridView(ORIENTATION_HORIZONTAL,SELECTION_MODE_MULTIPLE) Base.show(devnull, grid_view) @test Mousetrap.is_native_widget(grid_view) @test get_orientation(grid_view) == ORIENTATION_HORIZONTAL set_orientation!(grid_view, ORIENTATION_VERTICAL) @test get_orientation(grid_view) == ORIENTATION_VERTICAL @test get_max_n_columns(grid_view) > 0 set_max_n_columns!(grid_view, 3) @test get_max_n_columns(grid_view) == 3 @test get_min_n_columns(grid_view) > 0 set_min_n_columns!(grid_view, 3) @test get_min_n_columns(grid_view) == 3 set_enable_rubberband_selection!(grid_view, true) @test get_enable_rubberband_selection(grid_view) == true @test get_single_click_activate(grid_view) == false set_single_click_activate!(grid_view, true) @test get_single_click_activate(grid_view) == true push_front!(grid_view, Separator()) push_back!(grid_view, Separator()) child = Separator() insert_at!(grid_view, 1, child) @test find(grid_view, child) == 1 remove!(grid_view, 1) @test get_n_items(grid_view) == 2 @test get_selection_model(grid_view) isa SelectionModel activate_item_called = Ref{Bool}(false) connect_signal_activate_item!(grid_view, activate_item_called) do self::GridView, index, activate_item_called activate_item_called[] = true return nothing end #@test activate_called[] == true end end ### COLORS function test_colors(::Container) @testset "Colors" begin color_rgba = RGBA(1, 0, 1, 1) color_hsva = HSVA(1, 0, 1, 1) Base.show(devnull, color_rgba) Base.show(devnull, color_hsva) @test color_rgba == hsva_to_rgba(rgba_to_hsva(color_rgba)) @test color_hsva == rgba_to_hsva(hsva_to_rgba(color_hsva)) end end ### HEADER_BAR function test_header_bar(::Container) @testset "HeaderBar" begin layout = "close:minimize,maximize" header_bar = HeaderBar(layout) @test Mousetrap.is_native_widget(header_bar) Base.show(devnull, header_bar) @test get_layout(header_bar) == layout set_layout!(header_bar, "") @test get_layout(header_bar) == "" @test get_show_title_buttons(header_bar) == true set_show_title_buttons!(header_bar, false) @test get_show_title_buttons(header_bar) == false widget = Separator() push_front!(header_bar, widget) push_back!(header_bar, Separator()) remove!(header_bar, widget) set_title_widget!(header_bar, Label("title")) remove_title_widget!(header_bar) end end function test_icon(::Container) theme = IconTheme(Main.window[]) names = get_icon_names(theme) # names will contain linux default theme, but is empty on windows @testset "Icon" begin if !isempty(names) icon_name = names[1] icon = Icon(theme, icon_name, 64) @test get_size(icon).x == 64 && get_size(icon).y == 64 else icon = Icon() end Base.show(devnull, icon) end @testset "IconTheme" begin Base.show(devnull, theme) add_resource_path!(theme, ".") set_resource_path!(theme, ".") # TODO: validate resource path end end ### IMAGE function test_image(::Container) @testset "Image" begin image = Image(1, 1, RGBA(1, 0, 1, 1)) Base.show(devnull, image) @test get_size(image) == Vector2i(1, 1) @test get_n_pixels(image) == 1 @test get_pixel(image, 1, 1) == RGBA(1, 0, 1, 1) set_pixel!(image, 1, 1, RGBA(0, 0, 1, 1)) @test get_pixel(image, 1, 1) == RGBA(0, 0, 1, 1) flipped = as_flipped(image, true, true) @test get_size(flipped) == Vector2i(1, 1) @test get_size(as_scaled(image, 2, 2, INTERPOLATION_TYPE_HYPERBOLIC)) == Vector2i(2, 2) @test get_size(as_cropped(image, 0, 0, 2, 2)) == Vector2i(2, 2) save_to_file(image, tempname() * ".png") end end ### IMAGE_DISPLAY function test_image_display(::Container) @testset "ImageDisplay" begin image_display = ImageDisplay() Base.show(devnull, image_display) @test Mousetrap.is_native_widget(image_display) image = Image(1, 1, RGBA(1, 0, 1, 1)) create_from_image!(image_display, image) @test get_size(image_display) == Vector2i(1, 1) clear!(image_display) create_from_icon!(image_display, Main.icon[]) @test get_size(image_display) == get_size(Main.icon[]) clear!(image_display) @test get_size(image_display) == Vector2i(0, 0) end end ### KEY_FILE function test_key_file(::Container) @testset "KeyFile" begin file = KeyFile() Base.show(devnull, file) group_name = "group_01" bool_key = "bool" float_key = "float" integer_key = "integer" string_key = "string" bool_list_key = "bool_list" float_list_key = "float_list" integer_list_key = "integer_list" color_key = "rgba" string_list_key = "string_list" group_comment = " group comment" key_comment = " key comment" create_from_string!(file, """ #$group_comment [$group_name] #$key_comment $bool_key = true $float_key = 1234.0 $integer_key = 1234 $string_key = abcd $bool_list_key = true;false $float_list_key = 1234.0;5678.0 $integer_list_key = 1234;5678 $string_list_key = abcd;efgh $color_key = 1.0;0.0;1.0;1.0 """) @test get_groups(file) == [group_name] @test has_group(file, group_name) == true @test isempty(get_keys(file, group_name)) == false @test has_key(file, group_name, color_key) == true @test get_comment_above(file, group_name) == group_comment @test get_comment_above(file, group_name, bool_key) == key_comment @test get_value(file, group_name, bool_key, Bool) == true @test get_value(file, group_name, float_key, Float32) == 1234.0 @test get_value(file, group_name, integer_key, Int64) == 1234 @test get_value(file, group_name, string_key, String) == "abcd" @test get_value(file, group_name, color_key, RGBA) == RGBA(1, 0, 1, 1) @test get_value(file, group_name, color_key, HSVA) == HSVA(1, 0, 1, 1) @test get_value(file, group_name, bool_list_key, Vector{Bool}) == [true, false] @test get_value(file, group_name, float_list_key, Vector{Float32}) == Float32[1234.0, 5678.0] @test get_value(file, group_name, integer_list_key, Vector{Int64}) == Int64[1234, 5678] @test get_value(file, group_name, string_list_key, Vector{String}) == ["abcd", "efgh"] set_value!(file, group_name, bool_key, false) @test get_value(file, group_name, bool_key, Bool) == false set_value!(file, group_name, float_key, Float32(999)) @test get_value(file, group_name, float_key, Float32) == 999 set_value!(file, group_name, integer_key, Int64(999)) @test get_value(file, group_name, integer_key, Int64) == 999 set_value!(file, group_name, string_key, String("none")) @test get_value(file, group_name, string_key, String) == "none" set_value!(file, group_name, color_key, RGBA(0, 0, 0, 1)) @test get_value(file, group_name, color_key, RGBA) == RGBA(0, 0, 0, 1) set_value!(file, group_name, bool_list_key, [false, true]) @test get_value(file, group_name, bool_list_key, Vector{Bool}) == [false, true] set_value!(file, group_name, float_list_key, [Float32(1), Float32(2)]) @test get_value(file, group_name, float_list_key, Vector{Float32}) == Float32[1.0, 2.0] set_value!(file, group_name, integer_list_key, [Int64(1), Int64(2)]) @test get_value(file, group_name, integer_list_key, Vector{Int64}) == Int64[1, 2] set_value!(file, group_name, string_list_key, ["none", "nothing"]) @test get_value(file, group_name, string_list_key, Vector{String}) == ["none", "nothing"] end end ### LABEL function test_label(::Container) @testset "Label" begin label_string = "label" label = Label(label_string) Base.show(devnull, label) @test Mousetrap.is_native_widget(label) @test get_ellipsize_mode(label) == ELLIPSIZE_MODE_NONE set_ellipsize_mode!(label, ELLIPSIZE_MODE_MIDDLE) @test get_ellipsize_mode(label) == ELLIPSIZE_MODE_MIDDLE @test get_justify_mode(label) == JUSTIFY_MODE_LEFT set_justify_mode!(label, JUSTIFY_MODE_CENTER) @test get_justify_mode(label) == JUSTIFY_MODE_CENTER @test get_is_selectable(label) == false set_is_selectable!(label, true) @test get_is_selectable(label) == true @test get_max_width_chars(label) == -1 set_max_width_chars!(label, 1234) @test get_max_width_chars(label) == 1234 @test get_text(label) == label_string set_text!(label, "new") @test get_text(label) == "new" @test get_use_markup(label) == true set_use_markup!(label, false) @test get_use_markup(label) == false @test get_wrap_mode(label) == LABEL_WRAP_MODE_NONE set_wrap_mode!(label, LABEL_WRAP_MODE_WORD_OR_CHAR) @test get_wrap_mode(label) == LABEL_WRAP_MODE_WORD_OR_CHAR @test get_x_alignment(label) == 0.5 set_x_alignment!(label, 0.0) @test get_x_alignment(label) == 0.0 @test get_y_alignment(label) == 0.5 set_y_alignment!(label, 0.0) @test get_y_alignment(label) == 0.0 end end ### LEVEL_BAR function test_level_bar(::Container) @testset "LevelBar" begin level_bar = LevelBar(0, 1) Base.show(devnull, level_bar) @test Mousetrap.is_native_widget(level_bar) @test get_max_value(level_bar) == 1 set_max_value!(level_bar, 2) @test get_max_value(level_bar) == 2 @test get_min_value(level_bar) == 0 set_min_value!(level_bar, 1) @test get_min_value(level_bar) == 1 @test get_mode(level_bar) == LEVEL_BAR_MODE_CONTINUOUS set_mode!(level_bar, LEVEL_BAR_MODE_DISCRETE) @test get_mode(level_bar) == LEVEL_BAR_MODE_DISCRETE @test get_orientation(level_bar) == ORIENTATION_HORIZONTAL set_orientation!(level_bar, ORIENTATION_VERTICAL) @test get_orientation(level_bar) == ORIENTATION_VERTICAL set_value!(level_bar, 1) @test get_value(level_bar) == 1 marker_label = "marker" add_marker!(level_bar, marker_label, 1) remove_marker!(level_bar, marker_label) end end ### LIST_VIEW function test_list_view(::Container) @testset "ListView" begin list_view = ListView(ORIENTATION_HORIZONTAL, SELECTION_MODE_MULTIPLE) Base.show(devnull, list_view) @test Mousetrap.is_native_widget(list_view) @test get_orientation(list_view) == ORIENTATION_HORIZONTAL set_orientation!(list_view, ORIENTATION_VERTICAL) @test get_orientation(list_view) == ORIENTATION_VERTICAL set_enable_rubberband_selection!(list_view, true) @test get_enable_rubberband_selection(list_view) == true @test get_single_click_activate(list_view) == false set_single_click_activate!(list_view, true) @test get_single_click_activate(list_view) == true @test get_show_separators(list_view) == false set_show_separators!(list_view, true) @test get_show_separators(list_view) == true push_front!(list_view, Separator()) push_back!(list_view, Separator()) child = Separator() it = insert_at!(list_view, 1, child) @test find(list_view, child) == 1 push_back!(list_view, Separator(), it) set_widget_at!(list_view, 1, Separator(), it) @test get_n_items(list_view) == 3 remove!(list_view, 1) @test get_n_items(list_view) == 2 @test get_selection_model(list_view) isa SelectionModel connect_signal_activate_item!(list_view) do self::ListView, index::Integer @test true return nothing end end end ### LOG function test_log(::Container) @testset "Log" begin name = tempname() @test set_log_file!(name) == true set_surpress_debug!(Mousetrap.MOUSETRAP_DOMAIN, false) set_surpress_info!(Mousetrap.MOUSETRAP_DOMAIN, false) message = "LOG TEST" log_info(Mousetrap.MOUSETRAP_DOMAIN, message) log_debug(Mousetrap.MOUSETRAP_DOMAIN, message) log_warning(Mousetrap.MOUSETRAP_DOMAIN, message) log_critical(Mousetrap.MOUSETRAP_DOMAIN, message) # log_fatal(Mousetrap.MOUSETRAP_DOMAIN, message) # this would quit runtime file = open(name) lines = readlines(file) @test isempty(lines) == false close(file) @test get_surpress_debug(Mousetrap.MOUSETRAP_DOMAIN) == false set_surpress_debug!(Mousetrap.MOUSETRAP_DOMAIN, true) @test get_surpress_debug(Mousetrap.MOUSETRAP_DOMAIN) == true @test get_surpress_info(Mousetrap.MOUSETRAP_DOMAIN) == false set_surpress_info!(Mousetrap.MOUSETRAP_DOMAIN, true) @test get_surpress_info(Mousetrap.MOUSETRAP_DOMAIN) == true end end ### MENU_MODEL function test_menus(::Container) icon = Main.icon[] action = Action("test.action", Main.app[]) do self::Action end root = MenuModel() submenu = MenuModel() section = MenuModel() items_changed_called = Ref{Bool}(false) connect_signal_items_changed!(root, items_changed_called) do self::MenuModel, position::Integer, n_removed::Integer, n_added::Integer, items_changed_called items_changed_called[] = true return nothing end add_action!(submenu, "Action", action) add_icon!(submenu, icon, action) add_widget!(submenu, Separator()) add_action!(section, "Section", action) add_section!(submenu, "section", section) add_submenu!(root, "Submenu", submenu) @testset "MenuModel" begin Base.show(devnull, root) @test items_changed_called[] == true end @testset "MenuBar" begin bar = MenuBar(root) @test Mousetrap.is_native_widget(bar) Base.show(devnull, bar) end @testset "PopoverMenu" begin popover = PopoverMenu(root) @test Mousetrap.is_native_widget(popover) Base.show(devnull, popover) end end ### NOTE_BOOK function test_notebook(::Container) @testset "Notebook" begin notebook = Notebook() Base.show(devnull, notebook) @test Mousetrap.is_native_widget(notebook) page_added_called = Ref{Bool}(false) connect_signal_page_added!(notebook, page_added_called) do self::Notebook, page_index::Integer, page_added_called page_added_called[] = true return nothing end page_removed_called = Ref{Bool}(false) connect_signal_page_removed!(notebook, page_removed_called) do self::Notebook, page_index::Integer, page_removed_called page_removed_called[] = true return nothing end page_reordered_called = Ref{Bool}(false) connect_signal_page_reordered!(notebook, page_reordered_called) do self::Notebook, page_index::Integer, page_reordered_called page_reordered_called[] = true return nothing end page_selection_changed_called = Ref{Bool}(false) connect_signal_page_selection_changed!(notebook, page_selection_changed_called) do self::Notebook, page_index::Integer, page_selection_changed_called page_selection_changed_called[] = true return nothing end push_front!(notebook, Label("01"), Label("01")) push_back!(notebook, Label("02"), Label("02")) insert_at!(notebook, 1, Label("03"), Label("03")) move_page_to!(notebook, 1, 2) @test get_current_page(notebook) == 1 #= next_page!(notebook) @test get_current_page(notebook) == 2 previous_page!(notebook) @test get_current_page(notebook) == 1 =# # these tests pass, but they trigger "gtk_root_get_focus: assertion 'GTK_IS_ROOT (self)' failed" because notebook is not yet realized @test get_n_pages(notebook) == 3 remove!(notebook, 1) @test get_n_pages(notebook) == 2 @test get_is_scrollable(notebook) == false set_is_scrollable!(notebook, true) @test get_is_scrollable(notebook) == true @test get_has_border(notebook) == true set_has_border!(notebook, false) @test get_has_border(notebook) == false @test get_tabs_visible(notebook) == true set_tabs_visible!(notebook, false) @test get_tabs_visible(notebook) == false @test get_quick_change_menu_enabled(notebook) == false set_quick_change_menu_enabled!(notebook, true) @test get_quick_change_menu_enabled(notebook) == true @test get_tab_position(notebook) == RELATIVE_POSITION_ABOVE set_tab_position!(notebook, RELATIVE_POSITION_BELOW) @test get_tab_position(notebook) == RELATIVE_POSITION_BELOW @test get_tabs_reorderable(notebook) == false set_tabs_reorderable!(notebook, true) @test get_tabs_reorderable(notebook) == true @test page_added_called[] @test page_removed_called[] @test page_reordered_called[] @test page_selection_changed_called[] end end ### OVERLAY function test_overlay(::Container) @testset "Overlay" begin overlay = Overlay() Base.show(devnull, overlay) @test Mousetrap.is_native_widget(overlay) overlay_child = Separator() set_child!(overlay, Separator()) add_overlay!(overlay, overlay_child) remove_child!(overlay) remove_overlay!(overlay, overlay_child) @test overlay isa Widget end end ### PANED function test_paned(::Container) @testset "Paned" begin paned = Paned(ORIENTATION_HORIZONTAL) Base.show(devnull, paned) @test Mousetrap.is_native_widget(paned) set_start_child!(paned, Separator()) set_end_child!(paned, Separator()) @test get_start_child_resizable(paned) == true set_start_child_resizable!(paned, false) @test get_start_child_resizable(paned) == false @test get_start_child_shrinkable(paned) == true set_start_child_shrinkable!(paned, false) @test get_start_child_shrinkable(paned) == false @test get_end_child_resizable(paned) == true set_end_child_resizable!(paned, false) @test get_end_child_resizable(paned) == false @test get_end_child_shrinkable(paned) == true set_end_child_shrinkable!(paned, false) @test get_end_child_shrinkable(paned) == false @test get_has_wide_handle(paned) == true set_has_wide_handle!(paned, false) @test get_has_wide_handle(paned) == false @test get_orientation(paned) == ORIENTATION_HORIZONTAL set_orientation!(paned, ORIENTATION_VERTICAL) @test get_orientation(paned) == ORIENTATION_VERTICAL set_position!(paned, 32) @test get_position(paned) == 32 remove_start_child!(paned) remove_end_child!(paned) end end ### POPOVER function test_popover(container::Container) popover = Popover() @testset "Popover" begin Base.show(devnull, popover) @test Mousetrap.is_native_widget(popover) set_child!(popover, Separator()) id = add_child!(container, popover, "Popover") @test get_has_base_arrow(popover) == true set_has_base_arrow!(popover, false) @test get_has_base_arrow(popover) == false @test get_autohide(popover) == true set_autohide!(popover, false) @test get_autohide(popover) == false set_relative_position!(popover, RELATIVE_POSITION_BELOW) @test get_relative_position(popover) == RELATIVE_POSITION_BELOW connect_signal_closed!(popover) do self::Popover return nothing end connect_signal_realize!(popover) do self::Popover popup!(popover) popdown!(popover) end remove_child!(container, id) end @testset "PopoverButton" begin popover_button = PopoverButton(popover) Base.show(devnull, popover_button) @test Mousetrap.is_native_widget(popover_button) @test get_always_show_arrow(popover_button) == true set_always_show_arrow!(popover_button, false) @test get_always_show_arrow(popover_button) == false @test get_has_frame(popover_button) == true set_has_frame!(popover_button, false) @test get_has_frame(popover_button) == false @test get_is_circular(popover_button) == false set_is_circular!(popover_button, true) @test get_is_circular(popover_button) == true set_relative_position!(popover_button, RELATIVE_POSITION_BELOW) @test get_relative_position(popover_button) == RELATIVE_POSITION_BELOW set_child!(popover_button, Separator()) #remove_child!(popover_button) # cf. https://gitlab.gnome.org/GNOME/gtk/-/issues/5969 set_popover!(popover_button, Popover()) remove_popover!(popover_button) set_popover_menu!(popover_button, PopoverMenu(MenuModel())) remove_popover!(popover_button) activate_called = Ref{Bool}(false) connect_signal_activate!(popover_button, activate_called) do self::PopoverButton, activate_called activate_called[] = true return nothing end activate!(popover_button) @test activate_called[] == true end end ### POPUP_MESSAGE function test_popup_message(::Container) overlay = PopupMessageOverlay() Base.show(devnull, overlay) set_child!(overlay, Separator()) message = PopupMessage("title", "button") @test get_title(message) == "title" set_title!(message, "not title") @test get_title(message) == "not title" @test get_button_label(message) == "button" set_button_label!(message, "not button") @test get_button_label(message) == "not button" id = "test_popup_message.action" action = Action(id, Main.app[]) do self end set_button_action!(message, action) @test get_button_action_id(message) == id @test get_is_high_priority(message) == false set_is_high_priority!(message, true) @test get_is_high_priority(message) == true set_timeout!(message, seconds(1)) @test get_timeout(message) == seconds(1) connect_signal_dismissed!(message) do self::PopupMessage end connect_signal_button_clicked!(message) do self::PopupMessage end show_message!(overlay, message) remove_child!(overlay) end ### PROGRESS_BAR function test_progress_bar(::Container) @testset "ProgressBar" begin progress_bar = ProgressBar() Base.show(devnull, progress_bar) @test Mousetrap.is_native_widget(progress_bar) @test get_fraction(progress_bar) == 0.0 set_fraction!(progress_bar, 0.5) @test get_fraction(progress_bar) == 0.5 @test get_orientation(progress_bar) == ORIENTATION_HORIZONTAL set_orientation!(progress_bar, ORIENTATION_VERTICAL) @test get_orientation(progress_bar) == ORIENTATION_VERTICAL @test get_is_inverted(progress_bar) == false set_is_inverted!(progress_bar, true) @test get_is_inverted(progress_bar) == true @test get_show_text(progress_bar) == false set_show_text!(progress_bar, true) set_text!(progress_bar, "text") @test get_show_text(progress_bar) == true @test get_text(progress_bar) == "text" pulse(progress_bar) end end ### REVEALER function test_revealer(::Container) @testset "Revealer" begin revealer = Revealer() Base.show(devnull, revealer) @test Mousetrap.is_native_widget(revealer) revealed_called = Ref{Bool}(false) connect_signal_revealed!(revealer, revealed_called) do self::Revealer, revealed_called revealed_called[] = true return nothing end set_child!(revealer, Separator()) set_is_revealed!(revealer, false) @test get_is_revealed(revealer) == false set_is_revealed!(revealer, true) @test get_is_revealed(revealer) == true set_transition_duration!(revealer, seconds(1)) @test get_transition_duration(revealer) == seconds(1) set_transition_type!(revealer, REVEALER_TRANSITION_TYPE_SLIDE_DOWN) @test get_transition_type(revealer) == REVEALER_TRANSITION_TYPE_SLIDE_DOWN @test revealed_called[] == true end end ### ACTION BAR function test_action_bar(::Container) @testset "ActionBar" begin bar = ActionBar() Base.show(devnull, bar) @test Mousetrap.is_native_widget(bar) widget = Label("") push_front!(bar, widget) push_back!(bar, Label("")) remove!(bar, widget) set_center_child!(bar, Label("")) remove_center_child!(bar) @test get_is_revealed(bar) == true set_is_revealed!(bar, false) @test get_is_revealed(bar) == false end end ### SCALE function test_scale(::Container) @testset "Scale" begin scale = Scale(0, 1, 0.01) Base.show(devnull, scale) @test Mousetrap.is_native_widget(scale) value_changed_called = Ref{Bool}(false) connect_signal_value_changed!(scale, value_changed_called) do self::Scale, value_changed_called value_changed_called[] = true return nothing end @test get_value(scale) == 0.5 set_value!(scale, 0.6) @test get_value(scale) == 0.6f0 @test get_lower(scale) == 0.0 set_lower!(scale, 1.0) @test get_lower(scale) == 1.0 @test get_upper(scale) == 1.0 set_upper!(scale, 2.0) @test get_upper(scale) == 2.0 @test get_step_increment(scale) == 0.01f0 set_step_increment!(scale, 0.5) @test get_step_increment(scale) == 0.5 @test get_has_origin(scale) == true set_has_origin!(scale, false) @test get_has_origin(scale) == false @test get_orientation(scale) == ORIENTATION_HORIZONTAL set_orientation!(scale, ORIENTATION_VERTICAL) @test get_orientation(scale) == ORIENTATION_VERTICAL @test get_should_draw_value(scale) == false set_should_draw_value!(scale, true) @test get_should_draw_value(scale) == true @test get_adjustment(scale) isa Adjustment add_mark!(scale, 1.5, RELATIVE_POSITION_RIGHT_OF, "label") clear_marks!(scale) @test value_changed_called[] == true end end ### SCROLLBAR function test_scrollbar(::Container) @testset "Scrollbar" begin scrollbar = Scrollbar(ORIENTATION_HORIZONTAL, Adjustment(0, 0, 1, 0.01)) Base.show(devnull, scrollbar) @test Mousetrap.is_native_widget(scrollbar) @test get_value(get_adjustment(scrollbar)) == 0.0 @test get_orientation(scrollbar) == ORIENTATION_HORIZONTAL set_orientation!(scrollbar, ORIENTATION_VERTICAL) @test get_orientation(scrollbar) == ORIENTATION_VERTICAL end end ### SELECTION_MODEL function test_selection_model(::Container) @testset "SelectionModel" begin list_view = ListView(ORIENTATION_HORIZONTAL, SELECTION_MODE_MULTIPLE) push_back!(list_view, Separator()) push_back!(list_view, Separator()) push_back!(list_view, Separator()) selection_model = get_selection_model(list_view) Base.show(devnull, selection_model) @test Mousetrap.is_native_widget(list_view) @test get_n_items(selection_model) == 3 select!(selection_model, 1) select_all!(selection_model) @test length(get_selection(selection_model)) == 3 unselect_all!(selection_model) @test length(get_selection(selection_model)) == 0 end end ### SEPARATOR function test_separator(::Container) @testset "Separator" begin separator = Separator(ORIENTATION_HORIZONTAL) Base.show(devnull, separator) @test Mousetrap.is_native_widget(separator) @test get_orientation(separator) == ORIENTATION_HORIZONTAL set_orientation!(separator, ORIENTATION_VERTICAL) @test get_orientation(separator) == ORIENTATION_VERTICAL end end ### SPIN_BUTTON function test_spin_button(::Container) @testset "SpinButton" begin scale = SpinButton(0, 1, 0.01) Base.show(devnull, scale) @test Mousetrap.is_native_widget(scale) value_changed_called = Ref{Bool}(false) connect_signal_value_changed!(scale, value_changed_called) do self::SpinButton, value_changed_called value_changed_called[] = true return nothing end set_value!(scale, 0.5) @test get_value(scale) == 0.5 @test get_lower(scale) == 0.0 set_lower!(scale, 1.0) @test get_lower(scale) == 1.0 @test get_upper(scale) == 1.0 set_upper!(scale, 2.0) @test get_upper(scale) == 2.0 @test get_step_increment(scale) == 0.01f0 set_step_increment!(scale, 0.5) @test get_step_increment(scale) == 0.5f0 set_acceleration_rate!(scale, 2) @test get_acceleration_rate(scale) == 2 @test get_allow_only_numeric(scale) == true set_allow_only_numeric!(scale, false) @test get_allow_only_numeric(scale) == false @test get_n_digits(scale) > 0 set_n_digits!(scale, 10) @test get_n_digits(scale) == 10 @test get_should_wrap(scale) == false set_should_wrap!(scale, true) @test get_should_wrap(scale) == true @test get_should_snap_to_ticks(scale) == false set_should_snap_to_ticks!(scale, true) @test get_should_snap_to_ticks(scale) == true set_text_to_value_function!(scale) do self::SpinButton, text::String value::Float32 = 0 return value end set_value_to_text_function!(scale) do self::SpinButton, value::AbstractFloat result = "" return result end @test value_changed_called[] == true end end ### SPINNER function test_spinner(::Container) @testset "Spinner" begin spinner = Spinner() Base.show(devnull, spinner) @test Mousetrap.is_native_widget(spinner) @test get_is_spinning(spinner) == true set_is_spinning!(spinner, false) @test get_is_spinning(spinner) == false stop!(spinner) @test get_is_spinning(spinner) == false start!(spinner) @test get_is_spinning(spinner) == true end end ### STACK function test_stack(::Container) @testset "Stack" begin stack = Stack() Base.show(devnull, stack) @test Mousetrap.is_native_widget(stack) id_01 = add_child!(stack, Separator(), "01") id_02 = add_child!(stack, Separator(), "02") @test get_child_at(stack, 1) == id_01 @test get_is_horizontally_homogeneous(stack) == true set_is_horizontally_homogeneous!(stack, false) @test get_is_horizontally_homogeneous(stack) == false @test get_is_vertically_homogeneous(stack) == true set_is_vertically_homogeneous!(stack, false) @test get_is_vertically_homogeneous(stack) == false @test get_should_interpolate_size(stack) == false set_should_interpolate_size!(stack, true) @test get_should_interpolate_size(stack) == true set_transition_type!(stack, STACK_TRANSITION_TYPE_OVER_UP) @test get_transition_type(stack) == STACK_TRANSITION_TYPE_OVER_UP set_transition_duration!(stack, seconds(1)) @test get_transition_duration(stack) == seconds(1) @test get_selection_model(stack) isa SelectionModel set_visible_child!(stack, id_02) sidebar = StackSidebar(stack) @test sidebar isa Widget switcher = StackSwitcher(stack) @test switcher isa Widget end end ### SWITCH function test_switch(::Container) @testset "Switch" begin switch = Switch() Base.show(devnull, switch) @test Mousetrap.is_native_widget(switch) switched_called = Ref{Bool}(false) connect_signal_switched!(switch, switched_called) do self::Switch, switched_called switched_called[] = true set_is_active!(self, true) # `activate!` toggles switch only once it is realized, so we do it manually to avoid having to wait for the window to render return nothing end set_is_active!(switch, false) @test get_is_active(switch) == false set_is_active!(switch, true) @test get_is_active(switch) == true @test switched_called[] == true end end ### TEXT_VIEW function test_text_view(::Container) @testset "TextView" begin text_view = TextView() Base.show(devnull, text_view) @test Mousetrap.is_native_widget(text_view) text_changed_called = Ref{Bool}(false) connect_signal_text_changed!(text_view, text_changed_called) do self::TextView, text_changed_called text_changed_called[] = true return nothing end @test get_bottom_margin(text_view) == 0 set_bottom_margin!(text_view, 10) @test get_bottom_margin(text_view) == 10 @test get_left_margin(text_view) == 0 set_left_margin!(text_view, 10) @test get_left_margin(text_view) == 10 @test get_right_margin(text_view) == 0 set_right_margin!(text_view, 10) @test get_right_margin(text_view) == 10 @test get_top_margin(text_view) == 0 set_top_margin!(text_view, 10) @test get_top_margin(text_view) == 10 @test get_editable(text_view) == true set_editable!(text_view, false) @test get_editable(text_view) == false set_was_modified!(text_view, false) @test get_was_modified(text_view) == false set_text!(text_view, "modified") @test get_was_modified(text_view) == true undo!(text_view) redo!(text_view) @test text_changed_called[] == true end end ### TOGGLE_BUTTON function test_toggle_button(::Container) @testset "ToggleButton" begin button = ToggleButton() Base.show(devnull, button) @test Mousetrap.is_native_widget(button) toggled_called = Ref{Bool}(false) connect_signal_toggled!(button, toggled_called) do self::ToggleButton, toggled_called toggled_called[] = true return nothing end clicked_called = Ref{Bool}(false) connect_signal_clicked!(button, clicked_called) do self::ToggleButton, clicked_called clicked_called[] = true return nothing end set_is_active!(button, true) @test get_is_active(button) == true set_child!(button, Separator()) set_icon!(button, Main.icon[]) remove_child!(button) @test get_is_circular(button) == false set_is_circular!(button, true) @test get_is_circular(button) == true end end function test_typed_function(::Container) @testset "TypedFunction" begin yes_f(x::Int64) ::Nothing = return nothing no_f1(x::Int32) ::Nothing = return nothing no_f2(x::Int64) ::Int64 = return 1234 Test.@test TypedFunction(yes_f, Nothing, (Int64,)) isa TypedFunction Test.@test_throws AssertionError TypedFunction(no_f1, Nothing, (Int64,)) Test.@test_throws AssertionError TypedFunction(no_f2, Nothing, (Int64,)) isa TypedFunction # if return_t is nothing, the result is ignored, regardless of the results type Base.show(devnull, TypedFunction(() -> nothing, Nothing, ())) end end function test_viewport(::Container) @testset "Viewport" begin viewport = Viewport() Base.show(devnull, viewport) @test Mousetrap.is_native_widget(viewport) connect_signal_scroll_child!(viewport) do self::Viewport, scroll_type::ScrollType, is_horizontal::Bool end @test get_has_frame(viewport) == false set_has_frame!(viewport, true) @test get_has_frame(viewport) == true @test get_horizontal_adjustment(viewport) isa Adjustment @test get_vertical_adjustment(viewport) isa Adjustment @test get_kinetic_scrolling_enabled(viewport) == true set_kinetic_scrolling_enabled!(viewport, false) @test get_kinetic_scrolling_enabled(viewport) == false @test get_propagate_natural_width(viewport) == false set_propagate_natural_width!(viewport, true) @test get_propagate_natural_width(viewport) == true @test get_propagate_natural_height(viewport) == false set_propagate_natural_height!(viewport, true) @test get_propagate_natural_height(viewport) == true @test get_vertical_scrollbar_policy(viewport) == SCROLLBAR_VISIBILITY_POLICY_AUTOMATIC set_vertical_scrollbar_policy!(viewport, SCROLLBAR_VISIBILITY_POLICY_NEVER) @test get_vertical_scrollbar_policy(viewport) == SCROLLBAR_VISIBILITY_POLICY_NEVER @test get_horizontal_scrollbar_policy(viewport) == SCROLLBAR_VISIBILITY_POLICY_AUTOMATIC set_horizontal_scrollbar_policy!(viewport, SCROLLBAR_VISIBILITY_POLICY_NEVER) @test get_horizontal_scrollbar_policy(viewport) == SCROLLBAR_VISIBILITY_POLICY_NEVER set_scrollbar_placement!(viewport, CORNER_PLACEMENT_TOP_LEFT) @test get_scrollbar_placement(viewport) == CORNER_PLACEMENT_TOP_LEFT end end function test_window(::Container) @testset "Window" begin window = Window(Main.app[]) other_window = Window(Main.app[]) Base.show(devnull, window) @test Mousetrap.is_native_widget(window) close_request_called = Ref{Bool}(false) connect_signal_close_request!(window, close_request_called) do self::Window, close_request_called close_request_called[] = true return WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE end activate_default_widget_called = Ref{Bool}(false) connect_signal_activate_default_widget!(window, activate_default_widget_called) do self::Window, activate_default_widget_called activate_default_widget_called[] = true return nothing end activate_focused_widget_called = Ref{Bool}(false) connect_signal_activate_focused_widget!(window, activate_focused_widget_called) do self::Window, activate_focused_widget_called activate_focused_widget_called[] = true return nothing end @test get_destroy_with_parent(window) == false set_destroy_with_parent!(window, true) @test get_destroy_with_parent(window) == true @test get_focus_visible(window) == true set_focus_visible!(window, false) @test get_focus_visible(window) == false @test get_has_close_button(window) == true set_has_close_button!(window, false) @test get_has_close_button(window) == false @test get_is_decorated(window) == true set_is_decorated!(window, false) @test get_is_decorated(window) == false @test get_is_modal(window) == false set_is_modal!(window, true) @test get_is_modal(window) == true set_title!(window, "test") @test get_title(window) == "test" button = Entry() set_child!(window, button) set_default_widget!(window, button) activate!(button) set_transient_for!(other_window, window) #@test activate_default_widget_called[] == true #@test activate_focused_widget_called[] == true @test get_header_bar(window) isa HeaderBar @test get_hide_on_close(window) == false set_hide_on_close!(window, true) @test get_hide_on_close(window) == true @test get_is_closed(window) == true present!(window) @test get_is_closed(window) == false set_minimized!(window, true) set_maximized!(window, true) close!(other_window) close!(window) @test get_is_closed(window) == true destroy!(window) destroy!(other_window) end end ### WIDGET function test_widget(widget::Container) @testset "Widget" begin for s in [:realize, :unrealize, :destroy, :hide, :show, :map, :unmap] signal_called = Ref{Bool}(false) connect_signal_f = Symbol("connect_signal_$(s)!") eval(:( $connect_signal_f($widget, $signal_called) do self::Container, signal_called signal_called[] = true @test signal_called[] return nothing end )) end set_size_request!(widget, Vector2f(50, 100)) @test get_size_request(widget) == Vector2f(50, 100) set_margin_top!(widget, 1) @test get_margin_top(widget) == 1 set_margin_bottom!(widget, 2) @test get_margin_bottom(widget) == 2 set_margin_start!(widget, 3) @test get_margin_start(widget) == 3 set_margin_end!(widget, 4) @test get_margin_end(widget) == 4 set_margin_horizontal!(widget, 5) @test get_margin_start(widget) == 5 @test get_margin_end(widget) == 5 set_margin_vertical!(widget, 6) @test get_margin_top(widget) == 6 @test get_margin_bottom(widget) == 6 set_margin!(widget, 7) @test get_margin_top(widget) == 7 @test get_margin_bottom(widget) == 7 @test get_margin_start(widget) == 7 @test get_margin_end(widget) == 7 set_expand_horizontally!(widget, true) @test get_expand_horizontally(widget) == true set_expand_vertically!(widget, true) @test get_expand_horizontally(widget) == true set_expand!(widget, false) @test get_expand_horizontally(widget) == false @test get_expand_horizontally(widget) == false set_horizontal_alignment!(widget, ALIGNMENT_START) @test get_horizontal_alignment(widget) == ALIGNMENT_START set_vertical_alignment!(widget, ALIGNMENT_START) @test get_vertical_alignment(widget) == ALIGNMENT_START set_alignment!(widget, ALIGNMENT_END) @test get_vertical_alignment(widget) == ALIGNMENT_END @test get_horizontal_alignment(widget) == ALIGNMENT_END @test isapprox(get_opacity(widget), 1.0) set_opacity!(widget, 0.6) @test isapprox(get_opacity(widget), 0.6) set_opacity!(widget, 1.0) @test calculate_monitor_dpi(widget) > 0 @test get_scale_factor(widget) > 0 set_is_visible!(widget, false) @test get_is_visible(widget) == false set_is_visible!(widget, true) set_tooltip_text!(widget, "test") set_tooltip_widget!(widget, Label("test")) remove_tooltip_widget!(widget) set_cursor!(widget, CURSOR_TYPE_NONE) set_cursor_from_image!(widget, Image(1, 1, RGBA(1, 0, 0, 1))) hide!(widget) show!(widget) set_is_focusable!(widget, true) @test get_is_focusable(widget) == true set_is_focusable!(widget, false) @test get_is_focusable(widget) == false set_focus_on_click!(widget, true) @test get_focus_on_click(widget) == true grab_focus!(widget) @test get_has_focus(widget) isa Bool set_can_respond_to_input!(widget, false) @test get_can_respond_to_input(widget) == false set_can_respond_to_input!(widget, true) @test get_can_respond_to_input(widget) == true @test get_is_realized(widget) == true # sizes are only available after first show minimum_size = get_minimum_size(widget) @test minimum_size.x >= 0 && minimum_size.y >= 0 natural_size = get_natural_size(widget) @test natural_size.x >= 0 && natural_size.y >= 0 position = get_position(widget) @test position.x >= 0 && position.y >= 0 allocated_size = get_allocated_size(widget) @test allocated_size.x >= 0 && allocated_size.y >= 0 set_hide_on_overflow!(widget, true) @test get_hide_on_overflow(widget) == true set_hide_on_overflow!(widget, false) Mousetrap.add_css_class!(widget, "test") @test !isempty(Mousetrap.get_css_classes(widget)) Mousetrap.remove_css_class!(widget, "test") @test isempty(Mousetrap.get_css_classes(widget)) tick_callback_called = Ref{Bool}(false) set_tick_callback!(widget, tick_callback_called) do clock::FrameClock, tick_callback_called tick_callback_called[] = true @testset "FrameClock" begin Base.show(devnull, clock) paint_called = Ref{Bool}(false) connect_signal_paint!(clock, paint_called) do self::FrameClock, paint_called paint_called[] = true @test get_target_frame_duration(clock) isa Time @test get_time_since_last_frame(clock) isa Time return nothing end update_called = Ref{Bool}(false) connect_signal_update!(clock, update_called) do self::FrameClock, update_called update_called[] = true @test get_target_frame_duration(clock) isa Time @test get_time_since_last_frame(clock) isa Time return nothing end @test clock isa FrameClock end return TICK_CALLBACK_RESULT_DISCONTINUE end # @test tick_callback_called[] == true # only called after first frame is done, which never happens in tests end end ### RENDER_AREA function test_render_area(::Container) if !Mousetrap.MOUSETRAP_ENABLE_OPENGL_COMPONENT || Mousetrap.detail.is_opengl_disabled() return end render_area = RenderArea() @testset "RenderArea" begin #make_current(render_area) # would print soft warning because render area is not yet realized queue_render(render_area) @test render_area isa RenderArea end for pair in [ "Point" => Point(Vector2f(0, 0)), "Points" => Points([Vector2f(0.5, 0.5), Vector2f(0, 0,)]), "Triangle" => Triangle(Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5), Vector2f(0.5, -0.5)), "Rectangle" => Rectangle(Vector2f(-0.5, -0.5), Vector2f(1, 1)), "Circle" => Circle(Vector2f(0, 0), 1, 16), "Ellipse" => Ellipse(Vector2f(0, 0), 0.5, 1, 16), "Line" => Line(Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5)), "LineStrip" => LineStrip([Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5), Vector2f(0.5, -0.5)]), "Polygon" => Polygon([Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5), Vector2f(0.5, -0.5)]), "RectangularFrame" => RectangularFrame(Vector2f(-0.5, 0.5), Vector2f(1, 1), 0.1, 0.1), "CircularRing" => CircularRing(Vector2f(0.0, 0.0), 1.0, 0.1, 8), "EllipticalRing" => EllipticalRing(Vector2f(0.0, 0.0), 1.0, 0.1, 0.2, 0.1, 8), "Wireframe" => Wireframe([Vector2f(-0.5, 0.5), Vector2f(0.5, 0.5), Vector2f(0.5, -0.5)]) ] @testset "$(pair[1])" begin shape = pair[2] add_render_task!(render_area, RenderTask(shape)) add_render_task!(render_area, RenderTask(Outline(shape))) for i in 1:get_n_vertices(shape) set_vertex_color!(shape, i, RGBA(1, 0, 1, 1)) @test get_vertex_color(shape, i) == RGBA(1, 0, 1, 1) coordinate = get_vertex_texture_coordinate(shape, i) @test coordinate.x >= 0 && coordinate.x <= 1 @test coordinate.y >= 0 && coordinate.y <= 1 set_vertex_position!(shape, i, Vector3f(0, 0, 0)) @test get_vertex_position(shape, i) == Vector3f(0, 0, 0) set_vertex_texture_coordinate!(shape, i, Vector2f(-1, -1)) @test get_vertex_texture_coordinate(shape, i) == Vector2f(-1, -1) Base.show(devnull, shape) end @test get_is_visible(shape) == true set_is_visible!(shape, false) @test get_is_visible(shape) == false bounding_box = get_bounding_box(shape) @test bounding_box.top_left == get_top_left(shape) @test bounding_box.size == get_size(shape) set_top_left!(shape, Vector2f(1, 2)) @test get_top_left(shape) == Vector2f(1, 2) set_centroid!(shape, Vector2f(1, 2)) centroid = get_centroid(shape) @test isapprox(centroid.x, 1) && isapprox(centroid.y, 2) rotate!(shape, degrees(180), get_centroid(shape)) new_centroid = get_centroid(shape) @test isapprox(centroid.x, new_centroid.x) && isapprox(centroid.y, new_centroid.y) set_color!(shape, RGBA(0, 1, 1, 1)) @test get_vertex_color(shape, 1) == RGBA(0, 1, 1, 1) end end @testset "Shader" begin vertex_shader_source = """ #version 330 layout (location = 0) in vec3 _vertex_position_in; layout (location = 1) in vec4 _vertex_color_in; layout (location = 2) in vec2 _vertex_texture_coordinates_in; uniform mat4 _transform; out vec4 _vertex_color; out vec2 _texture_coordinates; out vec3 _vertex_position; void main() { gl_Position = _transform * vec4(_vertex_position_in, 1.0); _vertex_color = _vertex_color_in; _vertex_position = _vertex_position_in; _texture_coordinates = _vertex_texture_coordinates_in; } """ fragment_shader_source = """ #version 130 in vec4 _vertex_color; in vec2 _texture_coordinates; in vec3 _vertex_position; out vec4 _fragment_color; uniform int _texture_set; uniform sampler2D _texture; uniform float _float; uniform int _int; uniform uint _uint; uniform vec2 _vec2; uniform vec3 _vec3; uniform vec4 _vec4; uniform mat4 _mat4; void main() { // prevent optimizing uniform away float value = _float + _int + _uint + _vec2.x + _vec3.x + _vec4.x + _mat4[0][0]; _fragment_color = vec4(vec3(value), 1); } """ shader = Shader() @test create_from_string!(shader, SHADER_TYPE_FRAGMENT, fragment_shader_source) @test create_from_string!(shader, SHADER_TYPE_VERTEX, vertex_shader_source) @test get_program_id(shader) != 0 @test get_fragment_shader_id(shader) != 0 @test get_vertex_shader_id(shader) != 0 for name in ["_float", "_int", "_uint", "_vec2", "_vec3", "_vec4", "_mat4"] @test get_uniform_location(shader, name) >= 0 end set_uniform_float!(shader, "_float", Cfloat(1234.0)) set_uniform_int!(shader, "_int", Cint(1234)) set_uniform_uint!(shader, "_uint", Cuint(1234)) set_uniform_vec2!(shader, "_vec2", Vector2f(1, 2)) set_uniform_vec3!(shader, "_vec3", Vector3f(1, 2, 3)) set_uniform_vec4!(shader, "_vec4", Vector4f(1, 2, 3, 4)) set_uniform_transform!(shader, "_mat4", GLTransform()) Base.show(devnull, shader) end @testset "RenderTask" begin shape = Rectangle(Vector2f(-0.5, 0.5), Vector2f(1, 1)) shader = Shader() blend_mode = BLEND_MODE_NONE transform = GLTransform() task = RenderTask(shape; shader = shader, transform = transform, blend_mode = blend_mode) set_uniform_float!(task, "_float", Cfloat(1234.0)) @test get_uniform_float(task, "_float") == 1234.0 set_uniform_int!(task, "_int", Cint(1234)) @test get_uniform_int(task, "_int") == 1234 set_uniform_uint!(task, "_uint", UInt32(1234)) @test get_uniform_uint(task, "_uint") == UInt(1234) set_uniform_vec2!(task, "_vec2", Vector2f(1, 2)) @test get_uniform_vec2(task, "_vec2") == Vector2f(1, 2) set_uniform_vec3!(task, "_vec3", Vector3f(1, 2, 3)) @test get_uniform_vec3(task, "_vec3") == Vector3f(1, 2, 3) set_uniform_vec4!(task, "_vec4", Vector4f(1, 2, 3, 4)) @test get_uniform_vec4(task, "_vec4") == Vector4f(1, 2, 3, 4) set_uniform_rgba!(task, "_rgba", RGBA(1, 0, 1, 1)) @test get_uniform_rgba(task, "_rgba") == RGBA(1, 0, 1, 1) set_uniform_hsva!(task, "_hsva", HSVA(0.5, 0, 1, 1)) @test get_uniform_hsva(task, "_hsva") == HSVA(0.5, 0, 1, 1) transform = GLTransform() translate!(transform, Vector2f(0.5, 0.5)) set_uniform_transform!(task, "_transform", transform) @test get_uniform_transform(task, "_transform") == transform Base.show(devnull, task) end @testset "TextureObject" begin image = Image(32, 32, RGBA(1, 0, 0, 1)) texture = Texture() render_texture = RenderTexture() for t in [texture, render_texture] create!(t, 100, 100) create_from_image!(t, image) Mousetrap.download(texture) == image Mousetrap.bind(t) Mousetrap.unbind(t) @test get_wrap_mode(t) == TEXTURE_WRAP_MODE_REPEAT set_wrap_mode!(t, TEXTURE_WRAP_MODE_STRETCH) @test get_wrap_mode(t) == TEXTURE_WRAP_MODE_STRETCH @test get_scale_mode(t) == TEXTURE_SCALE_MODE_NEAREST set_scale_mode!(t, TEXTURE_SCALE_MODE_LINEAR) @test get_scale_mode(t) == TEXTURE_SCALE_MODE_LINEAR @test get_size(t) == Vector2i(32, 32) @test get_native_handle(t) != 0 Base.show(devnull, t) end bind_as_render_target(render_texture) unbind_as_render_target(render_texture) end end ### MAIN main(Main.app_id) do app::Application #= Main.app[] = app Main.window[] = Window(app) set_is_decorated!(window, false) # prevent user from closing the window during tests theme = IconTheme(Main.window[]) Main.icon[] = Icon() container = Container() viewport = Viewport() set_child!(viewport, container) set_child!(window, viewport) connect_signal_realize!(container, window) do container::Container, window temp = """ test_action(container) test_adjustment(container) test_alert_dialog(container) test_angle(container) test_application(container) test_aspect_frame(container) test_box(container) test_button(container) test_center_box(container) test_check_button(container) test_clamp_frame(container) test_clipboard(container) test_color_chooser(container) test_colors(container) test_column_view(container) test_drop_down(container) test_entry(container) test_event_controller(container) test_expander(container) test_file_chooser(container) test_file_descriptor(container) test_fixed(container) test_frame(container) test_flow_box(container) test_gl_transform(container) test_gl_area(container) test_grid(container) test_grid_view(container) test_header_bar(container) test_icon(container) test_image(container) test_image_display(container) test_key_file(container) test_label(container) test_level_bar(container) test_list_view(container) test_log(container) test_menus(container) test_notebook(container) test_overlay(container) test_paned(container) test_popup_message(container) test_popover(container) test_progress_bar(container) test_revealer(container) test_render_area(container) test_scale(container) test_scrollbar(container) test_selection_model(container) test_separator(container) test_spin_button(container) test_spinner(container) test_stack(container) test_switch(container) test_text_view(container) test_time(container) test_toggle_button(container) test_typed_function(container) test_viewport(container) test_widget(container) test_window(container) """ return nothing end present!(window) close!(window) #quit!(app) =# window = Window(Main.app[]) Base.show(devnull, window) @test Mousetrap.is_native_widget(window) close_request_called = Ref{Bool}(false) connect_signal_close_request!(window, close_request_called) do self::Window, close_request_called close_request_called[] = true return WINDOW_CLOSE_REQUEST_RESULT_ALLOW_CLOSE end activate_default_widget_called = Ref{Bool}(false) connect_signal_activate_default_widget!(window, activate_default_widget_called) do self::Window, activate_default_widget_called activate_default_widget_called[] = true return nothing end activate_focused_widget_called = Ref{Bool}(false) connect_signal_activate_focused_widget!(window, activate_focused_widget_called) do self::Window, activate_focused_widget_called activate_focused_widget_called[] = true return nothing end @test get_destroy_with_parent(window) == false set_destroy_with_parent!(window, true) @test get_destroy_with_parent(window) == true @test get_focus_visible(window) == true set_focus_visible!(window, false) @test get_focus_visible(window) == false @test get_has_close_button(window) == true set_has_close_button!(window, false) @test get_has_close_button(window) == false @test get_is_decorated(window) == true set_is_decorated!(window, false) @test get_is_decorated(window) == false @test get_is_modal(window) == false set_is_modal!(window, true) @test get_is_modal(window) == true set_title!(window, "test") @test get_title(window) == "test" button = Entry() set_child!(window, button) set_default_widget!(window, button) activate!(button) #@test activate_default_widget_called[] == true #@test activate_focused_widget_called[] == true @test get_header_bar(window) isa HeaderBar @test get_hide_on_close(window) == false set_hide_on_close!(window, true) @test get_hide_on_close(window) == true other_window = Window(app) set_transient_for!(other_window, window) @test get_is_closed(window) == true present!(window) @test get_is_closed(window) == false set_minimized!(window, true) set_maximized!(window, true) close!(window) @test get_is_closed(window) == true destroy!(window) println("TODO: done.") end