@@ -16,6 +16,7 @@ import SwiftUI
1616
1717let sideRatio = 0.4
1818let shadowRadius : CGFloat = 2
19+ let cellWidth : CGFloat = 30
1920
2021enum BubblePosition {
2122 case left
@@ -25,16 +26,26 @@ enum BubblePosition {
2526
2627struct BubbleShape : Shape {
2728 let s : CGFloat
29+ let height : CGFloat
2830 let position : BubblePosition
31+ let labels : [ String ]
32+ let index : Int
2933
3034 func path( in rect: CGRect ) -> Path {
3135 var path = Path ( )
3236 let w = rect. width
3337 let h = rect. height
3438 let r = keyCornerRadius
3539 let c = r * 2
36- let left = position == . left ? 0 : ( position == . middle ? s : 2 * s)
37- let right = position == . left ? ( w - 2 * s) : ( position == . middle ? ( w - s) : w)
40+ let left =
41+ position == . left ? 0 : ( ( position == . middle ? s : 2 * s) + CGFloat( index) * cellWidth)
42+ let right =
43+ w
44+ - ( position == . right
45+ ? 0 : ( ( position == . middle ? s : 2 * s) + CGFloat( labels. count - 1 - index) * cellWidth) )
46+ let leftArc = position != . left && index > 0
47+ let rightArc = position != . right && index < labels. count - 1
48+ let middle = h - height - columnGap
3849
3950 path. move ( to: CGPoint ( x: 0 , y: c) )
4051 // top left corner
@@ -53,13 +64,32 @@ struct BubbleShape: Shape {
5364 startAngle: . degrees( 270 ) ,
5465 endAngle: . degrees( 360 ) ,
5566 clockwise: false )
56- // upper right
57- path. addLine ( to: CGPoint ( x: w, y: h * 0.4 ) )
58- // middle right
59- path. addCurve (
60- to: CGPoint ( x: right, y: h * 0.65 ) ,
61- control1: CGPoint ( x: w, y: h * 0.55 ) ,
62- control2: CGPoint ( x: right, y: h * 0.5 ) )
67+ if rightArc {
68+ // upper right
69+ path. addLine ( to: CGPoint ( x: w, y: middle - c) )
70+ path. addArc (
71+ center: CGPoint ( x: w - c, y: middle - c) ,
72+ radius: c,
73+ startAngle: . degrees( 0 ) ,
74+ endAngle: . degrees( 90 ) ,
75+ clockwise: false )
76+ // middle right
77+ path. addLine ( to: CGPoint ( x: right + columnGap + r, y: middle) )
78+ path. addArc (
79+ center: CGPoint ( x: right + columnGap + r, y: middle + columnGap + r) ,
80+ radius: columnGap + r,
81+ startAngle: . degrees( 270 ) ,
82+ endAngle: . degrees( 180 ) ,
83+ clockwise: true )
84+ } else {
85+ // upper right
86+ path. addLine ( to: CGPoint ( x: w, y: h * 0.4 ) )
87+ // middle right
88+ path. addCurve (
89+ to: CGPoint ( x: right, y: h * 0.65 ) ,
90+ control1: CGPoint ( x: w, y: h * 0.55 ) ,
91+ control2: CGPoint ( x: right, y: h * 0.5 ) )
92+ }
6393 // lower right
6494 path. addLine ( to: CGPoint ( x: right, y: h - r) )
6595 // bottom right corner
@@ -78,13 +108,32 @@ struct BubbleShape: Shape {
78108 startAngle: . degrees( 90 ) ,
79109 endAngle: . degrees( 180 ) ,
80110 clockwise: false )
81- // lower left
82- path. addLine ( to: CGPoint ( x: left, y: h * 0.65 ) )
83- // middle left
84- path. addCurve (
85- to: CGPoint ( x: 0 , y: h * 0.4 ) ,
86- control1: CGPoint ( x: left, y: h * 0.5 ) ,
87- control2: CGPoint ( x: 0 , y: h * 0.55 ) )
111+ if leftArc {
112+ // lower left
113+ path. addLine ( to: CGPoint ( x: left, y: middle + columnGap + r) )
114+ path. addArc (
115+ center: CGPoint ( x: left - columnGap - r, y: middle + columnGap + r) ,
116+ radius: columnGap + r,
117+ startAngle: . degrees( 360 ) ,
118+ endAngle: . degrees( 270 ) ,
119+ clockwise: true )
120+ // middle left
121+ path. addLine ( to: CGPoint ( x: c, y: middle) )
122+ path. addArc (
123+ center: CGPoint ( x: c, y: middle - c) ,
124+ radius: c,
125+ startAngle: . degrees( 90 ) ,
126+ endAngle: . degrees( 180 ) ,
127+ clockwise: false )
128+ } else {
129+ // lower left
130+ path. addLine ( to: CGPoint ( x: left, y: h * 0.65 ) )
131+ // middle left
132+ path. addCurve (
133+ to: CGPoint ( x: 0 , y: h * 0.4 ) ,
134+ control1: CGPoint ( x: left, y: h * 0.5 ) ,
135+ control2: CGPoint ( x: 0 , y: h * 0.55 ) )
136+ }
88137 // upper left
89138 path. closeSubpath ( )
90139
@@ -101,21 +150,47 @@ struct BubbleView: View {
101150 let background : Color
102151 let shadow : Color
103152 let label : String ?
153+ let labels : [ String ]
154+ let index : Int
155+ let highlight : Int
104156
105157 var body : some View {
106158 let h = 2 * height + 1.5 * rowGap - shadowRadius
107159 let s = sideRatio * width
108160 let position : BubblePosition =
109161 x - width / 2 - s < 0 ? . left : ( x + width / 2 + s > keyboardWidth ? . right : . middle)
110162 let offsetX = position == . left ? s : ( position == . middle ? 0 : - s)
111- BubbleShape ( s: sideRatio * width, position: position)
112- . fill ( background)
113- . shadow ( color: shadow, radius: shadowRadius)
114- . frame ( width: ( 1 + 2 * sideRatio) * width, height: h)
115- . overlay (
116- Text ( label ?? " " ) . font ( . system( size: h * 0.4 ) . weight ( . light) )
117- . offset ( y: - h / 4 )
118- )
119- . position ( x: x + offsetX, y: y - ( h - height) / 2 )
163+ if let label = label {
164+ BubbleShape ( s: s, height: height, position: position, labels: [ label] , index: 0 )
165+ . fill ( background)
166+ . shadow ( color: shadow, radius: shadowRadius)
167+ . frame ( width: ( 1 + 2 * sideRatio) * width, height: h)
168+ . overlay (
169+ Text ( label) . font ( . system( size: h * 0.4 ) . weight ( . light) )
170+ . offset ( y: - h / 4 )
171+ )
172+ . position ( x: x + offsetX, y: y - ( h - height) / 2 )
173+ } else {
174+ let offsetCell = ( CGFloat ( labels. count - 1 ) / 2 - CGFloat( index) ) * cellWidth
175+ BubbleShape ( s: s, height: height, position: position, labels: labels, index: index)
176+ . fill ( background)
177+ . shadow ( color: shadow, radius: shadowRadius)
178+ . frame (
179+ width: ( 1 + 2 * sideRatio) * width + CGFloat( labels. count - 1 ) * cellWidth, height: h
180+ )
181+ . overlay (
182+ HStack ( spacing: 0 ) {
183+ ForEach ( Array ( labels. enumerated ( ) ) , id: \. offset) { i, label in
184+ Text ( label) . font ( . system( size: h * 0.25 ) . weight ( . light) )
185+ . frame ( width: cellWidth)
186+ . condition ( i == highlight) {
187+ $0. foregroundColor ( . white) . background ( highlightBackground)
188+ }
189+ . cornerRadius ( keyCornerRadius)
190+ }
191+ } . offset ( y: - h / 4 )
192+ )
193+ . position ( x: x + offsetCell + offsetX, y: y - ( h - height) / 2 )
194+ }
120195 }
121196}
0 commit comments