Initial commit
Some checks failed
SwiftLint / SwiftLint (push) Failing after 5s

This commit is contained in:
david-swift 2024-10-13 21:11:31 +02:00
commit 7afe735433
35 changed files with 2113 additions and 0 deletions

View File

@ -0,0 +1,40 @@
name: Bug report
description: Something is not working as expected.
title: Description of the bug
labels: bug
body:
- type: textarea
attributes:
label: Describe the bug
description: >-
A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: >-
Steps to reproduce the behavior.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: >-
A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: >-
Add any other context about the problem here.

View File

@ -0,0 +1,29 @@
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 winui-swift?
validations:
required: false
- type: textarea
attributes:
label: Additional context
placeholder: >-
Add any other context about the component request here.
validations:
required: false

View File

@ -0,0 +1,36 @@
name: Feature request
description: Suggest an idea for this project
title: Description of the feature request
labels: enhancement
body:
- type: input
attributes:
label: Is your feature request related to a problem? Please describe.
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: false
- type: textarea
attributes:
label: Describe the solution you'd like
placeholder: >-
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
placeholder: >-
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Additional context
placeholder: >-
Add any other context or screenshots about the feature request here.
validations:
required: true

View File

@ -0,0 +1,12 @@
## Steps
- [ ] 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!
## Purpose
_Describe the problem or feature._
_If there is a related issue, add the link._
## Approach
_Describe how this pull request solves the problem or adds the feature._

View File

@ -0,0 +1,30 @@
name: SwiftLint
on:
push:
paths:
- '.gitea/workflows/swiftlint.yml'
- '.swiftlint.yml'
- '**/*.swift'
pull_request:
paths:
- '.gitea/workflows/swiftlint.yml'
- '.swiftlint.yml'
- '**/*.swift'
workflow_dispatch:
paths:
- '.gitea/workflows/swiftlint.yml'
- '.swiftlint.yml'
- '**/*.swift'
jobs:
SwiftLint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SwiftLint
uses: norio-nomura/action-swiftlint@3.2.1
with:
args: --strict
env:
WORKING_DIRECTORY: Source

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
/Package.resolved
.Ulysses-Group.plist
/.docc-build
/.vscode

157
.swiftlint.yml Normal file
View File

@ -0,0 +1,157 @@
# Opt-In Rules
opt_in_rules:
- anonymous_argument_in_multiline_closure
- array_init
- attributes
- closure_body_length
- closure_end_indentation
- closure_spacing
- collection_alignment
- comma_inheritance
- conditional_returns_on_newline
- contains_over_filter_count
- contains_over_filter_is_empty
- contains_over_first_not_nil
- contains_over_range_nil_comparison
- convenience_type
- discouraged_none_name
- discouraged_object_literal
- empty_collection_literal
- empty_count
- empty_string
- enum_case_associated_values_count
- explicit_init
- fallthrough
- file_header
- file_name
- file_name_no_space
- first_where
- flatmap_over_map_reduce
- force_unwrapping
- function_default_parameter_at_end
- identical_operands
- implicit_return
- implicitly_unwrapped_optional
- joined_default_parameter
- last_where
- legacy_multiple
- let_var_whitespace
- literal_expression_end_indentation
- local_doc_comment
- lower_acl_than_parent
- missing_docs
- modifier_order
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- no_extension_access_modifier
- no_grouping_extension
- number_separator
- operator_usage_whitespace
- optional_enum_case_matching
- prefer_self_in_static_references
- prefer_self_type_over_type_of_self
- prefer_zero_over_explicit_init
- prohibited_interface_builder
- redundant_nil_coalescing
- redundant_type_annotation
- return_value_from_void_function
- shorthand_optional_binding
- sorted_first_last
- sorted_imports
- static_operator
- strict_fileprivate
- switch_case_on_newline
- toggle_bool
- trailing_closure
- type_contents_order
- unneeded_parentheses_in_closure_argument
- yoda_condition
# Disabled Rules
disabled_rules:
- block_based_kvo
- class_delegate_protocol
- dynamic_inline
- is_disjoint
- no_fallthrough_only
- notification_center_detachment
- ns_number_init_as_function_reference
- nsobject_prefer_isequal
- private_over_fileprivate
- redundant_objc_attribute
- self_in_property_initialization
- todo
- unavailable_condition
- valid_ibinspectable
- xctfail_message
# Custom Rules
custom_rules:
fatal_error:
name: 'Fatal Error'
regex: 'fatalError.*\(.*\)'
message: 'Fatal error should not be used.'
severity: error
enum_case_parameter:
name: 'Enum Case Parameter'
regex: 'case [a-zA-Z0-9]*\([a-zA-Z0-9\.<>?,\n\t =]+\)'
message: 'The associated values of an enum case should have parameters.'
severity: warning
tab:
name: 'Whitespaces Instead of Tab'
regex: '\t'
message: 'Spaces should be used instead of tabs.'
severity: warning
# Thanks to the creator of the SwiftLint rule
# "empty_first_line"
# https://github.com/coteditor/CotEditor/blob/main/.swiftlint.yml
# in the GitHub repository
# "CotEditor"
# https://github.com/coteditor/CotEditor
empty_first_line:
name: 'Empty First Line'
regex: '(^[ a-zA-Z ]*(?:protocol|extension|class|struct) (?!(?:var|let))[ a-zA-Z:]*\{\n *\S+)'
message: 'There should be an empty line after a declaration'
severity: error
# Analyzer Rules
analyzer_rules:
- unused_declaration
- unused_import
# Options
file_header:
required_pattern: '(// swift-tools-version: .+)?//\n// .*.swift\n// WinUI\n//\n// Created by .* on .*\.(\n// Edited by (.*,)+\.)*\n(\n// Thanks to .* for the .*:\n// ".*"\n// https://.* \(\d\d.\d\d.\d\d\))*//\n'
missing_docs:
warning: [internal, private]
error: [open, public]
excludes_extensions: false
excludes_inherited_types: false
type_contents_order:
order:
- case
- type_alias
- associated_type
- type_property
- instance_property
- ib_inspectable
- ib_outlet
- subscript
- initializer
- deinitializer
- subtype
- type_method
- view_life_cycle_method
- ib_action
- other_method
excluded:
- .build/

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 david-swift
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
Package.swift Normal file
View File

