From b632ea3374595753a1fe71fabbea550b24fef369 Mon Sep 17 00:00:00 2001 From: Roman Geraskin Date: Wed, 20 Aug 2025 23:32:01 +0300 Subject: [PATCH] feat(menu): add layouts submenu --- Amethyst/AppDelegate.swift | 87 ++++++++++++++++++++++++++++++++ Amethyst/Base.lproj/MainMenu.xib | 7 +++ 2 files changed, 94 insertions(+) diff --git a/Amethyst/AppDelegate.swift b/Amethyst/AppDelegate.swift index 083c6d11..32ffabaa 100644 --- a/Amethyst/AppDelegate.swift +++ b/Amethyst/AppDelegate.swift @@ -28,6 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet var versionMenuItem: NSMenuItem? @IBOutlet var startAtLoginMenuItem: NSMenuItem? @IBOutlet var toggleGlobalTilingMenuItem: NSMenuItem? + @IBOutlet var layoutsMenuItem: NSMenuItem? private var isFirstLaunch = true @@ -71,6 +72,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { hotKeyManager = HotKeyManager(userConfiguration: UserConfiguration.shared) hotKeyManager?.setUpWithWindowManager(windowManager!, configuration: UserConfiguration.shared, appDelegate: self) + + // Populate layouts menu now that windowManager is initialized + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.populateLayoutsMenu() + } } override func awakeFromNib() { @@ -93,6 +99,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { toggleGlobalTilingMenuItem?.title = "Disable" startAtLoginMenuItem?.state = (LoginServiceKit.isExistLoginItems(at: Bundle.main.bundlePath) ? .on : .off) + + // Set up layouts menu delegate to refresh when opened + layoutsMenuItem?.submenu?.delegate = self + + populateLayoutsMenu() } func applicationDidBecomeActive(_ notification: Notification) { @@ -173,6 +184,66 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } } + + private func populateLayoutsMenu() { + guard let layoutsMenuItem = layoutsMenuItem, + let submenu = layoutsMenuItem.submenu else { + return + } + + // Clear existing items + submenu.removeAllItems() + + // Get enabled layout keys from user configuration + let enabledLayoutKeys = UserConfiguration.shared.layoutKeys() + + // Get all available layouts with their display names + let availableLayouts = LayoutType.availableLayoutStrings() + + // Get current layout + let focusedScreenManager = windowManager?.focusedScreenManager() + var currentLayoutKey = focusedScreenManager?.currentLayout?.layoutKey + + // If no focused screen manager, fallback to the first screen manager + if focusedScreenManager == nil, let firstScreenManager = windowManager?.screenManager(at: 0) { + currentLayoutKey = firstScreenManager.currentLayout?.layoutKey + } + + // Filter to only enabled layouts and add menu items + for layoutKey in enabledLayoutKeys { + guard let layoutInfo = availableLayouts.first(where: { $0.key == layoutKey }) else { + continue + } + + let menuItem = NSMenuItem(title: layoutInfo.name, action: #selector(selectLayout(_:)), keyEquivalent: "") + menuItem.target = self + menuItem.representedObject = layoutKey + + // Mark current layout with checkmark + let isCurrentLayout = layoutKey == currentLayoutKey + menuItem.state = isCurrentLayout ? .on : .off + + submenu.addItem(menuItem) + } + + // If no layouts are enabled, show a disabled message + if enabledLayoutKeys.isEmpty { + let noLayoutsItem = NSMenuItem(title: "No layouts enabled", action: nil, keyEquivalent: "") + noLayoutsItem.isEnabled = false + submenu.addItem(noLayoutsItem) + } + } + + @IBAction func selectLayout(_ sender: NSMenuItem) { + guard let layoutKey = sender.representedObject as? String, + let windowManager = windowManager, + let screenManager = windowManager.focusedScreenManager() else { + return + } + + screenManager.selectLayout(layoutKey) + // Menu will be refreshed automatically when next opened via NSMenuDelegate + } } extension AppDelegate: NSWindowDelegate { @@ -181,6 +252,22 @@ extension AppDelegate: NSWindowDelegate { } } +extension AppDelegate: NSMenuDelegate { + func menuNeedsUpdate(_ menu: NSMenu) { + // Refresh layouts menu when it's about to be shown + if menu == layoutsMenuItem?.submenu { + populateLayoutsMenu() + } + } + + func menuWillOpen(_ menu: NSMenu) { + // Also refresh when menu is about to open + if menu == layoutsMenuItem?.submenu { + populateLayoutsMenu() + } + } +} + extension AppDelegate: UserConfigurationDelegate { func configurationGlobalTilingDidChange(_ userConfiguration: UserConfiguration) { var statusItemImage: NSImage? diff --git a/Amethyst/Base.lproj/MainMenu.xib b/Amethyst/Base.lproj/MainMenu.xib index be820371..8807054f 100644 --- a/Amethyst/Base.lproj/MainMenu.xib +++ b/Amethyst/Base.lproj/MainMenu.xib @@ -26,6 +26,12 @@ + + + + + + @@ -73,6 +79,7 @@ +