Set up documentation

This commit is contained in:
david-swift 2024-06-30 12:08:02 +02:00
parent 83a03441b5
commit 2551c0a14d
18 changed files with 122 additions and 219 deletions

View File

@ -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

View File

@ -1,5 +1,4 @@
## Steps ## 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. - [ ] Build the project on your machine. If it does not compile, fix the errors.
- [ ] Describe the purpose and approach of your pull request below. - [ ] Describe the purpose and approach of your pull request below.
- [ ] Submit the pull request. Thank you very much for your contribution! - [ ] 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 ## Approach
_Describe how this pull request solves the problem or adds the feature._ _Describe how this pull request solves the problem or adds the feature._
[1]: /Contributors.md

View File

@ -3,7 +3,6 @@ name: Deploy Docs
on: on:
push: push:
branches: ["main"] branches: ["main"]
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@ -22,31 +21,20 @@ jobs:
runs-on: macos-14 runs-on: macos-14
steps: steps:
- uses: actions/checkout@v4 - 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 - name: Build Docs
run: | run: |
xcrun xcodebuild docbuild \ xcrun xcodebuild docbuild \
-scheme Adwaita \ -scheme Meta \
-destination 'generic/platform=macOS' \ -destination 'generic/platform=macOS' \
-derivedDataPath "$PWD/.derivedData" -derivedDataPath "$PWD/.derivedData" \
-skipPackagePluginValidation
xcrun docc process-archive transform-for-static-hosting \ 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" \ --output-path "docs" \
--hosting-base-path "adwaita-swift" --hosting-base-path "Meta"
- name: Modify Docs - name: Modify Docs
run: | run: |
echo "<script>window.location.href += \"/documentation/adwaita\"</script>" > docs/index.html; echo "<script>window.location.href += \"/documentation/meta\"</script>" > docs/index.html;
sed -i '' 's/#06f/#ea3358/g' docs/css/documentation-topic~topic~tutorials-overview.d6f5411c.css
sed -i '' 's/,2px/,10px/g' docs/css/index.038e887c.css sed -i '' 's/,2px/,10px/g' docs/css/index.038e887c.css
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v3

View File

@ -1,4 +1,4 @@
// swift-tools-version: 5.10 // swift-tools-version: 5.9
// //
// Package.swift // Package.swift
// Meta // Meta

134
README.md
View File

