@@ -52,56 +52,36 @@ struct ChatWindowView: View {
5252struct ChatTitleBar : View {
5353 let store : StoreOf < ChatPanelFeature >
5454 @State var isHovering = false
55- @Environment ( \. controlActiveState) var controlActiveState
5655
5756 var body : some View {
58- HStack ( spacing: 4 ) {
59- Button ( action: {
60- store. send ( . hideButtonClicked)
61- } ) {
62- Circle ( )
63- . fill (
64- controlActiveState == . key
65- ? Color ( nsColor: . systemOrange)
66- : Color ( nsColor: . disabledControlTextColor)
67- )
68- . frame ( width: 10 , height: 10 )
69- . shadow ( radius: 0.5 )
70- . overlay {
71- if isHovering {
72- Image ( systemName: " minus " )
73- . resizable ( )
74- . foregroundStyle ( . black. opacity ( 0.7 ) )
75- . font ( Font . title. weight ( . heavy) )
76- . frame ( width: 5 , height: 1 )
77- }
78- }
57+ HStack ( spacing: 6 ) {
58+ TrafficLightButton (
59+ isHovering: isHovering,
60+ isActive: true ,
61+ color: Color ( nsColor: . systemOrange) ,
62+ action: {
63+ store. send ( . hideButtonClicked)
64+ }
65+ ) {
66+ Image ( systemName: " minus " )
67+ . foregroundStyle ( . black. opacity ( 0.5 ) )
68+ . font ( Font . system ( size: 8 ) . weight ( . heavy) )
7969 }
8070 . keyboardShortcut ( " m " , modifiers: [ . command] )
8171
8272 WithViewStore ( store, observe: { $0. chatPanelInASeparateWindow } ) { viewStore in
83- Button ( action: {
84- store. send ( . toggleChatPanelDetachedButtonClicked)
85- } ) {
86- Circle ( )
87- . fill (
88- controlActiveState == . key && viewStore. state
89- ? Color ( nsColor: . systemCyan)
90- : Color ( nsColor: . disabledControlTextColor)
91- )
92- . frame ( width: 10 , height: 10 )
93- . shadow ( radius: 0.5 )
94- . disabled ( !viewStore. state)
95- . overlay {
96- if isHovering {
97- Image ( systemName: " pin " )
98- . resizable ( )
99- . foregroundStyle ( . black. opacity ( 0.7 ) )
100- . font ( Font . title. weight ( . heavy) )
101- . frame ( width: 4 , height: 6 )
102- . transformEffect ( . init( translationX: 0 , y: 0.5 ) )
103- }
104- }
73+ TrafficLightButton (
74+ isHovering: isHovering,
75+ isActive: viewStore. state,
76+ color: Color ( nsColor: . systemCyan) ,
77+ action: {
78+ store. send ( . toggleChatPanelDetachedButtonClicked)
79+ }
80+ ) {
81+ Image ( systemName: " pin.fill " )
82+ . foregroundStyle ( . black. opacity ( 0.5 ) )
83+ . font ( Font . system ( size: 6 ) . weight ( . black) )
84+ . transformEffect ( . init( translationX: 0 , y: 0.5 ) )
10585 }
10686 }
10787
@@ -131,11 +111,47 @@ struct ChatTitleBar: View {
131111 . padding ( . horizontal, 6 )
132112 . padding ( . top, 1 )
133113 . frame ( maxWidth: . infinity)
134- . frame ( height: 16 )
114+ . frame ( height: Style . chatWindowTitleBarHeight )
135115 . onHover ( perform: { hovering in
136116 isHovering = hovering
137117 } )
138118 }
119+
120+ struct TrafficLightButton < Icon: View > : View {
121+ let isHovering : Bool
122+ let isActive : Bool
123+ let color : Color
124+ let action : ( ) -> Void
125+ let icon : ( ) -> Icon
126+
127+ @Environment ( \. controlActiveState) var controlActiveState
128+
129+ var body : some View {
130+ Button ( action: {
131+ action ( )
132+ } ) {
133+ Circle ( )
134+ . fill (
135+ controlActiveState == . key && isActive
136+ ? color
137+ : Color ( nsColor: . separatorColor)
138+ )
139+ . frame (
140+ width: Style . trafficLightButtonSize,
141+ height: Style . trafficLightButtonSize
142+ )
143+ . overlay {
144+ Circle ( ) . stroke ( lineWidth: 0.5 ) . foregroundColor ( . black. opacity ( 0.2 ) )
145+ }
146+ . overlay {
147+ if isHovering {
148+ icon ( )
149+ }
150+ }
151+ }
152+ . focusable ( false )
153+ }
154+ }
139155}
140156
141157private extension View {
@@ -315,29 +331,29 @@ struct ChatTabBarButton<Content: View, Icon: View>: View {
315331 icon ( ) . foregroundColor ( . secondary)
316332 content ( )
317333 }
318- . font ( . callout)
319- . lineLimit ( 1 )
320- . frame ( maxWidth: 120 )
321- . padding ( . horizontal, 28 )
322- . contentShape ( Rectangle ( ) )
323- . onTapGesture {
324- store. send ( . tabClicked( id: info. id) )
325- }
326- . overlay ( alignment: . leading) {
327- Button ( action: {
328- store. send ( . closeTabButtonClicked( id: info. id) )
329- } ) {
330- Image ( systemName: " xmark " )
331- . foregroundColor ( . secondary)
332- }
333- . buttonStyle ( . plain)
334- . padding ( 2 )
335- . padding ( . leading, 8 )
336- . opacity ( isHovered ? 1 : 0 )
334+ . font ( . callout)
335+ . lineLimit ( 1 )
336+ . frame ( maxWidth: 120 )
337+ . padding ( . horizontal, 28 )
338+ . contentShape ( Rectangle ( ) )
339+ . onTapGesture {
340+ store. send ( . tabClicked( id: info. id) )
341+ }
342+ . overlay ( alignment: . leading) {
343+ Button ( action: {
344+ store. send ( . closeTabButtonClicked( id: info. id) )
345+ } ) {
346+ Image ( systemName: " xmark " )
347+ . foregroundColor ( . secondary)
337348 }
338- . onHover { isHovered = $0 }
339- . animation ( . linear( duration: 0.1 ) , value: isHovered)
340- . animation ( . linear( duration: 0.1 ) , value: isSelected)
349+ . buttonStyle ( . plain)
350+ . padding ( 2 )
351+ . padding ( . leading, 8 )
352+ . opacity ( isHovered ? 1 : 0 )
353+ }
354+ . onHover { isHovered = $0 }
355+ . animation ( . linear( duration: 0.1 ) , value: isHovered)
356+ . animation ( . linear( duration: 0.1 ) , value: isSelected)
341357
342358 Divider ( ) . padding ( . vertical, 6 )
343359 }
0 commit comments