@@ -160,6 +160,7 @@ struct ChatTabBar: View {
160160 }
161161
162162 @Environment ( \. chatTabPool) var chatTabPool
163+ @State var draggingTabId : String ?
163164
164165 var body : some View {
165166 WithViewStore (
@@ -186,6 +187,21 @@ struct ChatTabBar: View {
186187 tab. menu
187188 }
188189 . id ( info. id)
190+ . onDrag {
191+ print ( " dragging tab \( info. id) " )
192+ draggingTabId = info. id
193+ return NSItemProvider ( object: info. id as NSString )
194+ }
195+ . onDrop (
196+ of: [ . text] ,
197+ delegate: ChatTabBarDropDelegate (
198+ store: store,
199+ tabs: viewStore. state. tabInfo,
200+ itemId: info. id,
201+ draggingTabId: $draggingTabId
202+ )
203+ )
204+
189205 } else {
190206 EmptyView ( )
191207 }
@@ -264,6 +280,30 @@ struct ChatTabBar: View {
264280 }
265281}
266282
283+ struct ChatTabBarDropDelegate : DropDelegate {
284+ let store : StoreOf < ChatPanelFeature >
285+ let tabs : IdentifiedArray < String , ChatTabInfo >
286+ let itemId : String
287+ @Binding var draggingTabId : String ?
288+
289+ func dropUpdated( info: DropInfo ) -> DropProposal ? {
290+ return DropProposal ( operation: . move)
291+ }
292+
293+ func performDrop( info: DropInfo ) -> Bool {
294+ draggingTabId = nil
295+ return true
296+ }
297+
298+ func dropEntered( info: DropInfo ) {
299+ guard itemId != draggingTabId else { return }
300+ let from = tabs. firstIndex { $0. id == draggingTabId }
301+ let to = tabs. firstIndex { $0. id == itemId }
302+ guard let from, let to, from != to else { return }
303+ store. send ( . moveChatTab( from: from, to: to) )
304+ }
305+ }
306+
267307struct ChatTabBarButton < Content: View > : View {
268308 let store : StoreOf < ChatPanelFeature >
269309 let info : ChatTabInfo
@@ -273,33 +313,30 @@ struct ChatTabBarButton<Content: View>: View {
273313
274314 var body : some View {
275315 HStack ( spacing: 0 ) {
276- Button ( action: {
277- store. send ( . tabClicked( id: info. id) )
278- } ) {
279- content ( )
280- . font ( . callout)
281- . lineLimit ( 1 )
282- . frame ( maxWidth: 120 )
283- . padding ( . horizontal, 32 )
284- . contentShape ( Rectangle ( ) )
285- }
286- . buttonStyle ( PlainButtonStyle ( ) )
287-
288- . overlay ( alignment: . leading) {
289- Button ( action: {
290- store. send ( . closeTabButtonClicked( id: info. id) )
291- } ) {
292- Image ( systemName: " xmark " )
293- . foregroundColor ( . secondary)
316+ content ( )
317+ . font ( . callout)
318+ . lineLimit ( 1 )
319+ . frame ( maxWidth: 120 )
320+ . padding ( . horizontal, 32 )
321+ . contentShape ( Rectangle ( ) )
322+ . onTapGesture {
323+ store. send ( . tabClicked( id: info. id) )
294324 }
295- . buttonStyle ( . plain)
296- . padding ( 2 )
297- . padding ( . leading, 8 )
298- . opacity ( isHovered ? 1 : 0 )
299- }
300- . onHover { isHovered = $0 }
301- . animation ( . linear( duration: 0.1 ) , value: isHovered)
302- . animation ( . linear( duration: 0.1 ) , value: isSelected)
325+ . overlay ( alignment: . leading) {
326+ Button ( action: {
327+ store. send ( . closeTabButtonClicked( id: info. id) )
328+ } ) {
329+ Image ( systemName: " xmark " )
330+ . foregroundColor ( . secondary)
331+ }
332+ . buttonStyle ( . plain)
333+ . padding ( 2 )
334+ . padding ( . leading, 8 )
335+ . opacity ( isHovered ? 1 : 0 )
336+ }
337+ . onHover { isHovered = $0 }
338+ . animation ( . linear( duration: 0.1 ) , value: isHovered)
339+ . animation ( . linear( duration: 0.1 ) , value: isSelected)
303340
304341 Divider ( ) . padding ( . vertical, 6 )
305342 }
0 commit comments