Add support for settings
Some checks failed
SwiftLint / SwiftLint (push) Failing after 9s
Deploy Docs / publish (push) Waiting to run

This commit is contained in:
david-swift 2024-11-08 17:16:38 +01:00
parent 4170728900
commit 448c26debb
10 changed files with 341 additions and 12 deletions

View File

@ -18,6 +18,8 @@ public struct Card: WinUIWidget, Wrapper {
context: WinUIMainView.self
)
var content: Body
/// Whether it is a settings card.
var settingsCard = false
/// Initialize the wrapper.
/// - Parameter content: The view content.
@ -29,7 +31,8 @@ public struct Card: WinUIWidget, Wrapper {
/// - Returns: The widget.
public func initializeWidget() -> Any {
let stack = WinUI.Grid()
stack.cornerRadius = .init(topLeft: 8, topRight: 8, bottomRight: 8, bottomLeft: 8)
let radius: Double = settingsCard ? 4 : 8
stack.cornerRadius = .init(topLeft: radius, topRight: radius, bottomRight: radius, bottomLeft: radius)
stack.padding = .init(left: 12, top: 12, right: 12, bottom: 12)
stack.borderThickness = .init(left: 1, top: 1, right: 1, bottom: 1)
stack.borderBrush = Application.current.resources.lookup("CardStrokeColorDefaultBrush") as? Brush
@ -37,4 +40,10 @@ public struct Card: WinUIWidget, Wrapper {
return stack
}
/// Use the settings style.
/// - Returns: The card.
public func settings() -> Self {
modify { $0.settingsCard = true }
}
}

View File

@ -0,0 +1,66 @@
//
// ToggleSwitch.swift
// WinUI
//
// Created by david-swift on 06.11.24.
//
import LevenshteinTransformations
import WinUI
/// The combo box widget.
public struct ComboBox<Element>: WinUIWidget where Element: CustomStringConvertible {
/// The selected element.
@BindingProperty(
observe: { box, selection, storage in
box.selectionChanged.addHandler { _, _ in
selection.wrappedValue = (storage.previousState as? Self)?.elements[safe: .init(box.selectedIndex)]
}
},
set: { box, value, _ in
if let value {
box.selectedItem = value.description
}
},
pointer: WinUI.ComboBox.self
)
var selection: Meta.Binding<Element?> = .constant(nil)
/// The elements.
@Property(
set: { box, newValue, storage in
((storage.previousState as? Self)?.elements ?? []).map { $0.description }.transform(
to: newValue.map { $0.description },
functions: .init { index in
box.items.removeAt(.init(index))
} insert: { index, element in
box.items.insertAt(.init(index), element)
}
)
},
pointer: WinUI.ComboBox.self
)
var elements: [Element] = []
/// Initialize a combo box.
/// - Parameters:
/// - elements: The elements.
/// - selection: The selected element.
public init(_ elements: [Element], selection: Meta.Binding<Element>) {
self.selection = .init {
selection.wrappedValue
} set: { newValue in
if let newValue {
selection.wrappedValue = newValue
}
}
self.elements = elements
}
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
WinUI.ComboBox()
}
}

View File

@ -0,0 +1,37 @@
//
// ToggleSwitch.swift
// WinUI
//
// Created by david-swift on 06.11.24.
//
import WinUI
/// The toggle switch widget.
public struct ToggleSwitch: WinUIWidget {
/// Whether the switch is active.
@BindingProperty(
observe: { view, binding in
view.toggled.addHandler { _, _ in
binding.wrappedValue = view.isOn
}
},
set: { $0.isOn = $1 },
pointer: WinUI.ToggleSwitch.self
)
var isOn: Meta.Binding<Bool> = .constant(false)
/// Initialize the toggle switch.
/// - Parameter isOn: Whether the switch is active.
public init(isOn: Meta.Binding<Bool>) {
self.isOn = isOn
}
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
WinUI.ToggleSwitch()
}
}

View File

