diff --git a/.github/ISSUE_TEMPLATE/component_request.yml b/.github/ISSUE_TEMPLATE/component_request.yml deleted file mode 100644 index 63a6c7b..0000000 --- a/.github/ISSUE_TEMPLATE/component_request.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Components request -description: Suggest an idea for a new component -title: Description of the component request -labels: enhancement - -body: - - type: textarea - attributes: - label: Why would you like to add a new component? - placeholder: >- - A clear and concise description of why the component should be added. - validations: - required: false - - - type: textarea - attributes: - label: Describe your idea for the implementation. - placeholder: >- - What could the implementation be like in Adwaita? - validations: - required: false - - - type: textarea - attributes: - label: Additional context - placeholder: >- - Add any other context about the component request here. - validations: - required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f0ed3f3..cce03ae 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,4 @@ ## Steps -- [ ] Add your name or username and a link to your GitHub profile into the [Contributors.md][1] file. - [ ] Build the project on your machine. If it does not compile, fix the errors. - [ ] Describe the purpose and approach of your pull request below. - [ ] Submit the pull request. Thank you very much for your contribution! @@ -10,5 +9,3 @@ _If there is a related issue, add the link._ ## Approach _Describe how this pull request solves the problem or adds the feature._ - -[1]: /Contributors.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4390761..c7fa86d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,6 @@ name: Deploy Docs on: push: branches: ["main"] - workflow_dispatch: permissions: contents: read @@ -22,31 +21,20 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 - - name: Install Libadwaita - run: | - brew update - brew install libadwaita - sed -i '' 's/-I..includedir.//g' $(brew --prefix)/Library/Homebrew/os/mac/pkgconfig/*/libffi.pc - - name: Clone DocC Repo - run: | - git clone https://github.com/AparokshaUI/Adwaita.docc Sources/Adwaita/Adwaita.docc - rm Sources/Adwaita/Adwaita.docc/LICENSE.md - rm Sources/Adwaita/Adwaita.docc/README.md - y | rm -r Sources/Adwaita/Adwaita.docc/.git - name: Build Docs run: | xcrun xcodebuild docbuild \ - -scheme Adwaita \ + -scheme Meta \ -destination 'generic/platform=macOS' \ - -derivedDataPath "$PWD/.derivedData" + -derivedDataPath "$PWD/.derivedData" \ + -skipPackagePluginValidation xcrun docc process-archive transform-for-static-hosting \ - "$PWD/.derivedData/Build/Products/Debug/Adwaita.doccarchive" \ + "$PWD/.derivedData/Build/Products/Debug/Meta.doccarchive" \ --output-path "docs" \ - --hosting-base-path "adwaita-swift" + --hosting-base-path "Meta" - name: Modify Docs run: | - echo "" > docs/index.html; - sed -i '' 's/#06f/#ea3358/g' docs/css/documentation-topic~topic~tutorials-overview.d6f5411c.css + echo "" > docs/index.html; sed -i '' 's/,2px/,10px/g' docs/css/index.038e887c.css - name: Upload Artifact uses: actions/upload-pages-artifact@v3 diff --git a/Package.swift b/Package.swift index c40594d..f10cee9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 5.9 // // Package.swift // Meta diff --git a/README.md b/README.md index c77f65a..5c06f54 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,57 @@

- Adwaita Icon -

Adwaita for Swift

+

Meta

- + Documentation · - + GitHub