@ -1,141 +1,57 @@
<p align="center"> <p align="center">
<img width="256" alt="Adwaita Icon" src="Icons/AdwaitaIcon.png"> <h1 align="center">Meta</h1>
<h1 align="center">Adwaita for Swift</h1>
</p> </p>
<p align="center"> <p align="center">
<a href="https://aparokshaui.github.io/adwaita-swift/"> <a href="https://aparokshaui.github.io/meta/">
Documentation Documentation
</a> </a>
· ·
<a href="https://github.com/AparokshaUI/Adwaita"> <a href="https://github.com/AparokshaUI/Meta">
GitHub GitHub
</a> </a>
</p> </p>
_Adwaita_ is a framework for creating user interfaces for GNOME with an API similar to SwiftUI. _Meta_ is a framework allowing the creation of user interface (UI) frameworks in Swift.
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].
## Table of Contents ## Table of Contents
- [Goals][2] - [Overview](#overview)
- [Widgets][3] - [Usage](#usage)
- [Installation][4] - [Thanks](#thanks)
- [Usage][5]
- [Thanks][6]
## 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 ## Usage
### Dependencies
#### Flatpak
It is recommended to develop apps inside of a Flatpak. _Meta_ can be used for creating UI frameworks in Swift which can then be used to create apps.
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.
#### 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. 1. Open your Swift package in GNOME Builder, Xcode, or any other IDE.
2. Open the `Package.swift` file. 2. Open the `Package.swift` file.
3. Into the `Package` initializer, under `dependencies`, paste: 3. Into the `Package` initializer, under `dependencies`, paste:
```swift ```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 ## Thanks
### Dependencies - [DocC](https://github.com/apple/swift-docc) used for the documentation
- [XMLCoder][18] licensed under the [MIT license][19] - [SwiftLint](https://github.com/realm/SwiftLint) for checking whether code style conventions are violated
- [Levenshtein Transformations](https://github.com/david-swift/LevenshteinTransformations) licensed under the [MIT license](https://github.com/david-swift/LevenshteinTransformations/blob/main/LICENSE.md) - The programming language [Swift](https://github.com/swiftlang/swift)
### 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

18
Sources/Meta.docc/Meta.md Normal file
View File

@ -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.

View File

@ -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"
}
}
}

View File

@ -71,7 +71,7 @@ public struct Binding<Value> {
private var handlers: [(Value) -> Void] = [] private var handlers: [(Value) -> Void] = []
/// Get a property of any content of a `Binding` as a `Binding`. /// 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. /// - Returns: The binding.
public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> { public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> {
.init { .init {

View File

@ -54,6 +54,7 @@ extension Array: AnyView where Element == AnyView {
/// - Parameters: /// - Parameters:
/// - modifiers: Modify views before generating the storages. /// - modifiers: Modify views before generating the storages.
/// - type: The type of the widgets. /// - type: The type of the widgets.
/// - Returns: The storages.
public func storages<WidgetType>( public func storages<WidgetType>(
modifiers: [(AnyView) -> AnyView], modifiers: [(AnyView) -> AnyView],
type: WidgetType.Type type: WidgetType.Type

View File

@ -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)
)
}
}
}

View File

@ -7,7 +7,7 @@
extension String { extension String {
/// A label for main content in a view storage. /// An identifier for main content in a view storage.
static var mainContent: Self { "main" } public static var mainContent: Self { "main" }
} }

View File

@ -51,7 +51,7 @@ extension AnyView {
/// Wrap the view into a widget. /// Wrap the view into a widget.
/// - Parameter modifiers: Modify views before being updated. /// - Parameter modifiers: Modify views before being updated.
/// - Returns: The widget. /// - Returns: The widget.
public func widget(modifiers: [(AnyView) -> AnyView]) -> Widget { func widget(modifiers: [(AnyView) -> AnyView]) -> Widget {
let modified = getModified(modifiers: modifiers) let modified = getModified(modifiers: modifiers)
if let peer = modified as? Widget { if let peer = modified as? Widget {
return peer return peer

View File

@ -11,7 +11,7 @@ import Foundation
@resultBuilder @resultBuilder
public enum ViewBuilder { public enum ViewBuilder {
/// A component used in the ``ArrayBuilder``. /// A component used in the ``ViewBuilder``.
public enum Component { public enum Component {
/// A view as a component. /// A view as a component.

View File

@ -5,19 +5,22 @@
// Created by david-swift on 26.05.24. // 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 { public class ViewStorage {
/// The pointer. /// 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? public var pointer: Any?
/// The view's content. /// The view's content for container widgets.
public var content: [String: [ViewStorage]] public var content: [String: [ViewStorage]]
/// The view's state (used in `StateWrapper`). /// The view's state (used in `StateWrapper`).
var state: [String: StateProtocol] = [:] var state: [String: StateProtocol] = [:]
/// Other properties. /// Various properties of a widget.
public var fields: [String: Any] = [:] 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? { public var opaquePointer: OpaquePointer? {
get { get {
pointer as? OpaquePointer pointer as? OpaquePointer
@ -30,7 +33,7 @@ public class ViewStorage {
/// Initialize a view storage. /// Initialize a view storage.
/// - Parameters: /// - Parameters:
/// - pointer: The pointer to the widget, its type depends on the backend. /// - 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( public init(
_ pointer: Any?, _ pointer: Any?,
content: [String: [ViewStorage]] = [:] content: [String: [ViewStorage]] = [:]

View File

@ -5,7 +5,10 @@
// Created by david-swift on 26.05.24. // 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 { public protocol Widget: AnyView {
/// The view storage. /// The view storage.

View File

@ -8,19 +8,16 @@
import Observation import Observation
/// A storage for `@State` properties. /// A storage for `@State` properties.
public struct StateWrapper: ConvenienceWidget { struct StateWrapper: ConvenienceWidget {
/// The content. /// The content.
var content: () -> Body var content: () -> Body
/// The state information (from properties with the `State` wrapper). /// The state information (from properties with the `State` wrapper).
var state: [String: StateProtocol] = [:] 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`. /// Initialize a `StateWrapper`.
/// - Parameter content: The view content. /// - Parameter content: The view content.
public init(@ViewBuilder content: @escaping () -> Body) { init(@ViewBuilder content: @escaping () -> Body) {
self.content = content self.content = content
} }
@ -39,14 +36,13 @@ public struct StateWrapper: ConvenienceWidget {
/// - modifiers: Modify views before being updated. /// - modifiers: Modify views before being updated.
/// - updateProperties: Whether to update properties. /// - updateProperties: Whether to update properties.
/// - type: The type of the widgets. /// - type: The type of the widgets.
public func update<WidgetType>( func update<WidgetType>(
_ storage: ViewStorage, _ storage: ViewStorage,
modifiers: [(AnyView) -> AnyView], modifiers: [(AnyView) -> AnyView],
updateProperties: Bool, updateProperties: Bool,
type: WidgetType.Type type: WidgetType.Type
) { ) {
var updateProperties = updateProperties ? true : (storage.fields[updateID] as? Bool ?? false) var updateProperties = updateProperties
storage.fields[updateID] = false
for property in state { for property in state {
if let oldID = storage.state[property.key]?.id { if let oldID = storage.state[property.key]?.id {
StateManager.changeID(old: oldID, new: property.value.id) StateManager.changeID(old: oldID, new: property.value.id)
@ -68,7 +64,7 @@ public struct StateWrapper: ConvenienceWidget {
/// - modifiers: Modify views before being updated. /// - modifiers: Modify views before being updated.
/// - type: The type of the widgets. /// - type: The type of the widgets.
/// - Returns: The view storage. /// - Returns: The view storage.
public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage { func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
let content = content().storages(modifiers: modifiers, type: type) let content = content().storages(modifiers: modifiers, type: type)
let storage = ViewStorage(nil, content: [.mainContent: content]) let storage = ViewStorage(nil, content: [.mainContent: content])
storage.state = state storage.state = state

View File

@ -5,7 +5,7 @@
// Created by david-swift on 27.05.24. // 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 { public struct Wrapper: ConvenienceWidget {
/// The content. /// The content.

View File

@ -65,12 +65,11 @@ struct DemoApp {
DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType) DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType)
} }
StateManager.addUpdateHandler { _ in StateManager.addUpdateHandler { force in
DemoView().updateStorage(storage, modifiers: [], updateProperties: false, type: backendType) DemoView().updateStorage(storage, modifiers: [], updateProperties: force, type: backendType)
} }
sleep(2) sleep(2)
DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType)
} }
} }