Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Examples/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Examples/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ let package = Package(
.executableTarget(
name: "HoverExample",
dependencies: exampleDependencies
),
.executableTarget(
),
.executableTarget(
name: "ForEachExample",
dependencies: exampleDependencies
),
),
.executableTarget(
name: "AdvancedCustomizationExample",
dependencies: exampleDependencies,
Expand Down
46 changes: 38 additions & 8 deletions Examples/Sources/ControlsExample/ControlsApp.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DefaultBackend
import Foundation
import SwiftCrossUI

#if canImport(SwiftBundlerRuntime)
Expand All @@ -16,10 +17,14 @@ struct ControlsApp: App {
@State var text = ""
@State var flavor: String? = nil
@State var enabled = true
@State var date = Date()
@State var datePickerStyle: DatePickerStyle? = .automatic
@State var menuToggleState = false
@State var progressViewSize: Int = 10
@State var isProgressViewResizable = true

@Environment(\.supportedDatePickerStyles) var supportedDatePickerStyles: [DatePickerStyle]

var body: some Scene {
WindowGroup("ControlsApp") {
#hotReloadable {
Expand Down Expand Up @@ -94,15 +99,40 @@ struct ControlsApp: App {
.frame(width: progressViewSize, height: progressViewSize)
}

VStack {
Text("Drop down")
HStack {
Text("Flavor: ")
Picker(
of: ["Vanilla", "Chocolate", "Strawberry"], selection: $flavor)
#if !canImport(Gtk3Backend)
VStack {
Text("Drop down")
HStack {
Text("Flavor: ")
Picker(
of: ["Vanilla", "Chocolate", "Strawberry"],
selection: $flavor
)
}
Text("You chose: \(flavor ?? "Nothing yet!")")
}
Text("You chose: \(flavor ?? "Nothing yet!")")
}

#if !os(tvOS)
VStack {
Text("Selected date: \(date)")

HStack {
Text("Date picker style: ")
Picker(
of: supportedDatePickerStyles,
selection: $datePickerStyle
)
}

DatePicker(selection: $date) {}
.datePickerStyle(datePickerStyle ?? .automatic)

Button("Reset date to now") {
date = Date()
}
}
#endif
#endif
}.padding().disabled(!enabled)

Toggle(enabled ? "Disable all" : "Enable all", isOn: $enabled)
Expand Down
12 changes: 6 additions & 6 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ let package = Package(
),
.package(
url: "https://github.com/stackotter/swift-windowsappsdk",
revision: "ba6f0ec377b70d8be835d253102ff665a0e47d99"
revision: "f1c50892f10c0f7f635d3c7a3d728fd634ad001a"
),
.package(
url: "https://github.com/stackotter/swift-windowsfoundation",
revision: "4ad57d20553514bcb23724bdae9121569b19f172"
),
.package(
url: "https://github.com/stackotter/swift-winui",
revision: "1695ee3ea2b7a249f6504c7f1759e7ec7a38eb86"
revision: "42c47f4e4129c8b5a5d9912f05e1168c924ac180"
),
.package(
url: "https://github.com/apple/swift-collections.git",
Expand Down
99 changes: 99 additions & 0 deletions Sources/AppKitBackend/AppKitBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public final class AppKitBackend: AppBackend {
public let menuImplementationStyle = MenuImplementationStyle.dynamicPopover
public let canRevealFiles = true
public let deviceClass = DeviceClass.desktop
public let supportedDatePickerStyles: [DatePickerStyle] = [.automatic, .graphical, .compact]

public var scrollBarWidth: Int {
// We assume that all scrollers have their controlSize set to `.regular` by default.
Expand Down Expand Up @@ -368,6 +369,14 @@ public final class AppKitBackend: AppBackend {
// Self.scrollBarWidth has changed
action()
}

NotificationCenter.default.addObserver(
forName: .NSSystemTimeZoneDidChange,
object: nil,
queue: .main
) { _ in
action()
}
}

public func computeWindowEnvironment(
Expand Down Expand Up @@ -1835,6 +1844,80 @@ public final class AppKitBackend: AppBackend {
parent.endSheet(sheet)
parent.nestedSheet = nil
}

public func createDatePicker() -> NSView {
let datePicker = CustomDatePicker()
datePicker.delegate = datePicker.strongDelegate
return datePicker
}

// Depending on the calendar, era is either necessary or must be omitted. Making the wrong
// choice for the current calendar means the cursor position is reset after every keystroke. I
// know of no simple way to tell whether NSDatePicker requires or forbids eras for a given
// calendar, so in lieu of that I have hardcoded the calendar identifiers.
private let calendarsRequiringEra: Set<Calendar.Identifier> = [
.buddhist, .coptic, .ethiopicAmeteAlem, .ethiopicAmeteMihret, .indian, .islamic,
.islamicCivil, .islamicTabular, .islamicUmmAlQura, .japanese, .persian, .republicOfChina,
]

public func updateDatePicker(
_ datePicker: NSView,
environment: EnvironmentValues,
date: Date,
range: ClosedRange<Date>,
components: DatePickerComponents,
onChange: @escaping (Date) -> Void
) {
let datePicker = datePicker as! CustomDatePicker

datePicker.isEnabled = environment.isEnabled
datePicker.textColor = environment.suggestedForegroundColor.nsColor

// If the time zone is set to autoupdatingCurrent, then the cursor position is reset after
// every keystroke. Thanks Apple
datePicker.timeZone =
environment.timeZone == .autoupdatingCurrent ? .current : environment.timeZone

// A couple properties cause infinite update loops if we assign to them on every update, so
// check their values first.
if datePicker.calendar != environment.calendar {
datePicker.calendar = environment.calendar
}

if datePicker.dateValue != date {
datePicker.dateValue = date
}

var elementFlags: NSDatePicker.ElementFlags = []
if components.contains(.date) {
elementFlags.insert(.yearMonthDay)
if calendarsRequiringEra.contains(environment.calendar.identifier) {
elementFlags.insert(.era)
}
}
if components.contains(.hourMinuteAndSecond) {
elementFlags.insert(.hourMinuteSecond)
} else if components.contains(.hourAndMinute) {
elementFlags.insert(.hourMinute)
}

if datePicker.datePickerElements != elementFlags {
datePicker.datePickerElements = elementFlags
}

datePicker.strongDelegate.onChange = onChange

datePicker.minDate = range.lowerBound
datePicker.maxDate = range.upperBound

datePicker.datePickerStyle =
switch environment.datePickerStyle {
case .automatic, .compact:
.textFieldAndStepper
case .graphical:
.clockAndCalendar
}
}
}

public final class NSCustomSheet: NSCustomWindow, NSWindowDelegate {
Expand Down Expand Up @@ -2367,3 +2450,19 @@ final class CustomWKNavigationDelegate: NSObject, WKNavigationDelegate {
onNavigate?(url)
}
}

final class CustomDatePicker: NSDatePicker {
var strongDelegate = CustomDatePickerDelegate()
}

final class CustomDatePickerDelegate: NSObject, NSDatePickerCellDelegate {
var onChange: ((Date) -> Void)?

func datePickerCell(
_: NSDatePickerCell,
validateProposedDateValue proposedDateValue: AutoreleasingUnsafeMutablePointer<NSDate>,
timeInterval _: UnsafeMutablePointer<TimeInterval>?
) {
onChange?(proposedDateValue.pointee as Date)
}
}
1 change: 1 addition & 0 deletions Sources/DummyBackend/DummyBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ public final class DummyBackend: AppBackend {
public var menuImplementationStyle = MenuImplementationStyle.dynamicPopover
public var deviceClass = DeviceClass.desktop
public var canRevealFiles = false
public var supportedDatePickerStyles: [DatePickerStyle] = []

public var incomingURLHandler: ((URL) -> Void)?

Expand Down
Loading
Loading