@ -0,0 +1,47 @@
// swift-tools-version: 6.0
//
// Package.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import PackageDescription
/// The WinUI package.
let package = Package(
name: "WinUI",
products: [
.library(
name: "WinUI",
targets: ["winui-swift"]
)
],
dependencies: [
.package(url: "https://git.aparoksha.dev/aparoksha/meta", branch: "main"),
.package(
url: "https://git.aparoksha.dev/aparoksha/levenshtein-transformations",
branch: "main"
),
.package(url: "https://github.com/thebrowsercompany/swift-winui", branch: "main"),
.package(url: "https://github.com/thebrowsercompany/swift-windowsappsdk", branch: "main"),
.package(url: "https://github.com/thebrowsercompany/swift-windowsfoundation", branch: "main")
],
targets: [
.target(
name: "winui-swift",
dependencies: [
.product(name: "WinUI", package: "swift-winui"),
.product(name: "WinAppSDK", package: "swift-windowsappsdk"),
.product(name: "WindowsFoundation", package: "swift-windowsfoundation"),
.product(name: "LevenshteinTransformations", package: "levenshtein-transformations"),
.product(name: "Meta", package: "meta")
]
),
.executableTarget(
name: "Demo",
dependencies: ["winui-swift"]
)
],
swiftLanguageModes: [.v5]
)

79
README.md Normal file
View File

