Support smaller primitive views

This commit is contained in:
Carson Katri 2022-07-06 20:17:28 -04:00
parent 0186b9dcf5
commit 312a8a7925
14 changed files with 141 additions and 46 deletions

View File

@ -19,8 +19,8 @@
/// 1. `View.makeMountedView`
/// 2. `MountedHostView.update` when reconciling
///
protocol EnvironmentReader {
mutating func setContent(from values: EnvironmentValues)
public protocol _EnvironmentReader {
mutating func _setContent(from values: EnvironmentValues)
}
@propertyWrapper
@ -37,7 +37,7 @@ public struct Environment<Value>: DynamicProperty {
self.keyPath = keyPath
}
mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
content = .value(values[keyPath: keyPath])
}
@ -52,4 +52,4 @@ public struct Environment<Value>: DynamicProperty {
}
}
extension Environment: EnvironmentReader {}
extension Environment: _EnvironmentReader {}

View File

@ -40,7 +40,7 @@ public struct EnvironmentObject<ObjectType>: DynamicProperty
var _store: ObjectType?
var _seed: Int = 0
mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
_store = values[ObjectIdentifier(ObjectType.self)]
}
@ -65,7 +65,7 @@ public struct EnvironmentObject<ObjectType>: DynamicProperty
public init() {}
}
extension EnvironmentObject: ObservedProperty, EnvironmentReader {}
extension EnvironmentObject: ObservedProperty, _EnvironmentReader {}
extension ObservableObject {
static var environmentStore: WritableKeyPath<EnvironmentValues, Self?> {

View File

@ -22,7 +22,7 @@ public protocol EnvironmentalModifier: ViewModifier {
static var _requiresMainThread: Bool { get }
}
private struct EnvironmentalModifierResolver<M>: ViewModifier, EnvironmentReader
private struct EnvironmentalModifierResolver<M>: ViewModifier, _EnvironmentReader
where M: EnvironmentalModifier
{
let modifier: M
@ -32,7 +32,7 @@ private struct EnvironmentalModifierResolver<M>: ViewModifier, EnvironmentReader
content.modifier(resolved)
}
mutating func setContent(from values: EnvironmentValues) {
mutating func _setContent(from values: EnvironmentValues) {
resolved = modifier.resolve(in: values)
}
}

View File

@ -309,8 +309,8 @@ public extension FiberReconciler {
storage.getter = { box.value }
value = storage
// Read from the environment.
} else if var environmentReader = value as? EnvironmentReader {
environmentReader.setContent(from: environment)
} else if var environmentReader = value as? _EnvironmentReader {
environmentReader._setContent(from: environment)
value = environmentReader
}
// Subscribe to observable properties.
@ -322,8 +322,8 @@ public extension FiberReconciler {
}
property.set(value: value, on: &content)
}
if var environmentReader = content as? EnvironmentReader {
environmentReader.setContent(from: environment)
if var environmentReader = content as? _EnvironmentReader {
environmentReader._setContent(from: environment)
content = environmentReader
}
}

View File

@ -127,7 +127,8 @@ extension EnvironmentValues {
}
}
var measureText: (Text, ProposedViewSize, EnvironmentValues) -> CGSize {
@_spi(TokamakCore)
public var measureText: (Text, ProposedViewSize, EnvironmentValues) -> CGSize {
get { self[MeasureTextKey.self] }
set { self[MeasureTextKey.self] = newValue }
}

View File

@ -37,9 +37,9 @@ extension ModifiedContent: ModifierContainer {
var environmentModifier: _EnvironmentModifier? { modifier as? _EnvironmentModifier }
}
extension ModifiedContent: EnvironmentReader where Modifier: EnvironmentReader {
mutating func setContent(from values: EnvironmentValues) {
modifier.setContent(from: values)
extension ModifiedContent: _EnvironmentReader where Modifier: _EnvironmentReader {
public mutating func _setContent(from values: EnvironmentValues) {
modifier._setContent(from: values)
}
}

View File

@ -38,7 +38,7 @@ public struct _BackgroundLayout<Content, Background>: _PrimitiveView
}
}
public struct _BackgroundModifier<Background>: ViewModifier, EnvironmentReader
public struct _BackgroundModifier<Background>: ViewModifier, _EnvironmentReader
where Background: View
{
public var environment: EnvironmentValues!
@ -58,7 +58,7 @@ public struct _BackgroundModifier<Background>: ViewModifier, EnvironmentReader
)
}
mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
environment = values
}
}
@ -90,7 +90,7 @@ public extension View {
}
@frozen
public struct _BackgroundShapeModifier<Style, Bounds>: ViewModifier, EnvironmentReader
public struct _BackgroundShapeModifier<Style, Bounds>: ViewModifier, _EnvironmentReader
where Style: ShapeStyle, Bounds: Shape
{
public var environment: EnvironmentValues!
@ -111,7 +111,7 @@ public struct _BackgroundShapeModifier<Style, Bounds>: ViewModifier, Environment
.background(shape.fill(style, style: fillStyle))
}
public mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
environment = values
}
}
@ -149,7 +149,7 @@ public struct _OverlayLayout<Content, Overlay>: _PrimitiveView
}
}
public struct _OverlayModifier<Overlay>: ViewModifier, EnvironmentReader
public struct _OverlayModifier<Overlay>: ViewModifier, _EnvironmentReader
where Overlay: View
{
public var environment: EnvironmentValues!
@ -169,7 +169,7 @@ public struct _OverlayModifier<Overlay>: ViewModifier, EnvironmentReader
)
}
mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
environment = values
}
}

