This commit is contained in:
commit
7afe735433
40
.gitea/ISSUE_TEMPLATE/bug_report.yml
Normal file
40
.gitea/ISSUE_TEMPLATE/bug_report.yml
Normal 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.
|
29
.gitea/ISSUE_TEMPLATE/component_request.yml
Normal file
29
.gitea/ISSUE_TEMPLATE/component_request.yml
Normal 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
|
36
.gitea/ISSUE_TEMPLATE/feature_request.yml
Normal file
36
.gitea/ISSUE_TEMPLATE/feature_request.yml
Normal 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
|
12
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
12
.gitea/PULL_REQUEST_TEMPLATE.md
Normal 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._
|
30
.gitea/workflows/swiftlint.yml
Normal file
30
.gitea/workflows/swiftlint.yml
Normal 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
13
.gitignore
vendored
Normal 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
157
.swiftlint.yml
Normal 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
21
LICENSE.md
Normal 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
47
Package.swift
Normal 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
79
README.md
Normal 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
105
Sources/Demo/Demo.swift
Normal 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
|
66
Sources/winui-swift/Menu/MenuButton.swift
Normal file
66
Sources/winui-swift/Menu/MenuButton.swift
Normal 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 }
|
||||
}
|
||||
|
||||
}
|
71
Sources/winui-swift/Menu/MenuCollection.swift
Normal file
71
Sources/winui-swift/Menu/MenuCollection.swift
Normal 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] ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
21
Sources/winui-swift/Menu/MenuContext.swift
Normal file
21
Sources/winui-swift/Menu/MenuContext.swift
Normal 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 { }
|
23
Sources/winui-swift/Menu/MenuEitherView.swift
Normal file
23
Sources/winui-swift/Menu/MenuEitherView.swift
Normal 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()
|
||||
}
|
||||
|
||||
}
|
21
Sources/winui-swift/Menu/Separator.swift
Normal file
21
Sources/winui-swift/Menu/Separator.swift
Normal 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()
|
||||
}
|
||||
|
||||
}
|
51
Sources/winui-swift/Menu/Submenu.swift
Normal file
51
Sources/winui-swift/Menu/Submenu.swift
Normal 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 }
|
||||
}
|
||||
|
||||
}
|
68
Sources/winui-swift/Menu/ToggleMenuButton.swift
Normal file
68
Sources/winui-swift/Menu/ToggleMenuButton.swift
Normal 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 }
|
||||
}
|
||||
|
||||
}
|
70
Sources/winui-swift/Model/Enumerations/Edge.swift
Normal file
70
Sources/winui-swift/Model/Enumerations/Edge.swift
Normal 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])
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
34
Sources/winui-swift/Model/Enumerations/Icon.swift
Normal file
34
Sources/winui-swift/Model/Enumerations/Icon.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
56
Sources/winui-swift/Model/WinUIApp.swift
Normal file
56
Sources/winui-swift/Model/WinUIApp.swift
Normal 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()
|
||||
}
|
||||
|
||||
}
|
18
Sources/winui-swift/Model/WinUIMainView.swift
Normal file
18
Sources/winui-swift/Model/WinUIMainView.swift
Normal 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
|
||||
|
||||
}
|
9
Sources/winui-swift/Model/WinUISceneElement.swift
Normal file
9
Sources/winui-swift/Model/WinUISceneElement.swift
Normal 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 { }
|
9
Sources/winui-swift/Model/WinUIWidget.swift
Normal file
9
Sources/winui-swift/Model/WinUIWidget.swift
Normal 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 { }
|
110
Sources/winui-swift/View/Button.swift
Normal file
110
Sources/winui-swift/View/Button.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
84
Sources/winui-swift/View/EitherView.swift
Normal file
84
Sources/winui-swift/View/EitherView.swift
Normal 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()
|
||||
}
|
||||
|
||||
}
|
144
Sources/winui-swift/View/Grid.swift
Normal file
144
Sources/winui-swift/View/Grid.swift
Normal 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 }
|
||||
}
|
||||
|
||||
}
|
120
Sources/winui-swift/View/ModifierWrapper.swift
Normal file
120
Sources/winui-swift/View/ModifierWrapper.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
160
Sources/winui-swift/View/NavigationView.swift
Normal file
160
Sources/winui-swift/View/NavigationView.swift
Normal 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
|
||||
}
|
||||
|
||||
}
|
98
Sources/winui-swift/View/Text.swift
Normal file
98
Sources/winui-swift/View/Text.swift
Normal 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 }
|
||||
}
|
||||
|
||||
}
|
63
Sources/winui-swift/View/VStack.swift
Normal file
63
Sources/winui-swift/View/VStack.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
127
Sources/winui-swift/Window/Window.swift
Normal file
127
Sources/winui-swift/Window/Window.swift
Normal 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
|
Loading…
Reference in New Issue
Block a user