Tokamak/Sources/TokamakCore/Views/Containers/Section.swift

111 lines
3.4 KiB
Swift

// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Carson Katri on 7/5/20.
//
protocol SectionView {
func listRow(_ style: ListStyle) -> AnyView
}
public struct Section<Parent, Content, Footer> {
let header: Parent
let footer: Footer
let content: Content
}
extension Section: View, SectionView where Parent: View, Content: View, Footer: View {
public init(header: Parent, footer: Footer, @ViewBuilder content: () -> Content) {
self.header = header
self.footer = footer
self.content = content()
}
@ViewBuilder
public var body: TupleView<(Parent, Content, Footer)> {
header
content
footer
}
func sectionContent(_ style: ListStyle) -> AnyView {
if let contentContainer = content as? ParentView {
let rows = _ListRow.buildItems(contentContainer.children) { view, isLast in
_ListRow.listRow(view, style, isLast: isLast)
}
if let style = style as? ListStyleDeferredToRenderer {
return style.sectionBody(rows)
} else {
return AnyView(rows)
}
} else if let style = style as? ListStyleDeferredToRenderer {
return style.sectionBody(content)
} else {
return AnyView(content)
}
}
func footerView(_ style: ListStyle) -> AnyView {
if footer is EmptyView {
return AnyView(EmptyView())
} else if let style = style as? ListStyleDeferredToRenderer {
return style.sectionFooter(footer)
} else {
return AnyView(_ListRow.listRow(footer, style, isLast: true))
}
}
func headerView(_ style: ListStyle) -> AnyView {
if header is EmptyView {
return AnyView(EmptyView())
} else if let style = style as? ListStyleDeferredToRenderer {
return style.sectionHeader(header)
} else {
return AnyView(header)
}
}
func listRow(_ style: ListStyle) -> AnyView {
AnyView(VStack(alignment: .leading) {
headerView(style)
sectionContent(style)
footerView(style)
}
.frame(minWidth: 0, maxWidth: .infinity))
}
}
extension Section where Parent == EmptyView, Content: View, Footer: View {
public init(footer: Footer, @ViewBuilder content: () -> Content) {
self.init(header: EmptyView(), footer: footer, content: content)
}
}
extension Section where Parent: View, Content: View, Footer == EmptyView {
public init(header: Parent, @ViewBuilder content: () -> Content) {
self.init(header: header, footer: EmptyView(), content: content)
}
}
extension Section where Parent == EmptyView, Content: View, Footer == EmptyView {
public init(@ViewBuilder content: () -> Content) {
self.init(header: EmptyView(), footer: EmptyView(), content: content)
}
}
// FIXME: Implement IsCollapsibleTraitKey (and TraitKeys)
// extension Section where Parent : View, Content : View, Footer : View {
// public func collapsible(_ collapsible: Bool) -> some View
// }