diff options
author | Andrej Mihajlov <and@mullvad.net> | 2020-12-22 16:40:28 +0100 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-12-23 14:40:54 +0100 |
commit | e54a5d9a13dc3eff90db6c19c738a93c92429174 (patch) | |
tree | 0a9fcc995d3a338681efb9c2e5fe22a48abc9572 | |
parent | UI: Avoid force unwrap when checking for errors (diff) | |
download | wireguard-apple-e54a5d9a13dc3eff90db6c19c738a93c92429174.tar.xz wireguard-apple-e54a5d9a13dc3eff90db6c19c738a93c92429174.zip |
UI: macOS: Group more than 10 tunnels into submenu
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
-rw-r--r-- | Sources/WireGuardApp/Base.lproj/Localizable.strings | 1 | ||||
-rw-r--r-- | Sources/WireGuardApp/UI/macOS/StatusMenu.swift | 169 |
2 files changed, 138 insertions, 32 deletions
diff --git a/Sources/WireGuardApp/Base.lproj/Localizable.strings b/Sources/WireGuardApp/Base.lproj/Localizable.strings index 4ef9540..a2cc455 100644 --- a/Sources/WireGuardApp/Base.lproj/Localizable.strings +++ b/Sources/WireGuardApp/Base.lproj/Localizable.strings @@ -297,6 +297,7 @@ "macMenuNetworksNone" = "Networks: None"; "macMenuTitle" = "WireGuard"; +"macTunnelsMenuTitle" = "Tunnels"; "macMenuManageTunnels" = "Manage Tunnels"; "macMenuImportTunnels" = "Import Tunnel(s) from File…"; "macMenuAddEmptyTunnel" = "Add Empty Tunnel…"; diff --git a/Sources/WireGuardApp/UI/macOS/StatusMenu.swift b/Sources/WireGuardApp/UI/macOS/StatusMenu.swift index 5630fdc..d30ed88 100644 --- a/Sources/WireGuardApp/UI/macOS/StatusMenu.swift +++ b/Sources/WireGuardApp/UI/macOS/StatusMenu.swift @@ -14,8 +14,14 @@ class StatusMenu: NSMenu { var statusMenuItem: NSMenuItem? var networksMenuItem: NSMenuItem? var deactivateMenuItem: NSMenuItem? - var firstTunnelMenuItemIndex = 0 - var numberOfTunnelMenuItems = 0 + + private let tunnelsBreakdownMenu = NSMenu() + private let tunnelsMenuItem = NSMenuItem(title: tr("macTunnelsMenuTitle"), action: nil, keyEquivalent: "") + private let tunnelsMenuSeparatorItem = NSMenuItem.separator() + + private var firstTunnelMenuItemIndex = 0 + private var numberOfTunnelMenuItems = 0 + private var tunnelsPresentationStyle = StatusMenuTunnelsPresentationStyle.inline var currentTunnel: TunnelContainer? { didSet { @@ -26,16 +32,20 @@ class StatusMenu: NSMenu { init(tunnelsManager: TunnelsManager) { self.tunnelsManager = tunnelsManager + super.init(title: tr("macMenuTitle")) addStatusMenuItems() addItem(NSMenuItem.separator()) + tunnelsMenuItem.submenu = tunnelsBreakdownMenu + addItem(tunnelsMenuItem) + firstTunnelMenuItemIndex = numberOfItems - let isAdded = addTunnelMenuItems() - if isAdded { - addItem(NSMenuItem.separator()) - } + populateInitialTunnelMenuItems() + + addItem(tunnelsMenuSeparatorItem) + addTunnelManagementItems() addItem(NSMenuItem.separator()) addApplicationItems() @@ -108,15 +118,6 @@ class StatusMenu: NSMenu { deactivateMenuItem.isHidden = tunnel.status != .active } - func addTunnelMenuItems() -> Bool { - let numberOfTunnels = tunnelsManager.numberOfTunnels() - for index in 0 ..< tunnelsManager.numberOfTunnels() { - let tunnel = tunnelsManager.tunnel(at: index) - insertTunnelMenuItem(for: tunnel, at: numberOfTunnelMenuItems) - } - return numberOfTunnels > 0 - } - func addTunnelManagementItems() { let manageItem = NSMenuItem(title: tr("macMenuManageTunnels"), action: #selector(manageTunnelsClicked), keyEquivalent: "") manageItem.target = self @@ -166,34 +167,121 @@ class StatusMenu: NSMenu { extension StatusMenu { func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) { + let nextNumberOfTunnels = numberOfTunnelMenuItems + 1 + + guard !reparentTunnelMenuItems(nextNumberOfTunnels: nextNumberOfTunnels) else { + return + } + + let menuItem = makeTunnelItem(tunnel: tunnel) + switch tunnelsPresentationStyle { + case .submenu: + tunnelsBreakdownMenu.insertItem(menuItem, at: tunnelIndex) + case .inline: + insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex) + } + + numberOfTunnelMenuItems = nextNumberOfTunnels + updateTunnelsMenuItemVisibility() + } + + func removeTunnelMenuItem(at tunnelIndex: Int) { + let nextNumberOfTunnels = numberOfTunnelMenuItems - 1 + + guard !reparentTunnelMenuItems(nextNumberOfTunnels: nextNumberOfTunnels) else { + return + } + + switch tunnelsPresentationStyle { + case .submenu: + tunnelsBreakdownMenu.removeItem(at: tunnelIndex) + case .inline: + removeItem(at: firstTunnelMenuItemIndex + tunnelIndex) + } + + numberOfTunnelMenuItems = nextNumberOfTunnels + updateTunnelsMenuItemVisibility() + } + + func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) { + let tunnel = tunnelsManager.tunnel(at: newTunnelIndex) + let menuItem = makeTunnelItem(tunnel: tunnel) + + switch tunnelsPresentationStyle { + case .submenu: + tunnelsBreakdownMenu.removeItem(at: oldTunnelIndex) + tunnelsBreakdownMenu.insertItem(menuItem, at: newTunnelIndex) + case .inline: + removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex) + insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex) + } + } + + private func makeTunnelItem(tunnel: TunnelContainer) -> TunnelMenuItem { let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:))) menuItem.target = self menuItem.isHidden = !tunnel.isTunnelAvailableToUser - insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex) - if numberOfTunnelMenuItems == 0 { - insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1) + return menuItem + } + + private func populateInitialTunnelMenuItems() { + let numberOfTunnels = tunnelsManager.numberOfTunnels() + let initialStyle = tunnelsPresentationStyle.preferredPresentationStyle(numberOfTunnels: numberOfTunnels) + + tunnelsPresentationStyle = initialStyle + switch initialStyle { + case .inline: + numberOfTunnelMenuItems = addTunnelMenuItems(into: self, at: firstTunnelMenuItemIndex) + case .submenu: + numberOfTunnelMenuItems = addTunnelMenuItems(into: tunnelsBreakdownMenu, at: 0) } - numberOfTunnelMenuItems += 1 + + updateTunnelsMenuItemVisibility() } - func removeTunnelMenuItem(at tunnelIndex: Int) { - removeItem(at: firstTunnelMenuItemIndex + tunnelIndex) - numberOfTunnelMenuItems -= 1 - if numberOfTunnelMenuItems == 0 { - if let firstItem = item(at: firstTunnelMenuItemIndex), firstItem.isSeparatorItem { - removeItem(at: firstTunnelMenuItemIndex) + private func reparentTunnelMenuItems(nextNumberOfTunnels: Int) -> Bool { + let nextStyle = tunnelsPresentationStyle.preferredPresentationStyle(numberOfTunnels: nextNumberOfTunnels) + + switch (tunnelsPresentationStyle, nextStyle) { + case (.inline, .submenu): + tunnelsPresentationStyle = nextStyle + for index in (0..<numberOfTunnelMenuItems).reversed() { + removeItem(at: firstTunnelMenuItemIndex + index) } + numberOfTunnelMenuItems = addTunnelMenuItems(into: tunnelsBreakdownMenu, at: 0) + updateTunnelsMenuItemVisibility() + return true + + case (.submenu, .inline): + tunnelsPresentationStyle = nextStyle + tunnelsBreakdownMenu.removeAllItems() + numberOfTunnelMenuItems = addTunnelMenuItems(into: self, at: firstTunnelMenuItemIndex) + updateTunnelsMenuItemVisibility() + return true + + case (.submenu, .submenu), (.inline, .inline): + return false } } - func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) { - guard let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex) as? TunnelMenuItem else { return } - let oldMenuItemTunnel = oldMenuItem.tunnel - removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex) - let menuItem = TunnelMenuItem(tunnel: oldMenuItemTunnel, action: #selector(tunnelClicked(sender:))) - menuItem.target = self - insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex) + private func addTunnelMenuItems(into menu: NSMenu, at startIndex: Int) -> Int { + let numberOfTunnels = tunnelsManager.numberOfTunnels() + for tunnelIndex in 0..<numberOfTunnels { + let tunnel = tunnelsManager.tunnel(at: tunnelIndex) + let menuItem = makeTunnelItem(tunnel: tunnel) + menu.insertItem(menuItem, at: startIndex + tunnelIndex) + } + return numberOfTunnels + } + private func updateTunnelsMenuItemVisibility() { + switch tunnelsPresentationStyle { + case .inline: + tunnelsMenuItem.isHidden = true + case .submenu: + tunnelsMenuItem.isHidden = false + } + tunnelsMenuSeparatorItem.isHidden = numberOfTunnelMenuItems == 0 } } @@ -232,3 +320,20 @@ class TunnelMenuItem: NSMenuItem { state = shouldShowCheckmark ? .on : .off } } + +private enum StatusMenuTunnelsPresentationStyle { + case inline + case submenu + + func preferredPresentationStyle(numberOfTunnels: Int) -> StatusMenuTunnelsPresentationStyle { + let maxInlineTunnels = 10 + + if case .inline = self, numberOfTunnels > maxInlineTunnels { + return .submenu + } else if case .submenu = self, numberOfTunnels <= maxInlineTunnels { + return .inline + } else { + return self + } + } +} |