77
88import SwiftUI
99import VirtualCore
10+ import BuddyFoundation
1011
11- struct NetworkConfigurationView : View {
12-
13- @Binding var hardware : VBMacDevice
14-
15- @State private var previousMACAddress : String ?
16-
17- init ( hardware: Binding < VBMacDevice > ) {
18- self . _hardware = hardware
19- self . _previousMACAddress = . init( wrappedValue: hardware. wrappedValue. networkDevices. first? . macAddress)
12+ enum NetworkDeviceSelection : Identifiable , Hashable {
13+ var id : String {
14+ switch self {
15+ case . disabled: " DISABLED "
16+ case . NAT: " NAT "
17+ case . bridge( let interfaceID) : " BRIDGE_ \( interfaceID) "
18+ }
2019 }
2120
22- private var kind : Binding < VBNetworkDevice . Kind ? > {
23- . init {
24- hardware. networkDevices. first? . kind
25- } set: { newValue in
26- if let newValue {
27- if hardware. networkDevices. isEmpty {
28- hardware. networkDevices = [ . default]
29- }
30- hardware. networkDevices [ 0 ] . kind = newValue
31- if let previousMACAddress {
32- hardware. networkDevices [ 0 ] . macAddress = previousMACAddress
33- }
34- } else {
35- hardware. networkDevices. removeAll ( )
36- }
37- }
21+ case disabled
22+ case NAT
23+ case bridge( _ interface: VBNetworkDeviceInterface . ID )
24+ }
3825
39- }
26+ struct NetworkConfigurationView : View {
4027
28+ @Binding var hardware : VBMacDevice
29+
4130 var body : some View {
4231 VStack ( alignment: . leading, spacing: 16 ) {
43- typePicker
44-
45- if let kind = kind. wrappedValue {
46- switch kind {
47- case . NAT:
48- natSettings
49- case . bridge:
50- bridgeSettings
51- }
32+ NetworkDevicePicker ( hardware: $hardware)
33+
34+ if hardware. networkDevices. isEmpty {
35+ Text ( " This virtual machine will have no internet or local network access. " )
36+ . foregroundColor ( . secondary)
5237 } else {
53- Text ( " This virtual machine won't have any access to the network. " )
54- . frame ( maxWidth: . infinity)
55- . foregroundColor ( . yellow)
56- . multilineTextAlignment ( . center)
57- }
58- }
59- }
60-
61- @ViewBuilder
62- private var typePicker : some View {
63- Picker ( " Type " , selection: kind) {
64- Text ( " None " )
65- . tag ( Optional< VBNetworkDevice . Kind> . none)
66-
67- ForEach ( VBNetworkDevice . Kind. allCases) { kind in
68- Text ( kind. name)
69- . tag ( Optional< VBNetworkDevice . Kind> . some( kind) )
38+ macAddressField
7039 }
7140 }
72- . pickerStyle ( . segmented)
73- . labelsHidden ( )
74- . help ( " The type of network device " )
7541 }
7642
7743 @ViewBuilder
7844 private var macAddressField : some View {
7945 PropertyControl ( " MAC Address " ) {
80- EphemeralTextField ( $hardware. networkDevices [ 0 ] . macAddress , alignment: . leading) { addr in
46+ EphemeralTextField ( $hardware. networkMACAddress , alignment: . leading) { addr in
8147 Text ( addr)
8248 . textCase ( . uppercase)
8349 } editableContent: { value in
@@ -88,77 +54,109 @@ struct NetworkConfigurationView: View {
8854 return VBNetworkDevice . validateMAC ( value)
8955 }
9056 }
91- . onChange ( of: hardware. networkDevices [ 0 ] . macAddress) { newValue in
92- previousMACAddress = newValue
57+ }
58+ }
59+
60+ extension VBMacDevice {
61+ var networkDeviceSelection : NetworkDeviceSelection {
62+ get {
63+ if let device = networkDevices. first {
64+ switch device. kind {
65+ case . NAT: . NAT
66+ case . bridge: . bridge( device. id)
67+ }
68+ } else {
69+ . disabled
70+ }
71+ }
72+ set {
73+ let restoreMACAddress = networkMACAddress
74+
75+ switch newValue {
76+ case . disabled:
77+ networkDevices. removeAll ( )
78+ case . NAT:
79+ networkDevices = [ . default. withMACAddress ( restoreMACAddress) ]
80+ case . bridge( let id) :
81+ networkDevices = [ . init( id: id, name: id, kind: . bridge) . withMACAddress ( restoreMACAddress) ]
82+ }
83+ }
84+ }
85+
86+ var networkMACAddress : String {
87+ get {
88+ switch networkDeviceSelection {
89+ case . disabled: " "
90+ case . NAT, . bridge: networkDevices. first? . macAddress ?? " "
91+ }
92+ }
93+ set {
94+ switch networkDeviceSelection {
95+ case . disabled: break
96+ case . NAT, . bridge:
97+ guard !networkDevices. isEmpty else { return }
98+ networkDevices [ 0 ] . macAddress = newValue
99+ }
93100 }
94101 }
95-
96- @State private var bridgeInterfaces : [ VBNetworkDeviceBridgeInterface ] = [ ]
97-
98- @ViewBuilder
99- private var natSettings : some View {
100- macAddressField
102+ }
103+
104+ extension VBNetworkDevice {
105+ func withMACAddress( _ address: String ) -> VBNetworkDevice {
106+ guard !address. isEmpty else { return self }
107+ var mself = self
108+ mself. macAddress = address
109+ return mself
101110 }
102-
103- @ViewBuilder
104- private var bridgeSettings : some View {
105- if VBNetworkDevice . appSupportsBridgedNetworking {
106- PropertyControl ( " Interface " ) {
107- HStack {
108- Picker ( " Interface " , selection: $hardware. networkDevices [ 0 ] . id) {
109- if bridgeInterfaces. isEmpty {
110- Text ( " No Interfaces Available " )
111- . tag ( hardware. networkDevices [ 0 ] . id)
112- } else {
113- ForEach ( bridgeInterfaces) { iface in
114- Text ( iface. name)
115- . tag ( iface. id)
116- }
111+ }
112+
113+ struct NetworkDevicePicker : View {
114+ @Binding var hardware : VBMacDevice
115+
116+ @State private var selectedOption : VBNetworkDeviceInterface ?
117+
118+ @State private var interfaces : [ VBNetworkDeviceInterface ] = [ . automatic]
119+
120+ var body : some View {
121+ PropertyControl ( " Interface " ) {
122+ HStack {
123+ Picker ( " Interface " , selection: $hardware. networkDeviceSelection) {
124+ Text ( " Disabled " ) . tag ( NetworkDeviceSelection . disabled)
125+
126+ Text ( " NAT " ) . tag ( NetworkDeviceSelection . NAT)
127+
128+ Section ( " Bridge " ) {
129+ ForEach ( interfaces) { interface in
130+ Text ( interface. name)
131+ . tag ( NetworkDeviceSelection . bridge ( interface. id) )
117132 }
118133 }
119- . disabled ( bridgeInterfaces. isEmpty)
120-
121- Spacer ( )
122-
123- Button {
124- bridgeInterfaces = VBNetworkDevice . bridgeInterfaces
125- } label: {
126- Image ( systemName: " arrow.clockwise " )
127- }
128- . buttonStyle ( . plain)
129- . help ( " Reload interfaces " )
130134 }
131- . onAppear {
132- bridgeInterfaces = VBNetworkDevice . bridgeInterfaces
135+ . labelsHidden ( )
136+
137+ Spacer ( )
138+
139+ Button {
140+ refresh ( )
141+ } label: {
142+ Image ( systemName: " arrow.clockwise " )
133143 }
144+ . buttonStyle ( . plain)
145+ . help ( " Reload interfaces " )
134146 }
135-
136- macAddressField
137- } else {
138- Text ( VBNetworkDevice . bridgeUnsupportedMessage)
139- . foregroundColor ( . red)
147+ . task { refresh ( ) }
140148 }
141149 }
150+
151+ private func refresh( ) {
152+ interfaces = [ . automatic] + VBNetworkDevice. bridgeInterfaces
153+ }
142154}
143155
144156#if DEBUG
145-
146- struct NetworkConfigurationView_Previews : PreviewProvider {
147- static var previews : some View {
148- _ConfigurationSectionPreview ( . networkPreviewNAT) {
149- NetworkConfigurationView ( hardware: $0. hardware)
150- }
151- . previewDisplayName ( " NAT " )
152-
153- _ConfigurationSectionPreview ( . networkPreviewBridge) {
154- NetworkConfigurationView ( hardware: $0. hardware)
155- }
156- . previewDisplayName ( " Bridge " )
157-
158- _ConfigurationSectionPreview ( . networkPreviewNone) {
159- NetworkConfigurationView ( hardware: $0. hardware)
160- }
161- . previewDisplayName ( " None " )
157+ #Preview {
158+ _ConfigurationSectionPreview ( . networkPreviewNAT) {
159+ NetworkConfigurationView ( hardware: $0. hardware)
162160 }
163161}
164162#endif
0 commit comments