@ -0,0 +1,42 @@
//
// HyperlinkButton.swift
// WinUI
//
// Created by david-swift on 07.11.24.
//
import Foundation
import WinUI
/// A hyperlink as a button.
public struct HyperlinkButton: WinUIWidget {
/// The button's label.
@Property(
set: { $0.content = $1 },
pointer: WinUI.HyperlinkButton.self
)
var label: String?
/// The hyperlink.
@Property(
set: { $0.navigateUri = .init($1) },
pointer: WinUI.HyperlinkButton.self
)
var url: String?
/// Initialize the hyperlink button.
/// - Parameters:
/// - label: The label.
/// - url: The hyperlink.
public init(_ label: String, url: String) {
self.label = label
self.url = url
}
/// Initialize the widget.
/// - Returns:
public func initializeWidget() -> Any {
WinUI.HyperlinkButton()
}
}

View File

@ -28,6 +28,8 @@ public struct ModifierWrapper: WinUIWidget, CommandBarWidget {
var gridColumn: Int?
/// Whether the element is displayed.
var visible: Bool?
/// The view's opacity.
var opacity: Double?
// swiftlint:disable large_tuple
/// Initialize the modifier wrapper.
@ -40,6 +42,7 @@ public struct ModifierWrapper: WinUIWidget, CommandBarWidget {
/// - verticalAlignment: The vertical alignment.
/// - gridColumn: The grid column.
/// - visible: Whether the view is visible.
/// - opacity: The view's opacity.
public init(
view: AnyView,
width: Double? = nil,
@ -48,7 +51,8 @@ public struct ModifierWrapper: WinUIWidget, CommandBarWidget {
horizontalAlignment: HorizontalAlignment? = nil,
verticalAlignment: VerticalAlignment? = nil,
gridColumn: Int? = nil,
visible: Bool? = nil
visible: Bool? = nil,
opacity: Double? = nil
) {
self.view = view
self.width = width
@ -58,6 +62,7 @@ public struct ModifierWrapper: WinUIWidget, CommandBarWidget {
self.verticalAlignment = verticalAlignment
self.gridColumn = gridColumn
self.visible = visible
self.opacity = opacity
}
// swiftlint:enable large_tuple
@ -114,6 +119,9 @@ public struct ModifierWrapper: WinUIWidget, CommandBarWidget {
if let visible, previousState?.visible != visible {
view.visibility = visible ? .visible : .collapsed
}
if let opacity, previousState?.opacity != opacity {
view.opacity = opacity
}
storage.previousState = self
}

View File

@ -0,0 +1,34 @@
//
// ScrollView.swift
// WinUI
//
// Created by david-swift on 06.11.24.
//
import WinUI
/// A scroll view.
public struct ScrollView: WinUIWidget {
/// The view content.
@ViewProperty(
set: { $0.content = $1 },
pointer: WinUI.ScrollView.self,
subview: UIElement.self,
context: WinUIMainView.self
)
var content
/// Initialize a scroll view.
/// - Parameter content: The content.
public init(@ViewBuilder content: () -> Body) {
self.content = content()
}
/// Initialize the widget.
/// - Returns: The widget.
public func initializeWidget() -> Any {
WinUI.ScrollView()
}
}

View File

@ -0,0 +1,62 @@
//
// SettingsControl.swift
// WinUI
//
// Created by david-swift on 06.11.24.
//
/// A single settings item.
public struct SettingsControl: SimpleView {
/// The settings item's title.
var title: String
/// The settings item's description.
var description: String?
/// The control view.
var control: Body
/// Initialize a settings item.
/// - Parameters:
/// - title: The settings item's title.
/// - description: The settings item's description.
/// - control: The control view.
public init(_ title: String, description: String? = nil, @ViewBuilder control: () -> Body) {
self.title = title
self.description = description
self.control = control()
}
/// The view.
public var view: Body {
ModifierWrapper(
view: Card {
ModifierWrapper(
view: Grid {
ModifierWrapper(
view: VStack {
Text(title)
ModifierWrapper(
view: Text(description ?? "")
.style(.caption),
visible: description != nil,
opacity: 0.8
)
},
horizontalAlignment: .stretch,
verticalAlignment: .center
)
ModifierWrapper(
view: control,
horizontalAlignment: .right
)
},
margin: (5, 5, 5, 5)
)
}
.settings(),
margin: (2, 2, 2, 2),
verticalAlignment: .top
)
}
}

View File

@ -0,0 +1,38 @@
//
// SettingsSection.swift
// WinUI
//
// Created by david-swift on 06.11.24.
//
/// A section in a settings page.
public struct SettingsSection: SimpleView {
/// The section title.
var title: String
/// The section's content.
var content: Body
/// Initialize the settings section.
/// - Parameters:
/// - title: The title.
/// - content: The content.
public init(_ title: String, @ViewBuilder content: () -> Body) {
self.title = title
self.content = content()
}
/// The view.
public var view: Body {
ModifierWrapper(
view: Text(title)
.style(.bodyStrong),
margin: (5, 5, 5, 5)
)
ModifierWrapper(
view: content,
margin: (0, 0, 0, 25)
)
}
}

View File

@ -30,18 +30,15 @@ struct Demo: App {
struct ContentView: View {
@State private var selectedItem: NavigationItem = .shop
@State private var selectedItem: NavigationSelection<NavigationItem> = .custom(item: .shop)
@State private var comboBoxSelection: NavigationItem = .items
@State private var switchState = false
var app: WinUIApp
var view: Body {
NavigationView(items: NavigationItem.allCases, selection: $selectedItem) {
if selectedItem != .shop {
Button("\(selectedItem)") {
selectedItem = .shop
}
.horizontalAlignment(.center)
.verticalAlignment(.center)
} else {
switch selectedItem {
case .custom(item: .shop):
FlipView(NavigationItem.allCases) { element in
Text(element.description)
.style(.bodyStrong)
@ -57,13 +54,42 @@ struct ContentView: View {
.margin(50)
.verticalAlignment(.center)
.verticalAlignment(.center)
case .settings:
ScrollView {
VStack {
SettingsSection("General") {
SettingsControl("Button Control") {
Button("Control") { }
}
SettingsControl("Combo Box", description: "Selected item: \(comboBoxSelection)") {
ComboBox(NavigationItem.allCases, selection: $comboBoxSelection)
}
SettingsControl("Toggle Switch", description: "Current state: \(switchState ? "on" : "off")") {
ToggleSwitch(isOn: $switchState)
}
}
SettingsSection("About") {
SettingsControl("WinUI for Swift Demo", description: "main") {
HyperlinkButton("Website", url: "https://aparoksha.dev/")
}
}
}
.margin(50, .horizontal)
.margin(20, .vertical)
}
case let .custom(item: item):
Button("\(item)") {
selectedItem = .custom(item: .shop)
}
.horizontalAlignment(.center)
.verticalAlignment(.center)
}
}
.header(settingsLabel: "Settings") {
AppBarButton("Delete", icon: .systemIcon(unicode: "\u{E74D}")) {
print("Delete")
}
.visible(selectedItem != .shop)
.visible(selectedItem != .settings)
}
}

View File

@ -29,7 +29,7 @@ extension AnyView {
/// - edges: The affected edges.
/// - Returns: The view.
public func margin(_ value: Double, _ edges: [Edge] = .all) -> AnyView {
ModifierWrapper(view: self, margin: edges.set(value))
ModifierWrapper(view: Grid { self }, margin: edges.set(value))
}
/// Set the view's horizontal alignment.
@ -60,6 +60,13 @@ extension AnyView {
ModifierWrapper(view: self, visible: visible)
}
/// Set the view's opacity.
/// - Parameter opacity: The opacity.
/// - Returns: The view.
public func opacity(_ opacity: Double?) -> AnyView {
ModifierWrapper(view: self, opacity: opacity)
}
/// Make the view match the card style.
/// - Returns: The card.
public func card() -> AnyView {