From 02fcdceceaa28c84cfd2b1d62ce6d312687c8578 Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Wed, 27 May 2026 13:01:12 +0800 Subject: [PATCH] shortcut library for widget picker --- VoidLink/Input/InputAccessoryBar.swift | 4 +- VoidLink/Input/OnScreenWidgetView.swift | 9 +- VoidLink/Input/PencilHandler.swift | 8 +- VoidLink/Localization/Localizable.xcstrings | 986 ++++++++++++++++++ .../LayoutOnScreenControlsViewController.m | 4 +- ...LayoutOnScreenControlsViewController.swift | 4 +- .../ToolboxViewController.swift | 4 +- VoidLinkPreview/WidgetPickerView.swift | 137 ++- 8 files changed, 1122 insertions(+), 34 deletions(-) diff --git a/VoidLink/Input/InputAccessoryBar.swift b/VoidLink/Input/InputAccessoryBar.swift index 12fb61de..aa53f47b 100644 --- a/VoidLink/Input/InputAccessoryBar.swift +++ b/VoidLink/Input/InputAccessoryBar.swift @@ -70,7 +70,7 @@ private struct InputAccessoryBarButtonRecord { } var displayTitle: String { - buttonLabel.isEmpty ? cmdString : buttonLabel + buttonLabel.isEmpty ? cmdString : LocalizationHelper.localizedString(forKey: buttonLabel) } } @@ -817,7 +817,7 @@ final class InputAccessoryBar: UIView, UIScrollViewDelegate, WidgetPickerViewCon pickerViewController.keyboardPickerMode = .shortcutPicker pickerViewController.shortcutPickerNeedAlias = true pickerViewController.shortcutPickerNeedButtonMode = true - pickerViewController.tabIdentifiers = ["keyboard"] + pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"] pickerViewController.initialTabIdentifier = "keyboard" parentViewController.view.endEditing(true) diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index 09c5769d..9066415f 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -1669,11 +1669,12 @@ import ObjectiveC.runtime if buttonMode == .movable, moveableButtonLongPressed() {return} if leaveNonSkillButtonAlone {return} self.buttonDownVisualEffect() - var autoReleaseComboButtons = CommandManager.shared.extractAutoReleaseButtonStrings(from: self.cmdString)! - autoReleaseComboButtons.removeAll{ - Set(CommandManager.pencilProButtonCmds).contains($0) + if var autoReleaseComboButtons = CommandManager.shared.extractAutoReleaseButtonStrings(from: self.cmdString) { + autoReleaseComboButtons.removeAll{ + Set(CommandManager.pencilProButtonCmds).contains($0) + } + CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: autoReleaseComboButtons) // send multi-key command } - CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: autoReleaseComboButtons) // send multi-key command DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.buttonUpVisualEffect() } diff --git a/VoidLink/Input/PencilHandler.swift b/VoidLink/Input/PencilHandler.swift index b036f9c5..d907bb6a 100644 --- a/VoidLink/Input/PencilHandler.swift +++ b/VoidLink/Input/PencilHandler.swift @@ -433,7 +433,7 @@ import UIKit let pickerViewController = WidgetPickerViewController() pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate) pickerViewController.keyboardPickerMode = .shortcutPicker - pickerViewController.tabIdentifiers = ["keyboard"] + pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"] pickerViewController.initialTabIdentifier = "keyboard" pickerViewController.shortcutIdentifier = "eraser" pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "Select eraser shortcut keys") @@ -483,7 +483,7 @@ import UIKit let pickerViewController = WidgetPickerViewController() pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate) pickerViewController.keyboardPickerMode = .shortcutPicker - pickerViewController.tabIdentifiers = ["keyboard"] + pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"] pickerViewController.initialTabIdentifier = "keyboard" pickerViewController.shortcutIdentifier = "brush" pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "Select brush shortcut keys") @@ -543,7 +543,7 @@ import UIKit let pickerViewController = WidgetPickerViewController() pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate) pickerViewController.keyboardPickerMode = .shortcutPicker - pickerViewController.tabIdentifiers = ["keyboard"] + pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"] pickerViewController.initialTabIdentifier = "keyboard" pickerViewController.shortcutIdentifier = "squeezePress" pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "squeezePressShortcutPickerTip") @@ -594,7 +594,7 @@ import UIKit let pickerViewController = WidgetPickerViewController() pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate) pickerViewController.keyboardPickerMode = .shortcutPicker - pickerViewController.tabIdentifiers = ["keyboard"] + pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"] pickerViewController.initialTabIdentifier = "keyboard" pickerViewController.shortcutIdentifier = "squeezeRelease" pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "squeezeReleaseShortcutPickerTip") diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index d746a26a..b686a850 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -2171,6 +2171,963 @@ } } }, + "=closeTab" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Close Tab" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭标签页" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉分頁" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉分頁" + } + } + } + }, + "=closeWindow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Close" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭窗口" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉視窗" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉視窗" + } + } + } + }, + "=copy" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "複製" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "複製" + } + } + } + }, + "=cut" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cut" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "剪切" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "剪下" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "剪下" + } + } + } + }, + "=desktop" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desktop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示桌面" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示桌面" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示桌面" + } + } + } + }, + "=fileExplorer" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "File Explorer" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文件管理" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "檔案管理" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "檔案管理" + } + } + } + }, + "=find" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Find" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查找" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "尋找" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "尋找" + } + } + } + }, + "=hdr" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HDR" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "HDR" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "HDR" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "HDR" + } + } + } + }, + "=ime" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "IME" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输入法" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "輸入法" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "輸入法" + } + } + } + }, + "=lang" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lang" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "中英切换" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "中英切換" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "中英切換" + } + } + } + }, + "=lock" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lock" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "锁定电脑" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖定電腦" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖定電腦" + } + } + } + }, + "=maximize" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maximize" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最大化" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "最大化" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "最大化" + } + } + } + }, + "=minimize" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Minimize" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最小化" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "最小化" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "最小化" + } + } + } + }, + "=newFolder" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New Folder" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新文件夹" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "新資料夾" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "新資料夾" + } + } + } + }, + "=newTab" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New Tab" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建标签页" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "新分頁" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "新分頁" + } + } + } + }, + "=nvidiaRec" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nvidia Rec" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "N卡录屏" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "N卡錄影" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "N卡錄影" + } + } + } + }, + "=nvidiaShot" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nvidia Shot" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "N卡截图" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "N卡截圖" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "N卡截圖" + } + } + } + }, + "=paste" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Paste" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "粘贴" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "貼上" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "貼上" + } + } + } + }, + "=redo" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redo" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重做" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "重做" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "重做" + } + } + } + }, + "=run" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Run" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "執行" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "執行" + } + } + } + }, + "=save" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存" + } + } + } + }, + "=search" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜尋" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜尋" + } + } + } + }, + "=selectAll" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select All" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全选" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "全選" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "全選" + } + } + } + }, + "=settingsShortcut" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开设置" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "開啟設定" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "開啟設定" + } + } + } + }, + "=snip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snip" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "截图" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "截圖" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "截圖" + } + } + } + }, + "=snapLeft" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snap Left" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "左侧分屏" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "左側分割" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "左側分割" + } + } + } + }, + "=snapRight" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snap Right" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "右侧分屏" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "右側分割" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "右側分割" + } + } + } + }, + "=switchWindow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Switch Window" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换窗口" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "切換視窗" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "切換視窗" + } + } + } + }, + "=taskManager" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Task Manager" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "任务管理器" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "工作管理員" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "工作管理員" + } + } + } + }, + "=taskView" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Task View" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "任务视图" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "工作檢視" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "工作檢視" + } + } + } + }, + "=undo" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Undo" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "撤销" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "撤銷" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "撤銷" + } + } + } + }, + "=zoomIn" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoom In" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "放大" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "放大" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "放大" + } + } + } + }, + "=zoomOut" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoom Out" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "缩小" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "縮小" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "縮小" + } + } + } + }, "🎮 overlay" : { "extractionState" : "manual", "localizations" : { @@ -12629,6 +13586,35 @@ } } }, + "ShortcutsTab" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shortcuts" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷键仓库" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷鍵倉庫" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷鍵倉庫" + } + } + } + }, "Show App" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index f48fdb7f..3d472b50 100644 --- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -663,7 +663,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) { if (@available(iOS 13.0, *)) { WidgetPickerViewController *pickerViewController = [[WidgetPickerViewController alloc] init]; pickerViewController.delegate = self; - pickerViewController.tabIdentifiers = @[@"gamepad", @"keyboard", @"functional"]; + pickerViewController.tabIdentifiers = @[@"gamepad", @"keyboard", @"functional", @"shortcuts"]; pickerViewController.initialTabIdentifier = @"gamepad"; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:pickerViewController]; @@ -800,7 +800,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) { if (@available(iOS 13.0, *)) { WidgetPickerViewController *pickerViewController = [[WidgetPickerViewController alloc] init]; pickerViewController.delegate = self; - pickerViewController.tabIdentifiers = @[@"gamepad", @"keyboard", @"functional"]; + pickerViewController.tabIdentifiers = @[@"gamepad", @"keyboard", @"functional", @"shortcuts"]; pickerViewController.initialTabIdentifier = @"gamepad"; pickerViewController.isEditMode = true; pickerViewController.initialCmdString = self->selectedWidgetView.cmdString; diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift index 5ed2e87f..d6f36314 100644 --- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift +++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift @@ -1468,7 +1468,7 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg if #available(iOS 13.0, *) { let pickerViewController = WidgetPickerViewController() pickerViewController.delegate = self - pickerViewController.tabIdentifiers = ["gamepad", "keyboard", "functional"] + pickerViewController.tabIdentifiers = ["gamepad", "keyboard", "functional","shortcuts"] pickerViewController.initialTabIdentifier = "gamepad" pickerViewController.presentOverFullScreen(from: self) return @@ -1503,7 +1503,7 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg if #available(iOS 13.0, *) { let pickerViewController = WidgetPickerViewController() pickerViewController.delegate = self - pickerViewController.tabIdentifiers = ["gamepad", "keyboard", "functional"] + pickerViewController.tabIdentifiers = ["gamepad", "keyboard", "functional", "shortcuts"] pickerViewController.initialTabIdentifier = "gamepad" pickerViewController.isEditMode = true pickerViewController.initialCmdString = selectedWidget.cmdString diff --git a/VoidLink/ViewControllers/ToolboxViewController.swift b/VoidLink/ViewControllers/ToolboxViewController.swift index 17626b91..07192e8d 100644 --- a/VoidLink/ViewControllers/ToolboxViewController.swift +++ b/VoidLink/ViewControllers/ToolboxViewController.swift @@ -290,7 +290,7 @@ import UIKit pickerViewController.delegate = (self as WidgetPickerViewControllerDelegate) pickerViewController.keyboardPickerMode = .shortcutPicker pickerViewController.shortcutPickerNeedAlias = true - pickerViewController.tabIdentifiers = ["keyboard"] + pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"] pickerViewController.initialTabIdentifier = "keyboard" pickerViewController.presentOverFullScreen(from: self) return @@ -393,7 +393,7 @@ import UIKit } else { let command = CommandManager.shared.getAllCommands()[indexPath.row-specialEntries.count] - cell.textLabel?.text = command.alias + cell.textLabel?.text = LocalizationHelper.localizedString(forKey: command.alias) } return cell } diff --git a/VoidLinkPreview/WidgetPickerView.swift b/VoidLinkPreview/WidgetPickerView.swift index 0c82ca5c..db337d3d 100644 --- a/VoidLinkPreview/WidgetPickerView.swift +++ b/VoidLinkPreview/WidgetPickerView.swift @@ -32,6 +32,7 @@ enum WidgetPickerTab: String, CaseIterable, Identifiable, Equatable { case gamepad = "Gamepad" case keyboard = "Keyboard & Mouse" case functional = "Functional" + case shortcuts = "Shortcuts" var id: String { rawValue } @@ -43,6 +44,8 @@ enum WidgetPickerTab: String, CaseIterable, Identifiable, Equatable { return "keyboard" case .functional: return "functional" + case .shortcuts: + return "shortcuts" } } @@ -54,6 +57,8 @@ enum WidgetPickerTab: String, CaseIterable, Identifiable, Equatable { return LocalizationHelper.localizedString(forKey: "Keyboard / Mouse") case .functional: return LocalizationHelper.localizedString(forKey: "Functional Widgets") + case .shortcuts: + return LocalizationHelper.localizedString(forKey: "ShortcutsTab") } } @@ -65,6 +70,8 @@ enum WidgetPickerTab: String, CaseIterable, Identifiable, Equatable { self = .keyboard case "functional", "function", "functions": self = .functional + case "shortcuts", "shortcut", "shortcutlibrary", "shortcut_library", "shortcut-library": + self = .shortcuts default: return nil } @@ -76,6 +83,7 @@ enum WidgetPoolSource: Equatable { case gamepad case keyboard case functional + case shortcuts case interval } @@ -86,6 +94,7 @@ enum WidgetPoolVisualKind: Equatable { case keyboardButton case keyboardPad case functionalButton + case shortcutButton case triggerInterval } @@ -113,6 +122,8 @@ struct WidgetPoolItem: Identifiable { return displayText ?? cmd case .functional: return cmd + case .shortcuts: + return cmd case .interval: return WidgetPickerView.intervalDisplayText(for: cmd) } @@ -120,7 +131,7 @@ struct WidgetPoolItem: Identifiable { var span: Int { switch visualKind { - case .gamepadButton, .keyboardButton, .functionalButton, .triggerInterval: + case .gamepadButton, .keyboardButton, .functionalButton, .shortcutButton, .triggerInterval: return 1 case .gamepadPad, .keyboardPad: return 2 @@ -755,6 +766,42 @@ struct WidgetPickerView: View { ), ] + private let shortcutLibraryOptions: [FunctionalButtonOption] = [ + FunctionalButtonOption(localizationKey: "=nvidiaShot", cmd: "ALT+F1", tip: LocalizationHelper.localizedString(forKey: "=nvidiaShot"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=nvidiaRec", cmd: "ALT+F9", tip: LocalizationHelper.localizedString(forKey: "=nvidiaRec"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=hdr", cmd: "WIN+ALT+B", tip: LocalizationHelper.localizedString(forKey: "=hdr"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=taskManager", cmd: "CTRL+SHIFT+ESC", tip: LocalizationHelper.localizedString(forKey: "=taskManager"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=snip", cmd: "WIN+SHIFT+S", tip: LocalizationHelper.localizedString(forKey: "=snip"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=copy", cmd: "CTRL+C", tip: LocalizationHelper.localizedString(forKey: "=copy"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=cut", cmd: "CTRL+X", tip: LocalizationHelper.localizedString(forKey: "=cut"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=paste", cmd: "CTRL+V", tip: LocalizationHelper.localizedString(forKey: "=paste"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=undo", cmd: "CTRL+Z", tip: LocalizationHelper.localizedString(forKey: "=undo"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=redo", cmd: "CTRL+Y", tip: LocalizationHelper.localizedString(forKey: "=redo"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=selectAll", cmd: "CTRL+A", tip: LocalizationHelper.localizedString(forKey: "=selectAll"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=save", cmd: "CTRL+S", tip: LocalizationHelper.localizedString(forKey: "=save"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=find", cmd: "CTRL+F", tip: LocalizationHelper.localizedString(forKey: "=find"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=zoomIn", cmd: "CTRL+EQUALS", tip: LocalizationHelper.localizedString(forKey: "=zoomIn"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=zoomOut", cmd: "CTRL+MINUS", tip: LocalizationHelper.localizedString(forKey: "=zoomOut"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=ime", cmd: "WIN+SPACE", tip: LocalizationHelper.localizedString(forKey: "=ime"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=lang", cmd: "ALT+SHIFT", tip: LocalizationHelper.localizedString(forKey: "=lang"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=switchWindow", cmd: "ALT+TAB", tip: LocalizationHelper.localizedString(forKey: "=switchWindow"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=closeWindow", cmd: "ALT+F4", tip: LocalizationHelper.localizedString(forKey: "=closeWindow"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=desktop", cmd: "WIN+D", tip: LocalizationHelper.localizedString(forKey: "=desktop"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=fileExplorer", cmd: "WIN+E", tip: LocalizationHelper.localizedString(forKey: "=fileExplorer"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=settingsShortcut", cmd: "WIN+I", tip: LocalizationHelper.localizedString(forKey: "=settingsShortcut"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=lock", cmd: "WIN+L", tip: LocalizationHelper.localizedString(forKey: "=lock"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=run", cmd: "WIN+R", tip: LocalizationHelper.localizedString(forKey: "=run"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=search", cmd: "WIN+S", tip: LocalizationHelper.localizedString(forKey: "=search"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=taskView", cmd: "WIN+TAB", tip: LocalizationHelper.localizedString(forKey: "=taskView"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=snapLeft", cmd: "WIN+LEFTARR", tip: LocalizationHelper.localizedString(forKey: "=snapLeft"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=snapRight", cmd: "WIN+RIGHTARR", tip: LocalizationHelper.localizedString(forKey: "=snapRight"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=maximize", cmd: "WIN+UPARR", tip: LocalizationHelper.localizedString(forKey: "=maximize"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=minimize", cmd: "WIN+DOWNARR", tip: LocalizationHelper.localizedString(forKey: "=minimize"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=newFolder", cmd: "CTRL+SHIFT+N", tip: LocalizationHelper.localizedString(forKey: "=newFolder"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=newTab", cmd: "CTRL+T", tip: LocalizationHelper.localizedString(forKey: "=newTab"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + FunctionalButtonOption(localizationKey: "=closeTab", cmd: "CTRL+W", tip: LocalizationHelper.localizedString(forKey: "=closeTab"), allowsKeyboardCombination: false, allowsGamepadCombination: false, allowsSkillCombo: false, allowsShortcutCombo: false, forcedComboMode: .shortcut), + ] + private var defaultTipMessage: String { if isShortcutPickerMode, let shortcutPickerTipText, !shortcutPickerTipText.isEmpty { return shortcutPickerTipText @@ -1073,6 +1120,17 @@ struct WidgetPickerView: View { .opacity(contentVisibilityOpacity(for: .functional)) .allowsHitTesting(isContentInteractive(for: .functional)) .accessibility(hidden: !isContentVisible(for: .functional)) + + FunctionalButtonCollectionView( + items: shortcutLibraryOptions, + isSelected: { selectedCmds.contains($0) }, + onSelect: { handleShortcutLibrarySelection($0) }, + onDeselect: { handleShortcutLibraryDeselection($0) } + ) + .padding(pickerInsetFunctional) + .opacity(contentVisibilityOpacity(for: .shortcuts)) + .allowsHitTesting(isContentInteractive(for: .shortcuts)) + .accessibility(hidden: !isContentVisible(for: .shortcuts)) } .frame(maxWidth: .infinity, maxHeight: .infinity) .mask( @@ -1441,6 +1499,8 @@ struct WidgetPickerView: View { return gamepadPadCommands.contains(cmd) ? .gamepadPad : .gamepadButton case .functional: return .functionalButton + case .shortcuts: + return .shortcutButton case .keyboard: return keyboardPadCommands.contains(cmd) ? .keyboardPad : .keyboardButton case .interval: @@ -1689,6 +1749,8 @@ struct WidgetPickerView: View { if shouldClearHighlight, selectedGyroCommand == deselectionCommand { selectedGyroCommand = nil } + case .shortcuts: + break case .interval: break } @@ -1724,7 +1786,7 @@ struct WidgetPickerView: View { switch item.visualKind { case .keyboardButton, .gamepadButton: return true - case .gamepadPad, .keyboardPad, .functionalButton, .triggerInterval: + case .gamepadPad, .keyboardPad, .functionalButton, .shortcutButton, .triggerInterval: return false } } @@ -1905,7 +1967,7 @@ struct WidgetPickerView: View { private func isButtonItem(_ item: WidgetPoolItem) -> Bool { switch item.visualKind { - case .gamepadButton, .keyboardButton, .functionalButton: + case .gamepadButton, .keyboardButton, .functionalButton, .shortcutButton: return true case .gamepadPad, .keyboardPad, .triggerInterval: return false @@ -2151,8 +2213,8 @@ struct WidgetPickerView: View { return false } - if source == .functional, !gyroCommands.contains(cmd) { - guard let option = functionalButtonOption(for: cmd) else { + if (source == .functional || source == .shortcuts), !gyroCommands.contains(cmd) { + guard let option = functionalButtonOption(for: cmd) ?? shortcutLibraryOption(for: cmd) else { resetTipMessage() return false } @@ -2162,7 +2224,7 @@ struct WidgetPickerView: View { return false } - if poolItems.contains(where: { $0.source == .functional }) { + if poolItems.contains(where: { $0.source == .functional || $0.source == .shortcuts }) { setTipMessage(LocalizationHelper.localizedString(forKey: "Functional widgets cannot be combined with other functional widgets"), type: .error) return false } @@ -2181,13 +2243,13 @@ struct WidgetPickerView: View { } if source == .keyboard || source == .gamepad { - if poolItems.contains(where: isPad) && poolItems.contains(where: { $0.source == .functional && !gyroCommands.contains($0.cmd) }) { + if poolItems.contains(where: isPad) && poolItems.contains(where: { ($0.source == .functional || $0.source == .shortcuts) && !gyroCommands.contains($0.cmd) }) { setTipMessage(LocalizationHelper.localizedString(forKey: "Functional widgets cannot be combined with touchpad controls"), type: .error) return false } - if let existingFunctionalItem = poolItems.first(where: { $0.source == .functional && !gyroCommands.contains($0.cmd) }), - let option = functionalButtonOption(for: existingFunctionalItem.cmd) { + if let existingFunctionalItem = poolItems.first(where: { ($0.source == .functional || $0.source == .shortcuts) && !gyroCommands.contains($0.cmd) }), + let option = functionalButtonOption(for: existingFunctionalItem.cmd) ?? shortcutLibraryOption(for: existingFunctionalItem.cmd) { if source == .keyboard && !option.allowsKeyboardCombination { setTipMessage(LocalizationHelper.localizedString(forKey: "This functional widget cannot be combined with keyboard or mouse buttons"), type: .error) return false @@ -2200,7 +2262,7 @@ struct WidgetPickerView: View { } if isPad(candidate), - poolItems.contains(where: { $0.source == .functional && !gyroCommands.contains($0.cmd) }) { + poolItems.contains(where: { ($0.source == .functional || $0.source == .shortcuts) && !gyroCommands.contains($0.cmd) }) { setTipMessage(LocalizationHelper.localizedString(forKey: "Functional widgets cannot be combined with touchpad controls"), type: .error) return false } @@ -2225,6 +2287,10 @@ struct WidgetPickerView: View { functionalButtonOptions.first(where: { $0.cmd == cmd }) } + private func shortcutLibraryOption(for cmd: String) -> FunctionalButtonOption? { + shortcutLibraryOptions.first(where: { $0.cmd == cmd }) + } + private func localizedFunctionalButtonLabel(for option: FunctionalButtonOption) -> String { LocalizationHelper.localizedString(forKey: option.localizationKey) } @@ -2275,6 +2341,15 @@ struct WidgetPickerView: View { removeCommand(option.cmd, source: .functional) } + private func handleShortcutLibrarySelection(_ option: FunctionalButtonOption) { + guard canSelectCommand(option.cmd, source: .shortcuts) else { return } + appendCommand(option.cmd, source: .shortcuts) + } + + private func handleShortcutLibraryDeselection(_ option: FunctionalButtonOption) { + removeCommand(option.cmd, source: .shortcuts) + } + private var createWidgetSheet: some View { let isPhone = UIDevice.current.userInterfaceIdiom == .phone let contentSpacing: CGFloat = isPhone ? 10 : 16 @@ -2849,7 +2924,7 @@ struct WidgetPickerView: View { switch firstPoolItem.visualKind { case .gamepadPad, .keyboardPad: return .pad - case .gamepadButton, .keyboardButton, .functionalButton, .triggerInterval: + case .gamepadButton, .keyboardButton, .functionalButton, .shortcutButton, .triggerInterval: return .button } } @@ -2859,13 +2934,13 @@ struct WidgetPickerView: View { } private var isFunctionalOnlySelection: Bool { - firstPoolItem?.source == .functional + firstPoolItem?.source == .functional || firstPoolItem?.source == .shortcuts } private var buttonCommandCount: Int { poolItems.filter { item in switch item.visualKind { - case .gamepadButton, .keyboardButton, .functionalButton: + case .gamepadButton, .keyboardButton, .functionalButton, .shortcutButton: return true case .gamepadPad, .keyboardPad, .triggerInterval: return false @@ -2886,10 +2961,10 @@ struct WidgetPickerView: View { } private var selectedFunctionalButtonOption: FunctionalButtonOption? { - guard let functionalItem = poolItems.first(where: { $0.source == .functional && !gyroCommands.contains($0.cmd) }) else { + guard let functionalItem = poolItems.first(where: { ($0.source == .functional || $0.source == .shortcuts) && !gyroCommands.contains($0.cmd) }) else { return nil } - return functionalButtonOption(for: functionalItem.cmd) + return functionalButtonOption(for: functionalItem.cmd) ?? shortcutLibraryOption(for: functionalItem.cmd) } private var forcedComboMode: WidgetCreateComboMode? { @@ -3182,6 +3257,12 @@ struct WidgetPickerView: View { } let label = item.displayCmd.trimmingCharacters(in: .whitespacesAndNewlines) return label.isEmpty ? nil : sanitizedAutoButtonLabel(label) + case .shortcuts: + if let option = shortcutLibraryOption(for: item.cmd) { + return option.localizationKey + } + let label = item.displayCmd.trimmingCharacters(in: .whitespacesAndNewlines) + return label.isEmpty ? nil : sanitizedAutoButtonLabel(label) case .interval: return nil } @@ -3243,11 +3324,15 @@ struct WidgetPickerView: View { selectedTab = .keyboard case .functional: selectedTab = .functional + case .shortcuts: + selectedTab = .shortcuts case .interval: break } } else if parsedCommands.contains(where: { $0.source == .functional }) { selectedTab = .functional + } else if parsedCommands.contains(where: { $0.source == .shortcuts }) { + selectedTab = .shortcuts } updateTipMessageForCurrentPoolState() @@ -3260,6 +3345,10 @@ struct WidgetPickerView: View { guard !normalized.isEmpty else { return [] } + if isShortcutPickerMode, shortcutLibraryCommands.contains(normalized) { + return [(cmd: normalized, source: .shortcuts, isTailLocked: false, displayText: macroDisplayText(for: normalized, source: .shortcuts))] + } + var commands: [String] if normalized.contains("+") { commands = normalized @@ -3310,7 +3399,7 @@ struct WidgetPickerView: View { let buttonDescriptorCount = parsed.filter { descriptor in let visualKind = visualKind(for: descriptor.cmd, source: descriptor.source) switch visualKind { - case .gamepadButton, .keyboardButton, .functionalButton: + case .gamepadButton, .keyboardButton, .functionalButton, .shortcutButton: return true case .gamepadPad, .keyboardPad, .triggerInterval: return false @@ -3322,7 +3411,7 @@ struct WidgetPickerView: View { rebuilt.append(descriptor) let visualKind = visualKind(for: descriptor.cmd, source: descriptor.source) switch visualKind { - case .gamepadButton, .keyboardButton, .functionalButton: + case .gamepadButton, .keyboardButton, .functionalButton, .shortcutButton: remainingButtons -= 1 if remainingButtons > 0 { rebuilt.append((cmd: Self.makeTriggerIntervalCommand(milliseconds: trailingUniformInterval), source: .interval, isTailLocked: false, displayText: nil)) @@ -3356,6 +3445,9 @@ struct WidgetPickerView: View { if gamepadCommands.contains(cmd) { return .gamepad } + if isShortcutPickerMode, shortcutLibraryCommands.contains(cmd) { + return .shortcuts + } if functionalCommands.contains(cmd) { return .functional } @@ -3403,6 +3495,11 @@ struct WidgetPickerView: View { return localizedFunctionalButtonLabel(for: option) } return cmd + case .shortcuts: + if let option = shortcutLibraryOption(for: cmd) { + return localizedFunctionalButtonLabel(for: option) + } + return cmd case .interval: return Self.intervalDisplayText(for: cmd) } @@ -3465,6 +3562,10 @@ struct WidgetPickerView: View { Set(functionalButtonOptions.map(\.cmd)).union(gyroCommands) } + private var shortcutLibraryCommands: Set { + Set(shortcutLibraryOptions.map(\.cmd)) + } + private enum InitialWidgetShape { case round case square @@ -4061,7 +4162,7 @@ struct WidgetPoolGridView: View { .padding(.horizontal, 8) ) .shadow(color: Color.black.opacity(0.08), radius: 6, x: 0, y: 4) - } else if item.visualKind == .functionalButton { + } else if item.visualKind == .functionalButton || item.visualKind == .shortcutButton { RoundedRectangle(cornerRadius: 16, style: .continuous) .fill( LinearGradient(