@ -0,0 +1,79 @@
<p align="center">
<h1 align="center">WinUI for Swift</h1>
</p>
<p align="center">
<a href="https://winui-swift.aparoksha.dev/">
Documentation
</a>
·
<a href="https://git.aparoksha.dev/aparoksha/winui-swift">
Code
</a>
</p>
_WinUI for Swift_ is a framework for creating user interfaces for Windows with an API similar to SwiftUI.
## Table of Contents
- [Installation][4]
- [Usage][5]
- [Thanks][6]
## Installation
### Dependencies
Install Swift following the [official instructions for Windows](https://www.swift.org/install/windows/).
Then, download and run the installer for the Windows App SDK from the following link:
https://www.swift.org/install/windows/
### Swift Package
1. Open your Swift package in VS Code or any other editor.
2. Open the `Package.swift` file.
3. Into the `Package` initializer, under `dependencies`, paste:
```swift
.package(url: "https://git.apaorksha.dev/aparoksha/winui-swift", from: "0.1.0")
```
## Usage
Find more information in the [documentation](https://winui-swift.aparoksha.dev/).
## Thanks
### Dependencies
- [Meta](https://git.aparoksha.dev/aparoksha/meta) licensed under the [MIT License](https://git.aparoksha.dev/aparoksha/meta/src/branch/main/LICENSE.md)
- [Levenshtein Transformations](https://git.aparoksha.dev/aparoksha/levenshtein-transformations) licensed under the [MIT License](https://git.aparoksha.dev/aparoksha/levenshtein-transformations/src/branch/main/LICENSE.md)
- [swift-winui](https://github.com/thebrowsercompany/swift-winui) licensed under the [BSD 3-Clause License](https://github.com/thebrowsercompany/swift-winui/blob/main/LICENSE)
- [swift-windowsappsdk](https://github.com/thebrowsercompany/swift-windowsappsdk) licensed under the [BSD 3-Clause License](https://github.com/thebrowsercompany/swift-windowsappsdk/blob/main/LICENSE)
- [swift-windowsfoundation](https://github.com/thebrowsercompany/swift-windowsfoundation) licensed under the [BSD 3-Clause License](https://github.com/thebrowsercompany/swift-windowsfoundation/blob/main/LICENSE)
### Other Thanks
- [DocC](https://github.com/swiftlang/swift-docc) used for generating the documentation
- [SwiftLint][21] for checking whether code style conventions are violated
- The programming language [Swift][22]
[1]: Tests/
[2]: #goals
[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

105
Sources/Demo/Demo.swift Normal file
View File

@ -0,0 +1,105 @@
//
// Demo.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
// swiftlint:disable missing_docs implicitly_unwrapped_optional no_magic_numbers
import winui_swift
import Foundation
Demo.main()
struct Demo: App {
let app = WinUIApp()
var scene: Scene {
Window("Demo", id: "main") { _ in
ContentView(app: app)
}
.extendContentIntoTitleBar()
}
}
struct ContentView: View {
@State private var selectedItem: NavigationItem = .shop
var app: WinUIApp
var view: Body {
NavigationView(items: NavigationItem.allCases, selection: $selectedItem) {
if selectedItem != .shop {
Button("\(selectedItem)") {
selectedItem = .shop
}
.horizontalAlignment(.center)
.verticalAlignment(.center)
} else {
Button {
Text("Shop")
.style(.bodyStrong)
} menu: {
menu
}
.horizontalAlignment(.center)
.verticalAlignment(.center)
}
}
}
@ViewBuilder var menu: Body {
NavigationItem.allCases.map { item in
MenuButton(item.description) {
selectedItem = item
}
.icon(item.icon)
}
Separator()
Submenu("Advanced") {
MenuButton("Update") {
StateManager.updateViews()
}
.keyboardShortcut(.init(key: .u))
MenuButton("Force Update") {
StateManager.updateViews(force: true)
}
.keyboardShortcut(.init(key: .u, shift: true))
}
MenuButton("Quit") {
app.quit()
}
.keyboardShortcut(.init(key: .q))
.icon(.systemIcon("\u{E7E8}"))
}
}
enum NavigationItem: String, CaseIterable, NavigationViewItem {
case shop
case items
case search
var description: String {
rawValue.capitalized
}
var icon: Icon {
let character: Character = switch self {
case .shop:
"\u{E719}"
case .items:
"\u{E71D}"
case .search:
"\u{E71E}"
}
return .systemIcon(character)
}
}
// swiftlint:enable missing_docs implicitly_unwrapped_optional no_magic_numbers

View File

@ -0,0 +1,66 @@
//
// MenuButton.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A button widget for menus.
public struct MenuButton: MenuWidget {
/// The button's label.
@Property(set: { $0.text = $1 }, pointer: MenuFlyoutItem.self)
var label = ""
/// An optional icon.
@Property(set: { $0.icon = $1.winIcon }, pointer: MenuFlyoutItem.self)
var icon: Icon?
/// The button's action handler.
@Property(
set: { item, closure, storage in
if storage.fields["handler"] == nil {
item.click.addHandler { _, _ in (storage.fields["handler"] as? () -> Void)?() }
}
storage.fields["handler"] = closure
},
pointer: MenuFlyoutItem.self
)
var handler: () -> Void = { }
/// The keyboard shortcut.
@Property(set: { item, shortcut in
item.keyboardAccelerators.clear()
item.keyboardAccelerators.append(shortcut.winShortcut)
}, pointer: MenuFlyoutItem.self)
var shortcut: KeyboardShortcut?
/// Initialize a menu button.
/// - Parameters:
/// - label: The buttons label.
/// - handler: The button's action handler.
public init(_ label: String, handler: @escaping () -> Void) {
self.label = label
self.handler = handler
}
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
MenuFlyoutItem()
}
/// Set the menu button's icon.
/// - Parameter icon: The icon.
/// - Returns: The menu button.
public func icon(_ icon: Icon?) -> Self {
modify { $0.icon = icon }
}
/// Set the menu button's keyboard shortcut.
/// - Parameter icon: The keyboard shortcut.
/// - Returns: The menu button.
public func keyboardShortcut(_ shortcut: KeyboardShortcut?) -> Self {
modify { $0.shortcut = shortcut }
}
}

View File

@ -0,0 +1,71 @@
//
// MenuCollection.swift
// WinUI
//
// Created by david-swift on 02.08.2024.
//
import Foundation
import WinUI
import WindowsFoundation
/// A collection of menus.
public struct MenuCollection: MenuWidget, Wrapper {
/// The content of the collection.
var content: Body
/// Initialize a menu.
/// - Parameter content: The content of the collection.
public init(@ViewBuilder content: @escaping () -> Body) {
self.content = content()
}
/// The view storage.
/// - Parameters:
/// - modifiers: Modify the views before updating.
/// - type: The type of the views.
/// - Returns: The view storage.
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let storages = content.storages(data: data, type: type)
var items: [MenuFlyoutItemBase?] = []
getItems(items: &items, storages: storages)
return .init(items, content: [.mainContent: storages])
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify the views before updating.
/// - updateProperties: Whether to update the properties.
/// - type: The type of the views.
public func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
guard let storages = storage.content[.mainContent] else {
return
}
content.update(storages, data: data, updateProperties: updateProperties, type: type)
}
/// Get the child items of the collection.
/// - Parameters:
/// - items: Pass the variable that will store the items.
/// - storages: The storages.
func getItems(items: inout [MenuFlyoutItemBase?], storages: [ViewStorage]) {
for item in storages {
if let item = item.pointer as? MenuFlyoutItemBase {
items.append(item)
} else {
items += item.pointer as? [MenuFlyoutItemBase] ?? []
}
}
}
}

View File

@ -0,0 +1,21 @@
//
// MenuContext.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
/// The menu items view context.
public enum MenuContext: ViewRenderData {
/// The type of the widgets.
public typealias WidgetType = MenuWidget
/// The wrapper type.
public typealias WrapperType = MenuCollection
/// The either view type.
public typealias EitherViewType = MenuEitherView
}
/// The type of the widgets.
public protocol MenuWidget: Meta.Widget { }

View File

@ -0,0 +1,23 @@
//
// MenuEitherView.swift
// WinUI
//
// Created by david-swift on 06.08.2024.
//
/// Show one of two views depending on a condition.
public struct MenuEitherView: Meta.EitherView, SimpleView {
/// The view.
public var view: Body
/// Initialize an either view.
/// - Parameters:
/// - condition: The condition.
/// - view1: The first view.
/// - view2: The second view.
public init(_ condition: Bool, view1: () -> Body, else view2: () -> Body) {
self.view = condition ? view1() : view2()
}
}

View File

@ -0,0 +1,21 @@
//
// MenuButton.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A button widget for menus.
public struct Separator: MenuWidget {
public init() { }
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
MenuFlyoutSeparator()
}
}

View File

@ -0,0 +1,51 @@
//
// Submenu.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A submenu widget.
public struct Submenu: MenuWidget {
/// The label of the submenu.
@Property(set: { $0.text = $1 }, pointer: MenuFlyoutSubItem.self)
var label = ""
/// An optional icon for the submenu.
@Property(set: { $0.icon = $1.winIcon }, pointer: MenuFlyoutSubItem.self)
var icon: Icon?
/// The content of the submenu.
@ViewProperty(
set: { subItem, items in
items.forEach { subItem.items.append($0) }
},
pointer: MenuFlyoutSubItem.self,
subview: [MenuFlyoutItemBase?].self
)
var content: Body
/// Initialize a submenu.
/// - Parameters:
/// - label: The submenu's label.
/// - content: The content of the section.
public init(_ label: String, @ViewBuilder content: () -> Body) {
self.label = label
self.content = content()
}
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
MenuFlyoutSubItem()
}
/// Set the submenu's icon.
/// - Parameter icon: The icon.
/// - Returns: The submenu.
public func icon(_ icon: Icon?) -> Self {
modify { $0.icon = icon }
}
}

View File

@ -0,0 +1,68 @@
//
// ToggleMenuButton.swift
// WinUI
//
// Created by david-swift on 13.10.24.
//
import WinUI
/// A toggle button widget for menus.
public struct ToggleMenuButton: MenuWidget {
/// The button's label.
@Property(set: { $0.text = $1 }, pointer: ToggleMenuFlyoutItem.self)
var label = ""
/// An optional icon.
@Property(set: { $0.icon = $1.winIcon }, pointer: ToggleMenuFlyoutItem.self)
var icon: Icon?
/// The keyboard shortcut.
@Property(set: { item, shortcut in
item.keyboardAccelerators.clear()
item.keyboardAccelerators.append(shortcut.winShortcut)
}, pointer: ToggleMenuFlyoutItem.self)
var shortcut: KeyboardShortcut?
/// Whether the toggle button is activated.
@BindingProperty(
observe: { item, binding in
item.click.addHandler { _, _ in
binding.wrappedValue = item.isChecked
}
},
set: { item, value in
item.isChecked = value
},
pointer: ToggleMenuFlyoutItem.self
)
var isOn: Meta.Binding<Bool> = .constant(false)
/// Initialize a toggle menu button.
/// - Parameters:
/// - label: The buttons label.
/// - isOn: Whether the toggle button is on.
public init(_ label: String, isOn: Meta.Binding<Bool>) {
self.label = label
self.isOn = isOn
}
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
ToggleMenuFlyoutItem()
}
/// Set the menu button's icon.
/// - Parameter icon: The icon.
/// - Returns: The menu button.
public func icon(_ icon: Icon?) -> Self {
modify { $0.icon = icon }
}
/// Set the menu button's keyboard shortcut.
/// - Parameter icon: The keyboard shortcut.
/// - Returns: The menu button.
public func keyboardShortcut(_ shortcut: KeyboardShortcut?) -> Self {
modify { $0.shortcut = shortcut }
}
}

View File

@ -0,0 +1,70 @@
//
// Edge.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
/// The edge type.
public enum Edge: Int {
/// The left edge.
case left
/// The top edge.
case top
/// The right edge.
case right
/// The bottom edge.
case bottom
}
extension [Edge] {
/// The left edge.
public static var left: Self {
[.left]
}
/// The right edge.
public static var right: Self {
[.right]
}
/// The top edge.
public static var top: Self {
[.top]
}
/// The bottom edge.
public static var bottom: Self {
[.bottom]
}
/// The left and right edge.
public static var horizontal: Self {
left + right
}
/// The top and bottom edge.
public static var vertical: Self {
top + bottom
}
/// The left, right, top, and bottome edges.
public static var all: Self {
vertical + horizontal
}
/// Convert a margin at certain edges to a tuple.
/// - Parameter value: The margin value.
/// - Returns: The tuple
public func set(_ value: Double) -> (Double, Double, Double, Double) {
var margin: [Double] = .init(repeating: 0, count: 4)
for edge in self {
margin[safe: edge.rawValue] = value
}
return (margin[0], margin[1], margin[2], margin[3])
}
}

View File

@ -0,0 +1,36 @@
//
// HorizontalAlignment.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
import WinUI
/// The horizontal alignment.
public enum HorizontalAlignment {
/// The left alignment.
case left
/// The right alignment.
case right
/// The center alignment.
case center
/// The strech alignment.
case stretch
/// The WinUI alignment.
public var winAlignment: WinUI.HorizontalAlignment {
switch self {
case .left:
.left
case .right:
.right
case .center:
.center
case .stretch:
.stretch
}
}
}

View File

@ -0,0 +1,34 @@
//
// Icon.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
import Foundation
import WinUI
/// An icon.
public enum Icon: Equatable {
/// An SVG icon.
case svg(URL)
/// A system icon.
case systemIcon(Character)
/// The WinUI icon.
var winIcon: IconElement {
switch self {
case let .svg(url):
let icon = ImageIcon()
let source = SvgImageSource(.init(url.path))
icon.source = source
return icon
case let .systemIcon(character):
let icon = FontIcon()
icon.glyph = String(character)
return icon
}
}
}

View File

@ -0,0 +1,49 @@
//
// KeyboardShortcut.swift
// WinUI
//
// Created by david-swift on 13.10.2024.
//
import Foundation
import UWP
import WinUI
/// Keyboard shortcut.
public struct KeyboardShortcut: Equatable {
/// The key.
var key: VirtualKey
/// Whether the control modifier key is part of the shortcut.
var control: Bool
/// Whether the shift modifier key is part of the shortcut.
var shift: Bool
/// Whether the menu (alt) modifier key is part of the shortcut.
var menu: Bool
/// The WinUI keyboard shortcut.
var winShortcut: KeyboardAccelerator {
let accelerator = KeyboardAccelerator()
accelerator.key = key
accelerator.modifiers = .init(
(control ? VirtualKeyModifiers.control.rawValue : 0)
| (shift ? VirtualKeyModifiers.shift.rawValue : 0)
| (menu ? VirtualKeyModifiers.menu.rawValue : 0)
)
return accelerator
}
/// Initialize a keyboard shortcut.
/// - Parameters:
/// - key: The key.
/// - ctrl: Whether the control modifier key is part of the shortcut.
/// - shift: Whether the shift modifier key is part of the shortcut.
/// - menu: Whether the menu (alt) modifier key is part of the shortcut.
public init(key: VirtualKey, ctrl: Bool = true, shift: Bool = false, menu: Bool = false) {
self.key = key
control = ctrl
self.shift = shift
self.menu = menu
}
}

View File

@ -0,0 +1,36 @@
//
// VerticalAlignment.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
import WinUI
/// The vertical alignment.
public enum VerticalAlignment {
/// The top alignment.
case top
/// The bottom alignment.
case bottom
/// The center alignment.
case center
/// The stretch alignment.
case stretch
/// The WinUI alignment.
public var winAlignment: WinUI.VerticalAlignment {
switch self {
case .top:
.top
case .bottom:
.bottom
case .center:
.center
case .stretch:
.stretch
}
}
}

View File

@ -0,0 +1,56 @@
//
// WinUIApp.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
import WinUI
@_exported import Meta
/// The app storage for the WinUI backend.
public class WinUIApp: AppStorage {
/// The scene element type of the WinUI backend.
public typealias SceneElementType = WinUISceneElement
/// The widget type of the WinUI backend.
public typealias WidgetType = WinUIWidget
/// The wrapper type of the WinUI backend.
public typealias WrapperType = VStack
/// The app storage.
public var storage: StandardAppStorage = .init()
/// Initialize the app storage.
public init() { }
/// Execute the app.
/// - Parameter setup: Set the scene elements up.
public func run(setup: @escaping () -> Void) {
BackendApp.run = setup
BackendApp.main()
}
/// Quit the app.
public func quit() {
try? BackendApp.current.exit()
}
}
/// The backend WinUI app.
class BackendApp: SwiftApplication {
/// The run function.
static var run: () -> Void = { }
/// Run the run function when being launched.
/// - Parameter args: The arguments.
override func onLaunched(_ args: LaunchActivatedEventArgs) {
_ = resources.insert("NavigationViewContentMargin", "0,48,0,0")
_ = resources.insert("WindowCaptionBackground", "Transparent")
_ = resources.insert("WindowCaptionBackgroundDisabled", "Transparent")
Self.run()
}
}

View File

@ -0,0 +1,18 @@
//
// WinUIMainView.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
/// The type of widgets of the WinUI backend.
public enum WinUIMainView: ViewRenderData {
/// The type of the widgets.
public typealias WidgetType = WinUIWidget
/// The wrapper type.
public typealias WrapperType = VStack
/// The either view type.
public typealias EitherViewType = EitherView
}

View File

@ -0,0 +1,9 @@
//
// WinUISceneElement.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
/// The type of scene elements of the WinUI backend.
public protocol WinUISceneElement: SceneElement { }

View File

@ -0,0 +1,9 @@
//
// WinUIWidget.swift
// WinUI
//
// Created by david-swift on 31.07.2024.
//
/// The type of widgets of the WinUI backend.
public protocol WinUIWidget: Widget { }

View File

@ -0,0 +1,110 @@
//
// VStack.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A button.
public struct Button: WinUIWidget {
/// The action to run when the button gets clicked.
var action: () -> Void
/// The label.
var label: Body
/// The menu.
var menu: Body?
/// Initialize a button.
/// - Parameters:
/// - label: The button's label.
/// - action: The button's action.
public init(_ label: String, action: @escaping () -> Void) {
self.action = action
self.label = [Text(label)]
}
/// Initialize a button.
/// - Parameters:
/// - action: The button's action.
/// - label: A view as the button's label.
public init(action: @escaping () -> Void, @ViewBuilder label: () -> Body) {
self.action = action
self.label = label()
}
/// Initialize a button with a menu.
/// - Parameters:
/// - label: The button's label.
/// - menu: The button's menu.
public init(menu label: String, @ViewBuilder menu: @escaping () -> Body) {
action = { }
self.label = [Text(label)]
self.menu = menu()
}
/// Initialize a button with a menu.
/// - Parameters:
/// - label: A view as the button's label.
/// - menu: The button's menu.
public init(@ViewBuilder label: () -> Body, @ViewBuilder menu: @escaping () -> Body) {
action = { }
self.menu = menu()
self.label = label()
}
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let button = WinUI.Button()
let storage = ViewStorage(button)
button.click.addHandler { _, _ in
(storage.fields["click"] as? () -> Void)?()
}
let label = label.storage(data: data, type: type)
button.content = label.pointer
storage.content[.mainContent] = [label]
if let menu {
let flyout = MenuFlyout()
let items = MenuCollection { menu }.container(data: data.noModifiers, type: MenuContext.self)
storage.content["menu"] = [items]
if let items = items.pointer as? [MenuFlyoutItemBase?] {
print(items)
items.forEach { flyout.items.append($0) }
}
button.flyout = flyout
}
update(storage, data: data, updateProperties: true, type: type)
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
storage.fields["click"] = action
if let content = storage.content[.mainContent]?.first {
label.updateStorage(content, data: data, updateProperties: updateProperties, type: type)
}
if let menu, let content = storage.content["menu"]?.first {
MenuCollection { menu }.updateStorage(content, data: data.noModifiers, updateProperties: updateProperties, type: MenuContext.self)
}
}
}

View File

@ -0,0 +1,84 @@
//
// ViewStack.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A widget showing one of two widgets based on a condition.
public struct EitherView: WinUIWidget, Meta.EitherView {
/// Whether the first view is visible.
var condition: Bool
/// The first view.
var view1: Body
/// The second view.
var view2: Body
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data : ViewRenderData {
let view = WinUI.Grid()
let storage = ViewStorage(view)
update(storage, data: data, updateProperties: true, type: type)
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data : ViewRenderData {
if let content1 = storage.content["view1"]?.first {
view1.updateStorage(content1, data: data, updateProperties: updateProperties, type: type)
}
if let content2 = storage.content["view2"]?.first {
view2.updateStorage(content2, data: data, updateProperties: updateProperties, type: type)
}
guard updateProperties, (storage.previousState as? Self)?.condition != condition, let grid = storage.pointer as? WinUI.Grid else {
return
}
if condition, let content = storage.content["view1"]?.first {
(content.pointer as? UIElement)?.visibility = .visible
} else if condition {
let contentStorage = view1.storage(data: data, type: type)
storage.content["view1"] = [contentStorage]
grid.children.append(contentStorage.pointer as? UIElement)
} else if let content = storage.content["view2"]?.first {
(content.pointer as? UIElement)?.visibility = .visible
} else {
let contentStorage = view2.storage(data: data, type: type)
storage.content["view2"] = [contentStorage]
grid.children.append(contentStorage.pointer as? UIElement)
}
if condition, let content = storage.content["view2"]?.first {
(content.pointer as? UIElement)?.visibility = .collapsed
} else if !condition, let content = storage.content["view1"]?.first {
(content.pointer as? UIElement)?.visibility = .collapsed
}
storage.previousState = self
}
/// Initialize the either view.
/// - Parameters:
/// - condition: Whether the first view is visible-
/// - view1: The first view, visible if true.
/// - view2: The second view, visible if false.
public init(
_ condition: Bool,
@ViewBuilder view1: () -> Body,
@ViewBuilder else view2: () -> Body
) {
self.condition = condition
self.view1 = view1()
self.view2 = view2()
}
}

View File

@ -0,0 +1,144 @@
//
// VStack.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import LevenshteinTransformations
import WinUI
/// A wrapper around ``VStack`` which applies the ``VStack`` only if there is more than one view.
public struct Grid: WinUIWidget {
/// The grid's content.
var content: Body
/// The grid's columns.
var columns: [Column]?
/// The padding.
var padding: (Double, Double, Double, Double)?
/// Whether to set the grid as the title bar (only for internal use).
var title = false
/// Initialize a grid.
/// - Parameters:
/// - columns: The available columns.
/// - content: The content.
public init(columns: [Column]? = nil, @ViewBuilder content: () -> Body) {
self.content = content()
self.columns = columns
}
/// A grid column.
public struct Column: Equatable {
/// The column's width.
public var width: Double?
/// The column's minimum width.
public var minWidth: Double?
/// The column's maximum width.
public var maxWidth: Double?
/// Initialize a column.
/// - Parameters:
/// - width: The column's width.
/// - minWidth: The column's minimum width.
/// - maxWidth: The column's maximum width.
public init(width: Double? = nil, minWidth: Double? = nil, maxWidth: Double? = nil) {
self.width = width
self.minWidth = minWidth
self.maxWidth = maxWidth
}
/// The WinUI column definition.
var winColumn: ColumnDefinition {
let column = ColumnDefinition()
if let width {
column.width = .init(value: width, gridUnitType: .pixel)
} else {
column.width = .init(value: 0, gridUnitType: .auto)
}
if let minWidth {
column.minWidth = minWidth
}
if let maxWidth {
column.maxWidth = maxWidth
}
return column
}
}
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let grid = WinUI.Grid()
let storage = ViewStorage(grid)
let storages = content.storages(data: data, type: type)
for element in storages {
grid.children.append(element.pointer as? UIElement)
}
storage.content[.mainContent] = storages
update(storage, data: data, updateProperties: true, type: type)
if title {
try? (data.sceneStorage.pointer as? WinUI.Window)?.setTitleBar(storage.pointer as? UIElement)
}
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
if let storages = storage.content[.mainContent] {
content.update(storages, data: data, updateProperties: updateProperties, type: type)
}
guard let grid = storage.pointer as? WinUI.Grid else {
return
}
let previousState = storage.previousState as? Self
if let columns {
(previousState?.columns ?? []).transform(to: columns, functions: .init { index, column in
grid.columnDefinitions[index] = column.winColumn
} delete: { index in
_ = grid.columnDefinitions.remove(at: index)
} insert: { index, column in
grid.columnDefinitions.insertAt(.init(index), column.winColumn)
})
}
if let padding, previousState?.padding ?? (0, 0, 0, 0) != padding {
grid.padding = .init(left: padding.0, top: padding.1, right: padding.2, bottom: padding.3)
}
storage.previousState = self
}
/// Set the grid's padding.
/// - Parameters:
/// - padding: The padding's value.
/// - edges: The affected edges.
/// - Returns: The grid.
public func padding(_ padding: Double, _ edges: [Edge]) -> Self {
modify { $0.padding = edges.set(padding) }
}
/// Set the grid as the title bar.
/// - Returns: The grid.
func titleBar() -> Self {
modify { $0.title = true }
}
}

View File

@ -0,0 +1,120 @@
//
// ModifierWrapper.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A wrapper view for modifiers.
struct ModifierWrapper: WinUIWidget {
/// The content view.
var view: AnyView
/// The view's width.
var width: Double?
/// The view's height.
var height: Double?
/// The view's margin.
var margin: (Double, Double, Double, Double)?
/// The horizontal alignment.
var horizontalAlignment: HorizontalAlignment?
/// The vertical alignment.
var verticalAlignment: VerticalAlignment?
/// The grid column.
var gridColumn: Int?
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data : ViewRenderData {
let storage = view.storage(data: data, type: type)
update(storage, data: data, updateProperties: true, type: type)
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data : ViewRenderData {
view.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
guard updateProperties, let view = storage.pointer as? WinUI.FrameworkElement else {
return
}
let previousState = storage.previousState as? Self
if let width, previousState?.width != width {
view.width = width
}
if let height, previousState?.height != height {
view.height = height
}
if let margin, previousState?.margin ?? (0, 0, 0, 0) != margin {
view.margin = .init(left: margin.0, top: margin.1, right: margin.2, bottom: margin.3)
}
if let horizontalAlignment, previousState?.horizontalAlignment != horizontalAlignment {
view.horizontalAlignment = horizontalAlignment.winAlignment
}
if let verticalAlignment, previousState?.verticalAlignment != verticalAlignment {
view.verticalAlignment = verticalAlignment.winAlignment
}
if let gridColumn, previousState?.gridColumn != gridColumn {
WinUI.Grid.setColumn(view, .init(gridColumn))
}
storage.previousState = self
}
}
extension AnyView {
/// Set the view's width.
/// - Parameter width: The width.
/// - Returns: The view.
public func width(_ width: Double?) -> AnyView {
ModifierWrapper(view: self, width: width)
}
/// Set the view's height.
/// - Parameter height: The height.
/// - Returns: The view.
public func height(_ height: Double?) -> AnyView {
ModifierWrapper(view: self, height: height)
}
/// Set the view's margin.
/// - Parameters:
/// - value: The value of the margin.
/// - edges: The affected edges.
/// - Returns: The view.
public func margin(_ value: Double, _ edges: [Edge]) -> AnyView {
ModifierWrapper(view: self, margin: edges.set(value))
}
/// Set the view's horizontal alignment.
/// - Parameter alignment: The alignment.
/// - Returns: The view.
public func horizontalAlignment(_ alignment: HorizontalAlignment?) -> AnyView {
ModifierWrapper(view: self, horizontalAlignment: alignment)
}
/// Set the view's vertical alignment.
/// - Parameter alignment: The alignment.
/// - Returns: The view.
public func verticalAlignment(_ alignment: VerticalAlignment?) -> AnyView {
ModifierWrapper(view: self, verticalAlignment: alignment)
}
/// Set the view's grid column.
/// - Parameter column: The column.
/// - Returns: The view.
public func gridColumn(_ column: Int) -> AnyView {
ModifierWrapper(view: self, gridColumn: column)
}
}

View File

@ -0,0 +1,160 @@
//
// NavigationView.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import LevenshteinTransformations
import WindowsFoundation
import WinUI
/// A view consisting of a sidebar with items and a content pane.
public struct NavigationView<Item>: WinUIWidget where Item: NavigationViewItem {
/// The selected item.
@Meta.Binding var selectedItem: Selection
/// The items.
var items: [Item]
/// The content.
var content: Body
/// Whether the settings item is available.
var settings = false
/// Initialize the navigation view without the settings item.
/// - Parameters:
/// - items: The items.
/// - selection: The selected item.
/// - content: The content view.
public init(items: [Item], selection: Meta.Binding<Item>, @ViewBuilder content: () -> Body) {
self.items = items
self.content = content()
self._selectedItem = .init {
.custom(selection.wrappedValue)
} set: { newValue in
if case let .custom(value) = newValue {
selection.wrappedValue = value
}
}
}
/// Initialize the navigation view with the settings item.
/// - Parameters:
/// - items: The items.
/// - selection: The selected item, which might be the settings item.
/// - content: The content view.
public init(items: [Item], selection: Meta.Binding<Selection>, @ViewBuilder content: () -> Body) {
self.items = items
self.content = content()
self._selectedItem = selection
settings = true
}
/// The selection type.
public enum Selection: Equatable {
/// The settings item.
case settings
/// A custom item.
case custom(Item)
}
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let view = WinUI.NavigationView()
view.isTitleBarAutoPaddingEnabled = false
view.alwaysShowHeader = true
view.isTabStop = false
view.isBackButtonVisible = .collapsed
let contentStorage = content.storage(data: data, type: type)
let storage = ViewStorage(view, content: [.mainContent: [contentStorage]])
view.content = contentStorage.pointer
if settings {
view.isSettingsVisible = true
} else {
view.isSettingsVisible = false
}
update(storage, data: data, updateProperties: true, type: type)
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
if let content = storage.content[.mainContent]?.first {
self.content.updateStorage(content, data: data, updateProperties: updateProperties, type: type)
}
guard let navigationView = storage.pointer as? WinUI.NavigationView else {
return
}
navigationView.selectionChanged.addHandler { _, args in
let name = (args?.selectedItem as? WinUI.NavigationViewItem)?.name ?? items.first?.description ?? ""
if let item = items.first(where: { $0.description == name }) {
if selectedItem != .custom(item) {
selectedItem = .custom(item)
}
} else {
selectedItem = .settings
}
}
guard updateProperties else {
return
}
let previousState = storage.previousState as? Self
(previousState?.items ?? [])?.transform(to: items, functions: .init { index, element in
navigationView.menuItems[index] = element.winItem
} delete: { index in
_ = navigationView.menuItems.remove(at: index)
} insert: { index, element in
navigationView.menuItems.insertAt(.init(index), element.winItem)
})
if case .settings = selectedItem {
navigationView.isSettingsVisible = true
} else if case let .custom(name) = selectedItem {
navigationView.selectedItem = navigationView.menuItems.first { ($0 as? WinUI.NavigationViewItem)?.name as? String == name.description } ?? nil
}
storage.previousState = self
}
}
/// The navigation selection type.
public typealias NavigationSelection<Item> = NavigationView<Item>.Selection where Item: NavigationViewItem
/// A navigation view item.
public protocol NavigationViewItem: CustomStringConvertible, Equatable {
var icon: Icon { get }
}
extension NavigationViewItem {
/// The navigation view item for the backend.
var winItem: WinUI.NavigationViewItem {
let item = WinUI.NavigationViewItem()
item.icon = icon.winIcon
item.content = description
item.tag = description
item.name = description
return item
}
}

View File

@ -0,0 +1,98 @@
//
// Text.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A view showing a simple text.
public struct Text: WinUIWidget {
/// The label.
var label: String
/// The style.
var textStyle: Style?
/// Initialize the text.
/// - Parameter label: The text.
public init(_ label: String) {
self.label = label
}
/// The text style.
public enum Style: String {
/// The caption text style.
case caption
/// The body text style.
case body
/// The body strong text style.
case bodyStrong
/// The body large text style.
case bodyLarge
/// The subtitle text style.
case subtitle
/// The title text style.
case title
/// The title large text style.
case titleLarge
/// The display text style
case display
/// Get the style resource.
public var resource: String {
"\(rawValue.prefix(1).uppercased())\(rawValue.dropFirst())TextBlockStyle"
}
}
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let text = TextBlock()
let storage = ViewStorage(text)
update(storage, data: data, updateProperties: true, type: type)
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
guard updateProperties, let text = storage.pointer as? TextBlock else {
return
}
let previousState = storage.previousState as? Self
if label != previousState?.label {
text.text = label
}
if let textStyle, textStyle != previousState?.textStyle {
text.style = Application.current.resources.lookup(textStyle.resource) as? WinUI.Style
}
storage.previousState = self
}
/// The text style.
/// - Parameter style: The style.
/// - Returns: The text view.
public func style(_ style: Style?) -> Self {
modify { $0.textStyle = style }
}
}

View File

@ -0,0 +1,63 @@
//
// VStack.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import WinUI
/// A container arranging its children vertically.
public struct VStack: WinUIWidget, Wrapper {
/// The content.
var content: Body
/// Initialize the wrapper.
/// - Parameter content: The view content.
public init(@ViewBuilder content: () -> Body) {
self.content = content()
}
/// The view storage.
/// - Parameters:
/// - data: The widget data.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
if content.count == 1, let view = content.first {
return view.storage(data: data, type: type)
}
let stack = StackPanel()
let storages = content.storages(data: data, type: type)
for storage in storages {
stack.children.append(storage.pointer as? UIElement)
}
return .init(stack, content: [.mainContent: storages])
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - data: The widget data.
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(
_ storage: ViewStorage,
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
if content.count == 1, let view = content.first {
view.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
return
}
if let storages = storage.content[.mainContent] {
content.update(storages, data: data, updateProperties: updateProperties, type: type)
}
}
}

View File

@ -0,0 +1,127 @@
//
// Window.swift
// WinUI
//
// Created by david-swift on 11.10.24.
//
import Foundation
import WinAppSDK
import WinUI
/// A structure representing a window type.
///
/// Note that it may be possible to open multiple instances of a window at the same time.
public struct Window: WinUISceneElement {
/// The window's identifier.
public var id: String
/// The window's title.
var title: String
/// The window's content.
var content: (WinUIWindow) -> Body
/// The number of instances of the window type that should be opened when the app is starting up.
var `open`: Int
/// Whether to extend the content into the title bar.
var extendContent: Bool?
/// Create a window type with a certain identifier and user interface.
/// - Parameters:
/// - title: The window's title.
/// - id: The identifier.
/// - open: The number of instances of the window type when the app is starting.
/// - content: The window's content.
public init(_ title: String, id: String, `open`: Int = 1, @ViewBuilder content: @escaping (WinUIWindow) -> Body) {
self.title = title
self.content = content
self.id = id
self.open = open
}
/// Get the content, including the title bar view.
/// - Parameter window: The WinUI window.
/// - Returns: The view.
func fullContent(window: WinUIWindow) -> Grid {
.init {
Grid(columns: [.init()]) {
Text(title)
.style(.caption)
.verticalAlignment(.center)
}
.titleBar()
.margin(48, .left)
.height(48)
.verticalAlignment(.top)
content(window)
}
}
/// Set up the initial scene storages.
/// - Parameter app: The app storage.
public func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
for _ in 0..<open {
let container = container(app: app)
container.show()
app.storage.sceneStorage.append(container)
}
}
/// The scene storage.
/// - Parameter app: The app storage.
/// - Returns: The scene storage.
public func container<Storage>(app: Storage) -> SceneStorage where Storage: AppStorage {
let window = WinUIWindow()
let scene = SceneStorage(id: id, pointer: window) {
try? window.activate()
}
let storage = fullContent(window: window).storage(data: .init(sceneStorage: scene, appStorage: app), type: WinUIMainView.self)
if let ui = storage.pointer as? UIElement {
window.content = ui
}
scene.content[.mainContent] = [storage]
window.systemBackdrop = MicaBackdrop()
update(scene, app: app, updateProperties: true)
return scene
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - app: The app storage.
/// - updateProperties: Whether to update the view's properties.
public func update<Storage>(
_ storage: SceneStorage,
app: Storage,
updateProperties: Bool
) where Storage: AppStorage {
guard let window = storage.pointer as? WinUIWindow,
let contentStorage = storage.content[.mainContent]?.first else {
return
}
fullContent(window: window).updateStorage(contentStorage, data: .init(sceneStorage: storage, appStorage: app), updateProperties: updateProperties, type: WinUIMainView.self)
guard updateProperties else {
return
}
let previousState = storage.previousState as? Self
if let extendContent, extendContent != previousState?.extendContent {
window.extendsContentIntoTitleBar = extendContent
}
if title != previousState?.title {
window.title = title
}
storage.previousState = self
}
/// Whether to extend the content into the title bar.
/// - Parameter enabled: Whether to extend the content.
/// - Returns: The window.
public func extendContentIntoTitleBar(_ enabled: Bool? = true) -> Self {
var newSelf = self
newSelf.extendContent = enabled
return newSelf
}
}
/// A WinUI window.
public typealias WinUIWindow = WinUI.Window