-_Adwaita_ is a framework for creating user interfaces for GNOME with an API similar to SwiftUI. - -The following code: - -```swift -struct Counter: View { - - @State private var count = 0 - - var view: Body { - HStack { - Button(icon: .default(icon: .goPrevious)) { - count -= 1 - } - Text("\(count)") - .style("title-1") - .frame(minWidth: 100) - Button(icon: .default(icon: .goNext)) { - count += 1 - } - } - } - -} -``` - -Describes a simple counter view: - -![Counter Example][image-1] - -More examples are available in the [demo app][1]. +_Meta_ is a framework allowing the creation of user interface (UI) frameworks in Swift. ## Table of Contents -- [Goals][2] -- [Widgets][3] -- [Installation][4] -- [Usage][5] -- [Thanks][6] +- [Overview](#overview) +- [Usage](#usage) +- [Thanks](#thanks) -## Goals +## Overview -_Adwaita_’s main goal is to provide an easy-to-use interface for creating GNOME apps. The backend should stay as simple as possible, while not limiting the possibilities there are with [Libadwaita][7] and [GTK][8]. +_Meta_ follows the following principles: -If you want to use _Adwaita_ in a project, but there are widgets missing, open an [issue on GitHub][9]. +- It is a **declarative** framework, meaning that instead of writing _how_ to construct a user interface, you write _what_ it looks like. +- The user interface is treated as a function of its **state**. Instead of directly modifying the UI, modify its state to update views. +- Multiple UI frameworks can be used in the same code, but the **selection of the framework** happens when executing the app. This enables the creation of cross-platform UI frameworks combining several UI frameworks rendering always with the same backend. -Find more information about the project's motivation in [this blog post](https://www.swift.org/blog/adwaita-swift/). +It knows the following layers of UI: -## Widgets +- An app is the entry point of the executable, containing the windows. +- A window is a container holding one or multiple views. +- A view is a part of the actual UI inside a window, another view or a menu. +- A menu is a list of buttons, other menus, and views. Certain views (such as menu buttons) allow menus to be used. -An overview of the widgets supported by _Adwaita_ is available [here](user-manual/Information/Widgets.md). +Detailed information can be found in the [docs](https://aparokshaui.github.io/meta/). -## Installation -### Dependencies -#### Flatpak +## Usage -It is recommended to develop apps inside of a Flatpak. -That way, you don't have to install Swift or any of the dependencies on your system, and you always have access to the latest versions. -Take a look at the [template repository](https://github.com/AparokshaUI/AdwaitaTemplate). -This works on Linux only. +_Meta_ can be used for creating UI frameworks in Swift which can then be used to create apps. -#### Directly on System +Follow those steps if you want to create a UI framework. -You can also run your apps directly on the system. - -If you are using a Linux distribution, install `libadwaita-devel` or `libadwaita` (or something similar, based on the package manager) as well as `gtk4-devel`, `gtk4` or similar. - -On macOS, follow these steps: -1. Install [Homebrew][11]. -2. Install Libadwaita (and thereby GTK 4): -``` -brew install libadwaita -``` - -### Swift Package 1. Open your Swift package in GNOME Builder, Xcode, or any other IDE. 2. Open the `Package.swift` file. 3. Into the `Package` initializer, under `dependencies`, paste: ```swift -.package(url: "https://github.com/AparokshaUI/Adwaita", from: "0.1.0") +.package(url: "https://github.com/AparokshaUI/Meta", from: "0.1.0") ``` -## Usage - -I recommend using the [template repository](https://github.com/AparokshaUI/AdwaitaTemplate) as a starting point. - -Follow the [interactive tutorial](https://aparokshaui.github.io/adwaita-swift/tutorials/table-of-contents) or [read the docs](https://aparokshaui.github.io/adwaita-swift/documentation/adwaita) in order to get to know _Adwaita for Swift_. - ## Thanks -### Dependencies -- [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] - -[1]: Tests/ -[2]: #goals -[3]: #widgets -[4]: #installation -[5]: #usage -[6]: #thanks -[7]: https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/index.html -[8]: https://docs.gtk.org/gtk4/ -[9]: https://github.com/AparokshaUI/Adwaita/issues -[10]: https://github.com/AparokshaUI/Libadwaita -[11]: https://brew.sh -[12]: user-manual/GettingStarted.md -[13]: user-manual/Basics/HelloWorld.md -[14]: user-manual/Basics/CreatingViews.md -[15]: user-manual/Basics/Windows.md -[16]: user-manual/Basics/KeyboardShortcuts.md -[17]: user-manual/Advanced/CreatingWidgets.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 -[23]: https://github.com/SourceDocs/SourceDocs - -[image-1]: Icons/Counter.png -[image-2]: Icons/Demo.png +- [DocC](https://github.com/apple/swift-docc) used for the documentation +- [SwiftLint](https://github.com/realm/SwiftLint) for checking whether code style conventions are violated +- The programming language [Swift](https://github.com/swiftlang/swift) diff --git a/Sources/Meta.docc/Meta.md b/Sources/Meta.docc/Meta.md new file mode 100644 index 0000000..e2d537c --- /dev/null +++ b/Sources/Meta.docc/Meta.md @@ -0,0 +1,18 @@ +# ``Meta`` + +_Meta_ is a framework allowing the creation of user interface (UI) frameworks in Swift. + +## Overview + +_Meta_ follows the following principles: + +- It is a **declarative** framework, meaning that instead of writing _how_ to construct a user interface, you write _what_ it looks like. +- The user interface is treated as a function of its **state**. Instead of directly modifying the UI, modify its state to update views. +- Multiple UI frameworks can be used in the same code, but the **selection of the framework** happens when executing the app. This enables the creation of cross-platform UI frameworks combining several UI frameworks rendering always with the same backend. + +It knows the following layers of UI: + +- An app is the entry point of the executable, containing the windows. +- A window is a container holding one or multiple views. +- A view is a part of the actual UI inside a window, another view or a menu. +- A menu is a list of buttons, other menus, and views. Certain views (such as menu buttons) allow menus to be used. diff --git a/Sources/Meta.docc/theme-settings.json b/Sources/Meta.docc/theme-settings.json new file mode 100644 index 0000000..ccd8a86 --- /dev/null +++ b/Sources/Meta.docc/theme-settings.json @@ -0,0 +1,46 @@ +{ + "theme": { + "border-radius": "10px", + "button": { + "border-radius": "20px" + }, + "color": { + "button-background": "#ea3358", + "button-background-active": "#ea3358", + "button-background-hover": "#fc557a", + "button-text": "#ffffff", + "header": "#7f313b", + "documentation-intro-accent": "var(--color-header)", + "documentation-intro-fill": "radial-gradient(circle at top, var(--color-header) 30%, #000 100%)", + "link": "#ea3358", + "nav-link-color": "#ea3358", + "nav-dark-link-color": "#ea3358", + "tutorials-overview-link": "#fb4469", + "step-background": { + "light": "#fffaff", + "dark": "#302c2d" + }, + "step-focused": "#ea3358", + "tabnav-item-border-color": "#ea3358", + "tutorial-background": { + "light": "", + "dark": "#1d1d1f" + }, + "tutorials-overview-background": "linear-gradient(180deg, rgba(43,20,23,1) 0%, rgba(41, 3, 8, 0.808) 60%, rgba(0,0,0,1) 100%)", + "fill-light-blue-secondary": "#ea3358", + "fill-blue": "#ea3358", + "figure-blue": "#ea3358", + "standard-blue-documentation-intro-fill": "#ea3358", + "figure-blue": "#ea3358", + "tutorial-hero-background": "#100a0b", + "navigator-item-hover": { + "light": "#ea335815", + "dark": "#7f313b" + } + }, + "additionalProperties": "#ea3358", + "tutorial-step": { + "border-radius": "15px" + } + } +} diff --git a/Sources/Model/Data Flow/Binding.swift b/Sources/Model/Data Flow/Binding.swift index c8e2501..d0bd0d4 100644 --- a/Sources/Model/Data Flow/Binding.swift +++ b/Sources/Model/Data Flow/Binding.swift @@ -71,7 +71,7 @@ public struct Binding { private var handlers: [(Value) -> Void] = [] /// Get a property of any content of a `Binding` as a `Binding`. - /// - Parameter dynamicMember: The path to the member. + /// - Parameter keyPath: The path to the member. /// - Returns: The binding. public subscript(dynamicMember keyPath: WritableKeyPath) -> Binding { .init { diff --git a/Sources/Model/Extensions/Array.swift b/Sources/Model/Extensions/Array.swift index c82c2f5..8c4b11a 100644 --- a/Sources/Model/Extensions/Array.swift +++ b/Sources/Model/Extensions/Array.swift @@ -54,6 +54,7 @@ extension Array: AnyView where Element == AnyView { /// - Parameters: /// - modifiers: Modify views before generating the storages. /// - type: The type of the widgets. + /// - Returns: The storages. public func storages( modifiers: [(AnyView) -> AnyView], type: WidgetType.Type diff --git a/Sources/Model/Extensions/DefaultStringInterpolation.swift b/Sources/Model/Extensions/DefaultStringInterpolation.swift deleted file mode 100644 index 0ff371e..0000000 --- a/Sources/Model/Extensions/DefaultStringInterpolation.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// DefaultStringInterpolation.swift -// Meta -// -// Created by david-swift on 26.05.24. -// -// Thanks to Eneko Alonso, Pyry Jahkola, cukr for the comments in this Swift forum discussion: -// "Multi-line string nested indentation with interpolation" -// https://forums.swift.org/t/multi-line-string-nested-indentation-with-interpolation/36933 -// - -extension DefaultStringInterpolation { - - /// Preserve the indentation in a multi line string. - /// - Parameter string: The string. - /// - /// Use it the following way: - /// """ - /// Hello - /// \(indented: "World\n Test") - /// """ - public mutating func appendInterpolation(indented string: String) { - // swiftlint:disable compiler_protocol_init - let indent = String(stringInterpolation: self).reversed().prefix { " \t".contains($0) } - // swiftlint:enable compiler_protocol_init - if indent.isEmpty { - appendInterpolation(string) - } else { - appendLiteral( - string.split(separator: "\n", omittingEmptySubsequences: false).joined(separator: "\n" + indent) - ) - } - } - -} diff --git a/Sources/Model/Extensions/String.swift b/Sources/Model/Extensions/String.swift index 80986c5..7b0b9f2 100644 --- a/Sources/Model/Extensions/String.swift +++ b/Sources/Model/Extensions/String.swift @@ -7,7 +7,7 @@ extension String { - /// A label for main content in a view storage. - static var mainContent: Self { "main" } + /// An identifier for main content in a view storage. + public static var mainContent: Self { "main" } } diff --git a/Sources/Model/User Interface/View/AnyView.swift b/Sources/Model/User Interface/View/AnyView.swift index fb4f3c8..3be5ccb 100644 --- a/Sources/Model/User Interface/View/AnyView.swift +++ b/Sources/Model/User Interface/View/AnyView.swift @@ -51,7 +51,7 @@ extension AnyView { /// Wrap the view into a widget. /// - Parameter modifiers: Modify views before being updated. /// - Returns: The widget. - public func widget(modifiers: [(AnyView) -> AnyView]) -> Widget { + func widget(modifiers: [(AnyView) -> AnyView]) -> Widget { let modified = getModified(modifiers: modifiers) if let peer = modified as? Widget { return peer diff --git a/Sources/Model/User Interface/View/ViewBuilder.swift b/Sources/Model/User Interface/View/ViewBuilder.swift index bfb5c9c..54fdae4 100644 --- a/Sources/Model/User Interface/View/ViewBuilder.swift +++ b/Sources/Model/User Interface/View/ViewBuilder.swift @@ -11,7 +11,7 @@ import Foundation @resultBuilder public enum ViewBuilder { - /// A component used in the ``ArrayBuilder``. + /// A component used in the ``ViewBuilder``. public enum Component { /// A view as a component. diff --git a/Sources/Model/User Interface/View/ViewStorage.swift b/Sources/Model/User Interface/View/ViewStorage.swift index db80fe9..d24152e 100644 --- a/Sources/Model/User Interface/View/ViewStorage.swift +++ b/Sources/Model/User Interface/View/ViewStorage.swift @@ -5,19 +5,22 @@ // Created by david-swift on 26.05.24. // -/// Store a rendered view in a view storage. +/// Store a reference to a rendered view in a view storage. public class ViewStorage { /// The pointer. + /// + /// It can be a C pointer, a Swift class, or other information depending on the backend. + /// Some convenience widgets do not need a pointer to a native framework at all. public var pointer: Any? - /// The view's content. + /// The view's content for container widgets. public var content: [String: [ViewStorage]] /// The view's state (used in `StateWrapper`). var state: [String: StateProtocol] = [:] - /// Other properties. + /// Various properties of a widget. public var fields: [String: Any] = [:] - /// The pointer as an opaque pointer. + /// The pointer as an opaque pointer, as this is needed with backends interoperating with C or C++. public var opaquePointer: OpaquePointer? { get { pointer as? OpaquePointer @@ -30,7 +33,7 @@ public class ViewStorage { /// Initialize a view storage. /// - Parameters: /// - pointer: The pointer to the widget, its type depends on the backend. - /// - content: The view's content. + /// - content: The view's content for container widgets. public init( _ pointer: Any?, content: [String: [ViewStorage]] = [:] diff --git a/Sources/Model/User Interface/View/Widget.swift b/Sources/Model/User Interface/View/Widget.swift index 947510f..300064b 100644 --- a/Sources/Model/User Interface/View/Widget.swift +++ b/Sources/Model/User Interface/View/Widget.swift @@ -5,7 +5,10 @@ // Created by david-swift on 26.05.24. // -/// A widget is a view that know about its GTUI widget. +/// A widget is a view that know about its native backend widget. +/// +/// It enables the translation from the declarative definition to the creation +/// and updating of widgets in an imperative way. public protocol Widget: AnyView { /// The view storage. diff --git a/Sources/View/StateWrapper.swift b/Sources/View/StateWrapper.swift index 45eda1f..89f9b47 100644 --- a/Sources/View/StateWrapper.swift +++ b/Sources/View/StateWrapper.swift @@ -8,19 +8,16 @@ import Observation /// A storage for `@State` properties. -public struct StateWrapper: ConvenienceWidget { +struct StateWrapper: ConvenienceWidget { /// The content. var content: () -> Body /// The state information (from properties with the `State` wrapper). var state: [String: StateProtocol] = [:] - /// The identifier of the field storing whether to update the wrapper's content. - private var updateID: String { "update" } - /// Initialize a `StateWrapper`. /// - Parameter content: The view content. - public init(@ViewBuilder content: @escaping () -> Body) { + init(@ViewBuilder content: @escaping () -> Body) { self.content = content } @@ -39,14 +36,13 @@ public struct StateWrapper: ConvenienceWidget { /// - modifiers: Modify views before being updated. /// - updateProperties: Whether to update properties. /// - type: The type of the widgets. - public func update( + func update( _ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type ) { - var updateProperties = updateProperties ? true : (storage.fields[updateID] as? Bool ?? false) - storage.fields[updateID] = false + var updateProperties = updateProperties for property in state { if let oldID = storage.state[property.key]?.id { StateManager.changeID(old: oldID, new: property.value.id) @@ -68,7 +64,7 @@ public struct StateWrapper: ConvenienceWidget { /// - modifiers: Modify views before being updated. /// - type: The type of the widgets. /// - Returns: The view storage. - public func container(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage { + func container(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage { let content = content().storages(modifiers: modifiers, type: type) let storage = ViewStorage(nil, content: [.mainContent: content]) storage.state = state diff --git a/Sources/View/Wrapper.swift b/Sources/View/Wrapper.swift index 77a3568..fa00877 100644 --- a/Sources/View/Wrapper.swift +++ b/Sources/View/Wrapper.swift @@ -5,7 +5,7 @@ // Created by david-swift on 27.05.24. // -/// Wrap a view into a single widget. +/// Wrap a body into a single widget. public struct Wrapper: ConvenienceWidget { /// The content. diff --git a/Tests/DemoApp/DemoApp.swift b/Tests/DemoApp/DemoApp.swift index f41e7c6..993459a 100644 --- a/Tests/DemoApp/DemoApp.swift +++ b/Tests/DemoApp/DemoApp.swift @@ -65,12 +65,11 @@ struct DemoApp { DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType) } - StateManager.addUpdateHandler { _ in - DemoView().updateStorage(storage, modifiers: [], updateProperties: false, type: backendType) + StateManager.addUpdateHandler { force in + DemoView().updateStorage(storage, modifiers: [], updateProperties: force, type: backendType) } sleep(2) - DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType) } }