View File

@ -246,16 +246,16 @@ extension EnvironmentValues {
for dynamicProp in info.properties.filter({ $0.type is DynamicProperty.Type }) {
guard let propInfo = typeInfo(of: dynamicProp.type) else { return }
var propWrapper = dynamicProp.get(from: element) as! DynamicProperty
for prop in propInfo.properties.filter({ $0.type is EnvironmentReader.Type }) {
var wrapper = prop.get(from: propWrapper) as! EnvironmentReader
wrapper.setContent(from: self)
for prop in propInfo.properties.filter({ $0.type is _EnvironmentReader.Type }) {
var wrapper = prop.get(from: propWrapper) as! _EnvironmentReader
wrapper._setContent(from: self)
prop.set(value: wrapper, on: &propWrapper)
}
dynamicProp.set(value: propWrapper, on: &element)
}
for prop in info.properties.filter({ $0.type is EnvironmentReader.Type }) {
var wrapper = prop.get(from: element) as! EnvironmentReader
wrapper.setContent(from: self)
for prop in info.properties.filter({ $0.type is _EnvironmentReader.Type }) {
var wrapper = prop.get(from: element) as! _EnvironmentReader
wrapper._setContent(from: self)
prop.set(value: wrapper, on: &element)
}
// swiftlint:enable force_cast

View File

@ -17,7 +17,7 @@
import Foundation
public struct ContainerRelativeShape: Shape, EnvironmentReader {
public struct ContainerRelativeShape: Shape, _EnvironmentReader {
var containerShape: (CGRect, GeometryProxy) -> Path? = { _, _ in nil }
public func path(in rect: CGRect) -> Path {
@ -26,7 +26,7 @@ public struct ContainerRelativeShape: Shape, EnvironmentReader {
public init() {}
public mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
containerShape = values._containerShape
}
}

View File

@ -58,7 +58,7 @@ public extension View {
@frozen
public struct _BackgroundStyleModifier<Style>: ViewModifier, _EnvironmentModifier,
EnvironmentReader
_EnvironmentReader
where Style: ShapeStyle
{
public var environment: EnvironmentValues!
@ -70,7 +70,8 @@ public struct _BackgroundStyleModifier<Style>: ViewModifier, _EnvironmentModifie
}
public typealias Body = Never
public mutating func setContent(from values: EnvironmentValues) {
public mutating func _setContent(from values: EnvironmentValues) {
environment = values
}

View File

@ -31,12 +31,15 @@ import Foundation
/// .bold()
/// .italic()
/// .underline(true, color: .red)
public struct Text: _PrimitiveView, Equatable {
public struct Text: _PrimitiveView, Equatable, _EnvironmentReader {
let storage: _Storage
let modifiers: [_Modifier]
@Environment(\.self)
var environment
public var environment: EnvironmentValues = .init()
public mutating func _setContent(from values: EnvironmentValues) {
environment = values
}
public static func == (lhs: Text, rhs: Text) -> Bool {
lhs.storage == rhs.storage

View File

@ -83,6 +83,7 @@ public struct _TextFieldProxy<Label: View> {
public var onCommit: () -> () { subject.onCommit }
public var onEditingChanged: (Bool) -> () { subject.onEditingChanged }
public var textFieldStyle: _AnyTextFieldStyle { subject.environment.textFieldStyle }
public var environment: EnvironmentValues { subject.environment }
public var foregroundColor: AnyColorBox.ResolvedValue? {
guard let foregroundColor = subject.environment.foregroundColor else {
return nil

View File

@ -17,6 +17,8 @@
import JavaScriptKit
import TokamakCore
@_spi(TokamakStaticHTML)
import TokamakStaticHTML
public typealias HTML = TokamakStaticHTML.HTML
@ -32,6 +34,7 @@ public struct DynamicHTML<Content>: View, AnyDynamicHTML {
public let attributes: [HTMLAttribute: String]
public let listeners: [String: Listener]
let content: Content
let visitContent: (ViewVisitor) -> ()
fileprivate let cachedInnerHTML: String?
@ -43,6 +46,10 @@ public struct DynamicHTML<Content>: View, AnyDynamicHTML {
public var body: Never {
neverBody("HTML")
}
public func _visitChildren<V>(_ visitor: V) where V: ViewVisitor {
visitContent(visitor)
}
}
public extension DynamicHTML where Content: StringProtocol {
@ -57,6 +64,7 @@ public extension DynamicHTML where Content: StringProtocol {
self.listeners = listeners
self.content = content
cachedInnerHTML = String(content)
visitContent = { _ in }
}
}
@ -65,13 +73,14 @@ extension DynamicHTML: ParentView where Content: View {
_ tag: String,
_ attributes: [HTMLAttribute: String] = [:],
listeners: [String: Listener] = [:],
@ViewBuilder content: () -> Content
@ViewBuilder content: @escaping () -> Content
) {
self.tag = tag
self.attributes = attributes
self.listeners = listeners
self.content = content()
cachedInnerHTML = nil
visitContent = { $0.visit(content()) }
}
@_spi(TokamakCore)
@ -89,3 +98,10 @@ public extension DynamicHTML where Content == EmptyView {
self = DynamicHTML(tag, attributes, listeners: listeners) { EmptyView() }
}
}
@_spi(TokamakStaticHTML)
extension DynamicHTML: HTMLConvertible {
public func attributes(useDynamicLayout: Bool) -> [HTMLAttribute: String] {
attributes
}
}

View File

@ -15,7 +15,12 @@
// Created by Jed Fox on 06/28/2020.
//
import Foundation
@_spi(TokamakCore)
import TokamakCore
@_spi(TokamakStaticHTML)
import TokamakStaticHTML
extension TextField: DOMPrimitive where Label == Text {
@ -39,19 +44,18 @@ extension TextField: DOMPrimitive where Label == Text {
}
}
var renderedBody: AnyView {
var attributes: [HTMLAttribute: String] {
let proxy = _TextFieldProxy(self)
return AnyView(DynamicHTML("input", [
return [
"type": proxy.textFieldStyle is RoundedBorderTextFieldStyle ? "search" : "text",
.value: proxy.textBinding.wrappedValue,
"placeholder": _TextProxy(proxy.label).rawText,
"style": """
\(css(for: proxy.textFieldStyle)) \
\(proxy.foregroundColor.map { "color: \($0.cssValue);" } ?? "")
""",
"class": className(for: proxy.textFieldStyle),
], listeners: [
]
}
var listeners: [String: Listener] {
let proxy = _TextFieldProxy(self)
return [
"focus": { _ in proxy.onEditingChanged(true) },
"blur": { _ in proxy.onEditingChanged(false) },
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
@ -60,6 +64,75 @@ extension TextField: DOMPrimitive where Label == Text {
proxy.textBinding.wrappedValue = newValue
}
},
]))
]
}
var renderedBody: AnyView {
let proxy = _TextFieldProxy(self)
return AnyView(DynamicHTML(
"input",
attributes.merging([
"style": """
\(css(for: proxy.textFieldStyle)) \
\(proxy.foregroundColor.map { "color: \($0.cssValue);" } ?? "")
""",
"class": className(for: proxy.textFieldStyle),
], uniquingKeysWith: { $1 }),
listeners: listeners
))
}
}
@_spi(TokamakStaticHTML)
extension TextField: HTMLConvertible, DOMNodeConvertible, Layout, _AnyLayout, Animatable
where Label == Text
{
public typealias AnimatableData = EmptyAnimatableData
public var tag: String { "input" }
public func attributes(useDynamicLayout: Bool) -> [HTMLAttribute: String] {
if useDynamicLayout {
return attributes
.merging(["style": "padding: 0; border: none;"], uniquingKeysWith: { $0 + $1 })
} else {
return attributes
}
}
public func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) -> CGSize {
let proxy = _TextFieldProxy(self)
var content = Text(proxy.textBinding.wrappedValue)
content._setContent(from: proxy.environment)
let contentSize = proxy.environment.measureText(
content,
proposal,
proxy.environment
)
var label = proxy.label
label._setContent(from: proxy.environment)
let labelSize = proxy.environment.measureText(
label,
proposal,
proxy.environment
)
let proposal = proposal.replacingUnspecifiedDimensions()
return .init(
width: max(proposal.width, max(contentSize.width, labelSize.width)),
height: max(contentSize.height, labelSize.height)
)
}
public func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) {
for subview in subviews {
subview.place(at: bounds.origin, proposal: proposal)
}
}
}