// // Swiftfin is subject to the terms of the Mozilla Public // License, v2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at https://mozilla.org/MPL/2.0/. // // Copyright (c) 2025 Jellyfin & Jellyfin Contributors // import SwiftUI struct BulletedList: View { private var content: () -> Content private var bullet: (Int) -> any View var body: some View { _VariadicView.Tree(BulletedListLayout(bullet: bullet)) { content() } } } extension BulletedList { init(@ViewBuilder _ content: @escaping () -> Content) { self.init( content: content, bullet: { _ in ZStack { Text(" ") Circle() .frame(width: 8) .padding(.trailing, 5) } } ) } func bullet(@ViewBuilder _ content: @escaping (Int) -> any View) -> Self { copy(modifying: \.bullet, with: content) } } extension BulletedList { struct BulletedListLayout: _VariadicView_UnaryViewRoot { var bullet: (Int) -> any View @ViewBuilder func body(children: _VariadicView.Children) -> some View { VStack(alignment: .leading, spacing: 0) { ForEach(Array(zip(children.indices, children)), id: \.0) { child in BulletedListItem( bullet: AnyView(bullet(child.0)), child: child.1 ) } } } } struct BulletedListItem: View { @State private var bulletSize: CGSize = .zero @State private var childSize: CGSize = .zero let bullet: Bullet let child: BulletContent private var _bullet: some View { bullet .trackingSize($bulletSize) } // TODO: this can cause clipping issues with text since // with .offset, find fix var body: some View { ZStack { child .trackingSize($childSize) .overlay(alignment: .topLeading) { _bullet .offset(x: -bulletSize.width) } } } } }