Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ Example/ReferenceImages/**/*.png
.docs
gh-pages
.swift_pd_guess
.claude/commands/guess_filename.md
.claude/commands/guess_filename.md
/.augments
8 changes: 4 additions & 4 deletions Sources/OpenSwiftUI/Util/OpenSwiftUIGlue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,17 @@ final public class OpenSwiftUIGlue2: CoreGlue2 {
#endif
}

override public func configureEmptyEnvironment(_ environment: inout EnvironmentValues) {
environment.configureForPlatform(traitCollection: nil)
}

override public final func configureDefaultEnvironment(_: inout EnvironmentValues) {
#if os(iOS) || os(visionOS)
#else
// TODO
#endif
}

override public func configureEmptyEnvironment(_ environment: inout EnvironmentValues) {
environment.configureForPlatform(traitCollection: nil)
}

override public final func makeRootView(base: AnyView, rootFocusScope: Namespace.ID) -> AnyView {
AnyView(base.safeAreaInsets(.zero, next: nil))
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/Test/TestApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public struct _TestApp {

package static let defaultEnvironment: EnvironmentValues = {
var environment = EnvironmentValues()
CoreGlue2.shared.configureDefaultEnvironment(&environment)
CoreGlue2.shared.configureEmptyEnvironment(&environment)
// TODO: Font: "HelveticaNeue"
environment.displayScale = 2.0
environment.setTestSystemColorDefinition()
Expand Down
4 changes: 2 additions & 2 deletions Sources/OpenSwiftUICore/Util/CoreGlue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,11 @@ open class CoreGlue2: NSObject {
_openSwiftUIBaseClassAbstractMethod()
}

open func configureEmptyEnvironment(_: inout EnvironmentValues) {
open func configureDefaultEnvironment(_: inout EnvironmentValues) {
_openSwiftUIBaseClassAbstractMethod()
}

open func configureDefaultEnvironment(_: inout EnvironmentValues) {
open func configureEmptyEnvironment(_: inout EnvironmentValues) {
_openSwiftUIBaseClassAbstractMethod()
}

Expand Down
280 changes: 280 additions & 0 deletions Sources/OpenSwiftUICore/Util/CustomRecursiveStringConvertible.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
//
// CustomRecursiveStringConvertible.swift
// OpenSwiftUICore
//
// Status: Complete
// Audited for 6.5.4
// ID: 2DFA09903A864CB0F038E089ECDB7AF8 (SwiftUICore)

import Foundation

// MARK: - CustomRecursiveStringConvertible

package protocol CustomRecursiveStringConvertible {
var descriptionName: String { get }

var descriptionAttributes: [(name: String, value: String)] { get }

var defaultDescriptionAttributes: Set<DefaultDescriptionAttribute> { get }

var descriptionChildren: [any CustomRecursiveStringConvertible] { get }

var hideFromDescription: Bool { get }
}

extension CustomRecursiveStringConvertible {
package var defaultDescriptionAttributes: Set<DefaultDescriptionAttribute> {
DefaultDescriptionAttribute.all
}

package var descriptionChildren: [any CustomRecursiveStringConvertible] {
[]
}

package var hideFromDescription: Bool {
false
}
}

extension CustomRecursiveStringConvertible {
package var descriptionName: String {
recursiveDescriptionName(Self.self)
}

package var descriptionAttributes: [(name: String, value: String)] {
[]
}

package var recursiveDescription: String {
_recursiveDescription(indent: 0, rounded: false)
}

package var roundedRecursiveDescription: String {
_recursiveDescription(indent: 0, rounded: true)
}

package func _recursiveDescription(
indent: Int,
rounded: Bool
) -> String {
let indentString = repeatElement(" ", count: indent).joined()
var attributes = descriptionAttributes
if rounded {
attributes = attributes.roundedAttributes()
}
attributes.append(contentsOf: indent == 0 ? topLevelAttributes : [])
let sortedAttributes = attributes.sorted(by: \.name)
let attributeString = sortedAttributes.isEmpty ? "" : " " + sortedAttributes
.map {
let escapedName = $0.name
.components(separatedBy: .whitespacesAndNewlines)
.joined(separator: "_")
.escapeXML()
let escapedValue = $0.value.escapeXML()
return #"\#(escapedName)="\#(escapedValue)""#
}
.joined(separator: " ")
let escapedName = descriptionName
.components(separatedBy: .whitespacesAndNewlines)
.joined(separator: "_")
.escapeXML()
let mark = "\(indentString)<\(escapedName)\(attributeString)"
if descriptionChildren.isEmpty {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decision to emit a self-closing tag is based on descriptionChildren.isEmpty before filtering hideFromDescription; if all children are hidden you’ll currently emit an open/close pair with no body. Consider basing this on the set of visible children to avoid surprising output differences in that edge case.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

return "\(mark) />\n"
} else {
var result = "\(mark)>\n"
for child in descriptionChildren {
guard !child.hideFromDescription else { continue }
result.append(child._recursiveDescription(indent: indent &+ 1, rounded: rounded))
}
result.append("\(indentString)")
result.append("</\(escapedName)>\n")
return result
}
}

package var topLevelAttributes: [(name: String, value: String)] {
guard _TestApp.isIntending(to: .includeStatusBar),
let isHidden = CoreGlue2.shared.isStatusBarHidden()
else { return [] }
return [(name: "statusBar", value: isHidden ? "hidden" : "visible")]
}
}

// MARK: - BridgeStringConvertible

package protocol BridgeStringConvertible {
var bridgeDescriptionChildren: [any CustomRecursiveStringConvertible] { get }
}

extension BridgeStringConvertible {
package var bridgeDescriptionChildren: [any CustomRecursiveStringConvertible] { [] }
}

// MARK: - recursiveDescriptionName

package func recursiveDescriptionName(_ type: any Any.Type) -> String {
var name = "\(type)"
if name.first == "(" {
var substring = name.dropFirst()
if let spaceIndex = substring.firstIndex(of: " ") {
substring.removeSubrange(spaceIndex...)
}
name = String(substring)
}
if let angleIndex = name.firstIndex(of: "<") {
name = String(name[..<angleIndex])
}
return name
}

// MARK: - String + Extension

extension String {
package func tupleOfDoubles() -> [(label: String, value: Double)]? {
guard let first, first == "(",
let last, last == ")"
else { return nil }

func decomposeTuple() -> (labels: [String], values: [String]) {
let inner = dropFirst().dropLast()
let parts = inner.split(separator: ",", omittingEmptySubsequences: true)
var labels: [String] = []
var values: [String] = []
for part in parts {
if let colonIndex = part.firstIndex(of: ":") {
let label = String(part[..<colonIndex]).trimmingCharacters(in: .whitespaces)
let value = String(part[part.index(after: colonIndex)...]).trimmingCharacters(in: .whitespaces)
labels.append(label)
values.append(value)
} else {
labels.append("")
values.append(part.trimmingCharacters(in: .whitespaces))
}
}
return (labels: labels, values: values)
}

let (labels, values) = decomposeTuple()
var doubles: [Double] = []
for valueString in values {
guard let value = Double(valueString) else {
return nil
}
doubles.append(value)
}
guard labels.count == doubles.count else { return nil }
return zip(labels, doubles).map { (label: $0, value: $1) }
}

fileprivate func escapeXML() -> String {
var result = ""
result.reserveCapacity(count)
for char in self {
switch char {
case "\"": result.append("&quot;")
case "&": result.append("&amp;")
case "'": result.append("&apos;")
case "<": result.append("&lt;")
case ">": result.append("&gt;")
case "\n": result.append("\\n")
case "\r": result.append("\\r")
default: result.append(char)
}
}
return result
}
}

// MARK: - Sequence.roundedAttributes [?]

extension Sequence where Element == (name: String, value: String) {
package func roundedAttributes() -> [(name: String, value: String)] {
map { (name, value) in
if let doubleValue = Double(value) {
let rounded = round(doubleValue * 256.0) / 256.0
return (name: name, value: rounded.description)
} else if let tupleValues = value.tupleOfDoubles() {
let roundedTuple = tupleValues.map { (label: $0.label, value: round($0.value * 256.0) / 256.0) }
if roundedTuple.count == 4,
name.range(of: "color", options: .caseInsensitive) != nil
{
let floats = roundedTuple.map { Float($0.value) }
if let colorName = colorNameForColorComponents(floats[0], floats[1], floats[2], floats[3]) {
return (name: name, value: colorName)
}
}
let parts: [String] = roundedTuple.map { item in
if item.label.isEmpty {
return "\(item.value)"
} else {
return "\(item.label): \(item.value)"
}
}
return (name: name, value: "(" + parts.joined(separator: ", ") + ")")
} else {
return (name, value)
}
}
}
}

// MARK: - Color.Resolved.name

extension Color.Resolved {
package var name: String? {
@inline(__always)
func quantize(_ value: Float) -> Float {
round(value * 256.0) / 256.0
}
return colorNameForColorComponents(
quantize(linearRed),
quantize(linearGreen),
quantize(linearBlue),
quantize(opacity)
)
}
}

private func colorNameForColorComponents(_ r: Float, _ g: Float, _ b: Float, _ a: Float) -> String? {
if r == 0 && g == 0 && b == 0 {
if a == 0 {
return "clear"
} else if a == 1 {
return "black"
}
}
if r == 1 && g == 1 && b == 1 && a == 1 {
return "white"
} else if r == 8.0 / 256.0 && g == 8.0 / 256.0 && b == 8.0 / 256.0 && a == 1 {
return "gray"
} else if r == 1 && g == 0 && b == 0 && a == 1 {
return "red"
} else if r == 1 && g == 11.0 / 256.0 && b == 11.0 / 256.0 && a == 1 {
return "system-red"
} else if r == 1 && g == 15.0 / 256.0 && b == 11.0 / 256.0 && a == 1 {
return "system-red-dark"
} else if r == 0 && g == 1 && b == 0 && a == 1 {
return "green"
} else if r == 0 && g == 0 && b == 1 && a == 1 {
return "blue"
} else if r == 1 && g == 1 && b == 0 && a == 1 {
return "yellow"
} else if r == 55.0 / 256.0 && g == 0 && b == 55.0 / 256.0 && a == 1 {
return "purple"
} else if r == 1 && g == 55.0 / 256.0 && b == 0 && a == 1 {
return "orange"
} else if r == 0 && g == 1 && b == 1 && a == 1 {
return "teal"
} else if r == 55.0 / 256.0 && g == 55.0 / 256.0 && b == 1 && a == 1 {
return "indigo"
} else if r == 1 && g == 0 && b == 55.0 / 256.0 && a == 1 {
return "pink"
} else if r == 12.0 / 256.0 && g == 12.0 / 256.0 && b == 14.0 / 256.0 && a == 64.0 / 256.0 {
return "brown"
} else if r == 12.0 / 256.0 && g == 12.0 / 256.0 && b == 14.0 / 256.0 && a == 76.0 / 256.0 {
return "placeholder-text"
} else {
return nil
}
}
Loading
Loading