Compare commits
11 Commits
21e28344c1
...
05dd06af0a
| Author | SHA1 | Date | |
|---|---|---|---|
| 05dd06af0a | |||
| 3ad655428b | |||
| a64873733a | |||
| 376d8c724f | |||
| ec86bf1324 | |||
|
|
a6bace169a | ||
| 8c68676994 | |||
| c96e65e622 | |||
| c48abc1c9a | |||
| eadc08d61d | |||
| ef337544bc |
@ -4,21 +4,9 @@ on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: macos-14
|
||||
publish:
|
||||
runs-on: david-macbook
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build Docs
|
||||
@ -31,15 +19,16 @@ jobs:
|
||||
xcrun docc process-archive transform-for-static-hosting \
|
||||
"$PWD/.derivedData/Build/Products/Debug/TermKitBackend.doccarchive" \
|
||||
--output-path "docs" \
|
||||
--hosting-base-path "TermKitBackend"
|
||||
--hosting-base-path "/"
|
||||
- name: Modify Docs
|
||||
run: |
|
||||
echo "<script>window.location.href += \"/documentation/termkitbackend\"</script>" > docs/index.html;
|
||||
sed -i '' 's/,2px/,10px/g' docs/css/index.038e887c.css
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
echo "<script>window.location.href += \"/documentation/termkitbackend\"</script><p>Please enable JavaScript to view the documentation <a href='/documentation/termkitbackend'>here</a>.</p>" > docs/index.html;
|
||||
sed -i '' 's/,2px/,10px/g' docs/css/index.*.css
|
||||
- name: Upload
|
||||
uses: wangyucode/sftp-upload-action@v2.0.2
|
||||
with:
|
||||
path: 'docs'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
host: 'volans.uberspace.de'
|
||||
username: 'akforum'
|
||||
password: ${{ secrets.password }}
|
||||
localDir: 'docs'
|
||||
remoteDir: '/var/www/virtual/akforum/term-kit-backend.aparoksha.dev/'
|
||||
@ -3,17 +3,17 @@ name: SwiftLint
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.gitea/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.gitea/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
workflow_dispatch:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.gitea/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
|
||||
@ -21,10 +21,10 @@ jobs:
|
||||
SwiftLint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@3.2.1
|
||||
with:
|
||||
args: --strict
|
||||
env:
|
||||
WORKING_DIRECTORY: Source
|
||||
WORKING_DIRECTORY: Source
|
||||
@ -94,12 +94,6 @@ disabled_rules:
|
||||
|
||||
# Custom Rules
|
||||
custom_rules:
|
||||
github_issue:
|
||||
name: 'GitHub Issue'
|
||||
regex: '//.(TODO|FIXME):.(?!.*(https://github\.com/david-swift/TermKitBackend/issues/\d))'
|
||||
message: 'The related GitHub issue must be included in a TODO or FIXME.'
|
||||
severity: warning
|
||||
|
||||
fatal_error:
|
||||
name: 'Fatal Error'
|
||||
regex: 'fatalError.*\(.*\)'
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
Thank you very much for taking the time for contributing to this project.
|
||||
|
||||
## Report a Bug
|
||||
Just open a new issue on GitHub and describe the bug. It helps if your description is detailed. Thank you very much for your contribution!
|
||||
|
||||
## Suggest a New Feature
|
||||
Just open a new issue on GitHub and describe the idea. Thank you very much for your contribution!
|
||||
|
||||
## Pull Requests
|
||||
I am happy for every pull request, you do not have to follow these guidelines. However, it might help you to understand the project structure and make it easier for me to merge your pull request. Thank you very much for your contribution!
|
||||
|
||||
### 1. Fork & Clone this Project
|
||||
Start by clicking on the `Fork` button at the top of the page. Then, clone this repository to your computer.
|
||||
|
||||
### 2. Open the Project
|
||||
Open the project folder in GNOME Builder, Xcode or another IDE.
|
||||
|
||||
### 3. Understand the Project Structure
|
||||
- The `README.md` file contains a description of the app or package.
|
||||
- The `LICENSE.md` contains an MIT license.
|
||||
- `CONTRIBUTING.md` is this file.
|
||||
- Directory `Icons` that contains SVG files for the images used in the app and guides.
|
||||
- `Sources` contains the source code of the project as well as a test app.
|
||||
|
||||
### 4. Edit the Code
|
||||
Edit the code. If you add a new type, add documentation in the code.
|
||||
|
||||
### 5. Commit to the Fork
|
||||
Commit and push the fork.
|
||||
|
||||
### 6. Pull Request
|
||||
Open GitHub to submit a pull request. Thank you very much for your contribution!
|
||||
@ -1,4 +1,4 @@
|
||||
// swift-tools-version: 5.9
|
||||
// swift-tools-version: 6.0
|
||||
//
|
||||
// Package.swift
|
||||
// TermKitBackend
|
||||
@ -19,17 +19,21 @@ let package = Package(
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/AparokshaUI/Meta", branch: "main"),
|
||||
.package(url: "https://git.aparoksha.dev/aparoksha/meta", branch: "main"),
|
||||
.package(url: "https://github.com/david-swift/TermKit", branch: "main")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "TermKitBackend",
|
||||
dependencies: ["TermKit", "Meta"]
|
||||
dependencies: [
|
||||
"TermKit",
|
||||
.product(name: "Meta", package: "meta")
|
||||
]
|
||||
),
|
||||
.executableTarget(
|
||||
name: "TestApp",
|
||||
dependencies: ["TermKitBackend"]
|
||||
)
|
||||
]
|
||||
],
|
||||
swiftLanguageModes: [.v5]
|
||||
)
|
||||
|
||||
16
README.md
16
README.md
@ -3,16 +3,16 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://david-swift.github.io/termkitbackend/">
|
||||
<a href="https://term-kit-backend.aparoksha.dev/">
|
||||
Documentation
|
||||
</a>
|
||||
·
|
||||
<a href="https://github.com/david-swift/TermKitBackend">
|
||||
GitHub
|
||||
<a href="https://git.aparoksha.dev/david-swift/term-kit-backend">
|
||||
Code
|
||||
</a>
|
||||
</p>
|
||||
|
||||
_TermKitBackend_ is a declarative framework allowing the creation of user interface for the terminal. It works on Linux, macOS and Windows thanks to the [TermKit project](https://github.com/migueldeicaza/TermKit).
|
||||
_TermKitBackend_ is a declarative framework allowing the creation of user interfaces for the terminal. It works on Linux and macOS thanks to the [TermKit project](https://github.com/migueldeicaza/TermKit).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@ -22,21 +22,21 @@ _TermKitBackend_ is a declarative framework allowing the creation of user interf
|
||||
|
||||
## Overview
|
||||
|
||||
The declarative approach is based on the [Meta package](https://aparokshaui.github.io/meta/) which can be found on [GitHub](https://github.com/AparokshaUI/Meta).
|
||||
The declarative approach is based on the [Meta package](https://meta.aparoksha.dev/), browse the code [here](https://git.aparoksha.dev/aparoksha/meta).
|
||||
It is powered by [a fork of TermKit for Swift](https://github.com/david-swift/TermKit).
|
||||
|
||||
Detailed information about the declarative approach can be found in the [Meta docs](https://aparokshaui.github.io/meta/). Find the available widgets [here](https://david-swift.github.io/termkitbackend).
|
||||
Detailed information about the declarative approach can be found in the [Meta docs](https://meta.aparoksha.dev/). Find the available widgets [here](https://term-kit-backend.aparoksha.dev/).
|
||||
|
||||
## Usage
|
||||
|
||||
Follow the tutorial in the [docs](https://david-swift.github.io/termkitbackend).
|
||||
Follow the tutorial in the [docs](https://term-kit-backend.aparoksha.dev/).
|
||||
|
||||
## Thanks
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [TermKit](https://github.com/david-swift/TermKit) licensed under the [MIT License](https://github.com/david-swift/TermKit/blob/main/LICENSE)
|
||||
- [Meta](https://github.com/AparokshaUI/Meta) licensed under the [MIT License](https://github.com/AparokshaUI/Meta/blob/main/LICENSE.md)
|
||||
- [Meta](https://git.aparoksha.dev/aparoksha/meta) licensed under the [MIT License](https://git.aparoksha.dev/aparoksha/meta/src/branch/main/LICENSE.md)
|
||||
|
||||
### Other Thanks
|
||||
|
||||
|
||||
@ -7,16 +7,14 @@
|
||||
|
||||
import TermKit
|
||||
|
||||
/// A menu is an item of a ``MenuBar``.
|
||||
public struct ButtonCollection: ButtonContext.Widget, Wrapper {
|
||||
/// A collection of buttons.
|
||||
public struct ButtonCollection: ButtonWidget, Wrapper {
|
||||
|
||||
/// The content of the menu.
|
||||
/// The content of the collection.
|
||||
var content: Body
|
||||
|
||||
/// Initialize a menu.
|
||||
/// - Parameters:
|
||||
/// - label: The menu's label, displayed in the menu bar.
|
||||
/// - content: The content of the menu.
|
||||
/// Initialize a collection.
|
||||
/// - Parameter content: The content of the collection.
|
||||
public init(@ViewBuilder content: @escaping () -> Body) {
|
||||
self.content = content()
|
||||
}
|
||||
@ -27,18 +25,18 @@ public struct ButtonCollection: ButtonContext.Widget, Wrapper {
|
||||
/// - type: The type of the views.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
var buttons: [Button] = []
|
||||
for element in content.storages(modifiers: modifiers, type: type) {
|
||||
for element in content.storages(data: data, type: type) {
|
||||
if let button = element.pointer as? Button {
|
||||
buttons.append(button)
|
||||
} else if let collection = element.pointer as? [Button] {
|
||||
buttons += collection
|
||||
}
|
||||
}
|
||||
return .init(buttons, content: [.mainContent: content.storages(modifiers: modifiers, type: type)])
|
||||
return .init(buttons, content: [.mainContent: content.storages(data: data, type: type)])
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
@ -49,7 +47,7 @@ public struct ButtonCollection: ButtonContext.Widget, Wrapper {
|
||||
/// - type: The type of the views.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(AnyView) -> AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
|
||||
@ -11,11 +11,13 @@ import TermKit
|
||||
public enum ButtonContext: ViewRenderData {
|
||||
|
||||
/// The type of the widgets.
|
||||
public typealias WidgetType = Widget
|
||||
public typealias WidgetType = ButtonWidget
|
||||
/// The wrapper type.
|
||||
public typealias WrapperType = ButtonCollection
|
||||
|
||||
/// The type of the widgets.
|
||||
public protocol Widget: Meta.Widget { }
|
||||
/// The either view type.
|
||||
public typealias EitherViewType = NotUpdatableEitherView
|
||||
|
||||
}
|
||||
|
||||
/// The type of the widgets.
|
||||
public protocol ButtonWidget: Meta.Widget { }
|
||||
|
||||
23
Sources/TermKitBackend/Button/NotUpdatableEitherView.swift
Normal file
23
Sources/TermKitBackend/Button/NotUpdatableEitherView.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// NotUpdatableEitherView.swift
|
||||
// TermKitBackend
|
||||
//
|
||||
// Created by david-swift on 25.08.2024.
|
||||
//
|
||||
|
||||
/// An either view for views which do not support updating.
|
||||
public struct NotUpdatableEitherView: SimpleView, Meta.EitherView {
|
||||
|
||||
/// The content.
|
||||
public var view: Body
|
||||
|
||||
/// Initialize the either view.
|
||||
/// - Parameters:
|
||||
/// - condition: The condition.
|
||||
/// - view1: The first view.
|
||||
/// - view2: The other view.
|
||||
public init(_ condition: Bool, view1: () -> Body, else view2: () -> Body) {
|
||||
self.view = condition ? view1() : view2()
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,8 +7,8 @@
|
||||
|
||||
import TermKit
|
||||
|
||||
/// A menu is an item of a ``MenuBar``.
|
||||
public struct Menu: MenuContext.Widget {
|
||||
/// A menu is an item of a `MenuBar`.
|
||||
public struct Menu: MenuWidget {
|
||||
|
||||
/// The menu's label, displayed in the menu bar.
|
||||
var label: String
|
||||
@ -30,10 +30,10 @@ public struct Menu: MenuContext.Widget {
|
||||
/// - type: The type of the views.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let children = content.storages(modifiers: modifiers, type: type)
|
||||
let children = content.storages(data: data, type: type)
|
||||
let menu = MenuBarItem(title: label, children: children.compactMap { $0.pointer as? MenuItem })
|
||||
return .init(menu, content: [.mainContent: children])
|
||||
}
|
||||
@ -46,14 +46,14 @@ public struct Menu: MenuContext.Widget {
|
||||
/// - type: The type of the views.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(AnyView) -> AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storages = storage.content[.mainContent] else {
|
||||
return
|
||||
}
|
||||
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,16 +7,14 @@
|
||||
|
||||
import TermKit
|
||||
|
||||
/// A menu is an item of a ``MenuBar``.
|
||||
public struct MenuCollection: MenuContext.Widget, Wrapper {
|
||||
/// A collection of menus.
|
||||
public struct MenuCollection: MenuWidget, Wrapper {
|
||||
|
||||
/// The content of the menu.
|
||||
/// The content of the collection.
|
||||
var content: Body
|
||||
|
||||
/// Initialize a menu.
|
||||
/// - Parameters:
|
||||
/// - label: The menu's label, displayed in the menu bar.
|
||||
/// - content: The content of the menu.
|
||||
/// - Parameter content: The content of the collection.
|
||||
public init(@ViewBuilder content: @escaping () -> Body) {
|
||||
self.content = content()
|
||||
}
|
||||
@ -27,12 +25,12 @@ public struct MenuCollection: MenuContext.Widget, Wrapper {
|
||||
/// - type: The type of the views.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
var storages: [ViewStorage] = []
|
||||
forEachMenu { menu in
|
||||
storages.append(menu.container(modifiers: modifiers, type: type))
|
||||
storages.append(menu.container(data: data, type: type))
|
||||
}
|
||||
return .init(storages.compactMap { $0.pointer }, content: [.mainContent: storages])
|
||||
}
|
||||
@ -45,7 +43,7 @@ public struct MenuCollection: MenuContext.Widget, Wrapper {
|
||||
/// - type: The type of the views.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(AnyView) -> AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
|
||||
@ -11,11 +11,13 @@ import TermKit
|
||||
public enum MenuContext: ViewRenderData {
|
||||
|
||||
/// The type of the widgets.
|
||||
public typealias WidgetType = Widget
|
||||
public typealias WidgetType = MenuWidget
|
||||
/// The wrapper type.
|
||||
public typealias WrapperType = MenuCollection
|
||||
|
||||
/// The type of the widgets.
|
||||
public protocol Widget: Meta.Widget { }
|
||||
/// The either view type.
|
||||
public typealias EitherViewType = NotUpdatableEitherView
|
||||
|
||||
}
|
||||
|
||||
/// The type of the widgets.
|
||||
public protocol MenuWidget: Meta.Widget { }
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
import TermKit
|
||||
|
||||
/// Extend `AnyView`.
|
||||
extension AnyView {
|
||||
|
||||
/// Set a view's width and height.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// TermKitSceneElement.swift
|
||||
// TermKitApp.swift
|
||||
// TermKitBackend
|
||||
//
|
||||
// Created by david-swift on 01.07.2024.
|
||||
@ -13,19 +13,13 @@ public class TermKitApp: AppStorage {
|
||||
|
||||
/// The scene element type of the TermKit backend.
|
||||
public typealias SceneElementType = TermKitSceneElement
|
||||
/// The widget type of the TermKit backend.
|
||||
public typealias WidgetType = TermKitWidget
|
||||
/// The wrapper type of the TermKit backend.
|
||||
public typealias WrapperType = VStack
|
||||
|
||||
/// The app storage.
|
||||
public var storage: StandardAppStorage = .init()
|
||||
|
||||
/// Initialize the app storage.
|
||||
/// - Parameters:
|
||||
/// - id: The identifier.
|
||||
/// - app: The application.
|
||||
public required init(id: String) { }
|
||||
/// - Parameter id: The identifier.
|
||||
public init() { }
|
||||
|
||||
/// Execute the app.
|
||||
/// - Parameter setup: Set the scene elements up.
|
||||
|
||||
@ -12,5 +12,7 @@ public enum TermKitMainView: ViewRenderData {
|
||||
public typealias WidgetType = TermKitWidget
|
||||
/// The wrapper type.
|
||||
public typealias WrapperType = VStack
|
||||
/// The either view type.
|
||||
public typealias EitherViewType = EitherView
|
||||
|
||||
}
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
import TermKit
|
||||
|
||||
/// The menu bar scene element adds a menu bar to the top of the app.
|
||||
public struct MenuBar: TermKitSceneElement {
|
||||
struct MenuBar: TermKitSceneElement {
|
||||
|
||||
/// The identifier of the menu bar.
|
||||
public var id: String
|
||||
var id: String
|
||||
/// The menu bar's content.
|
||||
var content: Body
|
||||
|
||||
@ -19,34 +19,37 @@ public struct MenuBar: TermKitSceneElement {
|
||||
/// - Parameters:
|
||||
/// - id: The identifier of the menu bar.
|
||||
/// - content: The menu bar's content.
|
||||
public init(id: String = "main-menu", @ViewBuilder content: () -> Body) {
|
||||
init(id: String = "main-menu", @ViewBuilder content: () -> Body) {
|
||||
self.id = id
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
/// Set up the initial scene storages.
|
||||
/// - Parameter app: The app storage.
|
||||
public func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
|
||||
func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
|
||||
app.storage.sceneStorage.append(container(app: app))
|
||||
}
|
||||
|
||||
/// The scene storage.
|
||||
/// - Parameter app: The app storage.
|
||||
public func container<Storage>(app: Storage) -> SceneStorage where Storage: AppStorage {
|
||||
let items = MenuCollection { content }.container(modifiers: [], type: MenuContext.self)
|
||||
func container<Storage>(app: Storage) -> SceneStorage where Storage: AppStorage {
|
||||
let storage = SceneStorage(id: id, pointer: nil) { }
|
||||
let items = MenuCollection { content }.container(
|
||||
data: .init(sceneStorage: storage, appStorage: app),
|
||||
type: MenuContext.self
|
||||
)
|
||||
let menubar = TermKit.MenuBar(
|
||||
menus: items.pointer as? [TermKit.MenuBarItem] ?? []
|
||||
)
|
||||
Task {
|
||||
try await Task.sleep(nanoseconds: 1)
|
||||
for element in Application.top.subviews {
|
||||
element.y = .bottom(of: menubar)
|
||||
}
|
||||
Application.top.addSubview(menubar)
|
||||
for element in Application.top.subviews {
|
||||
element.y = .bottom(of: menubar)
|
||||
}
|
||||
return .init(id: id, pointer: menubar) {
|
||||
Application.top.addSubview(menubar)
|
||||
storage.pointer = menubar
|
||||
storage.show = {
|
||||
menubar.ensureFocus()
|
||||
}
|
||||
return storage
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
@ -54,7 +57,7 @@ public struct MenuBar: TermKitSceneElement {
|
||||
/// - storage: The storage to update.
|
||||
/// - app: The app storage.
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
public func update<Storage>(
|
||||
func update<Storage>(
|
||||
_ storage: SceneStorage,
|
||||
app: Storage,
|
||||
updateProperties: Bool
|
||||
|
||||
@ -40,13 +40,18 @@ public struct Window: TermKitSceneElement {
|
||||
let win = TermKit.Window(title)
|
||||
win.fill()
|
||||
Application.top.addSubview(win)
|
||||
let viewStorage = content.storage(modifiers: [], type: TermKitMainView.self)
|
||||
let storage = SceneStorage(id: id, pointer: win) {
|
||||
win.ensureFocus()
|
||||
}
|
||||
let viewStorage = content.storage(
|
||||
data: .init(sceneStorage: storage, appStorage: app),
|
||||
type: TermKitMainView.self
|
||||
)
|
||||
if let pointer = viewStorage.pointer as? TermKit.View {
|
||||
win.addSubview(pointer)
|
||||
}
|
||||
return .init(id: id, pointer: win, content: [.mainContent: [viewStorage]]) {
|
||||
win.ensureFocus()
|
||||
}
|
||||
storage.content = [.mainContent: [viewStorage]]
|
||||
return storage
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
@ -63,8 +68,21 @@ public struct Window: TermKitSceneElement {
|
||||
return
|
||||
}
|
||||
content
|
||||
.updateStorage(viewStorage, modifiers: [], updateProperties: updateProperties, type: TermKitMainView.self)
|
||||
.updateStorage(
|
||||
viewStorage,
|
||||
data: .init(sceneStorage: storage, appStorage: app),
|
||||
updateProperties: updateProperties,
|
||||
type: TermKitMainView.self
|
||||
)
|
||||
Application.refresh()
|
||||
}
|
||||
|
||||
/// Add a menubar to the app.
|
||||
/// - Parameter content: The mnu bar's content.
|
||||
@SceneBuilder
|
||||
public func menuBar(@ViewBuilder content: @escaping () -> Body) -> Scene {
|
||||
self
|
||||
MenuBar(content: content)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
39
Sources/TermKitBackend/TermKitBackend.docc/GettingStarted.md
Normal file
39
Sources/TermKitBackend/TermKitBackend.docc/GettingStarted.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Getting Started
|
||||
|
||||
Learn how to use the TermKit backend.
|
||||
|
||||
Knowledge about the Meta project is required.
|
||||
Find more information [here](https://meta.aparoksha.dev).
|
||||
|
||||
## The App
|
||||
|
||||
Define your app in the following way:
|
||||
|
||||
```swift
|
||||
import TermKitBackend
|
||||
|
||||
@main
|
||||
struct TestApp: App {
|
||||
|
||||
let app = TermKitApp()
|
||||
|
||||
var scene: Scene {
|
||||
Window {
|
||||
// Views (see list in documentation)
|
||||
}
|
||||
.menuBar {
|
||||
Menu("_File") { // Menus
|
||||
Button("_New") { // Buttons
|
||||
print("Hi")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Widgets
|
||||
|
||||
All the available widgets can be found in the documentation.
|
||||
Take a look at the [sample app](https://git.aparoksha.dev/david-swift/term-kit-backend/src/branch/main/Sources/TestApp/TestApp.swift) for more help.
|
||||
@ -1,8 +1,8 @@
|
||||
# ``TermKitBackend``
|
||||
|
||||
_TermKitBackend_ is a declarative framework allowing the creation of user interface for the terminal. It works on Linux, macOS and Windows thanks to the [TermKit project](https://github.com/migueldeicaza/TermKit).
|
||||
_TermKitBackend_ is a declarative framework allowing the creation of user interface for the terminal. It works on Linux and macOS thanks to the [TermKit project](https://github.com/migueldeicaza/TermKit).
|
||||
|
||||
## Overview
|
||||
|
||||
The declarative approach is based on the [Meta package](https://aparokshaui.github.io/meta/) which can be found on [GitHub](https://github.com/AparokshaUI/Meta).
|
||||
The declarative approach is based on the [Meta package](https://meta.aparoksha.dev/), browse the code [here](https://git.aparoksha.dev/aparoksha/meta).
|
||||
It is powered by [a fork of TermKit for Swift](https://github.com/david-swift/TermKit).
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
"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",
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
import TermKit
|
||||
|
||||
/// A simple button widget.
|
||||
public struct Button: TermKitWidget, ButtonContext.Widget, MenuContext.Widget {
|
||||
public struct Button: TermKitWidget, ButtonWidget, MenuWidget {
|
||||
|
||||
/// The button's label.
|
||||
var label: String
|
||||
@ -33,7 +33,7 @@ public struct Button: TermKitWidget, ButtonContext.Widget, MenuContext.Widget {
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
if type == MenuContext.self {
|
||||
@ -48,7 +48,7 @@ public struct Button: TermKitWidget, ButtonContext.Widget, MenuContext.Widget {
|
||||
return ViewStorage(self)
|
||||
}
|
||||
let button = TermKit.Button(label, clicked: action)
|
||||
return .init(button)
|
||||
return .init(button, state: self)
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
@ -59,19 +59,20 @@ public struct Button: TermKitWidget, ButtonContext.Widget, MenuContext.Widget {
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
if type == MenuContext.self {
|
||||
storage.fields[actionID] = action
|
||||
}
|
||||
guard let storage = storage.pointer as? TermKit.Button else {
|
||||
guard let pointer = storage.pointer as? TermKit.Button else {
|
||||
return
|
||||
}
|
||||
storage.clicked = { _ in action() }
|
||||
if updateProperties {
|
||||
storage.text = label
|
||||
pointer.clicked = { _ in action() }
|
||||
if updateProperties, (storage.previousState as? Self)?.label != label {
|
||||
pointer.text = label
|
||||
storage.previousState = self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,9 +11,15 @@ import TermKit
|
||||
public struct Checkbox: TermKitWidget {
|
||||
|
||||
/// The label of the checkbox.
|
||||
var label: String
|
||||
@Property(set: { $0.text = $1 }, pointer: TermKit.Checkbox.self)
|
||||
var label = ""
|
||||
/// Whether the checkbox is on.
|
||||
var isOn: Binding<Bool>
|
||||
@BindingProperty(
|
||||
observe: { box, value in box.toggled = { value.wrappedValue = $0.checked } },
|
||||
set: { $0.checked = $1 },
|
||||
pointer: TermKit.Checkbox.self
|
||||
)
|
||||
var isOn: Binding<Bool> = .constant(false)
|
||||
|
||||
/// Initialize the checkbox.
|
||||
/// - Parameters:
|
||||
@ -24,42 +30,10 @@ public struct Checkbox: TermKitWidget {
|
||||
self.isOn = isOn
|
||||
}
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let button = TermKit.Checkbox(label, checked: isOn.wrappedValue)
|
||||
button.toggled = { _ in
|
||||
isOn.wrappedValue = button.checked
|
||||
}
|
||||
return .init(button)
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - modifiers: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storage = storage.pointer as? TermKit.Checkbox, updateProperties else {
|
||||
return
|
||||
}
|
||||
storage.text = label
|
||||
storage.checked = isOn.wrappedValue
|
||||
storage.toggled = { _ in
|
||||
isOn.wrappedValue = storage.checked
|
||||
}
|
||||
/// Get the widget.
|
||||
/// - Returns: The widget.
|
||||
public func initializeWidget() -> Any {
|
||||
TermKit.Checkbox(label)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -32,11 +32,11 @@ struct Box: TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storage = ViewStorage(nil)
|
||||
let contentStorage = content.storage(modifiers: modifiers, type: type)
|
||||
let contentStorage = content.storage(data: data, type: type)
|
||||
storage.pointer = contentStorage.pointer
|
||||
return .init(contentStorage.pointer, content: [.mainContent: [contentStorage]])
|
||||
}
|
||||
@ -49,16 +49,16 @@ struct Box: TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storage = storage.content[.mainContent]?.first else {
|
||||
return
|
||||
}
|
||||
content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||
let buttons = ButtonCollection { self.buttons }
|
||||
.storage(modifiers: modifiers, type: ButtonContext.self).pointer as? [Button] ?? []
|
||||
.storage(data: data, type: ButtonContext.self).pointer as? [Button] ?? []
|
||||
storage.fields[boxButtonsID] = buttons
|
||||
if signal.update {
|
||||
if buttons.isEmpty {
|
||||
@ -77,6 +77,7 @@ struct Box: TermKitWidget {
|
||||
|
||||
}
|
||||
|
||||
/// Extend `AnyView`,
|
||||
extension AnyView {
|
||||
|
||||
/// Add a query box.
|
||||
|
||||
78
Sources/TermKitBackend/View/EitherView.swift
Normal file
78
Sources/TermKitBackend/View/EitherView.swift
Normal file
@ -0,0 +1,78 @@
|
||||
//
|
||||
// EitherView.swift
|
||||
// TermKitBackend
|
||||
//
|
||||
// Created by david-swift on 25.08.2024.
|
||||
//
|
||||
|
||||
import TermKit
|
||||
|
||||
/// A container which draws a frame around its contents.
|
||||
public struct EitherView: TermKitWidget, Meta.EitherView {
|
||||
|
||||
/// The condition.
|
||||
var condition: Bool
|
||||
/// The first view.
|
||||
var view1: Body
|
||||
/// The second view.
|
||||
var view2: Body
|
||||
|
||||
/// Initialize an either view.
|
||||
/// - Parameters:
|
||||
/// - condition: The condition.
|
||||
/// - view1: The first view.
|
||||
/// - view2: The second view.
|
||||
public init(_ condition: Bool, @ViewBuilder view1: () -> Body, @ViewBuilder else view2: () -> Body) {
|
||||
self.condition = condition
|
||||
self.view1 = view1()
|
||||
self.view2 = view2()
|
||||
}
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let view = TermKit.View()
|
||||
let storage = ViewStorage(view)
|
||||
update(storage, data: data, updateProperties: true, type: type)
|
||||
return storage
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - modifiers: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let parent = storage.pointer as? TermKit.View else {
|
||||
return
|
||||
}
|
||||
let view: TermKit.View?
|
||||
let body = condition ? view1 : view2
|
||||
if let content = storage.content[condition.description]?.first {
|
||||
body.updateStorage(content, data: data, updateProperties: updateProperties, type: type)
|
||||
view = content.pointer as? TermKit.View
|
||||
} else {
|
||||
let content = body.storage(data: data, type: type)
|
||||
storage.content[condition.description] = [content]
|
||||
view = content.pointer as? TermKit.View
|
||||
}
|
||||
if let view, (storage.previousState as? Self)?.condition != condition {
|
||||
parent.removeAllSubviews()
|
||||
parent.addSubview(view)
|
||||
}
|
||||
storage.previousState = self
|
||||
}
|
||||
|
||||
}
|
||||
@ -11,8 +11,10 @@ import TermKit
|
||||
public struct Frame: TermKitWidget {
|
||||
|
||||
/// The frame's label.
|
||||
@Property(set: { $0.title = $1 }, pointer: TermKit.Frame.self)
|
||||
var label: String?
|
||||
/// The content.
|
||||
@ViewProperty(set: { $0.addSubview($1) }, pointer: TermKit.Frame.self, subview: TermKit.View.self)
|
||||
var view: Body
|
||||
|
||||
/// Initialize a frame.
|
||||
@ -20,46 +22,14 @@ public struct Frame: TermKitWidget {
|
||||
/// - label: The frame's label.
|
||||
/// - content: The content.
|
||||
public init(_ label: String? = nil, @ViewBuilder content: @escaping () -> Body) {
|
||||
self.label = label
|
||||
self.view = content()
|
||||
self.label = label
|
||||
}
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let frame = TermKit.Frame(label)
|
||||
let subview = view.storage(modifiers: modifiers, type: type)
|
||||
if let pointer = subview.pointer as? TermKit.View {
|
||||
frame.addSubview(pointer)
|
||||
}
|
||||
return .init(frame, content: [.mainContent: [subview]])
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - modifiers: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
if let storage = storage.content[.mainContent]?.first {
|
||||
view.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
guard let storage = storage.pointer as? TermKit.Frame, updateProperties else {
|
||||
return
|
||||
}
|
||||
storage.title = label
|
||||
/// Get the widget.
|
||||
/// - Returns: The widget.
|
||||
public func initializeWidget() -> Any {
|
||||
TermKit.Frame()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -24,10 +24,10 @@ public struct HStack: Wrapper, TermKitWidget {
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storages = content.storages(modifiers: modifiers, type: type)
|
||||
let storages = content.storages(data: data, type: type)
|
||||
if storages.count == 1 {
|
||||
return .init(storages[0].pointer, content: [.mainContent: storages])
|
||||
}
|
||||
@ -51,14 +51,14 @@ public struct HStack: Wrapper, TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storages = storage.content[.mainContent] else {
|
||||
return
|
||||
}
|
||||
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,7 +11,8 @@ import TermKit
|
||||
public struct Label: TermKitWidget {
|
||||
|
||||
/// The label.
|
||||
var label: String
|
||||
@Property(set: { $0.text = $1 }, pointer: TermKit.Label.self)
|
||||
var label = ""
|
||||
|
||||
/// Initialize a label.
|
||||
/// - Parameter label: The label.
|
||||
@ -19,35 +20,10 @@ public struct Label: TermKitWidget {
|
||||
self.label = label
|
||||
}
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let button = TermKit.Label(label)
|
||||
return .init(button)
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - modifiers: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storage = storage.pointer as? TermKit.Label, updateProperties else {
|
||||
return
|
||||
}
|
||||
storage.text = label
|
||||
/// Get the widget.
|
||||
/// - Returns: The widget.
|
||||
public func initializeWidget() -> Any {
|
||||
TermKit.Label(label)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ public struct ListView<Element>: TermKitWidget where Element: CustomStringConver
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let list = TermKit.ListView(items: items.map { $0.description })
|
||||
@ -46,7 +46,7 @@ public struct ListView<Element>: TermKitWidget where Element: CustomStringConver
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
|
||||
@ -11,49 +11,21 @@ import TermKit
|
||||
public struct ProgressBar: TermKitWidget {
|
||||
|
||||
/// The current value.
|
||||
var value: Double
|
||||
/// The maximum value.
|
||||
var max: Double
|
||||
@Property(set: { $0.fraction = $1 }, pointer: TermKit.ProgressBar.self)
|
||||
var fraction: Float = 0
|
||||
|
||||
/// Initialize a progress bar.
|
||||
/// - Parameters:
|
||||
/// - value: The current value.
|
||||
/// - max: The maximum value.
|
||||
public init(value: Double, max: Double = 1) {
|
||||
self.value = value
|
||||
self.max = max
|
||||
self.fraction = .init(value / max)
|
||||
}
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let bar = TermKit.ProgressBar()
|
||||
bar.fraction = .init(value / max)
|
||||
return .init(bar)
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - modifiers: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storage = storage.pointer as? TermKit.ProgressBar, updateProperties else {
|
||||
return
|
||||
}
|
||||
storage.fraction = .init(value / max)
|
||||
/// Get the widget.
|
||||
/// - Returns: The widget.
|
||||
public func initializeWidget() -> Any {
|
||||
TermKit.ProgressBar()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,10 +25,10 @@ public struct ScrollView: TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storages = content.storages(modifiers: modifiers, type: type)
|
||||
let storages = content.storages(data: data, type: type)
|
||||
if storages.count == 1 {
|
||||
return .init(storages[0].pointer, content: [.mainContent: storages])
|
||||
}
|
||||
@ -52,14 +52,14 @@ public struct ScrollView: TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storages = storage.content[.mainContent] else {
|
||||
return
|
||||
}
|
||||
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,8 +11,18 @@ import TermKit
|
||||
public struct TextField: TermKitWidget {
|
||||
|
||||
/// The text.
|
||||
var text: Binding<String>
|
||||
@BindingProperty(
|
||||
observe: { pointer, value in
|
||||
pointer.textChanged = { _, _ in
|
||||
value.wrappedValue = pointer.text
|
||||
}
|
||||
},
|
||||
set: { $0.text = $1 },
|
||||
pointer: TermKit.TextField.self
|
||||
)
|
||||
var text: Binding<String> = .constant("")
|
||||
/// Whether the text field is secret.
|
||||
@Property(set: { $0.secret = $1 }, pointer: TermKit.TextField.self)
|
||||
var secret = false
|
||||
|
||||
/// The identifier for the closure.
|
||||
@ -24,55 +34,10 @@ public struct TextField: TermKitWidget {
|
||||
self.text = text
|
||||
}
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
/// - Returns: The view storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let field = TermKit.TextField(text.wrappedValue)
|
||||
let storage = ViewStorage(field)
|
||||
field.secret = secret
|
||||
field.textChanged = { _, _ in
|
||||
(storage.fields[closureID] as? () -> Void)?()
|
||||
}
|
||||
storage.fields[closureID] = {
|
||||
if field.text != text.wrappedValue {
|
||||
text.wrappedValue = field.text
|
||||
}
|
||||
}
|
||||
return storage
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - modifiers: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let field = storage.pointer as? TermKit.TextField else {
|
||||
return
|
||||
}
|
||||
storage.fields[closureID] = {
|
||||
if field.text != text.wrappedValue {
|
||||
text.wrappedValue = field.text
|
||||
}
|
||||
}
|
||||
if updateProperties {
|
||||
field.secret = secret
|
||||
if field.text != text.wrappedValue {
|
||||
field.text = text.wrappedValue
|
||||
}
|
||||
}
|
||||
/// Get the widget.
|
||||
/// - Returns: The widget.
|
||||
public func initializeWidget() -> Any {
|
||||
TermKit.TextField(text.wrappedValue)
|
||||
}
|
||||
|
||||
/// Set whether the text field is secret.
|
||||
|
||||
@ -24,10 +24,10 @@ public struct VStack: Wrapper, TermKitWidget {
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storages = content.storages(modifiers: modifiers, type: type)
|
||||
let storages = content.storages(data: data, type: type)
|
||||
if storages.count == 1 {
|
||||
return .init(storages[0].pointer, content: [.mainContent: storages])
|
||||
}
|
||||
@ -51,14 +51,14 @@ public struct VStack: Wrapper, TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storages = storage.content[.mainContent] else {
|
||||
return
|
||||
}
|
||||
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -24,10 +24,10 @@ public struct ZStack: Wrapper, TermKitWidget {
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - type: The type of the app storage.
|
||||
public func container<Data>(
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storages = content.reversed().storages(modifiers: modifiers, type: type)
|
||||
let storages = content.reversed().storages(data: data, type: type)
|
||||
if storages.count == 1 {
|
||||
return .init(storages[0].pointer, content: [.mainContent: storages])
|
||||
}
|
||||
@ -48,14 +48,14 @@ public struct ZStack: Wrapper, TermKitWidget {
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
modifiers: [(any AnyView) -> any AnyView],
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
guard let storages = storage.content[.mainContent] else {
|
||||
return
|
||||
}
|
||||
content.reversed().update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||
content.reversed().update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,19 +5,17 @@
|
||||
// Created by david-swift on 01.07.2024.
|
||||
//
|
||||
|
||||
import Meta
|
||||
import TermKitBackend
|
||||
|
||||
@main
|
||||
struct TestApp: App {
|
||||
|
||||
@State private var about: Signal = .init()
|
||||
@State private var state = false
|
||||
|
||||
let id = "io.github.AparokshaUI.TestApp"
|
||||
var app: TermKitApp!
|
||||
let app = TermKitApp()
|
||||
|
||||
var scene: Scene {
|
||||
menuBar
|
||||
Window {
|
||||
VStack {
|
||||
Demos()
|
||||
@ -29,10 +27,7 @@ struct TestApp: App {
|
||||
.vcenter()
|
||||
.infoBox("About TermKitBackend", message: aboutInfo, signal: about)
|
||||
}
|
||||
}
|
||||
|
||||
var menuBar: MenuBar {
|
||||
.init {
|
||||
.menuBar {
|
||||
fileMenu
|
||||
Menu("_Actions") {
|
||||
Button("_Hello, world!") { }
|
||||
@ -86,31 +81,45 @@ struct Demos: View {
|
||||
|
||||
}
|
||||
|
||||
struct ControlsModel: Model {
|
||||
|
||||
var isOn = false
|
||||
var fraction = 0
|
||||
var text = "Controls"
|
||||
|
||||
var model: ModelData?
|
||||
|
||||
func increaseFraction() {
|
||||
Task { @MainActor in
|
||||
setModel { $0.fraction += 1 }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Controls: View {
|
||||
|
||||
@State private var isOn = false
|
||||
@State private var fraction = 0
|
||||
@State private var text = "Controls"
|
||||
@State private var model = ControlsModel()
|
||||
|
||||
var view: Body {
|
||||
Frame(text) {
|
||||
Frame(model.text) {
|
||||
HStack {
|
||||
Button("Button (progress)") {
|
||||
if fraction == 10 {
|
||||
fraction = 0
|
||||
if model.fraction == 10 {
|
||||
model.fraction = 0
|
||||
} else {
|
||||
fraction += 1
|
||||
model.increaseFraction()
|
||||
}
|
||||
}
|
||||
Button("Button (text)") {
|
||||
text = "Hello"
|
||||
model.text = "Hello"
|
||||
}
|
||||
}
|
||||
.frame(height: 1)
|
||||
Checkbox(isOn ? "On" : "Off", isOn: $isOn)
|
||||
TextField(text: $text)
|
||||
.secret(isOn)
|
||||
ProgressBar(value: .init(fraction), max: 10)
|
||||
Checkbox(model.isOn ? "On" : "Off", isOn: $model.isOn)
|
||||
TextField(text: $model.text)
|
||||
.secret(model.isOn)
|
||||
ProgressBar(value: .init(model.fraction), max: 10)
|
||||
}
|
||||
.frame(width: 40, height: 7)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user