From d8de6115109748b428585e8000136930178b0a22 Mon Sep 17 00:00:00 2001 From: david-swift Date: Mon, 22 Jan 2024 21:41:21 +0100 Subject: [PATCH] Add support for auto-generated widget bindings Additionally fix an update problem occurring with custom views --- .github/ISSUE_TEMPLATE/component_request.yml | 8 - .swiftlint.yml | 3 + CONTRIBUTING.md | 2 + LICENSE.md | 696 +--------------- Package.swift | 22 +- README.md | 10 +- SUMMARY.md | 1 + Sources/Adwaita/Menu/MenuButton.swift | 13 +- Sources/Adwaita/Menu/MenuSection.swift | 8 +- Sources/Adwaita/Menu/Submenu.swift | 8 +- Sources/Adwaita/Model/ArrayBuilder.swift | 2 - Sources/Adwaita/Model/Data Flow/State.swift | 18 +- .../Model/Enumerations/Alignment.swift | 44 + Sources/Adwaita/Model/Enumerations/Edge.swift | 20 + Sources/Adwaita/Model/Enumerations/Icon.swift | 767 ++++++++++++++++++ .../Model/Enumerations/Transition.swift | 75 ++ Sources/Adwaita/Model/Extensions/Array.swift | 26 +- Sources/Adwaita/Model/Extensions/Bool.swift | 15 + Sources/Adwaita/Model/Extensions/Int.swift | 17 + .../Extensions/Libadwaita.FileDialog.swift | 74 -- .../Model/Extensions/NativeWidgetPeer.swift | 37 - .../Model/Extensions/OpaquePointer.swift | 16 + Sources/Adwaita/Model/Extensions/Set.swift | 35 + Sources/Adwaita/Model/Extensions/String.swift | 24 + Sources/Adwaita/Model/Extensions/UInt.swift | 15 + .../Extensions/UnsafeMutablePointer.swift | 23 + .../Extensions/UnsafeMutableRawPointer.swift | 17 + .../Model/User Interface/App/App.swift | 2 - .../Model/User Interface/App/GTUIApp.swift | 66 +- .../Model/User Interface/Menu/MenuItem.swift | 4 +- .../User Interface/Menu/MenuItemGroup.swift | 4 +- .../Model/User Interface/View/View.swift | 22 +- .../User Interface/View/ViewStorage.swift | 175 +++- .../Model/User Interface/View/Widget.swift | 2 - .../Window/GTUIAboutWindow.swift | 49 ++ .../Window/GTUIApplicationWindow.swift | 32 +- .../Window/GTUIFileDialog.swift | 138 ++++ .../User Interface/Window/GTUIWindow.swift | 82 +- .../User Interface/Window/WindowScene.swift | 2 - .../User Interface/Window/WindowStorage.swift | 2 - .../User Interface/Window/WindowType.swift | 2 - Sources/Adwaita/View/Banner+.swift | 33 + Sources/Adwaita/View/Banner.swift | 76 -- Sources/Adwaita/View/Button+.swift | 64 ++ Sources/Adwaita/View/Button.swift | 98 --- Sources/Adwaita/View/Carousel+.swift | 17 + Sources/Adwaita/View/Carousel.swift | 49 -- Sources/Adwaita/View/Container.swift | 101 --- Sources/Adwaita/View/Forms/ActionRow+.swift | 17 + Sources/Adwaita/View/Forms/ActionRow.swift | 99 --- Sources/Adwaita/View/Forms/ComboRow+.swift | 61 ++ Sources/Adwaita/View/Forms/ComboRow.swift | 132 --- Sources/Adwaita/View/Forms/EntryRow+.swift | 59 ++ Sources/Adwaita/View/Forms/EntryRow.swift | 136 ---- Sources/Adwaita/View/Forms/Form.swift | 39 +- Sources/Adwaita/View/Forms/FormSection+.swift | 30 + Sources/Adwaita/View/Forms/FormSection.swift | 89 -- .../View/Forms/PasswordEntryRow+.swift | 43 + Sources/Adwaita/View/Forms/SpinRow+.swift | 62 ++ Sources/Adwaita/View/Forms/SpinRow.swift | 149 ---- Sources/Adwaita/View/Forms/SwitchRow+.swift | 21 + Sources/Adwaita/View/Forms/SwitchRow.swift | 113 --- .../Adwaita/View/Generated/ActionRow.swift | 334 ++++++++ Sources/Adwaita/View/Generated/Avatar.swift | 139 ++++ Sources/Adwaita/View/Generated/Banner.swift | 161 ++++ Sources/Adwaita/View/Generated/Bin.swift | 81 ++ Sources/Adwaita/View/Generated/Box.swift | 173 ++++ Sources/Adwaita/View/Generated/Button.swift | 221 +++++ .../View/Generated/ButtonContent.swift | 162 ++++ Sources/Adwaita/View/Generated/Carousel.swift | 229 ++++++ .../Adwaita/View/Generated/CenterBox.swift | 183 +++++ .../Adwaita/View/Generated/CheckButton.swift | 246 ++++++ Sources/Adwaita/View/Generated/Clamp.swift | 146 ++++ Sources/Adwaita/View/Generated/ComboRow.swift | 380 +++++++++ Sources/Adwaita/View/Generated/EntryRow.swift | 293 +++++++ .../Adwaita/View/Generated/ExpanderRow.swift | 332 ++++++++ .../Adwaita/View/Generated/HeaderBar.swift | 331 ++++++++ Sources/Adwaita/View/Generated/Label.swift | 502 ++++++++++++ Sources/Adwaita/View/Generated/LevelBar.swift | 238 ++++++ .../Adwaita/View/Generated/LinkButton.swift | 246 ++++++ Sources/Adwaita/View/Generated/ListBox.swift | 319 ++++++++ Sources/Adwaita/View/Generated/Menu.swift | 289 +++++++ Sources/Adwaita/View/Generated/Overlay.swift | 167 ++++ .../View/Generated/OverlaySplitView.swift | 361 +++++++++ .../View/Generated/PasswordEntryRow.swift | 248 ++++++ .../View/Generated/PreferencesGroup.swift | 159 ++++ .../View/Generated/PreferencesPage.swift | 157 ++++ .../View/Generated/PreferencesRow.swift | 134 +++ .../Adwaita/View/Generated/ProgressBar.swift | 184 +++++ .../View/Generated/ScrolledWindow.swift | 403 +++++++++ Sources/Adwaita/View/Generated/SpinRow.swift | 432 ++++++++++ Sources/Adwaita/View/Generated/Spinner.swift | 83 ++ .../Adwaita/View/Generated/SplitButton.swift | 274 +++++++ .../Adwaita/View/Generated/StatusPage.swift | 133 +++ .../Adwaita/View/Generated/SwitchRow.swift | 309 +++++++ .../Adwaita/View/Generated/ToastOverlay.swift | 108 +++ .../Adwaita/View/Generated/ToggleButton.swift | 296 +++++++ .../Adwaita/View/Generated/ToolbarView.swift | 332 ++++++++ .../Adwaita/View/Generated/WindowTitle.swift | 99 +++ Sources/Adwaita/View/HStack.swift | 31 +- Sources/Adwaita/View/HeaderBar+.swift | 52 ++ Sources/Adwaita/View/HeaderBar.swift | 113 --- Sources/Adwaita/View/List.swift | 121 +-- Sources/Adwaita/View/Menu+.swift | 53 ++ Sources/Adwaita/View/Menu.swift | 101 --- .../View/Modifiers/AppearObserver.swift | 15 +- Sources/Adwaita/View/Modifiers/Clamp+.swift | 52 ++ Sources/Adwaita/View/Modifiers/Clamp.swift | 52 -- .../View/Modifiers/ContentModifier.swift | 2 - .../View/Modifiers/InspectorWrapper.swift | 44 +- .../View/Modifiers/ModifierStopper.swift | 2 - Sources/Adwaita/View/Modifiers/Overlay.swift | 56 -- .../View/Modifiers/ToastOverlay+.swift | 78 ++ .../Adwaita/View/Modifiers/ToastOverlay.swift | 83 -- .../Adwaita/View/Modifiers/ToolbarView.swift | 93 --- Sources/Adwaita/View/Modifiers/View+.swift | 34 + .../Adwaita/View/NavigationSplitView.swift | 16 +- Sources/Adwaita/View/OverlaySplitView+.swift | 39 + Sources/Adwaita/View/OverlaySplitView.swift | 98 --- Sources/Adwaita/View/ProgressBar+.swift | 23 + Sources/Adwaita/View/ProgressBar.swift | 46 -- Sources/Adwaita/View/ScrollView.swift | 28 +- Sources/Adwaita/View/StateWrapper.swift | 4 +- Sources/Adwaita/View/StatusPage+.swift | 29 + Sources/Adwaita/View/StatusPage.swift | 64 -- Sources/Adwaita/View/Text.swift | 37 +- Sources/Adwaita/View/Toggle.swift | 92 +-- Sources/Adwaita/View/VStack.swift | 35 +- Sources/Adwaita/View/ViewStack.swift | 29 +- Sources/Adwaita/View/ViewSwitcher.swift | 39 +- Sources/Adwaita/Window/AboutWindow.swift | 36 +- Sources/Adwaita/Window/FileDialog.swift | 13 +- Sources/Adwaita/Window/Window.swift | 6 +- Sources/CAdw/module.modulemap | 6 + Sources/CAdw/shim.h | 37 + Sources/Generation/Extensions/String.swift | 80 ++ Sources/Generation/GIR/Class+.swift | 304 +++++++ Sources/Generation/GIR/Class.swift | 227 ++++++ Sources/Generation/GIR/Constructor.swift | 16 + Sources/Generation/GIR/GIR.swift | 60 ++ Sources/Generation/GIR/GIRType.swift | 38 + Sources/Generation/GIR/Namespace.swift | 22 + Sources/Generation/GIR/Parameter.swift | 9 + Sources/Generation/GIR/Parameters.swift | 22 + Sources/Generation/GIR/Property.swift | 268 ++++++ Sources/Generation/GIR/Signal.swift | 66 ++ Sources/Generation/Generation.swift | 103 +++ .../Generation/GenerationConfiguration.swift | 266 ++++++ Sources/Generation/WidgetConfiguration.swift | 68 ++ Tests/CounterDemo.swift | 1 - Tests/Demo.swift | 12 +- Tests/FormDemo.swift | 44 +- Tests/Page.swift | 3 +- Tests/ToolbarDemo.swift | 6 - Tests/ViewSwitcherDemo.swift | 1 - user-manual/Advanced/CreatingWidgets.md | 17 +- .../Information/AutoGeneratedWidgets.md | 89 ++ user-manual/Information/Widgets.md | 12 +- 158 files changed, 13296 insertions(+), 3047 deletions(-) create mode 100644 Sources/Adwaita/Model/Enumerations/Alignment.swift create mode 100644 Sources/Adwaita/Model/Enumerations/Edge.swift create mode 100644 Sources/Adwaita/Model/Enumerations/Icon.swift create mode 100644 Sources/Adwaita/Model/Enumerations/Transition.swift create mode 100644 Sources/Adwaita/Model/Extensions/Bool.swift create mode 100644 Sources/Adwaita/Model/Extensions/Int.swift delete mode 100644 Sources/Adwaita/Model/Extensions/Libadwaita.FileDialog.swift delete mode 100644 Sources/Adwaita/Model/Extensions/NativeWidgetPeer.swift create mode 100644 Sources/Adwaita/Model/Extensions/OpaquePointer.swift create mode 100644 Sources/Adwaita/Model/Extensions/Set.swift create mode 100644 Sources/Adwaita/Model/Extensions/UInt.swift create mode 100644 Sources/Adwaita/Model/Extensions/UnsafeMutablePointer.swift create mode 100644 Sources/Adwaita/Model/Extensions/UnsafeMutableRawPointer.swift create mode 100644 Sources/Adwaita/Model/User Interface/Window/GTUIAboutWindow.swift create mode 100644 Sources/Adwaita/Model/User Interface/Window/GTUIFileDialog.swift create mode 100644 Sources/Adwaita/View/Banner+.swift delete mode 100644 Sources/Adwaita/View/Banner.swift create mode 100644 Sources/Adwaita/View/Button+.swift delete mode 100644 Sources/Adwaita/View/Button.swift create mode 100644 Sources/Adwaita/View/Carousel+.swift delete mode 100644 Sources/Adwaita/View/Carousel.swift delete mode 100644 Sources/Adwaita/View/Container.swift create mode 100644 Sources/Adwaita/View/Forms/ActionRow+.swift delete mode 100644 Sources/Adwaita/View/Forms/ActionRow.swift create mode 100644 Sources/Adwaita/View/Forms/ComboRow+.swift delete mode 100644 Sources/Adwaita/View/Forms/ComboRow.swift create mode 100644 Sources/Adwaita/View/Forms/EntryRow+.swift delete mode 100644 Sources/Adwaita/View/Forms/EntryRow.swift create mode 100644 Sources/Adwaita/View/Forms/FormSection+.swift delete mode 100644 Sources/Adwaita/View/Forms/FormSection.swift create mode 100644 Sources/Adwaita/View/Forms/PasswordEntryRow+.swift create mode 100644 Sources/Adwaita/View/Forms/SpinRow+.swift delete mode 100644 Sources/Adwaita/View/Forms/SpinRow.swift create mode 100644 Sources/Adwaita/View/Forms/SwitchRow+.swift delete mode 100644 Sources/Adwaita/View/Forms/SwitchRow.swift create mode 100644 Sources/Adwaita/View/Generated/ActionRow.swift create mode 100644 Sources/Adwaita/View/Generated/Avatar.swift create mode 100644 Sources/Adwaita/View/Generated/Banner.swift create mode 100644 Sources/Adwaita/View/Generated/Bin.swift create mode 100644 Sources/Adwaita/View/Generated/Box.swift create mode 100644 Sources/Adwaita/View/Generated/Button.swift create mode 100644 Sources/Adwaita/View/Generated/ButtonContent.swift create mode 100644 Sources/Adwaita/View/Generated/Carousel.swift create mode 100644 Sources/Adwaita/View/Generated/CenterBox.swift create mode 100644 Sources/Adwaita/View/Generated/CheckButton.swift create mode 100644 Sources/Adwaita/View/Generated/Clamp.swift create mode 100644 Sources/Adwaita/View/Generated/ComboRow.swift create mode 100644 Sources/Adwaita/View/Generated/EntryRow.swift create mode 100644 Sources/Adwaita/View/Generated/ExpanderRow.swift create mode 100644 Sources/Adwaita/View/Generated/HeaderBar.swift create mode 100644 Sources/Adwaita/View/Generated/Label.swift create mode 100644 Sources/Adwaita/View/Generated/LevelBar.swift create mode 100644 Sources/Adwaita/View/Generated/LinkButton.swift create mode 100644 Sources/Adwaita/View/Generated/ListBox.swift create mode 100644 Sources/Adwaita/View/Generated/Menu.swift create mode 100644 Sources/Adwaita/View/Generated/Overlay.swift create mode 100644 Sources/Adwaita/View/Generated/OverlaySplitView.swift create mode 100644 Sources/Adwaita/View/Generated/PasswordEntryRow.swift create mode 100644 Sources/Adwaita/View/Generated/PreferencesGroup.swift create mode 100644 Sources/Adwaita/View/Generated/PreferencesPage.swift create mode 100644 Sources/Adwaita/View/Generated/PreferencesRow.swift create mode 100644 Sources/Adwaita/View/Generated/ProgressBar.swift create mode 100644 Sources/Adwaita/View/Generated/ScrolledWindow.swift create mode 100644 Sources/Adwaita/View/Generated/SpinRow.swift create mode 100644 Sources/Adwaita/View/Generated/Spinner.swift create mode 100644 Sources/Adwaita/View/Generated/SplitButton.swift create mode 100644 Sources/Adwaita/View/Generated/StatusPage.swift create mode 100644 Sources/Adwaita/View/Generated/SwitchRow.swift create mode 100644 Sources/Adwaita/View/Generated/ToastOverlay.swift create mode 100644 Sources/Adwaita/View/Generated/ToggleButton.swift create mode 100644 Sources/Adwaita/View/Generated/ToolbarView.swift create mode 100644 Sources/Adwaita/View/Generated/WindowTitle.swift create mode 100644 Sources/Adwaita/View/HeaderBar+.swift delete mode 100644 Sources/Adwaita/View/HeaderBar.swift create mode 100644 Sources/Adwaita/View/Menu+.swift delete mode 100644 Sources/Adwaita/View/Menu.swift create mode 100644 Sources/Adwaita/View/Modifiers/Clamp+.swift delete mode 100644 Sources/Adwaita/View/Modifiers/Clamp.swift delete mode 100644 Sources/Adwaita/View/Modifiers/Overlay.swift create mode 100644 Sources/Adwaita/View/Modifiers/ToastOverlay+.swift delete mode 100644 Sources/Adwaita/View/Modifiers/ToastOverlay.swift delete mode 100644 Sources/Adwaita/View/Modifiers/ToolbarView.swift create mode 100644 Sources/Adwaita/View/OverlaySplitView+.swift delete mode 100644 Sources/Adwaita/View/OverlaySplitView.swift create mode 100644 Sources/Adwaita/View/ProgressBar+.swift delete mode 100644 Sources/Adwaita/View/ProgressBar.swift create mode 100644 Sources/Adwaita/View/StatusPage+.swift delete mode 100644 Sources/Adwaita/View/StatusPage.swift create mode 100644 Sources/CAdw/module.modulemap create mode 100644 Sources/CAdw/shim.h create mode 100644 Sources/Generation/Extensions/String.swift create mode 100644 Sources/Generation/GIR/Class+.swift create mode 100644 Sources/Generation/GIR/Class.swift create mode 100644 Sources/Generation/GIR/Constructor.swift create mode 100644 Sources/Generation/GIR/GIR.swift create mode 100644 Sources/Generation/GIR/GIRType.swift create mode 100644 Sources/Generation/GIR/Namespace.swift create mode 100644 Sources/Generation/GIR/Parameter.swift create mode 100644 Sources/Generation/GIR/Parameters.swift create mode 100644 Sources/Generation/GIR/Property.swift create mode 100644 Sources/Generation/GIR/Signal.swift create mode 100644 Sources/Generation/Generation.swift create mode 100644 Sources/Generation/GenerationConfiguration.swift create mode 100644 Sources/Generation/WidgetConfiguration.swift create mode 100644 user-manual/Information/AutoGeneratedWidgets.md diff --git a/.github/ISSUE_TEMPLATE/component_request.yml b/.github/ISSUE_TEMPLATE/component_request.yml index a1f3e32..63a6c7b 100644 --- a/.github/ISSUE_TEMPLATE/component_request.yml +++ b/.github/ISSUE_TEMPLATE/component_request.yml @@ -12,14 +12,6 @@ body: validations: required: false - - type: textarea - attributes: - label: Name the equivalents in Libadwaita. - placeholder: >- - A list of the equivalents in the Libadwaita library (see dependencies). - validations: - required: true - - type: textarea attributes: label: Describe your idea for the implementation. diff --git a/.swiftlint.yml b/.swiftlint.yml index 350656d..062b47f 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -160,3 +160,6 @@ type_contents_order: - view_life_cycle_method - ib_action - other_method + +excluded: + - Sources/Adwaita/View/Generated/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4be32e2..a4b3e66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,8 @@ Open the project folder in GNOME Builder, Xcode or another IDE. - `User Interface` contains protocols and structures that are the basis of presenting content to the user. - `View` contains structures that conform to the `View` protocol and provide an easier-to-use wrapper around a GTUI `NativeWidgetPeer` type. - `Window` contains structures that conform to the `Window` protocol and simplify the creation of different types of windows. + - `CAdw` contains the reference to the C library. + - `Generation` contains the code for the auto-generation of Adwaita and Gtk widgets. - `Tests` contains an example application for testing `Adwaita`. ### 4. Edit the Code diff --git a/LICENSE.md b/LICENSE.md index 10926e8..626e124 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,675 +1,21 @@ - GNU 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. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - +MIT License + +Copyright (c) 2024 david-swift + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift index 16a12ae..a180a8a 100644 --- a/Package.swift +++ b/Package.swift @@ -15,25 +15,39 @@ let package = Package( .library( name: "Adwaita", targets: ["Adwaita"] + ), + .library( + name: "CAdw", + targets: ["CAdw"] ) ], dependencies: [ - .package(url: "https://github.com/AparokshaUI/Libadwaita", from: "0.1.6"), .package( url: "https://github.com/david-swift/LevenshteinTransformations", from: "0.1.1" - ) + ), + .package(url: "https://github.com/CoreOffice/XMLCoder", from: "0.17.1") ], targets: [ + .systemLibrary( + name: "CAdw", + pkgConfig: "libadwaita-1" + ), .target( name: "Adwaita", dependencies: [ - .product(name: "Libadwaita", package: "Libadwaita"), + "CAdw", .product(name: "LevenshteinTransformations", package: "LevenshteinTransformations") ] ), .executableTarget( - name: "Swift Adwaita Demo", + name: "Generation", + dependencies: [ + .product(name: "XMLCoder", package: "XMLCoder") + ] + ), + .executableTarget( + name: "Demo", dependencies: ["Adwaita"], path: "Tests" ) diff --git a/README.md b/README.md index 62c8954..3636660 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Adwaita Icon -

Adwaita

+

Adwaita for Swift

@@ -110,15 +110,17 @@ I recommend using the [template repository](https://github.com/AparokshaUI/Adwai ### Information * [Widgets](user-manual/Information/Widgets.md) +* [Auto-Generated Widgets](user-manual/Information/AutoGeneratedWidgets.md) ## Thanks ### Dependencies -- [Libadwaita][18] licensed under the [GPL-3.0 license][19] +- [XMLCoder][18] licensed under the [MIT license][19] - [Levenshtein Transformations](https://github.com/david-swift/LevenshteinTransformations) licensed under the [MIT license](https://github.com/david-swift/LevenshteinTransformations/blob/main/LICENSE.md) ### Other Thanks - The [contributors][20] +- The auto-generation of widgets is based on [Swift Cross UI](https://github.com/stackotter/swift-cross-ui) - [SwiftLint][21] for checking whether code style conventions are violated - The programming language [Swift][22] - [SourceDocs][23] used for generating the [docs][24] @@ -140,8 +142,8 @@ I recommend using the [template repository](https://github.com/AparokshaUI/Adwai [15]: user-manual/Basics/Windows.md [16]: user-manual/Basics/KeyboardShortcuts.md [17]: user-manual/Advanced/CreatingWidgets.md -[18]: https://github.com/AparokshaUI/Libadwaita -[19]: https://github.com/AparokshaUI/Libadwaita/blob/main/LICENSE.md +[18]: https://github.com/CoreOffice/XMLCoder +[19]: https://github.com/CoreOffice/XMLCoder/blob/main/LICENSE [20]: Contributors.md [21]: https://github.com/realm/SwiftLint [22]: https://github.com/apple/swift diff --git a/SUMMARY.md b/SUMMARY.md index 9e747b8..b203483 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -17,6 +17,7 @@ ## Information * [Widgets](user-manual/Information/Widgets.md) +* [Auto-Generated Widgets](user-manual/Information/AutoGeneratedWidgets.md) [1]: README.md [2]: user-manual/GettingStarted.md diff --git a/Sources/Adwaita/Menu/MenuButton.swift b/Sources/Adwaita/Menu/MenuButton.swift index 70b59c7..c3d4351 100644 --- a/Sources/Adwaita/Menu/MenuButton.swift +++ b/Sources/Adwaita/Menu/MenuButton.swift @@ -5,7 +5,7 @@ // Created by david-swift on 22.10.23. // -import Libadwaita +import CAdw /// A button widget for menus. public struct MenuButton: MenuItem { @@ -19,6 +19,9 @@ public struct MenuButton: MenuItem { /// Whether to prefer adding the action to the application window. var preferApplicationWindow: Bool + /// The action label. + var filteredLabel: String { label.filter { $0.isLetter || $0.isNumber || $0 == "-" || $0 == "." } } + /// Initialize a menu button. /// - Parameters: /// - label: The buttons label. @@ -35,11 +38,13 @@ public struct MenuButton: MenuItem { /// - menu: The menu. /// - app: The application containing the menu. /// - window: The application window containing the menu. - public func addMenuItem(menu: Libadwaita.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { + public func addMenuItem(menu: OpaquePointer?, app: GTUIApp, window: GTUIApplicationWindow?) { if let window, preferApplicationWindow { - _ = menu.append(label, window: window, shortcut: shortcut, handler: handler) + window.addKeyboardShortcut(shortcut, id: filteredLabel, handler: handler) + g_menu_append(menu, label, "win." + filteredLabel) } else { - _ = menu.append(label, app: app, shortcut: shortcut, handler: handler) + app.addKeyboardShortcut(shortcut, id: filteredLabel, handler: handler) + g_menu_append(menu, label, "app." + filteredLabel) } } diff --git a/Sources/Adwaita/Menu/MenuSection.swift b/Sources/Adwaita/Menu/MenuSection.swift index 6f67f6e..e4d48b1 100644 --- a/Sources/Adwaita/Menu/MenuSection.swift +++ b/Sources/Adwaita/Menu/MenuSection.swift @@ -5,7 +5,7 @@ // Created by david-swift on 22.10.23. // -import Libadwaita +import CAdw /// A section for menus. public struct MenuSection: MenuItem { @@ -24,9 +24,9 @@ public struct MenuSection: MenuItem { /// - menu: The menu. /// - app: The application containing the menu. /// - window: The application window containing the menu. - public func addMenuItem(menu: Libadwaita.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { - let section = Libadwaita.Menu() - _ = menu.append("", section: section) + public func addMenuItem(menu: OpaquePointer?, app: GTUIApp, window: GTUIApplicationWindow?) { + let section = g_menu_new() + g_menu_append_section(menu, nil, section?.cast()) for element in sectionContent { element.addMenuItems(menu: section, app: app, window: window) } diff --git a/Sources/Adwaita/Menu/Submenu.swift b/Sources/Adwaita/Menu/Submenu.swift index d1137b5..5cede15 100644 --- a/Sources/Adwaita/Menu/Submenu.swift +++ b/Sources/Adwaita/Menu/Submenu.swift @@ -5,7 +5,7 @@ // Created by david-swift on 22.10.23. // -import Libadwaita +import CAdw /// A submenu widget. public struct Submenu: MenuItem { @@ -29,9 +29,9 @@ public struct Submenu: MenuItem { /// - menu: The menu. /// - app: The application containing the menu. /// - window: The application window containing the menu. - public func addMenuItem(menu: Libadwaita.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { - let submenu = Libadwaita.Menu() - _ = menu.append(label, submenu: submenu) + public func addMenuItem(menu: OpaquePointer?, app: GTUIApp, window: GTUIApplicationWindow?) { + let submenu = g_menu_new() + g_menu_append_submenu(menu, label, submenu?.cast()) for element in submenuContent { element.addMenuItems(menu: submenu, app: app, window: window) } diff --git a/Sources/Adwaita/Model/ArrayBuilder.swift b/Sources/Adwaita/Model/ArrayBuilder.swift index 13fafa3..2adf197 100644 --- a/Sources/Adwaita/Model/ArrayBuilder.swift +++ b/Sources/Adwaita/Model/ArrayBuilder.swift @@ -9,8 +9,6 @@ // https://gist.github.com/JadenGeller/c375fc15ad5900a0ddac4ed8ba8307a9 // -import Foundation - /// The ``ArrayBuilder`` is a simple result builder that outputs an array of any type. /// /// You can define any array using Swift's DSL: diff --git a/Sources/Adwaita/Model/Data Flow/State.swift b/Sources/Adwaita/Model/Data Flow/State.swift index 9b4738c..38a45fb 100644 --- a/Sources/Adwaita/Model/Data Flow/State.swift +++ b/Sources/Adwaita/Model/Data Flow/State.swift @@ -5,8 +5,8 @@ // Created by david-swift on 06.08.23. // +import CAdw import Foundation -import Libadwaita /// A property wrapper for properties in a view that should be stored throughout view updates. @propertyWrapper @@ -110,11 +110,23 @@ public struct State: StateProtocol { } } + /// The directory used for storing user data. + /// - Returns: The URL. + public static func userDataDir() -> URL { + .init(fileURLWithPath: .init(cString: g_get_user_data_dir())) + } + + /// Copy a text to the clipboard. + /// - Parameter text: The text. + public static func copy(_ text: String) { + let clipboard = gdk_display_get_clipboard(gdk_display_get_default()) + gdk_clipboard_set_text(clipboard, text) + } + /// Get the settings directory path. /// - Returns: The path. private func dirPath() -> URL { - NativePeer - .getUserDataDirectory() + Self.userDataDir() .appendingPathComponent(content.storage.folder ?? GTUIApp.appID, isDirectory: true) } diff --git a/Sources/Adwaita/Model/Enumerations/Alignment.swift b/Sources/Adwaita/Model/Enumerations/Alignment.swift new file mode 100644 index 0000000..ee76cc5 --- /dev/null +++ b/Sources/Adwaita/Model/Enumerations/Alignment.swift @@ -0,0 +1,44 @@ +// +// Alignment.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +import CAdw + +/// The alignment for a widget. +public enum Alignment: Int { + + /// The widget will fill the available space. + case fill + /// The widget will start at the beginning of the available space. + case start + /// The widget will end at the end of the available space. + case end + /// The widget will be centered in the available space. + case center + /// The widget will be baseline aligned in the available space. + case baselineFill + /// The widget will be baseline aligned at the start of the available space. + case baselineCenter + + /// Get the GtkAlign alignment. + public var cAlign: GtkAlign { + switch self { + case .fill: + return GTK_ALIGN_FILL + case .start: + return GTK_ALIGN_START + case .end: + return GTK_ALIGN_END + case .center: + return GTK_ALIGN_CENTER + case .baselineFill: + return GTK_ALIGN_BASELINE_FILL + case .baselineCenter: + return GTK_ALIGN_BASELINE_CENTER + } + } + +} diff --git a/Sources/Adwaita/Model/Enumerations/Edge.swift b/Sources/Adwaita/Model/Enumerations/Edge.swift new file mode 100644 index 0000000..bb1896b --- /dev/null +++ b/Sources/Adwaita/Model/Enumerations/Edge.swift @@ -0,0 +1,20 @@ +// +// Edge.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +/// The edges for a widget. +public enum Edge { + + /// The leading (start) edge. + case leading + /// The trailing (end) edge. + case trailing + /// The top edge. + case top + /// The bottom edge. + case bottom + +} diff --git a/Sources/Adwaita/Model/Enumerations/Icon.swift b/Sources/Adwaita/Model/Enumerations/Icon.swift new file mode 100644 index 0000000..348751c --- /dev/null +++ b/Sources/Adwaita/Model/Enumerations/Icon.swift @@ -0,0 +1,767 @@ +// +// Icon.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +import Foundation + +// swiftlint:disable type_body_length file_length +/// An icon. +public enum Icon { + + /// A preinstalled icon. + /// - Parameter icon: The default icon. + case `default`(icon: DefaultIcon) + /// A custom icon. + /// - Parameter name: The icon's name. + case custom(name: String) + + /// A string representation of the icon. + public var string: String { + switch self { + case let .default(icon): + return icon.string + case let .custom(name): + return name + } + } + + /// A preinstalled icon. + public enum DefaultIcon: String { + // swiftlint:disable missing_docs identifier_name + case acAdapter + case accessoriesCalculator + case accessoriesCharacterMap + case accessoriesDictionary + case accessoriesTextEditor + case actionUnavailable + case addressBookNew + case airplaneMode + case alarm + case appRemove + case appletsScreenshooter + case applicationCertificate + case applicationExitRtl + case applicationExit + case applicationRss_plus_xml + case applicationXAddon + case applicationXAppliance + case applicationXExecutable + case applicationXFirmware + case applicationXSharedlib + case applicationsEngineering + case applicationsGames + case applicationsGraphics + case applicationsMultimedia + case applicationsScience + case applicationsSystem + case applicationsUtilities + case appointmentMissed + case appointmentNew + case appointmentSoon + case audioCard + case audioHeadphones + case audioHeadset + case audioInputMicrophone + case audioSpeakersRtl + case audioSpeakers + case audioVolumeHighRtl + case audioVolumeHigh + case audioVolumeLowRtl + case audioVolumeLow + case audioVolumeMediumRtl + case audioVolumeMedium + case audioVolumeMutedRtl + case audioVolumeMuted + case audioVolumeOveramplifiedRtl + case audioVolumeOveramplified + case audioXGeneric + case authFace + case authFingerprint + case authSimLocked + case authSimMissing + case authSim + case authSmartcard + case avatarDefault + case batteryAction + case batteryCautionCharging + case batteryCaution + case batteryEmptyCharging + case batteryEmpty + case batteryFullCharged + case batteryFullCharging + case batteryFull + case batteryGoodCharging + case batteryGood + case batteryLevel_0Charging + case batteryLevel_0 + case batteryLevel_10Charging + case batteryLevel_10 + case batteryLevel_100Charged + case batteryLevel_100 + case batteryLevel_20Charging + case batteryLevel_20 + case batteryLevel_30Charging + case batteryLevel_30 + case batteryLevel_40Charging + case batteryLevel_40 + case batteryLevel_50Charging + case batteryLevel_50 + case batteryLevel_60Charging + case batteryLevel_60 + case batteryLevel_70Charging + case batteryLevel_70 + case batteryLevel_80Charging + case batteryLevel_80 + case batteryLevel_90Charging + case batteryLevel_90 + case batteryLowCharging + case batteryLow + case batteryMissing + case battery + case bluetoothAcquiring + case bluetoothActive + case bluetoothDisabled + case bluetoothDisconnected + case bluetoothHardwareDisabled + case bluetooth + case bookmarkNew + case callIncoming + case callMissed + case callOutgoing + case callStart + case callStop + case cameraDisabled + case cameraHardwareDisabled + case cameraPhoto + case cameraSwitch + case cameraVideo + case cameraWeb + case capsLock + case changesAllow + case changesPrevent + case channelInsecure + case channelSecure + case chatMessageNew + case checkboxChecked + case checkboxMixed + case checkbox + case colorSelect + case colorimeterColorhug + case completionSnippet + case completionWord + case computerAppleIpad + case computerFail + case computer + case contactNew + case contentLoading + case daytimeSunrise + case daytimeSunset + case dialogError + case dialogInformation + case dialogPassword + case dialogQuestion + case dialogWarning + case displayBrightness + case displayProjector + case documentEdit + case documentNew + case documentOpenRecent + case documentOpen + case documentPageSetup + case documentPrintPreview + case documentPrint + case documentProperties + case documentRevertRtl + case documentRevert + case documentSaveAs + case documentSave + case documentSend + case driveHarddiskIeee1394 + case driveHarddiskSolidstate + case driveHarddisk + case driveHarddiskSystem + case driveHarddiskUsb + case driveMultidisk + case driveOptical + case driveRemovableMedia + case editClearAll + case editClearRtl + case editClear + case editCopy + case editCut + case editDelete + case editFindReplace + case editFind + case editPaste + case editRedo + case editSelectAll + case editSelect + case editUndo + case emblemDefault + case emblemDocuments + case emblemFavorite + case emblemImportant + case emblemMusic + case emblemOk + case emblemPhotos + case emblemShared + case emblemSynchronizing + case emblemSystem + case emblemVideos + case emojiActivities + case emojiBody + case emojiFlags + case emojiFood + case emojiNature + case emojiObjects + case emojiPeople + case emojiRecent + case emojiSymbols + case emojiTravel + case emoteLove + case errorCorrect + case faceAngel + case faceAngry + case faceConfused + case faceCool + case faceCrying + case faceDevilish + case faceEmbarrassed + case faceGlasses + case faceKiss + case faceLaugh + case faceMonkey + case facePlain + case faceRaspberry + case faceSad + case faceShutmouth + case faceSick + case faceSmileBig + case faceSmile + case faceSmirk + case faceSurprise + case faceTired + case faceUncertain + case faceWink + case faceWorried + case faceYawn + case findLocation + case focusLegacySystray + case focusTopBar + case focusWindows + case folderDocuments + case folderDownload + case folderDragAccept + case folderMusic + case folderNew + case folderOpen + case folderPictures + case folderPublicshare + case folderRemote + case folderSavedSearch + case folder + case folderTemplates + case folderVideos + case folderVisiting + case fontSelect + case fontXGeneric + case formatIndentLessRtl + case formatIndentLess + case formatIndentMoreRtl + case formatIndentMore + case formatJustifyCenter + case formatJustifyFill + case formatJustifyLeft + case formatJustifyRight + case formatTextBold + case formatTextDirectionLtr + case formatTextDirectionRtl + case formatTextDirection + case formatTextItalic + case formatTextPlaintext + case formatTextRich + case formatTextStrikethrough + case formatTextUnderline + case functionLinear + case gestureSwipeLeft + case gestureSwipeRight + case gnomeDisksStateStandby + case gnomePowerManager + case goBottom + case goDown + case goFirst + case goHome + case goJumpRtl + case goJump + case goLast + case goNext + case goPrevious + case goTop + case goUp + case goaAccountExchange + case goaAccountGoogle + case goaAccountLastfm + case goaAccountMsn + case goaAccountOwncloud + case goaAccount + case goaPanel + case gtk3Demo + case gtk3WidgetFactory + case helpAbout + case helpBrowser + case helpContents + case helpFaq + case imageLoading + case imageMissing + case imageXGeneric + case info + case inodeDirectory + case inputDialpad + case inputGaming + case inputKeyboard + case inputMouse + case inputTablet + case inputTouchpad + case insertImage + case insertLink + case insertObject + case insertText + case keyboardBrightness + case langClass + case langDefine + case langEnum + case langEnumValue + case langFunction + case langInclude + case langMethod + case langNamespace + case langStructField + case langStruct + case langTypedef + case langUnion + case langVariable + case libreofficeBase + case libreofficeCalc + case libreofficeDraw + case libreofficeImpress + case libreofficeMain + case libreofficeMath + case libreofficeWriter + case listAdd + case listDragHandle + case listRemoveAll + case listRemove + case locationServicesActive + case locationServicesDisabled + case mailAttachment + case mailForward + case mailMarkImportant + case mailMarkJunk + case mailMarkNotjunk + case mailMessageNew + case mailRead + case mailRepliedRtl + case mailReplied + case mailReplyAllRtl + case mailReplyAll + case mailReplySender + case mailSendReceive + case mailSend + case mailUnread + case markLocation + case mediaEject + case mediaFlash + case mediaFloppy + case mediaOpticalBd + case mediaOpticalCdAudio + case mediaOpticalCd + case mediaOpticalDvd + case mediaOptical + case mediaPlaybackPause + case mediaPlaybackStart + case mediaPlaybackStop + case mediaPlaylistConsecutive + case mediaPlaylistRepeatSong + case mediaPlaylistRepeat + case mediaPlaylistShuffle + case mediaRecord + case mediaRemovable + case mediaSeekBackward + case mediaSeekForward + case mediaSkipBackward + case mediaSkipForward + case mediaTape + case mediaViewSubtitles + case mediaZip + case microphoneDisabled + case microphoneHardwareDisabled + case microphoneSensitivityHigh + case microphoneSensitivityLow + case microphoneSensitivityMedium + case microphoneSensitivityMuted + case modem + case multimediaPlayerAppleIpodTouch + case multimediaPlayer + case multimediaVolumeControl + case networkCellular_2g + case networkCellular_3g + case networkCellular_4g + case networkCellular_5g + case networkCellularAcquiringRtl + case networkCellularAcquiring + case networkCellularConnected + case networkCellularDisabledRtl + case networkCellularDisabled + case networkCellularEdge + case networkCellularGprs + case networkCellularHardwareDisabledRtl + case networkCellularHardwareDisabled + case networkCellularHspa + case networkCellularNoRouteRtl + case networkCellularNoRoute + case networkCellularOfflineRtl + case networkCellularOffline + case networkCellularSignalExcellentRtl + case networkCellularSignalExcellent + case networkCellularSignalGoodRtl + case networkCellularSignalGood + case networkCellularSignalNoneRtl + case networkCellularSignalNone + case networkCellularSignalOkRtl + case networkCellularSignalOk + case networkCellularSignalWeakRtl + case networkCellularSignalWeak + case networkCellular + case networkError + case networkIdle + case networkNoRoute + case networkOffline + case networkReceive + case networkServer + case networkTransmitReceive + case networkTransmit + case networkVpnAcquiring + case networkVpnDisabled + case networkVpnDisconnected + case networkVpnNoRoute + case networkVpn + case networkWiredAcquiring + case networkWiredDisconnected + case networkWiredNoRoute + case networkWired + case networkWirelessAcquiring + case networkWirelessConnected + case networkWirelessDisabled + case networkWirelessEncrypted + case networkWirelessHardwareDisabled + case networkWirelessHotspot + case networkWirelessNoRoute + case networkWirelessOffline + case networkWirelessSignalExcellent + case networkWirelessSignalGood + case networkWirelessSignalNone + case networkWirelessSignalOk + case networkWirelessSignalWeak + case networkWireless + case networkWorkgroup + case nightLightDisabled + case nightLight + case nmDeviceWiredSecure + case nmDeviceWired + case nmDeviceWwan + case nonStarred + case notificationsDisabled + case objectFlipHorizontal + case objectFlipVertical + case objectRotateLeft + case objectRotateRight + case objectSelect + case openMenu + case orca + case fedoraprojectAnacondaInstaller = "org.fedoraproject.AnacondaInstaller" + case freedesktopMalcontentControl = "org.freedesktop.MalcontentControl" + case gnomeAdwaita1Demo = "org.gnome.Adwaita1.Demo" + case gnomeBoxes = "org.gnome.Boxes" + case gnomeBuilder = "org.gnome.Builder" + case gnomeCalculator = "org.gnome.Calculator" + case gnomeCharacters = "org.gnome.Characters" + case gnomeCheese = "org.gnome.Cheese" + case gnomeConsole = "org.gnome.Console" + case gnomeDiskUtility = "org.gnome.DiskUtility" + case gnomeEpiphany = "org.gnome.Epiphany" + case gnomeEvince = "org.gnome.Evince" + case gnomeLogs = "org.gnome.Logs" + case gnomeMaps = "org.gnome.Maps" + case gnomeNautilus = "org.gnome.Nautilus" + case gnomePhotos = "org.gnome.Photos" + case gnomeRhythmbox3 = "org.gnome.Rhythmbox3" + case gnomeSettingsAbout = "org.gnome.Settings-about" + case gnomeSettingsAccessibility = "org.gnome.Settings-accessibility" + case gnomeSettingsAppearance = "org.gnome.Settings-appearance" + case gnomeSettingsApplications = "org.gnome.Settings-applications" + case gnomeSettingsBluetooth = "org.gnome.Settings-bluetooth" + case gnomeSettingsCamera = "org.gnome.Settings-camera" + case gnomeSettingsColor = "org.gnome.Settings-color" + case gnomeSettingsDefaultApps = "org.gnome.Settings-default-apps" + case gnomeSettingsDiagnostics = "org.gnome.Settings-diagnostics" + case gnomeSettingsDisplay = "org.gnome.Settings-display" + case gnomeSettingsFileHistory = "org.gnome.Settings-file-history" + case gnomeSettingsKeyboard = "org.gnome.Settings-keyboard" + case gnomeSettingsLocation = "org.gnome.Settings-location" + case gnomeSettingsMicrophone = "org.gnome.Settings-microphone" + case gnomeSettingsMobileNetwork = "org.gnome.Settings-mobile-network" + case gnomeSettingsMouse = "org.gnome.Settings-mouse" + case gnomeSettingsMultitasking = "org.gnome.Settings-multitasking" + case gnomeSettingsNetwork = "org.gnome.Settings-network" + case gnomeSettingsNotifications = "org.gnome.Settings-notifications" + case gnomeSettingsOnlineAccounts = "org.gnome.Settings-online-accounts" + case gnomeSettingsPower = "org.gnome.Settings-power" + case gnomeSettingsPinters = "org.gnome.Setting-printers" + case gnomeSettingsRegion = "org.gnome.Settings-region" + case gnomeSettingsRemovableMedia = "org.gnome.Settings-removable-media" + case gnomeSettingsSearch = "org.gnome.Settings-search" + case gnomeSettingsSharing = "org.gnome.Settings-sharing" + case gnomeSettingsSound = "org.gnome.Settings-sound" + case gnomeSettings = "org.gnome.Settings" + case gnomeSettingsSystemLockScreen = "org.gnome.Settings-system-lock-screen" + case gnomeSettingsThunderbolt = "org.gnome.Settings-thunderbolt" + case gnomeSettingsTime = "org.gnome.Settings-time" + case gnomeSettingsUsers = "org.gnome.Settings-users" + case gnomeSettingsWacom = "org.gnome.Settings-wacom" + case gnomeShellExtensions = "org.gnome.Shell.Extensions" + case gnomeSoftware = "org.gnome.Software" + case gnomeSystemMonitor = "org.gnome.SystemMonitor" + case gnomeTextEditor = "org.gnome.TextEditor" + case gnomeTotem = "org.gnome.Totem" + case gnomeWeather = "org.gnome.Weather" + case gnomeYelp = "org.gnome.Yelp" + case gnomeBaobab = "org.gnome.baobab" + case gnomeClocks = "org.gnome.clocks" + case gnomeDesignIconLibrary = "org.gnome.design.IconLibrary" + case gnomeEog = "org.gnome.eog" + case gnomeFontViewer = "org.gnome.font-viewer" + case gnomeTweaks = "org.gnome.tweaks" + case gtkDemo4 = "org.gtk.Demo4" + case gtkIconBrowser4 = "org.gtk.IconBrowser4" + case gtkPrintEditor4 = "org.gtk.PrintEditor4" + case gtkWidgetFactory4 = "org.gtk.WidgetFactory4" + case gtkGtk4NodeEditor = "org.gtk.gtk4.NodeEditor" + case orientationLandscapeInverse + case orientationLandscape + case orientationPortraitInverse + case orientationPortraitLeft + case orientationPortraitRight + case orientationProtrait + case packageXGeneric + case panDown + case panEnd + case panStart + case panUp + case panelBottom + case panelCenter + case panelLeft + case panelModified + case panelRight + case panelTop + case pda + case phoneAppleIphone + case phoneOld + case phone + case powerProfileBalancedRtl + case powerProfileBalanced + case powerProfilePerformanceRtl + case powerProfilePerformance + case powerProfilePowerSaverRtl + case powerProfilePowerSaver + case preferencesColor + case preferencesDesktopAccessibility + case preferencesDesktopAppearance + case preferencesDesktopApps + case preferencesDesktopDisplay + case preferencesDesktopFont + case preferencesDesktopKeyboardShortcuts + case preferencesDesktopKeyboard + case preferencesDesktopLocale + case preferencesDesktopMultitasking + case preferencesDesktopRemoteDesktop + case preferencesDesktopScreensaver + case preferencesDesktopWallpaper + case preferencesOther + case preferencesSystemDetails + case preferencesSystemDevices + case preferencesSystemNetworkProxy + case preferencesSystemNetwork + case preferencesSystemNotifications + case preferencesSystemParentalControls + case preferencesSystemPrivacy + case preferencesSystemSearch + case preferencesSystemSharing + case preferencesSystem + case preferencesSystemTime + case printerError + case printerNetwork + case printerPrinting + case printer + case printerWarning + case processStop + case processWorking + case radioChecked + case radioMixed + case radio + case rotationAllowed + case rotationLocked + case scanner + case screenShared + case securityHigh + case securityLow + case securityMediumRtl + case securityMedium + case selectionEnd + case selectionMode + case selectionStart + case semiStarredRtl + case semiStarred + case sendTo + case sidebarShowRight + case sidebarShow + case softwareUpdateAvailable + case softwareUpdateUrgent + case speedometer + case starNew + case starred + case startHere + case switchOff + case switchOn + case systemFileManager + case systemHelp + case systemLockScreen + case systemLogOutRtl + case systemLogOut + case systemReboot + case systemRun + case systemSearch + case systemShutdown + case systemSoftwareInstall + case systemSwitchUserRtl + case systemSwitchUser + case systemUsers + case tabNew + case tablet + case taskDue + case taskPastDue + case temperature + case textEditor + case textXGeneric + case thunderboltAcquiring + case thunderbolt + case toolsCheckSpelling + case totemTv + case touchDisabled + case touchpadDisabled + case tv + case uninterruptiblePowerSupply + case userAvailable + case userAway + case userBookmarks + case userBusy + case userDesktop + case userHome + case userIdle + case userInfo + case userInvisible + case userNotTracked + case userOffline + case userStatusPending + case userTrashFull + case userTrash + case utilitiesTerminal + case valueDecrease + case valueIncrease + case videoDisplay + case videoJoineDisplays + case videoSingleDisplay + case videoXGeneric + case viewAppGrid + case viewConceal + case viewContinuous + case viewDual + case viewFullscreen + case viewGrid + case viewListBulletRtl + case viewListBullet + case viewListOrderedRtl + case viewListOrdered + case viewListRtl + case viewList + case viewMirror + case viewMoreHorizontal + case viewMore + case viewPagedRtl + case viewPaged + case viewPin + case viewRefresh + case viewRestore + case viewReveal + case viewSortAscendingRtl + case viewSortAscending + case viewSortDescendingRtl + case viewSortDescending + case viewWrappedRtl + case viewWrapped + case weatherClearNight + case weatherClear + case weatherFewCloudsNight + case weatherFewClouds + case weatherFog + case weatherHourly + case weatherOvercast + case weatherSevereAlert + case weatherShowersScattered + case weatherShowers + case weatherSnow + case weatherStorm + case weatherTornado + case weatherWindy + case webBrowser + case windowClose + case windowMaximize + case windowMinimize + case windowNew + case windowRestore + case xOfficeAddressBook + case xOfficeCalendar + case xOfficeDocument + case xOfficeDrawing + case xOfficePresentation + case xOfficeSpreadsheet + case zoomFitBest + case zoomIn + case zoomOriginal + case zoomOut + // swiftlint:enable missing_docs identifier_name + + /// A string representation of the icon. + public var string: String { + var string = rawValue + if !string.hasPrefix("org.") { + let result = string + .map { letter in + if letter.isUppercase { + return "-\(letter)" + } else { + return "\(letter)" + } + } + .joined() + string = result.lowercased() + } + string = string.replacingOccurrences(of: "_plus_", with: "+") + string = string.replacingOccurrences(of: "_", with: "-") + return string + "-symbolic" + } + } +} +// swiftlint:enable type_body_length file_length diff --git a/Sources/Adwaita/Model/Enumerations/Transition.swift b/Sources/Adwaita/Model/Enumerations/Transition.swift new file mode 100644 index 0000000..911eb9f --- /dev/null +++ b/Sources/Adwaita/Model/Enumerations/Transition.swift @@ -0,0 +1,75 @@ +// +// Transition.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +import CAdw + +/// A transition for a stack. +public enum Transition: Int { + + // swiftlint:disable missing_docs discouraged_none_name + case none + case crossfade + case slideRight, slideLeft, slideUp, slideDown, slideLeftRight, slideUpDown + case coverUp, coverDown, coverLeft, coverRight + case uncoverUp, uncoverDown, uncoverLeft, uncoverRight + case coverUpDown, coverDownUp, coverLeftRight, coverRightLeft + case rotateLeft, rotateRight, rotateLeftRight + // swiftlint:enable missing_docs discouraged_none_name + + /// Get the GtkStackTransitionType transition. + public var cTransition: GtkStackTransitionType { + switch self { + case .none: + return GTK_STACK_TRANSITION_TYPE_NONE + case .crossfade: + return GTK_STACK_TRANSITION_TYPE_CROSSFADE + case .slideRight: + return GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT + case .slideLeft: + return GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT + case .slideUp: + return GTK_STACK_TRANSITION_TYPE_SLIDE_UP + case .slideDown: + return GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN + case .slideLeftRight: + return GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT + case .slideUpDown: + return GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN + case .coverUp: + return GTK_STACK_TRANSITION_TYPE_OVER_UP + case .coverDown: + return GTK_STACK_TRANSITION_TYPE_OVER_DOWN + case .coverLeft: + return GTK_STACK_TRANSITION_TYPE_OVER_LEFT + case .coverRight: + return GTK_STACK_TRANSITION_TYPE_OVER_RIGHT + case .uncoverUp: + return GTK_STACK_TRANSITION_TYPE_UNDER_UP + case .uncoverDown: + return GTK_STACK_TRANSITION_TYPE_UNDER_DOWN + case .uncoverLeft: + return GTK_STACK_TRANSITION_TYPE_UNDER_LEFT + case .uncoverRight: + return GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT + case .coverUpDown: + return GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN + case .coverDownUp: + return GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP + case .coverLeftRight: + return GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT + case .coverRightLeft: + return GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT + case .rotateLeft: + return GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT + case .rotateRight: + return GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT + case .rotateLeftRight: + return GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT + } + } + +} diff --git a/Sources/Adwaita/Model/Extensions/Array.swift b/Sources/Adwaita/Model/Extensions/Array.swift index bdedc60..778fba7 100644 --- a/Sources/Adwaita/Model/Extensions/Array.swift +++ b/Sources/Adwaita/Model/Extensions/Array.swift @@ -5,7 +5,7 @@ // Created by david-swift on 06.08.23. // -import Libadwaita +import Foundation extension Array: View where Element == View { @@ -22,7 +22,7 @@ extension Array: View where Element == View { var modified = self for (index, view) in modified.enumerated() { for modifier in modifiers { - modified[index] = modifier(view) + modified[safe: index] = modifier(view) } } return VStack { modified } @@ -53,6 +53,28 @@ extension Array where Element == WindowSceneGroup { } +extension Array where Element == String { + + /// Get the C version of the array. + var cArray: UnsafePointer?>? { + let cStrings = self.map { $0.utf8CString } + let cStringPointers = cStrings.map { $0.withUnsafeBufferPointer { $0.baseAddress } } + let optionalCStringPointers = cStringPointers + [nil] + var optionalCStringPointersCopy = optionalCStringPointers + optionalCStringPointersCopy.withUnsafeMutableBufferPointer { bufferPointer in + bufferPointer.baseAddress?.advanced(by: cStrings.count).pointee = nil + } + let flatArray = optionalCStringPointersCopy.compactMap { $0 } + let pointer = UnsafeMutablePointer?>.allocate(capacity: flatArray.count + 1) + for (index, element) in flatArray.enumerated() { + pointer.advanced(by: index).pointee = element + } + pointer.advanced(by: flatArray.count).pointee = nil + return UnsafePointer(pointer) + } + +} + extension Array { /// Accesses the element at the specified position safely. diff --git a/Sources/Adwaita/Model/Extensions/Bool.swift b/Sources/Adwaita/Model/Extensions/Bool.swift new file mode 100644 index 0000000..ea35d6f --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/Bool.swift @@ -0,0 +1,15 @@ +// +// Bool.swift +// Adwaita +// +// Created by david-swift on 15.01.24. +// + +extension Bool { + + /// Get the gboolean for C. + public var cBool: Int32 { + self ? 1 : 0 + } + +} diff --git a/Sources/Adwaita/Model/Extensions/Int.swift b/Sources/Adwaita/Model/Extensions/Int.swift new file mode 100644 index 0000000..580036b --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/Int.swift @@ -0,0 +1,17 @@ +// +// Int.swift +// Adwaita +// +// Created by david-swift on 15.01.24. +// + +extension Int: Identifiable { + + /// Get the integer itself as the identifier. + public var id: Int { self } + /// The C integer. + public var cInt: Int32 { + .init(self) + } + +} diff --git a/Sources/Adwaita/Model/Extensions/Libadwaita.FileDialog.swift b/Sources/Adwaita/Model/Extensions/Libadwaita.FileDialog.swift deleted file mode 100644 index ac8c008..0000000 --- a/Sources/Adwaita/Model/Extensions/Libadwaita.FileDialog.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Libadwaita.FileDialog.swift -// Adwaita -// -// Created by david-swift on 09.12.23. -// - -import Foundation -import Libadwaita - -extension Libadwaita.FileDialog: WindowType { - - /// An ID for the importer field. - static var importer: String { "importer" } - /// An ID for the folder field. - static var folder: String { "folder" } - /// An ID for the result field. - static var result: String { "result" } - /// An ID for the cancel field. - static var cancel: String { "cancel" } - - /// Whether the file dialog is an importer. - var isImporter: Bool { - get { - fields[Self.importer] as? Bool ?? true - } - set { - fields[Self.importer] = newValue - } - } - /// The selected folder in the file dialog. - var folder: URL? { - get { - fields[Self.folder] as? URL - } - set { - fields[Self.folder] = newValue - } - } - /// A closure triggered on selecting a file in the dialog. - var onResult: ((URL) -> Void) { - get { - fields[Self.result] as? (URL) -> Void ?? { _ in } - } - set { - fields[Self.result] = newValue - } - } - /// A closure triggered when the dialog is canceled. - var onCancel: (() -> Void) { - get { - fields[Self.cancel] as? () -> Void ?? { } - } - set { - fields[Self.cancel] = newValue - } - } - - /// Set the window's parent window. - /// - Parameter parent: The parent window. - /// - /// Currently not implemented. - public func setParentWindow(_ parent: WindowType) { } - - /// Display the file dialog. - public func show() { - if isImporter { - self.open(folder: folder, onResult, onClose: onCancel) - } else { - self.save(folder: folder, onResult, onClose: onCancel) - } - } - -} diff --git a/Sources/Adwaita/Model/Extensions/NativeWidgetPeer.swift b/Sources/Adwaita/Model/Extensions/NativeWidgetPeer.swift deleted file mode 100644 index 6178c2e..0000000 --- a/Sources/Adwaita/Model/Extensions/NativeWidgetPeer.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// NativeWidgetPeer.swift -// Adwaita -// -// Created by david-swift on 05.08.23. -// - -import Libadwaita - -extension NativeWidgetPeer: Widget { - - /// A `Libadwaita.NativeWidgetPeer` is static. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { } - - /// A `Libadwaita.NativeWidgetPeer`'s container is itself. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let bold = "\(modifier(code: 1))" - let yellow = 33 - let warning = "\(modifier(code: yellow))\(bold)" - let reset = modifier(code: 0) - print("\(warning)warning: \(reset)discouraged use of GTUI widgets (here: \(bold)\(Self.self)\(reset)) in views") - return .init(self) - } - - /// Get a modifier stirng. - /// - Parameter code: The modifier. - /// - Returns: The string. - private func modifier(code: Int) -> String { - "\u{001B}[\(code)m" - } - -} diff --git a/Sources/Adwaita/Model/Extensions/OpaquePointer.swift b/Sources/Adwaita/Model/Extensions/OpaquePointer.swift new file mode 100644 index 0000000..b594bfd --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/OpaquePointer.swift @@ -0,0 +1,16 @@ +// +// OpaquePointer.swift +// Adwaita +// +// Created by david-swift on 15.01.23. +// + +extension OpaquePointer { + + /// Convert an opaque pointer into an unsafe mutable pointer with a defined type. + /// - Returns: The unsafe mutable pointer. + public func cast() -> UnsafeMutablePointer { + .init(self) + } + +} diff --git a/Sources/Adwaita/Model/Extensions/Set.swift b/Sources/Adwaita/Model/Extensions/Set.swift new file mode 100644 index 0000000..7df5c9c --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/Set.swift @@ -0,0 +1,35 @@ +// +// Set.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +extension Set where Element == Edge { + + /// Horizontal and vertical edges. + public static var all: Self { vertical.union(horizontal) } + + /// Top and bottom edges. + public static var vertical: Self { top.union(bottom) } + + /// Leading and trailing edges. + public static var horizontal: Self { leading.union(trailing) } + + /// Top edge. + public static var top: Self { [.top] } + + /// Bottom edge. + public static var bottom: Self { [.bottom] } + + /// Leading edge. + public static var leading: Self { [.leading] } + + /// Trailing edge. + public static var trailing: Self { [.trailing] } + + /// Add a collection of edges to a collection of edges. + /// - Parameter edges: The collection of edges. + /// - Returns: Both collections combined. + public func add(_ edges: Self) -> Self { union(edges) } +} diff --git a/Sources/Adwaita/Model/Extensions/String.swift b/Sources/Adwaita/Model/Extensions/String.swift index d922699..1f76efe 100644 --- a/Sources/Adwaita/Model/Extensions/String.swift +++ b/Sources/Adwaita/Model/Extensions/String.swift @@ -14,4 +14,28 @@ extension String { /// A label for the navigation label in a GTUI widget's fields. static var navigationLabel: Self { "navigation-label" } + /// Add the Ctrl key to a shortcut. + /// - Returns: The shortcut. + public func ctrl() -> String { "\(self)" } + + /// Add the Shift key to a shortcut. + /// - Returns: The shortcut. + public func shift() -> String { "\(self)" } + + /// Add the Alt key to a shortcut. + /// - Returns: The shortcut. + public func alt() -> String { "\(self)" } + + /// Add the Meta key to a shortcut. + /// - Returns: The shortcut. + public func meta() -> String { "\(self)" } + + /// Add the Super key to a shortcut. + /// - Returns: The shortcut. + public func `super`() -> String { "\(self)" } + + /// Add the Hyper key to a shortcut. + /// - Returns: The shortcut. + public func hyper() -> String { "\(self)" } + } diff --git a/Sources/Adwaita/Model/Extensions/UInt.swift b/Sources/Adwaita/Model/Extensions/UInt.swift new file mode 100644 index 0000000..e417c3d --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/UInt.swift @@ -0,0 +1,15 @@ +// +// UInt.swift +// Adwaita +// +// Created by david-swift on 19.01.24. +// + +extension UInt { + + /// Convert an unsigned integer into the C form. + public var cInt: UInt32 { + .init(self) + } + +} diff --git a/Sources/Adwaita/Model/Extensions/UnsafeMutablePointer.swift b/Sources/Adwaita/Model/Extensions/UnsafeMutablePointer.swift new file mode 100644 index 0000000..52475a2 --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/UnsafeMutablePointer.swift @@ -0,0 +1,23 @@ +// +// UnsafeMutablePointer.swift +// Adwaita +// +// Created by david-swift on 15.01.24. +// + +extension UnsafeMutablePointer { + + /// Convert into an opaque pointer. + /// - Returns: The opaque pointer. + public func opaque() -> OpaquePointer { + .init(self) + } + + /// Convert into an unsafe mutable pointer of another type. + /// - Returns: The unsafe mutable pointer. + public func cast() -> UnsafeMutablePointer { + let pointer = UnsafeMutableRawPointer(self).bindMemory(to: T.self, capacity: 1) + return UnsafeMutablePointer(mutating: pointer) + } + +} diff --git a/Sources/Adwaita/Model/Extensions/UnsafeMutableRawPointer.swift b/Sources/Adwaita/Model/Extensions/UnsafeMutableRawPointer.swift new file mode 100644 index 0000000..2ab2fdb --- /dev/null +++ b/Sources/Adwaita/Model/Extensions/UnsafeMutableRawPointer.swift @@ -0,0 +1,17 @@ +// +// UnsafeMutableRawPointer.swift +// Adwaita +// +// Created by david-swift on 15.01.24. +// + +extension UnsafeMutableRawPointer { + + /// Convert into an unsafe mutable pointer of a certain type. + /// - Returns: The unsafe mutable pointer. + public func cast() -> UnsafeMutablePointer { + let pointer = UnsafeMutableRawPointer(self).bindMemory(to: T.self, capacity: 1) + return UnsafeMutablePointer(mutating: pointer) + } + +} diff --git a/Sources/Adwaita/Model/User Interface/App/App.swift b/Sources/Adwaita/Model/User Interface/App/App.swift index 95a5040..74dab3b 100644 --- a/Sources/Adwaita/Model/User Interface/App/App.swift +++ b/Sources/Adwaita/Model/User Interface/App/App.swift @@ -5,8 +5,6 @@ // Created by david-swift on 05.08.23. // -import Libadwaita - /// A structure conforming to `App` is the entry point of your app. /// /// ```swift diff --git a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift index 687d814..1828443 100644 --- a/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift +++ b/Sources/Adwaita/Model/User Interface/App/GTUIApp.swift @@ -5,16 +5,21 @@ // Created by david-swift on 05.08.23. // -import Libadwaita +import CAdw /// The GTUI application. -public class GTUIApp: Application { +public class GTUIApp { /// The handlers which are called when a state changes. static var updateHandlers: [() -> Void] = [] /// The app's id for the file name for storing the data. static var appID = "temporary" + /// The pointer to the application. + public var pointer: UnsafeMutablePointer? + /// Fields for additional information. + public var fields: [String: Any] = [:] + /// The app's content. var body: () -> App /// The scenes that are displayed. @@ -28,11 +33,11 @@ public class GTUIApp: Application { /// - body: The application's content. init(_ id: String, body: @escaping () -> App) { self.body = body - super.init(name: id) + self.pointer = adw_application_new(id, G_APPLICATION_DEFAULT_FLAGS)?.cast() } /// The entry point of the application. - override public func onActivate() { + public func onActivate() { let body = body() for windowScene in body.scene.windows() { for _ in 0.. Void + ) { + let action = g_simple_action_new(id, nil) + let data = ViewStorage.SignalData(closure: handler) + g_signal_connect_data( + action?.cast(), + "activate", + unsafeBitCast(data.threeParamsHandler, to: GCallback.self), + Unmanaged.passUnretained(data).toOpaque().cast(), + nil, + G_CONNECT_AFTER + ) + if let window { + g_action_map_add_action(.init(window.pointer), action) + window.fields[id] = data + } else { + g_action_map_add_action(.init(pointer), action) + fields[id] = data + } + gtk_application_set_accels_for_action(pointer, (window == nil ? "app." : "win.") + id, [shortcut].cArray) + } + /// Focus the window with a certain id. Create the window if it doesn't already exist. /// - Parameters: /// - id: The window's id. @@ -75,4 +127,10 @@ public class GTUIApp: Application { } } } + + /// Terminate the application. + public func quit() { + g_application_quit(pointer?.cast()) + } + } diff --git a/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift b/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift index 02edf5c..06e44ee 100644 --- a/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift +++ b/Sources/Adwaita/Model/User Interface/Menu/MenuItem.swift @@ -5,7 +5,7 @@ // Created by david-swift on 22.10.23. // -import Libadwaita +import CAdw /// A structure representing the content for a certain menu item type. public protocol MenuItem: MenuItemGroup { @@ -15,7 +15,7 @@ public protocol MenuItem: MenuItemGroup { /// - menu: The menu. /// - app: The application containing the menu. /// - window: The application window containing the menu. - func addMenuItem(menu: Libadwaita.Menu, app: GTUIApp, window: GTUIApplicationWindow?) + func addMenuItem(menu: OpaquePointer?, app: GTUIApp, window: GTUIApplicationWindow?) } diff --git a/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift b/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift index 3d2ce38..8125e1d 100644 --- a/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift +++ b/Sources/Adwaita/Model/User Interface/Menu/MenuItemGroup.swift @@ -5,7 +5,7 @@ // Created by david-swift on 22.10.23. // -import Libadwaita +import CAdw /// A structure conforming to `MenuItemGroup` can be added to the content accepting a menu. public protocol MenuItemGroup { @@ -19,7 +19,7 @@ extension MenuItemGroup { /// Add the menu items described by the group to a menu. /// - Parameter menu: The menu. - func addMenuItems(menu: Libadwaita.Menu, app: GTUIApp, window: GTUIApplicationWindow?) { + func addMenuItems(menu: OpaquePointer?, app: GTUIApp, window: GTUIApplicationWindow?) { for element in content { if let item = element as? MenuItem { item.addMenuItem(menu: menu, app: app, window: window) diff --git a/Sources/Adwaita/Model/User Interface/View/View.swift b/Sources/Adwaita/Model/User Interface/View/View.swift index 7870d99..223aa7a 100644 --- a/Sources/Adwaita/Model/User Interface/View/View.swift +++ b/Sources/Adwaita/Model/User Interface/View/View.swift @@ -5,8 +5,6 @@ // Created by david-swift on 05.08.23. // -import Libadwaita - /// A structure conforming to `View` is referred to as a view. /// It can be part of a body. /// @@ -37,13 +35,7 @@ extension View { if let peer = modified as? Widget { return peer } else { - var state: [String: StateProtocol] = [:] - for property in Mirror(reflecting: self).children { - if let label = property.label, let value = property.value as? StateProtocol { - state[label] = value - } - } - return StateWrapper(content: { view }, state: state) + return StateWrapper(content: { view }, state: getState()) } } @@ -56,8 +48,18 @@ extension View { if let widget = modified as? Widget { widget.update(storage, modifiers: modifiers) } else { - StateWrapper { self }.update(storage, modifiers: modifiers) + StateWrapper(content: { view }, state: getState()).update(storage, modifiers: modifiers) + } + } + + func getState() -> [String: StateProtocol] { + var state: [String: StateProtocol] = [:] + for property in Mirror(reflecting: self).children { + if let label = property.label, let value = property.value as? StateProtocol { + state[label] = value + } } + return state } /// Get a storage. diff --git a/Sources/Adwaita/Model/User Interface/View/ViewStorage.swift b/Sources/Adwaita/Model/User Interface/View/ViewStorage.swift index 281ad8b..ebce24e 100644 --- a/Sources/Adwaita/Model/User Interface/View/ViewStorage.swift +++ b/Sources/Adwaita/Model/User Interface/View/ViewStorage.swift @@ -5,31 +5,194 @@ // Created by david-swift on 31.08.23. // -import Libadwaita +import CAdw /// Store a rendered view in a view storage. public class ViewStorage { - /// The GTUI widget. - public var view: NativeWidgetPeer + /// The pointer. + public var pointer: OpaquePointer? /// The view's content. public var content: [String: [ViewStorage]] /// The view's state (used in `StateWrapper`). public var state: [String: StateProtocol] + /// The signal handlers. + public var handlers: [String: SignalData] = [:] + /// Other properties. + public var fields: [String: Any] = [:] /// Initialize a view storage. /// - Parameters: - /// - view: The GTUI widget. + /// - pointer: The pointer to the Gtk widget. /// - content: The view's content. /// - state: The view's state. public init( - _ view: NativeWidgetPeer, + _ pointer: OpaquePointer?, content: [String: [ViewStorage]] = [:], state: [String: StateProtocol] = [:] ) { - self.view = view + self.pointer = pointer self.content = content self.state = state } + /// Data to pass to signal handlers. + public class SignalData { + + /// The closure. + public var closure: ([UnsafeMutableRawPointer]) -> Void + + /// The closure as a C handler. + var handler: @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> Void { + { _, data in + let data = unsafeBitCast(data, to: SignalData.self) + data.closure([]) + } + } + + /// The closure as a C handler with three parameters. + var threeParamsHandler: @convention(c) ( + UnsafeMutableRawPointer, + UnsafeMutableRawPointer, + UnsafeMutableRawPointer + ) -> Void { + { _, arg1, data in + let data = unsafeBitCast(data, to: SignalData.self) + data.closure([arg1]) + } + } + + /// The closure as a C handler with four parameters. + var fourParamsHandler: @convention(c) ( + UnsafeMutableRawPointer, + UnsafeMutableRawPointer, + UnsafeMutableRawPointer, + UnsafeMutableRawPointer + ) -> Void { + { _, arg1, arg2, data in + let data = unsafeBitCast(data, to: SignalData.self) + data.closure([arg1, arg2]) + } + } + + /// The closure as a C handler with five parameters. + var fiveParamsHandler: @convention(c) ( + UnsafeMutableRawPointer, + UnsafeMutableRawPointer, + UnsafeMutableRawPointer, + UnsafeMutableRawPointer, + UnsafeMutableRawPointer + ) -> Void { + { _, arg1, arg2, arg3, data in + let data = unsafeBitCast(data, to: SignalData.self) + data.closure([arg1, arg2, arg3]) + print("Hi") + } + } + + /// Initialize the signal data. + /// - Parameter closure: The signal's closure. + public convenience init(closure: @escaping () -> Void) { + self.init { _ in closure() } + } + + /// Initialize the signal data. + /// - Parameter closure: The signal's closure. + public init(closure: @escaping ([UnsafeMutableRawPointer]) -> Void) { + self.closure = closure + } + + } + + /// Connect a handler to the observer of a property. + /// - Parameters: + /// - name: The property's name. + /// - id: The handlers id to separate form others connecting to the signal. + /// - connectFlags: The GConnectFlags. + /// - handler: The signal's handler. + public func notify( + name: String, + id: String = "", + connectFlags: GConnectFlags = G_CONNECT_AFTER, + handler: @escaping () -> Void + ) { + let name = "notify::" + name + connectSignal(name: name, id: id, connectFlags: connectFlags, argCount: 1, handler: handler) + } + + /// Connect a handler to a signal. + /// - Parameters: + /// - name: The signal's name. + /// - id: The handlers id to separate form others connecting to the signal. + /// - connectFlags: The GConnectFlags. + /// - argCount: The number of additional arguments (without the first and the last one). + /// - handler: The signal's handler. + public func connectSignal( + name: String, + id: String = "", + connectFlags: GConnectFlags = G_CONNECT_AFTER, + argCount: Int = 0, + handler: @escaping () -> Void + ) { + connectSignal(name: name, id: id, connectFlags: connectFlags, argCount: argCount) { _ in + handler() + } + } + + /// Connect a handler to a signal. + /// - Parameters: + /// - name: The signal's name. + /// - id: The handlers id to separate form others connecting to the signal. + /// - connectFlags: The GConnectFlags. + /// - argCount: The number of additional arguments (without the first and the last one). + /// - handler: The signal's handler. + public func connectSignal( + name: String, + id: String = "", + connectFlags: GConnectFlags = G_CONNECT_AFTER, + argCount: Int = 0, + handler: @escaping ([UnsafeMutableRawPointer]) -> Void + ) { + if let data = handlers[name + id] { + data.closure = handler + } else { + let data = SignalData(closure: handler) + handlers[name + id] = data + let callback: GCallback + let three = 3 + let two = 2 + if argCount >= three { + callback = unsafeBitCast(data.fiveParamsHandler, to: GCallback.self) + } else if argCount == two { + callback = unsafeBitCast(data.fourParamsHandler, to: GCallback.self) + } else if argCount == 1 { + callback = unsafeBitCast(data.threeParamsHandler, to: GCallback.self) + } else { + callback = unsafeBitCast(data.handler, to: GCallback.self) + } + g_signal_connect_data( + pointer?.cast(), + name, + callback, + Unmanaged.passUnretained(data).toOpaque().cast(), + nil, + connectFlags + ) + } + } + + /// Modify the view. + /// - Parameter modify: The modification function. + public func modify(_ modify: (OpaquePointer?) -> Void) { + modify(pointer) + } + + /// Convert the pointer to a pointer of a certain type and modify the view. + /// - Parameters: + /// - type: The pointer's type. + /// - modify: The modification function. + public func modify(_ type: T.Type, _ modify: (UnsafeMutablePointer?) -> Void) { + modify(pointer?.cast()) + } + } diff --git a/Sources/Adwaita/Model/User Interface/View/Widget.swift b/Sources/Adwaita/Model/User Interface/View/Widget.swift index 11372b5..f12a2bb 100644 --- a/Sources/Adwaita/Model/User Interface/View/Widget.swift +++ b/Sources/Adwaita/Model/User Interface/View/Widget.swift @@ -5,8 +5,6 @@ // Created by david-swift on 16.08.23. // -import Libadwaita - /// A widget is a view that know about its GTUI widget. public protocol Widget: View { diff --git a/Sources/Adwaita/Model/User Interface/Window/GTUIAboutWindow.swift b/Sources/Adwaita/Model/User Interface/Window/GTUIAboutWindow.swift new file mode 100644 index 0000000..a3ac51b --- /dev/null +++ b/Sources/Adwaita/Model/User Interface/Window/GTUIAboutWindow.swift @@ -0,0 +1,49 @@ +// +// GTUIAboutWindow.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +import CAdw + +/// A GTUI about window. +public class GTUIAboutWindow: GTUIWindow { + + /// Initialize an about window using the AppStream metadata. + /// - Parameter filePath: The path. + public init(filePath: String? = nil) { + super.init(fields: [:]) + if let filePath { + pointer = adw_about_window_new_from_appdata(filePath, nil)?.cast() + } else { + pointer = adw_about_window_new()?.cast() + } + } + + /// Set the general data. + /// - Parameters: + /// - title: The app name. + /// - icon: The app icon. + /// - developer: The app's developer. + /// - version: The app's version. + public func generalData(title: String, icon: Icon, developer: String, version: String) { + adw_about_window_set_application_name(.init(pointer), title) + adw_about_window_set_application_icon(.init(pointer), icon.string) + adw_about_window_set_developer_name(.init(pointer), developer) + adw_about_window_set_version(.init(pointer), version) + } + + /// Set the website. + /// - Parameter url: The website. + public func website(url: String) { + adw_about_window_set_website(.init(pointer), url) + } + + /// Set the URL for issues. + /// - Parameter issues: The issues website. + public func issues(url: String) { + adw_about_window_set_issue_url(.init(pointer), url) + } + +} diff --git a/Sources/Adwaita/Model/User Interface/Window/GTUIApplicationWindow.swift b/Sources/Adwaita/Model/User Interface/Window/GTUIApplicationWindow.swift index 4a997a2..ec1863b 100644 --- a/Sources/Adwaita/Model/User Interface/Window/GTUIApplicationWindow.swift +++ b/Sources/Adwaita/Model/User Interface/Window/GTUIApplicationWindow.swift @@ -5,7 +5,35 @@ // Created by david-swift on 19.10.23. // -import Libadwaita +import CAdw /// A GTUI application window. -public typealias GTUIApplicationWindow = Libadwaita.ApplicationWindow +public class GTUIApplicationWindow: GTUIWindow { + + /// The window's parent app. + public var app: GTUIApp + + /// Initialize the application window. + /// - Parameter app: The application. + public init(app: GTUIApp) { + self.app = app + super.init(fields: [:]) + pointer = adw_application_window_new(app.pointer)?.cast() + } + + /// Add a keyboard shortcut. + /// - Parameters: + /// - shortcut: The keyboard shortcut. + /// - id: The action's id. + /// - handler: The action's handler. + public func addKeyboardShortcut(_ shortcut: String, id: String, handler: @escaping () -> Void) { + app.addKeyboardShortcut(shortcut, id: id, window: self, handler: handler) + } + + /// Set the window's child. + /// - Parameter child: The child. + override public func setChild(_ child: OpaquePointer?) { + adw_application_window_set_content(pointer?.cast(), child?.cast()) + } + +} diff --git a/Sources/Adwaita/Model/User Interface/Window/GTUIFileDialog.swift b/Sources/Adwaita/Model/User Interface/Window/GTUIFileDialog.swift new file mode 100644 index 0000000..b17a813 --- /dev/null +++ b/Sources/Adwaita/Model/User Interface/Window/GTUIFileDialog.swift @@ -0,0 +1,138 @@ +// +// GTUIFileDialog.swift +// Adwaita +// +// Created by david-swift on 09.12.23. +// + +import CAdw +import Foundation + +/// A GTUI file dialog window. +public class GTUIFileDialog: WindowType { + + /// The file dialog's pointer. + public var pointer: OpaquePointer? + /// Fields for additional data. + public var fields: [String: Any] = [:] + /// A link to the file dialog. + var selfAddr: UInt64 { + unsafeBitCast(self, to: UInt64.self) + } + /// The parent window. + var parent: OpaquePointer? + /// Whether the file dialog is an importer. + var isImporter = false + /// The selected folder in the file dialog. + var folder: URL? + /// A closure triggered on selecting a file in the dialog. + var onResult: (URL) -> Void = { _ in } + /// A closure triggered when the dialog is canceled. + var onCancel: () -> Void = { } + + /// Initialize the window. + public init() { + pointer = gtk_file_dialog_new() + } + + /// Set the window's parent window. + /// - Parameter parent: The parent window. + public func setParentWindow(_ parent: WindowType) { + if let window = parent as? GTUIWindow { + self.parent = .init(window.pointer) + } + } + + /// Set the initial name. + /// - Parameter name: The parent window. + public func setInitialName(_ name: String) { + gtk_file_dialog_set_initial_name(pointer, name) + } + + // swiftlint:disable discouraged_optional_collection + /// Set the allowed file extensions. + /// - Parameters: + /// - extensions: The file extensions. + public func setExtensions(_ extensions: [String]?) { + if let extensions { + let filter = gtk_file_filter_new() + for name in extensions { + gtk_file_filter_add_suffix(filter, name) + } + gtk_file_dialog_set_default_filter(pointer, filter) + } else { + gtk_file_dialog_set_default_filter(pointer, nil) + } + } + // swiftlint:enable discouraged_optional_collection + + /// Display the file dialog. + public func show() { + if let folder { + gtk_file_dialog_set_initial_folder(pointer, g_file_new_for_path(folder.absoluteString)) + } + if isImporter { + gtui_filedialog_open(UInt64(Int(bitPattern: pointer)), selfAddr, UInt64(Int(bitPattern: parent))) + } else { + gtui_filedialog_save(UInt64(Int(bitPattern: pointer)), selfAddr, UInt64(Int(bitPattern: parent))) + } + } + + /// Run this when a file gets opened. + /// - Parameter path: The file path. + func onOpen(_ path: String) { + let url = URL(fileURLWithPath: path) + onResult(url) + } + + /// Run this when a file gets saved. + /// - Parameter path: The file path. + func onSave(_ path: String) { + let url = URL(fileURLWithPath: path) + onResult(url) + } + + /// Run this when the user cancels the action. + func onClose() { + onCancel() + } + +} + +/// Run when a file should be opened. +/// - Parameters: +/// - ptr: The pointer. +/// - file: The path to the file. +/// - userData: The file dialog data. +@_cdecl("filedialog_on_open_cb") +func filedialog_on_open_cb( + ptr: UnsafeMutableRawPointer, + file: UnsafePointer?, + userData: UnsafeMutableRawPointer +) { + let dialog = Unmanaged.fromOpaque(userData).takeUnretainedValue() + if let file { + dialog.onOpen(.init(cString: file)) + } else { + dialog.onClose() + } +} + +/// Run when a file should be saved. +/// - Parameters: +/// - ptr: The pointer. +/// - file: The path to the file. +/// - userData: The file dialog data. +@_cdecl("filedialog_on_save_cb") +func filedialog_on_save_cb( + ptr: UnsafeMutableRawPointer, + file: UnsafePointer?, + userData: UnsafeMutableRawPointer +) { + let dialog = Unmanaged.fromOpaque(userData).takeUnretainedValue() + if let file { + dialog.onSave(.init(cString: file)) + } else { + dialog.onClose() + } +} diff --git a/Sources/Adwaita/Model/User Interface/Window/GTUIWindow.swift b/Sources/Adwaita/Model/User Interface/Window/GTUIWindow.swift index 4fe9e92..9ad8715 100644 --- a/Sources/Adwaita/Model/User Interface/Window/GTUIWindow.swift +++ b/Sources/Adwaita/Model/User Interface/Window/GTUIWindow.swift @@ -5,19 +5,93 @@ // Created by david-swift on 12.10.23. // -import Libadwaita +import CAdw /// A GTUI window. -public typealias GTUIWindow = Libadwaita.Window +public class GTUIWindow: WindowType { -extension GTUIWindow: WindowType { + /// The window's pointer. + public var pointer: UnsafeMutablePointer? + /// Fields for additional information. + public var fields: [String: Any] = [:] + + /// Initialize the window. + public init() { + pointer = adw_window_new()?.cast() + } + + /// Initialize the window, but not the pointer. + /// - Parameter fields: The fields. + init(fields: [String: Any]) { + self.fields = fields + } + + /// Set the default window size. + /// - Parameters: + /// - width: The width. + /// - height: The height. + public func setDefaultSize(width: Int?, height: Int?) { + gtk_window_set_default_size(pointer, width?.cInt ?? -1, height?.cInt ?? -1) + } + + /// Set the resizability. + /// - Parameter resizable: Whether the window is resizable. + public func setResizability(_ resizable: Bool) { + gtk_window_set_resizable(pointer, resizable.cBool) + } + + /// Set the deletability. + /// - Parameter deletable: Whether the window is deletable. + public func setDeletability(_ deletable: Bool) { + gtk_window_set_deletable(pointer, deletable.cBool) + } + + /// Set the window title. + /// - Parameter title: The window's title. + public func setTitle(_ title: String) { + gtk_window_set_title(pointer, title) + } + + /// Set the window's child. + /// - Parameter child: The child. + public func setChild(_ child: OpaquePointer?) { + gtk_window_set_child(pointer, child?.cast()) + } + + /// Present the window. + public func show() { + gtk_window_present(pointer) + } + + /// Observe when the window is being closed. + /// - Parameter observer: The signal closure. + public func observeHide(observer: @escaping () -> Void) { + let hideObserver = ViewStorage.SignalData(closure: observer) + self.fields["observe-hide"] = hideObserver + g_signal_connect_data( + pointer?.cast(), + "destroy", + unsafeBitCast(hideObserver.handler, to: GCallback.self), + Unmanaged.passUnretained(hideObserver).toOpaque().cast(), + nil, + G_CONNECT_AFTER + ) + } + + /// Close the window. + public func close() { + gtk_window_close(pointer) + } /// Set the window's parent window. /// - Parameter parent: The parent window. public func setParentWindow(_ parent: WindowType) { + // swiftlint:disable prefer_self_in_static_references if let window = parent as? GTUIWindow { - self.setParent(window) + gtk_window_set_modal(pointer, 1) + gtk_window_set_transient_for(pointer, window.pointer) } + // swiftlint:enable prefer_self_in_static_references } } diff --git a/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift b/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift index b386f12..fbed105 100644 --- a/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift +++ b/Sources/Adwaita/Model/User Interface/Window/WindowScene.swift @@ -5,8 +5,6 @@ // Created by david-swift on 05.08.23. // -import Libadwaita - /// A structure representing the content for a certain window type. public protocol WindowScene: WindowSceneGroup { diff --git a/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift b/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift index 07567c3..4cd6cee 100644 --- a/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift +++ b/Sources/Adwaita/Model/User Interface/Window/WindowStorage.swift @@ -5,8 +5,6 @@ // Created by david-swift on 31.08.23. // -import Libadwaita - /// A storage for an app's window. public class WindowStorage { diff --git a/Sources/Adwaita/Model/User Interface/Window/WindowType.swift b/Sources/Adwaita/Model/User Interface/Window/WindowType.swift index 283c154..e2b3204 100644 --- a/Sources/Adwaita/Model/User Interface/Window/WindowType.swift +++ b/Sources/Adwaita/Model/User Interface/Window/WindowType.swift @@ -5,8 +5,6 @@ // Created by david-swift on 09.12.23. // -import Libadwaita - /// A window type. public protocol WindowType { diff --git a/Sources/Adwaita/View/Banner+.swift b/Sources/Adwaita/View/Banner+.swift new file mode 100644 index 0000000..064c513 --- /dev/null +++ b/Sources/Adwaita/View/Banner+.swift @@ -0,0 +1,33 @@ +// +// Banner+.swift +// Adwaita +// +// Created by david-swift on 17.01.24. +// + +import CAdw + +extension Banner { + + /// Initialize a text widget. + /// - Parameters: + /// - title: The content. + /// - visible: Whether the banner is visible. + public init(_ title: String, visible: Bool) { + self.init(title: title) + self = self.revealed(visible) + } + + /// Configure the banner's button. + /// - Parameters: + /// - label: The button's title. + /// - handler: The button's handler. + /// - Returns: The banner. + public func button(_ label: String, handler: @escaping () -> Void) -> Self { + buttonLabel(label) + .buttonClicked { + handler() + } + } + +} diff --git a/Sources/Adwaita/View/Banner.swift b/Sources/Adwaita/View/Banner.swift deleted file mode 100644 index 96ee65d..0000000 --- a/Sources/Adwaita/View/Banner.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// Banner.swift -// Adwaita -// -// Created by david-swift on 03.01.24. -// - -import Libadwaita - -/// A banner widget. -public struct Banner: Widget { - - /// The content. - var title: String - /// Whether the banner is visible. - var visible: Bool - /// The button's label. - var buttonLabel: String? - /// The button's handler. - var handler: () -> Void = { } - - /// Initialize a text widget. - /// - Parameters: - /// - title: The content. - /// - visible: Whether the banner is visible. - public init(_ title: String, visible: Bool) { - self.title = title - self.visible = visible - } - - /// Update the view storage of the text widget. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let banner = storage.view as? Libadwaita.Banner { - update(banner: banner) - } - } - - /// Get the container of the text widget. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let banner = Libadwaita.Banner(title) - update(banner: banner) - return .init(banner) - } - - /// Update the banner. - /// - Parameter banner: The banner. - func update(banner: Libadwaita.Banner) { - _ = banner.title(title) - if let buttonLabel { - _ = banner.buttonLabel(buttonLabel) - _ = banner.buttonHandler(handler) - } - if visible { - banner.show() - } else { - banner.hide() - } - } - - /// Configure the banner's button. - /// - Parameters: - /// - label: The button's title. - /// - handler: The button's handler. - /// - Returns: The banner. - public func button(_ label: String, handler: @escaping () -> Void) -> Self { - var newSelf = self - newSelf.buttonLabel = label - newSelf.handler = handler - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Button+.swift b/Sources/Adwaita/View/Button+.swift new file mode 100644 index 0000000..7cd8d78 --- /dev/null +++ b/Sources/Adwaita/View/Button+.swift @@ -0,0 +1,64 @@ +// +// Button+.swift +// Adwaita +// +// Created by david-swift on 15.01.24. +// + +import CAdw + +/// A button widget. +extension Button { + + // swiftlint:disable function_default_parameter_at_end + /// Initialize a button. + /// - Parameters: + /// - label: The button's label. + /// - icon: The button's icon. + /// - handler: The button's action handler. + public init(_ label: String? = nil, icon: Icon, handler: @escaping () -> Void) { + self.init() + self = self.child { + ButtonContent() + .label(label) + .iconName(icon.string) + } + self = self.clicked(handler) + } + // swiftlint:enable function_default_parameter_at_end + + /// Initialize a button. + /// - Parameters: + /// - label: The buttons label. + /// - handler: The button's action handler. + public init(_ label: String, handler: @escaping () -> Void) { + self.init() + self = self.label(label) + self = self.clicked(handler) + } + + /// Create a keyboard shortcut for an application window from a button. + /// + /// Note that the keyboard shortcut is available after the view has been visible for the first time. + /// - Parameters: + /// - shortcut: The keyboard shortcut. + /// - window: The application window. + /// - Returns: The button. + public func keyboardShortcut(_ shortcut: String, window: GTUIApplicationWindow) -> Self { + window.addKeyboardShortcut(shortcut, id: shortcut) { self.clicked?() } + return self + } + + /// Create a keyboard shortcut for an application from a button. + /// + /// Note that the keyboard shortcut is available after the view has been visible for the first time. + /// - Parameters: + /// - shortcut: The keyboard shortcut. + /// - window: The application. + /// - Returns: The button. + public func keyboardShortcut(_ shortcut: String, app: GTUIApp) -> Self { + app.addKeyboardShortcut(shortcut, id: shortcut) { self.clicked?() } + return self + } + +} diff --git a/Sources/Adwaita/View/Button.swift b/Sources/Adwaita/View/Button.swift deleted file mode 100644 index e872c1c..0000000 --- a/Sources/Adwaita/View/Button.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Button.swift -// Adwaita -// -// Created by david-swift on 10.09.23. -// - -import Libadwaita - -/// A button widget. -public struct Button: Widget { - - /// The button's label. - var label: String? - /// The button's icon. - var icon: Icon? - /// The button's action handler. - var handler: () -> Void - - // swiftlint:disable function_default_parameter_at_end - /// Initialize a button. - /// - Parameters: - /// - label: The button's label. - /// - icon: The button's icon. - /// - handler: The button's action handler. - public init(_ label: String? = nil, icon: Icon, handler: @escaping () -> Void) { - self.label = label - self.icon = icon - self.handler = handler - } - // swiftlint:enable function_default_parameter_at_end - - /// Initialize a button. - /// - Parameters: - /// - label: The buttons label. - /// - handler: The button's action handler. - public init(_ label: String, handler: @escaping () -> Void) { - self.label = label - self.handler = handler - } - - /// Update a button's view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let button = storage.view as? Libadwaita.Button { - let content = button.getContent() - if let label { - if icon == nil { - button.setLabel(label) - } else { - content?.setLabel(label) - } - } - if let icon { - content?.setIcon(icon) - } - _ = button.handler(handler) - } - } - - /// Get a button's view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The button's view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - if let icon { - return .init(Libadwaita.Button(label, icon: icon).handler(handler)) - } else { - return .init(Libadwaita.Button(label ?? .init()).handler(handler)) - } - } - - /// Create a keyboard shortcut for an application window from a button. - /// - /// Note that the keyboard shortcut is available after the view has been visible for the first time. - /// - Parameters: - /// - shortcut: The keyboard shortcut. - /// - window: The application window. - /// - Returns: The button. - public func keyboardShortcut(_ shortcut: String, window: GTUIApplicationWindow) -> Self { - window.addKeyboardShortcut(shortcut, id: shortcut, handler: handler) - return self - } - - /// Create a keyboard shortcut for an application from a button. - /// - /// Note that the keyboard shortcut is available after the view has been visible for the first time. - /// - Parameters: - /// - shortcut: The keyboard shortcut. - /// - window: The application. - /// - Returns: The button. - public func keyboardShortcut(_ shortcut: String, app: GTUIApp) -> Self { - app.addKeyboardShortcut(shortcut, id: shortcut, handler: handler) - return self - } - -} diff --git a/Sources/Adwaita/View/Carousel+.swift b/Sources/Adwaita/View/Carousel+.swift new file mode 100644 index 0000000..99b08e2 --- /dev/null +++ b/Sources/Adwaita/View/Carousel+.swift @@ -0,0 +1,17 @@ +// +// Carousel+.swift +// Adwaita +// +// Created by david-swift on 18.01.24. +// + +extension Carousel { + + /// Set whether long swipes are allowed or not. + /// - Parameter longSwipes: Whether long swipes are allowed. + /// - Returns: The carousel. + public func longSwipes(_ longSwipes: Bool = true) -> Self { + allowLongSwipes(longSwipes) + } + +} diff --git a/Sources/Adwaita/View/Carousel.swift b/Sources/Adwaita/View/Carousel.swift deleted file mode 100644 index ad04265..0000000 --- a/Sources/Adwaita/View/Carousel.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Carousel.swift -// Adwaita -// -// Created by david-swift on 01.01.24. -// - -import Libadwaita - -/// A carousel view. -public struct Carousel: View where Element: Identifiable { - - /// The elements. - var elements: [Element] - /// The content. - var content: (Element) -> Body - /// Whether long swipes are allowed. - var allowLongSwipes = false - - /// The view. - public var view: Body { - Container(elements, content: content) { - Libadwaita.Carousel() - } - .inspect { _ = ($0 as? Libadwaita.Carousel)?.longSwipes(allowLongSwipes) } - } - - /// Initialize `Carousel`. - /// - Parameters: - /// - elements: The elements. - /// - content: The view for an element. - public init( - _ elements: [Element], - @ViewBuilder content: @escaping (Element) -> Body - ) { - self.content = content - self.elements = elements - } - - /// Set whether long swipes are allowed or not. - /// - Parameter longSwipes: Whether long swipes are allowed. - /// - Returns: The carousel. - public func longSwipes(_ longSwipes: Bool = true) -> Self { - var newSelf = self - newSelf.allowLongSwipes = longSwipes - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Container.swift b/Sources/Adwaita/View/Container.swift deleted file mode 100644 index aceec96..0000000 --- a/Sources/Adwaita/View/Container.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Container.swift -// Adwaita -// -// Created by david-swift on 01.01.24. -// - -import LevenshteinTransformations -import Libadwaita - -/// A container widget. -public struct Container: Widget -where Element: Identifiable, Type: InsertableContainer, Type: NativeWidgetPeer { - - /// The elements. - var elements: [Element] - /// The content. - var content: (Element) -> Body - /// Get the container for initialization. - var container: () -> Type - - /// The identifier of the elements storage. - let elementsID = "elements" - - /// Initialize `Container`. - /// - Parameters: - /// - elements: The elements. - /// - content: The view for an element. - /// - container: Get the initial Libadwaita container widget. - public init( - _ elements: [Element], - @ViewBuilder content: @escaping (Element) -> Body, - container: @escaping () -> Type - ) { - self.content = content - self.elements = elements - self.container = container - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let container = storage.view as? Type { - var content: [ViewStorage] = storage.content[.mainContent] ?? [] - updateContainer(container, content: .init { content } set: { content = $0 }, modifiers: modifiers) - storage.content[.mainContent] = content - for (index, element) in elements.enumerated() { - self.content(element).widget(modifiers: modifiers).update(content[index], modifiers: modifiers) - } - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let container = self.container() - var content: [ViewStorage] = [] - updateContainer(container, content: .init { content } set: { content = $0 }, modifiers: modifiers) - return .init(container, content: [.mainContent: content]) - } - - /// Update the container's content. - /// - Parameters: - /// - container: The container. - /// - content: The content's view storage. - /// - modifiers: The view modifiers. - func updateContainer(_ container: Type, content: Binding<[ViewStorage]>, modifiers: [(View) -> View]) { - let old = container.fields[elementsID] as? [Element] ?? [] - old.identifiableTransform( - to: elements, - functions: .init { index, element in - let widget = getWidget(element: element, modifiers: modifiers) - _ = container.removeWidgets([content.wrappedValue[index].view]) - _ = container.insert(widget.view, at: index) - content.wrappedValue.remove(at: index) - content.wrappedValue.insert(widget, at: index) - } delete: { index in - _ = container.removeWidgets([content.wrappedValue[index].view]) - content.wrappedValue.remove(at: index) - } insert: { index, element in - let widget = getWidget(element: element, modifiers: modifiers) - _ = container.insert(widget.view, at: index) - content.wrappedValue.insert(widget, at: index) - } - ) - container.fields[elementsID] = elements - } - - /// Get the view storage of an element. - /// - Parameters: - /// - element: The element. - /// - modifiers: The modifiers. - /// - Returns: The view storage. - func getWidget(element: Element, modifiers: [(View) -> View]) -> ViewStorage { - self.content(element).widget(modifiers: modifiers).container(modifiers: modifiers) - } - -} diff --git a/Sources/Adwaita/View/Forms/ActionRow+.swift b/Sources/Adwaita/View/Forms/ActionRow+.swift new file mode 100644 index 0000000..0a4257d --- /dev/null +++ b/Sources/Adwaita/View/Forms/ActionRow+.swift @@ -0,0 +1,17 @@ +// +// ActionRow+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +/// A form content row showing a title and optionally a subtitle and widgets. +extension ActionRow { + + /// Initialize an action row. + /// - Parameter title: The row's title. + public init(_ title: String) { + self = self.title(title) + } + +} diff --git a/Sources/Adwaita/View/Forms/ActionRow.swift b/Sources/Adwaita/View/Forms/ActionRow.swift deleted file mode 100644 index caa5168..0000000 --- a/Sources/Adwaita/View/Forms/ActionRow.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// ActionRow.swift -// Adwaita -// -// Created by david-swift on 03.01.24. -// - -import Libadwaita - -/// A form content row showing a title and optionally a subtitle and widgets. -public struct ActionRow: Widget { - - /// The title. - var title: String - /// The subtitle. - var subtitle = "" - /// The prefix. - var prefix: Body = [] - /// The suffix. - var suffix: Body = [] - - /// The identifier for the prefix content. - let prefixID = "prefix" - /// The identifier for the suffix content. - let suffixID = "suffix" - - /// Initialize an action row. - /// - Parameter title: The row's title. - public init(_ title: String) { - self.title = title - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let row = storage.view as? Libadwaita.ActionRow { - update(row: row) - } - if let prefixStorage = storage.content[prefixID]?.first { - prefix.widget(modifiers: modifiers).update(prefixStorage, modifiers: modifiers) - } - if let suffixStorage = storage.content[suffixID]?.first { - suffix.widget(modifiers: modifiers).update(suffixStorage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let row: Libadwaita.ActionRow = .init(title: title, subtitle: subtitle) - let prefixContent = prefix.widget(modifiers: modifiers).container(modifiers: modifiers) - let suffixContent = suffix.widget(modifiers: modifiers).container(modifiers: modifiers) - if !prefix.isEmpty { - _ = row.addPrefix(prefixContent.view) - } - if !suffix.isEmpty { - _ = row.addSuffix(suffixContent.view) - } - return .init(row, content: [prefixID: [prefixContent], suffixID: [suffixContent]]) - } - - /// Update the action row. - /// - Parameter row: The action row. - func update(row: Libadwaita.ActionRow) { - _ = row.title(title) - _ = row.subtitle(subtitle) - } - - /// Set the action row's subtitle. - /// - Parameter subtitle: The subtitle. - /// - Returns: The action row. - public func subtitle(_ subtitle: String) -> Self { - var newSelf = self - newSelf.subtitle = subtitle - return newSelf - } - - /// Set the action row's prefix view. - /// - Parameter prefix: The prefix. - /// - Returns: The action row. - public func prefix(@ViewBuilder _ prefix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.prefix = prefix() - return newSelf - } - - /// Set the action row's suffix view. - /// - Parameter suffix: The suffix. - /// - Returns: The action row. - public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.suffix = suffix() - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Forms/ComboRow+.swift b/Sources/Adwaita/View/Forms/ComboRow+.swift new file mode 100644 index 0000000..1286d13 --- /dev/null +++ b/Sources/Adwaita/View/Forms/ComboRow+.swift @@ -0,0 +1,61 @@ +// +// ComboRow+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A row for selecting an element out of a list of elements. +extension ComboRow { + + static var values: String { "values" } + static var stringList: String { "string-list" } + + /// Initialize a combo row. + /// - Parameters: + /// - title: The row's title. + /// - selection: The selected value. + /// - values: The available values. + public init( + _ title: String, + selection: Binding, + values: [Element] + ) where Element: Identifiable, Element: CustomStringConvertible { + self = self.title(title) + self = self.selected(.init { + .init(values.firstIndex { $0.id == selection.wrappedValue } ?? 0) + } set: { index in + if let id = values[safe: .init(index)]?.id { + selection.wrappedValue = id + } + }) + appearFunctions.append { storage in + let list = gtk_string_list_new(nil) + storage.fields[Self.stringList] = list + adw_combo_row_set_model(storage.pointer?.cast(), list) + } + updateFunctions.append { storage in + if let list = storage.fields[Self.stringList] as? OpaquePointer { + let old = storage.fields[Self.values] as? [Element] ?? [] + var new = values.filter { element in !old.contains { $0.id == element.id } } + new = old + new + old.identifiableTransform( + to: new, + functions: .init { index, element in + gtk_string_list_remove(list, .init(index)) + gtk_string_list_append(list, element.description) + } delete: { index in + gtk_string_list_remove(list, .init(index)) + } insert: { _, element in + gtk_string_list_append(list, element.description) + } + ) + storage.fields[Self.values] = new + } + } + } + +} diff --git a/Sources/Adwaita/View/Forms/ComboRow.swift b/Sources/Adwaita/View/Forms/ComboRow.swift deleted file mode 100644 index a281310..0000000 --- a/Sources/Adwaita/View/Forms/ComboRow.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// ComboRow.swift -// Adwaita -// -// Created by david-swift on 04.01.24. -// - -import Libadwaita - -/// A row for selecting an element out of a list of elements. -public struct ComboRow: Widget -where Element: CustomStringConvertible, Element: Identifiable, Element: Equatable { - - /// The title. - var title: String - /// The selected element. - @Binding var selection: Element.ID - /// The content. - var content: [Element] - /// The subtitle. - var subtitle = "" - /// The prefix. - var prefix: Body = [] - /// The suffix. - var suffix: Body = [] - - /// The identifier for the prefix content. - let prefixID = "prefix" - /// The identifier for the suffix content. - let suffixID = "suffix" - - /// The identifier for the elements. - let elementsID = "elements" - - /// Initialize a combo row. - /// - Parameters: - /// - title: The row's title. - /// - selection: The selected value. - /// - values: The available values. - public init(_ title: String, selection: Binding, values: [Element]) { - self.title = title - self._selection = selection - content = values - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let row = storage.view as? Libadwaita.ComboRow { - update(row: row) - } - if let prefixStorage = storage.content[prefixID]?.first { - prefix.widget(modifiers: modifiers).update(prefixStorage, modifiers: modifiers) - } - if let suffixStorage = storage.content[suffixID]?.first { - suffix.widget(modifiers: modifiers).update(suffixStorage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let row: Libadwaita.ComboRow = .init(title: title, subtitle: subtitle) - let prefixContent = prefix.widget(modifiers: modifiers).container(modifiers: modifiers) - let suffixContent = suffix.widget(modifiers: modifiers).container(modifiers: modifiers) - if !prefix.isEmpty { - _ = row.addPrefix(prefixContent.view) - } - if !suffix.isEmpty { - _ = row.addSuffix(suffixContent.view) - } - update(row: row) - _ = row.onChange { - if let element = content.first(where: { $0.description == row.selected() }), element.id != selection { - selection = element.id - } - } - return .init(row, content: [prefixID: [prefixContent], suffixID: [suffixContent]]) - } - - /// Update the combo row. - /// - Parameter row: The combo row. - func update(row: Libadwaita.ComboRow) { - _ = row.title(title) - _ = row.subtitle(subtitle) - let oldElements = row.fields[elementsID] as? [Element] ?? [] - if oldElements != content { - for element in oldElements { - row.remove(at: 0) - } - for element in content { - row.append(element.description) - } - } - let index = content.firstIndex { $0.id == selection } ?? 0 - if row.selected() != content[safe: index]?.description { - row.select(at: index) - } - row.fields[elementsID] = content - } - - /// Set the combo row's subtitle. - /// - Parameter subtitle: The subtitle. - /// - Returns: The combo row. - public func subtitle(_ subtitle: String) -> Self { - var newSelf = self - newSelf.subtitle = subtitle - return newSelf - } - - /// Set the combo row's prefix view. - /// - Parameter prefix: The prefix. - /// - Returns: The combo row. - public func prefix(@ViewBuilder _ prefix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.prefix = prefix() - return newSelf - } - - /// Set the combo row's suffix view. - /// - Parameter suffix: The suffix. - /// - Returns: The combo row. - public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.suffix = suffix() - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Forms/EntryRow+.swift b/Sources/Adwaita/View/Forms/EntryRow+.swift new file mode 100644 index 0000000..1bf0d90 --- /dev/null +++ b/Sources/Adwaita/View/Forms/EntryRow+.swift @@ -0,0 +1,59 @@ +// +// EntryRow+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +import CAdw + +extension EntryRow { + + static var textField: String { "text" } + + /// Initialize an entry row. + /// - Parameters: + /// - title: The row's title. + /// - text: The text. + public init(_ title: String, text: Binding) { + self.init() + self = self.title(title) + appearFunctions.append { storage in + storage.fields[Self.textField] = text + storage.notify(name: "text") { + if let binding = storage.fields[Self.textField] as? Binding { + binding.wrappedValue = .init(cString: gtk_editable_get_text(storage.pointer)) + } + } + } + updateFunctions.append { storage in + if text.wrappedValue != .init(cString: gtk_editable_get_text(storage.pointer)) { + gtk_editable_set_text(storage.pointer, text.wrappedValue) + } + } + } + + /// Set the entry row's subtitle. + /// - Parameter subtitle: The subtitle. + /// - Returns: The entry row. + public func onSubmit(_ onSubmit: @escaping () -> Void) -> Self { + showApplyButton() + .apply(onSubmit) + } + + /// Let the user securely enter private text. + /// - Parameter: The text. + /// - Returns: The entry row. + public func secure(text: Binding? = nil) -> PasswordEntryRow { + .init(title ?? "", text: text ?? .constant("")) + .activatesDefault(activatesDefault) + .enableEmojiCompletion(enableEmojiCompletion) + .showApplyButton(showApplyButton) + .titleSelectable(titleSelectable) + .useMarkup(useMarkup) + .useUnderline(useUnderline) + .apply(apply ?? { }) + .entryActivated(entryActivated ?? { }) + } + +} diff --git a/Sources/Adwaita/View/Forms/EntryRow.swift b/Sources/Adwaita/View/Forms/EntryRow.swift deleted file mode 100644 index 1b1968a..0000000 --- a/Sources/Adwaita/View/Forms/EntryRow.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// EntryRow.swift -// Adwaita -// -// Created by david-swift on 04.01.24. -// - -import Libadwaita - -/// A form content row accepting text input. -public struct EntryRow: Widget { - - /// The title. - var title: String - /// The text. - @Binding var text: String - /// The prefix. - var prefix: Body = [] - /// The suffix. - var suffix: Body = [] - /// The handler that gets executed when the user submits the content. - var onSubmit: (() -> Void)? - /// Whether the password entry row should be used. - var password = false - - /// The identifier for the prefix content. - let prefixID = "prefix" - /// The identifier for the suffix content. - let suffixID = "suffix" - - /// The identifier for the title. - let titleID = "title" - - /// Initialize an entry row. - /// - Parameters: - /// - title: The row's title. - /// - text: The text. - public init(_ title: String, text: Binding) { - self.title = title - self._text = text - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let row = storage.view as? Libadwaita.EntryRow { - update(row: row) - } - if let prefixStorage = storage.content[prefixID]?.first { - prefix.widget(modifiers: modifiers).update(prefixStorage, modifiers: modifiers) - } - if let suffixStorage = storage.content[suffixID]?.first { - suffix.widget(modifiers: modifiers).update(suffixStorage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let row: Libadwaita.EntryRow - if password { - row = PasswordEntryRow(title: title) - } else { - row = .init(title: title) - } - let prefixContent = prefix.widget(modifiers: modifiers).container(modifiers: modifiers) - let suffixContent = suffix.widget(modifiers: modifiers).container(modifiers: modifiers) - if !prefix.isEmpty { - _ = row.addPrefix(prefixContent.view) - } - if !suffix.isEmpty { - _ = row.addSuffix(suffixContent.view) - } - _ = row.changeHandler { - if row.contents() != text { - text = row.contents() - } - } - update(row: row) - return .init(row, content: [prefixID: [prefixContent], suffixID: [suffixContent]]) - } - - /// Update the entry row. - /// - Parameter row: The entry row. - func update(row: Libadwaita.EntryRow) { - if row.fields[titleID] as? String != title { - _ = row.title(title) - row.fields[titleID] = title - } - if row.contents() != text { - row.setContents(text) - } - if let onSubmit { - _ = row.submitHandler(onSubmit) - } - } - - /// Set the entry row's subtitle. - /// - Parameter subtitle: The subtitle. - /// - Returns: The entry row. - public func onSubmit(_ onSubmit: @escaping () -> Void) -> Self { - var newSelf = self - newSelf.onSubmit = onSubmit - return newSelf - } - - /// Set the entry row's prefix view. - /// - Parameter prefix: The prefix. - /// - Returns: The entry row. - public func prefix(@ViewBuilder _ prefix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.prefix = prefix() - return newSelf - } - - /// Set the entry row's suffix view. - /// - Parameter suffix: The suffix. - /// - Returns: The entry row. - public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.suffix = suffix() - return newSelf - } - - /// Let the user securely enter private text. - /// - Returns: The entry row. - public func secure() -> Self { - var newSelf = self - newSelf.password = true - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Forms/Form.swift b/Sources/Adwaita/View/Forms/Form.swift index d172f3c..386436e 100644 --- a/Sources/Adwaita/View/Forms/Form.swift +++ b/Sources/Adwaita/View/Forms/Form.swift @@ -5,43 +5,24 @@ // Created by david-swift on 03.01.24. // -import Libadwaita +import CAdw /// A list with no dynamic content styled as a boxed list. -public struct Form: Widget { +public struct Form: View { /// The content. - var content: () -> Body + var content: Body + + /// The view's body. + public var view: Body { + List([Int](content.indices), selection: nil) { index in content[index] } + .style("boxed-list") + } /// Initialize a `Form`. /// - Parameter content: The view content, usually different kind of rows. public init(@ViewBuilder content: @escaping () -> Body) { - self.content = content - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - content().update(storage.content[.mainContent] ?? [], modifiers: modifiers) - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let form: ListBox = .init() - _ = form - .noSelection() - .addStyle("boxed-list") - var content: [ViewStorage] = [] - for element in self.content() { - let widget = element.storage(modifiers: modifiers) - _ = form.append(widget.view) - content.append(widget) - } - return .init(form, content: [.mainContent: content]) + self.content = content() } } diff --git a/Sources/Adwaita/View/Forms/FormSection+.swift b/Sources/Adwaita/View/Forms/FormSection+.swift new file mode 100644 index 0000000..2b86000 --- /dev/null +++ b/Sources/Adwaita/View/Forms/FormSection+.swift @@ -0,0 +1,30 @@ +// +// FormSection+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +/// A section usually groups forms. +public typealias FormSection = PreferencesGroup + +extension FormSection { + + /// Initialize a form section. + /// - Parameters: + /// - title: The title. + /// - content: The content, usually one or more forms. + public init(_ title: String, @ViewBuilder content: @escaping () -> Body) { + self.init() + self = self.title(title) + self = self.child(content) + } + + /// Set the form section's suffix view. + /// - Parameter suffix: The suffix. + /// - Returns: The form section. + public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { + headerSuffix(suffix) + } + +} diff --git a/Sources/Adwaita/View/Forms/FormSection.swift b/Sources/Adwaita/View/Forms/FormSection.swift deleted file mode 100644 index 4936501..0000000 --- a/Sources/Adwaita/View/Forms/FormSection.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// FormSection.swift -// Adwaita -// -// Created by david-swift on 03.01.24. -// - -import Libadwaita - -/// A section usually groups forms. -public struct FormSection: Widget { - - /// The title. - var title: String - /// The content. - var content: Body - /// The description. - var description = "" - /// The suffix. - var suffix: Body = [] - - /// The identifier for the suffix content. - let suffixID = "suffix" - - /// Initialize a form section. - /// - Parameters: - /// - title: The title. - /// - content: The content, usually one or more forms. - public init(_ title: String, @ViewBuilder content: () -> Body) { - self.title = title - self.content = content() - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let group = storage.view as? Libadwaita.PreferencesGroup { - update(group: group) - } - if let storage = storage.content[.mainContent]?.first { - content.widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - if let suffixStorage = storage.content[suffixID]?.first { - suffix.widget(modifiers: modifiers).update(suffixStorage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let group: Libadwaita.PreferencesGroup = .init(name: title, description: description) - let content = content.widget(modifiers: modifiers).container(modifiers: modifiers) - let suffixContent = suffix.widget(modifiers: modifiers).container(modifiers: modifiers) - group.add(content.view) - if !suffix.isEmpty { - _ = group.headerSuffix(suffixContent.view) - } - return .init(group, content: [.mainContent: [content], suffixID: [suffixContent]]) - } - - /// Update the form section. - /// - Parameter group: The form section. - func update(group: Libadwaita.PreferencesGroup) { - _ = group.title(title) - _ = group.description(description) - } - - /// Set the form section's description. - /// - Parameter description: The description. - /// - Returns: The form section. - public func description(_ description: String) -> Self { - var newSelf = self - newSelf.description = description - return newSelf - } - - /// Set the form section's suffix view. - /// - Parameter suffix: The suffix. - /// - Returns: The form section. - public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.suffix = suffix() - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Forms/PasswordEntryRow+.swift b/Sources/Adwaita/View/Forms/PasswordEntryRow+.swift new file mode 100644 index 0000000..5fdf8fc --- /dev/null +++ b/Sources/Adwaita/View/Forms/PasswordEntryRow+.swift @@ -0,0 +1,43 @@ +// +// PasswordEntryRow+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +import CAdw + +extension PasswordEntryRow { + + static var textField: String { "text" } + + /// Initialize an entry row. + /// - Parameters: + /// - title: The row's title. + /// - text: The text. + public init(_ title: String, text: Binding) { + self.init() + self = self.title(title) + appearFunctions.append { storage in + storage.fields[Self.textField] = text + storage.notify(name: "text") { + if let binding = storage.fields[Self.textField] as? Binding { + binding.wrappedValue = .init(cString: gtk_editable_get_text(storage.pointer)) + } + } + } + updateFunctions.append { storage in + if text.wrappedValue != .init(cString: gtk_editable_get_text(storage.pointer)) { + gtk_editable_set_text(storage.pointer, text.wrappedValue) + } + } + } + + /// Set the entry row's subtitle. + /// - Parameter subtitle: The subtitle. + /// - Returns: The entry row. + public func onSubmit(_ onSubmit: @escaping () -> Void) -> Self { + apply(onSubmit) + } + +} diff --git a/Sources/Adwaita/View/Forms/SpinRow+.swift b/Sources/Adwaita/View/Forms/SpinRow+.swift new file mode 100644 index 0000000..2d145ed --- /dev/null +++ b/Sources/Adwaita/View/Forms/SpinRow+.swift @@ -0,0 +1,62 @@ +// +// SpinRow+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +import CAdw + +extension SpinRow { + + /// Initialize a spin row. + /// - Parameters: + /// - title: The row's title. + /// - value: The selected value. + /// - min: The minimum value. + /// - max: The maximum value. + public init(_ title: String, value: Binding, min: Int, max: Int) { + self.init( + title, + value: .init { .init(value.wrappedValue) } set: { value.wrappedValue = .init($0) }, + min: .init(min), + max: .init(max) + ) + } + + /// Initialize a spin row. + /// - Parameters: + /// - title: The row's title. + /// - value: The selected value. + /// - min: The minimum value. + /// - max: The maximum value. + public init(_ title: String, value: Binding, min: Double, max: Double) { + self.init(climbRate: 1, digits: 0) + self = self.title(title) + self = self.value(value) + self = self.step(1) + updateFunctions.append { storage in + adw_spin_row_set_range(storage.pointer, min, max) + } + } + + /// Set the difference a single click on the increase/decrease buttons makes. + /// - Parameter step: The increase/decrease step. + /// - Returns: The spin row. + public func step(_ step: Int) -> Self { + self.step(.init(step)) + } + + /// Set the difference a single click on the increase/decrease buttons makes. + /// - Parameter step: The increase/decrease step. + /// - Returns: The spin row. + public func step(_ step: Double) -> Self { + var newSelf = self + newSelf.updateFunctions.append { storage in + let adjustment = adw_spin_row_get_adjustment(storage.pointer) + gtk_adjustment_set_step_increment(adjustment, step) + } + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Forms/SpinRow.swift b/Sources/Adwaita/View/Forms/SpinRow.swift deleted file mode 100644 index fdee2eb..0000000 --- a/Sources/Adwaita/View/Forms/SpinRow.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// SpinRow.swift -// Adwaita -// -// Created by david-swift on 04.01.24. -// - -import Libadwaita - -/// A spin row lets the user select an integer in a certain range. -public struct SpinRow: Widget { - - /// The title. - var title: String - /// The selected value. - @Binding var value: Int - /// The minimum value. - var min: Int - /// The maximum value. - var max: Int - /// The increase/decrease step. - var step = 1 - /// The subtitle. - var subtitle = "" - /// The prefix. - var prefix: Body = [] - /// The suffix. - var suffix: Body = [] - - /// The identifier for the prefix content. - let prefixID = "prefix" - /// The identifier for the suffix content. - let suffixID = "suffix" - - /// The identifier of the configuration field. - let configID = "config" - - /// Initialize a spin row. - /// - Parameters: - /// - title: The row's title. - /// - value: The selected value. - /// - min: The minimum value. - /// - max: The maximum value. - public init(_ title: String, value: Binding, min: Int, max: Int) { - self.title = title - self._value = value - self.min = min - self.max = max - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let row = storage.view as? Libadwaita.SpinRow { - update(row: row) - } - if let prefixStorage = storage.content[prefixID]?.first { - prefix.widget(modifiers: modifiers).update(prefixStorage, modifiers: modifiers) - } - if let suffixStorage = storage.content[suffixID]?.first { - suffix.widget(modifiers: modifiers).update(suffixStorage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let row: Libadwaita.SpinRow = .init( - title: title, - subtitle: subtitle, - min: .init(min), - max: .init(max), - step: .init(step) - ) - row.fields[configID] = (min, max, step) - let prefixContent = prefix.widget(modifiers: modifiers).container(modifiers: modifiers) - let suffixContent = suffix.widget(modifiers: modifiers).container(modifiers: modifiers) - if !prefix.isEmpty { - _ = row.addPrefix(prefixContent.view) - } - if !suffix.isEmpty { - _ = row.addSuffix(suffixContent.view) - } - _ = row.onChange { - let value = Int(row.getValue().rounded()) - if self.value != value { - self.value = value - } - } - update(row: row) - return .init(row, content: [prefixID: [prefixContent], suffixID: [suffixContent]]) - } - - // swiftlint:disable large_tuple - /// Update the spin row. - /// - Parameter row: The spin row. - func update(row: Libadwaita.SpinRow) { - _ = row.title(title) - _ = row.subtitle(subtitle) - if row.fields[configID] as? (Int, Int, Int) ?? (0, 0, 0) != (min, max, step) { - _ = row.configuration(min: .init(min), max: .init(max), step: .init(step)) - row.fields[configID] = (min, max, step) - } - if row.getValue() != .init(value) { - row.setValue(.init(value)) - } - } - // swiftlint:enable large_tuple - - /// Set the spin row's subtitle. - /// - Parameter subtitle: The subtitle. - /// - Returns: The spin row. - public func subtitle(_ subtitle: String) -> Self { - var newSelf = self - newSelf.subtitle = subtitle - return newSelf - } - - /// Set the spin row's prefix view. - /// - Parameter prefix: The prefix. - /// - Returns: The spin row. - public func prefix(@ViewBuilder _ prefix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.prefix = prefix() - return newSelf - } - - /// Set the spin row's suffix view. - /// - Parameter suffix: The suffix. - /// - Returns: The spin row. - public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.suffix = suffix() - return newSelf - } - - /// Set the difference a single click on the increase/decrease buttons makes. - /// - Parameter step: The increase/decrease step. - /// - Returns: The spin row. - public func step(_ step: Int) -> Self { - var newSelf = self - newSelf.step = step - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Forms/SwitchRow+.swift b/Sources/Adwaita/View/Forms/SwitchRow+.swift new file mode 100644 index 0000000..48c6b44 --- /dev/null +++ b/Sources/Adwaita/View/Forms/SwitchRow+.swift @@ -0,0 +1,21 @@ +// +// SwitchRow+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +/// A row representing a boolean state. +extension SwitchRow { + + /// Initialize a switch row. + /// - Parameters: + /// - title: The row's title. + /// - isOn: Whether the switch is on. + public init(_ title: String, isOn: Binding) { + self.init() + self = self.title(title) + self = self.active(isOn) + } + +} diff --git a/Sources/Adwaita/View/Forms/SwitchRow.swift b/Sources/Adwaita/View/Forms/SwitchRow.swift deleted file mode 100644 index ec863e6..0000000 --- a/Sources/Adwaita/View/Forms/SwitchRow.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// SwitchRow.swift -// Adwaita -// -// Created by david-swift on 04.01.24. -// - -import Libadwaita - -/// A row representing a boolean state. -public struct SwitchRow: Widget { - - /// The title. - var title: String - /// Whether the switch is activated. - @Binding var isOn: Bool - /// The subtitle. - var subtitle = "" - /// The prefix. - var prefix: Body = [] - /// The suffix. - var suffix: Body = [] - - /// The identifier for the prefix content. - let prefixID = "prefix" - /// The identifier for the suffix content. - let suffixID = "suffix" - - /// Initialize a switch row. - /// - Parameters: - /// - title: The row's title. - /// - isOn: Whether the switch is on. - public init(_ title: String, isOn: Binding) { - self.title = title - self._isOn = isOn - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let row = storage.view as? Libadwaita.SwitchRow { - update(row: row) - } - if let prefixStorage = storage.content[prefixID]?.first { - prefix.widget(modifiers: modifiers).update(prefixStorage, modifiers: modifiers) - } - if let suffixStorage = storage.content[suffixID]?.first { - suffix.widget(modifiers: modifiers).update(suffixStorage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let row: Libadwaita.SwitchRow = .init(title: title, subtitle: subtitle) - let prefixContent = prefix.widget(modifiers: modifiers).container(modifiers: modifiers) - let suffixContent = suffix.widget(modifiers: modifiers).container(modifiers: modifiers) - if !prefix.isEmpty { - _ = row.addPrefix(prefixContent.view) - } - if !suffix.isEmpty { - _ = row.addSuffix(suffixContent.view) - } - _ = row.onChange { - if row.getActive() != isOn { - isOn = row.getActive() - } - } - update(row: row) - return .init(row, content: [prefixID: [prefixContent], suffixID: [suffixContent]]) - } - - /// Update the switch row. - /// - Parameter row: The switch row. - func update(row: Libadwaita.SwitchRow) { - _ = row.title(title) - _ = row.subtitle(subtitle) - if row.getActive() != isOn { - row.setActive(isOn) - } - } - - /// Set the switch row's subtitle. - /// - Parameter subtitle: The subtitle. - /// - Returns: The switch row. - public func subtitle(_ subtitle: String) -> Self { - var newSelf = self - newSelf.subtitle = subtitle - return newSelf - } - - /// Set the switch row's prefix view. - /// - Parameter prefix: The prefix. - /// - Returns: The switch row. - public func prefix(@ViewBuilder _ prefix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.prefix = prefix() - return newSelf - } - - /// Set the switch row's suffix view. - /// - Parameter suffix: The suffix. - /// - Returns: The switch row. - public func suffix(@ViewBuilder _ suffix: @escaping () -> Body) -> Self { - var newSelf = self - newSelf.suffix = suffix() - return newSelf - } - -} diff --git a/Sources/Adwaita/View/Generated/ActionRow.swift b/Sources/Adwaita/View/Generated/ActionRow.swift new file mode 100644 index 0000000..95c071b --- /dev/null +++ b/Sources/Adwaita/View/Generated/ActionRow.swift @@ -0,0 +1,334 @@ +// +// ActionRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@Gtk.ListBoxRow] used to present actions. +/// +/// action-row +/// +/// The `AdwActionRow` widget can have a title, a subtitle and an icon. The row +/// can receive additional widgets at its end, or prefix widgets at its start. +/// +/// It is convenient to present a preference and its related actions. +/// +/// `AdwActionRow` is unactivatable by default, giving it an activatable widget +/// will automatically make it activatable, but unsetting it won't change the +/// row's activatability. +/// +/// ## AdwActionRow as GtkBuildable +/// +/// The `AdwActionRow` implementation of the [iface@Gtk.Buildable] interface +/// supports adding a child at its end by specifying “suffix” or omitting the +/// “type” attribute of a element. +/// +/// It also supports adding a child as a prefix widget by specifying “prefix” as +/// the “type” attribute of a element. +/// +/// ## CSS nodes +/// +/// `AdwActionRow` has a main CSS node with name `row`. +/// +/// It contains the subnode `box.header` for its main horizontal box, and +/// `box.title` for the vertical box containing the title and subtitle labels. +/// +/// It contains subnodes `label.title` and `label.subtitle` representing +/// respectively the title label and subtitle label. +public struct ActionRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + var activatableWidget: (() -> Body)? + /// The icon name for this row. + var iconName: String? + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var subtitle: String? + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var subtitleLines: Int? + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var subtitleSelectable: Bool? + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var titleLines: Int? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// This signal is emitted after the row has been activated. + var activated: (() -> Void)? + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ActionRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_action_row_new()?.opaque()) + update(storage, modifiers: modifiers) + if let activatableWidgetStorage = activatableWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["activatableWidget"] = [activatableWidgetStorage] + adw_action_row_set_activatable_widget(storage.pointer?.cast(), activatableWidgetStorage.pointer?.cast()) + } + + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activated { + storage.connectSignal(name: "activated") { + activated() + } + } + storage.modify { widget in + if let widget = storage.content["activatableWidget"]?.first { + activatableWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let iconName { + adw_action_row_set_icon_name(widget?.cast(), iconName) + } + if let subtitle { + adw_action_row_set_subtitle(widget?.cast(), subtitle) + } + if let subtitleLines { + adw_action_row_set_subtitle_lines(widget?.cast(), subtitleLines.cInt) + } + if let subtitleSelectable { + adw_action_row_set_subtitle_selectable(widget?.cast(), subtitleSelectable.cBool) + } + if let titleLines { + adw_action_row_set_title_lines(widget?.cast(), titleLines.cInt) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + if let suffixStorage = storage.content["suffix"] { + for (index, view) in suffix().enumerated() { + if let storage = suffixStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let prefixStorage = storage.content["prefix"] { + for (index, view) in prefix().enumerated() { + if let storage = prefixStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + public func activatableWidget(@ViewBuilder _ activatableWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.activatableWidget = activatableWidget + + return newSelf + } + + /// The icon name for this row. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func subtitle(_ subtitle: String?) -> Self { + var newSelf = self + newSelf.subtitle = subtitle + + return newSelf + } + + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func subtitleLines(_ subtitleLines: Int?) -> Self { + var newSelf = self + newSelf.subtitleLines = subtitleLines + + return newSelf + } + + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func subtitleSelectable(_ subtitleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.subtitleSelectable = subtitleSelectable + + return newSelf + } + + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func titleLines(_ titleLines: Int?) -> Self { + var newSelf = self + newSelf.titleLines = titleLines + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// This signal is emitted after the row has been activated. + public func activated(_ activated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activated = activated + return newSelf + } + + /// Set the body for "suffix". + /// - Parameter body: The body. + /// - Returns: The widget. + public func suffix(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.suffix = body + return newSelf + } + /// Set the body for "prefix". + /// - Parameter body: The body. + /// - Returns: The widget. + public func prefix(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.prefix = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/Avatar.swift b/Sources/Adwaita/View/Generated/Avatar.swift new file mode 100644 index 0000000..1ae76f6 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Avatar.swift @@ -0,0 +1,139 @@ +// +// Avatar.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A widget displaying an image, with a generated fallback. +/// +/// avatar +/// +/// `AdwAvatar` is a widget that shows a round avatar. +/// +/// `AdwAvatar` generates an avatar with the initials of the +/// [property@Avatar:text] on top of a colored background. +/// +/// The color is picked based on the hash of the [property@Avatar:text]. +/// +/// If [property@Avatar:show-initials] is set to `FALSE`, +/// [property@Avatar:icon-name] or `avatar-default-symbolic` is shown instead of +/// the initials. +/// +/// Use [property@Avatar:custom-image] to set a custom image. +/// +/// ## CSS nodes +/// +/// `AdwAvatar` has a single CSS node with name `avatar`. +public struct Avatar: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The name of an icon to use as a fallback. + /// + /// If no name is set, `avatar-default-symbolic` will be used. + var iconName: String? + /// Whether initials are used instead of an icon on the fallback avatar. + /// + /// See [property@Avatar:icon-name] for how to change the fallback icon. + var showInitials: Bool + /// The size of the avatar. + var size: Int + /// Sets the text used to generate the fallback initials and color. + /// + /// It's only used to generate the color if [property@Avatar:show-initials] is + /// `FALSE`. + var text: String? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Avatar`. + public init(showInitials: Bool, size: Int) { + self.showInitials = showInitials + self.size = size + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_avatar_new(size.cInt, text, showInitials.cBool)?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let iconName { + adw_avatar_set_icon_name(widget, iconName) + } + adw_avatar_set_show_initials(widget, showInitials.cBool) + adw_avatar_set_size(widget, size.cInt) + if let text { + adw_avatar_set_text(widget, text) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The name of an icon to use as a fallback. + /// + /// If no name is set, `avatar-default-symbolic` will be used. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// Whether initials are used instead of an icon on the fallback avatar. + /// + /// See [property@Avatar:icon-name] for how to change the fallback icon. + public func showInitials(_ showInitials: Bool) -> Self { + var newSelf = self + newSelf.showInitials = showInitials + + return newSelf + } + + /// The size of the avatar. + public func size(_ size: Int) -> Self { + var newSelf = self + newSelf.size = size + + return newSelf + } + + /// Sets the text used to generate the fallback initials and color. + /// + /// It's only used to generate the color if [property@Avatar:show-initials] is + /// `FALSE`. + public func text(_ text: String?) -> Self { + var newSelf = self + newSelf.text = text + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Banner.swift b/Sources/Adwaita/View/Generated/Banner.swift new file mode 100644 index 0000000..1aa1a33 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Banner.swift @@ -0,0 +1,161 @@ +// +// Banner.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A bar with contextual information. +/// +/// banner +/// +/// Banners are hidden by default, use [property@Banner:revealed] to show them. +/// +/// Banners have a title, set with [property@Banner:title]. Titles can be marked +/// up with Pango markup, use [property@Banner:use-markup] to enable it. +/// +/// The title will be shown centered or left-aligned depending on available +/// space. +/// +/// Banners can optionally have a button with text on it, set through +/// [property@Banner:button-label]. The button can be used with a `GAction`, +/// or with the [signal@Banner::button-clicked] signal. +/// +/// ## CSS nodes +/// +/// `AdwBanner` has a main CSS node with the name `banner`. +public struct Banner: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The label to show on the button. + /// + /// If set to `""` or `NULL`, the button won't be shown. + /// + /// The button can be used with a `GAction`, or with the + /// [signal@Banner::button-clicked] signal. + var buttonLabel: String? + /// Whether the banner is currently revealed. + var revealed: Bool? + /// The title for this banner. + /// + /// See also: [property@Banner:use-markup]. + var title: String + /// Whether to use Pango markup for the banner title. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// This signal is emitted after the action button has been clicked. + /// + /// It can be used as an alternative to setting an action. + var buttonClicked: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Banner`. + public init(title: String) { + self.title = title + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_banner_new(title)?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let buttonClicked { + storage.connectSignal(name: "button-clicked") { + buttonClicked() + } + } + storage.modify { widget in + if let buttonLabel { + adw_banner_set_button_label(widget, buttonLabel) + } + if let revealed { + adw_banner_set_revealed(widget, revealed.cBool) + } + adw_banner_set_title(widget, title) + if let useMarkup { + adw_banner_set_use_markup(widget, useMarkup.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The label to show on the button. + /// + /// If set to `""` or `NULL`, the button won't be shown. + /// + /// The button can be used with a `GAction`, or with the + /// [signal@Banner::button-clicked] signal. + public func buttonLabel(_ buttonLabel: String?) -> Self { + var newSelf = self + newSelf.buttonLabel = buttonLabel + + return newSelf + } + + /// Whether the banner is currently revealed. + public func revealed(_ revealed: Bool? = true) -> Self { + var newSelf = self + newSelf.revealed = revealed + + return newSelf + } + + /// The title for this banner. + /// + /// See also: [property@Banner:use-markup]. + public func title(_ title: String) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether to use Pango markup for the banner title. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// This signal is emitted after the action button has been clicked. + /// + /// It can be used as an alternative to setting an action. + public func buttonClicked(_ buttonClicked: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.buttonClicked = buttonClicked + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Bin.swift b/Sources/Adwaita/View/Generated/Bin.swift new file mode 100644 index 0000000..0555bdf --- /dev/null +++ b/Sources/Adwaita/View/Generated/Bin.swift @@ -0,0 +1,81 @@ +// +// Bin.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A widget with one child. +/// +/// bin +/// +/// The `AdwBin` widget has only one child, set with the [property@Bin:child] +/// property. +/// +/// It is useful for deriving subclasses, since it provides common code needed +/// for handling a single child widget. +public struct Bin: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The child widget of the `AdwBin`. + var child: (() -> Body)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Bin`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_bin_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + adw_bin_set_child(storage.pointer?.cast(), childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The child widget of the `AdwBin`. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Box.swift b/Sources/Adwaita/View/Generated/Box.swift new file mode 100644 index 0000000..5bc77a5 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Box.swift @@ -0,0 +1,173 @@ +// +// Box.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// The `GtkBox` widget arranges child widgets into a single row or column. +/// +/// ![An example GtkBox](box.png) +/// +/// Whether it is a row or column depends on the value of its +/// [property@Gtk.Orientable:orientation] property. Within the other +/// dimension, all children are allocated the same size. Of course, the +/// [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign] properties +/// can be used on the children to influence their allocation. +/// +/// Use repeated calls to [method@Gtk.Box.append] to pack widgets into a +/// `GtkBox` from start to end. Use [method@Gtk.Box.remove] to remove widgets +/// from the `GtkBox`. [method@Gtk.Box.insert_child_after] can be used to add +/// a child at a particular position. +/// +/// Use [method@Gtk.Box.set_homogeneous] to specify whether or not all children +/// of the `GtkBox` are forced to get the same amount of space. +/// +/// Use [method@Gtk.Box.set_spacing] to determine how much space will be minimally +/// placed between all children in the `GtkBox`. Note that spacing is added +/// *between* the children. +/// +/// Use [method@Gtk.Box.reorder_child_after] to move a child to a different +/// place in the box. +/// +/// # CSS nodes +/// +/// `GtkBox` uses a single CSS node with name box. +/// +/// # Accessibility +/// +/// Until GTK 4.10, `GtkBox` used the `GTK_ACCESSIBLE_ROLE_GROUP` role. +/// +/// Starting from GTK 4.12, `GtkBox` uses the `GTK_ACCESSIBLE_ROLE_GENERIC` role. +public struct Box: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The child that determines the baseline, in vertical orientation. + var baselineChild: Int? + /// Whether the children should all be the same size. + var homogeneous: Bool? + /// The amount of space between children. + var spacing: Int + /// The body for the widget "append". + var append: () -> Body = { [] } + /// The body for the widget "prepend". + var prepend: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Box`. + public init(spacing: Int) { + self.spacing = spacing + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing.cInt)?.opaque()) + update(storage, modifiers: modifiers) + + var appendStorage: [ViewStorage] = [] + for view in append() { + appendStorage.append(view.storage(modifiers: modifiers)) + gtk_box_append(storage.pointer?.cast(), appendStorage.last?.pointer?.cast()) + } + storage.content["append"] = appendStorage + var prependStorage: [ViewStorage] = [] + for view in prepend() { + prependStorage.append(view.storage(modifiers: modifiers)) + gtk_box_prepend(storage.pointer?.cast(), prependStorage.last?.pointer?.cast()) + } + storage.content["prepend"] = prependStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let baselineChild { + gtk_box_set_baseline_child(widget?.cast(), baselineChild.cInt) + } + if let homogeneous { + gtk_box_set_homogeneous(widget?.cast(), homogeneous.cBool) + } + gtk_box_set_spacing(widget?.cast(), spacing.cInt) + + if let appendStorage = storage.content["append"] { + for (index, view) in append().enumerated() { + if let storage = appendStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let prependStorage = storage.content["prepend"] { + for (index, view) in prepend().enumerated() { + if let storage = prependStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The child that determines the baseline, in vertical orientation. + public func baselineChild(_ baselineChild: Int?) -> Self { + var newSelf = self + newSelf.baselineChild = baselineChild + + return newSelf + } + + /// Whether the children should all be the same size. + public func homogeneous(_ homogeneous: Bool? = true) -> Self { + var newSelf = self + newSelf.homogeneous = homogeneous + + return newSelf + } + + /// The amount of space between children. + public func spacing(_ spacing: Int) -> Self { + var newSelf = self + newSelf.spacing = spacing + + return newSelf + } + + /// Set the body for "append". + /// - Parameter body: The body. + /// - Returns: The widget. + public func append(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.append = body + return newSelf + } + /// Set the body for "prepend". + /// - Parameter body: The body. + /// - Returns: The widget. + public func prepend(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.prepend = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/Button.swift b/Sources/Adwaita/View/Generated/Button.swift new file mode 100644 index 0000000..ec2f0d6 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Button.swift @@ -0,0 +1,221 @@ +// +// Button.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// The `GtkButton` widget is generally used to trigger a callback function that is +/// called when the button is pressed. +/// +/// ![An example GtkButton](button.png) +/// +/// The `GtkButton` widget can hold any valid child widget. That is, it can hold +/// almost any other standard `GtkWidget`. The most commonly used child is the +/// `GtkLabel`. +/// +/// # CSS nodes +/// +/// `GtkButton` has a single CSS node with name button. The node will get the +/// style classes .image-button or .text-button, if the content is just an +/// image or label, respectively. It may also receive the .flat style class. +/// When activating a button via the keyboard, the button will temporarily +/// gain the .keyboard-activating style class. +/// +/// Other style classes that are commonly used with `GtkButton` include +/// .suggested-action and .destructive-action. In special cases, buttons +/// can be made round by adding the .circular style class. +/// +/// Button-like widgets like [class@Gtk.ToggleButton], [class@Gtk.MenuButton], +/// [class@Gtk.VolumeButton], [class@Gtk.LockButton], [class@Gtk.ColorButton] +/// or [class@Gtk.FontButton] use style classes such as .toggle, .popup, .scale, +/// .lock, .color on the button node to differentiate themselves from a plain +/// `GtkButton`. +/// +/// # Accessibility +/// +/// `GtkButton` uses the %GTK_ACCESSIBLE_ROLE_BUTTON role. +public struct Button: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + /// + /// For text buttons, setting this property will allow ellipsizing the label. + /// + /// If the contents of a button are an icon or a custom widget, setting this + /// property has no effect. + var canShrink: Bool? + /// The child widget. + var child: (() -> Body)? + /// Whether the button has a frame. + var hasFrame: Bool? + /// The name of the icon used to automatically populate the button. + var iconName: String? + /// Text of the label inside the button, if the button contains a label widget. + var label: String? + /// If set, an underline in the text indicates that the following character is + /// to be used as mnemonic. + var useUnderline: Bool? + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect + /// to this signal, but use the [signal@Gtk.Button::clicked] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + var activate: (() -> Void)? + /// Emitted when the button has been activated (pressed and released). + var clicked: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Button`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_button_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_button_set_child(storage.pointer?.cast(), childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activate { + storage.connectSignal(name: "activate") { + activate() + } + } + if let clicked { + storage.connectSignal(name: "clicked") { + clicked() + } + } + storage.modify { widget in + if let canShrink { + gtk_button_set_can_shrink(widget?.cast(), canShrink.cBool) + } + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let hasFrame { + gtk_button_set_has_frame(widget?.cast(), hasFrame.cBool) + } + if let iconName { + gtk_button_set_icon_name(widget?.cast(), iconName) + } + if let label, storage.content["child"] == nil { + gtk_button_set_label(widget?.cast(), label) + } + if let useUnderline { + gtk_button_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + /// + /// For text buttons, setting this property will allow ellipsizing the label. + /// + /// If the contents of a button are an icon or a custom widget, setting this + /// property has no effect. + public func canShrink(_ canShrink: Bool? = true) -> Self { + var newSelf = self + newSelf.canShrink = canShrink + + return newSelf + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// Whether the button has a frame. + public func hasFrame(_ hasFrame: Bool? = true) -> Self { + var newSelf = self + newSelf.hasFrame = hasFrame + + return newSelf + } + + /// The name of the icon used to automatically populate the button. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// Text of the label inside the button, if the button contains a label widget. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// If set, an underline in the text indicates that the following character is + /// to be used as mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect + /// to this signal, but use the [signal@Gtk.Button::clicked] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + public func activate(_ activate: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activate = activate + return newSelf + } + + /// Emitted when the button has been activated (pressed and released). + public func clicked(_ clicked: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.clicked = clicked + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ButtonContent.swift b/Sources/Adwaita/View/Generated/ButtonContent.swift new file mode 100644 index 0000000..35b3928 --- /dev/null +++ b/Sources/Adwaita/View/Generated/ButtonContent.swift @@ -0,0 +1,162 @@ +// +// ButtonContent.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A helper widget for creating buttons. +/// +/// button-content +/// +/// `AdwButtonContent` is a box-like widget with an icon and a label. +/// +/// It's intended to be used as a direct child of [class@Gtk.Button], +/// [class@Gtk.MenuButton] or [class@SplitButton], when they need to have both an +/// icon and a label, as follows: +/// +/// ```xml +/// document-open-symbolic_OpenTrue +/// ``` +/// +/// `AdwButtonContent` handles style classes and connecting the mnemonic to the +/// button automatically. +/// +/// ## CSS nodes +/// +/// ``` +/// buttoncontent +/// ├── image +/// ╰── label +/// ``` +/// +/// `AdwButtonContent`'s CSS node is called `buttoncontent`. It contains the +/// subnodes `image` and `label`. +/// +/// When inside a `GtkButton` or `AdwSplitButton`, the button will receive the +/// `.image-text-button` style class. When inside a `GtkMenuButton`, the +/// internal `GtkButton` will receive it instead. +/// +/// ## Accessibility +/// +/// `AdwButtonContent` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct ButtonContent: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the button can be smaller than the natural size of its contents. + /// + /// If set to `TRUE`, the label will ellipsize. + /// + /// See [property@Gtk.Button:can-shrink]. + var canShrink: Bool? + /// The name of the displayed icon. + /// + /// If empty, the icon is not shown. + var iconName: String? + /// The displayed label. + var label: String? + /// Whether an underline in the text indicates a mnemonic. + /// + /// The mnemonic can be used to activate the parent button. + /// + /// See [property@ButtonContent:label]. + var useUnderline: Bool? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ButtonContent`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_button_content_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let canShrink { + adw_button_content_set_can_shrink(widget, canShrink.cBool) + } + if let iconName { + adw_button_content_set_icon_name(widget, iconName) + } + if let label { + adw_button_content_set_label(widget, label) + } + if let useUnderline { + adw_button_content_set_use_underline(widget, useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the button can be smaller than the natural size of its contents. + /// + /// If set to `TRUE`, the label will ellipsize. + /// + /// See [property@Gtk.Button:can-shrink]. + public func canShrink(_ canShrink: Bool? = true) -> Self { + var newSelf = self + newSelf.canShrink = canShrink + + return newSelf + } + + /// The name of the displayed icon. + /// + /// If empty, the icon is not shown. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The displayed label. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// Whether an underline in the text indicates a mnemonic. + /// + /// The mnemonic can be used to activate the parent button. + /// + /// See [property@ButtonContent:label]. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Carousel.swift b/Sources/Adwaita/View/Generated/Carousel.swift new file mode 100644 index 0000000..978eece --- /dev/null +++ b/Sources/Adwaita/View/Generated/Carousel.swift @@ -0,0 +1,229 @@ +// +// Carousel.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A paginated scrolling widget. +/// +/// carousel +/// +/// The `AdwCarousel` widget can be used to display a set of pages with +/// swipe-based navigation between them. +/// +/// [class@CarouselIndicatorDots] and [class@CarouselIndicatorLines] can be used +/// to provide page indicators for `AdwCarousel`. +/// +/// ## CSS nodes +/// +/// `AdwCarousel` has a single CSS node with name `carousel`. +public struct Carousel: Widget where Element: Identifiable { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether to allow swiping for more than one page at a time. + /// + /// If the value is `FALSE`, each swipe can only move to the adjacent pages. + var allowLongSwipes: Bool? + /// Sets whether the `AdwCarousel` can be dragged with mouse pointer. + /// + /// If the value is `FALSE`, dragging is only available on touch. + var allowMouseDrag: Bool? + /// Whether the widget will respond to scroll wheel events. + /// + /// If the value is `FALSE`, wheel events will be ignored. + var allowScrollWheel: Bool? + /// Whether the carousel can be navigated. + /// + /// This can be used to temporarily disable the carousel to only allow + /// navigating it in a certain state. + var interactive: Bool? + /// The number of pages in a `AdwCarousel`. + var nPages: UInt? + /// Page reveal duration, in milliseconds. + /// + /// Reveal duration is used when animating adding or removing pages. + var revealDuration: UInt? + /// Spacing between pages in pixels. + var spacing: UInt? + /// This signal is emitted after a page has been changed. + /// + /// It can be used to implement "infinite scrolling" by amending the pages + /// after every scroll. Note that an empty carousel is indicated by + /// `(int)index == -1`. + var pageChanged: (() -> Void)? + /// The dynamic widget elements. + var elements: [Element] + /// The dynamic widget content. + var content: (Element) -> Body + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Carousel`. + public init(_ elements: [Element], @ViewBuilder content: @escaping (Element) -> Body) { + self.elements = elements + self.content = content + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_carousel_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let pageChanged { + storage.connectSignal(name: "page-changed") { + pageChanged() + } + } + storage.modify { widget in + if let allowLongSwipes { + adw_carousel_set_allow_long_swipes(widget, allowLongSwipes.cBool) + } + if let allowMouseDrag { + adw_carousel_set_allow_mouse_drag(widget, allowMouseDrag.cBool) + } + if let allowScrollWheel { + adw_carousel_set_allow_scroll_wheel(widget, allowScrollWheel.cBool) + } + if let interactive { + adw_carousel_set_interactive(widget, interactive.cBool) + } + if let revealDuration { + adw_carousel_set_reveal_duration(widget, revealDuration.cInt) + } + if let spacing { + adw_carousel_set_spacing(widget, spacing.cInt) + } + + var contentStorage: [ViewStorage] = storage.content[.mainContent] ?? [] + let old = storage.fields["element"] as? [Element] ?? [] + old.identifiableTransform( + to: elements, + functions: .init { index, element in + let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers) + adw_carousel_remove(widget, adw_carousel_get_nth_page(widget, UInt(index).cInt)) + adw_carousel_insert(widget, child.pointer?.cast(), index.cInt) + contentStorage.remove(at: index) + contentStorage.insert(child, at: index) + } delete: { index in + adw_carousel_remove(widget, adw_carousel_get_nth_page(widget, UInt(index).cInt)) + contentStorage.remove(at: index) + } insert: { index, element in + let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers) + adw_carousel_insert(widget, child.pointer?.cast(), index.cInt) + contentStorage.insert(child, at: index) + } + ) + storage.fields["element"] = elements + storage.content[.mainContent] = contentStorage + for (index, element) in elements.enumerated() { + content(element).widget(modifiers: modifiers).update(contentStorage[index], modifiers: modifiers) + } + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether to allow swiping for more than one page at a time. + /// + /// If the value is `FALSE`, each swipe can only move to the adjacent pages. + public func allowLongSwipes(_ allowLongSwipes: Bool? = true) -> Self { + var newSelf = self + newSelf.allowLongSwipes = allowLongSwipes + + return newSelf + } + + /// Sets whether the `AdwCarousel` can be dragged with mouse pointer. + /// + /// If the value is `FALSE`, dragging is only available on touch. + public func allowMouseDrag(_ allowMouseDrag: Bool? = true) -> Self { + var newSelf = self + newSelf.allowMouseDrag = allowMouseDrag + + return newSelf + } + + /// Whether the widget will respond to scroll wheel events. + /// + /// If the value is `FALSE`, wheel events will be ignored. + public func allowScrollWheel(_ allowScrollWheel: Bool? = true) -> Self { + var newSelf = self + newSelf.allowScrollWheel = allowScrollWheel + + return newSelf + } + + /// Whether the carousel can be navigated. + /// + /// This can be used to temporarily disable the carousel to only allow + /// navigating it in a certain state. + public func interactive(_ interactive: Bool? = true) -> Self { + var newSelf = self + newSelf.interactive = interactive + + return newSelf + } + + /// The number of pages in a `AdwCarousel`. + public func nPages(_ nPages: UInt?) -> Self { + var newSelf = self + newSelf.nPages = nPages + + return newSelf + } + + /// Page reveal duration, in milliseconds. + /// + /// Reveal duration is used when animating adding or removing pages. + public func revealDuration(_ revealDuration: UInt?) -> Self { + var newSelf = self + newSelf.revealDuration = revealDuration + + return newSelf + } + + /// Spacing between pages in pixels. + public func spacing(_ spacing: UInt?) -> Self { + var newSelf = self + newSelf.spacing = spacing + + return newSelf + } + + /// This signal is emitted after a page has been changed. + /// + /// It can be used to implement "infinite scrolling" by amending the pages + /// after every scroll. Note that an empty carousel is indicated by + /// `(int)index == -1`. + public func pageChanged(_ pageChanged: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.pageChanged = pageChanged + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/CenterBox.swift b/Sources/Adwaita/View/Generated/CenterBox.swift new file mode 100644 index 0000000..7055e4d --- /dev/null +++ b/Sources/Adwaita/View/Generated/CenterBox.swift @@ -0,0 +1,183 @@ +// +// CenterBox.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// `GtkCenterBox` arranges three children in a row, keeping the middle child +/// centered as well as possible. +/// +/// ![An example GtkCenterBox](centerbox.png) +/// +/// To add children to `GtkCenterBox`, use [method@Gtk.CenterBox.set_start_widget], +/// [method@Gtk.CenterBox.set_center_widget] and +/// [method@Gtk.CenterBox.set_end_widget]. +/// +/// The sizing and positioning of children can be influenced with the +/// align and expand properties of the children. +/// +/// # GtkCenterBox as GtkBuildable +/// +/// The `GtkCenterBox` implementation of the `GtkBuildable` interface +/// supports placing children in the 3 positions by specifying “start”, “center” +/// or “end” as the “type” attribute of a `` element. +/// +/// # CSS nodes +/// +/// `GtkCenterBox` uses a single CSS node with the name “box”, +/// +/// The first child of the `GtkCenterBox` will be allocated depending on the +/// text direction, i.e. in left-to-right layouts it will be allocated on the +/// left and in right-to-left layouts on the right. +/// +/// In vertical orientation, the nodes of the children are arranged from top to +/// bottom. +/// +/// # Accessibility +/// +/// Until GTK 4.10, `GtkCenterBox` used the `GTK_ACCESSIBLE_ROLE_GROUP` role. +/// +/// Starting from GTK 4.12, `GtkCenterBox` uses the `GTK_ACCESSIBLE_ROLE_GENERIC` role. +public struct CenterBox: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The widget that is placed at the center position. + var centerWidget: (() -> Body)? + /// The widget that is placed at the end position. + /// + /// In vertical orientation, the end position is at the bottom. + /// In horizontal orientation, the end position is at the trailing + /// edge wrt. to the text direction. + var endWidget: (() -> Body)? + /// Whether to shrink the center widget after other children. + /// + /// By default, when there's no space to give all three children their + /// natural widths, the start and end widgets start shrinking and the + /// center child keeps natural width until they reach minimum width. + /// + /// If set to `FALSE`, start and end widgets keep natural width and the + /// center widget starts shrinking instead. + var shrinkCenterLast: Bool? + /// The widget that is placed at the start position. + /// + /// In vertical orientation, the start position is at the top. + /// In horizontal orientation, the start position is at the leading + /// edge wrt. to the text direction. + var startWidget: (() -> Body)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `CenterBox`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_center_box_new()?.opaque()) + update(storage, modifiers: modifiers) + if let centerWidgetStorage = centerWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["centerWidget"] = [centerWidgetStorage] + gtk_center_box_set_center_widget(storage.pointer, centerWidgetStorage.pointer?.cast()) + } + if let endWidgetStorage = endWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["endWidget"] = [endWidgetStorage] + gtk_center_box_set_end_widget(storage.pointer, endWidgetStorage.pointer?.cast()) + } + if let startWidgetStorage = startWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["startWidget"] = [startWidgetStorage] + gtk_center_box_set_start_widget(storage.pointer, startWidgetStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let widget = storage.content["centerWidget"]?.first { + centerWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let widget = storage.content["endWidget"]?.first { + endWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let shrinkCenterLast { + gtk_center_box_set_shrink_center_last(widget, shrinkCenterLast.cBool) + } + if let widget = storage.content["startWidget"]?.first { + startWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The widget that is placed at the center position. + public func centerWidget(@ViewBuilder _ centerWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.centerWidget = centerWidget + + return newSelf + } + + /// The widget that is placed at the end position. + /// + /// In vertical orientation, the end position is at the bottom. + /// In horizontal orientation, the end position is at the trailing + /// edge wrt. to the text direction. + public func endWidget(@ViewBuilder _ endWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.endWidget = endWidget + + return newSelf + } + + /// Whether to shrink the center widget after other children. + /// + /// By default, when there's no space to give all three children their + /// natural widths, the start and end widgets start shrinking and the + /// center child keeps natural width until they reach minimum width. + /// + /// If set to `FALSE`, start and end widgets keep natural width and the + /// center widget starts shrinking instead. + public func shrinkCenterLast(_ shrinkCenterLast: Bool? = true) -> Self { + var newSelf = self + newSelf.shrinkCenterLast = shrinkCenterLast + + return newSelf + } + + /// The widget that is placed at the start position. + /// + /// In vertical orientation, the start position is at the top. + /// In horizontal orientation, the start position is at the leading + /// edge wrt. to the text direction. + public func startWidget(@ViewBuilder _ startWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.startWidget = startWidget + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/CheckButton.swift b/Sources/Adwaita/View/Generated/CheckButton.swift new file mode 100644 index 0000000..e8198b8 --- /dev/null +++ b/Sources/Adwaita/View/Generated/CheckButton.swift @@ -0,0 +1,246 @@ +// +// CheckButton.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A `GtkCheckButton` places a label next to an indicator. +/// +/// ![Example GtkCheckButtons](check-button.png) +/// +/// A `GtkCheckButton` is created by calling either [ctor@Gtk.CheckButton.new] +/// or [ctor@Gtk.CheckButton.new_with_label]. +/// +/// The state of a `GtkCheckButton` can be set specifically using +/// [method@Gtk.CheckButton.set_active], and retrieved using +/// [method@Gtk.CheckButton.get_active]. +/// +/// # Inconsistent state +/// +/// In addition to "on" and "off", check buttons can be an +/// "in between" state that is neither on nor off. This can be used +/// e.g. when the user has selected a range of elements (such as some +/// text or spreadsheet cells) that are affected by a check button, +/// and the current values in that range are inconsistent. +/// +/// To set a `GtkCheckButton` to inconsistent state, use +/// [method@Gtk.CheckButton.set_inconsistent]. +/// +/// # Grouping +/// +/// Check buttons can be grouped together, to form mutually exclusive +/// groups - only one of the buttons can be toggled at a time, and toggling +/// another one will switch the currently toggled one off. +/// +/// Grouped check buttons use a different indicator, and are commonly referred +/// to as *radio buttons*. +/// +/// ![Example GtkCheckButtons](radio-button.png) +/// +/// To add a `GtkCheckButton` to a group, use [method@Gtk.CheckButton.set_group]. +/// +/// When the code must keep track of the state of a group of radio buttons, it +/// is recommended to keep track of such state through a stateful +/// `GAction` with a target for each button. Using the `toggled` signals to keep +/// track of the group changes and state is discouraged. +/// +/// # CSS nodes +/// +/// ``` +/// checkbutton[.text-button] +/// ├── check +/// ╰── [label] +/// ``` +/// +/// A `GtkCheckButton` has a main node with name checkbutton. If the +/// [property@Gtk.CheckButton:label] or [property@Gtk.CheckButton:child] +/// properties are set, it contains a child widget. The indicator node +/// is named check when no group is set, and radio if the checkbutton +/// is grouped together with other checkbuttons. +/// +/// # Accessibility +/// +/// `GtkCheckButton` uses the %GTK_ACCESSIBLE_ROLE_CHECKBOX role. +public struct CheckButton: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// If the check button is active. + /// + /// Setting `active` to %TRUE will add the `:checked:` state to both + /// the check button and the indicator CSS node. + var active: Binding? + /// The child widget. + var child: (() -> Body)? + /// If the check button is in an “in between” state. + /// + /// The inconsistent state only affects visual appearance, + /// not the semantics of the button. + var inconsistent: Bool? + /// Text of the label inside the check button, if it contains a label widget. + var label: String? + /// If set, an underline in the text indicates that the following + /// character is to be used as mnemonic. + var useUnderline: Bool? + /// Emitted to when the check button is activated. + /// + /// The `::activate` signal on `GtkCheckButton` is an action signal and + /// emitting it causes the button to animate press then release. + /// + /// Applications should never connect to this signal, but use the + /// [signal@Gtk.CheckButton::toggled] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + var activate: (() -> Void)? + /// Emitted when the buttons's [property@Gtk.CheckButton:active] + /// property changes. + var toggled: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `CheckButton`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_check_button_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_check_button_set_child(storage.pointer?.cast(), childStorage.pointer?.cast()) + } + + + storage.notify(name: "active") { + active?.wrappedValue = gtk_check_button_get_active(storage.pointer?.cast()) != 0 + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activate { + storage.connectSignal(name: "activate") { + activate() + } + } + if let toggled { + storage.connectSignal(name: "toggled") { + toggled() + } + } + storage.modify { widget in + if let active { + gtk_check_button_set_active(widget?.cast(), active.wrappedValue.cBool) + } + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let inconsistent { + gtk_check_button_set_inconsistent(widget?.cast(), inconsistent.cBool) + } + if let label, storage.content["child"] == nil { + gtk_check_button_set_label(widget?.cast(), label) + } + if let useUnderline { + gtk_check_button_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// If the check button is active. + /// + /// Setting `active` to %TRUE will add the `:checked:` state to both + /// the check button and the indicator CSS node. + public func active(_ active: Binding?) -> Self { + var newSelf = self + newSelf.active = active + + return newSelf + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// If the check button is in an “in between” state. + /// + /// The inconsistent state only affects visual appearance, + /// not the semantics of the button. + public func inconsistent(_ inconsistent: Bool? = true) -> Self { + var newSelf = self + newSelf.inconsistent = inconsistent + + return newSelf + } + + /// Text of the label inside the check button, if it contains a label widget. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// If set, an underline in the text indicates that the following + /// character is to be used as mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted to when the check button is activated. + /// + /// The `::activate` signal on `GtkCheckButton` is an action signal and + /// emitting it causes the button to animate press then release. + /// + /// Applications should never connect to this signal, but use the + /// [signal@Gtk.CheckButton::toggled] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + public func activate(_ activate: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activate = activate + return newSelf + } + + /// Emitted when the buttons's [property@Gtk.CheckButton:active] + /// property changes. + public func toggled(_ toggled: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.toggled = toggled + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Clamp.swift b/Sources/Adwaita/View/Generated/Clamp.swift new file mode 100644 index 0000000..ee0a6c2 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Clamp.swift @@ -0,0 +1,146 @@ +// +// Clamp.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A widget constraining its child to a given size. +/// +/// clamp-wideclamp-narrow +/// +/// The `AdwClamp` widget constrains the size of the widget it contains to a +/// given maximum size. It will constrain the width if it is horizontal, or the +/// height if it is vertical. The expansion of the child from its minimum to its +/// maximum size is eased out for a smooth transition. +/// +/// If the child requires more than the requested maximum size, it will be +/// allocated the minimum size it can fit in instead. +/// +/// `AdwClamp` can scale with the text scale factor, use the +/// [property@ClampLayout:unit] property to enable that behavior. +/// +/// ## CSS nodes +/// +/// `AdwClamp` has a single CSS node with name `clamp`. +public struct Clamp: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The child widget of the `AdwClamp`. + var child: (() -> Body)? + /// The maximum size allocated to the child. + /// + /// It is the width if the clamp is horizontal, or the height if it is vertical. + var maximumSize: Int? + /// The size above which the child is clamped. + /// + /// Starting from this size, the clamp will tighten its grip on the child, + /// slowly allocating less and less of the available size up to the maximum + /// allocated size. Below that threshold and below the maximum size, the child + /// will be allocated all the available size. + /// + /// If the threshold is greater than the maximum size to allocate to the child, + /// the child will be allocated all the size up to the maximum. + /// If the threshold is lower than the minimum size to allocate to the child, + /// that size will be used as the tightening threshold. + /// + /// Effectively, tightening the grip on the child before it reaches its maximum + /// size makes transitions to and from the maximum size smoother when resizing. + var tighteningThreshold: Int? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Clamp`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_clamp_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + adw_clamp_set_child(storage.pointer, childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let maximumSize { + adw_clamp_set_maximum_size(widget, maximumSize.cInt) + } + if let tighteningThreshold { + adw_clamp_set_tightening_threshold(widget, tighteningThreshold.cInt) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The child widget of the `AdwClamp`. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// The maximum size allocated to the child. + /// + /// It is the width if the clamp is horizontal, or the height if it is vertical. + public func maximumSize(_ maximumSize: Int?) -> Self { + var newSelf = self + newSelf.maximumSize = maximumSize + + return newSelf + } + + /// The size above which the child is clamped. + /// + /// Starting from this size, the clamp will tighten its grip on the child, + /// slowly allocating less and less of the available size up to the maximum + /// allocated size. Below that threshold and below the maximum size, the child + /// will be allocated all the available size. + /// + /// If the threshold is greater than the maximum size to allocate to the child, + /// the child will be allocated all the size up to the maximum. + /// If the threshold is lower than the minimum size to allocate to the child, + /// that size will be used as the tightening threshold. + /// + /// Effectively, tightening the grip on the child before it reaches its maximum + /// size makes transitions to and from the maximum size smoother when resizing. + public func tighteningThreshold(_ tighteningThreshold: Int?) -> Self { + var newSelf = self + newSelf.tighteningThreshold = tighteningThreshold + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ComboRow.swift b/Sources/Adwaita/View/Generated/ComboRow.swift new file mode 100644 index 0000000..9c7804c --- /dev/null +++ b/Sources/Adwaita/View/Generated/ComboRow.swift @@ -0,0 +1,380 @@ +// +// ComboRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@Gtk.ListBoxRow] used to choose from a list of items. +/// +/// combo-row +/// +/// The `AdwComboRow` widget allows the user to choose from a list of valid +/// choices. The row displays the selected choice. When activated, the row +/// displays a popover which allows the user to make a new choice. +/// +/// Example of an `AdwComboRow` UI definition: +/// ```xml +/// Combo RowFooBarBaz +/// ``` +/// +/// The [property@ComboRow:selected] and [property@ComboRow:selected-item] +/// properties can be used to keep track of the selected item and react to their +/// changes. +/// +/// `AdwComboRow` mirrors [class@Gtk.DropDown], see that widget for details. +/// +/// `AdwComboRow` is [property@Gtk.ListBoxRow:activatable] if a model is set. +/// +/// ## CSS nodes +/// +/// `AdwComboRow` has a main CSS node with name `row` and the `.combo` style +/// class. +/// +/// Its popover has the node named `popover` with the `.menu` style class, it +/// contains a [class@Gtk.ScrolledWindow], which in turn contains a +/// [class@Gtk.ListView], both are accessible via their regular nodes. +/// +/// ## Accessibility +/// +/// `AdwComboRow` uses the `GTK_ACCESSIBLE_ROLE_COMBO_BOX` role. +public struct ComboRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether to show a search entry in the popup. + /// + /// If set to `TRUE`, a search entry will be shown in the popup that + /// allows to search for items in the list. + /// + /// Search requires [property@ComboRow:expression] to be set. + var enableSearch: Bool? + /// The position of the selected item. + /// + /// If no item is selected, the property has the value + /// [const@Gtk.INVALID_LIST_POSITION] + var selected: Binding? + /// Whether to use the current value as the subtitle. + /// + /// If you use a custom list item factory, you will need to give the row a + /// name conversion expression with [property@ComboRow:expression]. + /// + /// If set to `TRUE`, you should not access [property@ActionRow:subtitle]. + /// + /// The subtitle is interpreted as Pango markup if + /// [property@PreferencesRow:use-markup] is set to `TRUE`. + var useSubtitle: Bool? + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + var activatableWidget: (() -> Body)? + /// The icon name for this row. + var iconName: String? + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var subtitle: String? + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var subtitleLines: Int? + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var subtitleSelectable: Bool? + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var titleLines: Int? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// This signal is emitted after the row has been activated. + var activated: (() -> Void)? + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ComboRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_combo_row_new()?.opaque()) + update(storage, modifiers: modifiers) + if let activatableWidgetStorage = activatableWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["activatableWidget"] = [activatableWidgetStorage] + adw_action_row_set_activatable_widget(storage.pointer?.cast(), activatableWidgetStorage.pointer?.cast()) + } + + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + storage.notify(name: "selected") { + selected?.wrappedValue = .init(adw_combo_row_get_selected(storage.pointer?.cast())) + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activated { + storage.connectSignal(name: "activated") { + activated() + } + } + storage.modify { widget in + if let enableSearch { + adw_combo_row_set_enable_search(widget?.cast(), enableSearch.cBool) + } + if let selected { + adw_combo_row_set_selected(widget?.cast(), selected.wrappedValue.cInt) + } + if let useSubtitle { + adw_combo_row_set_use_subtitle(widget?.cast(), useSubtitle.cBool) + } + if let widget = storage.content["activatableWidget"]?.first { + activatableWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let iconName { + adw_action_row_set_icon_name(widget?.cast(), iconName) + } + if let subtitle { + adw_action_row_set_subtitle(widget?.cast(), subtitle) + } + if let subtitleLines { + adw_action_row_set_subtitle_lines(widget?.cast(), subtitleLines.cInt) + } + if let subtitleSelectable { + adw_action_row_set_subtitle_selectable(widget?.cast(), subtitleSelectable.cBool) + } + if let titleLines { + adw_action_row_set_title_lines(widget?.cast(), titleLines.cInt) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether to show a search entry in the popup. + /// + /// If set to `TRUE`, a search entry will be shown in the popup that + /// allows to search for items in the list. + /// + /// Search requires [property@ComboRow:expression] to be set. + public func enableSearch(_ enableSearch: Bool? = true) -> Self { + var newSelf = self + newSelf.enableSearch = enableSearch + + return newSelf + } + + /// The position of the selected item. + /// + /// If no item is selected, the property has the value + /// [const@Gtk.INVALID_LIST_POSITION] + public func selected(_ selected: Binding?) -> Self { + var newSelf = self + newSelf.selected = selected + + return newSelf + } + + /// Whether to use the current value as the subtitle. + /// + /// If you use a custom list item factory, you will need to give the row a + /// name conversion expression with [property@ComboRow:expression]. + /// + /// If set to `TRUE`, you should not access [property@ActionRow:subtitle]. + /// + /// The subtitle is interpreted as Pango markup if + /// [property@PreferencesRow:use-markup] is set to `TRUE`. + public func useSubtitle(_ useSubtitle: Bool? = true) -> Self { + var newSelf = self + newSelf.useSubtitle = useSubtitle + + return newSelf + } + + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + public func activatableWidget(@ViewBuilder _ activatableWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.activatableWidget = activatableWidget + + return newSelf + } + + /// The icon name for this row. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func subtitle(_ subtitle: String?) -> Self { + var newSelf = self + newSelf.subtitle = subtitle + + return newSelf + } + + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func subtitleLines(_ subtitleLines: Int?) -> Self { + var newSelf = self + newSelf.subtitleLines = subtitleLines + + return newSelf + } + + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func subtitleSelectable(_ subtitleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.subtitleSelectable = subtitleSelectable + + return newSelf + } + + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func titleLines(_ titleLines: Int?) -> Self { + var newSelf = self + newSelf.titleLines = titleLines + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// This signal is emitted after the row has been activated. + public func activated(_ activated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activated = activated + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/EntryRow.swift b/Sources/Adwaita/View/Generated/EntryRow.swift new file mode 100644 index 0000000..ef2bf50 --- /dev/null +++ b/Sources/Adwaita/View/Generated/EntryRow.swift @@ -0,0 +1,293 @@ +// +// EntryRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@Gtk.ListBoxRow] with an embedded text entry. +/// +/// entry-row +/// +/// `AdwEntryRow` has a title that doubles as placeholder text. It shows an icon +/// indicating that it's editable and can receive additional widgets before or +/// after the editable part. +/// +/// If [property@EntryRow:show-apply-button] is set to `TRUE`, `AdwEntryRow` can +/// show an apply button when editing its contents. This can be useful if +/// changing its contents can result in an expensive operation, such as network +/// activity. +/// +/// `AdwEntryRow` provides only minimal API and should be used with the +/// [iface@Gtk.Editable] API. +/// +/// See also [class@PasswordEntryRow]. +/// +/// ## AdwEntryRow as GtkBuildable +/// +/// The `AdwEntryRow` implementation of the [iface@Gtk.Buildable] interface +/// supports adding a child at its end by specifying “suffix” or omitting the +/// “type” attribute of a element. +/// +/// It also supports adding a child as a prefix widget by specifying “prefix” as +/// the “type” attribute of a element. +/// +/// ## CSS nodes +/// +/// `AdwEntryRow` has a single CSS node with name `row` and the `.entry` style +/// class. +public struct EntryRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether activating the embedded entry can activate the default widget. + var activatesDefault: Bool? + /// Whether to suggest emoji replacements on the entry row. + /// + /// Emoji replacement is done with :-delimited names, like `:heart:`. + var enableEmojiCompletion: Bool? + /// Whether to show the apply button. + /// + /// When set to `TRUE`, typing text in the entry will reveal an apply button. + /// Clicking it or pressing the Enter key will hide the button and + /// emit the [signal@EntryRow::apply] signal. + /// + /// This is useful if changing the entry contents can trigger an expensive + /// operation, e.g. network activity, to avoid triggering it after typing every + /// character. + var showApplyButton: Bool? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// Emitted when the apply button is pressed. + /// + /// See [property@EntryRow:show-apply-button]. + var apply: (() -> Void)? + /// Emitted when the embedded entry is activated. + var entryActivated: (() -> Void)? + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `EntryRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_entry_row_new()?.opaque()) + update(storage, modifiers: modifiers) + + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_entry_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_entry_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let apply { + storage.connectSignal(name: "apply") { + apply() + } + } + if let entryActivated { + storage.connectSignal(name: "entry-activated") { + entryActivated() + } + } + storage.modify { widget in + if let activatesDefault { + adw_entry_row_set_activates_default(widget?.cast(), activatesDefault.cBool) + } + if let enableEmojiCompletion { + adw_entry_row_set_enable_emoji_completion(widget?.cast(), enableEmojiCompletion.cBool) + } + if let showApplyButton { + adw_entry_row_set_show_apply_button(widget?.cast(), showApplyButton.cBool) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + if let suffixStorage = storage.content["suffix"] { + for (index, view) in suffix().enumerated() { + if let storage = suffixStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let prefixStorage = storage.content["prefix"] { + for (index, view) in prefix().enumerated() { + if let storage = prefixStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether activating the embedded entry can activate the default widget. + public func activatesDefault(_ activatesDefault: Bool? = true) -> Self { + var newSelf = self + newSelf.activatesDefault = activatesDefault + + return newSelf + } + + /// Whether to suggest emoji replacements on the entry row. + /// + /// Emoji replacement is done with :-delimited names, like `:heart:`. + public func enableEmojiCompletion(_ enableEmojiCompletion: Bool? = true) -> Self { + var newSelf = self + newSelf.enableEmojiCompletion = enableEmojiCompletion + + return newSelf + } + + /// Whether to show the apply button. + /// + /// When set to `TRUE`, typing text in the entry will reveal an apply button. + /// Clicking it or pressing the Enter key will hide the button and + /// emit the [signal@EntryRow::apply] signal. + /// + /// This is useful if changing the entry contents can trigger an expensive + /// operation, e.g. network activity, to avoid triggering it after typing every + /// character. + public func showApplyButton(_ showApplyButton: Bool? = true) -> Self { + var newSelf = self + newSelf.showApplyButton = showApplyButton + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted when the apply button is pressed. + /// + /// See [property@EntryRow:show-apply-button]. + public func apply(_ apply: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.apply = apply + return newSelf + } + + /// Emitted when the embedded entry is activated. + public func entryActivated(_ entryActivated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.entryActivated = entryActivated + return newSelf + } + + /// Set the body for "suffix". + /// - Parameter body: The body. + /// - Returns: The widget. + public func suffix(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.suffix = body + return newSelf + } + /// Set the body for "prefix". + /// - Parameter body: The body. + /// - Returns: The widget. + public func prefix(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.prefix = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/ExpanderRow.swift b/Sources/Adwaita/View/Generated/ExpanderRow.swift new file mode 100644 index 0000000..cb2450c --- /dev/null +++ b/Sources/Adwaita/View/Generated/ExpanderRow.swift @@ -0,0 +1,332 @@ +// +// ExpanderRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@Gtk.ListBoxRow] used to reveal widgets. +/// +/// expander-row +/// +/// The `AdwExpanderRow` widget allows the user to reveal or hide widgets below +/// it. It also allows the user to enable the expansion of the row, allowing to +/// disable all that the row contains. +/// +/// ## AdwExpanderRow as GtkBuildable +/// +/// The `AdwExpanderRow` implementation of the [iface@Gtk.Buildable] interface +/// supports adding a child as an suffix widget by specifying “suffix” as the +/// “type” attribute of a element. +/// +/// It also supports adding it as a prefix widget by specifying “prefix” as the +/// “type” attribute of a element. +/// +/// ## CSS nodes +/// +/// `AdwExpanderRow` has a main CSS node with name `row` and the `.expander` +/// style class. It has the `.empty` style class when it contains no children. +/// +/// It contains the subnodes `row.header` for its main embedded row, +/// `list.nested` for the list it can expand, and `image.expander-row-arrow` for +/// its arrow. +public struct ExpanderRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether expansion is enabled. + var enableExpansion: Binding? + /// Whether the row is expanded. + var expanded: Binding? + /// The icon name for this row. + var iconName: String? + /// Whether the switch enabling the expansion is visible. + var showEnableSwitch: Bool? + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var subtitle: String? + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var subtitleLines: Int? + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var titleLines: Int? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// The body for the widget "rows". + var rows: () -> Body = { [] } + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ExpanderRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_expander_row_new()?.opaque()) + update(storage, modifiers: modifiers) + + var rowsStorage: [ViewStorage] = [] + for view in rows() { + rowsStorage.append(view.storage(modifiers: modifiers)) + adw_expander_row_add_row(storage.pointer?.cast(), rowsStorage.last?.pointer?.cast()) + } + storage.content["rows"] = rowsStorage + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_expander_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_expander_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + storage.notify(name: "enable-expansion") { + enableExpansion?.wrappedValue = adw_expander_row_get_enable_expansion(storage.pointer?.cast()) != 0 + } + storage.notify(name: "expanded") { + expanded?.wrappedValue = adw_expander_row_get_expanded(storage.pointer?.cast()) != 0 + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let enableExpansion { + adw_expander_row_set_enable_expansion(widget?.cast(), enableExpansion.wrappedValue.cBool) + } + if let expanded { + adw_expander_row_set_expanded(widget?.cast(), expanded.wrappedValue.cBool) + } + if let iconName { + adw_expander_row_set_icon_name(widget?.cast(), iconName) + } + if let showEnableSwitch { + adw_expander_row_set_show_enable_switch(widget?.cast(), showEnableSwitch.cBool) + } + if let subtitle { + adw_expander_row_set_subtitle(widget?.cast(), subtitle) + } + if let subtitleLines { + adw_expander_row_set_subtitle_lines(widget?.cast(), subtitleLines.cInt) + } + if let titleLines { + adw_expander_row_set_title_lines(widget?.cast(), titleLines.cInt) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + if let rowsStorage = storage.content["rows"] { + for (index, view) in rows().enumerated() { + if let storage = rowsStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let suffixStorage = storage.content["suffix"] { + for (index, view) in suffix().enumerated() { + if let storage = suffixStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let prefixStorage = storage.content["prefix"] { + for (index, view) in prefix().enumerated() { + if let storage = prefixStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether expansion is enabled. + public func enableExpansion(_ enableExpansion: Binding?) -> Self { + var newSelf = self + newSelf.enableExpansion = enableExpansion + + return newSelf + } + + /// Whether the row is expanded. + public func expanded(_ expanded: Binding?) -> Self { + var newSelf = self + newSelf.expanded = expanded + + return newSelf + } + + /// The icon name for this row. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// Whether the switch enabling the expansion is visible. + public func showEnableSwitch(_ showEnableSwitch: Bool? = true) -> Self { + var newSelf = self + newSelf.showEnableSwitch = showEnableSwitch + + return newSelf + } + + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func subtitle(_ subtitle: String?) -> Self { + var newSelf = self + newSelf.subtitle = subtitle + + return newSelf + } + + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func subtitleLines(_ subtitleLines: Int?) -> Self { + var newSelf = self + newSelf.subtitleLines = subtitleLines + + return newSelf + } + + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func titleLines(_ titleLines: Int?) -> Self { + var newSelf = self + newSelf.titleLines = titleLines + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Set the body for "rows". + /// - Parameter body: The body. + /// - Returns: The widget. + public func rows(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.rows = body + return newSelf + } + /// Set the body for "suffix". + /// - Parameter body: The body. + /// - Returns: The widget. + public func suffix(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.suffix = body + return newSelf + } + /// Set the body for "prefix". + /// - Parameter body: The body. + /// - Returns: The widget. + public func prefix(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.prefix = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/HeaderBar.swift b/Sources/Adwaita/View/Generated/HeaderBar.swift new file mode 100644 index 0000000..fc9e241 --- /dev/null +++ b/Sources/Adwaita/View/Generated/HeaderBar.swift @@ -0,0 +1,331 @@ +// +// HeaderBar.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A title bar widget. +/// +/// header-bar +/// +/// `AdwHeaderBar` is similar to [class@Gtk.HeaderBar], but provides additional +/// features compared to it. Refer to `GtkHeaderBar` for details. It is typically +/// used as a top bar within [class@ToolbarView]. +/// +/// ## Navigation View Integration +/// +/// When placed inside an [class@NavigationPage], `AdwHeaderBar` will display the +/// page title instead of window title. +/// +/// When used together with [class@NavigationView] or [class@NavigationSplitView], +/// it will also display a back button that can be used to go back to the previous +/// page. The button also has a context menu, allowing to pop multiple pages at +/// once, potentially across multiple navigation views. In rare scenarios, set +/// [property@HeaderBar:show-back-button] to `FALSE` to disable the back button +/// if it's unwanted (e.g. in an extra header bar on the same page). +/// +/// ## Split View Integration +/// +/// When placed inside `AdwNavigationSplitView` or `AdwOverlaySplitView`, +/// `AdwHeaderBar` will automatically hide the title buttons other than at the +/// edges of the window. +/// +/// ## Centering Policy +/// +/// [property@HeaderBar:centering-policy] allows to enforce strict centering of +/// the title widget. This can be useful for entries inside [class@Clamp]. +/// +/// ## Title Buttons +/// +/// Unlike `GtkHeaderBar`, `AdwHeaderBar` allows to toggle title button +/// visibility for each side individually, using the +/// [property@HeaderBar:show-start-title-buttons] and +/// [property@HeaderBar:show-end-title-buttons] properties. +/// +/// ## CSS nodes +/// +/// ``` +/// headerbar +/// ╰── windowhandle +/// ╰── box +/// ├── widget +/// │ ╰── box.start +/// │ ├── windowcontrols.start +/// │ ├── widget +/// │ │ ╰── [button.back] +/// │ ╰── [other children] +/// ├── widget +/// │ ╰── [Title Widget] +/// ╰── widget +/// ╰── box.end +/// ├── [other children] +/// ╰── windowcontrols.end +/// ``` +/// +/// `AdwHeaderBar`'s CSS node is called `headerbar`. It contains a `windowhandle` +/// subnode, which contains a `box` subnode, which contains three `widget` +/// subnodes at the start, center and end of the header bar. The start and end +/// subnotes contain a `box` subnode with the `.start` and `.end` style classes +/// respectively, and the center node contains a node that represents the title. +/// +/// Each of the boxes contains a `windowcontrols` subnode, see +/// [class@Gtk.WindowControls] for details, as well as other children. +/// +/// When [property@HeaderBar:show-back-button] is `TRUE`, the start box also +/// contains a node with the name `widget` that contains a node with the name +/// `button` and `.back` style class. +/// +/// ## Accessibility +/// +/// `AdwHeaderBar` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct HeaderBar: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The decoration layout for buttons. + /// + /// If this property is not set, the + /// [property@Gtk.Settings:gtk-decoration-layout] setting is used. + /// + /// The format of the string is button names, separated by commas. A colon + /// separates the buttons that should appear at the start from those at the + /// end. Recognized button names are minimize, maximize, close and icon (the + /// window icon). + /// + /// For example, “icon:minimize,maximize,close” specifies an icon at the start, + /// and minimize, maximize and close buttons at the end. + var decorationLayout: String? + /// Whether the header bar can show the back button. + /// + /// The back button will never be shown unless the header bar is placed inside an + /// [class@NavigationView]. Usually, there is no reason to set this to `FALSE`. + var showBackButton: Bool? + /// Whether to show title buttons at the end of the header bar. + /// + /// See [property@HeaderBar:show-start-title-buttons] for the other side. + /// + /// Which buttons are actually shown and where is determined by the + /// [property@HeaderBar:decoration-layout] property, and by the state of the + /// window (e.g. a close button will not be shown if the window can't be + /// closed). + var showEndTitleButtons: Bool? + /// Whether to show title buttons at the start of the header bar. + /// + /// See [property@HeaderBar:show-end-title-buttons] for the other side. + /// + /// Which buttons are actually shown and where is determined by the + /// [property@HeaderBar:decoration-layout] property, and by the state of the + /// window (e.g. a close button will not be shown if the window can't be + /// closed). + var showStartTitleButtons: Bool? + /// Whether the title widget should be shown. + var showTitle: Bool? + /// The title widget to display. + /// + /// When set to `NULL`, the header bar will display the title of the window it + /// is contained in. + /// + /// To use a different title, use [class@WindowTitle]: + /// + /// ```xml + /// Title + /// ``` + var titleWidget: (() -> Body)? + /// The body for the widget "start". + var start: () -> Body = { [] } + /// The body for the widget "end". + var end: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `HeaderBar`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_header_bar_new()?.opaque()) + update(storage, modifiers: modifiers) + if let titleWidgetStorage = titleWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["titleWidget"] = [titleWidgetStorage] + adw_header_bar_set_title_widget(storage.pointer, titleWidgetStorage.pointer?.cast()) + } + + var startStorage: [ViewStorage] = [] + for view in start() { + startStorage.append(view.storage(modifiers: modifiers)) + adw_header_bar_pack_start(storage.pointer, startStorage.last?.pointer?.cast()) + } + storage.content["start"] = startStorage + var endStorage: [ViewStorage] = [] + for view in end() { + endStorage.append(view.storage(modifiers: modifiers)) + adw_header_bar_pack_end(storage.pointer, endStorage.last?.pointer?.cast()) + } + storage.content["end"] = endStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let decorationLayout { + adw_header_bar_set_decoration_layout(widget, decorationLayout) + } + if let showBackButton { + adw_header_bar_set_show_back_button(widget, showBackButton.cBool) + } + if let showEndTitleButtons { + adw_header_bar_set_show_end_title_buttons(widget, showEndTitleButtons.cBool) + } + if let showStartTitleButtons { + adw_header_bar_set_show_start_title_buttons(widget, showStartTitleButtons.cBool) + } + if let showTitle { + adw_header_bar_set_show_title(widget, showTitle.cBool) + } + if let widget = storage.content["titleWidget"]?.first { + titleWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + + if let startStorage = storage.content["start"] { + for (index, view) in start().enumerated() { + if let storage = startStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let endStorage = storage.content["end"] { + for (index, view) in end().enumerated() { + if let storage = endStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The decoration layout for buttons. + /// + /// If this property is not set, the + /// [property@Gtk.Settings:gtk-decoration-layout] setting is used. + /// + /// The format of the string is button names, separated by commas. A colon + /// separates the buttons that should appear at the start from those at the + /// end. Recognized button names are minimize, maximize, close and icon (the + /// window icon). + /// + /// For example, “icon:minimize,maximize,close” specifies an icon at the start, + /// and minimize, maximize and close buttons at the end. + public func decorationLayout(_ decorationLayout: String?) -> Self { + var newSelf = self + newSelf.decorationLayout = decorationLayout + + return newSelf + } + + /// Whether the header bar can show the back button. + /// + /// The back button will never be shown unless the header bar is placed inside an + /// [class@NavigationView]. Usually, there is no reason to set this to `FALSE`. + public func showBackButton(_ showBackButton: Bool? = true) -> Self { + var newSelf = self + newSelf.showBackButton = showBackButton + + return newSelf + } + + /// Whether to show title buttons at the end of the header bar. + /// + /// See [property@HeaderBar:show-start-title-buttons] for the other side. + /// + /// Which buttons are actually shown and where is determined by the + /// [property@HeaderBar:decoration-layout] property, and by the state of the + /// window (e.g. a close button will not be shown if the window can't be + /// closed). + public func showEndTitleButtons(_ showEndTitleButtons: Bool? = true) -> Self { + var newSelf = self + newSelf.showEndTitleButtons = showEndTitleButtons + + return newSelf + } + + /// Whether to show title buttons at the start of the header bar. + /// + /// See [property@HeaderBar:show-end-title-buttons] for the other side. + /// + /// Which buttons are actually shown and where is determined by the + /// [property@HeaderBar:decoration-layout] property, and by the state of the + /// window (e.g. a close button will not be shown if the window can't be + /// closed). + public func showStartTitleButtons(_ showStartTitleButtons: Bool? = true) -> Self { + var newSelf = self + newSelf.showStartTitleButtons = showStartTitleButtons + + return newSelf + } + + /// Whether the title widget should be shown. + public func showTitle(_ showTitle: Bool? = true) -> Self { + var newSelf = self + newSelf.showTitle = showTitle + + return newSelf + } + + /// The title widget to display. + /// + /// When set to `NULL`, the header bar will display the title of the window it + /// is contained in. + /// + /// To use a different title, use [class@WindowTitle]: + /// + /// ```xml + /// Title + /// ``` + public func titleWidget(@ViewBuilder _ titleWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.titleWidget = titleWidget + + return newSelf + } + + /// Set the body for "start". + /// - Parameter body: The body. + /// - Returns: The widget. + public func start(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.start = body + return newSelf + } + /// Set the body for "end". + /// - Parameter body: The body. + /// - Returns: The widget. + public func end(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.end = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/Label.swift b/Sources/Adwaita/View/Generated/Label.swift new file mode 100644 index 0000000..3d879a6 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Label.swift @@ -0,0 +1,502 @@ +// +// Label.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// The `GtkLabel` widget displays a small amount of text. +/// +/// As the name implies, most labels are used to label another widget +/// such as a [class@Button]. +/// +/// ![An example GtkLabel](label.png) +/// +/// # CSS nodes +/// +/// ``` +/// label +/// ├── [selection] +/// ├── [link] +/// ┊ +/// ╰── [link] +/// ``` +/// +/// `GtkLabel` has a single CSS node with the name label. A wide variety +/// of style classes may be applied to labels, such as .title, .subtitle, +/// .dim-label, etc. In the `GtkShortcutsWindow`, labels are used with the +/// .keycap style class. +/// +/// If the label has a selection, it gets a subnode with name selection. +/// +/// If the label has links, there is one subnode per link. These subnodes +/// carry the link or visited state depending on whether they have been +/// visited. In this case, label node also gets a .link style class. +/// +/// # GtkLabel as GtkBuildable +/// +/// The GtkLabel implementation of the GtkBuildable interface supports a +/// custom `` element, which supports any number of `` +/// elements. The element has attributes named “name“, “value“, +/// “start“ and “end“ and allows you to specify [struct@Pango.Attribute] +/// values for this label. +/// +/// An example of a UI definition fragment specifying Pango attributes: +/// ```xml +/// +/// ``` +/// +/// The start and end attributes specify the range of characters to which the +/// Pango attribute applies. If start and end are not specified, the attribute is +/// applied to the whole text. Note that specifying ranges does not make much +/// sense with translatable attributes. Use markup embedded in the translatable +/// content instead. +/// +/// # Accessibility +/// +/// `GtkLabel` uses the %GTK_ACCESSIBLE_ROLE_LABEL role. +/// +/// # Mnemonics +/// +/// Labels may contain “mnemonics”. Mnemonics are underlined characters in the +/// label, used for keyboard navigation. Mnemonics are created by providing a +/// string with an underscore before the mnemonic character, such as `"_File"`, +/// to the functions [ctor@Gtk.Label.new_with_mnemonic] or +/// [method@Gtk.Label.set_text_with_mnemonic]. +/// +/// Mnemonics automatically activate any activatable widget the label is +/// inside, such as a [class@Gtk.Button]; if the label is not inside the +/// mnemonic’s target widget, you have to tell the label about the target +/// using [class@Gtk.Label.set_mnemonic_widget]. Here’s a simple example where +/// the label is inside a button: +/// +/// ```c +/// // Pressing Alt+H will activate this button +/// GtkWidget *button = gtk_button_new (); +/// GtkWidget *label = gtk_label_new_with_mnemonic ("_Hello"); +/// gtk_button_set_child (GTK_BUTTON (button), label); +/// ``` +/// +/// There’s a convenience function to create buttons with a mnemonic label +/// already inside: +/// +/// ```c +/// // Pressing Alt+H will activate this button +/// GtkWidget *button = gtk_button_new_with_mnemonic ("_Hello"); +/// ``` +/// +/// To create a mnemonic for a widget alongside the label, such as a +/// [class@Gtk.Entry], you have to point the label at the entry with +/// [method@Gtk.Label.set_mnemonic_widget]: +/// +/// ```c +/// // Pressing Alt+H will focus the entry +/// GtkWidget *entry = gtk_entry_new (); +/// GtkWidget *label = gtk_label_new_with_mnemonic ("_Hello"); +/// gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); +/// ``` +/// +/// # Markup (styled text) +/// +/// To make it easy to format text in a label (changing colors, +/// fonts, etc.), label text can be provided in a simple +/// markup format: +/// +/// Here’s how to create a label with a small font: +/// ```c +/// GtkWidget *label = gtk_label_new (NULL); +/// gtk_label_set_markup (GTK_LABEL (label), "Small text"); +/// ``` +/// +/// (See the Pango manual for complete documentation] of available +/// tags, [func@Pango.parse_markup]) +/// +/// The markup passed to [method@Gtk.Label.set_markup] must be valid; for example, +/// literal `<`, `>` and `&` characters must be escaped as `<`, `>`, and `&`. +/// If you pass text obtained from the user, file, or a network to +/// [method@Gtk.Label.set_markup], you’ll want to escape it with +/// [func@GLib.markup_escape_text] or [func@GLib.markup_printf_escaped]. +/// +/// Markup strings are just a convenient way to set the [struct@Pango.AttrList] +/// on a label; [method@Gtk.Label.set_attributes] may be a simpler way to set +/// attributes in some cases. Be careful though; [struct@Pango.AttrList] tends +/// to cause internationalization problems, unless you’re applying attributes +/// to the entire string (i.e. unless you set the range of each attribute +/// to [0, %G_MAXINT)). The reason is that specifying the start_index and +/// end_index for a [struct@Pango.Attribute] requires knowledge of the exact +/// string being displayed, so translations will cause problems. +/// +/// # Selectable labels +/// +/// Labels can be made selectable with [method@Gtk.Label.set_selectable]. +/// Selectable labels allow the user to copy the label contents to +/// the clipboard. Only labels that contain useful-to-copy information +/// — such as error messages — should be made selectable. +/// +/// # Text layout +/// +/// A label can contain any number of paragraphs, but will have +/// performance problems if it contains more than a small number. +/// Paragraphs are separated by newlines or other paragraph separators +/// understood by Pango. +/// +/// Labels can automatically wrap text if you call [method@Gtk.Label.set_wrap]. +/// +/// [method@Gtk.Label.set_justify] sets how the lines in a label align +/// with one another. If you want to set how the label as a whole aligns +/// in its available space, see the [property@Gtk.Widget:halign] and +/// [property@Gtk.Widget:valign] properties. +/// +/// The [property@Gtk.Label:width-chars] and [property@Gtk.Label:max-width-chars] +/// properties can be used to control the size allocation of ellipsized or +/// wrapped labels. For ellipsizing labels, if either is specified (and less +/// than the actual text size), it is used as the minimum width, and the actual +/// text size is used as the natural width of the label. For wrapping labels, +/// width-chars is used as the minimum width, if specified, and max-width-chars +/// is used as the natural width. Even if max-width-chars specified, wrapping +/// labels will be rewrapped to use all of the available width. +/// +/// # Links +/// +/// GTK supports markup for clickable hyperlinks in addition to regular Pango +/// markup. The markup for links is borrowed from HTML, using the `` with +/// “href“, “title“ and “class“ attributes. GTK renders links similar to the +/// way they appear in web browsers, with colored, underlined text. The “title“ +/// attribute is displayed as a tooltip on the link. The “class“ attribute is +/// used as style class on the CSS node for the link. +/// +/// An example looks like this: +/// +/// ```c +/// const char *text = +/// "Go to the " +/// "" +/// "GTK website for more..."; +/// GtkWidget *label = gtk_label_new (NULL); +/// gtk_label_set_markup (GTK_LABEL (label), text); +/// ``` +/// +/// It is possible to implement custom handling for links and their tooltips +/// with the [signal@Gtk.Label::activate-link] signal and the +/// [method@Gtk.Label.get_current_uri] function. +public struct Label: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The contents of the label. + /// + /// If the string contains Pango markup (see [func@Pango.parse_markup]), + /// you will have to set the [property@Gtk.Label:use-markup] property to + /// %TRUE in order for the label to display the markup attributes. See also + /// [method@Gtk.Label.set_markup] for a convenience function that sets both + /// this property and the [property@Gtk.Label:use-markup] property at the + /// same time. + /// + /// If the string contains underlines acting as mnemonics, you will have to + /// set the [property@Gtk.Label:use-underline] property to %TRUE in order + /// for the label to display them. + var label: String + /// The number of lines to which an ellipsized, wrapping label + /// should be limited. + /// + /// This property has no effect if the label is not wrapping or ellipsized. + /// Set this property to -1 if you don't want to limit the number of lines. + var lines: Int? + /// The desired maximum width of the label, in characters. + /// + /// If this property is set to -1, the width will be calculated automatically. + /// + /// See the section on [text layout](class.Label.html#text-layout) for details of how + /// [property@Gtk.Label:width-chars] and [property@Gtk.Label:max-width-chars] + /// determine the width of ellipsized and wrapped labels. + var maxWidthChars: Int? + /// The mnemonic accelerator key for the label. + var mnemonicKeyval: UInt? + /// The widget to be activated when the labels mnemonic key is pressed. + var mnemonicWidget: (() -> Body)? + /// Whether the label text can be selected with the mouse. + var selectable: Bool? + /// Whether the label is in single line mode. + /// + /// In single line mode, the height of the label does not depend on the + /// actual text, it is always set to ascent + descent of the font. This + /// can be an advantage in situations where resizing the label because + /// of text changes would be distracting, e.g. in a statusbar. + var singleLineMode: Bool? + /// %TRUE if the text of the label includes Pango markup. + /// + /// See [func@Pango.parse_markup]. + var useMarkup: Bool? + /// %TRUE if the text of the label indicates a mnemonic with an _ + /// before the mnemonic character. + var useUnderline: Bool? + /// The desired width of the label, in characters. + /// + /// If this property is set to -1, the width will be calculated automatically. + /// + /// See the section on [text layout](class.Label.html#text-layout) for details of how + /// [property@Gtk.Label:width-chars] and [property@Gtk.Label:max-width-chars] + /// determine the width of ellipsized and wrapped labels. + var widthChars: Int? + /// %TRUE if the label text will wrap if it gets too wide. + var wrap: Bool? + /// The horizontal alignment of the label text inside its size allocation. + /// + /// Compare this to [property@Gtk.Widget:halign], which determines how the + /// labels size allocation is positioned in the space available for the label. + var xalign: Float? + /// The vertical alignment of the label text inside its size allocation. + /// + /// Compare this to [property@Gtk.Widget:valign], which determines how the + /// labels size allocation is positioned in the space available for the label. + var yalign: Float? + /// Gets emitted to copy the selection to the clipboard. + /// + /// The ::copy-clipboard signal is a [keybinding signal](class.SignalAction.html). + /// + /// The default binding for this signal is Ctrl+c. + var copyClipboard: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Label`. + public init(label: String) { + self.label = label + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_label_new(label)?.opaque()) + update(storage, modifiers: modifiers) + if let mnemonicWidgetStorage = mnemonicWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["mnemonicWidget"] = [mnemonicWidgetStorage] + gtk_label_set_mnemonic_widget(storage.pointer, mnemonicWidgetStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let copyClipboard { + storage.connectSignal(name: "copy-clipboard") { + copyClipboard() + } + } + storage.modify { widget in + gtk_label_set_label(widget, label) + if let lines { + gtk_label_set_lines(widget, lines.cInt) + } + if let maxWidthChars { + gtk_label_set_max_width_chars(widget, maxWidthChars.cInt) + } + if let widget = storage.content["mnemonicWidget"]?.first { + mnemonicWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let selectable { + gtk_label_set_selectable(widget, selectable.cBool) + } + if let singleLineMode { + gtk_label_set_single_line_mode(widget, singleLineMode.cBool) + } + if let useMarkup { + gtk_label_set_use_markup(widget, useMarkup.cBool) + } + if let useUnderline { + gtk_label_set_use_underline(widget, useUnderline.cBool) + } + if let widthChars { + gtk_label_set_width_chars(widget, widthChars.cInt) + } + if let wrap { + gtk_label_set_wrap(widget, wrap.cBool) + } + if let xalign { + gtk_label_set_xalign(widget, xalign) + } + if let yalign { + gtk_label_set_yalign(widget, yalign) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The contents of the label. + /// + /// If the string contains Pango markup (see [func@Pango.parse_markup]), + /// you will have to set the [property@Gtk.Label:use-markup] property to + /// %TRUE in order for the label to display the markup attributes. See also + /// [method@Gtk.Label.set_markup] for a convenience function that sets both + /// this property and the [property@Gtk.Label:use-markup] property at the + /// same time. + /// + /// If the string contains underlines acting as mnemonics, you will have to + /// set the [property@Gtk.Label:use-underline] property to %TRUE in order + /// for the label to display them. + public func label(_ label: String) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// The number of lines to which an ellipsized, wrapping label + /// should be limited. + /// + /// This property has no effect if the label is not wrapping or ellipsized. + /// Set this property to -1 if you don't want to limit the number of lines. + public func lines(_ lines: Int?) -> Self { + var newSelf = self + newSelf.lines = lines + + return newSelf + } + + /// The desired maximum width of the label, in characters. + /// + /// If this property is set to -1, the width will be calculated automatically. + /// + /// See the section on [text layout](class.Label.html#text-layout) for details of how + /// [property@Gtk.Label:width-chars] and [property@Gtk.Label:max-width-chars] + /// determine the width of ellipsized and wrapped labels. + public func maxWidthChars(_ maxWidthChars: Int?) -> Self { + var newSelf = self + newSelf.maxWidthChars = maxWidthChars + + return newSelf + } + + /// The mnemonic accelerator key for the label. + public func mnemonicKeyval(_ mnemonicKeyval: UInt?) -> Self { + var newSelf = self + newSelf.mnemonicKeyval = mnemonicKeyval + + return newSelf + } + + /// The widget to be activated when the labels mnemonic key is pressed. + public func mnemonicWidget(@ViewBuilder _ mnemonicWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.mnemonicWidget = mnemonicWidget + + return newSelf + } + + /// Whether the label text can be selected with the mouse. + public func selectable(_ selectable: Bool? = true) -> Self { + var newSelf = self + newSelf.selectable = selectable + + return newSelf + } + + /// Whether the label is in single line mode. + /// + /// In single line mode, the height of the label does not depend on the + /// actual text, it is always set to ascent + descent of the font. This + /// can be an advantage in situations where resizing the label because + /// of text changes would be distracting, e.g. in a statusbar. + public func singleLineMode(_ singleLineMode: Bool? = true) -> Self { + var newSelf = self + newSelf.singleLineMode = singleLineMode + + return newSelf + } + + /// %TRUE if the text of the label includes Pango markup. + /// + /// See [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// %TRUE if the text of the label indicates a mnemonic with an _ + /// before the mnemonic character. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// The desired width of the label, in characters. + /// + /// If this property is set to -1, the width will be calculated automatically. + /// + /// See the section on [text layout](class.Label.html#text-layout) for details of how + /// [property@Gtk.Label:width-chars] and [property@Gtk.Label:max-width-chars] + /// determine the width of ellipsized and wrapped labels. + public func widthChars(_ widthChars: Int?) -> Self { + var newSelf = self + newSelf.widthChars = widthChars + + return newSelf + } + + /// %TRUE if the label text will wrap if it gets too wide. + public func wrap(_ wrap: Bool? = true) -> Self { + var newSelf = self + newSelf.wrap = wrap + + return newSelf + } + + /// The horizontal alignment of the label text inside its size allocation. + /// + /// Compare this to [property@Gtk.Widget:halign], which determines how the + /// labels size allocation is positioned in the space available for the label. + public func xalign(_ xalign: Float?) -> Self { + var newSelf = self + newSelf.xalign = xalign + + return newSelf + } + + /// The vertical alignment of the label text inside its size allocation. + /// + /// Compare this to [property@Gtk.Widget:valign], which determines how the + /// labels size allocation is positioned in the space available for the label. + public func yalign(_ yalign: Float?) -> Self { + var newSelf = self + newSelf.yalign = yalign + + return newSelf + } + + /// Gets emitted to copy the selection to the clipboard. + /// + /// The ::copy-clipboard signal is a [keybinding signal](class.SignalAction.html). + /// + /// The default binding for this signal is Ctrl+c. + public func copyClipboard(_ copyClipboard: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.copyClipboard = copyClipboard + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/LevelBar.swift b/Sources/Adwaita/View/Generated/LevelBar.swift new file mode 100644 index 0000000..c01513d --- /dev/null +++ b/Sources/Adwaita/View/Generated/LevelBar.swift @@ -0,0 +1,238 @@ +// +// LevelBar.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// `GtkLevelBar` is a widget that can be used as a level indicator. +/// +/// Typical use cases are displaying the strength of a password, or +/// showing the charge level of a battery. +/// +/// ![An example GtkLevelBar](levelbar.png) +/// +/// Use [method@Gtk.LevelBar.set_value] to set the current value, and +/// [method@Gtk.LevelBar.add_offset_value] to set the value offsets at which +/// the bar will be considered in a different state. GTK will add a few +/// offsets by default on the level bar: %GTK_LEVEL_BAR_OFFSET_LOW, +/// %GTK_LEVEL_BAR_OFFSET_HIGH and %GTK_LEVEL_BAR_OFFSET_FULL, with +/// values 0.25, 0.75 and 1.0 respectively. +/// +/// Note that it is your responsibility to update preexisting offsets +/// when changing the minimum or maximum value. GTK will simply clamp +/// them to the new range. +/// +/// ## Adding a custom offset on the bar +/// +/// ```c +/// static GtkWidget * +/// create_level_bar (void) +/// { +/// GtkWidget *widget; +/// GtkLevelBar *bar; +/// +/// widget = gtk_level_bar_new (); +/// bar = GTK_LEVEL_BAR (widget); +/// +/// // This changes the value of the default low offset +/// +/// gtk_level_bar_add_offset_value (bar, +/// GTK_LEVEL_BAR_OFFSET_LOW, +/// 0.10); +/// +/// // This adds a new offset to the bar; the application will +/// // be able to change its color CSS like this: +/// // +/// // levelbar block.my-offset { +/// // background-color: magenta; +/// // border-style: solid; +/// // border-color: black; +/// // border-width: 1px; +/// // } +/// +/// gtk_level_bar_add_offset_value (bar, "my-offset", 0.60); +/// +/// return widget; +/// } +/// ``` +/// +/// The default interval of values is between zero and one, but it’s possible +/// to modify the interval using [method@Gtk.LevelBar.set_min_value] and +/// [method@Gtk.LevelBar.set_max_value]. The value will be always drawn in +/// proportion to the admissible interval, i.e. a value of 15 with a specified +/// interval between 10 and 20 is equivalent to a value of 0.5 with an interval +/// between 0 and 1. When %GTK_LEVEL_BAR_MODE_DISCRETE is used, the bar level +/// is rendered as a finite number of separated blocks instead of a single one. +/// The number of blocks that will be rendered is equal to the number of units +/// specified by the admissible interval. +/// +/// For instance, to build a bar rendered with five blocks, it’s sufficient to +/// set the minimum value to 0 and the maximum value to 5 after changing the +/// indicator mode to discrete. +/// +/// # GtkLevelBar as GtkBuildable +/// +/// The `GtkLevelBar` implementation of the `GtkBuildable` interface supports a +/// custom `` element, which can contain any number of `` elements, +/// each of which must have "name" and "value" attributes. +/// +/// # CSS nodes +/// +/// ``` +/// levelbar[.discrete] +/// ╰── trough +/// ├── block.filled.level-name +/// ┊ +/// ├── block.empty +/// ┊ +/// ``` +/// +/// `GtkLevelBar` has a main CSS node with name levelbar and one of the style +/// classes .discrete or .continuous and a subnode with name trough. Below the +/// trough node are a number of nodes with name block and style class .filled +/// or .empty. In continuous mode, there is exactly one node of each, in discrete +/// mode, the number of filled and unfilled nodes corresponds to blocks that are +/// drawn. The block.filled nodes also get a style class .level-name corresponding +/// to the level for the current value. +/// +/// In horizontal orientation, the nodes are always arranged from left to right, +/// regardless of text direction. +/// +/// # Accessibility +/// +/// `GtkLevelBar` uses the %GTK_ACCESSIBLE_ROLE_METER role. +public struct LevelBar: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the `GtkLeveBar` is inverted. + /// + /// Level bars normally grow from top to bottom or left to right. + /// Inverted level bars grow in the opposite direction. + var inverted: Bool? + /// Determines the maximum value of the interval that can be displayed by the bar. + var maxValue: Double? + /// Determines the minimum value of the interval that can be displayed by the bar. + var minValue: Double? + /// Determines the currently filled value of the level bar. + var value: Double? + /// Emitted when an offset specified on the bar changes value. + /// + /// This typically is the result of a [method@Gtk.LevelBar.add_offset_value] + /// call. + /// + /// The signal supports detailed connections; you can connect to the + /// detailed signal "changed::x" in order to only receive callbacks when + /// the value of offset "x" changes. + var offsetChanged: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `LevelBar`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_level_bar_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let offsetChanged { + storage.connectSignal(name: "offset-changed") { + offsetChanged() + } + } + storage.modify { widget in + if let inverted { + gtk_level_bar_set_inverted(widget, inverted.cBool) + } + if let maxValue { + gtk_level_bar_set_max_value(widget, maxValue) + } + if let minValue { + gtk_level_bar_set_min_value(widget, minValue) + } + if let value { + gtk_level_bar_set_value(widget, value) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the `GtkLeveBar` is inverted. + /// + /// Level bars normally grow from top to bottom or left to right. + /// Inverted level bars grow in the opposite direction. + public func inverted(_ inverted: Bool? = true) -> Self { + var newSelf = self + newSelf.inverted = inverted + + return newSelf + } + + /// Determines the maximum value of the interval that can be displayed by the bar. + public func maxValue(_ maxValue: Double?) -> Self { + var newSelf = self + newSelf.maxValue = maxValue + + return newSelf + } + + /// Determines the minimum value of the interval that can be displayed by the bar. + public func minValue(_ minValue: Double?) -> Self { + var newSelf = self + newSelf.minValue = minValue + + return newSelf + } + + /// Determines the currently filled value of the level bar. + public func value(_ value: Double?) -> Self { + var newSelf = self + newSelf.value = value + + return newSelf + } + + /// Emitted when an offset specified on the bar changes value. + /// + /// This typically is the result of a [method@Gtk.LevelBar.add_offset_value] + /// call. + /// + /// The signal supports detailed connections; you can connect to the + /// detailed signal "changed::x" in order to only receive callbacks when + /// the value of offset "x" changes. + public func offsetChanged(_ offsetChanged: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.offsetChanged = offsetChanged + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/LinkButton.swift b/Sources/Adwaita/View/Generated/LinkButton.swift new file mode 100644 index 0000000..3270ba2 --- /dev/null +++ b/Sources/Adwaita/View/Generated/LinkButton.swift @@ -0,0 +1,246 @@ +// +// LinkButton.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A `GtkLinkButton` is a button with a hyperlink. +/// +/// ![An example GtkLinkButton](link-button.png) +/// +/// It is useful to show quick links to resources. +/// +/// A link button is created by calling either [ctor@Gtk.LinkButton.new] or +/// [ctor@Gtk.LinkButton.new_with_label]. If using the former, the URI you +/// pass to the constructor is used as a label for the widget. +/// +/// The URI bound to a `GtkLinkButton` can be set specifically using +/// [method@Gtk.LinkButton.set_uri]. +/// +/// By default, `GtkLinkButton` calls [method@Gtk.FileLauncher.launch] when the button +/// is clicked. This behaviour can be overridden by connecting to the +/// [signal@Gtk.LinkButton::activate-link] signal and returning %TRUE from +/// the signal handler. +/// +/// # CSS nodes +/// +/// `GtkLinkButton` has a single CSS node with name button. To differentiate +/// it from a plain `GtkButton`, it gets the .link style class. +/// +/// # Accessibility +/// +/// `GtkLinkButton` uses the %GTK_ACCESSIBLE_ROLE_LINK role. +public struct LinkButton: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The URI bound to this button. + var uri: String + /// The 'visited' state of this button. + /// + /// A visited link is drawn in a different color. + var visited: Bool? + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + /// + /// For text buttons, setting this property will allow ellipsizing the label. + /// + /// If the contents of a button are an icon or a custom widget, setting this + /// property has no effect. + var canShrink: Bool? + /// The child widget. + var child: (() -> Body)? + /// Whether the button has a frame. + var hasFrame: Bool? + /// The name of the icon used to automatically populate the button. + var iconName: String? + /// Text of the label inside the button, if the button contains a label widget. + var label: String? + /// If set, an underline in the text indicates that the following character is + /// to be used as mnemonic. + var useUnderline: Bool? + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect + /// to this signal, but use the [signal@Gtk.Button::clicked] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + var activate: (() -> Void)? + /// Emitted when the button has been activated (pressed and released). + var clicked: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `LinkButton`. + public init(uri: String) { + self.uri = uri + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_link_button_new(uri)?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_button_set_child(storage.pointer?.cast(), childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activate { + storage.connectSignal(name: "activate") { + activate() + } + } + if let clicked { + storage.connectSignal(name: "clicked") { + clicked() + } + } + storage.modify { widget in + gtk_link_button_set_uri(widget, uri) + if let visited { + gtk_link_button_set_visited(widget, visited.cBool) + } + if let canShrink { + gtk_button_set_can_shrink(widget?.cast(), canShrink.cBool) + } + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let hasFrame { + gtk_button_set_has_frame(widget?.cast(), hasFrame.cBool) + } + if let iconName { + gtk_button_set_icon_name(widget?.cast(), iconName) + } + if let label, storage.content["child"] == nil { + gtk_button_set_label(widget?.cast(), label) + } + if let useUnderline { + gtk_button_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The URI bound to this button. + public func uri(_ uri: String) -> Self { + var newSelf = self + newSelf.uri = uri + + return newSelf + } + + /// The 'visited' state of this button. + /// + /// A visited link is drawn in a different color. + public func visited(_ visited: Bool? = true) -> Self { + var newSelf = self + newSelf.visited = visited + + return newSelf + } + + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + /// + /// For text buttons, setting this property will allow ellipsizing the label. + /// + /// If the contents of a button are an icon or a custom widget, setting this + /// property has no effect. + public func canShrink(_ canShrink: Bool? = true) -> Self { + var newSelf = self + newSelf.canShrink = canShrink + + return newSelf + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// Whether the button has a frame. + public func hasFrame(_ hasFrame: Bool? = true) -> Self { + var newSelf = self + newSelf.hasFrame = hasFrame + + return newSelf + } + + /// The name of the icon used to automatically populate the button. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// Text of the label inside the button, if the button contains a label widget. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// If set, an underline in the text indicates that the following character is + /// to be used as mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect + /// to this signal, but use the [signal@Gtk.Button::clicked] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + public func activate(_ activate: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activate = activate + return newSelf + } + + /// Emitted when the button has been activated (pressed and released). + public func clicked(_ clicked: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.clicked = clicked + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ListBox.swift b/Sources/Adwaita/View/Generated/ListBox.swift new file mode 100644 index 0000000..ecb6a83 --- /dev/null +++ b/Sources/Adwaita/View/Generated/ListBox.swift @@ -0,0 +1,319 @@ +// +// ListBox.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// `GtkListBox` is a vertical list. +/// +/// A `GtkListBox` only contains `GtkListBoxRow` children. These rows can +/// by dynamically sorted and filtered, and headers can be added dynamically +/// depending on the row content. It also allows keyboard and mouse navigation +/// and selection like a typical list. +/// +/// Using `GtkListBox` is often an alternative to `GtkTreeView`, especially +/// when the list contents has a more complicated layout than what is allowed +/// by a `GtkCellRenderer`, or when the contents is interactive (i.e. has a +/// button in it). +/// +/// Although a `GtkListBox` must have only `GtkListBoxRow` children, you can +/// add any kind of widget to it via [method@Gtk.ListBox.prepend], +/// [method@Gtk.ListBox.append] and [method@Gtk.ListBox.insert] and a +/// `GtkListBoxRow` widget will automatically be inserted between the list +/// and the widget. +/// +/// `GtkListBoxRows` can be marked as activatable or selectable. If a row is +/// activatable, [signal@Gtk.ListBox::row-activated] will be emitted for it when +/// the user tries to activate it. If it is selectable, the row will be marked +/// as selected when the user tries to select it. +/// +/// # GtkListBox as GtkBuildable +/// +/// The `GtkListBox` implementation of the `GtkBuildable` interface supports +/// setting a child as the placeholder by specifying “placeholder” as the “type” +/// attribute of a `` element. See [method@Gtk.ListBox.set_placeholder] +/// for info. +/// +/// # CSS nodes +/// +/// |[ +/// list[.separators][.rich-list][.navigation-sidebar][.boxed-list] +/// ╰── row[.activatable] +/// ]| +/// +/// `GtkListBox` uses a single CSS node named list. It may carry the .separators +/// style class, when the [property@Gtk.ListBox:show-separators] property is set. +/// Each `GtkListBoxRow` uses a single CSS node named row. The row nodes get the +/// .activatable style class added when appropriate. +/// +/// It may also carry the .boxed-list style class. In this case, the list will be +/// automatically surrounded by a frame and have separators. +/// +/// The main list node may also carry style classes to select +/// the style of [list presentation](section-list-widget.html#list-styles): +/// .rich-list, .navigation-sidebar or .data-table. +/// +/// # Accessibility +/// +/// `GtkListBox` uses the %GTK_ACCESSIBLE_ROLE_LIST role and `GtkListBoxRow` uses +/// the %GTK_ACCESSIBLE_ROLE_LIST_ITEM role. +public struct ListBox: Widget where Element: Identifiable { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether to accept unpaired release events. + var acceptUnpairedRelease: Bool? + /// Determines whether children can be activated with a single + /// click, or require a double-click. + var activateOnSingleClick: Bool? + /// Whether to show separators between rows. + var showSeparators: Bool? + /// activateCursorRow + var activateCursorRow: (() -> Void)? + /// moveCursor + var moveCursor: (() -> Void)? + /// Emitted when a row has been activated by the user. + var rowActivated: (() -> Void)? + /// Emitted when a new row is selected, or (with a %NULL @row) + /// when the selection is cleared. + /// + /// When the @box is using %GTK_SELECTION_MULTIPLE, this signal will not + /// give you the full picture of selection changes, and you should use + /// the [signal@Gtk.ListBox::selected-rows-changed] signal instead. + var rowSelected: (() -> Void)? + /// Emitted to select all children of the box, if the selection + /// mode permits it. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The default binding for this signal is Ctrl-a. + var selectAll: (() -> Void)? + /// Emitted when the set of selected rows changes. + var selectedRowsChanged: (() -> Void)? + /// toggleCursorRow + var toggleCursorRow: (() -> Void)? + /// Emitted to unselect all children of the box, if the selection + /// mode permits it. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The default binding for this signal is + /// Ctrl-Shift-a. + var unselectAll: (() -> Void)? + /// The dynamic widget elements. + var elements: [Element] + /// The dynamic widget content. + var content: (Element) -> Body + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ListBox`. + public init(_ elements: [Element], @ViewBuilder content: @escaping (Element) -> Body) { + self.elements = elements + self.content = content + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_list_box_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activateCursorRow { + storage.connectSignal(name: "activate-cursor-row") { + activateCursorRow() + } + } + if let moveCursor { + storage.connectSignal(name: "move-cursor") { + moveCursor() + } + } + if let rowActivated { + storage.connectSignal(name: "row-activated") { + rowActivated() + } + } + if let rowSelected { + storage.connectSignal(name: "row-selected") { + rowSelected() + } + } + if let selectAll { + storage.connectSignal(name: "select-all") { + selectAll() + } + } + if let selectedRowsChanged { + storage.connectSignal(name: "selected-rows-changed") { + selectedRowsChanged() + } + } + if let toggleCursorRow { + storage.connectSignal(name: "toggle-cursor-row") { + toggleCursorRow() + } + } + if let unselectAll { + storage.connectSignal(name: "unselect-all") { + unselectAll() + } + } + storage.modify { widget in + if let activateOnSingleClick { + gtk_list_box_set_activate_on_single_click(widget, activateOnSingleClick.cBool) + } + if let showSeparators { + gtk_list_box_set_show_separators(widget, showSeparators.cBool) + } + + var contentStorage: [ViewStorage] = storage.content[.mainContent] ?? [] + let old = storage.fields["element"] as? [Element] ?? [] + old.identifiableTransform( + to: elements, + functions: .init { index, element in + let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers) + gtk_list_box_remove(widget, gtk_list_box_get_row_at_index(widget, index.cInt)?.cast()) + gtk_list_box_insert(widget, child.pointer?.cast(), index.cInt) + contentStorage.remove(at: index) + contentStorage.insert(child, at: index) + } delete: { index in + gtk_list_box_remove(widget, gtk_list_box_get_row_at_index(widget, index.cInt)?.cast()) + contentStorage.remove(at: index) + } insert: { index, element in + let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers) + gtk_list_box_insert(widget, child.pointer?.cast(), index.cInt) + contentStorage.insert(child, at: index) + } + ) + storage.fields["element"] = elements + storage.content[.mainContent] = contentStorage + for (index, element) in elements.enumerated() { + content(element).widget(modifiers: modifiers).update(contentStorage[index], modifiers: modifiers) + } + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether to accept unpaired release events. + public func acceptUnpairedRelease(_ acceptUnpairedRelease: Bool? = true) -> Self { + var newSelf = self + newSelf.acceptUnpairedRelease = acceptUnpairedRelease + + return newSelf + } + + /// Determines whether children can be activated with a single + /// click, or require a double-click. + public func activateOnSingleClick(_ activateOnSingleClick: Bool? = true) -> Self { + var newSelf = self + newSelf.activateOnSingleClick = activateOnSingleClick + + return newSelf + } + + /// Whether to show separators between rows. + public func showSeparators(_ showSeparators: Bool? = true) -> Self { + var newSelf = self + newSelf.showSeparators = showSeparators + + return newSelf + } + + /// activateCursorRow + public func activateCursorRow(_ activateCursorRow: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activateCursorRow = activateCursorRow + return newSelf + } + + /// moveCursor + public func moveCursor(_ moveCursor: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.moveCursor = moveCursor + return newSelf + } + + /// Emitted when a row has been activated by the user. + public func rowActivated(_ rowActivated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.rowActivated = rowActivated + return newSelf + } + + /// Emitted when a new row is selected, or (with a %NULL @row) + /// when the selection is cleared. + /// + /// When the @box is using %GTK_SELECTION_MULTIPLE, this signal will not + /// give you the full picture of selection changes, and you should use + /// the [signal@Gtk.ListBox::selected-rows-changed] signal instead. + public func rowSelected(_ rowSelected: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.rowSelected = rowSelected + return newSelf + } + + /// Emitted to select all children of the box, if the selection + /// mode permits it. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The default binding for this signal is Ctrl-a. + public func selectAll(_ selectAll: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.selectAll = selectAll + return newSelf + } + + /// Emitted when the set of selected rows changes. + public func selectedRowsChanged(_ selectedRowsChanged: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.selectedRowsChanged = selectedRowsChanged + return newSelf + } + + /// toggleCursorRow + public func toggleCursorRow(_ toggleCursorRow: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.toggleCursorRow = toggleCursorRow + return newSelf + } + + /// Emitted to unselect all children of the box, if the selection + /// mode permits it. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The default binding for this signal is + /// Ctrl-Shift-a. + public func unselectAll(_ unselectAll: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.unselectAll = unselectAll + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Menu.swift b/Sources/Adwaita/View/Generated/Menu.swift new file mode 100644 index 0000000..12cc597 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Menu.swift @@ -0,0 +1,289 @@ +// +// Menu.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// The `GtkMenuButton` widget is used to display a popup when clicked. +/// +/// ![An example GtkMenuButton](menu-button.png) +/// +/// This popup can be provided either as a `GtkPopover` or as an abstract +/// `GMenuModel`. +/// +/// The `GtkMenuButton` widget can show either an icon (set with the +/// [property@Gtk.MenuButton:icon-name] property) or a label (set with the +/// [property@Gtk.MenuButton:label] property). If neither is explicitly set, +/// a [class@Gtk.Image] is automatically created, using an arrow image oriented +/// according to [property@Gtk.MenuButton:direction] or the generic +/// “open-menu-symbolic” icon if the direction is not set. +/// +/// The positioning of the popup is determined by the +/// [property@Gtk.MenuButton:direction] property of the menu button. +/// +/// For menus, the [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign] +/// properties of the menu are also taken into account. For example, when the +/// direction is %GTK_ARROW_DOWN and the horizontal alignment is %GTK_ALIGN_START, +/// the menu will be positioned below the button, with the starting edge +/// (depending on the text direction) of the menu aligned with the starting +/// edge of the button. If there is not enough space below the button, the +/// menu is popped up above the button instead. If the alignment would move +/// part of the menu offscreen, it is “pushed in”. +/// +/// | | start | center | end | +/// | - | --- | --- | --- | +/// | **down** | ![](down-start.png) | ![](down-center.png) | ![](down-end.png) | +/// | **up** | ![](up-start.png) | ![](up-center.png) | ![](up-end.png) | +/// | **left** | ![](left-start.png) | ![](left-center.png) | ![](left-end.png) | +/// | **right** | ![](right-start.png) | ![](right-center.png) | ![](right-end.png) | +/// +/// # CSS nodes +/// +/// ``` +/// menubutton +/// ╰── button.toggle +/// ╰── ╰── [arrow] +/// ``` +/// +/// `GtkMenuButton` has a single CSS node with name `menubutton` +/// which contains a `button` node with a `.toggle` style class. +/// +/// If the button contains an icon, it will have the `.image-button` style class, +/// if it contains text, it will have `.text-button` style class. If an arrow is +/// visible in addition to an icon, text or a custom child, it will also have +/// `.arrow-button` style class. +/// +/// Inside the toggle button content, there is an `arrow` node for +/// the indicator, which will carry one of the `.none`, `.up`, `.down`, +/// `.left` or `.right` style classes to indicate the direction that +/// the menu will appear in. The CSS is expected to provide a suitable +/// image for each of these cases using the `-gtk-icon-source` property. +/// +/// Optionally, the `menubutton` node can carry the `.circular` style class +/// to request a round appearance. +/// +/// # Accessibility +/// +/// `GtkMenuButton` uses the %GTK_ACCESSIBLE_ROLE_BUTTON role. +public struct Menu: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the menu button is active. + var active: Binding? + /// Whether to show a dropdown arrow even when using an icon or a custom child. + var alwaysShowArrow: Bool? + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + var canShrink: Bool? + /// The child widget. + var child: (() -> Body)? + /// Whether the button has a frame. + var hasFrame: Bool? + /// The name of the icon used to automatically populate the button. + var iconName: String? + /// The label for the button. + var label: String? + /// The `GMenuModel` from which the popup will be created. + /// + /// See [method@Gtk.MenuButton.set_menu_model] for the interaction + /// with the [property@Gtk.MenuButton:popover] property. + var menuModel: (() -> MenuContent)? + /// Whether the menu button acts as a primary menu. + /// + /// Primary menus can be opened using the F10 key + var primary: Bool? + /// If set an underscore in the text indicates a mnemonic. + var useUnderline: Bool? + /// Emitted to when the menu button is activated. + /// + /// The `::activate` signal on `GtkMenuButton` is an action signal and + /// emitting it causes the button to pop up its menu. + var activate: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Menu`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_menu_button_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_menu_button_set_child(storage.pointer, childStorage.pointer?.cast()) + } + if let declarative = menuModel?(), let app { + let menu = g_menu_new() + gtk_menu_button_set_menu_model(storage.pointer, menu?.cast()) + for item in declarative { + item.addMenuItems(menu: menu, app: app, window: window) + } + } + + + storage.notify(name: "active") { + active?.wrappedValue = gtk_menu_button_get_active(storage.pointer) != 0 + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activate { + storage.connectSignal(name: "activate") { + activate() + } + } + storage.modify { widget in + if let active { + gtk_menu_button_set_active(widget, active.wrappedValue.cBool) + } + if let alwaysShowArrow { + gtk_menu_button_set_always_show_arrow(widget, alwaysShowArrow.cBool) + } + if let canShrink { + gtk_menu_button_set_can_shrink(widget, canShrink.cBool) + } + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let hasFrame { + gtk_menu_button_set_has_frame(widget, hasFrame.cBool) + } + if let iconName { + gtk_menu_button_set_icon_name(widget, iconName) + } + if let label, storage.content["child"] == nil { + gtk_menu_button_set_label(widget, label) + } + if let primary { + gtk_menu_button_set_primary(widget, primary.cBool) + } + if let useUnderline { + gtk_menu_button_set_use_underline(widget, useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the menu button is active. + public func active(_ active: Binding?) -> Self { + var newSelf = self + newSelf.active = active + + return newSelf + } + + /// Whether to show a dropdown arrow even when using an icon or a custom child. + public func alwaysShowArrow(_ alwaysShowArrow: Bool? = true) -> Self { + var newSelf = self + newSelf.alwaysShowArrow = alwaysShowArrow + + return newSelf + } + + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + public func canShrink(_ canShrink: Bool? = true) -> Self { + var newSelf = self + newSelf.canShrink = canShrink + + return newSelf + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// Whether the button has a frame. + public func hasFrame(_ hasFrame: Bool? = true) -> Self { + var newSelf = self + newSelf.hasFrame = hasFrame + + return newSelf + } + + /// The name of the icon used to automatically populate the button. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The label for the button. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// The `GMenuModel` from which the popup will be created. + /// + /// See [method@Gtk.MenuButton.set_menu_model] for the interaction + /// with the [property@Gtk.MenuButton:popover] property. + public func menuModel(app: GTUIApp, window: GTUIApplicationWindow? = nil, @MenuBuilder _ menuModel: @escaping (() -> MenuContent)) -> Self { + var newSelf = self + newSelf.menuModel = menuModel + newSelf.app = app; newSelf.window = window + return newSelf + } + + /// Whether the menu button acts as a primary menu. + /// + /// Primary menus can be opened using the F10 key + public func primary(_ primary: Bool? = true) -> Self { + var newSelf = self + newSelf.primary = primary + + return newSelf + } + + /// If set an underscore in the text indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted to when the menu button is activated. + /// + /// The `::activate` signal on `GtkMenuButton` is an action signal and + /// emitting it causes the button to pop up its menu. + public func activate(_ activate: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activate = activate + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Overlay.swift b/Sources/Adwaita/View/Generated/Overlay.swift new file mode 100644 index 0000000..a6ed828 --- /dev/null +++ b/Sources/Adwaita/View/Generated/Overlay.swift @@ -0,0 +1,167 @@ +// +// Overlay.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// `GtkOverlay` is a container which contains a single main child, on top +/// of which it can place “overlay” widgets. +/// +/// ![An example GtkOverlay](overlay.png) +/// +/// The position of each overlay widget is determined by its +/// [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign] +/// properties. E.g. a widget with both alignments set to %GTK_ALIGN_START +/// will be placed at the top left corner of the `GtkOverlay` container, +/// whereas an overlay with halign set to %GTK_ALIGN_CENTER and valign set +/// to %GTK_ALIGN_END will be placed a the bottom edge of the `GtkOverlay`, +/// horizontally centered. The position can be adjusted by setting the margin +/// properties of the child to non-zero values. +/// +/// More complicated placement of overlays is possible by connecting +/// to the [signal@Gtk.Overlay::get-child-position] signal. +/// +/// An overlay’s minimum and natural sizes are those of its main child. +/// The sizes of overlay children are not considered when measuring these +/// preferred sizes. +/// +/// # GtkOverlay as GtkBuildable +/// +/// The `GtkOverlay` implementation of the `GtkBuildable` interface +/// supports placing a child as an overlay by specifying “overlay” as +/// the “type” attribute of a `` element. +/// +/// # CSS nodes +/// +/// `GtkOverlay` has a single CSS node with the name “overlay”. Overlay children +/// whose alignments cause them to be positioned at an edge get the style classes +/// “.left”, “.right”, “.top”, and/or “.bottom” according to their position. +public struct Overlay: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The main child widget. + var child: (() -> Body)? + /// Emitted to determine the position and size of any overlay + /// child widgets. + /// + /// A handler for this signal should fill @allocation with + /// the desired position and size for @widget, relative to + /// the 'main' child of @overlay. + /// + /// The default handler for this signal uses the @widget's + /// halign and valign properties to determine the position + /// and gives the widget its natural size (except that an + /// alignment of %GTK_ALIGN_FILL will cause the overlay to + /// be full-width/height). If the main child is a + /// `GtkScrolledWindow`, the overlays are placed relative + /// to its contents. + var getChildPosition: (() -> Void)? + /// The body for the widget "overlay". + var overlay: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Overlay`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_overlay_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_overlay_set_child(storage.pointer, childStorage.pointer?.cast()) + } + + var overlayStorage: [ViewStorage] = [] + for view in overlay() { + overlayStorage.append(view.storage(modifiers: modifiers)) + gtk_overlay_add_overlay(storage.pointer, overlayStorage.last?.pointer?.cast()) + } + storage.content["overlay"] = overlayStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let getChildPosition { + storage.connectSignal(name: "get-child-position") { + getChildPosition() + } + } + storage.modify { widget in + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + + if let overlayStorage = storage.content["overlay"] { + for (index, view) in overlay().enumerated() { + if let storage = overlayStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The main child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// Emitted to determine the position and size of any overlay + /// child widgets. + /// + /// A handler for this signal should fill @allocation with + /// the desired position and size for @widget, relative to + /// the 'main' child of @overlay. + /// + /// The default handler for this signal uses the @widget's + /// halign and valign properties to determine the position + /// and gives the widget its natural size (except that an + /// alignment of %GTK_ALIGN_FILL will cause the overlay to + /// be full-width/height). If the main child is a + /// `GtkScrolledWindow`, the overlays are placed relative + /// to its contents. + public func getChildPosition(_ getChildPosition: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.getChildPosition = getChildPosition + return newSelf + } + + /// Set the body for "overlay". + /// - Parameter body: The body. + /// - Returns: The widget. + public func overlay(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.overlay = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/OverlaySplitView.swift b/Sources/Adwaita/View/Generated/OverlaySplitView.swift new file mode 100644 index 0000000..8ec0a8a --- /dev/null +++ b/Sources/Adwaita/View/Generated/OverlaySplitView.swift @@ -0,0 +1,361 @@ +// +// OverlaySplitView.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A widget presenting sidebar and content side by side or as an overlay. +/// +/// overlay-split-viewoverlay-split-view-collapsed +/// +/// `AdwOverlaySplitView` has two children: sidebar and content, and displays +/// them side by side. +/// +/// When [property@OverlaySplitView:collapsed] is set to `TRUE`, the sidebar is +/// instead shown as an overlay above the content widget. +/// +/// The sidebar can be hidden or shown using the +/// [property@OverlaySplitView:show-sidebar] property. +/// +/// Sidebar can be displayed before or after the content, this can be controlled +/// with the [property@OverlaySplitView:sidebar-position] property. +/// +/// Collapsing the split view automatically hides the sidebar widget, and +/// uncollapsing it shows the sidebar. If this behavior is not desired, the +/// [property@OverlaySplitView:pin-sidebar] property can be used to override it. +/// +/// `AdwOverlaySplitView` supports an edge swipe gesture for showing the sidebar, +/// and a swipe from the sidebar for hiding it. Gestures are only supported on +/// touchscreen, but not touchpad. Gestures can be controlled with the +/// [property@OverlaySplitView:enable-show-gesture] and +/// [property@OverlaySplitView:enable-hide-gesture] properties. +/// +/// See also [class@NavigationSplitView]. +/// +/// `AdwOverlaySplitView` is typically used together with an [class@Breakpoint] +/// setting the `collapsed` property to `TRUE` on small widths, as follows: +/// +/// ```xml +/// 360200800800max-width: 400spTrue +/// ``` +/// +/// `AdwOverlaySplitView` is often used for implementing the +/// [utility pane](https://developer.gnome.org/hig/patterns/containers/utility-panes.html) +/// pattern. +/// +/// ## Sizing +/// +/// When not collapsed, `AdwOverlaySplitView` changes the sidebar width +/// depending on its own width. +/// +/// If possible, it tries to allocate a fraction of the total width, controlled +/// with the [property@OverlaySplitView:sidebar-width-fraction] property. +/// +/// The sidebar also has minimum and maximum sizes, controlled with the +/// [property@OverlaySplitView:min-sidebar-width] and +/// [property@OverlaySplitView:max-sidebar-width] properties. +/// +/// The minimum and maximum sizes are using the length unit specified with the +/// [property@OverlaySplitView:sidebar-width-unit]. +/// +/// By default, sidebar is using 25% of the total width, with 180sp as the +/// minimum size and 280sp as the maximum size. +/// +/// When collapsed, the preferred width fraction is ignored and the sidebar uses +/// [property@OverlaySplitView:max-sidebar-width] when possible. +/// +/// ## Header Bar Integration +/// +/// When used inside `AdwOverlaySplitView`, [class@HeaderBar] will automatically +/// hide the window buttons in the middle. +/// +/// ## `AdwOverlaySplitView` as `GtkBuildable` +/// +/// The `AdwOverlaySplitView` implementation of the [iface@Gtk.Buildable] +/// interface supports setting the sidebar widget by specifying “sidebar” as the +/// “type” attribute of a `` element, Specifying “content” child type or +/// omitting it results in setting the content widget. +/// +/// ## CSS nodes +/// +/// `AdwOverlaySplitView` has a single CSS node with the name +/// `overlay-split-view`. +/// +/// It contains two nodes with the name `widget`, containing the sidebar and +/// content children. +/// +/// When not collapsed, they have the `.sidebar-view` and `.content-view` style +/// classes respectively. +/// +/// ``` +/// overlay-split-view +/// ├── widget.sidebar-pane +/// │ ╰── [sidebar child] +/// ╰── widget.content-pane +/// ╰── [content child] +/// ``` +/// +/// When collapsed, the one containing the sidebar child has the `.background` +/// style class and the other one has no style classes. +/// +/// ``` +/// overlay-split-view +/// ├── widget.background +/// │ ╰── [sidebar child] +/// ╰── widget +/// ╰── [content child] +/// ``` +/// +/// ## Accessibility +/// +/// `AdwOverlaySplitView` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct OverlaySplitView: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the split view is collapsed. + /// + /// When collapsed, the sidebar widget is presented as an overlay above the + /// content widget, otherwise they are displayed side by side. + var collapsed: Bool? + /// The content widget. + var content: (() -> Body)? + /// Whether the sidebar can be closed with a swipe gesture. + /// + /// Only touchscreen swipes are supported. + var enableHideGesture: Bool? + /// Whether the sidebar can be opened with an edge swipe gesture. + /// + /// Only touchscreen swipes are supported. + var enableShowGesture: Bool? + /// The maximum sidebar width. + /// + /// Maximum width is affected by + /// [property@OverlaySplitView:sidebar-width-unit]. + /// + /// The sidebar widget can still be allocated with larger width if its own + /// minimum width exceeds it. + var maxSidebarWidth: Double? + /// The minimum sidebar width. + /// + /// Minimum width is affected by + /// [property@OverlaySplitView:sidebar-width-unit]. + /// + /// The sidebar widget can still be allocated with larger width if its own + /// minimum width exceeds it. + var minSidebarWidth: Double? + /// Whether the sidebar widget is pinned. + /// + /// By default, collapsing @self automatically hides the sidebar widget, and + /// uncollapsing it shows the sidebar. If set to `TRUE`, sidebar visibility + /// never changes on its own. + var pinSidebar: Bool? + /// Whether the sidebar widget is shown. + var showSidebar: Binding? + /// The sidebar widget. + var sidebar: (() -> Body)? + /// The preferred sidebar width as a fraction of the total width. + /// + /// The preferred width is additionally limited by + /// [property@OverlaySplitView:min-sidebar-width] and + /// [property@OverlaySplitView:max-sidebar-width]. + /// + /// The sidebar widget can be allocated with larger width if its own minimum + /// width exceeds the preferred width. + var sidebarWidthFraction: Double? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `OverlaySplitView`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_overlay_split_view_new()?.opaque()) + update(storage, modifiers: modifiers) + if let contentStorage = content?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["content"] = [contentStorage] + adw_overlay_split_view_set_content(storage.pointer, contentStorage.pointer?.cast()) + } + if let sidebarStorage = sidebar?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["sidebar"] = [sidebarStorage] + adw_overlay_split_view_set_sidebar(storage.pointer, sidebarStorage.pointer?.cast()) + } + + + storage.notify(name: "show-sidebar") { + showSidebar?.wrappedValue = adw_overlay_split_view_get_show_sidebar(storage.pointer) != 0 + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let collapsed { + adw_overlay_split_view_set_collapsed(widget, collapsed.cBool) + } + if let widget = storage.content["content"]?.first { + content?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let enableHideGesture { + adw_overlay_split_view_set_enable_hide_gesture(widget, enableHideGesture.cBool) + } + if let enableShowGesture { + adw_overlay_split_view_set_enable_show_gesture(widget, enableShowGesture.cBool) + } + if let maxSidebarWidth { + adw_overlay_split_view_set_max_sidebar_width(widget, maxSidebarWidth) + } + if let minSidebarWidth { + adw_overlay_split_view_set_min_sidebar_width(widget, minSidebarWidth) + } + if let pinSidebar { + adw_overlay_split_view_set_pin_sidebar(widget, pinSidebar.cBool) + } + if let showSidebar { + adw_overlay_split_view_set_show_sidebar(widget, showSidebar.wrappedValue.cBool) + } + if let widget = storage.content["sidebar"]?.first { + sidebar?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let sidebarWidthFraction { + adw_overlay_split_view_set_sidebar_width_fraction(widget, sidebarWidthFraction) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the split view is collapsed. + /// + /// When collapsed, the sidebar widget is presented as an overlay above the + /// content widget, otherwise they are displayed side by side. + public func collapsed(_ collapsed: Bool? = true) -> Self { + var newSelf = self + newSelf.collapsed = collapsed + + return newSelf + } + + /// The content widget. + public func content(@ViewBuilder _ content: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.content = content + + return newSelf + } + + /// Whether the sidebar can be closed with a swipe gesture. + /// + /// Only touchscreen swipes are supported. + public func enableHideGesture(_ enableHideGesture: Bool? = true) -> Self { + var newSelf = self + newSelf.enableHideGesture = enableHideGesture + + return newSelf + } + + /// Whether the sidebar can be opened with an edge swipe gesture. + /// + /// Only touchscreen swipes are supported. + public func enableShowGesture(_ enableShowGesture: Bool? = true) -> Self { + var newSelf = self + newSelf.enableShowGesture = enableShowGesture + + return newSelf + } + + /// The maximum sidebar width. + /// + /// Maximum width is affected by + /// [property@OverlaySplitView:sidebar-width-unit]. + /// + /// The sidebar widget can still be allocated with larger width if its own + /// minimum width exceeds it. + public func maxSidebarWidth(_ maxSidebarWidth: Double?) -> Self { + var newSelf = self + newSelf.maxSidebarWidth = maxSidebarWidth + + return newSelf + } + + /// The minimum sidebar width. + /// + /// Minimum width is affected by + /// [property@OverlaySplitView:sidebar-width-unit]. + /// + /// The sidebar widget can still be allocated with larger width if its own + /// minimum width exceeds it. + public func minSidebarWidth(_ minSidebarWidth: Double?) -> Self { + var newSelf = self + newSelf.minSidebarWidth = minSidebarWidth + + return newSelf + } + + /// Whether the sidebar widget is pinned. + /// + /// By default, collapsing @self automatically hides the sidebar widget, and + /// uncollapsing it shows the sidebar. If set to `TRUE`, sidebar visibility + /// never changes on its own. + public func pinSidebar(_ pinSidebar: Bool? = true) -> Self { + var newSelf = self + newSelf.pinSidebar = pinSidebar + + return newSelf + } + + /// Whether the sidebar widget is shown. + public func showSidebar(_ showSidebar: Binding?) -> Self { + var newSelf = self + newSelf.showSidebar = showSidebar + + return newSelf + } + + /// The sidebar widget. + public func sidebar(@ViewBuilder _ sidebar: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.sidebar = sidebar + + return newSelf + } + + /// The preferred sidebar width as a fraction of the total width. + /// + /// The preferred width is additionally limited by + /// [property@OverlaySplitView:min-sidebar-width] and + /// [property@OverlaySplitView:max-sidebar-width]. + /// + /// The sidebar widget can be allocated with larger width if its own minimum + /// width exceeds the preferred width. + public func sidebarWidthFraction(_ sidebarWidthFraction: Double?) -> Self { + var newSelf = self + newSelf.sidebarWidthFraction = sidebarWidthFraction + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/PasswordEntryRow.swift b/Sources/Adwaita/View/Generated/PasswordEntryRow.swift new file mode 100644 index 0000000..ce96051 --- /dev/null +++ b/Sources/Adwaita/View/Generated/PasswordEntryRow.swift @@ -0,0 +1,248 @@ +// +// PasswordEntryRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@EntryRow] tailored for entering secrets. +/// +/// password-entry-row +/// +/// It does not show its contents in clear text, does not allow to copy it to the +/// clipboard, and shows a warning when Caps Lock is engaged. If the underlying +/// platform allows it, `AdwPasswordEntryRow` will also place the text in a +/// non-pageable memory area, to avoid it being written out to disk by the +/// operating system. +/// +/// It offer a way to reveal the contents in clear text. +/// +/// ## CSS Nodes +/// +/// `AdwPasswordEntryRow` has a single CSS node with name `row` that carries +/// `.entry` and `.password` style classes. +public struct PasswordEntryRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether activating the embedded entry can activate the default widget. + var activatesDefault: Bool? + /// Whether to suggest emoji replacements on the entry row. + /// + /// Emoji replacement is done with :-delimited names, like `:heart:`. + var enableEmojiCompletion: Bool? + /// Whether to show the apply button. + /// + /// When set to `TRUE`, typing text in the entry will reveal an apply button. + /// Clicking it or pressing the Enter key will hide the button and + /// emit the [signal@EntryRow::apply] signal. + /// + /// This is useful if changing the entry contents can trigger an expensive + /// operation, e.g. network activity, to avoid triggering it after typing every + /// character. + var showApplyButton: Bool? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// Emitted when the apply button is pressed. + /// + /// See [property@EntryRow:show-apply-button]. + var apply: (() -> Void)? + /// Emitted when the embedded entry is activated. + var entryActivated: (() -> Void)? + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `PasswordEntryRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_password_entry_row_new()?.opaque()) + update(storage, modifiers: modifiers) + + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_entry_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_entry_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let apply { + storage.connectSignal(name: "apply") { + apply() + } + } + if let entryActivated { + storage.connectSignal(name: "entry-activated") { + entryActivated() + } + } + storage.modify { widget in + if let activatesDefault { + adw_entry_row_set_activates_default(widget?.cast(), activatesDefault.cBool) + } + if let enableEmojiCompletion { + adw_entry_row_set_enable_emoji_completion(widget?.cast(), enableEmojiCompletion.cBool) + } + if let showApplyButton { + adw_entry_row_set_show_apply_button(widget?.cast(), showApplyButton.cBool) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether activating the embedded entry can activate the default widget. + public func activatesDefault(_ activatesDefault: Bool? = true) -> Self { + var newSelf = self + newSelf.activatesDefault = activatesDefault + + return newSelf + } + + /// Whether to suggest emoji replacements on the entry row. + /// + /// Emoji replacement is done with :-delimited names, like `:heart:`. + public func enableEmojiCompletion(_ enableEmojiCompletion: Bool? = true) -> Self { + var newSelf = self + newSelf.enableEmojiCompletion = enableEmojiCompletion + + return newSelf + } + + /// Whether to show the apply button. + /// + /// When set to `TRUE`, typing text in the entry will reveal an apply button. + /// Clicking it or pressing the Enter key will hide the button and + /// emit the [signal@EntryRow::apply] signal. + /// + /// This is useful if changing the entry contents can trigger an expensive + /// operation, e.g. network activity, to avoid triggering it after typing every + /// character. + public func showApplyButton(_ showApplyButton: Bool? = true) -> Self { + var newSelf = self + newSelf.showApplyButton = showApplyButton + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted when the apply button is pressed. + /// + /// See [property@EntryRow:show-apply-button]. + public func apply(_ apply: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.apply = apply + return newSelf + } + + /// Emitted when the embedded entry is activated. + public func entryActivated(_ entryActivated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.entryActivated = entryActivated + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/PreferencesGroup.swift b/Sources/Adwaita/View/Generated/PreferencesGroup.swift new file mode 100644 index 0000000..0f50d71 --- /dev/null +++ b/Sources/Adwaita/View/Generated/PreferencesGroup.swift @@ -0,0 +1,159 @@ +// +// PreferencesGroup.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A group of preference rows. +/// +/// preferences-group +/// +/// An `AdwPreferencesGroup` represents a group or tightly related preferences, +/// which in turn are represented by [class@PreferencesRow]. +/// +/// To summarize the role of the preferences it gathers, a group can have both a +/// title and a description. The title will be used by [class@PreferencesWindow] +/// to let the user look for a preference. +/// +/// ## AdwPreferencesGroup as GtkBuildable +/// +/// The `AdwPreferencesGroup` implementation of the [iface@Gtk.Buildable] interface +/// supports adding [class@PreferencesRow]s to the list by omitting "type". If "type" +/// is omitted and the widget isn't a [class@PreferencesRow] the child is added to +/// a box below the list. +/// +/// When the "type" attribute of a child is `header-suffix`, the child +/// is set as the suffix on the end of the title and description. +/// +/// ## CSS nodes +/// +/// `AdwPreferencesGroup` has a single CSS node with name `preferencesgroup`. +/// +/// ## Accessibility +/// +/// `AdwPreferencesGroup` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct PreferencesGroup: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The description for this group of preferences. + var description: String? + /// The header suffix widget. + /// + /// Displayed above the list, next to the title and description. + /// + /// Suffixes are commonly used to show a button or a spinner for the whole + /// group. + var headerSuffix: (() -> Body)? + /// The title for this group of preferences. + var title: String? + /// The body for the widget "child". + var child: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `PreferencesGroup`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_preferences_group_new()?.opaque()) + update(storage, modifiers: modifiers) + if let headerSuffixStorage = headerSuffix?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["headerSuffix"] = [headerSuffixStorage] + adw_preferences_group_set_header_suffix(storage.pointer?.cast(), headerSuffixStorage.pointer?.cast()) + } + + var childStorage: [ViewStorage] = [] + for view in child() { + childStorage.append(view.storage(modifiers: modifiers)) + adw_preferences_group_add(storage.pointer?.cast(), childStorage.last?.pointer?.cast()) + } + storage.content["child"] = childStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let description { + adw_preferences_group_set_description(widget?.cast(), description) + } + if let widget = storage.content["headerSuffix"]?.first { + headerSuffix?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let title { + adw_preferences_group_set_title(widget?.cast(), title) + } + + if let childStorage = storage.content["child"] { + for (index, view) in child().enumerated() { + if let storage = childStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The description for this group of preferences. + public func description(_ description: String?) -> Self { + var newSelf = self + newSelf.description = description + + return newSelf + } + + /// The header suffix widget. + /// + /// Displayed above the list, next to the title and description. + /// + /// Suffixes are commonly used to show a button or a spinner for the whole + /// group. + public func headerSuffix(@ViewBuilder _ headerSuffix: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.headerSuffix = headerSuffix + + return newSelf + } + + /// The title for this group of preferences. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Set the body for "child". + /// - Parameter body: The body. + /// - Returns: The widget. + public func child(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.child = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/PreferencesPage.swift b/Sources/Adwaita/View/Generated/PreferencesPage.swift new file mode 100644 index 0000000..aa9a174 --- /dev/null +++ b/Sources/Adwaita/View/Generated/PreferencesPage.swift @@ -0,0 +1,157 @@ +// +// PreferencesPage.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A page from [class@PreferencesWindow]. +/// +/// preferences-page +/// +/// The `AdwPreferencesPage` widget gathers preferences groups into a single page +/// of a preferences window. +/// +/// ## CSS nodes +/// +/// `AdwPreferencesPage` has a single CSS node with name `preferencespage`. +/// +/// ## Accessibility +/// +/// `AdwPreferencesPage` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct PreferencesPage: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The description to be displayed at the top of the page. + var description: String? + /// The icon name for this page. + var iconName: String? + /// The name of this page. + var name: String? + /// The title for this page. + var title: String? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// The body for the widget "child". + var child: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `PreferencesPage`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_preferences_page_new()?.opaque()) + update(storage, modifiers: modifiers) + + var childStorage: [ViewStorage] = [] + for view in child() { + childStorage.append(view.storage(modifiers: modifiers)) + adw_preferences_group_add(storage.pointer?.cast(), childStorage.last?.pointer?.cast()) + } + storage.content["child"] = childStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let description { + adw_preferences_page_set_description(widget?.cast(), description) + } + if let iconName { + adw_preferences_page_set_icon_name(widget?.cast(), iconName) + } + if let name { + adw_preferences_page_set_name(widget?.cast(), name) + } + if let title { + adw_preferences_page_set_title(widget?.cast(), title) + } + if let useUnderline { + adw_preferences_page_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + if let childStorage = storage.content["child"] { + for (index, view) in child().enumerated() { + if let storage = childStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The description to be displayed at the top of the page. + public func description(_ description: String?) -> Self { + var newSelf = self + newSelf.description = description + + return newSelf + } + + /// The icon name for this page. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The name of this page. + public func name(_ name: String?) -> Self { + var newSelf = self + newSelf.name = name + + return newSelf + } + + /// The title for this page. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Set the body for "child". + /// - Parameter body: The body. + /// - Returns: The widget. + public func child(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.child = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/PreferencesRow.swift b/Sources/Adwaita/View/Generated/PreferencesRow.swift new file mode 100644 index 0000000..922d954 --- /dev/null +++ b/Sources/Adwaita/View/Generated/PreferencesRow.swift @@ -0,0 +1,134 @@ +// +// PreferencesRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@Gtk.ListBoxRow] used to present preferences. +/// +/// The `AdwPreferencesRow` widget has a title that [class@PreferencesWindow] +/// will use to let the user look for a preference. It doesn't present the title +/// in any way and lets you present the preference as you please. +/// +/// [class@ActionRow] and its derivatives are convenient to use as preference +/// rows as they take care of presenting the preference's title while letting you +/// compose the inputs of the preference around it. +public struct PreferencesRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `PreferencesRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_preferences_row_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ProgressBar.swift b/Sources/Adwaita/View/Generated/ProgressBar.swift new file mode 100644 index 0000000..4d86290 --- /dev/null +++ b/Sources/Adwaita/View/Generated/ProgressBar.swift @@ -0,0 +1,184 @@ +// +// ProgressBar.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// `GtkProgressBar` is typically used to display the progress of a long +/// running operation. +/// +/// It provides a visual clue that processing is underway. `GtkProgressBar` +/// can be used in two different modes: percentage mode and activity mode. +/// +/// ![An example GtkProgressBar](progressbar.png) +/// +/// When an application can determine how much work needs to take place +/// (e.g. read a fixed number of bytes from a file) and can monitor its +/// progress, it can use the `GtkProgressBar` in percentage mode and the +/// user sees a growing bar indicating the percentage of the work that +/// has been completed. In this mode, the application is required to call +/// [method@Gtk.ProgressBar.set_fraction] periodically to update the progress bar. +/// +/// When an application has no accurate way of knowing the amount of work +/// to do, it can use the `GtkProgressBar` in activity mode, which shows +/// activity by a block moving back and forth within the progress area. In +/// this mode, the application is required to call [method@Gtk.ProgressBar.pulse] +/// periodically to update the progress bar. +/// +/// There is quite a bit of flexibility provided to control the appearance +/// of the `GtkProgressBar`. Functions are provided to control the orientation +/// of the bar, optional text can be displayed along with the bar, and the +/// step size used in activity mode can be set. +/// +/// # CSS nodes +/// +/// ``` +/// progressbar[.osd] +/// ├── [text] +/// ╰── trough[.empty][.full] +/// ╰── progress[.pulse] +/// ``` +/// +/// `GtkProgressBar` has a main CSS node with name progressbar and subnodes with +/// names text and trough, of which the latter has a subnode named progress. The +/// text subnode is only present if text is shown. The progress subnode has the +/// style class .pulse when in activity mode. It gets the style classes .left, +/// .right, .top or .bottom added when the progress 'touches' the corresponding +/// end of the GtkProgressBar. The .osd class on the progressbar node is for use +/// in overlays like the one Epiphany has for page loading progress. +/// +/// # Accessibility +/// +/// `GtkProgressBar` uses the %GTK_ACCESSIBLE_ROLE_PROGRESS_BAR role. +public struct ProgressBar: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The fraction of total work that has been completed. + var fraction: Double? + /// Invert the direction in which the progress bar grows. + var inverted: Bool? + /// The fraction of total progress to move the bounding block when pulsed. + var pulseStep: Double? + /// Sets whether the progress bar will show a text in addition + /// to the bar itself. + /// + /// The shown text is either the value of the [property@Gtk.ProgressBar:text] + /// property or, if that is %NULL, the [property@Gtk.ProgressBar:fraction] + /// value, as a percentage. + /// + /// To make a progress bar that is styled and sized suitably for showing text + /// (even if the actual text is blank), set [property@Gtk.ProgressBar:show-text] + /// to %TRUE and [property@Gtk.ProgressBar:text] to the empty string (not %NULL). + var showText: Bool? + /// Text to be displayed in the progress bar. + var text: String? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ProgressBar`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_progress_bar_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let fraction { + gtk_progress_bar_set_fraction(widget, fraction) + } + if let inverted { + gtk_progress_bar_set_inverted(widget, inverted.cBool) + } + if let pulseStep { + gtk_progress_bar_set_pulse_step(widget, pulseStep) + } + if let showText { + gtk_progress_bar_set_show_text(widget, showText.cBool) + } + if let text { + gtk_progress_bar_set_text(widget, text) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The fraction of total work that has been completed. + public func fraction(_ fraction: Double?) -> Self { + var newSelf = self + newSelf.fraction = fraction + + return newSelf + } + + /// Invert the direction in which the progress bar grows. + public func inverted(_ inverted: Bool? = true) -> Self { + var newSelf = self + newSelf.inverted = inverted + + return newSelf + } + + /// The fraction of total progress to move the bounding block when pulsed. + public func pulseStep(_ pulseStep: Double?) -> Self { + var newSelf = self + newSelf.pulseStep = pulseStep + + return newSelf + } + + /// Sets whether the progress bar will show a text in addition + /// to the bar itself. + /// + /// The shown text is either the value of the [property@Gtk.ProgressBar:text] + /// property or, if that is %NULL, the [property@Gtk.ProgressBar:fraction] + /// value, as a percentage. + /// + /// To make a progress bar that is styled and sized suitably for showing text + /// (even if the actual text is blank), set [property@Gtk.ProgressBar:show-text] + /// to %TRUE and [property@Gtk.ProgressBar:text] to the empty string (not %NULL). + public func showText(_ showText: Bool? = true) -> Self { + var newSelf = self + newSelf.showText = showText + + return newSelf + } + + /// Text to be displayed in the progress bar. + public func text(_ text: String?) -> Self { + var newSelf = self + newSelf.text = text + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ScrolledWindow.swift b/Sources/Adwaita/View/Generated/ScrolledWindow.swift new file mode 100644 index 0000000..31f0079 --- /dev/null +++ b/Sources/Adwaita/View/Generated/ScrolledWindow.swift @@ -0,0 +1,403 @@ +// +// ScrolledWindow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// `GtkScrolledWindow` is a container that makes its child scrollable. +/// +/// It does so using either internally added scrollbars or externally +/// associated adjustments, and optionally draws a frame around the child. +/// +/// Widgets with native scrolling support, i.e. those whose classes implement +/// the [iface@Gtk.Scrollable] interface, are added directly. For other types +/// of widget, the class [class@Gtk.Viewport] acts as an adaptor, giving +/// scrollability to other widgets. [method@Gtk.ScrolledWindow.set_child] +/// intelligently accounts for whether or not the added child is a `GtkScrollable`. +/// If it isn’t, then it wraps the child in a `GtkViewport`. Therefore, you can +/// just add any child widget and not worry about the details. +/// +/// If [method@Gtk.ScrolledWindow.set_child] has added a `GtkViewport` for you, +/// it will be automatically removed when you unset the child. +/// Unless [property@Gtk.ScrolledWindow:hscrollbar-policy] and +/// [property@Gtk.ScrolledWindow:vscrollbar-policy] are %GTK_POLICY_NEVER or +/// %GTK_POLICY_EXTERNAL, `GtkScrolledWindow` adds internal `GtkScrollbar` widgets +/// around its child. The scroll position of the child, and if applicable the +/// scrollbars, is controlled by the [property@Gtk.ScrolledWindow:hadjustment] +/// and [property@Gtk.ScrolledWindow:vadjustment] that are associated with the +/// `GtkScrolledWindow`. See the docs on [class@Gtk.Scrollbar] for the details, +/// but note that the “step_increment” and “page_increment” fields are only +/// effective if the policy causes scrollbars to be present. +/// +/// If a `GtkScrolledWindow` doesn’t behave quite as you would like, or +/// doesn’t have exactly the right layout, it’s very possible to set up +/// your own scrolling with `GtkScrollbar` and for example a `GtkGrid`. +/// +/// # Touch support +/// +/// `GtkScrolledWindow` has built-in support for touch devices. When a +/// touchscreen is used, swiping will move the scrolled window, and will +/// expose 'kinetic' behavior. This can be turned off with the +/// [property@Gtk.ScrolledWindow:kinetic-scrolling] property if it is undesired. +/// +/// `GtkScrolledWindow` also displays visual 'overshoot' indication when +/// the content is pulled beyond the end, and this situation can be +/// captured with the [signal@Gtk.ScrolledWindow::edge-overshot] signal. +/// +/// If no mouse device is present, the scrollbars will overlaid as +/// narrow, auto-hiding indicators over the content. If traditional +/// scrollbars are desired although no mouse is present, this behaviour +/// can be turned off with the [property@Gtk.ScrolledWindow:overlay-scrolling] +/// property. +/// +/// # CSS nodes +/// +/// `GtkScrolledWindow` has a main CSS node with name scrolledwindow. +/// It gets a .frame style class added when [property@Gtk.ScrolledWindow:has-frame] +/// is %TRUE. +/// +/// It uses subnodes with names overshoot and undershoot to draw the overflow +/// and underflow indications. These nodes get the .left, .right, .top or .bottom +/// style class added depending on where the indication is drawn. +/// +/// `GtkScrolledWindow` also sets the positional style classes (.left, .right, +/// .top, .bottom) and style classes related to overlay scrolling +/// (.overlay-indicator, .dragging, .hovering) on its scrollbars. +/// +/// If both scrollbars are visible, the area where they meet is drawn +/// with a subnode named junction. +/// +/// # Accessibility +/// +/// Until GTK 4.10, `GtkScrolledWindow` used the `GTK_ACCESSIBLE_ROLE_GROUP` role. +/// +/// Starting from GTK 4.12, `GtkScrolledWindow` uses the `GTK_ACCESSIBLE_ROLE_GENERIC` role. +public struct ScrolledWindow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The child widget. + var child: (() -> Body)? + /// Whether to draw a frame around the contents. + var hasFrame: Bool? + /// Whether kinetic scrolling is enabled or not. + /// + /// Kinetic scrolling only applies to devices with source %GDK_SOURCE_TOUCHSCREEN. + var kineticScrolling: Bool? + /// The maximum content height of @scrolled_window. + var maxContentHeight: Int? + /// The maximum content width of @scrolled_window. + var maxContentWidth: Int? + /// The minimum content height of @scrolled_window. + var minContentHeight: Int? + /// The minimum content width of @scrolled_window. + var minContentWidth: Int? + /// Whether overlay scrolling is enabled or not. + /// + /// If it is, the scrollbars are only added as traditional widgets + /// when a mouse is present. Otherwise, they are overlaid on top of + /// the content, as narrow indicators. + /// + /// Note that overlay scrolling can also be globally disabled, with + /// the [property@Gtk.Settings:gtk-overlay-scrolling] setting. + var overlayScrolling: Bool? + /// Whether the natural height of the child should be calculated and propagated + /// through the scrolled window’s requested natural height. + /// + /// This is useful in cases where an attempt should be made to allocate exactly + /// enough space for the natural size of the child. + var propagateNaturalHeight: Bool? + /// Whether the natural width of the child should be calculated and propagated + /// through the scrolled window’s requested natural width. + /// + /// This is useful in cases where an attempt should be made to allocate exactly + /// enough space for the natural size of the child. + var propagateNaturalWidth: Bool? + /// Emitted whenever user initiated scrolling makes the scrolled + /// window firmly surpass the limits defined by the adjustment + /// in that orientation. + /// + /// A similar behavior without edge resistance is provided by the + /// [signal@Gtk.ScrolledWindow::edge-reached] signal. + /// + /// Note: The @pos argument is LTR/RTL aware, so callers should be + /// aware too if intending to provide behavior on horizontal edges. + var edgeOvershot: (() -> Void)? + /// Emitted whenever user-initiated scrolling makes the scrolled + /// window exactly reach the lower or upper limits defined by the + /// adjustment in that orientation. + /// + /// A similar behavior with edge resistance is provided by the + /// [signal@Gtk.ScrolledWindow::edge-overshot] signal. + /// + /// Note: The @pos argument is LTR/RTL aware, so callers should be + /// aware too if intending to provide behavior on horizontal edges. + var edgeReached: (() -> Void)? + /// Emitted when focus is moved away from the scrolled window by a + /// keybinding. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The default bindings for this signal are + /// `Ctrl + Tab` to move forward and `Ctrl + Shift + Tab` to + /// move backward. + var moveFocusOut: (() -> Void)? + /// Emitted when a keybinding that scrolls is pressed. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The horizontal or vertical adjustment is updated which triggers a + /// signal that the scrolled window’s child may listen to and scroll itself. + var scrollChild: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ScrolledWindow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_scrolled_window_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_scrolled_window_set_child(storage.pointer, childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let edgeOvershot { + storage.connectSignal(name: "edge-overshot") { + edgeOvershot() + } + } + if let edgeReached { + storage.connectSignal(name: "edge-reached") { + edgeReached() + } + } + if let moveFocusOut { + storage.connectSignal(name: "move-focus-out") { + moveFocusOut() + } + } + if let scrollChild { + storage.connectSignal(name: "scroll-child") { + scrollChild() + } + } + storage.modify { widget in + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let hasFrame { + gtk_scrolled_window_set_has_frame(widget, hasFrame.cBool) + } + if let kineticScrolling { + gtk_scrolled_window_set_kinetic_scrolling(widget, kineticScrolling.cBool) + } + if let maxContentHeight { + gtk_scrolled_window_set_max_content_height(widget, maxContentHeight.cInt) + } + if let maxContentWidth { + gtk_scrolled_window_set_max_content_width(widget, maxContentWidth.cInt) + } + if let minContentHeight { + gtk_scrolled_window_set_min_content_height(widget, minContentHeight.cInt) + } + if let minContentWidth { + gtk_scrolled_window_set_min_content_width(widget, minContentWidth.cInt) + } + if let overlayScrolling { + gtk_scrolled_window_set_overlay_scrolling(widget, overlayScrolling.cBool) + } + if let propagateNaturalHeight { + gtk_scrolled_window_set_propagate_natural_height(widget, propagateNaturalHeight.cBool) + } + if let propagateNaturalWidth { + gtk_scrolled_window_set_propagate_natural_width(widget, propagateNaturalWidth.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// Whether to draw a frame around the contents. + public func hasFrame(_ hasFrame: Bool? = true) -> Self { + var newSelf = self + newSelf.hasFrame = hasFrame + + return newSelf + } + + /// Whether kinetic scrolling is enabled or not. + /// + /// Kinetic scrolling only applies to devices with source %GDK_SOURCE_TOUCHSCREEN. + public func kineticScrolling(_ kineticScrolling: Bool? = true) -> Self { + var newSelf = self + newSelf.kineticScrolling = kineticScrolling + + return newSelf + } + + /// The maximum content height of @scrolled_window. + public func maxContentHeight(_ maxContentHeight: Int?) -> Self { + var newSelf = self + newSelf.maxContentHeight = maxContentHeight + + return newSelf + } + + /// The maximum content width of @scrolled_window. + public func maxContentWidth(_ maxContentWidth: Int?) -> Self { + var newSelf = self + newSelf.maxContentWidth = maxContentWidth + + return newSelf + } + + /// The minimum content height of @scrolled_window. + public func minContentHeight(_ minContentHeight: Int?) -> Self { + var newSelf = self + newSelf.minContentHeight = minContentHeight + + return newSelf + } + + /// The minimum content width of @scrolled_window. + public func minContentWidth(_ minContentWidth: Int?) -> Self { + var newSelf = self + newSelf.minContentWidth = minContentWidth + + return newSelf + } + + /// Whether overlay scrolling is enabled or not. + /// + /// If it is, the scrollbars are only added as traditional widgets + /// when a mouse is present. Otherwise, they are overlaid on top of + /// the content, as narrow indicators. + /// + /// Note that overlay scrolling can also be globally disabled, with + /// the [property@Gtk.Settings:gtk-overlay-scrolling] setting. + public func overlayScrolling(_ overlayScrolling: Bool? = true) -> Self { + var newSelf = self + newSelf.overlayScrolling = overlayScrolling + + return newSelf + } + + /// Whether the natural height of the child should be calculated and propagated + /// through the scrolled window’s requested natural height. + /// + /// This is useful in cases where an attempt should be made to allocate exactly + /// enough space for the natural size of the child. + public func propagateNaturalHeight(_ propagateNaturalHeight: Bool? = true) -> Self { + var newSelf = self + newSelf.propagateNaturalHeight = propagateNaturalHeight + + return newSelf + } + + /// Whether the natural width of the child should be calculated and propagated + /// through the scrolled window’s requested natural width. + /// + /// This is useful in cases where an attempt should be made to allocate exactly + /// enough space for the natural size of the child. + public func propagateNaturalWidth(_ propagateNaturalWidth: Bool? = true) -> Self { + var newSelf = self + newSelf.propagateNaturalWidth = propagateNaturalWidth + + return newSelf + } + + /// Emitted whenever user initiated scrolling makes the scrolled + /// window firmly surpass the limits defined by the adjustment + /// in that orientation. + /// + /// A similar behavior without edge resistance is provided by the + /// [signal@Gtk.ScrolledWindow::edge-reached] signal. + /// + /// Note: The @pos argument is LTR/RTL aware, so callers should be + /// aware too if intending to provide behavior on horizontal edges. + public func edgeOvershot(_ edgeOvershot: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.edgeOvershot = edgeOvershot + return newSelf + } + + /// Emitted whenever user-initiated scrolling makes the scrolled + /// window exactly reach the lower or upper limits defined by the + /// adjustment in that orientation. + /// + /// A similar behavior with edge resistance is provided by the + /// [signal@Gtk.ScrolledWindow::edge-overshot] signal. + /// + /// Note: The @pos argument is LTR/RTL aware, so callers should be + /// aware too if intending to provide behavior on horizontal edges. + public func edgeReached(_ edgeReached: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.edgeReached = edgeReached + return newSelf + } + + /// Emitted when focus is moved away from the scrolled window by a + /// keybinding. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The default bindings for this signal are + /// `Ctrl + Tab` to move forward and `Ctrl + Shift + Tab` to + /// move backward. + public func moveFocusOut(_ moveFocusOut: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.moveFocusOut = moveFocusOut + return newSelf + } + + /// Emitted when a keybinding that scrolls is pressed. + /// + /// This is a [keybinding signal](class.SignalAction.html). + /// + /// The horizontal or vertical adjustment is updated which triggers a + /// signal that the scrolled window’s child may listen to and scroll itself. + public func scrollChild(_ scrollChild: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.scrollChild = scrollChild + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/SpinRow.swift b/Sources/Adwaita/View/Generated/SpinRow.swift new file mode 100644 index 0000000..f12db34 --- /dev/null +++ b/Sources/Adwaita/View/Generated/SpinRow.swift @@ -0,0 +1,432 @@ +// +// SpinRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// An [class@ActionRow] with an embedded spin button. +/// +/// spin-row +/// +/// Example of an `AdwSpinRow` UI definition: +/// +/// ```xml +/// Spin Row010050101 +/// ``` +/// +/// See [class@Gtk.SpinButton] for details. +/// +/// ## CSS nodes +/// +/// `AdwSpinRow` has the same structure as [class@ActionRow], as well as the +/// `.spin` style class on the main node. +public struct SpinRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The acceleration rate when you hold down a button or key. + var climbRate: Double + /// The number of decimal places to display. + var digits: UInt + /// Whether non-numeric characters should be ignored. + var numeric: Bool? + /// Whether invalid values are snapped to the nearest step increment. + var snapToTicks: Bool? + /// The current value. + var value: Binding? + /// Whether the spin row should wrap upon reaching its limits. + var wrap: Bool? + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + var activatableWidget: (() -> Body)? + /// The icon name for this row. + var iconName: String? + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var subtitle: String? + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var subtitleLines: Int? + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var subtitleSelectable: Bool? + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var titleLines: Int? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// Emitted to convert the user's input into a double value. + /// + /// The signal handler is expected to use [method@Gtk.Editable.get_text] to + /// retrieve the text of the spinbutton and set new_value to the new value. + /// + /// The default conversion uses [func@GLib.strtod]. + /// + /// See [signal@Gtk.SpinButton::input]. + var input: (() -> Void)? + /// Emitted to tweak the formatting of the value for display. + /// + /// See [signal@Gtk.SpinButton::output]. + var output: (() -> Void)? + /// Emitted right after the spinbutton wraps. + /// + /// See [signal@Gtk.SpinButton::wrapped]. + var wrapped: (() -> Void)? + /// This signal is emitted after the row has been activated. + var activated: (() -> Void)? + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `SpinRow`. + public init(climbRate: Double, digits: UInt) { + self.climbRate = climbRate + self.digits = digits + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_spin_row_new(nil, climbRate, digits.cInt)?.opaque()) + update(storage, modifiers: modifiers) + if let activatableWidgetStorage = activatableWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["activatableWidget"] = [activatableWidgetStorage] + adw_action_row_set_activatable_widget(storage.pointer?.cast(), activatableWidgetStorage.pointer?.cast()) + } + + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + storage.notify(name: "value") { + value?.wrappedValue = adw_spin_row_get_value(storage.pointer) + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let input { + storage.connectSignal(name: "input") { + input() + } + } + if let output { + storage.connectSignal(name: "output") { + output() + } + } + if let wrapped { + storage.connectSignal(name: "wrapped") { + wrapped() + } + } + if let activated { + storage.connectSignal(name: "activated") { + activated() + } + } + storage.modify { widget in + adw_spin_row_set_climb_rate(widget, climbRate) + adw_spin_row_set_digits(widget, digits.cInt) + if let numeric { + adw_spin_row_set_numeric(widget, numeric.cBool) + } + if let snapToTicks { + adw_spin_row_set_snap_to_ticks(widget, snapToTicks.cBool) + } + if let value { + adw_spin_row_set_value(widget, value.wrappedValue) + } + if let wrap { + adw_spin_row_set_wrap(widget, wrap.cBool) + } + if let widget = storage.content["activatableWidget"]?.first { + activatableWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let iconName { + adw_action_row_set_icon_name(widget?.cast(), iconName) + } + if let subtitle { + adw_action_row_set_subtitle(widget?.cast(), subtitle) + } + if let subtitleLines { + adw_action_row_set_subtitle_lines(widget?.cast(), subtitleLines.cInt) + } + if let subtitleSelectable { + adw_action_row_set_subtitle_selectable(widget?.cast(), subtitleSelectable.cBool) + } + if let titleLines { + adw_action_row_set_title_lines(widget?.cast(), titleLines.cInt) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The acceleration rate when you hold down a button or key. + public func climbRate(_ climbRate: Double) -> Self { + var newSelf = self + newSelf.climbRate = climbRate + + return newSelf + } + + /// The number of decimal places to display. + public func digits(_ digits: UInt) -> Self { + var newSelf = self + newSelf.digits = digits + + return newSelf + } + + /// Whether non-numeric characters should be ignored. + public func numeric(_ numeric: Bool? = true) -> Self { + var newSelf = self + newSelf.numeric = numeric + + return newSelf + } + + /// Whether invalid values are snapped to the nearest step increment. + public func snapToTicks(_ snapToTicks: Bool? = true) -> Self { + var newSelf = self + newSelf.snapToTicks = snapToTicks + + return newSelf + } + + /// The current value. + public func value(_ value: Binding?) -> Self { + var newSelf = self + newSelf.value = value + + return newSelf + } + + /// Whether the spin row should wrap upon reaching its limits. + public func wrap(_ wrap: Bool? = true) -> Self { + var newSelf = self + newSelf.wrap = wrap + + return newSelf + } + + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + public func activatableWidget(@ViewBuilder _ activatableWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.activatableWidget = activatableWidget + + return newSelf + } + + /// The icon name for this row. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func subtitle(_ subtitle: String?) -> Self { + var newSelf = self + newSelf.subtitle = subtitle + + return newSelf + } + + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func subtitleLines(_ subtitleLines: Int?) -> Self { + var newSelf = self + newSelf.subtitleLines = subtitleLines + + return newSelf + } + + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func subtitleSelectable(_ subtitleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.subtitleSelectable = subtitleSelectable + + return newSelf + } + + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func titleLines(_ titleLines: Int?) -> Self { + var newSelf = self + newSelf.titleLines = titleLines + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted to convert the user's input into a double value. + /// + /// The signal handler is expected to use [method@Gtk.Editable.get_text] to + /// retrieve the text of the spinbutton and set new_value to the new value. + /// + /// The default conversion uses [func@GLib.strtod]. + /// + /// See [signal@Gtk.SpinButton::input]. + public func input(_ input: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.input = input + return newSelf + } + + /// Emitted to tweak the formatting of the value for display. + /// + /// See [signal@Gtk.SpinButton::output]. + public func output(_ output: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.output = output + return newSelf + } + + /// Emitted right after the spinbutton wraps. + /// + /// See [signal@Gtk.SpinButton::wrapped]. + public func wrapped(_ wrapped: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.wrapped = wrapped + return newSelf + } + + /// This signal is emitted after the row has been activated. + public func activated(_ activated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activated = activated + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/Spinner.swift b/Sources/Adwaita/View/Generated/Spinner.swift new file mode 100644 index 0000000..5db7f8c --- /dev/null +++ b/Sources/Adwaita/View/Generated/Spinner.swift @@ -0,0 +1,83 @@ +// +// Spinner.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A `GtkSpinner` widget displays an icon-size spinning animation. +/// +/// It is often used as an alternative to a [class@Gtk.ProgressBar] +/// for displaying indefinite activity, instead of actual progress. +/// +/// ![An example GtkSpinner](spinner.png) +/// +/// To start the animation, use [method@Gtk.Spinner.start], to stop it +/// use [method@Gtk.Spinner.stop]. +/// +/// # CSS nodes +/// +/// `GtkSpinner` has a single CSS node with the name spinner. +/// When the animation is active, the :checked pseudoclass is +/// added to this node. +public struct Spinner: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the spinner is spinning + var spinning: Bool? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `Spinner`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_spinner_new()?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let spinning { + gtk_spinner_set_spinning(widget, spinning.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the spinner is spinning + public func spinning(_ spinning: Bool? = true) -> Self { + var newSelf = self + newSelf.spinning = spinning + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/SplitButton.swift b/Sources/Adwaita/View/Generated/SplitButton.swift new file mode 100644 index 0000000..71228e7 --- /dev/null +++ b/Sources/Adwaita/View/Generated/SplitButton.swift @@ -0,0 +1,274 @@ +// +// SplitButton.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A combined button and dropdown widget. +/// +/// split-button +/// +/// `AdwSplitButton` is typically used to present a set of actions in a menu, +/// but allow access to one of them with a single click. +/// +/// The API is very similar to [class@Gtk.Button] and [class@Gtk.MenuButton], see +/// their documentation for details. +/// +/// ## CSS nodes +/// +/// ``` +/// splitbutton[.image-button][.text-button] +/// ├── button +/// │ ╰── ├── separator +/// ╰── menubutton +/// ╰── button.toggle +/// ╰── arrow +/// ``` +/// +/// `AdwSplitButton`'s CSS node is called `splitbutton`. It contains the css +/// nodes: `button`, `separator`, `menubutton`. See [class@Gtk.MenuButton] +/// documentation for the `menubutton` contents. +/// +/// The main CSS node will contain the `.image-button` or `.text-button` style +/// classes matching the button contents. The nested button nodes will never +/// contain them. +/// +/// ## Accessibility +/// +/// `AdwSplitButton` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct SplitButton: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the button can be smaller than the natural size of its contents. + /// + /// If set to `TRUE`, the label will ellipsize. + /// + /// See [property@Gtk.Button:can-shrink] and + /// [property@Gtk.MenuButton:can-shrink]. + var canShrink: Bool? + /// The child widget. + /// + /// Setting the child widget will set [property@SplitButton:label] and + /// [property@SplitButton:icon-name] to `NULL`. + var child: (() -> Body)? + /// The tooltip of the dropdown button. + /// + /// The tooltip can be marked up with the Pango text markup language. + var dropdownTooltip: String? + /// The name of the icon used to automatically populate the button. + /// + /// Setting the icon name will set [property@SplitButton:label] and + /// [property@SplitButton:child] to `NULL`. + var iconName: String? + /// The label for the button. + /// + /// Setting the label will set [property@SplitButton:icon-name] and + /// [property@SplitButton:child] to `NULL`. + var label: String? + /// The `GMenuModel` from which the popup will be created. + /// + /// If the menu model is `NULL`, the dropdown is disabled. + /// + /// A [class@Gtk.Popover] will be created from the menu model with + /// [ctor@Gtk.PopoverMenu.new_from_model]. Actions will be connected as + /// documented for this function. + /// + /// If [property@SplitButton:popover] is already set, it will be dissociated + /// from the button, and the property is set to `NULL`. + var menuModel: (() -> MenuContent)? + /// Whether an underline in the text indicates a mnemonic. + /// + /// See [property@SplitButton:label]. + var useUnderline: Bool? + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect to this signal, + /// but use the [signal@SplitButton::clicked] signal. + var activate: (() -> Void)? + /// Emitted when the button has been activated (pressed and released). + var clicked: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `SplitButton`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_split_button_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + adw_split_button_set_child(storage.pointer, childStorage.pointer?.cast()) + } + if let declarative = menuModel?(), let app { + let menu = g_menu_new() + adw_split_button_set_menu_model(storage.pointer, menu?.cast()) + for item in declarative { + item.addMenuItems(menu: menu, app: app, window: window) + } + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activate { + storage.connectSignal(name: "activate") { + activate() + } + } + if let clicked { + storage.connectSignal(name: "clicked") { + clicked() + } + } + storage.modify { widget in + if let canShrink { + adw_split_button_set_can_shrink(widget, canShrink.cBool) + } + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let dropdownTooltip { + adw_split_button_set_dropdown_tooltip(widget, dropdownTooltip) + } + if let iconName { + adw_split_button_set_icon_name(widget, iconName) + } + if let label, storage.content["child"] == nil { + adw_split_button_set_label(widget, label) + } + if let useUnderline { + adw_split_button_set_use_underline(widget, useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the button can be smaller than the natural size of its contents. + /// + /// If set to `TRUE`, the label will ellipsize. + /// + /// See [property@Gtk.Button:can-shrink] and + /// [property@Gtk.MenuButton:can-shrink]. + public func canShrink(_ canShrink: Bool? = true) -> Self { + var newSelf = self + newSelf.canShrink = canShrink + + return newSelf + } + + /// The child widget. + /// + /// Setting the child widget will set [property@SplitButton:label] and + /// [property@SplitButton:icon-name] to `NULL`. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// The tooltip of the dropdown button. + /// + /// The tooltip can be marked up with the Pango text markup language. + public func dropdownTooltip(_ dropdownTooltip: String?) -> Self { + var newSelf = self + newSelf.dropdownTooltip = dropdownTooltip + + return newSelf + } + + /// The name of the icon used to automatically populate the button. + /// + /// Setting the icon name will set [property@SplitButton:label] and + /// [property@SplitButton:child] to `NULL`. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The label for the button. + /// + /// Setting the label will set [property@SplitButton:icon-name] and + /// [property@SplitButton:child] to `NULL`. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// The `GMenuModel` from which the popup will be created. + /// + /// If the menu model is `NULL`, the dropdown is disabled. + /// + /// A [class@Gtk.Popover] will be created from the menu model with + /// [ctor@Gtk.PopoverMenu.new_from_model]. Actions will be connected as + /// documented for this function. + /// + /// If [property@SplitButton:popover] is already set, it will be dissociated + /// from the button, and the property is set to `NULL`. + public func menuModel(app: GTUIApp, window: GTUIApplicationWindow? = nil, @MenuBuilder _ menuModel: @escaping (() -> MenuContent)) -> Self { + var newSelf = self + newSelf.menuModel = menuModel + newSelf.app = app; newSelf.window = window + return newSelf + } + + /// Whether an underline in the text indicates a mnemonic. + /// + /// See [property@SplitButton:label]. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect to this signal, + /// but use the [signal@SplitButton::clicked] signal. + public func activate(_ activate: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activate = activate + return newSelf + } + + /// Emitted when the button has been activated (pressed and released). + public func clicked(_ clicked: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.clicked = clicked + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/StatusPage.swift b/Sources/Adwaita/View/Generated/StatusPage.swift new file mode 100644 index 0000000..2c15b34 --- /dev/null +++ b/Sources/Adwaita/View/Generated/StatusPage.swift @@ -0,0 +1,133 @@ +// +// StatusPage.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A page used for empty/error states and similar use-cases. +/// +/// status-page +/// +/// The `AdwStatusPage` widget can have an icon, a title, a description and a +/// custom widget which is displayed below them. +/// +/// ## CSS nodes +/// +/// `AdwStatusPage` has a main CSS node with name `statuspage`. +/// +/// `AdwStatusPage` can use the +/// [`.compact`](style-classes.html#compact-status-page) style class for when it +/// needs to fit into a small space such a sidebar or a popover. +public struct StatusPage: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The child widget. + var child: (() -> Body)? + /// The description markup to be displayed below the title. + var description: String? + /// The name of the icon to be used. + /// + /// Changing this will set [property@StatusPage:paintable] to `NULL`. + var iconName: String? + /// The title to be displayed below the icon. + /// + /// It is not parsed as Pango markup. + var title: String? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `StatusPage`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_status_page_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + adw_status_page_set_child(storage.pointer, childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let description { + adw_status_page_set_description(widget, description) + } + if let iconName { + adw_status_page_set_icon_name(widget, iconName) + } + if let title { + adw_status_page_set_title(widget, title) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// The description markup to be displayed below the title. + public func description(_ description: String?) -> Self { + var newSelf = self + newSelf.description = description + + return newSelf + } + + /// The name of the icon to be used. + /// + /// Changing this will set [property@StatusPage:paintable] to `NULL`. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The title to be displayed below the icon. + /// + /// It is not parsed as Pango markup. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/SwitchRow.swift b/Sources/Adwaita/View/Generated/SwitchRow.swift new file mode 100644 index 0000000..e5f5a7f --- /dev/null +++ b/Sources/Adwaita/View/Generated/SwitchRow.swift @@ -0,0 +1,309 @@ +// +// SwitchRow.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A [class@Gtk.ListBoxRow] used to represent two states. +/// +/// switch-row +/// +/// The `AdwSwitchRow` widget contains a [class@Gtk.Switch] that allows the user +/// to select between two states: "on" or "off". When activated, the row will +/// invert its active state. +/// +/// The user can control the switch by activating the row or by dragging on the +/// switch handle. +/// +/// See [class@Gtk.Switch] for details. +/// +/// Example of an `AdwSwitchRow` UI definition: +/// ```xml +/// Switch Row +/// ``` +/// +/// The [property@SwitchRow:active] property should be connected to in order to +/// monitor changes to the active state. +public struct SwitchRow: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// Whether the switch row is in the "on" or "off" position. + var active: Binding? + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + var activatableWidget: (() -> Body)? + /// The icon name for this row. + var iconName: String? + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var subtitle: String? + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var subtitleLines: Int? + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var subtitleSelectable: Bool? + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + var titleLines: Int? + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + var title: String? + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + var titleSelectable: Bool? + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + var useMarkup: Bool? + /// Whether an embedded underline in the title indicates a mnemonic. + var useUnderline: Bool? + /// This signal is emitted after the row has been activated. + var activated: (() -> Void)? + /// The body for the widget "suffix". + var suffix: () -> Body = { [] } + /// The body for the widget "prefix". + var prefix: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `SwitchRow`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_switch_row_new()?.opaque()) + update(storage, modifiers: modifiers) + if let activatableWidgetStorage = activatableWidget?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["activatableWidget"] = [activatableWidgetStorage] + adw_action_row_set_activatable_widget(storage.pointer?.cast(), activatableWidgetStorage.pointer?.cast()) + } + + var suffixStorage: [ViewStorage] = [] + for view in suffix() { + suffixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_suffix(storage.pointer?.cast(), suffixStorage.last?.pointer?.cast()) + } + storage.content["suffix"] = suffixStorage + var prefixStorage: [ViewStorage] = [] + for view in prefix() { + prefixStorage.append(view.storage(modifiers: modifiers)) + adw_action_row_add_prefix(storage.pointer?.cast(), prefixStorage.last?.pointer?.cast()) + } + storage.content["prefix"] = prefixStorage + + storage.notify(name: "active") { + active?.wrappedValue = adw_switch_row_get_active(storage.pointer) != 0 + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let activated { + storage.connectSignal(name: "activated") { + activated() + } + } + storage.modify { widget in + if let active { + adw_switch_row_set_active(widget, active.wrappedValue.cBool) + } + if let widget = storage.content["activatableWidget"]?.first { + activatableWidget?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let iconName { + adw_action_row_set_icon_name(widget?.cast(), iconName) + } + if let subtitle { + adw_action_row_set_subtitle(widget?.cast(), subtitle) + } + if let subtitleLines { + adw_action_row_set_subtitle_lines(widget?.cast(), subtitleLines.cInt) + } + if let subtitleSelectable { + adw_action_row_set_subtitle_selectable(widget?.cast(), subtitleSelectable.cBool) + } + if let titleLines { + adw_action_row_set_title_lines(widget?.cast(), titleLines.cInt) + } + if let title { + adw_preferences_row_set_title(widget?.cast(), title) + } + if let titleSelectable { + adw_preferences_row_set_title_selectable(widget?.cast(), titleSelectable.cBool) + } + if let useMarkup { + adw_preferences_row_set_use_markup(widget?.cast(), useMarkup.cBool) + } + if let useUnderline { + adw_preferences_row_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// Whether the switch row is in the "on" or "off" position. + public func active(_ active: Binding?) -> Self { + var newSelf = self + newSelf.active = active + + return newSelf + } + + /// The widget to activate when the row is activated. + /// + /// The row can be activated either by clicking on it, calling + /// [method@ActionRow.activate], or via mnemonics in the title. + /// See the [property@PreferencesRow:use-underline] property to enable + /// mnemonics. + /// + /// The target widget will be activated by emitting the + /// [signal@Gtk.Widget::mnemonic-activate] signal on it. + public func activatableWidget(@ViewBuilder _ activatableWidget: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.activatableWidget = activatableWidget + + return newSelf + } + + /// The icon name for this row. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// The subtitle for this row. + /// + /// The subtitle is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func subtitle(_ subtitle: String?) -> Self { + var newSelf = self + newSelf.subtitle = subtitle + + return newSelf + } + + /// The number of lines at the end of which the subtitle label will be + /// ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func subtitleLines(_ subtitleLines: Int?) -> Self { + var newSelf = self + newSelf.subtitleLines = subtitleLines + + return newSelf + } + + /// Whether the user can copy the subtitle from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func subtitleSelectable(_ subtitleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.subtitleSelectable = subtitleSelectable + + return newSelf + } + + /// The number of lines at the end of which the title label will be ellipsized. + /// + /// If the value is 0, the number of lines won't be limited. + public func titleLines(_ titleLines: Int?) -> Self { + var newSelf = self + newSelf.titleLines = titleLines + + return newSelf + } + + /// The title of the preference represented by this row. + /// + /// The title is interpreted as Pango markup unless + /// [property@PreferencesRow:use-markup] is set to `FALSE`. + public func title(_ title: String?) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + + /// Whether the user can copy the title from the label. + /// + /// See also [property@Gtk.Label:selectable]. + public func titleSelectable(_ titleSelectable: Bool? = true) -> Self { + var newSelf = self + newSelf.titleSelectable = titleSelectable + + return newSelf + } + + /// Whether to use Pango markup for the title label. + /// + /// Subclasses may also use it for other labels, such as subtitle. + /// + /// See also [func@Pango.parse_markup]. + public func useMarkup(_ useMarkup: Bool? = true) -> Self { + var newSelf = self + newSelf.useMarkup = useMarkup + + return newSelf + } + + /// Whether an embedded underline in the title indicates a mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// This signal is emitted after the row has been activated. + public func activated(_ activated: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activated = activated + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ToastOverlay.swift b/Sources/Adwaita/View/Generated/ToastOverlay.swift new file mode 100644 index 0000000..6605666 --- /dev/null +++ b/Sources/Adwaita/View/Generated/ToastOverlay.swift @@ -0,0 +1,108 @@ +// +// ToastOverlay.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A widget showing toasts above its content. +/// +/// toast-overlay +/// +/// Much like [class@Gtk.Overlay], `AdwToastOverlay` is a container with a single +/// main child, on top of which it can display a [class@Toast], overlaid. +/// Toasts can be shown with [method@ToastOverlay.add_toast]. +/// +/// See [class@Toast] for details. +/// +/// ## CSS nodes +/// +/// ``` +/// toastoverlay +/// ├── [child] +/// ├── toast +/// ┊ ├── widget +/// ┊ │ ├── [label.heading] +/// │ ╰── [custom title] +/// ├── [button] +/// ╰── button.circular.flat +/// ``` +/// +/// `AdwToastOverlay`'s CSS node is called `toastoverlay`. It contains the child, +/// as well as zero or more `toast` subnodes. +/// +/// Each of the `toast` nodes contains a `widget` subnode, optionally a `button` +/// subnode, and another `button` subnode with `.circular` and `.flat` style +/// classes. +/// +/// The `widget` subnode contains a `label` subnode with the `.heading` style +/// class, or a custom widget provided by the application. +/// +/// ## Accessibility +/// +/// `AdwToastOverlay` uses the `GTK_ACCESSIBLE_ROLE_TAB_GROUP` role. +public struct ToastOverlay: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The child widget. + var child: (() -> Body)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ToastOverlay`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_toast_overlay_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + adw_toast_overlay_set_child(storage.pointer, childStorage.pointer?.cast()) + } + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ToggleButton.swift b/Sources/Adwaita/View/Generated/ToggleButton.swift new file mode 100644 index 0000000..8b9b95f --- /dev/null +++ b/Sources/Adwaita/View/Generated/ToggleButton.swift @@ -0,0 +1,296 @@ +// +// ToggleButton.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A `GtkToggleButton` is a button which remains “pressed-in” when +/// clicked. +/// +/// Clicking again will cause the toggle button to return to its normal state. +/// +/// A toggle button is created by calling either [ctor@Gtk.ToggleButton.new] or +/// [ctor@Gtk.ToggleButton.new_with_label]. If using the former, it is advisable +/// to pack a widget, (such as a `GtkLabel` and/or a `GtkImage`), into the toggle +/// button’s container. (See [class@Gtk.Button] for more information). +/// +/// The state of a `GtkToggleButton` can be set specifically using +/// [method@Gtk.ToggleButton.set_active], and retrieved using +/// [method@Gtk.ToggleButton.get_active]. +/// +/// To simply switch the state of a toggle button, use +/// [method@Gtk.ToggleButton.toggled]. +/// +/// ## Grouping +/// +/// Toggle buttons can be grouped together, to form mutually exclusive +/// groups - only one of the buttons can be toggled at a time, and toggling +/// another one will switch the currently toggled one off. +/// +/// To add a `GtkToggleButton` to a group, use [method@Gtk.ToggleButton.set_group]. +/// +/// ## CSS nodes +/// +/// `GtkToggleButton` has a single CSS node with name button. To differentiate +/// it from a plain `GtkButton`, it gets the `.toggle` style class. +/// +/// ## Accessibility +/// +/// `GtkToggleButton` uses the %GTK_ACCESSIBLE_ROLE_TOGGLE_BUTTON role. +/// +/// ## Creating two `GtkToggleButton` widgets. +/// +/// ```c +/// static void +/// output_state (GtkToggleButton *source, +/// gpointer user_data) +/// { +/// g_print ("Toggle button "%s" is active: %s", +/// gtk_button_get_label (GTK_BUTTON (source)), +/// gtk_toggle_button_get_active (source) ? "Yes" : "No"); +/// } +/// +/// static void +/// make_toggles (void) +/// { +/// GtkWidget *window, *toggle1, *toggle2; +/// GtkWidget *box; +/// const char *text; +/// +/// window = gtk_window_new (); +/// box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); +/// +/// text = "Hi, I’m toggle button one"; +/// toggle1 = gtk_toggle_button_new_with_label (text); +/// +/// g_signal_connect (toggle1, "toggled", +/// G_CALLBACK (output_state), +/// NULL); +/// gtk_box_append (GTK_BOX (box), toggle1); +/// +/// text = "Hi, I’m toggle button two"; +/// toggle2 = gtk_toggle_button_new_with_label (text); +/// g_signal_connect (toggle2, "toggled", +/// G_CALLBACK (output_state), +/// NULL); +/// gtk_box_append (GTK_BOX (box), toggle2); +/// +/// gtk_window_set_child (GTK_WINDOW (window), box); +/// gtk_window_present (GTK_WINDOW (window)); +/// } +/// ``` +public struct ToggleButton: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// If the toggle button should be pressed in. + var active: Binding? + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + /// + /// For text buttons, setting this property will allow ellipsizing the label. + /// + /// If the contents of a button are an icon or a custom widget, setting this + /// property has no effect. + var canShrink: Bool? + /// The child widget. + var child: (() -> Body)? + /// Whether the button has a frame. + var hasFrame: Bool? + /// The name of the icon used to automatically populate the button. + var iconName: String? + /// Text of the label inside the button, if the button contains a label widget. + var label: String? + /// If set, an underline in the text indicates that the following character is + /// to be used as mnemonic. + var useUnderline: Bool? + /// Emitted whenever the `GtkToggleButton`'s state is changed. + var toggled: (() -> Void)? + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect + /// to this signal, but use the [signal@Gtk.Button::clicked] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + var activate: (() -> Void)? + /// Emitted when the button has been activated (pressed and released). + var clicked: (() -> Void)? + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ToggleButton`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(gtk_toggle_button_new()?.opaque()) + update(storage, modifiers: modifiers) + if let childStorage = child?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["child"] = [childStorage] + gtk_button_set_child(storage.pointer?.cast(), childStorage.pointer?.cast()) + } + + + storage.notify(name: "active") { + active?.wrappedValue = gtk_toggle_button_get_active(storage.pointer?.cast()) != 0 + } + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + if let toggled { + storage.connectSignal(name: "toggled") { + toggled() + } + } + if let activate { + storage.connectSignal(name: "activate") { + activate() + } + } + if let clicked { + storage.connectSignal(name: "clicked") { + clicked() + } + } + storage.modify { widget in + if let active { + gtk_toggle_button_set_active(widget?.cast(), active.wrappedValue.cBool) + } + if let canShrink { + gtk_button_set_can_shrink(widget?.cast(), canShrink.cBool) + } + if let widget = storage.content["child"]?.first { + child?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let hasFrame { + gtk_button_set_has_frame(widget?.cast(), hasFrame.cBool) + } + if let iconName { + gtk_button_set_icon_name(widget?.cast(), iconName) + } + if let label, storage.content["child"] == nil { + gtk_button_set_label(widget?.cast(), label) + } + if let useUnderline { + gtk_button_set_use_underline(widget?.cast(), useUnderline.cBool) + } + + + } + for function in updateFunctions { + function(storage) + } + } + + /// If the toggle button should be pressed in. + public func active(_ active: Binding?) -> Self { + var newSelf = self + newSelf.active = active + + return newSelf + } + + /// Whether the size of the button can be made smaller than the natural + /// size of its contents. + /// + /// For text buttons, setting this property will allow ellipsizing the label. + /// + /// If the contents of a button are an icon or a custom widget, setting this + /// property has no effect. + public func canShrink(_ canShrink: Bool? = true) -> Self { + var newSelf = self + newSelf.canShrink = canShrink + + return newSelf + } + + /// The child widget. + public func child(@ViewBuilder _ child: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.child = child + + return newSelf + } + + /// Whether the button has a frame. + public func hasFrame(_ hasFrame: Bool? = true) -> Self { + var newSelf = self + newSelf.hasFrame = hasFrame + + return newSelf + } + + /// The name of the icon used to automatically populate the button. + public func iconName(_ iconName: String?) -> Self { + var newSelf = self + newSelf.iconName = iconName + + return newSelf + } + + /// Text of the label inside the button, if the button contains a label widget. + public func label(_ label: String?) -> Self { + var newSelf = self + newSelf.label = label + + return newSelf + } + + /// If set, an underline in the text indicates that the following character is + /// to be used as mnemonic. + public func useUnderline(_ useUnderline: Bool? = true) -> Self { + var newSelf = self + newSelf.useUnderline = useUnderline + + return newSelf + } + + /// Emitted whenever the `GtkToggleButton`'s state is changed. + public func toggled(_ toggled: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.toggled = toggled + return newSelf + } + + /// Emitted to animate press then release. + /// + /// This is an action signal. Applications should never connect + /// to this signal, but use the [signal@Gtk.Button::clicked] signal. + /// + /// The default bindings for this signal are all forms of the + /// and Enter keys. + public func activate(_ activate: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.activate = activate + return newSelf + } + + /// Emitted when the button has been activated (pressed and released). + public func clicked(_ clicked: @escaping () -> Void) -> Self { + var newSelf = self + newSelf.clicked = clicked + return newSelf + } + +} diff --git a/Sources/Adwaita/View/Generated/ToolbarView.swift b/Sources/Adwaita/View/Generated/ToolbarView.swift new file mode 100644 index 0000000..41cc162 --- /dev/null +++ b/Sources/Adwaita/View/Generated/ToolbarView.swift @@ -0,0 +1,332 @@ +// +// ToolbarView.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A widget containing a page, as well as top and/or bottom bars. +/// +/// toolbar-view +/// +/// `AdwToolbarView` has a single content widget and one or multiple top and +/// bottom bars, shown at the top and bottom sides respectively. +/// +/// Example of an `AdwToolbarView` UI definition: +/// ```xml +/// +/// ``` +/// +/// The following kinds of top and bottom bars are supported: +/// +/// - [class@HeaderBar] +/// - [class@TabBar] +/// - [class@ViewSwitcherBar] +/// - [class@Gtk.ActionBar] +/// - [class@Gtk.HeaderBar] +/// - [class@Gtk.PopoverMenuBar] +/// - [class@Gtk.SearchBar] +/// - Any [class@Gtk.Box] or a similar widget with the +/// [`.toolbar`](style-classes.html#toolbars) style class +/// +/// By default, top and bottom bars are flat and scrolling content has a subtle +/// undershoot shadow, same as when using the +/// [`.undershoot-top`](style-classes.html#undershot-indicators) and +/// [`.undershoot-bottom`](style-classes.html#undershot-indicators) style +/// classes. This works well in most cases, e.g. with [class@StatusPage] or +/// [class@PreferencesPage], where the background at the top and bottom parts of +/// the page is uniform. Additionally, windows with sidebars should always use +/// this style. +/// +/// [property@ToolbarView:top-bar-style] and +/// [property@ToolbarView:bottom-bar-style] properties can be used add an opaque +/// background and a persistent shadow to top and bottom bars, this can be useful +/// for content such as [utility panes](https://developer.gnome.org/hig/patterns/containers/utility-panes.html), +/// where some elements are adjacent to the top/bottom bars, or [class@TabView], +/// where each page can have a different background. +/// +/// toolbar-view-flat-1toolbar-view-flat-2toolbar-view-raised +/// +/// `AdwToolbarView` ensures the top and bottom bars have consistent backdrop +/// styles and vertical spacing. For comparison: +/// +/// toolbar-view-spacingtoolbar-view-spacing-box +/// +/// Any top and bottom bars can also be dragged to move the window, equivalent +/// to putting them into a [class@Gtk.WindowHandle]. +/// +/// Content is typically place between top and bottom bars, but can also extend +/// behind them. This is controlled with the +/// [property@ToolbarView:extend-content-to-top-edge] and +/// [property@ToolbarView:extend-content-to-bottom-edge] properties. +/// +/// Top and bottom bars can be hidden and revealed with an animation using the +/// [property@ToolbarView:reveal-top-bars] and +/// [property@ToolbarView:reveal-bottom-bars] properties. +/// +/// ## `AdwToolbarView` as `GtkBuildable` +/// +/// The `AdwToolbarView` implementation of the [iface@Gtk.Buildable] interface +/// supports adding a top bar by specifying “top” as the “type” attribute of a +/// `` element, or adding a bottom bar by specifying “bottom”. +/// +/// ## Accessibility +/// +/// `AdwToolbarView` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role. +public struct ToolbarView: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The current bottom bar height. + /// + /// Bottom bar height does change depending on + /// [property@ToolbarView:reveal-bottom-bars], including during the transition. + /// + /// See [property@ToolbarView:top-bar-height]. + var bottomBarHeight: Int? + /// The content widget. + var content: (() -> Body)? + /// Whether the content widget can extend behind bottom bars. + /// + /// This can be used in combination with + /// [property@ToolbarView:reveal-bottom-bars] to show and hide toolbars in + /// fullscreen. + /// + /// See [property@ToolbarView:extend-content-to-top-edge]. + var extendContentToBottomEdge: Bool? + /// Whether the content widget can extend behind top bars. + /// + /// This can be used in combination with [property@ToolbarView:reveal-top-bars] + /// to show and hide toolbars in fullscreen. + /// + /// See [property@ToolbarView:extend-content-to-bottom-edge]. + var extendContentToTopEdge: Bool? + /// Whether bottom bars are visible. + /// + /// The transition will be animated. + /// + /// This can be used in combination with + /// [property@ToolbarView:extend-content-to-bottom-edge] to show and hide + /// toolbars in fullscreen. + /// + /// See [property@ToolbarView:reveal-top-bars]. + var revealBottomBars: Bool? + /// Whether top bars are revealed. + /// + /// The transition will be animated. + /// + /// This can be used in combination with + /// [property@ToolbarView:extend-content-to-top-edge] to show and hide toolbars + /// in fullscreen. + /// + /// See [property@ToolbarView:reveal-bottom-bars]. + var revealTopBars: Bool? + /// The current top bar height. + /// + /// Top bar height does change depending [property@ToolbarView:reveal-top-bars], + /// including during the transition. + /// + /// See [property@ToolbarView:bottom-bar-height]. + var topBarHeight: Int? + /// The body for the widget "bottom". + var bottom: () -> Body = { [] } + /// The body for the widget "top". + var top: () -> Body = { [] } + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `ToolbarView`. + public init() { + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_toolbar_view_new()?.opaque()) + update(storage, modifiers: modifiers) + if let contentStorage = content?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["content"] = [contentStorage] + adw_toolbar_view_set_content(storage.pointer, contentStorage.pointer?.cast()) + } + + var bottomStorage: [ViewStorage] = [] + for view in bottom() { + bottomStorage.append(view.storage(modifiers: modifiers)) + adw_toolbar_view_add_bottom_bar(storage.pointer, bottomStorage.last?.pointer?.cast()) + } + storage.content["bottom"] = bottomStorage + var topStorage: [ViewStorage] = [] + for view in top() { + topStorage.append(view.storage(modifiers: modifiers)) + adw_toolbar_view_add_top_bar(storage.pointer, topStorage.last?.pointer?.cast()) + } + storage.content["top"] = topStorage + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + if let widget = storage.content["content"]?.first { + content?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + if let extendContentToBottomEdge { + adw_toolbar_view_set_extend_content_to_bottom_edge(widget, extendContentToBottomEdge.cBool) + } + if let extendContentToTopEdge { + adw_toolbar_view_set_extend_content_to_top_edge(widget, extendContentToTopEdge.cBool) + } + if let revealBottomBars { + adw_toolbar_view_set_reveal_bottom_bars(widget, revealBottomBars.cBool) + } + if let revealTopBars { + adw_toolbar_view_set_reveal_top_bars(widget, revealTopBars.cBool) + } + + if let bottomStorage = storage.content["bottom"] { + for (index, view) in bottom().enumerated() { + if let storage = bottomStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + if let topStorage = storage.content["top"] { + for (index, view) in top().enumerated() { + if let storage = topStorage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + + } + for function in updateFunctions { + function(storage) + } + } + + /// The current bottom bar height. + /// + /// Bottom bar height does change depending on + /// [property@ToolbarView:reveal-bottom-bars], including during the transition. + /// + /// See [property@ToolbarView:top-bar-height]. + public func bottomBarHeight(_ bottomBarHeight: Int?) -> Self { + var newSelf = self + newSelf.bottomBarHeight = bottomBarHeight + + return newSelf + } + + /// The content widget. + public func content(@ViewBuilder _ content: @escaping (() -> Body)) -> Self { + var newSelf = self + newSelf.content = content + + return newSelf + } + + /// Whether the content widget can extend behind bottom bars. + /// + /// This can be used in combination with + /// [property@ToolbarView:reveal-bottom-bars] to show and hide toolbars in + /// fullscreen. + /// + /// See [property@ToolbarView:extend-content-to-top-edge]. + public func extendContentToBottomEdge(_ extendContentToBottomEdge: Bool? = true) -> Self { + var newSelf = self + newSelf.extendContentToBottomEdge = extendContentToBottomEdge + + return newSelf + } + + /// Whether the content widget can extend behind top bars. + /// + /// This can be used in combination with [property@ToolbarView:reveal-top-bars] + /// to show and hide toolbars in fullscreen. + /// + /// See [property@ToolbarView:extend-content-to-bottom-edge]. + public func extendContentToTopEdge(_ extendContentToTopEdge: Bool? = true) -> Self { + var newSelf = self + newSelf.extendContentToTopEdge = extendContentToTopEdge + + return newSelf + } + + /// Whether bottom bars are visible. + /// + /// The transition will be animated. + /// + /// This can be used in combination with + /// [property@ToolbarView:extend-content-to-bottom-edge] to show and hide + /// toolbars in fullscreen. + /// + /// See [property@ToolbarView:reveal-top-bars]. + public func revealBottomBars(_ revealBottomBars: Bool? = true) -> Self { + var newSelf = self + newSelf.revealBottomBars = revealBottomBars + + return newSelf + } + + /// Whether top bars are revealed. + /// + /// The transition will be animated. + /// + /// This can be used in combination with + /// [property@ToolbarView:extend-content-to-top-edge] to show and hide toolbars + /// in fullscreen. + /// + /// See [property@ToolbarView:reveal-bottom-bars]. + public func revealTopBars(_ revealTopBars: Bool? = true) -> Self { + var newSelf = self + newSelf.revealTopBars = revealTopBars + + return newSelf + } + + /// The current top bar height. + /// + /// Top bar height does change depending [property@ToolbarView:reveal-top-bars], + /// including during the transition. + /// + /// See [property@ToolbarView:bottom-bar-height]. + public func topBarHeight(_ topBarHeight: Int?) -> Self { + var newSelf = self + newSelf.topBarHeight = topBarHeight + + return newSelf + } + + /// Set the body for "bottom". + /// - Parameter body: The body. + /// - Returns: The widget. + public func bottom(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.bottom = body + return newSelf + } + /// Set the body for "top". + /// - Parameter body: The body. + /// - Returns: The widget. + public func top(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.top = body + return newSelf + } +} diff --git a/Sources/Adwaita/View/Generated/WindowTitle.swift b/Sources/Adwaita/View/Generated/WindowTitle.swift new file mode 100644 index 0000000..c2833d8 --- /dev/null +++ b/Sources/Adwaita/View/Generated/WindowTitle.swift @@ -0,0 +1,99 @@ +// +// WindowTitle.swift +// Adwaita +// +// Created by auto-generation on 22.01.24. +// + +import CAdw +import LevenshteinTransformations + +/// A helper widget for setting a window's title and subtitle. +/// +/// window-title +/// +/// `AdwWindowTitle` shows a title and subtitle. It's intended to be used as the +/// title child of [class@Gtk.HeaderBar] or [class@HeaderBar]. +/// +/// ## CSS nodes +/// +/// `AdwWindowTitle` has a single CSS node with name `windowtitle`. +public struct WindowTitle: Widget { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + + /// The subtitle to display. + /// + /// The subtitle should give the user additional details. + var subtitle: String + /// The title to display. + /// + /// The title typically identifies the current view or content item, and + /// generally does not use the application name. + var title: String + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + + /// Initialize `WindowTitle`. + public init(subtitle: String, title: String) { + self.subtitle = subtitle + self.title = title + } + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(adw_window_title_new(title, subtitle)?.opaque()) + update(storage, modifiers: modifiers) + + + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { + storage.modify { widget in + adw_window_title_set_subtitle(widget, subtitle) + adw_window_title_set_title(widget, title) + + + } + for function in updateFunctions { + function(storage) + } + } + + /// The subtitle to display. + /// + /// The subtitle should give the user additional details. + public func subtitle(_ subtitle: String) -> Self { + var newSelf = self + newSelf.subtitle = subtitle + + return newSelf + } + + /// The title to display. + /// + /// The title typically identifies the current view or content item, and + /// generally does not use the application name. + public func title(_ title: String) -> Self { + var newSelf = self + newSelf.title = title + + return newSelf + } + +} diff --git a/Sources/Adwaita/View/HStack.swift b/Sources/Adwaita/View/HStack.swift index 9b5fc25..594b706 100644 --- a/Sources/Adwaita/View/HStack.swift +++ b/Sources/Adwaita/View/HStack.swift @@ -5,40 +5,21 @@ // Created by david-swift on 26.09.23. // -import Libadwaita - /// A horizontal GtkBox equivalent. -public struct HStack: Widget { +public struct HStack: View { /// The content. var content: () -> Body + /// The view's body. + public var view: Body { + VStack(horizontal: true, content: content) + } + /// Initialize a `HStack`. /// - Parameter content: The view content. public init(@ViewBuilder content: @escaping () -> Body) { self.content = content } - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - content().update(storage.content[.mainContent] ?? [], modifiers: modifiers) - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let box: Box = .init(horizontal: true) - var content: [ViewStorage] = [] - for element in self.content() { - let widget = element.storage(modifiers: modifiers) - _ = box.append(widget.view) - content.append(widget) - } - return .init(box, content: [.mainContent: content]) - } - } diff --git a/Sources/Adwaita/View/HeaderBar+.swift b/Sources/Adwaita/View/HeaderBar+.swift new file mode 100644 index 0000000..59a4e86 --- /dev/null +++ b/Sources/Adwaita/View/HeaderBar+.swift @@ -0,0 +1,52 @@ +// +// HeaderBar+.swift +// Adwaita +// +// Created by david-swift on 21.01.23. +// + +extension HeaderBar { + + /// Initialize a header bar. + /// - Parameters: + /// - titleButtons: Whether the title buttons (e.g. close button) are visible. + /// - start: The start content. + /// - end: The end content. + public init( + titleButtons: Bool = true, + @ViewBuilder start: @escaping () -> Body, + @ViewBuilder end: @escaping () -> Body + ) { + self.init() + self = self.showStartTitleButtons(titleButtons).showEndTitleButtons(titleButtons) + self = self.start(start).end(end) + } + + /// Initialize an empty header bar. + /// - Returns: The header bar. + public static func empty() -> Self { + .init(start: { }, end: { }) + } + + /// Initialize a header bar with only views at the start. + /// - Parameter start: The views. + /// - Returns: The header bar. + public static func start(@ViewBuilder start: @escaping () -> Body) -> Self { + .init(start: start) { } + } + + /// Initialize a header bar with only views at the end. + /// - Parameter start: The views. + /// - Returns: The header bar. + public static func end(@ViewBuilder end: @escaping () -> Body) -> Self { + .init(start: { }, end: end) + } + + /// Set the title widget for the header bar. + /// - Parameter view: The widget in the header bar. + /// - Returns: The header bar. + public func headerBarTitle(@ViewBuilder view: @escaping () -> Body) -> Self { + titleWidget(view) + } + +} diff --git a/Sources/Adwaita/View/HeaderBar.swift b/Sources/Adwaita/View/HeaderBar.swift deleted file mode 100644 index 38412b5..0000000 --- a/Sources/Adwaita/View/HeaderBar.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// HeaderBar.swift -// Adwaita -// -// Created by david-swift on 23.08.23. -// - -import Libadwaita - -/// A header bar widget. -public struct HeaderBar: Widget { - - /// The start content of the header bar. - var start: Body - /// The end content of the header bar. - var end: Body - /// Whether the title buttons are visible. - var titleButtons: Bool - /// The view acting as the title of the header bar. - var headerBarTitle: Body? - - /// The start content's id. - let startID = "start" - /// The end content's id. - let endID = "end" - /// The title's id. - let titleID = "title" - - /// Initialize a header bar. - /// - Parameters: - /// - titleButtons: Whether the title buttons (e.g. close button) are visible. - /// - start: The start content. - /// - end: The end content. - public init(titleButtons: Bool = true, @ViewBuilder start: () -> Body, @ViewBuilder end: () -> Body) { - self.titleButtons = titleButtons - self.start = start() - self.end = end() - } - - /// Initialize an empty header bar. - /// - Returns: The header bar. - public static func empty() -> Self { - .init(start: { }, end: { }) - } - - /// Initialize a header bar with only views at the start. - /// - Parameter start: The views. - /// - Returns: The header bar. - public static func start(@ViewBuilder start: () -> Body) -> Self { - .init(start: start) { } - } - - /// Initialize a header bar with only views at the end. - /// - Parameter start: The views. - /// - Returns: The header bar. - public static func end(@ViewBuilder end: () -> Body) -> Self { - .init(start: { }, end: end) - } - - /// Update a header bar's view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let bar = storage.view as? Libadwaita.HeaderBar { - _ = bar.showTitleButtons(titleButtons) - } - start.update(storage.content[startID] ?? [], modifiers: modifiers) - end.update(storage.content[endID] ?? [], modifiers: modifiers) - if let first = storage.content[titleID]?.first { - headerBarTitle?.widget(modifiers: modifiers).update(first, modifiers: modifiers) - } - } - - /// Get the container for a header bar. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let bar: Libadwaita.HeaderBar = .init() - var startContent: [ViewStorage] = [] - var endContent: [ViewStorage] = [] - for element in start { - let element = element.storage(modifiers: modifiers) - _ = bar.packStart(element.view) - startContent.append(element) - } - for element in end { - let element = element.storage(modifiers: modifiers) - _ = bar.packEnd(element.view) - endContent.append(element) - } - let title = headerBarTitle?.widget(modifiers: modifiers).container(modifiers: modifiers) - let titleStorage: [ViewStorage] - if let title { - _ = bar.titleWidget(title.view) - titleStorage = [title] - } else { - titleStorage = [] - } - _ = bar.showTitleButtons(titleButtons) - return .init(bar, content: [startID: startContent, endID: endContent, titleID: titleStorage]) - } - - /// Set the title widget for the header bar. - /// - Parameter view: The widget in the header bar. - /// - Returns: The header bar. - public func headerBarTitle(@ViewBuilder view: () -> Body) -> Self { - var newSelf = self - newSelf.headerBarTitle = view() - return newSelf - } - -} diff --git a/Sources/Adwaita/View/List.swift b/Sources/Adwaita/View/List.swift index 41c4e25..5afec94 100644 --- a/Sources/Adwaita/View/List.swift +++ b/Sources/Adwaita/View/List.swift @@ -5,114 +5,59 @@ // Created by david-swift on 25.09.23. // -import LevenshteinTransformations -import Libadwaita +import CAdw /// A list box widget. -public struct List: Widget where Element: Identifiable { +public typealias List = ListBox - /// The elements. - var elements: [Element] - /// The content. - var content: (Element) -> Body - /// The identifier of the selected element. - @Binding var selection: Element.ID +extension List { - /// The identifier of the elements storage. - let elementsID = "elements" + /// The ID for the field storing the selection value. + static var selectionField: String { "selection" } + /// The ID for the field storing the elements. + static var elementsField: String { "element" } /// Initialize `List`. /// - Parameters: /// - elements: The elements. - /// - selection: The identifier of the selected element. + /// - selection: The identifier of the selected element. Selection disabled if `nil`. /// - content: The view for an element. public init( _ elements: [Element], - selection: Binding, + selection: Binding?, @ViewBuilder content: @escaping (Element) -> Body ) { - self.content = content - self.elements = elements - self._selection = selection - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let box = storage.view as? ListBox { - var content: [ViewStorage] = storage.content[.mainContent] ?? [] - updateList(box: box, content: .init { content } set: { content = $0 }, modifiers: modifiers) - storage.content[.mainContent] = content - for (index, element) in elements.enumerated() { - self.content(element).widget(modifiers: modifiers).update(content[index], modifiers: modifiers) + self.init(elements, content: content) + let id: (ViewStorage, [Element]) -> Element.ID? = { storage, elements in + if let row = gtk_list_box_get_selected_row(storage.pointer) { + return elements[safe: .init(gtk_list_box_row_get_index(row))]?.id } + return nil } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let box: ListBox = .init() - var content: [ViewStorage] = [] - updateList(box: box, content: .init { content } set: { content = $0 }, modifiers: modifiers) - _ = box.handler { - let selection = box.getSelectedRow() - if let id = (box.fields[elementsID] as? [Element] ?? elements)[safe: selection]?.id { - self.selection = id + if let selection { + appearFunctions.append { storage in + storage.fields[Self.selectionField] = selection + storage.connectSignal(name: "selected_rows_changed", id: Self.selectionField) { + if let binding = storage.fields[Self.selectionField] as? Binding, + let elements = storage.fields[Self.elementsField] as? [Element], + let id = id(storage, elements) { + binding.wrappedValue = id + } + } } - } - return .init(box, content: [.mainContent: content]) - } - - /// Update the list's content and selection. - /// - Parameters: - /// - box: The list box. - /// - content: The content's view storage. - /// - modifiers: The view modifiers. - func updateList(box: ListBox, content: Binding<[ViewStorage]>, modifiers: [(View) -> View]) { - let old = box.fields[elementsID] as? [Element] ?? [] - old.identifiableTransform( - to: elements, - functions: .init { index, element in - let widget = getWidget(element: element, modifiers: modifiers) - _ = box.remove(at: index) - _ = box.insert(widget.view, at: index) - content.wrappedValue.remove(at: index) - content.wrappedValue.insert(widget, at: index) - } delete: { index in - _ = box.remove(at: index) - content.wrappedValue.remove(at: index) - updateSelection(box: box) - } insert: { index, element in - let widget = getWidget(element: element, modifiers: modifiers) - _ = box.insert(widget.view, at: index) - content.wrappedValue.insert(widget, at: index) + updateFunctions.append { storage in + if selection.wrappedValue != id(storage, elements), + let index = elements.firstIndex(where: { $0.id == selection.wrappedValue })?.cInt { + gtk_list_box_select_row(storage.pointer, gtk_list_box_get_row_at_index(storage.pointer, index)) + } + } + } else { + appearFunctions.append { storage in + gtk_list_box_set_selection_mode(storage.pointer, GTK_SELECTION_NONE) } - ) - box.fields[elementsID] = elements - updateSelection(box: box) - } - - /// Update the list's selection. - /// - Parameter box: The list box. - func updateSelection(box: ListBox) { - if let index = elements.firstIndex(where: { $0.id == selection }) { - box.selectRow(at: index) } } - /// Get the view storage of an element. - /// - Parameters: - /// - element: The element. - /// - modifiers: The modifiers. - /// - Returns: The view storage. - func getWidget(element: Element, modifiers: [(View) -> View]) -> ViewStorage { - self.content(element).widget(modifiers: modifiers).container(modifiers: modifiers) - } - /// Add the "navigation-sidebar" style class. public func sidebarStyle() -> View { style("navigation-sidebar") diff --git a/Sources/Adwaita/View/Menu+.swift b/Sources/Adwaita/View/Menu+.swift new file mode 100644 index 0000000..1b06e64 --- /dev/null +++ b/Sources/Adwaita/View/Menu+.swift @@ -0,0 +1,53 @@ +// +// Menu+.swift +// Adwaita +// +// Created by david-swift on 21.10.23. +// + +import CAdw + +extension Menu { + + // swiftlint:disable function_default_parameter_at_end + /// Initialize a menu button. + /// - Parameters: + /// - label: The button's label. + /// - icon: The button's icon. + /// - app: The application. + /// - window: The application window. + /// - content: The menu's content. + public init( + _ label: String? = nil, + icon: Icon, + app: GTUIApp, + window: GTUIApplicationWindow?, + @MenuBuilder content: @escaping () -> MenuContent + ) { + self.init() + self = self + .child { + ButtonContent() + .iconName(icon.string) + .label(label) + } + .menuModel(app: app, window: window, content) + } + // swiftlint:enable function_default_parameter_at_end + + /// Initialize a menu button. + /// - Parameters: + /// - label: The buttons label. + /// - app: The application. + /// - window: The application window. + /// - content: The menu's content. + public init( + _ label: String, + app: GTUIApp, + window: GTUIApplicationWindow?, + @MenuBuilder content: () -> MenuContent + ) { + self.init() + } + +} diff --git a/Sources/Adwaita/View/Menu.swift b/Sources/Adwaita/View/Menu.swift deleted file mode 100644 index bd532d6..0000000 --- a/Sources/Adwaita/View/Menu.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Menu.swift -// Adwaita -// -// Created by david-swift on 21.10.23. -// - -import Libadwaita - -/// A menu button widget. -public struct Menu: Widget { - - /// The button's label. - var label: String? - /// The button's icon. - var icon: Icon? - /// The menu's content. - var content: MenuContent - /// The application. - var app: GTUIApp - /// The window. - var window: GTUIApplicationWindow? - - // swiftlint:disable function_default_parameter_at_end - /// Initialize a menu button. - /// - Parameters: - /// - label: The button's label. - /// - icon: The button's icon. - /// - app: The application. - /// - window: The application window. - /// - content: The menu's content. - public init( - _ label: String? = nil, - icon: Icon, - app: GTUIApp, - window: GTUIApplicationWindow?, - @MenuBuilder content: () -> MenuContent - ) { - self.label = label - self.icon = icon - self.app = app - self.window = window - self.content = content() - } - // swiftlint:enable function_default_parameter_at_end - - /// Initialize a menu button. - /// - Parameters: - /// - label: The buttons label. - /// - app: The application. - /// - window: The application window. - /// - content: The menu's content. - public init( - _ label: String, - app: GTUIApp, - window: GTUIApplicationWindow?, - @MenuBuilder content: () -> MenuContent - ) { - self.label = label - self.app = app - self.window = window - self.content = content() - } - - /// Update a button's view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let button = storage.view as? Libadwaita.MenuButton { - let content = button.getContent() - if let label { - if icon == nil { - button.setLabel(label) - } else { - content?.setLabel(label) - } - } - if let icon { - content?.setIcon(icon) - } - } - } - - /// Get a button's view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The button's view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let button: Libadwaita.MenuButton - if let icon { - button = .init(label, icon: icon) - } else { - button = .init(label ?? .init()) - } - for element in content { - element.addMenuItems(menu: button.getMenu(), app: app, window: window) - } - return .init(button) - } - -} diff --git a/Sources/Adwaita/View/Modifiers/AppearObserver.swift b/Sources/Adwaita/View/Modifiers/AppearObserver.swift index e8fbf4b..a9569e4 100644 --- a/Sources/Adwaita/View/Modifiers/AppearObserver.swift +++ b/Sources/Adwaita/View/Modifiers/AppearObserver.swift @@ -5,13 +5,13 @@ // Created by david-swift on 29.11.23. // -import Libadwaita +import CAdw /// A widget which executes a custom code when being rendered for the first time. struct AppearObserver: Widget { /// The function. - var onAppear: (NativeWidgetPeer) -> Void + var onAppear: (ViewStorage) -> Void /// The content. var content: View @@ -20,7 +20,7 @@ struct AppearObserver: Widget { /// - Returns: The content's container. func container(modifiers: [(View) -> View]) -> ViewStorage { let storage = content.storage(modifiers: modifiers) - onAppear(storage.view) + onAppear(storage) return storage } @@ -39,7 +39,7 @@ extension View { /// Run a function on the widget when it appears for the first time. /// - Parameter closure: The function. /// - Returns: A view. - public func inspectOnAppear(_ closure: @escaping (NativeWidgetPeer) -> Void) -> View { + public func inspectOnAppear(_ closure: @escaping (ViewStorage) -> Void) -> View { AppearObserver(onAppear: closure, content: self) } @@ -54,7 +54,12 @@ extension View { /// - Parameter handler: The function. /// - Returns: A view. public func onClick(handler: @escaping () -> Void) -> View { - inspectOnAppear { _ = $0.onClick(closure: handler) } + inspectOnAppear { storage in + let controller = ViewStorage(gtk_gesture_click_new()) + gtk_widget_add_controller(storage.pointer?.cast(), controller.pointer) + storage.fields["controller"] = controller + controller.connectSignal(name: "stopped", argCount: 0, handler: handler) + } } } diff --git a/Sources/Adwaita/View/Modifiers/Clamp+.swift b/Sources/Adwaita/View/Modifiers/Clamp+.swift new file mode 100644 index 0000000..c66ec4f --- /dev/null +++ b/Sources/Adwaita/View/Modifiers/Clamp+.swift @@ -0,0 +1,52 @@ +// +// Clamp+.swift +// Adwaita +// +// Created by david-swift on 20.01.24. +// + +import CAdw + +extension Clamp { + + /// Initialize either a horizontal or vertical clamp. + /// - Parameter vertical: Whether it is a vertical clamp. + init(vertical: Bool) { + self.init() + if vertical { + appearFunctions.append { storage in + gtk_orientable_set_orientation(storage.pointer, GTK_ORIENTATION_VERTICAL) + } + } + } + +} + +extension View { + + /// Set the view's maximum width. + /// - Parameter maxSize: The maximum width. + /// - Returns: A view. + public func frame(maxSize: Int? = nil) -> Clamp { + frame(maxWidth: maxSize) + } + + /// Set the view's maximum width. + /// - Parameter maxWidth: The maximum width. + /// - Returns: A view. + public func frame(maxWidth: Int? = nil) -> Clamp { + .init() + .child { self } + .maximumSize(maxWidth ?? -1) + } + + /// Set the view's maximum height. + /// - Parameter maxHeight: The maximum height. + /// - Returns: A view. + public func frame(maxHeight: Int? = nil) -> Clamp { + .init(vertical: true) + .child { self } + .maximumSize(maxHeight ?? -1) + } + +} diff --git a/Sources/Adwaita/View/Modifiers/Clamp.swift b/Sources/Adwaita/View/Modifiers/Clamp.swift deleted file mode 100644 index 25a9f80..0000000 --- a/Sources/Adwaita/View/Modifiers/Clamp.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Clamp.swift -// Adwaita -// -// Created by david-swift on 12.10.23. -// - -import Libadwaita - -/// A horizontal AdwClamp equivalent. -struct Clamp: Widget { - - /// The content. - var content: View - /// The maximum size. - var maxSize: Int - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let clamp = storage.view as? Libadwaita.Clamp { - _ = clamp.maximumSize(maxSize) - } - if let storage = storage.content[.mainContent]?[safe: 0] { - content.widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - func container(modifiers: [(View) -> View]) -> ViewStorage { - let container = content.storage(modifiers: modifiers) - let clamp: Libadwaita.Clamp = .init(container.view) - _ = clamp.maximumSize(maxSize) - return .init(clamp, content: [.mainContent: [container]]) - } - -} - -extension View { - - /// Set the view's maximal size. - /// - Parameter maxSize: The maximal size. - /// - Returns: A view. - public func frame(maxSize: Int? = nil) -> View { - Clamp(content: self, maxSize: maxSize ?? -1) - } - -} diff --git a/Sources/Adwaita/View/Modifiers/ContentModifier.swift b/Sources/Adwaita/View/Modifiers/ContentModifier.swift index 74d6802..8614ff3 100644 --- a/Sources/Adwaita/View/Modifiers/ContentModifier.swift +++ b/Sources/Adwaita/View/Modifiers/ContentModifier.swift @@ -5,8 +5,6 @@ // Created by david-swift on 11.11.23. // -import Libadwaita - /// A widget which replaces views of a specific type in its content. struct ContentModifier: Widget where Content: View { diff --git a/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift b/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift index dddf401..41ad057 100644 --- a/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift +++ b/Sources/Adwaita/View/Modifiers/InspectorWrapper.swift @@ -5,13 +5,13 @@ // Created by david-swift on 10.09.23. // -import Libadwaita +import CAdw /// A widget which executes a custom code on the GTUI widget when being created and updated. struct InspectorWrapper: Widget { /// The custom code to edit the widget. - var modify: (NativeWidgetPeer?) -> Void + var modify: (ViewStorage) -> Void /// The wrapped view. var content: View @@ -20,7 +20,7 @@ struct InspectorWrapper: Widget { /// - Returns: The content's container. func container(modifiers: [(View) -> View]) -> ViewStorage { let storage = content.storage(modifiers: modifiers) - modify(storage.view) + modify(storage) return storage } @@ -30,7 +30,7 @@ struct InspectorWrapper: Widget { /// - modifiers: Modify views before being updated. func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { content.updateStorage(storage, modifiers: modifiers) - modify(storage.view) + modify(storage) } } @@ -40,7 +40,7 @@ extension View { /// Modify a GTUI widget before being displayed and when being updated. /// - Parameter modify: Modify the widget. /// - Returns: A view. - public func inspect(_ modify: @escaping (NativeWidgetPeer?) -> Void) -> View { + public func inspect(_ modify: @escaping (ViewStorage) -> Void) -> View { InspectorWrapper(modify: modify, content: self) } @@ -50,35 +50,40 @@ extension View { /// - edges: The edges which are affected by the padding. /// - Returns: A view. public func padding(_ padding: Int = 10, _ edges: Set = .all) -> View { - inspect { _ = $0?.padding(padding, edges) } + inspect { widget in + if edges.contains(.leading) { gtk_widget_set_margin_start(widget.pointer?.cast(), padding.cInt) } + if edges.contains(.trailing) { gtk_widget_set_margin_end(widget.pointer?.cast(), padding.cInt) } + if edges.contains(.top) { gtk_widget_set_margin_top(widget.pointer?.cast(), padding.cInt) } + if edges.contains(.bottom) { gtk_widget_set_margin_bottom(widget.pointer?.cast(), padding.cInt) } + } } /// Enable or disable the horizontal expansion. /// - Parameter enabled: Whether it is enabled or disabled. /// - Returns: A view. public func hexpand(_ enabled: Bool = true) -> View { - inspect { _ = $0?.hexpand() } + inspect { gtk_widget_set_hexpand($0.pointer?.cast(), enabled.cBool) } } /// Enable or disable the vertical expansion. /// - Parameter enabled: Whether it is enabled or disabled. /// - Returns: A view. public func vexpand(_ enabled: Bool = true) -> View { - inspect { _ = $0?.vexpand() } + inspect { gtk_widget_set_vexpand($0.pointer?.cast(), enabled.cBool) } } /// Set the horizontal alignment. /// - Parameter align: The alignment. /// - Returns: A view. public func halign(_ align: Alignment) -> View { - inspect { _ = $0?.halign(align) } + inspect { gtk_widget_set_halign($0.pointer?.cast(), align.cAlign) } } /// Set the vertical alignment. /// - Parameter align: The alignment. /// - Returns: A view. public func valign(_ align: Alignment) -> View { - inspect { _ = $0?.valign(align) } + inspect { gtk_widget_set_valign($0.pointer?.cast(), align.cAlign) } } /// Set the view's minimal width or height. @@ -87,28 +92,28 @@ extension View { /// - minHeight: The minimal height. /// - Returns: A view. public func frame(minWidth: Int? = nil, minHeight: Int? = nil) -> View { - inspect { _ = $0?.frame(minWidth: minWidth, minHeight: minHeight) } + inspect { gtk_widget_set_size_request($0.pointer?.cast(), minWidth?.cInt ?? 1, minHeight?.cInt ?? -1) } } /// Set the view's transition. /// - Parameter transition: The transition. /// - Returns: A view. public func transition(_ transition: Transition) -> View { - inspect { $0?.fields[.transition] = transition } + inspect { $0.fields[.transition] = transition } } /// Set the view's navigation title. /// - Parameter label: The navigation title. /// - Returns: A view. public func navigationTitle(_ label: String) -> View { - inspect { $0?.fields[.navigationLabel] = label } + inspect { $0.fields[.navigationLabel] = label } } /// Add a style class to the view. /// - Parameter style: The style class. /// - Returns: A view. public func style(_ style: String) -> View { - inspect { _ = $0?.addStyle(style) } + inspect { gtk_widget_add_css_class($0.pointer?.cast(), style) } } /// Run a function when the view gets an update. @@ -122,16 +127,7 @@ extension View { /// - Parameter insensitive: Whether the view is insensitive. /// - Returns: A view. public func insensitive(_ insensitive: Bool = true) -> View { - inspect { _ = $0?.sensitive(!insensitive) } + inspect { gtk_widget_set_sensitive($0.pointer?.cast(), insensitive ? 0 : 1) } } } - -/// The alignment for a widget. -public typealias Alignment = Libadwaita.Alignment -/// The edges of a widget. -public typealias Edge = Libadwaita.Edge -/// An icon. -public typealias Icon = Libadwaita.Icon -/// A transition for a stack. -public typealias Transition = Libadwaita.Transition diff --git a/Sources/Adwaita/View/Modifiers/ModifierStopper.swift b/Sources/Adwaita/View/Modifiers/ModifierStopper.swift index a6dc3cd..c4b8c26 100644 --- a/Sources/Adwaita/View/Modifiers/ModifierStopper.swift +++ b/Sources/Adwaita/View/Modifiers/ModifierStopper.swift @@ -5,8 +5,6 @@ // Created by david-swift on 11.11.23. // -import Libadwaita - /// Remove all of the content modifiers for the wrapped views. struct ModifierStopper: Widget { diff --git a/Sources/Adwaita/View/Modifiers/Overlay.swift b/Sources/Adwaita/View/Modifiers/Overlay.swift deleted file mode 100644 index 9e95aaf..0000000 --- a/Sources/Adwaita/View/Modifiers/Overlay.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Overlay.swift -// Adwaita -// -// Created by david-swift on 03.01.24. -// - -import Libadwaita - -/// A wrapper around a view for adding other views on top. -struct Overlay: Widget { - - /// The child view. - var child: View - /// The overlay view. - var overlay: Body - - /// The identifier for the overlay content. - let overlayID = "overlay" - - /// Get the overlay's view storage. - /// - Parameter modifiers: The view modifiers. - /// - Returns: The view storage. - func container(modifiers: [(View) -> View]) -> ViewStorage { - let contentStorage = child.storage(modifiers: modifiers) - let overlayStorage = overlay.widget(modifiers: modifiers).storage(modifiers: modifiers) - let overlay = Libadwaita.Overlay().child(contentStorage.view).addOverlay(overlayStorage.view) - return .init(overlay, content: [.mainContent: [contentStorage], overlayID: [overlayStorage]]) - } - - /// Update the overlay's view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: The view modifiers. - func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let storage = storage.content[.mainContent]?.first { - child.updateStorage(storage, modifiers: modifiers) - } - if let storage = storage.content[overlayID]?.first { - overlay.widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - } - -} - -extension View { - - /// Add an overlay view. - /// - Parameters: - /// - overlay: The overlay view. - /// - Returns: A view. - public func overlay(@ViewBuilder _ overlay: @escaping () -> Body) -> View { - Overlay(child: self, overlay: overlay()) - } - -} diff --git a/Sources/Adwaita/View/Modifiers/ToastOverlay+.swift b/Sources/Adwaita/View/Modifiers/ToastOverlay+.swift new file mode 100644 index 0000000..ced3207 --- /dev/null +++ b/Sources/Adwaita/View/Modifiers/ToastOverlay+.swift @@ -0,0 +1,78 @@ +// +// ToastOverlay+.swift +// Adwaita +// +// Created by david-swift on 21.01.24. +// + +import CAdw +import Foundation + +/// A wrapper around a view presenting toasts. +extension ToastOverlay { + + /// Initialize a toast overlay. + /// - Parameters: + /// - title: The toast's title. + /// - signal: The signal for adding a toast. + public init(_ title: String, signal: Signal) { + appearFunctions.append { storage in + storage.fields["signal"] = signal + } + updateFunctions.append { storage in + if let signal = storage.fields["signal"] as? Signal, signal.update { + let toast = ViewStorage(adw_toast_new(title)) + storage.fields[UUID().uuidString] = toast + if let button = storage.fields["button"] as? String, + let handler = storage.fields["handler"] as? () -> Void { + adw_toast_set_button_label(toast.pointer, button) + toast.connectSignal(name: "button-clicked", handler: handler) + } + adw_toast_overlay_add_toast(storage.pointer, toast.pointer) + } + } + } + + /// Add an action button to the toast. + /// - Parameters: + /// - button: The button's label. + /// - handler: The handler. + /// - Returns: The toast overlay. + public func action(button: String, handler: @escaping () -> Void) -> Self { + var newSelf = self + let action: (ViewStorage) -> Void = { storage in + storage.fields["button"] = button + storage.fields["handler"] = handler + } + newSelf.updateFunctions.insert(action, at: 0) + return newSelf + } + +} + +extension View { + + /// Present a toast when the signal gets activated. + /// - Parameters: + /// - title: The title of the toast. + /// - signal: The signal which activates the presentation of a toast. + /// - Returns: A view. + public func toast(_ title: String, signal: Signal) -> View { + ToastOverlay(title, signal: signal) + .child { self } + } + + /// Present a toast with a button when the signal gets activated. + /// - Parameters: + /// - title: The title of the toast. + /// - signal: The signal which activates the presentation of a toast. + /// - button: The button's label. + /// - handler: The handler for the button. + /// - Returns: A view. + public func toast(_ title: String, signal: Signal, button: String, handler: @escaping () -> Void) -> View { + ToastOverlay(title, signal: signal) + .child { self } + .action(button: button, handler: handler) + } + +} diff --git a/Sources/Adwaita/View/Modifiers/ToastOverlay.swift b/Sources/Adwaita/View/Modifiers/ToastOverlay.swift deleted file mode 100644 index 5266519..0000000 --- a/Sources/Adwaita/View/Modifiers/ToastOverlay.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// ToastOverlay.swift -// Adwaita -// -// Created by david-swift on 30.11.23. -// - -import Libadwaita - -/// A wrapper around a view presenting toasts. -struct ToastOverlay: Widget { - - /// The signal for showing the toast./// Present a toast when the signal gets activated. - var signal: Signal - /// The child view. - var child: View - /// The title of the toast. - var title: String - /// Information about the button if available (label and handler). - var button: (String, () -> Void)? - - /// Get the overlay's view storage. - /// - Parameter modifiers: The view modifiers. - /// - Returns: The view storage. - func container(modifiers: [(View) -> View]) -> ViewStorage { - let contentStorage = child.widget(modifiers: modifiers).storage(modifiers: modifiers) - let overlay = Libadwaita.ToastOverlay(contentStorage.view) - setVisibility(overlay) - return .init(overlay, content: [.mainContent: [contentStorage]]) - } - - /// Update the overlay's view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: The view modifiers. - func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let overlay = storage.view as? Libadwaita.ToastOverlay { - setVisibility(overlay) - } - if let storage = storage.content[.mainContent]?.first { - child.widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - } - - /// Add a toast if the signal is active. - /// - Parameter overlay: The toast overlay. - func setVisibility(_ overlay: Libadwaita.ToastOverlay) { - if signal.update { - let toast = Toast(title) - if let button { - _ = toast - .buttonLabel(button.0) - .buttonHandler(button.1) - } - overlay.addToast(toast) - } - } - -} - -extension View { - - /// Present a toast when the signal gets activated. - /// - Parameters: - /// - title: The title of the toast. - /// - signal: The signal which activates the presentation of a toast. - /// - Returns: A view. - public func toast(_ title: String, signal: Signal) -> View { - ToastOverlay(signal: signal, child: self, title: title) - } - - /// Present a toast with a button when the signal gets activated. - /// - Parameters: - /// - title: The title of the toast. - /// - signal: The signal which activates the presentation of a toast. - /// - button: The button's label. - /// - handler: The handler for the button. - /// - Returns: A view. - public func toast(_ title: String, signal: Signal, button: String, handler: @escaping () -> Void) -> View { - ToastOverlay(signal: signal, child: self, title: title, button: (button, handler)) - } - -} diff --git a/Sources/Adwaita/View/Modifiers/ToolbarView.swift b/Sources/Adwaita/View/Modifiers/ToolbarView.swift deleted file mode 100644 index 03a710a..0000000 --- a/Sources/Adwaita/View/Modifiers/ToolbarView.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// ToolbarView.swift -// Adwaita -// -// Created by david-swift on 24.09.23. -// - -import Libadwaita - -/// A toolbar view widget. -struct ToolbarView: Widget { - - /// The sidebar's content. - var content: View - /// The toolbars. - var toolbar: () -> Body - /// Whether the toolbars are bottom toolbars. - var bottom: Bool - /// Whether the toolbar is visible. - var visible: Bool - - /// The identifier of the toolbar content. - let toolbarID = "toolbar" - - /// Get the container of the toolbar view widget. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - func container(modifiers: [(View) -> View]) -> ViewStorage { - let content = content.storage(modifiers: modifiers) - let view = Libadwaita.ToolbarView(content.view) - var toolbarContent: [ViewStorage] = [] - for item in toolbar() { - let storage = item.storage(modifiers: modifiers) - toolbarContent.append(storage) - if bottom { - _ = view.addBottomBar(storage.view) - } else { - _ = view.addTopBar(storage.view) - } - } - if bottom { - view.setRevealBottomBar(visible) - } else { - view.setRevealTopBar(visible) - } - return .init(view, content: [.mainContent: [content], toolbarID: toolbarContent]) - } - - /// Update the view storage of the toolbar view widget. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let mainContent = storage.content[.mainContent]?.first { - content.widget(modifiers: modifiers).update(mainContent, modifiers: modifiers) - } - if let toolbar = storage.content[toolbarID] { - for (index, content) in toolbar.enumerated() { - self.toolbar()[safe: index]?.updateStorage(content, modifiers: modifiers) - } - } - if let view = storage.view as? Libadwaita.ToolbarView { - if bottom { - view.setRevealBottomBar(visible) - } else { - view.setRevealTopBar(visible) - } - } - } - -} - -extension View { - - /// Add a top toolbar to the view. - /// - Parameters: - /// - toolbar: The toolbar's content. - /// - visible: Whether the toolbar is visible. - /// - Returns: A view. - public func topToolbar(visible: Bool = true, @ViewBuilder _ toolbar: @escaping () -> Body) -> View { - ToolbarView(content: self, toolbar: toolbar, bottom: false, visible: visible) - } - - /// Add a bottom toolbar to the view. - /// - Parameters: - /// - toolbar: The toolbar's content. - /// - visible: Whether the toolbar is visible. - /// - Returns: A view. - public func bottomToolbar(visible: Bool = true, @ViewBuilder _ toolbar: @escaping () -> Body) -> View { - ToolbarView(content: self, toolbar: toolbar, bottom: true, visible: visible) - } - -} diff --git a/Sources/Adwaita/View/Modifiers/View+.swift b/Sources/Adwaita/View/Modifiers/View+.swift index e2ebd3e..b43ada2 100644 --- a/Sources/Adwaita/View/Modifiers/View+.swift +++ b/Sources/Adwaita/View/Modifiers/View+.swift @@ -21,4 +21,38 @@ extension View { .halign(.center) } + /// Add a top toolbar to the view. + /// - Parameters: + /// - toolbar: The toolbar's content. + /// - visible: Whether the toolbar is visible. + /// - Returns: A view. + public func topToolbar(visible: Bool = true, @ViewBuilder _ toolbar: @escaping () -> Body) -> View { + ToolbarView() + .content { self } + .top(toolbar) + .revealTopBars(visible) + } + + /// Add a bottom toolbar to the view. + /// - Parameters: + /// - toolbar: The toolbar's content. + /// - visible: Whether the toolbar is visible. + /// - Returns: A view. + public func bottomToolbar(visible: Bool = true, @ViewBuilder _ toolbar: @escaping () -> Body) -> View { + ToolbarView() + .content { self } + .bottom(toolbar) + .revealBottomBars(visible) + } + + /// Add an overlay view. + /// - Parameters: + /// - overlay: The overlay view. + /// - Returns: A view. + public func overlay(@ViewBuilder _ overlay: @escaping () -> Body) -> View { + Overlay() + .child { self } + .overlay(overlay) + } + } diff --git a/Sources/Adwaita/View/NavigationSplitView.swift b/Sources/Adwaita/View/NavigationSplitView.swift index 5a1b449..a3f2016 100644 --- a/Sources/Adwaita/View/NavigationSplitView.swift +++ b/Sources/Adwaita/View/NavigationSplitView.swift @@ -5,7 +5,7 @@ // Created by david-swift on 24.09.23. // -import Libadwaita +import CAdw /// A navigation split view widget. public struct NavigationSplitView: Widget { @@ -33,20 +33,22 @@ public struct NavigationSplitView: Widget { /// - Parameter modifiers: Modify views before being updated. /// - Returns: The view storage. public func container(modifiers: [(View) -> View]) -> ViewStorage { - let splitView: Libadwaita.NavigationSplitView = .init() + let splitView = adw_navigation_split_view_new() var content: [String: [ViewStorage]] = [:] let sidebar = sidebar().widget(modifiers: modifiers).container(modifiers: modifiers) - let label = sidebar.view.fields[.navigationLabel] as? String ?? "" - _ = splitView.sidebar(sidebar.view, title: label) + let label = sidebar.fields[.navigationLabel] as? String ?? "" + let sidebarPage = adw_navigation_page_new(sidebar.pointer?.cast(), label) + adw_navigation_split_view_set_sidebar(.init(splitView), sidebarPage?.cast()) content[sidebarID] = [sidebar] let mainContent = self.content().widget(modifiers: modifiers).container(modifiers: modifiers) - let mainLabel = mainContent.view.fields[.navigationLabel] as? String ?? "" - _ = splitView.content(mainContent.view, title: mainLabel) + let mainLabel = mainContent.fields[.navigationLabel] as? String ?? "" + let mainPage = adw_navigation_page_new(mainContent.pointer?.cast(), mainLabel) + adw_navigation_split_view_set_content(.init(splitView), mainPage?.cast()) content[contentID] = [mainContent] - return .init(splitView, content: content) + return .init(.init(splitView), content: content) } /// Update the view storage of the navigation split view widget. diff --git a/Sources/Adwaita/View/OverlaySplitView+.swift b/Sources/Adwaita/View/OverlaySplitView+.swift new file mode 100644 index 0000000..e0d1ac3 --- /dev/null +++ b/Sources/Adwaita/View/OverlaySplitView+.swift @@ -0,0 +1,39 @@ +// +// OverlaySplitView+.swift +// Adwaita +// +// Created by david-swift on 21.01.23. +// + +import CAdw + +extension OverlaySplitView { + + /// Initialize an overlay split view. + /// - Parameters: + /// - visible: Whether the sidebar is visible. + /// - sidebar: The sidebar content. + /// - content: The main content. + public init( + visible: Binding = .constant(true), + @ViewBuilder sidebar: @escaping () -> Body, + @ViewBuilder content: @escaping () -> Body + ) { + self.init() + self = self.sidebar(sidebar) + self = self.content(content) + self = self.showSidebar(visible) + } + + /// The position of the sidebar. + /// - Parameter trailing: Whether the sidebar is at the trailing position. + /// - Returns: The navigation split view. + public func trailingSidebar(_ trailing: Bool = true) -> Self { + var newSelf = self + newSelf.updateFunctions.append { storage in + adw_overlay_split_view_set_sidebar_position(storage.pointer, trailing ? GTK_PACK_END : GTK_PACK_START) + } + return newSelf + } + +} diff --git a/Sources/Adwaita/View/OverlaySplitView.swift b/Sources/Adwaita/View/OverlaySplitView.swift deleted file mode 100644 index 678eda0..0000000 --- a/Sources/Adwaita/View/OverlaySplitView.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// OverlaySplitView.swift -// Adwaita -// -// Created by david-swift on 19.12.23. -// - -import Libadwaita - -/// An overlay split view widget. -public struct OverlaySplitView: Widget { - - /// The sidebar's content. - var sidebar: () -> Body - /// The split view's main content. - var content: () -> Body - /// Whether the sidebar is at the trailing position. - var trailing = false - /// Whether the sidebar is visible. - var visible: Bool - - /// The sidebar content's id. - let sidebarID = "sidebar" - /// The main content's id. - let contentID = "content" - - /// Initialize an overlay split view. - /// - Parameters: - /// - visible: Whether the sidebar is visible. - /// - sidebar: The sidebar content. - /// - content: The main content. - public init( - visible: Bool = true, - @ViewBuilder sidebar: @escaping () -> Body, - @ViewBuilder content: @escaping () -> Body - ) { - self.sidebar = sidebar - self.content = content - self.visible = visible - } - - /// The position of the sidebar. - /// - Parameter trailing: Whether the sidebar is at the trailing position. - /// - Returns: The navigation split view. - public func trailingSidebar(_ trailing: Bool = true) -> Self { - var newSelf = self - newSelf.trailing = trailing - return newSelf - } - - /// Get the container of the overlay split view widget. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let splitView: Libadwaita.OverlaySplitView = .init() - var content: [String: [ViewStorage]] = [:] - - let sidebar = sidebar().widget(modifiers: modifiers).container(modifiers: modifiers) - _ = splitView.sidebar(sidebar.view) - content[sidebarID] = [sidebar] - - let mainContent = self.content().widget(modifiers: modifiers).container(modifiers: modifiers) - _ = splitView.content(mainContent.view) - content[contentID] = [mainContent] - - updatePosition(splitView) - - return .init(splitView, content: content) - } - - /// Update the view storage of the overlay split view widget. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let storage = storage.content[contentID]?[safe: 0] { - content().widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - if let storage = storage.content[sidebarID]?[safe: 0] { - sidebar().widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - if let splitView = storage.view as? Libadwaita.OverlaySplitView { - updatePosition(splitView) - } - } - - /// Update the sidebar's position in the split view. - /// - Parameter splitView: The overlay split view. - func updatePosition(_ splitView: Libadwaita.OverlaySplitView) { - _ = splitView.position(trailing: trailing) - if visible { - splitView.showSidebar() - } else { - splitView.hideSidebar() - } - } - -} diff --git a/Sources/Adwaita/View/ProgressBar+.swift b/Sources/Adwaita/View/ProgressBar+.swift new file mode 100644 index 0000000..ee5166e --- /dev/null +++ b/Sources/Adwaita/View/ProgressBar+.swift @@ -0,0 +1,23 @@ +// +// Progressbar+.swift +// Adwaita +// +// Created by david-swift on 15.01.24. +// + +extension ProgressBar { + + /// Initialize a progress bar widget. + /// - Parameters: + /// - value: The value. + /// - total: The maximum value. + public init(value: Double, total: Double) { + self.init() + if total != 0 { + self = self.fraction(value / total) + } else { + self = self.fraction(0) + } + } + +} diff --git a/Sources/Adwaita/View/ProgressBar.swift b/Sources/Adwaita/View/ProgressBar.swift deleted file mode 100644 index 4367c3e..0000000 --- a/Sources/Adwaita/View/ProgressBar.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Progressbar.swift -// Adwaita -// -// Created by david-swift on 03.01.24. -// - -import Libadwaita - -/// A progress bar widget. -public struct ProgressBar: Widget { - - /// The value. - var value: Double - - /// Initialize a progress bar widget. - /// - Parameters: - /// - value: The value. - /// - total: The maximum value. - public init(value: Double, total: Double) { - if total != 0 { - self.value = value / total - } else { - self.value = 0 - } - } - - /// Update the view storage of the progress bar widget. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let bar = storage.view as? Libadwaita.ProgressBar { - _ = bar.fraction(value) - } - } - - /// Get the container of the progress bar widget. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let bar = Libadwaita.ProgressBar().fraction(value) - _ = bar.sensitive(false) - return .init(bar) - } - -} diff --git a/Sources/Adwaita/View/ScrollView.swift b/Sources/Adwaita/View/ScrollView.swift index d4482a8..3815667 100644 --- a/Sources/Adwaita/View/ScrollView.swift +++ b/Sources/Adwaita/View/ScrollView.swift @@ -5,36 +5,16 @@ // Created by david-swift on 26.09.23. // -import Libadwaita - /// A GtkScrolledWindow equivalent. -public struct ScrollView: Widget { +public typealias ScrollView = ScrolledWindow - /// The content. - var content: () -> Body +extension ScrollView { /// Initialize a `ScrollView`. /// - Parameter content: The view content. public init(@ViewBuilder content: @escaping () -> Body) { - self.content = content - } - - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let first = storage.content[.mainContent]?.first { - content().widget(modifiers: modifiers).update(first, modifiers: modifiers) - } - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let container = content().widget(modifiers: modifiers).container(modifiers: modifiers) - return .init(Scrolled().setChild(container.view), content: [.mainContent: [container]]) + self.init() + self = self.child(content) } } diff --git a/Sources/Adwaita/View/StateWrapper.swift b/Sources/Adwaita/View/StateWrapper.swift index aa2350d..317d011 100644 --- a/Sources/Adwaita/View/StateWrapper.swift +++ b/Sources/Adwaita/View/StateWrapper.swift @@ -5,8 +5,6 @@ // Created by david-swift on 26.09.23. // -import Libadwaita - /// A storage for `@State` properties. public struct StateWrapper: Widget { @@ -50,7 +48,7 @@ public struct StateWrapper: Widget { /// - Returns: The view storage. public func container(modifiers: [(View) -> View]) -> ViewStorage { let content = content().widget(modifiers: modifiers).container(modifiers: modifiers) - return .init(content.view, content: [.mainContent: [content]], state: state) + return .init(content.pointer, content: [.mainContent: [content]], state: state) } } diff --git a/Sources/Adwaita/View/StatusPage+.swift b/Sources/Adwaita/View/StatusPage+.swift new file mode 100644 index 0000000..472e9ae --- /dev/null +++ b/Sources/Adwaita/View/StatusPage+.swift @@ -0,0 +1,29 @@ +// +// StatusPage+.swift +// Adwaita +// +// Created by david-swift on 17.01.23. +// + +extension StatusPage { + + /// Initialize a status page widget. + /// - Parameters: + /// - title: The title. + /// - icon: The icon. + /// - description: Additional details. + /// - content: Additional content. + public init( + _ title: String, + icon: Icon? = nil, + description: String = "", + @ViewBuilder content: @escaping () -> Body = { [] } + ) { + self.init() + self = self.title(title) + self = self.description(description) + self = self.iconName(icon?.string ?? "") + self = self.child(content) + } + +} diff --git a/Sources/Adwaita/View/StatusPage.swift b/Sources/Adwaita/View/StatusPage.swift deleted file mode 100644 index 9b598df..0000000 --- a/Sources/Adwaita/View/StatusPage.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// StatusPage.swift -// Adwaita -// -// Created by david-swift on 25.09.23. -// - -import Libadwaita - -/// A status page widget. -public struct StatusPage: Widget { - - /// The title. - var title: String - /// The description. - var description: String - /// The icon. - var icon: Icon - /// Additional content. - var content: Body - - /// Initialize a status page widget. - /// - Parameters: - /// - title: The title. - /// - icon: The icon. - /// - description: Additional details. - /// - content: Additional content. - public init( - _ title: String, - icon: Icon? = nil, - description: String = "", - @ViewBuilder content: () -> Body = { [] } - ) { - self.title = title - self.description = description - self.icon = icon ?? .custom(name: "") - self.content = content() - } - - /// Update the view storage of the text widget. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let statusPage = storage.view as? Libadwaita.StatusPage { - _ = statusPage.title(title).description(description).icon(icon) - } - if let storage = storage.content[.mainContent]?.first { - content.widget(modifiers: modifiers).update(storage, modifiers: modifiers) - } - } - - /// Get the container of the text widget. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let child = content.widget(modifiers: []).container(modifiers: modifiers) - return .init( - Libadwaita.StatusPage().title(title).description(description).icon(icon).child(child.view), - content: [.mainContent: [child]] - ) - } - -} diff --git a/Sources/Adwaita/View/Text.swift b/Sources/Adwaita/View/Text.swift index ee6e5d8..f723212 100644 --- a/Sources/Adwaita/View/Text.swift +++ b/Sources/Adwaita/View/Text.swift @@ -5,46 +5,15 @@ // Created by david-swift on 23.08.23. // -import Libadwaita - /// A text widget. -public struct Text: Widget { +public typealias Text = Label - /// The content. - var text: String - /// Whether line wrapping is allowed. - var lineWrapping = false +extension Text { /// Initialize a text widget. /// - Parameter text: The content. public init(_ text: String) { - self.text = text - } - - /// Update the view storage of the text widget. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let label = storage.view as? MarkupLabel { - label.setText(text) - _ = label.wrap(lineWrapping) - } - } - - /// Get the container of the text widget. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - .init(MarkupLabel(self.text).wrap(lineWrapping)) - } - - /// Line wrapping allows the text view to span multiple lines if the width is narrow. - /// - Parameter wrap: Whether to allow line wrapping. - /// - Returns: The text. - public func wrap(_ wrap: Bool = true) -> Self { - var newSelf = self - newSelf.lineWrapping = wrap - return newSelf + self.init(label: text) } } diff --git a/Sources/Adwaita/View/Toggle.swift b/Sources/Adwaita/View/Toggle.swift index 2660c08..5a6112a 100644 --- a/Sources/Adwaita/View/Toggle.swift +++ b/Sources/Adwaita/View/Toggle.swift @@ -5,19 +5,11 @@ // Created by david-swift on 19.12.23. // -import Libadwaita - /// A toggle button widget. -public struct Toggle: Widget { +public typealias Toggle = ToggleButton - /// The button's label. - var label: String? - /// The button's icon. - var icon: Icon? - /// Whether the toggle is on. - @Binding var isOn: Bool - /// Whether to use GtkCheckButton instead of GtkToggleButton - var isCheckButton = false +/// A toggle button widget. +extension Toggle { // swiftlint:disable function_default_parameter_at_end /// Initialize a toggle button. @@ -26,9 +18,12 @@ public struct Toggle: Widget { /// - icon: The button's icon. /// - isOn: Whether the toggle is on. public init(_ label: String? = nil, icon: Icon? = nil, isOn: Binding) { - self.label = label - self.icon = icon - self._isOn = isOn + self = self.child { + ButtonContent() + .label(label) + .iconName(icon?.string) + } + self.active = isOn } // swiftlint:enable function_default_parameter_at_end @@ -38,68 +33,21 @@ public struct Toggle: Widget { /// - isOn: Whether the toggle is on. public init(_ label: String, isOn: Binding) { self.label = label - self._isOn = isOn - } - - /// Update a toggle button's view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let toggle = storage.view as? Libadwaita.ToggleButton { - updateState(toggle: toggle) - } else if let toggle = storage.view as? Libadwaita.CheckButton { - updateState(toggle: toggle) - } - } - - /// Get a button's view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The button's view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - if isCheckButton { - let toggle: Libadwaita.CheckButton = .init(label ?? "") - updateState(toggle: toggle) - _ = toggle.handler { - self.isOn.toggle() - } - return .init(toggle) - } else { - let toggle: Libadwaita.ToggleButton = .init(label ?? "") - updateState(toggle: toggle) - _ = toggle.handler { - self.isOn.toggle() - } - return .init(toggle) - } - } - - /// Update the toggle's state. - /// - Parameter toggle: The toggle. - func updateState(toggle: Libadwaita.ToggleButton) { - if let icon { - toggle.setLabel(icon: icon, label: label) - } else if let label { - toggle.setLabel(label) - } - toggle.setActive(isOn) - } - - /// Update the check button's state. - /// - Parameter toggle: The toggle. - func updateState(toggle: Libadwaita.CheckButton) { - if let label { - toggle.setLabel(label) - } - toggle.setActive(isOn) + self.active = isOn } /// Use the check button style. /// - Returns: The toggle. - public func checkButton() -> Self { - var newSelf = self - newSelf.isCheckButton = true - return newSelf + public func checkButton() -> CheckButton { + if let child { + return .init() + .active(active) + .child(child) + } else { + return .init() + .active(active) + .label(label) + } } } diff --git a/Sources/Adwaita/View/VStack.swift b/Sources/Adwaita/View/VStack.swift index ad0b7be..4018267 100644 --- a/Sources/Adwaita/View/VStack.swift +++ b/Sources/Adwaita/View/VStack.swift @@ -5,40 +5,27 @@ // Created by david-swift on 23.08.23. // -import Libadwaita +import CAdw /// A GtkBox equivalent. -public struct VStack: Widget { +public typealias VStack = Box - /// The content. - var content: () -> Body +extension VStack { /// Initialize a `VStack`. /// - Parameter content: The view content. public init(@ViewBuilder content: @escaping () -> Body) { - self.content = content + self.init(horizontal: false, content: content) } - /// Update a view storage. - /// - Parameters: - /// - storage: The view storage. - /// - modifiers: Modify views before being updated. - public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - content().update(storage.content[.mainContent] ?? [], modifiers: modifiers) - } - - /// Get a view storage. - /// - Parameter modifiers: Modify views before being updated. - /// - Returns: The view storage. - public func container(modifiers: [(View) -> View]) -> ViewStorage { - let box: Box = .init(horizontal: false) - var content: [ViewStorage] = [] - for element in self.content() { - let widget = element.storage(modifiers: modifiers) - _ = box.append(widget.view) - content.append(widget) + init(horizontal: Bool, @ViewBuilder content: @escaping () -> Body) { + self.init(spacing: 0) + self = self.append(content) + if horizontal { + appearFunctions.append { storage in + gtk_orientable_set_orientation(storage.pointer, GTK_ORIENTATION_HORIZONTAL) + } } - return .init(box, content: [.mainContent: content]) } } diff --git a/Sources/Adwaita/View/ViewStack.swift b/Sources/Adwaita/View/ViewStack.swift index 745d45a..2abdebc 100644 --- a/Sources/Adwaita/View/ViewStack.swift +++ b/Sources/Adwaita/View/ViewStack.swift @@ -5,7 +5,7 @@ // Created by david-swift on 30.12.23. // -import Libadwaita +import CAdw /// A widget holding multiple children but only displaying one. public struct ViewStack: Widget { @@ -43,8 +43,8 @@ public struct ViewStack: Widget { /// - Parameter modifiers: Modify views before being updated. /// - Returns: The stack's view storage. public func container(modifiers: [(View) -> View]) -> ViewStorage { - let stack = Stack() - let storage = ViewStorage(stack) + let stack = gtk_stack_new() + let storage = ViewStorage(.init(stack)) update(storage, modifiers: modifiers) return storage } @@ -54,18 +54,19 @@ public struct ViewStack: Widget { /// - storage: The view storage. /// - modifiers: Modify views before being updated. public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let stack = storage.view as? Stack { - let widget = content.widget(modifiers: modifiers) - if let view = storage.content[id.description]?.first { - widget.updateStorage(view, modifiers: modifiers) - } else { - let view = widget.storage(modifiers: modifiers) - _ = stack.append(view.view) - storage.content[id.description] = [view] - } - if let visibleView = storage.content[id.description]?.first?.view { - _ = stack.setVisible(visibleView, transition: visibleView.fields[.transition] as? Transition) + let widget = content.widget(modifiers: modifiers) + if let view = storage.content[id.description]?.first { + widget.updateStorage(view, modifiers: modifiers) + } else { + let view = widget.storage(modifiers: modifiers) + gtk_stack_add_named(storage.pointer, view.pointer?.cast(), id.description) + storage.content[id.description] = [view] + } + if let visibleView = storage.content[id.description]?.first { + if let transition = visibleView.fields[.transition] as? Transition { + gtk_stack_set_transition_type(storage.pointer, transition.cTransition) } + gtk_stack_set_visible_child_name(storage.pointer, id.description) } } diff --git a/Sources/Adwaita/View/ViewSwitcher.swift b/Sources/Adwaita/View/ViewSwitcher.swift index f7fd1e8..36711bb 100644 --- a/Sources/Adwaita/View/ViewSwitcher.swift +++ b/Sources/Adwaita/View/ViewSwitcher.swift @@ -5,7 +5,7 @@ // Created by david-swift on 03.01.24. // -import Libadwaita +import CAdw /// A picker used for indicating multiple views. /// @@ -27,18 +27,27 @@ public struct ViewSwitcher: Widget where Element: ViewSwitcherOption { /// - Parameter modifiers: Modify views before being updated. /// - Returns: The view storage. public func container(modifiers: [(View) -> View]) -> ViewStorage { - let switcher = Libadwaita.ViewSwitcher() + let switcher = ViewStorage(.init(adw_view_switcher_new())) + let stack = ViewStorage(.init(adw_view_stack_new())) + adw_view_switcher_set_stack(switcher.pointer, stack.pointer) for option in Element.allCases { - _ = switcher.addOption(title: option.title, icon: option.icon) + adw_view_stack_add_titled_with_icon( + stack.pointer, + gtk_label_new(""), + option.title, + option.title, + option.icon.string + ) } - _ = switcher.onSelect { - let selection = switcher.getSelection() - if let element = Element(title: selection) { - self.selection = element + stack.notify(name: "visible-child") { + if let title = adw_view_stack_get_visible_child_name(stack.pointer), + let option = Element(title: .init(cString: title)) { + selection = option } } updateSwitcher(switcher: switcher) - return .init(switcher) + switcher.fields["stack"] = stack + return switcher } /// Update a view switcher's view storage. @@ -46,16 +55,18 @@ public struct ViewSwitcher: Widget where Element: ViewSwitcherOption { /// - storage: The view storage. /// - modifiers: Modify views before being updated. public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let switcher = storage.view as? Libadwaita.ViewSwitcher { - updateSwitcher(switcher: switcher) - } + updateSwitcher(switcher: storage) } /// Update a view switcher's style and selection. /// - Parameter switcher: The view switcher. - func updateSwitcher(switcher: Libadwaita.ViewSwitcher) { - _ = switcher.wideDesign(wide) - switcher.select(title: selection.title) + func updateSwitcher(switcher: ViewStorage) { + adw_view_switcher_set_policy( + switcher.pointer, + wide ? ADW_VIEW_SWITCHER_POLICY_WIDE : ADW_VIEW_SWITCHER_POLICY_NARROW + ) + let stack = adw_view_switcher_get_stack(switcher.pointer) + adw_view_stack_set_visible_child_name(stack, selection.title) } /// Set whether to use the wide design. diff --git a/Sources/Adwaita/Window/AboutWindow.swift b/Sources/Adwaita/Window/AboutWindow.swift index 3ce0243..8a08e8d 100644 --- a/Sources/Adwaita/Window/AboutWindow.swift +++ b/Sources/Adwaita/Window/AboutWindow.swift @@ -5,8 +5,8 @@ // Created by david-swift on 05.12.23. // +import CAdw import Foundation -import Libadwaita /// A structure representing an about window. public struct AboutWindow: WindowScene { @@ -20,15 +20,17 @@ public struct AboutWindow: WindowScene { /// The keyboard shortcuts on the app level. public var appShortcuts: [String: (GTUIApp) -> Void] = [:] /// The app's name. - var appName: String + var appName: String? /// The developer's name. - var developer: String + var developer: String? /// The app version. - var version: String + var version: String? /// The app icon. var icon: Icon? /// The app's website. var website: URL? + /// The path to the app data file. + var path: URL? /// Create a window type with a certain identifier and content. /// - Parameters: @@ -43,6 +45,15 @@ public struct AboutWindow: WindowScene { self.version = version } + /// Create a window type with a certain identifier and content. + /// - Parameters: + /// - id: The identifier. + /// - path: The path to the app data file. + public init(id: String, path: URL) { + self.id = id + self.path = path + } + /// Set the app icon. /// - Parameter icon: The app icon. /// - Returns: The window. @@ -74,8 +85,13 @@ public struct AboutWindow: WindowScene { /// Get the window. /// - Parameter app: The application. /// - Returns: The window. - func createGTUIWindow(app: GTUIApp) -> Libadwaita.AboutWindow { - let window = Libadwaita.AboutWindow() + func createGTUIWindow(app: GTUIApp) -> GTUIAboutWindow { + let window: GTUIAboutWindow + if let path { + window = .init(filePath: path.path) + } else { + window = .init() + } updateAppShortcuts(app: app) updateData(window: window) window.show() @@ -93,9 +109,11 @@ public struct AboutWindow: WindowScene { /// Update the data for a window. /// - Parameter window: The window. - func updateData(window: Libadwaita.AboutWindow) { - _ = window.generalData(title: appName, icon: icon ?? .custom(name: ""), developer: developer, version: version) - if let website { _ = window.website(url: website.absoluteString) } + func updateData(window: GTUIAboutWindow) { + if let appName, let icon, let developer, let version { + window.generalData(title: appName, icon: icon, developer: developer, version: version) + } + if let website { window.website(url: website.absoluteString) } } } diff --git a/Sources/Adwaita/Window/FileDialog.swift b/Sources/Adwaita/Window/FileDialog.swift index d088f1a..89fad76 100644 --- a/Sources/Adwaita/Window/FileDialog.swift +++ b/Sources/Adwaita/Window/FileDialog.swift @@ -8,7 +8,6 @@ // swiftlint:disable discouraged_optional_collection import Foundation -import Libadwaita /// A structure representing a file dialog window. public struct FileDialog: WindowScene { @@ -29,8 +28,6 @@ public struct FileDialog: WindowScene { var initialName: String? /// The accepted extensions for the file importer. var extensions: [String]? - /// Whether folders are accepted in the file importer. - var folders = false /// The closure to run when the import or export is successful. var result: ((URL) -> Void)? /// The closure to run when the import or export is not successful. @@ -48,14 +45,12 @@ public struct FileDialog: WindowScene { importer: String, initialFolder: URL? = nil, extensions: [String]? = nil, - folders: Bool = false, onOpen: @escaping (URL) -> Void, onClose: @escaping () -> Void ) { self.id = importer self.initialFolder = initialFolder self.extensions = extensions - self.folders = folders self.result = onOpen self.cancel = onClose } @@ -86,7 +81,7 @@ public struct FileDialog: WindowScene { /// - Parameter app: The application. /// - Returns: The storage. public func createWindow(app: GTUIApp) -> WindowStorage { - let window = Libadwaita.FileDialog(nil) + let window = GTUIFileDialog() let windowStorage = WindowStorage(id: id, window: window, view: nil) windowStorage.parentID = parentID update(window: window) @@ -99,7 +94,7 @@ public struct FileDialog: WindowScene { /// - app: The application. public func update(_ storage: WindowStorage, app: GTUIApp) { updateAppShortcuts(app: app) - if let window = storage.window as? Libadwaita.FileDialog { + if let window = storage.window as? GTUIFileDialog { update(window: window) } storage.destroy = true @@ -107,13 +102,13 @@ public struct FileDialog: WindowScene { /// Update the window. /// - Parameter window: The window. - func update(window: Libadwaita.FileDialog) { + func update(window: GTUIFileDialog) { window.isImporter = importer window.folder = initialFolder if let initialName { window.setInitialName(initialName) } - window.setExtensions(extensions, folders: folders) + window.setExtensions(extensions) if let result { window.onResult = result } diff --git a/Sources/Adwaita/Window/Window.swift b/Sources/Adwaita/Window/Window.swift index e8e3060..403b584 100644 --- a/Sources/Adwaita/Window/Window.swift +++ b/Sources/Adwaita/Window/Window.swift @@ -8,7 +8,6 @@ // swiftlint:disable discouraged_optional_collection import Foundation -import Libadwaita /// A structure representing an application window type. /// @@ -58,7 +57,6 @@ public struct Window: WindowScene { let windowStorage = WindowStorage(id: id, window: window, view: storage) window.observeHide { windowStorage.destroy = true - return false } windowStorage.parentID = parentID return windowStorage @@ -80,7 +78,7 @@ public struct Window: WindowScene { func getViewStorage(window: GTUIApplicationWindow) -> ViewStorage { let content = content(window) let storage = content.widget(modifiers: []).container(modifiers: []) - window.setChild(storage.view) + window.setChild(storage.pointer) window.setDefaultSize(width: defaultSize?.0, height: defaultSize?.1) setProperties(window: window) updateShortcuts(window: window) @@ -139,7 +137,6 @@ public struct Window: WindowScene { _ signal: Signal, initialFolder: URL? = nil, extensions: [String]? = nil, - folders: Bool = false, onOpen: @escaping (URL) -> Void, onClose: @escaping () -> Void ) -> Scene { @@ -151,7 +148,6 @@ public struct Window: WindowScene { importer: signal.id.uuidString, initialFolder: initialFolder, extensions: extensions, - folders: folders, onOpen: onOpen, onClose: onClose ) diff --git a/Sources/CAdw/module.modulemap b/Sources/CAdw/module.modulemap new file mode 100644 index 0000000..7c9ce0b --- /dev/null +++ b/Sources/CAdw/module.modulemap @@ -0,0 +1,6 @@ +module CAdw [system] { + header "shim.h" + link "adwaita-1" + link "gtk-4" + export * +} \ No newline at end of file diff --git a/Sources/CAdw/shim.h b/Sources/CAdw/shim.h new file mode 100644 index 0000000..0ea21e3 --- /dev/null +++ b/Sources/CAdw/shim.h @@ -0,0 +1,37 @@ +#include +#include + +static void +filedialog_on_open_cb (void *, void *, void *); +static void +filedialog_on_save_cb (void *, void *, void *); + +static void +gtui_filedialog_save_finish (uint64_t dialog, uint64_t result, uint64_t data) +{ + GFile *file = gtk_file_dialog_save_finish (dialog, result, NULL); + const char *path = g_file_get_path (file); + filedialog_on_save_cb (dialog, path, data); +} + +static void +gtui_filedialog_save (uint64_t dialog, uint64_t data, uint64_t window) +{ + swift_retain (data); + gtk_file_dialog_save (dialog, window, NULL, G_CALLBACK (gtui_filedialog_save_finish), (void *)data); +} + +static void +gtui_filedialog_open_finish (uint64_t dialog, uint64_t result, uint64_t data) +{ + GFile *file = gtk_file_dialog_open_finish (dialog, result, NULL); + const char *path = g_file_get_path (file); + filedialog_on_open_cb (dialog, path, data); +} + +static void +gtui_filedialog_open (uint64_t dialog, uint64_t data, uint64_t window) +{ + swift_retain (data); + gtk_file_dialog_open (dialog, window, NULL, G_CALLBACK (gtui_filedialog_open_finish), (void *)data); +} diff --git a/Sources/Generation/Extensions/String.swift b/Sources/Generation/Extensions/String.swift new file mode 100644 index 0000000..5a9e791 --- /dev/null +++ b/Sources/Generation/Extensions/String.swift @@ -0,0 +1,80 @@ +// +// String.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +extension String: CodingKey { + + /// The string. + public var stringValue: String { + self + } + + /// A string cannot be represented as an integer. + public var intValue: Int? { + nil + } + + /// Initialize from an int value. + /// - Parameter intValue: The int value. + public init?(intValue: Int) { + nil + } + + /// Initialize from a string value. + /// - Parameter stringValue: The string value. + public init?(stringValue: String) { + self = stringValue + } + + /// Generate a doc comment out of the string. + /// - Parameter indent: Indentation added at the beginning of every line. + /// - Returns: The comment. + func docComment(indent: String = "") -> String { + split(separator: "\n", omittingEmptySubsequences: false) + .map { $0.trimmingCharacters(in: .whitespaces) } + .enumerated() + .map { $0.offset == 0 ? $0.element.prefix(1).capitalized + $0.element.dropFirst() : $0.element } + .map { "\(indent)/// \($0)" } + .joined(separator: "\n") + } + + /// Convert delimited to camel casing. + /// - Parameters: + /// - delimiter: The demiliter. + /// - unshorten: Whether to unshorten. + /// - configuration: The generation configuration. + /// - Returns: The string using camel casing. + func convertDelimitedCasingToCamel( + delimiter: Character, + configuration: GenerationConfiguration, + unshorten: Bool = false + ) -> String { + var parts = split(separator: delimiter).map(String.init) + for (index, part) in parts.enumerated() { + if let replacement = configuration.unshorteningMap[part] { + parts[index] = replacement + } + } + let first = parts.removeFirst() + return first + parts.map(\.capitalized).joined() + } + + /// Convert a C type to its Swift equivalent using the generation configuration. + /// - Parameter configuration: The generation configuration. + /// - Returns: The Swift type. + func convertCType(configuration: GenerationConfiguration) -> String { + if let replacement = configuration.cTypeReplacements[self] { + return replacement + } + var type = self + if type.last == "*" { + let pointeeType = String(type.dropLast()).convertCType(configuration: configuration) + type = "UnsafeMutablePointer<\(pointeeType)>!" + } + return type + } + +} diff --git a/Sources/Generation/GIR/Class+.swift b/Sources/Generation/GIR/Class+.swift new file mode 100644 index 0000000..2da2856 --- /dev/null +++ b/Sources/Generation/GIR/Class+.swift @@ -0,0 +1,304 @@ +// +// Class+.swift +// Adwaita +// +// Created by david-swift on 22.01.24. +// + +extension Class { + + /// Generate the Swift initializer. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateAdwaitaInitializer( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + let requiredProperties = properties(classes: classes, configurations: configs) + .filter { config.requiredProperties.contains($0.name) } + var initializer = "public init(" + if config.dynamicWidget != nil { + initializer.append("_ elements: [Element], ") + } + for property in requiredProperties { + initializer.append("\(property.parameter(config: config, genConfig: genConfig)), ") + } + if config.dynamicWidget != nil { + initializer.append("@ViewBuilder content: @escaping (Element) -> Body, ") + } + if !requiredProperties.isEmpty || config.dynamicWidget != nil { + initializer.removeLast(", ".count) + } + initializer.append(") {") + for property in requiredProperties { + let name = property.convertPropertyName(configuration: genConfig) + initializer.append("\n self.\(name) = \(name)") + } + if config.dynamicWidget != nil { + initializer.append( + """ + + self.elements = elements + self.content = content + """ + ) + } + initializer.append("\n }") + return initializer + } + + /// Generate the call of the C initializer. + /// - Parameters: + /// - name: The class name. + /// - config: The widget configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateInitializer( + name: String, + config: WidgetConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + if let initializer = config.initializer { + return initializer + } + if let initializer = constructors.first(where: { ($0.parameters?.parameters.count ?? 0) == 0 }) { + return initializer.cIdentifier + "()" + } + // swiftlint:disable fatal_error + fatalError("No initializer with no parameters for \(name). Configure manually.") + // swiftlint:enable fatal_error + } + + /// Generate the assignments for the widgets and menus. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateWidgetAssignments( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + var content = "" + for property in properties(classes: classes, configurations: configs) + where !config.excludeProperties.contains(property.name) && (property.type?.isWidget ?? false) { + content += property.generateWidgetAssignment(prefix: prefix(), config: config, genConfig: genConfig) + } + for property in properties(classes: classes, configurations: configs) + where !config.excludeProperties.contains(property.name) && (property.type?.isMenu ?? false) { + content += property.generateMenuAssignment(prefix: prefix(), config: config, genConfig: genConfig) + } + content += staticWidgets(classes: classes, configs: configs) + return content + } + + /// Generate the assignments for bindings. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateBindingAssignments( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + var content = "" + for property in properties(classes: classes, configurations: configs) { + if let binding = config.bindings.first(where: { $0.property == property.name }) { + content += property.generateBindingAssignment( + prefix: prefix(), + signal: binding.signal, + config: config, + genConfig: genConfig + ) + } + } + return content + } + + /// Generate the properties. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateProperties( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + var content = "" + for property in properties(classes: classes, configurations: configs) + where !config.excludeProperties.contains(property.name) || config.requiredProperties.contains(property.name) { + content += property.generate(config: config, genConfig: genConfig) + } + for signal in signals(classes: classes) where !config.excludeSignals.contains(signal.name) { + content += signal.generateProperty(config: config, genConfig: genConfig) + } + if config.dynamicWidget != nil { + content += """ + + /// The dynamic widget elements. + var elements: [Element] + /// The dynamic widget content. + var content: (Element) -> Body + """ + } + content += staticWidgetProperties(classes: classes, configs: configs) + content += """ + + /// The application. + var app: GTUIApp? + /// The window. + var window: GTUIApplicationWindow? + """ + return content + } + + /// Generate the property modifications for updating. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateModifications( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + var content = "" + for property in properties(classes: classes, configurations: configs) + where !config.excludeProperties.contains(property.name) { + content += property.generateModification(config: config, genConfig: genConfig, prefix: prefix()) + } + for widget in config.staticWidgets { + content += """ + + if let \(widget.name)Storage = storage.content["\(widget.name)"] { + for (index, view) in \(widget.name)().enumerated() { + if let storage = \(widget.name)Storage[safe: index] { + view.updateStorage(storage, modifiers: modifiers) + } + } + } + """ + } + return content + } + + /// Generate the content of the update closure for dynamic widgets. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateDynamicWidgetUpdate(config: WidgetConfiguration, genConfig: GenerationConfiguration) -> String { + let child = "let child = content(element).widget(modifiers: modifiers).container(modifiers: modifiers)" + let pointer = "child.pointer?.cast()" + let widget = "widget" + (config.cast ? "?.cast()" : "") + if let dynamicWidget = config.dynamicWidget { + // swiftlint:disable line_length + return """ + var contentStorage: [ViewStorage] = storage.content[.mainContent] ?? [] + let old = storage.fields["element"] as? [Element] ?? [] + old.identifiableTransform( + to: elements, + functions: .init { index, element in + \(child) + \(dynamicWidget.remove)(\(widget), \(dynamicWidget.getElement)) + \(dynamicWidget.insert)(\(widget), \(pointer), index.cInt) + contentStorage.remove(at: index) + contentStorage.insert(child, at: index) + } delete: { index in + \(dynamicWidget.remove)(\(widget), \(dynamicWidget.getElement)) + contentStorage.remove(at: index) + } insert: { index, element in + \(child) + \(dynamicWidget.insert)(\(widget), \(pointer), index.cInt) + contentStorage.insert(child, at: index) + } + ) + storage.fields["element"] = elements + storage.content[.mainContent] = contentStorage + for (index, element) in elements.enumerated() { + content(element).widget(modifiers: modifiers).update(contentStorage[index], modifiers: modifiers) + } + """ + // swiftlint:enable line_length + } else { + return "" + } + } + + /// Generate the modifications for the signals. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - Returns: The code. + func generateSignalModifications( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class] + ) -> String { + var content = "" + for signal in signals(classes: classes) where !config.excludeSignals.contains(signal.name) { + content += signal.generateModification(config: config, genConfig: genConfig) + } + return content + } + + /// Generate the modifiers. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generateModifiers( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Class], + configs: [WidgetConfiguration] + ) -> String { + var content = "" + for property in properties(classes: classes, configurations: configs) + where !config.excludeProperties.contains(property.name) { + content += property.generateModifier(config: config, genConfig: genConfig) + } + for signal in signals(classes: classes) where !config.excludeSignals.contains(signal.name) { + content += signal.generateModifier(config: config, genConfig: genConfig) + } + for widget in config.staticWidgets { + content += """ + + /// Set the body for "\(widget.name)". + /// - Parameter body: The body. + /// - Returns: The widget. + public func \(widget.name)(@ViewBuilder _ body: @escaping () -> Body) -> Self { + var newSelf = self + newSelf.\(widget.name) = body + return newSelf + } + """ + } + return content + } +} diff --git a/Sources/Generation/GIR/Class.swift b/Sources/Generation/GIR/Class.swift new file mode 100644 index 0000000..3932b16 --- /dev/null +++ b/Sources/Generation/GIR/Class.swift @@ -0,0 +1,227 @@ +// +// Class.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +import Foundation + +/// A class. +struct Class: Decodable { + + /// The name of the class. + var name: String + /// The prefix for C symbols. + var cSymbolPrefix: String + /// The type in C. + var cType: String? + /// The parent class. + var parent: String? + + /// The doc string. + var doc: String + /// The available initializers. + var constructors: [Constructor] + /// The available properties. + var properties: [Property] + /// The available signals (callbacks). + var signals: [Signal] + + /// The coding keys for the class + enum CodingKeys: String, CodingKey { + + /// Coding key + case name, cSymbolPrefix, cType, parent, doc + /// Coding key + case constructors = "constructor" + /// Coding key + case properties = "property" + /// Coding key + case signals = "glibSignal" + + } + + /// Get the properties of the class and its parent classes. + /// - Parameters: + /// - classes: The classes in the namespace. + /// - configurations: The configurations for the classes in the namespace. + /// - Returns: The properties. + func properties(classes: [Self], configurations: [WidgetConfiguration]) -> [Property] { + if let parentClass = parentClass(classes: classes) { + return properties + parentClass + .properties(classes: classes, configurations: configurations) + .map { property in + var property = property + if property.prefix == nil { + property.prefix = parentClass.prefix() + property.cast = configurations.first { $0.class == parentClass.name }?.cast ?? false + } + return property + } + } + return properties + } + + /// Get the signals of the class and its parent classes. + /// - Parameter classes: The classes in the namespace. + /// - Returns: The signals. + func signals(classes: [Self]) -> [Signal] { + if let parentClass = parentClass(classes: classes) { + return signals + parentClass.signals(classes: classes) + } + return signals + } + + /// Get the assignments for the static widgets of the class and its parent classes. + /// - Parameters: + /// - classes: The classes in the namespace. + /// - configs: The configurations. + /// - Returns: The code. + func staticWidgets(classes: [Self], configs: [WidgetConfiguration]) -> String { + var content = "" + if let parentClass = parentClass(classes: classes) { + content += parentClass.staticWidgets(classes: classes, configs: configs) + } + guard let config = configs.first(where: { $0.class == name }) else { + return content + } + let widgetPointer = config.cast ? "storage.pointer?.cast()" : "storage.pointer" + for widget in config.staticWidgets { + content += """ + + var \(widget.name)Storage: [ViewStorage] = [] + for view in \(widget.name)() { + \(widget.name)Storage.append(view.storage(modifiers: modifiers)) + \(widget.add)(\(widgetPointer), \(widget.name)Storage.last?.pointer?.cast()) + } + storage.content["\(widget.name)"] = \(widget.name)Storage + """ + } + return content + } + + /// Get the code for the properties of the static widgets. + /// - Parameters: + /// - classes: The classes in the namespace. + /// - configs: The configurations. + /// - Returns: The code. + func staticWidgetProperties(classes: [Self], configs: [WidgetConfiguration]) -> String { + var content = "" + if let parentClass = parentClass(classes: classes) { + content += parentClass.staticWidgetProperties(classes: classes, configs: configs) + } + guard let config = configs.first(where: { $0.class == name }) else { + return content + } + for staticWidget in config.staticWidgets { + content += """ + + /// The body for the widget "\(staticWidget.name)". + var \(staticWidget.name): () -> Body = { [] } + """ + } + return content + } + + /// Get the parent class. + /// - Parameter classes: The classes in the namespace. + /// - Returns: The class. + func parentClass(classes: [Self]) -> Self? { + if let parent = classes.first(where: { $0.name == parent }), parent.name != "Widget" { + return parent + } + return nil + } + + /// Get the widget's prefix (e.g. "adw_preferences_row"). + /// - Returns: The prefix. + func prefix() -> String { + if cType?.hasPrefix("Adw") ?? false { + return "adw_\(cSymbolPrefix)" + } else { + return "gtk_\(cSymbolPrefix)" + } + } + + // swiftlint:disable function_body_length line_length + /// Generate the code for the class. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - classes: The available classes. + /// - configs: The available widget configurations. + /// - Returns: The code. + func generate( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + classes: [Self], + configs: [WidgetConfiguration] + ) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd.MM.yy" + let widgetName = config.name ?? config.class + let definition: String + if config.dynamicWidget == nil { + definition = "\(widgetName): Widget" + } else { + definition = "\(widgetName): Widget where Element: Identifiable" + } + return """ + // + // \(widgetName).swift + // Adwaita + // + // Created by auto-generation on \(dateFormatter.string(from: .now)). + // + + import CAdw + import LevenshteinTransformations + + \(doc.docComment()) + public struct \(definition) { + + /// Additional update functions for type extensions. + var updateFunctions: [(ViewStorage) -> Void] = [] + /// Additional appear functions for type extensions. + var appearFunctions: [(ViewStorage) -> Void] = [] + \(generateProperties(config: config, genConfig: genConfig, classes: classes, configs: configs)) + + /// Initialize `\(widgetName)`. + \(generateAdwaitaInitializer(config: config, genConfig: genConfig, classes: classes, configs: configs)) + + /// Get the widget's view storage. + /// - Parameter modifiers: The view modifiers. + /// - Returns: The view storage. + public func container(modifiers: [(View) -> View]) -> ViewStorage { + let storage = ViewStorage(\(generateInitializer(name: widgetName, config: config, classes: classes, configs: configs))?.opaque()) + update(storage, modifiers: modifiers) + \(generateWidgetAssignments(config: config, genConfig: genConfig, classes: classes, configs: configs)) + \(generateBindingAssignments(config: config, genConfig: genConfig, classes: classes, configs: configs)) + for function in appearFunctions { + function(storage) + } + return storage + } + + /// Update the widget's view storage. + /// - Parameters: + /// - storage: The view storage. + /// - modifiers: The view modifiers. + public func update(_ storage: ViewStorage, modifiers: [(View) -> View]) {\(generateSignalModifications(config: config, genConfig: genConfig, classes: classes)) + storage.modify { widget in + \(generateModifications(config: config, genConfig: genConfig, classes: classes, configs: configs)) + \(generateDynamicWidgetUpdate(config: config, genConfig: genConfig)) + } + for function in updateFunctions { + function(storage) + } + } + \(generateModifiers(config: config, genConfig: genConfig, classes: classes, configs: configs)) + } + + """ + } + // swiftlint:enable function_body_length line_length + +} diff --git a/Sources/Generation/GIR/Constructor.swift b/Sources/Generation/GIR/Constructor.swift new file mode 100644 index 0000000..c1d3e11 --- /dev/null +++ b/Sources/Generation/GIR/Constructor.swift @@ -0,0 +1,16 @@ +// +// Constructor.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// An initializer. +struct Constructor: Decodable { + + /// The identifier of the C constructor. + var cIdentifier: String + /// The parameters. + var parameters: Parameters? + +} diff --git a/Sources/Generation/GIR/GIR.swift b/Sources/Generation/GIR/GIR.swift new file mode 100644 index 0000000..4820c43 --- /dev/null +++ b/Sources/Generation/GIR/GIR.swift @@ -0,0 +1,60 @@ +// +// GIR.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +import Foundation +import XMLCoder + +/// The result of decoding a GIR file. +struct GIR: Decodable { + + /// The namespace. + var namespace: Namespace + + /// Decode a GIR file. + /// - Returns: The GIR data. + static func decodeGIR(_ data: Data) throws -> Self { + let decoder = XMLDecoder() + decoder.keyDecodingStrategy = .custom { path in + let codingKey = path[path.count - 1] + let containsColon = codingKey.stringValue.contains(":") + let containsHyphen = codingKey.stringValue.contains("-") + if containsColon || containsHyphen { + var input = codingKey.stringValue + var output = "" + + // Remove namespace + if containsColon { + let parts = input.split(separator: ":").map(String.init) + output = parts[0] + input = parts[1] + } + + // Convert kebab-case to camelCase + if containsHyphen { + var parts = input.split(separator: "-") + let firstPart = String(parts.removeFirst()) + if containsColon { + output += firstPart.capitalized + } else { + output += firstPart + } + + for part in parts { + output += part.capitalized + } + } else { + output += input.capitalized + } + return output + } else { + return codingKey + } + } + return try decoder.decode(Self.self, from: data) + } + +} diff --git a/Sources/Generation/GIR/GIRType.swift b/Sources/Generation/GIR/GIRType.swift new file mode 100644 index 0000000..95659b9 --- /dev/null +++ b/Sources/Generation/GIR/GIRType.swift @@ -0,0 +1,38 @@ +// +// GIRType.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// A GIR type. +struct GIRType: Decodable { + + /// The type's name. + var name: String? + /// The C type. + var cType: String? + + /// Whether the type is `GtkWidget`. + var isWidget: Bool { name == "Widget" || name == "Gtk.Widget" } + /// Whether the type is `GioMenuModel`. + var isMenu: Bool { name == "MenuModel" || name == "Gio.MenuModel" } + + /// Generate the Swift representation of the type. + /// - Parameter configuration: The generation configuration. + /// - Returns: The code. + func generate(configuration: GenerationConfiguration) throws -> String { + if let cType { + return cType.convertCType(configuration: configuration) + } + return "String" + } + + /// Generate the Swift type as a binding. + /// - Parameter configuration: The generation configuration. + /// - Returns: The code. + func binding(configuration: GenerationConfiguration) throws -> String { + "Binding<\(try generate(configuration: configuration))>" + } + +} diff --git a/Sources/Generation/GIR/Namespace.swift b/Sources/Generation/GIR/Namespace.swift new file mode 100644 index 0000000..5bfac5d --- /dev/null +++ b/Sources/Generation/GIR/Namespace.swift @@ -0,0 +1,22 @@ +// +// Namespace.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// A namespace. +struct Namespace: Decodable { + + /// The classes. + var classes: [Class] + + /// The coding keys. + enum CodingKeys: String, CodingKey { + + /// Coding key. + case classes = "class" + + } + +} diff --git a/Sources/Generation/GIR/Parameter.swift b/Sources/Generation/GIR/Parameter.swift new file mode 100644 index 0000000..f5f2551 --- /dev/null +++ b/Sources/Generation/GIR/Parameter.swift @@ -0,0 +1,9 @@ +// +// Parameter.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// A parameter. +struct Parameter: Decodable { } diff --git a/Sources/Generation/GIR/Parameters.swift b/Sources/Generation/GIR/Parameters.swift new file mode 100644 index 0000000..2751a02 --- /dev/null +++ b/Sources/Generation/GIR/Parameters.swift @@ -0,0 +1,22 @@ +// +// Parameters.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// Parameters. +struct Parameters: Decodable { + + /// The parameters. + var parameters: [Parameter] + + /// The coding keys. + enum CodingKeys: String, CodingKey { + + /// Coding key. + case parameters = "parameter" + + } + +} diff --git a/Sources/Generation/GIR/Property.swift b/Sources/Generation/GIR/Property.swift new file mode 100644 index 0000000..390df9d --- /dev/null +++ b/Sources/Generation/GIR/Property.swift @@ -0,0 +1,268 @@ +// +// Property.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// A property. +struct Property: Decodable { + + /// The property's name. + var name: String + /// The docs. + var doc: String? + /// The property's getter. + var getter: String? + /// The property's setter. + var setter: String? + /// The property's type. + var type: GIRType? + /// This overwrites the prefix provided by the caller of a function. + var prefix: String? + // swiftlint:disable discouraged_optional_boolean + /// This overwrites the cast information provided by the caller of a function. + var cast: Bool? + // swiftlint:enable discouraged_optional_boolean + + /// Get the Swift property name. + /// - Parameter configuration: The generation configuration. + /// - Returns: The name. + func convertPropertyName(configuration: GenerationConfiguration) -> String { + name.convertDelimitedCasingToCamel(delimiter: "-", configuration: configuration, unshorten: true) + } + + /// Get the property's name and type for parameters or as part of a property definition. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - modifier: Whether it is used as a parameter. + /// - defaultValue: Whether to set the default value. + /// - Returns: The code. + func parameter( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + modifier: Bool = false, + defaultValue: Bool = false + ) -> String { + var type: String + if config.bindings.contains { $0.property == name } { + type = (try? self.type?.binding(configuration: genConfig)) ?? "Binding" + } else { + type = (try? self.type?.generate(configuration: genConfig)) ?? "String" + } + if self.type?.isWidget ?? false { + type = "\(modifier ? "@escaping" : "") (() -> Body)" + } else if self.type?.isMenu ?? false { + type = "\(modifier ? "@escaping" : "") (() -> MenuContent)" + } + if !config.requiredProperties.contains(name) + && !(((self.type?.isWidget ?? false) || (self.type?.isMenu ?? false)) && modifier) { + type += "?" + } + if defaultValue, let value = genConfig.defaultModifierValues[type] { + type += " = \(value)" + } + return "\(convertPropertyName(configuration: genConfig)): \(type)" + } + + /// Generate the property as a Swift property. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generate(config: WidgetConfiguration, genConfig: GenerationConfiguration) -> String { + """ + + \(doc?.docComment(indent: " ") ?? "/// \(name)") + var \(parameter(config: config, genConfig: genConfig)) + """ + } + + // swiftlint:disable line_length + /// Generate the property's modifier. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateModifier(config: WidgetConfiguration, genConfig: GenerationConfiguration) -> String { + let property = convertPropertyName(configuration: genConfig) + let builder = (type?.isWidget ?? false) ? "@ViewBuilder " : ((type?.isMenu ?? false) ? "@MenuBuilder " : "") + let mainParameter = parameter(config: config, genConfig: genConfig, modifier: true, defaultValue: true) + var sideParameters = "" + var sideAssignments = "" + if type?.isMenu ?? false { + sideParameters += "app: GTUIApp, window: GTUIApplicationWindow? = nil, " + sideAssignments += "newSelf.app = app; newSelf.window = window" + } + return """ + + \(doc?.docComment(indent: " ") ?? "/// \(name)") + public func \(convertPropertyName(configuration: genConfig))(\(sideParameters)\(builder)_ \(mainParameter)) -> Self { + var newSelf = self + newSelf.\(property) = \(property) + \(sideAssignments) + return newSelf + } + + """ + } + + /// Generate the property's modification when being updated. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - prefix: The widget's prefix. + /// - Returns: The code. + func generateModification( + config: WidgetConfiguration, + genConfig: GenerationConfiguration, + prefix: String + ) -> String { + let name = convertPropertyName(configuration: genConfig) + guard !(type?.isWidget ?? false) else { + return """ + if let widget = storage.content["\(name)"]?.first { + \(name)?().widget(modifiers: modifiers).update(widget, modifiers: modifiers) + } + + """ + } + guard !(type?.isMenu ?? false) else { + return "" + } + guard var setter else { + return "" + } + setter = (self.prefix ?? prefix) + "_" + setter + let type = try? type?.generate(configuration: genConfig) + let cProperty = genConfig.cTypeProperties.first { $0.key == type } + let propertyString = cProperty == nil ? "" : ".\(cProperty?.value ?? "")" + let widget = (self.cast ?? config.cast) ? "widget?.cast()" : "widget" + var setConditions = "" + var onlySetConditions = "" + var onlySetConditionsIndentation = "" + var onlySetConditionsEnd = "" + if let conditions = config.setConditions[name] { + setConditions = ", \(conditions)" + onlySetConditions = "if \(conditions) {\n " + onlySetConditionsIndentation = " " + onlySetConditionsEnd = "\n }" + } + if config.bindings.contains { $0.property == self.name } { + return """ + if let \(name)\(setConditions) { + \(setter)(\(widget), \(name).wrappedValue\(propertyString)) + } + + """ + } else if config.requiredProperties.contains(self.name) { + return """ + \(onlySetConditions)\(onlySetConditionsIndentation)\(setter)(\(widget), \(name)\(propertyString))\(onlySetConditionsEnd) + + """ + } else { + return """ + if let \(name)\(setConditions) { + \(setter)(\(widget), \(name)\(propertyString)) + } + + """ + } + } + // swiftlint:enable line_length + + /// Generate the widget assignments. + /// - Parameters: + /// - prefix: The widget's prefix. + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateWidgetAssignment( + prefix: String, + config: WidgetConfiguration, + genConfig: GenerationConfiguration + ) -> String { + guard var setter else { + return "" + } + setter = (self.prefix ?? prefix) + "_" + setter + let name = convertPropertyName(configuration: genConfig) + let view = (self.cast ?? config.cast) ? "storage.pointer?.cast()" : "storage.pointer" + return """ + if let \(name)Storage = \(name)?().widget(modifiers: modifiers).storage(modifiers: modifiers) { + storage.content["\(name)"] = [\(name)Storage] + \(setter)(\(view), \(name)Storage.pointer?.cast()) + } + + """ + } + + /// Generate the menu assignments. + /// - Parameters: + /// - prefix: The widget's prefix. + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateMenuAssignment( + prefix: String, + config: WidgetConfiguration, + genConfig: GenerationConfiguration + ) -> String { + guard var setter else { + return "" + } + setter = (self.prefix ?? prefix) + "_" + setter + let name = convertPropertyName(configuration: genConfig) + let view = (self.cast ?? config.cast) ? "storage.pointer?.cast()" : "storage.pointer" + return """ + if let declarative = \(name)?(), let app { + let menu = g_menu_new() + \(setter)(\(view), menu?.cast()) + for item in declarative { + item.addMenuItems(menu: menu, app: app, window: window) + } + } + + """ + } + + /// Generate a binding assignment. + /// - Parameters: + /// - prefix: The widget's prefix. + /// - signal: The signal's name, if there is one. + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateBindingAssignment( + prefix: String, + signal: String?, + config: WidgetConfiguration, + genConfig: GenerationConfiguration + ) -> String { + guard var getter else { + return "" + } + let widget = (self.cast ?? config.cast) ? "storage.pointer?.cast()" : "storage.pointer" + getter = "\((self.prefix ?? prefix) + "_" + getter)(\(widget))" + let name = convertPropertyName(configuration: genConfig) + let finalGetter = genConfig.getterTypeConversions[type?.name ?? ""]?(getter) ?? getter + let setter = "\(name)?.wrappedValue = \(finalGetter)" + if let signal { + return """ + + storage.connectSignal(name: "\(signal)", id: "\(self.name)") { + \(setter) + } + """ + } else { + return """ + + storage.notify(name: "\(self.name)") { + \(setter) + } + """ + } + } + +} diff --git a/Sources/Generation/GIR/Signal.swift b/Sources/Generation/GIR/Signal.swift new file mode 100644 index 0000000..7232313 --- /dev/null +++ b/Sources/Generation/GIR/Signal.swift @@ -0,0 +1,66 @@ +// +// Signal.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +/// A signal. +struct Signal: Decodable { + + /// The signal's name. + var name: String + /// The signal's documentation. + var doc: String? + + /// Generate the signal's property. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateProperty(config: WidgetConfiguration, genConfig: GenerationConfiguration) -> String { + let name = name.convertDelimitedCasingToCamel(delimiter: "-", configuration: genConfig, unshorten: true) + return """ + + \(doc?.docComment(indent: " ") ?? " /// \(name)") + var \(name): (() -> Void)? + """ + } + + /// Generate the signal's modifier. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateModifier(config: WidgetConfiguration, genConfig: GenerationConfiguration) -> String { + let name = name.convertDelimitedCasingToCamel(delimiter: "-", configuration: genConfig, unshorten: true) + return """ + + \(doc?.docComment(indent: " ") ?? " /// \(name)") + public func \(name)(_ \(name): @escaping () -> Void) -> Self { + var newSelf = self + newSelf.\(name) = \(name) + return newSelf + } + + """ + } + + /// Generate the signal's modification. + /// - Parameters: + /// - config: The widget configuration. + /// - genConfig: The generation configuration. + /// - Returns: The code. + func generateModification(config: WidgetConfiguration, genConfig: GenerationConfiguration) -> String { + let name = name.convertDelimitedCasingToCamel(delimiter: "-", configuration: genConfig, unshorten: true) + return """ + + if let \(name) { + storage.connectSignal(name: "\(self.name)") { + \(name)() + } + } + """ + } + +} diff --git a/Sources/Generation/Generation.swift b/Sources/Generation/Generation.swift new file mode 100644 index 0000000..29e77a5 --- /dev/null +++ b/Sources/Generation/Generation.swift @@ -0,0 +1,103 @@ +// +// Generation.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +import Foundation + +/// The generation executable. +@main +struct Generation { + + /// The generation configuration. + var configuration: GenerationConfiguration = .init() + + /// The static main function. + static func main() throws { + let generation = Self() + try generation.run() + } + + /// The main function. + func run() throws { + removeOldFiles() + let gtkDefinitions = getDefinitions(path: configuration.gtkGirFilePath) + let adwDefinitions = getDefinitions(path: configuration.adwGirFilePath) + for `class` in gtkDefinitions?.namespace.classes ?? [] { + if let config = configuration.gtkWidgets.first(where: { $0.class == `class`.name }) { + createFile( + class: `class`, + config: config, + classes: gtkDefinitions?.namespace.classes ?? [], + configs: configuration.gtkWidgets + ) + } + } + for `class` in adwDefinitions?.namespace.classes ?? [] { + if let config = configuration.adwWidgets.first(where: { $0.class == `class`.name }) { + createFile( + class: `class`, + config: config, + classes: adwDefinitions?.namespace.classes ?? [], + configs: configuration.adwWidgets + ) + } + } + } + + /// Remove the old files in the generation directory. + func removeOldFiles() { + if let fileURLs = try? FileManager.default.contentsOfDirectory( + at: .init(fileURLWithPath: configuration.folder), + includingPropertiesForKeys: nil, + options: .skipsHiddenFiles + ) { + for fileURL in fileURLs where fileURL.pathExtension == "swift" { + print("Removing \(fileURL.lastPathComponent)") + try? FileManager.default.removeItem(at: fileURL) + } + } + } + + /// Get the definitions of a GIR file at a specified path. + /// - Parameter path: The path. + /// - Returns: The GIR data. + func getDefinitions(path: String) -> GIR? { + var path = path + let girDir = getGirDir() + path.replace(GenerationConfiguration.includeDir, with: girDir) + let data = (try? Data(contentsOf: .init(fileURLWithPath: path))) ?? .init() + return try? GIR.decodeGIR(data) + } + + /// Get the directory of the gir files. + /// - Returns: The path. + func getGirDir() -> String { + let process = Process() + process.executableURL = .init(fileURLWithPath: "/bin/bash") + process.arguments = ["-c", "pkg-config --variable=includedir gtk4"] + let pipe = Pipe() + process.standardOutput = pipe + try? process.run() + let data = pipe.fileHandleForReading.readDataToEndOfFile() + return .init(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + } + + /// Create a file containing a class. + /// - Parameters: + /// - class: The class. + /// - config: The widget configuration. + /// - classes: All the available classes. + /// - configs: All the available configs. + func createFile(`class`: Class, config: WidgetConfiguration, classes: [Class], configs: [WidgetConfiguration]) { + print("Generating \(config.name ?? `class`.name).swift") + let path = "\(configuration.folder)\(config.name ?? config.class).swift" + let data = `class` + .generate(config: config, genConfig: configuration, classes: classes, configs: configs) + .data(using: .utf8) + try? data?.write(to: .init(fileURLWithPath: path)) + } + +} diff --git a/Sources/Generation/GenerationConfiguration.swift b/Sources/Generation/GenerationConfiguration.swift new file mode 100644 index 0000000..c4c1818 --- /dev/null +++ b/Sources/Generation/GenerationConfiguration.swift @@ -0,0 +1,266 @@ +// +// GenerationConfiguration.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +import Foundation + +/// The configuration for the generation. +struct GenerationConfiguration { + + /// The include directory. + static let includeDir = "$(pkg-config --variable=includedir gtk4)" + /// The directory containing the GIR files. + static let girFilePath = "\(includeDir)/../share/gir-1.0/" + + /// The Gtk GIR file. + var gtkGirFilePath = girFilePath + "Gtk-4.0.gir" + /// The Adw GIR file. + var adwGirFilePath = girFilePath + "Adw-1.gir" + + /// The folder containing the generated Swift files. + var folder = "Sources/Adwaita/View/Generated/" + + /// The Libadwaita widgets. + var adwWidgets: [WidgetConfiguration] = [ + .init(class: "StatusPage", excludeProperties: ["paintable"]), + .init(class: "Banner", initializer: "adw_banner_new(title)", requiredProperties: ["title"]), + .init( + class: "Avatar", + initializer: "adw_avatar_new(size.cInt, text, showInitials.cBool)", + requiredProperties: ["size", "show-initials"], + excludeProperties: ["custom-image"] + ), + .init( + class: "WindowTitle", + initializer: "adw_window_title_new(title, subtitle)", + requiredProperties: ["title", "subtitle"] + ), + .init(class: "Bin", cast: true), + .init(class: "ButtonContent"), + .init( + class: "Carousel", + dynamicWidget: .init( + insert: "adw_carousel_insert", + remove: "adw_carousel_remove", + getElement: "adw_carousel_get_nth_page(widget, UInt(index).cInt)" + ), + excludeProperties: ["position", "scroll-params"] + ), + .init(class: "Clamp", excludeProperties: ["unit"]), + .init(class: "PreferencesRow", cast: true), + .init( + class: "ActionRow", + staticWidgets: [ + .init(name: "suffix", add: "adw_action_row_add_suffix"), + .init(name: "prefix", add: "adw_action_row_add_prefix") + ], + cast: true + ), + .init(class: "SwitchRow", bindings: [.init(property: "active")]), + .init( + class: "ComboRow", + bindings: [.init(property: "selected")], + excludeProperties: ["expression", "factory", "list-factory", "model", "selected-item"], + cast: true + ), + .init( + class: "ExpanderRow", + bindings: [.init(property: "expanded"), .init(property: "enable-expansion")], + staticWidgets: [ + .init(name: "rows", add: "adw_expander_row_add_row"), + .init(name: "suffix", add: "adw_expander_row_add_suffix"), + .init(name: "prefix", add: "adw_expander_row_add_prefix") + ], + cast: true + ), + .init( + class: "EntryRow", + staticWidgets: [ + .init(name: "suffix", add: "adw_entry_row_add_suffix"), + .init(name: "prefix", add: "adw_entry_row_add_prefix") + ], + excludeProperties: ["attributes", "input-hints", "input-purpose"], + cast: true + ), + .init( + class: "PasswordEntryRow", + excludeProperties: ["attributes", "input-hints", "input-purpose"], + cast: true + ), + .init( + class: "SpinRow", + initializer: "adw_spin_row_new(nil, climbRate, digits.cInt)", + bindings: [.init(property: "value")], + requiredProperties: ["climb-rate", "digits"], + excludeProperties: ["adjustment", "update-policy"] + ), + .init( + class: "PreferencesGroup", + staticWidgets: [.init(name: "child", add: "adw_preferences_group_add")], + cast: true + ), + .init( + class: "PreferencesPage", + staticWidgets: [.init(name: "child", add: "adw_preferences_group_add")], + cast: true + ), + .init( + class: "OverlaySplitView", + bindings: [.init(property: "show-sidebar")], + excludeProperties: ["sidebar-position", "sidebar-width-unit"] + ), + .init( + class: "HeaderBar", + staticWidgets: [ + .init(name: "start", add: "adw_header_bar_pack_start"), + .init(name: "end", add: "adw_header_bar_pack_end") + ], + excludeProperties: ["centering-policy"] + ), + .init( + class: "ToolbarView", + staticWidgets: [ + .init(name: "bottom", add: "adw_toolbar_view_add_bottom_bar"), + .init(name: "top", add: "adw_toolbar_view_add_top_bar") + ], + excludeProperties: ["top-bar-style", "bottom-bar-style"] + ), + .init(class: "ToastOverlay"), + .init( + class: "SplitButton", + excludeProperties: ["direction", "popover"], + setConditions: ["label": "storage.content[\"child\"] == nil"] + ) + ] + + /// The Gtk widgets. + var gtkWidgets: [WidgetConfiguration] = [ + .init( + class: "Label", + initializer: "gtk_label_new(label)", + requiredProperties: ["label"], + excludeProperties: [ + "attributes", + "ellipsize", + "extra-menu", + "justify", + "natural-wrap-mode", + "tabs", + "wrap-mode" + ], + excludeSignals: ["activate-current-link", "activate-link", "move-cursor"] + ), + .init( + class: "Box", + initializer: "gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing.cInt)", + staticWidgets: [ + .init(name: "append", add: "gtk_box_append"), + .init(name: "prepend", add: "gtk_box_prepend") + ], + requiredProperties: ["spacing"], + excludeProperties: ["baseline-position"], + cast: true + ), + .init(class: "Spinner"), + .init(class: "LevelBar", excludeProperties: ["mode"]), + .init( + class: "ListBox", + dynamicWidget: .init( + insert: "gtk_list_box_insert", + remove: "gtk_list_box_remove", + getElement: "gtk_list_box_get_row_at_index(widget, index.cInt)?.cast()" + ), + excludeProperties: ["selection-mode"] + ), + .init(class: "ProgressBar", excludeProperties: ["ellipsize"]), + .init(class: "Button", cast: true, setConditions: ["label": "storage.content[\"child\"] == nil"]), + .init( + class: "ToggleButton", + bindings: [.init(property: "active")], + excludeProperties: ["group"], + cast: true, + setConditions: ["label": "storage.content[\"child\"] == nil"] + ), + .init( + class: "LinkButton", + initializer: "gtk_link_button_new(uri)", + requiredProperties: ["uri"], + excludeSignals: ["activate-link"], + setConditions: ["label": "storage.content[\"child\"] == nil"] + ), + .init( + class: "CheckButton", + bindings: [.init(property: "active")], + excludeProperties: ["group"], + cast: true, + setConditions: ["label": "storage.content[\"child\"] == nil"] + ), + .init( + class: "MenuButton", + name: "Menu", + bindings: [.init(property: "active")], + excludeProperties: ["direction", "popover"], + setConditions: ["label": "storage.content[\"child\"] == nil"] + ), + .init( + class: "CenterBox", + excludeProperties: ["baseline-position"] + ), + .init( + class: "ScrolledWindow", + excludeProperties: [ + "hadjustment", + "hscrollbar-policy", + "vadjustment", + "vscrollbar-policy", + "window-placement" + ] + ), + .init(class: "Overlay", staticWidgets: [.init(name: "overlay", add: "gtk_overlay_add_overlay")]) + ] + + /// The unshortening map. + var unshorteningMap = [ + "char": "character", + "str": "string" + ] + + /// Replacements for C types. + var cTypeReplacements = [ + "const char*": "String", + "char*": "String", + "gchar*": "String", + "gboolean": "Bool", + "gdouble": "Double", + "guint": "UInt", + "gint": "Int", + "gfloat": "Float", + "double": "Double", + "GIcon*": "OpaquePointer", + "GdkPixbuf*": "OpaquePointer", + "GdkPaintable*": "OpaquePointer", + "NavigationPage": "NavigationPage" + ] + + /// Modifications for converting a Swift into a C type. + var cTypeProperties = [ + "Bool": "cBool", + "Int": "cInt", + "UInt": "cInt" + ] + + /// Default values for modifiers with a certain type. + var defaultModifierValues = [ + "Bool?": "true" + ] + + /// Modifications for converting a C into a Swift type. + var getterTypeConversions: [String: (String) -> String] = [ + "gboolean": { "\($0) != 0" }, + "guint": { ".init(\($0))" } + ] + +} diff --git a/Sources/Generation/WidgetConfiguration.swift b/Sources/Generation/WidgetConfiguration.swift new file mode 100644 index 0000000..95b05f4 --- /dev/null +++ b/Sources/Generation/WidgetConfiguration.swift @@ -0,0 +1,68 @@ +// +// WidgetConfiguration.swift +// Adwaita +// +// Created by david-swift on 14.01.24. +// + +import Foundation + +/// The configuration for a single widget. +struct WidgetConfiguration { + + /// The class name. + var `class`: String + /// The name of the Swift structure. + var name: String? + /// Explicitly set the C initializer. + var initializer: String? + /// The properties that should be treated as bindings. + var bindings: [BindingConfiguration] = [] + /// The dynamic widget. + var dynamicWidget: DynamicWidget? + /// The static widgets. + var staticWidgets: [StaticWidget] = [] + /// Non-optional properties. + var requiredProperties: [String] = [] + /// Excluded properties. + var excludeProperties: [String] = [] + /// Excluded signals. + var excludeSignals: [String] = [] + /// Whether to cast the type. + var cast = false + /// Conditions for when to update a property. + var setConditions: [String: String] = [:] + + /// The configuration for a binding. + struct BindingConfiguration { + + /// The property. + var property: String + /// The signal. If not specified, use "notify::property-name". + var signal: String? + + } + + /// The configuration for a dynamic widget. + struct DynamicWidget { + + /// The insert function. + var insert: String + /// The remove function. + var remove: String + /// Get a child to remove from its index. + var getElement: String = "index.cInt" + + } + + /// The configuration for a static widget. + struct StaticWidget { + + /// The widget's name. + var name: String + /// The function for adding a child. + var add: String + + } + +} diff --git a/Tests/CounterDemo.swift b/Tests/CounterDemo.swift index 4b82cf4..3cae929 100644 --- a/Tests/CounterDemo.swift +++ b/Tests/CounterDemo.swift @@ -8,7 +8,6 @@ // swiftlint:disable missing_docs import Adwaita -import Libadwaita struct CounterDemo: View { diff --git a/Tests/Demo.swift b/Tests/Demo.swift index 7ad34c0..8d248d6 100644 --- a/Tests/Demo.swift +++ b/Tests/Demo.swift @@ -44,7 +44,7 @@ struct Demo: App { .closeShortcut() .defaultSize(width: 400, height: 250) Window(id: "toolbar-demo", open: 0) { _ in - ToolbarDemo.WindowContent() + ToolbarDemo.WindowContent().stopModifiers() } .closeShortcut() .defaultSize(width: 400, height: 250) @@ -77,7 +77,7 @@ struct Demo: App { var app: GTUIApp! var view: Body { - OverlaySplitView(visible: sidebarVisible) { + OverlaySplitView(visible: $sidebarVisible) { ScrollView { List(Page.allCases, selection: $selection) { element in Text(element.label) @@ -91,9 +91,7 @@ struct Demo: App { menu } .headerBarTitle { - Text("Demo") - .style("heading") - .transition(.crossfade) + WindowTitle(subtitle: "", title: "Demo") } } } content: { @@ -119,8 +117,7 @@ struct Demo: App { Text("") .transition(.crossfade) } else { - Text("Swift Adwaita Demo") - .style("heading") + WindowTitle(subtitle: "Demo", title: selection.label) .transition(.crossfade) } } @@ -145,6 +142,7 @@ struct Demo: App { .keyboardShortcut("q".ctrl()) } } + .primary() } } diff --git a/Tests/FormDemo.swift b/Tests/FormDemo.swift index 6922116..b5f7fe6 100644 --- a/Tests/FormDemo.swift +++ b/Tests/FormDemo.swift @@ -8,7 +8,6 @@ // swiftlint:disable missing_docs no_magic_numbers import Adwaita -import Libadwaita struct FormDemo: View { @@ -37,32 +36,30 @@ struct FormDemo: View { var view: Body { ScrollView { VStack { - Form { - ActionRow("Rows have a title") - .subtitle(text) - ActionRow("Rows can have suffix widgets") - .suffix { - Button("Action") { } - .verticalCenter() - } - } - .padding() + actionRows FormSection("Entry Rows") { Form { EntryRow("Entry Row", text: $text) .suffix { - Button(icon: .default(icon: .editCopy)) { Clipboard.copy(text) } + Button(icon: .default(icon: .editCopy)) { State.copy(text) } .style("flat") .verticalCenter() } - EntryRow("Password", text: $password) - .secure() + EntryRow(password, text: $password) + .secure(text: $password) } } .padding() - rowDemo("Spin Rows", row: SpinRow("Spin Row", value: $value, min: 0, max: 100)) - rowDemo("Switch Rows", row: SwitchRow("Switch Row", isOn: $isOn)) - rowDemo("Combo Rows", row: ComboRow("Combo Row", selection: $selection, values: values)) + rowDemo("Spin Rows", row: SpinRow("Spin Row", value: $value, min: 0, max: 100).subtitle("\(value)")) + rowDemo("Switch Rows", row: SwitchRow("Switch Row", isOn: $isOn).subtitle(isOn ? "On" : "Off")) + rowDemo( + "Combo Rows", + row: ComboRow("Combo Row", selection: $selection, values: values).subtitle(selection) + ) + rowDemo("Expander Rows", row: ExpanderRow().title("Expander Row").rows { + ActionRow("Hello") + ActionRow("World") + }) } .padding() .frame(maxSize: 400) @@ -72,6 +69,19 @@ struct FormDemo: View { } } + var actionRows: Form { + .init { + ActionRow("Rows have a title") + .subtitle(text) + ActionRow("Rows can have suffix widgets") + .suffix { + Button("Action") { } + .verticalCenter() + } + } + .padding() + } + func rowDemo(_ title: String, row: View) -> View { FormSection(title) { Form { diff --git a/Tests/Page.swift b/Tests/Page.swift index 4902a7a..d6bf9de 100644 --- a/Tests/Page.swift +++ b/Tests/Page.swift @@ -8,7 +8,6 @@ // swiftlint:disable missing_docs implicitly_unwrapped_optional import Adwaita -import Libadwaita enum Page: String, Identifiable, CaseIterable, Codable { @@ -40,7 +39,7 @@ enum Page: String, Identifiable, CaseIterable, Codable { } } - var icon: Libadwaita.Icon? { + var icon: Icon? { switch self { case .welcome: return .default(icon: .emojiNature) diff --git a/Tests/ToolbarDemo.swift b/Tests/ToolbarDemo.swift index f1d3250..632396f 100644 --- a/Tests/ToolbarDemo.swift +++ b/Tests/ToolbarDemo.swift @@ -55,10 +55,4 @@ struct ToolbarDemo: View { } -extension Int: Identifiable { - - public var id: Self { self } - -} - // swiftlint:enable missing_docs no_magic_numbers diff --git a/Tests/ViewSwitcherDemo.swift b/Tests/ViewSwitcherDemo.swift index a6b5166..c35d56f 100644 --- a/Tests/ViewSwitcherDemo.swift +++ b/Tests/ViewSwitcherDemo.swift @@ -8,7 +8,6 @@ // swiftlint:disable missing_docs import Adwaita -import Libadwaita struct ViewSwitcherDemo: View { diff --git a/user-manual/Advanced/CreatingWidgets.md b/user-manual/Advanced/CreatingWidgets.md index f3fed67..eb35edb 100644 --- a/user-manual/Advanced/CreatingWidgets.md +++ b/user-manual/Advanced/CreatingWidgets.md @@ -2,7 +2,7 @@ Widgets are special views that do not provide a collection of other views as a content, but have functions that are called when creating or updating the view. -Normally, a widget manages a GTK or Libadwaita widget using [Libadwaita][1]. +Normally, a widget manages a GTK or Libadwaita widget using the C API. ## Recreate the `Text` widget In this tutorial, we will recreate the text widget. @@ -21,6 +21,8 @@ struct CustomText: Widget { This widget can be called in a view body using `CustomText(text: "Hello, world!")`. Now, add the two functions required by the protocol: ```swift +import CAdw + struct CustomText: Widget { var text: String @@ -30,6 +32,7 @@ struct CustomText: Widget { } ``` +Import CAdw which exposes the whole C Libadwaita and Gtk API to Swift. ## The `container(modifiers:)` Function This function initializes the widget when the widget appears for the first time. @@ -37,10 +40,9 @@ It expects a `ViewStorage` as the return type. In our case, this function is very simple: ```swift func container(modifiers: [(View) -> View]) -> ViewStorage { - .init(MarkupLabel(self.text)) + .init(gtk_label_new(text)?.opaque()) } ``` -`MarkupLabel` is defined in [Libadwaita][1]. ## The `update(_:modifiers:)` Function Whenever a state of the app changes, the `update(_:)` function of the widget gets called. @@ -48,9 +50,7 @@ You get the view storage that you have previously initialized as a parameter. Update the storage to reflect the current state of the widget: ```swift func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { - if let label = storage.view as? MarkupLabel { - label.setText(text) - } + gtk_label_set_label(storage.pointer, text) } ``` @@ -58,7 +58,6 @@ func update(_ storage: ViewStorage, modifiers: [(View) -> View]) { Some widgets act as containers that accept other widgets as children. In that case, use the `ViewStorage`'s `content` property for storing their view storages. In the `update(_:modifiers:)` function, update the children's storages. -An example showcasing how to implement containers is the [VStack][2]. +An example showcasing how to implement containers is the [Box][1] (it is auto-generated). -[1]: https://github.com/AparokshaUI/Libadwaita -[2]: ../../Sources/Adwaita/View/VStack.swift +[1]: ../../Sources/Adwaita/View/Generated/Box.swift diff --git a/user-manual/Information/AutoGeneratedWidgets.md b/user-manual/Information/AutoGeneratedWidgets.md new file mode 100644 index 0000000..3064288 --- /dev/null +++ b/user-manual/Information/AutoGeneratedWidgets.md @@ -0,0 +1,89 @@ +# Auto-Generated Widgets + +Visit the [Libadwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/) or [Gtk](https://docs.gtk.org/gtk4/index.html) docs +and find the widget you want to implement. + +There is an auto-generated interface available for many Libadwaita and Gtk widgets. +This page shows how to use them, using [AdwSplitButton](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/class.SplitButton.html) as an example. + +## The Initializer +The type name is the original type name without the prefix, in that case `SplitButton`. + +Most of the widgets enable initialization with an empty initializer. +Exceptions will cause a helpful error message when trying with an empty initializer. + +```swift +var view: Body { + SplitButton() +} +``` + +## Simple Properties +Properties with "simple" types, such as strings (and most other types), have +a Swift equivalent with the same name in camel case. +For example, you will find the properties `label` and `dropdown-tooltip` in the docs. +They can be used in the following way: + +```swift +var view: Body { + SplitButton() + .label("Hello") + .dropdownTooltip("World") +} +``` + +Properties that are booleans can be set implicitly to `true`: + +```swift +var view: Body { + SplitButton() + .label("Hello") + .canShrink() +} +``` + +## Signals +You can connect to signals using their names. + +```swift +var view: Body { + SplitButton() + .label("Hello") + .clicked { + print("Clicked") + } +} +``` + +## Views and Menu Models +Properties of the type `GtkWidget` can be used in the following way: +```swift + SplitButton() + .child { + ButtonContent() + .label("Hello") + .iconName("zoom-original-symbolic") + } +} +``` + +`GMenuModel` is treated in a similarly: +```swift +var view: Body { + SplitButton() + .label("Hello") + .menuModel(app: app) { + MenuButton("Test") { + print("Test") + } + MenuButton("World") { + print("World") + } + } +} +``` + +## Bindings +Some properties can be changed by a user action (e.g. toggles). +They expect a binding instead of a "normal" value. +Compilation error messages will be helpful in spotting those properties. diff --git a/user-manual/Information/Widgets.md b/user-manual/Information/Widgets.md index a0d05ca..5a0a288 100644 --- a/user-manual/Information/Widgets.md +++ b/user-manual/Information/Widgets.md @@ -1,6 +1,7 @@ # Widgets -This is an overview of the available widgets and other components in _Adwaita_. +This is an overview of the available widgets and other components in _Adwaita_ that are not auto-generated. +There are many more widgets available using auto-generation. Learn [how to use them.](AutoGeneratedWidgets.md) | Name | Description | Widget | | -------------------- | ------------------------------------------------------------------- | ---------------------- | @@ -35,20 +36,21 @@ This is an overview of the available widgets and other components in _Adwaita_. | Syntax | Description | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| `inspect(_:)` | Edit the underlying [Libadwaita](https://github.com/AparokshaUI/Libadwaita) widget. | +| `inspect(_:)` | Edit the underlying Gtk or Libadwaita widget. | | `padding(_:_:)` | Add empty space around a view. | | `hexpand(_:)` | Enable or disable the horizontal expansion of a view. | | `vexpand(_:)` | Enable or disable the vertical expansion of a view. | | `halign(_:)` | Set the horizontal alignment of a view. | | `valign(_:)` | Set the vertical alignment of a view. | -| `frame(minWidth:minHeight:)` | Set the view’s minimal width or height. | -| `frame(maxSize:)` | Set the view’s maximal size. | +| `frame(minWidth:minHeight:)` | Set the view’s minimum width or height. | +| `frame(maxWidth:)` | Set the view’s maximum width. | +| `frame(maxHeight:)` | Set the view’s maximum height. | | `transition(_:)` | Assign a transition with the view that is used if it is a direct child of an EitherView. | | `onUpdate(_:)` | Run a function every time a view gets updated. | | `navigationTitle(_:)` | Add a title that is used if the view is a direct child of a NavigationView. | | `style(_:)` | Add a style class to the view. | | `onAppear(_:)` | Run when the view is rendered for the first time. | -| `inspectOnAppear(_:)` | Edit the underlying [Libadwaita](https://github.com/AparokshaUI/Libadwaita) class when the view is rendered for the first time.| +| `inspectOnAppear(_:)` | Edit the underlying Gtk or Libadwaita class when the view is rendered for the first time. | | `topToolbar(visible:_:)` | Add a native toolbar to the view. Normally, it contains a HeaderBar. | | `bottomToolbar(visible:_:)` | Add a native bottom toolbar to the view. | | `modifyContent(_:modify:)` | Replace all occurrences of a certain view type with another view. |