From d3b62e38fc8577b014f494d96faeb82f0edf6ae1 Mon Sep 17 00:00:00 2001 From: TrueZhuangjia Date: Tue, 13 May 2025 23:18:32 +0800 Subject: [PATCH] Onscreen Widget Tool: - edit/duplicate function support - opened from cmd tool by sliding from screen edge --- Limelight/Base.lproj/iPad.storyboard | 77 +++--- Limelight/Base.lproj/iPhone.storyboard | 83 ++++--- Limelight/Input/CommandManager.swift | 48 ++-- Limelight/Input/OnScreenWidgetView.swift | 4 +- Limelight/Input/StreamView.m | 2 +- .../Moonlight v1.10.xcdatamodel/contents | 2 +- Limelight/Localization/Localizable.xcstrings | 232 ++++++++++++++---- .../CommandManagerViewController.swift | 70 ++++-- .../LayoutOnScreenControlsViewController.h | 3 +- .../LayoutOnScreenControlsViewController.m | 202 +++++++++++---- .../ViewControllers/SettingsViewController.m | 7 +- .../StreamFrameViewController.m | 12 +- Limelight/mul.lproj/iPad.xcstrings | 36 ++- Limelight/mul.lproj/iPhone.xcstrings | 24 ++ 14 files changed, 573 insertions(+), 229 deletions(-) diff --git a/Limelight/Base.lproj/iPad.storyboard b/Limelight/Base.lproj/iPad.storyboard index 848a5fbe..42be2326 100644 --- a/Limelight/Base.lproj/iPad.storyboard +++ b/Limelight/Base.lproj/iPad.storyboard @@ -751,14 +751,14 @@ ICAgICAgICAgICAgICA - + - + - + - + + + - @@ -807,42 +820,43 @@ ICAgICAgICAgICAgICA - - + - + - + @@ -939,6 +953,7 @@ ICAgICAgICAgICAgICA + @@ -1038,7 +1053,7 @@ ICAgICAgICAgICAgICA - + diff --git a/Limelight/Base.lproj/iPhone.storyboard b/Limelight/Base.lproj/iPhone.storyboard index b1e4837b..cc952df2 100644 --- a/Limelight/Base.lproj/iPhone.storyboard +++ b/Limelight/Base.lproj/iPhone.storyboard @@ -722,7 +722,7 @@ ICAgICAgICAgICAgIA - + @@ -732,6 +732,7 @@ ICAgICAgICAgICAgIA + @@ -749,14 +750,14 @@ ICAgICAgICAgICAgIA - + - + - + - + - - + + + + + + + + + + + + + + - + + + + - + - + @@ -942,7 +947,7 @@ ICAgICAgICAgICAgIA - + diff --git a/Limelight/Input/CommandManager.swift b/Limelight/Input/CommandManager.swift index 534202ff..1c77081c 100644 --- a/Limelight/Input/CommandManager.swift +++ b/Limelight/Input/CommandManager.swift @@ -19,29 +19,29 @@ import UIKit // MARK: - Properties - @objc var keyboardCmdString: String + @objc var cmdString: String @objc var alias: String // MARK: - Initialization - init(keyboardCmdString: String, alias: String) { - self.keyboardCmdString = keyboardCmdString + init(cmdString: String, alias: String) { + self.cmdString = cmdString self.alias = alias } // MARK: - NSSecureCoding required public init?(coder: NSCoder) { - guard let keyboardCmdString = coder.decodeObject(of: NSString.self, forKey: "keyboardCmdString") as String?, + guard let cmdString = coder.decodeObject(of: NSString.self, forKey: "keyboardCmdString") as String?, let alias = coder.decodeObject(of: NSString.self, forKey: "alias") as String? else { return nil } - self.keyboardCmdString = keyboardCmdString + self.cmdString = cmdString self.alias = alias } public func encode(with coder: NSCoder) { - coder.encode(keyboardCmdString, forKey: "keyboardCmdString") + coder.encode(cmdString, forKey: "keyboardCmdString") coder.encode(alias, forKey: "alias") } } @@ -377,21 +377,21 @@ import UIKit let defaults = UserDefaults.standard //if true { // save default entries if the data is empty. if defaults.data(forKey: "savedCommands") == nil { // save default entries if the data is empty. - let defaultCommands: [RemoteCommand] = [ - RemoteCommand(keyboardCmdString: "WIN", alias: "WIN"), - RemoteCommand(keyboardCmdString: "F11", alias: "F11"), - RemoteCommand(keyboardCmdString: "ESC", alias: "ESC"), - RemoteCommand(keyboardCmdString: "CTRL+SHIFT+ESC", alias: "任务管理器(Task Manager)"), - RemoteCommand(keyboardCmdString: "ALT+F1", alias: "N卡截图(Nvidia Screenshot)"), - RemoteCommand(keyboardCmdString: "ALT+F9", alias: "N卡录屏(Nvidia Screen Recording)"), - RemoteCommand(keyboardCmdString: "ALT+F4", alias: "关闭窗口(ALT+F4)"), - RemoteCommand(keyboardCmdString: "CTRL+A", alias: "全选(Select All)"), - RemoteCommand(keyboardCmdString: "CTRL+C", alias: "复制(Copy)"), - RemoteCommand(keyboardCmdString: "CTRL+V", alias: "粘贴(Paste)"), - RemoteCommand(keyboardCmdString: "WIN+D", alias: "切换桌面(Switch to Desktop)"), - RemoteCommand(keyboardCmdString: "WIN+P", alias: "多显模式(Project)"), - RemoteCommand(keyboardCmdString: "WIN+G", alias: "Xbox Game Bar"), - RemoteCommand(keyboardCmdString: "SHIFT+TAB", alias: "Steam Overlay"), + var defaultCommands: [RemoteCommand] = [ + RemoteCommand(cmdString: "WIN", alias: "WIN"), + RemoteCommand(cmdString: "F11", alias: "F11"), + RemoteCommand(cmdString: "ESC", alias: "ESC"), + RemoteCommand(cmdString: "CTRL+SHIFT+ESC", alias: "任务管理器(Task Manager)"), + RemoteCommand(cmdString: "ALT+F1", alias: "N卡截图(Nvidia Screenshot)"), + RemoteCommand(cmdString: "ALT+F9", alias: "N卡录屏(Nvidia Screen Recording)"), + RemoteCommand(cmdString: "ALT+F4", alias: "关闭窗口(ALT+F4)"), + RemoteCommand(cmdString: "CTRL+A", alias: "全选(Select All)"), + RemoteCommand(cmdString: "CTRL+C", alias: "复制(Copy)"), + RemoteCommand(cmdString: "CTRL+V", alias: "粘贴(Paste)"), + RemoteCommand(cmdString: "WIN+D", alias: "切换桌面(Switch to Desktop)"), + RemoteCommand(cmdString: "WIN+P", alias: "多显模式(Project)"), + RemoteCommand(cmdString: "WIN+G", alias: "Xbox Game Bar"), + RemoteCommand(cmdString: "SHIFT+TAB", alias: "Steam Overlay"), ] let data = try? NSKeyedArchiver.archivedData(withRootObject: defaultCommands, requiringSecureCoding: false) @@ -505,9 +505,9 @@ import UIKit } @objc public func addCommand(_ command: RemoteCommand) -> Bool { - command.keyboardCmdString = command.keyboardCmdString.uppercased() // convert all letters to upper case - if(command.alias.trimmingCharacters(in: .whitespacesAndNewlines).count == 0) {command.alias = command.keyboardCmdString} // copy cmd string as alias when alias is empty - let keyStrings = extractKeyStringsFromComboCommand(from: command.keyboardCmdString) + command.cmdString = command.cmdString.uppercased() // convert all letters to upper case + if(command.alias.trimmingCharacters(in: .whitespacesAndNewlines).count == 0) {command.alias = command.cmdString} // copy cmd string as alias when alias is empty + let keyStrings = extractKeyStringsFromComboCommand(from: command.cmdString) if (keyStrings == nil) {return false} // in case of non-keyboard command strings, return false commands.append(command) saveCommands() diff --git a/Limelight/Input/OnScreenWidgetView.swift b/Limelight/Input/OnScreenWidgetView.swift index 04b5aac7..b1587a40 100644 --- a/Limelight/Input/OnScreenWidgetView.swift +++ b/Limelight/Input/OnScreenWidgetView.swift @@ -223,14 +223,14 @@ import UIKit // ====================================================================================================== - @objc public func setLocation(xOffset:CGFloat, yOffset:CGFloat) { + @objc public func setLocation(position: CGPoint) { /* NSLayoutConstraint.activate([ self.centerXAnchor.constraint(equalTo: self.superview!.leadingAnchor, constant: xOffset), self.centerYAnchor.constraint(equalTo: self.superview!.topAnchor, constant: yOffset), ]) */ - storedCenter = CGPointMake(xOffset, yOffset) + storedCenter = position center = storedCenter } diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index daf83d2a..61740cd2 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -417,7 +417,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; // Add the widgetView to the view controller's view [self->streamFrameTopLayerView addSubview:widgetView]; // add keyboard button to the stream frame view. must add it to the target view before setting location. buttonState.position = [self denormalizeWidgetPosition:buttonState.position]; - [widgetView setLocationWithXOffset:buttonState.position.x yOffset:buttonState.position.y]; + [widgetView setLocationWithPosition:buttonState.position]; [widgetView resizeWidgetView]; // resize must be called after relocation [widgetView adjustTransparencyWithAlpha:buttonState.backgroundAlpha]; [widgetView adjustBorderWithWidth:buttonState.borderWidth]; diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents index b16013cc..476a2df4 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents @@ -45,7 +45,7 @@ - + diff --git a/Limelight/Localization/Localizable.xcstrings b/Limelight/Localization/Localizable.xcstrings index 89026697..538181ee 100644 --- a/Limelight/Localization/Localizable.xcstrings +++ b/Limelight/Localization/Localizable.xcstrings @@ -53,6 +53,29 @@ } } }, + "[ Open On-Screen Widget Tool ]" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "[ 打开屏显控件工具 ]" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "[ 打开屏显控件工具 ]" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "[ 打开屏显控件工具 ]" + } + } + } + }, "%@ is currently running" : { "extractionState" : "manual", "localizations" : { @@ -398,6 +421,29 @@ } } }, + "Command Tool" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令工具" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令工具" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令工具" + } + } + } + }, "Connecting" : { "extractionState" : "manual", "localizations" : { @@ -565,6 +611,29 @@ } } }, + "Create New" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增" + } + } + } + }, "Current profile updated successfully" : { "extractionState" : "manual", "localizations" : { @@ -749,6 +818,29 @@ } } }, + "Edit Selected Widget" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑所选控件" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑所选控件" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑所选控件" + } + } + } + }, "Enter a new command and alias" : { "extractionState" : "manual", "localizations" : { @@ -1238,6 +1330,29 @@ } } }, + "left" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "左" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "左" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "左" + } + } + } + }, "Local Keyboard Toggle Disabled" : { "extractionState" : "manual", "localizations" : { @@ -1342,6 +1457,29 @@ } } }, + "Modify" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "修改" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "修改" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "修改" + } + } + } + }, "Moonlight-iOS/tvOS only supports adding PCs on your local network" : { "extractionState" : "manual", "localizations" : { @@ -1768,6 +1906,29 @@ } } }, + "Open widget tool in streaming by:\nSliding from %@ screen edge to open cmd tool.\nOr tap %d fingers on stream view, number of fingers required:" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在串流视图中修改布局:\n从屏幕%@边缘向内滑动.\n或轻按%d根手指, 手势需要的手指数量:" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "在串流视图中修改布局:\n从屏幕%@边缘向内滑动.\n或轻按%d根手指, 手势需要的手指数量:" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "在串流视图中修改布局:\n从屏幕%@边缘向内滑动.\n或轻按%d根手指, 手势需要的手指数量:" + } + } + } + }, "Pairing" : { "extractionState" : "manual", "localizations" : { @@ -2113,7 +2274,7 @@ } } }, - "Rebase in Stream View" : { + "Rebase in Streaming" : { "extractionState" : "manual", "localizations" : { "zh-Hans" : { @@ -2205,6 +2366,29 @@ } } }, + "right" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "右" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "右" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "右" + } + } + } + }, "Save" : { "extractionState" : "manual", "localizations" : { @@ -2355,29 +2539,6 @@ } } }, - "Send Commands" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "发送命令" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "发送命令" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "发送命令" - } - } - } - }, "Sensitivity: %.2f" : { "extractionState" : "manual", "localizations" : { @@ -2539,29 +2700,6 @@ } } }, - "Tap %d fingers to change layout in stream view, or change the number of fingers required to:" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "在串流视图中轻按%d根手指修改布局, 或将手势需要的手指数量改为:" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "在串流视图中轻按%d根手指修改布局, 或将手势需要的手指数量改为:" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "在串流视图中轻按%d根手指修改布局, 或将手势需要的手指数量改为:" - } - } - } - }, "Tap %d Fingers to Change OSC Layout in Stream View" : { "extractionState" : "manual", "localizations" : { diff --git a/Limelight/ViewControllers/CommandManagerViewController.swift b/Limelight/ViewControllers/CommandManagerViewController.swift index 01942893..66707e54 100644 --- a/Limelight/ViewControllers/CommandManagerViewController.swift +++ b/Limelight/ViewControllers/CommandManagerViewController.swift @@ -19,7 +19,11 @@ import UIKit private let viewBackgroundColor = UIColor(white: 0.2, alpha: 0.8); private let highlightColor = UIColor(white: 0.39, alpha: 0.8); private let titleLabel = UILabel() - + @objc public var specialEntries : NSMutableArray = ["widgetTool"] + private let specialEntryAliasDic : [String:String] = [ + "widgetTool":SwiftLocalizationHelper.localizedString(forKey: "[ Open On-Screen Widget Tool ]") + ] + private var viewPinned: Bool = false private var isEditingMode: Bool = false { didSet { @@ -42,9 +46,11 @@ import UIKit CommandManager.shared.viewController = self } + override public func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() setupConstraints() + reloadTableView() } private func setupViews() { @@ -54,7 +60,7 @@ import UIKit view.layer.masksToBounds = true // Set up the title label - titleLabel.text = SwiftLocalizationHelper.localizedString(forKey: "Send Commands") + titleLabel.text = SwiftLocalizationHelper.localizedString(forKey: "Command Tool") titleLabel.font = UIFont.boldSystemFont(ofSize: 20) // Adjust font size as needed titleLabel.textColor = UIColor.white // Adjust color as needed titleLabel.textAlignment = .center @@ -96,7 +102,7 @@ import UIKit private func setupConstraints() { - guard let superview = view.superview else { + guard view.superview != nil else { return } @@ -187,7 +193,7 @@ import UIKit let submitAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: "Add"), style: .default) { [unowned alert] _ in let keyboardCmdString = alert.textFields?[0].text ?? "" let alias = alert.textFields?[1].text ?? keyboardCmdString - let newCommand = RemoteCommand(keyboardCmdString: keyboardCmdString, alias: alias) + let newCommand = RemoteCommand(cmdString: keyboardCmdString, alias: alias) let addCommandSuceeded = CommandManager.shared.addCommand(newCommand) //self.reloadTableView() // don't know why but this reload has to be called from the CommandManager, it doesn't work here. @@ -207,8 +213,9 @@ import UIKit alert.addAction(cancelAction) if let selectedIndexPath = self.tableView.indexPathForSelectedRow { - let selectedCommand = CommandManager.shared.getAllCommands()[selectedIndexPath.row] - alert.textFields?[0].text = selectedCommand.keyboardCmdString // load selected keyboard cmd string + if isSpecialEntrySelected() {return} + let selectedCommand = CommandManager.shared.getAllCommands()[selectedIndexPath.row-specialEntries.count] + alert.textFields?[0].text = selectedCommand.cmdString // load selected keyboard cmd string //alert.textFields?[1].text = selectedCommand.alias // leave the alias input field empty } @@ -220,9 +227,12 @@ import UIKit guard let selectedIndexPath = tableView.indexPathForSelectedRow else { return } + + if isSpecialEntrySelected() {return} + let previouslySelectedIndexPath = selectedIndexPath // memorize selected row before reloading tableview - CommandManager.shared.deleteCommand(at: selectedIndexPath.row) + CommandManager.shared.deleteCommand(at: selectedIndexPath.row-specialEntries.count) reloadTableView() // target new entry after deletion var targetRowAfterDel = previouslySelectedIndexPath.row - 1 @@ -235,6 +245,8 @@ import UIKit @objc private func editButtonTapped() { isEditingMode.toggle() + deleteButton.isEnabled = !isSpecialEntrySelected() + addButton.isEnabled = !isSpecialEntrySelected() } @objc private func exitButtonTapped() { @@ -257,7 +269,7 @@ import UIKit // UITableViewDataSource public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return CommandManager.shared.getAllCommands().count + return specialEntries.count + CommandManager.shared.getAllCommands().count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -271,9 +283,14 @@ import UIKit let selectedBackgroundView = UIView() selectedBackgroundView.backgroundColor = highlightColor // Color for the selected state cell.selectedBackgroundView = selectedBackgroundView - - let command = CommandManager.shared.getAllCommands()[indexPath.row] - cell.textLabel?.text = command.alias + + if indexPath.row < specialEntries.count { + cell.textLabel?.text = specialEntryAliasDic[specialEntries[indexPath.row] as! String] + } + else { + let command = CommandManager.shared.getAllCommands()[indexPath.row-specialEntries.count] + cell.textLabel?.text = command.alias + } return cell } @@ -292,18 +309,41 @@ import UIKit } } } - + if !isEditingMode { // Sending keyboard command - let command = CommandManager.shared.getAllCommands()[indexPath.row] - sendKeyboardCommand(command) + if indexPath.row < specialEntries.count{ + handleSpecialEntries(index: indexPath.row) + } + else { + let command = CommandManager.shared.getAllCommands()[indexPath.row-specialEntries.count] + sendKeyboardCommand(command) + } if !viewPinned { dismiss(animated: false, completion: nil) } // dimiss the view in sending mode & the view is not pinned } + else { + let specialEntrySelected = indexPath.row < specialEntries.count + addButton.isEnabled = !specialEntrySelected + deleteButton.isEnabled = !specialEntrySelected + } + } + + private func isSpecialEntrySelected() -> Bool { + return self.tableView.indexPathForSelectedRow?.row ?? specialEntries.count+1 < specialEntries.count + } + + private func handleSpecialEntries(index:Int){ + switch specialEntries[index] as? String { + case "widgetTool": + dismiss(animated: true, completion: nil) + NotificationCenter.default.post(name: Notification.Name("OscLayoutToolOpenedFromCmdToolNotification"),object: nil) // inform streamFrameViewController to open osc tool + default: break + } } private func sendKeyboardCommand(_ cmd: RemoteCommand) { print("Sending key-value") - let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: cmd.keyboardCmdString) + let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: cmd.cmdString) CommandManager.shared.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings!) } } diff --git a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h index b51884bd..8f157cd8 100644 --- a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h +++ b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h @@ -3,7 +3,8 @@ // Moonlight // // Created by Long Le on 9/27/22. -// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved. +// Modified by True砖家 since 2024.6.24 +// Copyright © True砖家 @ Bilibili & 2022 Moonlight Game Streaming Project. All rights reserved. // #import diff --git a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index 1b916842..b40b6257 100644 --- a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -3,7 +3,8 @@ // Moonlight // // Created by Long Le on 9/27/22. -// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved. +// Modified by True砖家 since 2024.6.24 +// Copyright © True砖家 @ Bilibili & 2022 Moonlight Game Streaming Project. All rights reserved. // #import "LayoutOnScreenControlsViewController.h" @@ -110,7 +111,7 @@ // Add the widgetView to the view controller's view [self.view addSubview:widgetView]; buttonState.position = [self denormalizeWidgetPosition:buttonState.position]; - [widgetView setLocationWithXOffset:buttonState.position.x yOffset:buttonState.position.y]; + [widgetView setLocationWithPosition:buttonState.position]; [widgetView resizeWidgetView]; // resize must be called after relocation [widgetView adjustTransparencyWithAlpha:buttonState.backgroundAlpha]; [widgetView adjustBorderWithWidth:buttonState.borderWidth]; @@ -119,7 +120,6 @@ } } - - (void) viewDidLoad { [super viewDidLoad]; profilesManager = [OSCProfilesManager sharedManager:self.view.bounds]; @@ -230,6 +230,7 @@ - (void) viewDidAppear:(BOOL)animated { OnScreenWidgetView.editMode = true; + selectedWidgetView = nil; [super viewWillAppear:animated]; [self profileRefresh]; } @@ -368,6 +369,8 @@ - (IBAction) addTapped:(id)sender{ + NSMutableDictionary* widgetInitParams = [NSMutableDictionary dictionary]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@""] message:[LocalizationHelper localizedStringForKey:@"New On-Screen Widget"] preferredStyle:UIAlertControllerStyleAlert]; @@ -418,54 +421,11 @@ UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - /* - alertController.textFields[0].keyboardType = UIKeyboardTypeASCIICapable; - alertController.textFields[0].autocorrectionType = UITextAutocorrectionTypeNo; - alertController.textFields[0].spellCheckingType = UITextSpellCheckingTypeNo; - alertController.textFields[1].keyboardType = UIKeyboardTypeASCIICapable; - alertController.textFields[1].autocorrectionType = UITextAutocorrectionTypeNo; - alertController.textFields[1].spellCheckingType = UITextSpellCheckingTypeNo;*/ - - NSString *cmdString = [alertController.textFields[0].text uppercaseString]; // convert to uppercase - NSString *buttonLabel = alertController.textFields[1].text; - NSString *minStickOffsetString = alertController.textFields[2].text; - NSString *widgetShape = [alertController.textFields[3].text lowercaseString]; - - - if([buttonLabel isEqualToString:@""]) buttonLabel = [[cmdString lowercaseString] capitalizedString]; - bool noValidKeyboardString = [CommandManager.shared extractKeyStringsFromComboCommandFrom:cmdString] == nil; // this is a invalid string. - bool noValidSuperComboButtonString = [CommandManager.shared extractSinglCmdStringsFromComboKeysFrom:cmdString] == nil; // this is a invalid string. - bool noValidMouseButtonString = ![CommandManager.mouseButtonMappings.allKeys containsObject:cmdString]; - bool noValidTouchPadString = ![CommandManager.touchPadCmds containsObject:cmdString]; - bool noValidOscButtonString = ![CommandManager.oscButtonMappings.allKeys containsObject:cmdString]; - bool noValidSpecialButtonString = ![CommandManager.specialOverlayButtonCmds containsObject:cmdString]; - - bool invalidInput = noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString && noValidSuperComboButtonString; - - if([widgetShape isEqualToString:@"r"]) widgetShape = @"round"; - else if([widgetShape isEqualToString:@"s"]) widgetShape = @"square"; - else if([widgetShape isEqualToString:@""]) widgetShape = @"default"; - else invalidInput = true; - - NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; - NSString *trimmedString = [minStickOffsetString stringByTrimmingCharactersInSet:nonDigitCharacterSet]; - if(trimmedString.length != minStickOffsetString.length) invalidInput = true; - minStickOffsetString = trimmedString; - - if(invalidInput) { - [self presentInvalidWidgetCommandAlert]; - return; - } - - //saving & present the keyboard button: - OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:cmdString buttonLabel:buttonLabel shape:widgetShape]; - widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place - widgetView.minStickOffset = [minStickOffsetString floatValue]; - [self.OnScreenWidgetViews addObject:widgetView]; - // Add the widgetView to the view controller's view - [self.view addSubview:widgetView]; - [widgetView setLocationWithXOffset:50 yOffset:50]; - [widgetView resizeWidgetView]; + [widgetInitParams setObject: alertController.textFields[0].text forKey:@"cmdString"]; // convert to uppercase + [widgetInitParams setObject: alertController.textFields[1].text forKey:@"buttonLabel"]; // convert to uppercase + [widgetInitParams setObject: alertController.textFields[2].text forKey:@"minStickOffsetString"]; // convert to uppercase + [widgetInitParams setObject: alertController.textFields[3].text forKey:@"shape"]; // convert to uppercase + [self createWidgetFromParams:widgetInitParams]; }]; [alertController addAction:readInstruction]; [alertController addAction:cancelAction]; @@ -474,6 +434,146 @@ } +- (IBAction) editTapped:(id)sender{ + + NSMutableDictionary* widgetInitParams = [NSMutableDictionary dictionary]; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@""] + message:[LocalizationHelper localizedStringForKey:@"Edit Selected Widget"] + preferredStyle:UIAlertControllerStyleAlert]; + + if(self->selectedWidgetView == nil) return; + + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = [LocalizationHelper localizedStringForKey:@"Command"]; + textField.keyboardType = UIKeyboardTypeASCIICapable; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.spellCheckingType = UITextSpellCheckingTypeNo; + textField.text = [self->selectedWidgetView.cmdString lowercaseString]; + }]; + + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = [LocalizationHelper localizedStringForKey:@"Alias label (optional)"]; + textField.keyboardType = UIKeyboardTypeASCIICapable; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.spellCheckingType = UITextSpellCheckingTypeNo; + textField.text = self->selectedWidgetView.buttonLabel; + }]; + + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = [LocalizationHelper localizedStringForKey:@"Minimum stick offset (0~32766)"]; + textField.keyboardType = UIKeyboardTypeASCIICapable; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.spellCheckingType = UITextSpellCheckingTypeNo; + if(self->selectedWidgetView.minStickOffset > 0) textField.text = [NSString stringWithFormat:@"%d", (int)self->selectedWidgetView.minStickOffset]; + }]; + + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = [LocalizationHelper localizedStringForKey:@"Shape (r - round, s - square)"]; + textField.keyboardType = UIKeyboardTypeASCIICapable; + textField.autocorrectionType = UITextAutocorrectionTypeNo; + textField.spellCheckingType = UITextSpellCheckingTypeNo; + textField.text = self->selectedWidgetView.shape; + if([self->selectedWidgetView.shape isEqualToString: @"largeSquare"]) textField.enabled = false; + }]; + + + UIAlertAction *createNewAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Create New"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [widgetInitParams setObject: alertController.textFields[0].text forKey:@"cmdString"]; + [widgetInitParams setObject: alertController.textFields[1].text forKey:@"buttonLabel"]; + [widgetInitParams setObject: alertController.textFields[2].text forKey:@"minStickOffsetString"]; + [widgetInitParams setObject: alertController.textFields[3].text forKey:@"shape"]; + [self updateWidget:self->selectedWidgetView byParams:widgetInitParams createNew:true]; + }]; + + UIAlertAction *modifyAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Modify"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [widgetInitParams setObject: alertController.textFields[0].text forKey:@"cmdString"]; + [widgetInitParams setObject: alertController.textFields[1].text forKey:@"buttonLabel"]; + [widgetInitParams setObject: alertController.textFields[2].text forKey:@"minStickOffsetString"]; + [widgetInitParams setObject: alertController.textFields[3].text forKey:@"shape"]; + [self updateWidget:self->selectedWidgetView byParams:widgetInitParams createNew:false]; + }]; + + [alertController addAction:createNewAction]; + [alertController addAction:modifyAction]; + [self presentViewController:alertController animated:YES completion:nil]; +} + +- (bool) isWidgetParamsValid:(NSMutableDictionary* )widgetInitParams{ + NSString *cmdString = [widgetInitParams[@"cmdString"] uppercaseString]; // convert to uppercase + NSString *buttonLabel = widgetInitParams[@"buttonLabel"]; + NSString *minStickOffsetString = widgetInitParams[@"minStickOffsetString"]; + NSString *widgetShape = [widgetInitParams[@"shape"] lowercaseString]; + + widgetInitParams[@"cmdString"] = cmdString; + bool noValidKeyboardString = [CommandManager.shared extractKeyStringsFromComboCommandFrom:cmdString] == nil; // this is a invalid string. + bool noValidSuperComboButtonString = [CommandManager.shared extractSinglCmdStringsFromComboKeysFrom:cmdString] == nil; // this is a invalid string. + bool noValidMouseButtonString = ![CommandManager.mouseButtonMappings.allKeys containsObject:cmdString]; + bool noValidTouchPadString = ![CommandManager.touchPadCmds containsObject:cmdString]; + bool noValidOscButtonString = ![CommandManager.oscButtonMappings.allKeys containsObject:cmdString]; + bool noValidSpecialButtonString = ![CommandManager.specialOverlayButtonCmds containsObject:cmdString]; + bool paramInvalid = noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString && noValidSuperComboButtonString; + + if([buttonLabel isEqualToString:@""]) widgetInitParams[@"buttonLabel"] = [[cmdString lowercaseString] capitalizedString]; + + NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + NSString *trimmedString = [minStickOffsetString stringByTrimmingCharactersInSet:nonDigitCharacterSet]; + if(trimmedString.length != minStickOffsetString.length) paramInvalid = true; + widgetInitParams[@"minStickOffsetString"] = trimmedString; + + NSSet* validShapes = [NSSet setWithObjects:@"round", @"square", @"largesquare", nil]; + if([widgetShape isEqualToString:@"r"]) widgetShape = @"round"; + else if([widgetShape isEqualToString:@"s"]) widgetShape = @"square"; + else if([widgetShape isEqualToString:@""]) widgetShape = @"default"; + else if(![validShapes containsObject:widgetShape]){ + paramInvalid = true;} + widgetInitParams[@"shape"] = widgetShape; + + if(paramInvalid) [self presentInvalidWidgetCommandAlert]; + return !paramInvalid; +} + +- (void) updateWidget:(OnScreenWidgetView* )widget byParams:(NSMutableDictionary* )widgetInitParams createNew:(bool)createNew{ + if(![self isWidgetParamsValid:widgetInitParams]) return; + OnScreenWidgetView* newWidget = [[OnScreenWidgetView alloc] initWithCmdString:widgetInitParams[@"cmdString"] buttonLabel:widgetInitParams[@"buttonLabel"] shape:widgetInitParams[@"shape"]]; //reconstruct widgetView + newWidget.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place + newWidget.widthFactor = widget.widthFactor; + newWidget.heightFactor = widget.heightFactor; + newWidget.borderWidth = widget.borderWidth; + newWidget.sensitivityFactor = widget.sensitivityFactor; + newWidget.stickIndicatorOffset = widget.stickIndicatorOffset; + newWidget.minStickOffset = [widgetInitParams[@"minStickOffsetString"] floatValue]; + // Add the widgetView to the view controller's view + [self.view addSubview:newWidget]; + if(createNew) [newWidget setLocationWithPosition:CGPointMake(50, 50)]; + else [newWidget setLocationWithPosition:widget.center]; + [newWidget resizeWidgetView]; // resize must be called after relocation + [newWidget adjustTransparencyWithAlpha:widget.backgroundAlpha]; + [newWidget adjustBorderWithWidth:widget.borderWidth]; + if(!createNew) [self.OnScreenWidgetViews removeObject:widget]; + [self.OnScreenWidgetViews addObject:newWidget]; + if(!createNew) [widget removeFromSuperview]; + self->selectedWidgetView = newWidget; +} + + +- (void) createWidgetFromParams: (NSMutableDictionary*) widgetInitParams{ + if(![self isWidgetParamsValid:widgetInitParams]) return; + //saving & present the keyboard button: + OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:widgetInitParams[@"cmdString"] buttonLabel:widgetInitParams[@"buttonLabel"] shape:widgetInitParams[@"shape"]]; + widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place + widgetView.minStickOffset = [widgetInitParams[@"minStickOffsetString"] floatValue]; + [self.OnScreenWidgetViews addObject:widgetView]; + // Add the widgetView to the view controller's view + [self.view addSubview:widgetView]; + [widgetView setLocationWithPosition:CGPointMake(50, 50)]; + [widgetView resizeWidgetView]; +} + /* show pop up notification that lets users choose to save the current OSC layout configuration as a profile they can load when they want. User can also choose to cancel out of this pop up */ - (IBAction) saveTapped:(id)sender { diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m index 2bb5c64a..3ad06bab 100644 --- a/Limelight/ViewControllers/SettingsViewController.m +++ b/Limelight/ViewControllers/SettingsViewController.m @@ -602,8 +602,9 @@ BOOL isCustomResolution(CGSize res) { } - (void)showCustomOSCTip { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Rebase in Stream View"] - message:[LocalizationHelper localizedStringForKey:@"Tap %d fingers to change layout in stream view, or change the number of fingers required to:", self->oscLayoutFingers] + NSString* edgeSide = self.slideToSettingsScreenEdgeSelector.selectedSegmentIndex == 1 ? [LocalizationHelper localizedStringForKey:@"left"] : [LocalizationHelper localizedStringForKey:@"right"]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Rebase in Streaming"] + message:[LocalizationHelper localizedStringForKey:@"Open widget tool in streaming by:\nSliding from %@ screen edge to open cmd tool.\nOr tap %d fingers on stream view, number of fingers required:", edgeSide, self->oscLayoutFingers] preferredStyle:UIAlertControllerStyleAlert]; [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { @@ -612,7 +613,7 @@ BOOL isCustomResolution(CGSize res) { }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"] - style:UIAlertActionStyleCancel + style:UIAlertActionStyleDefault handler:nil]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"] diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index f7277533..dc532ae3 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -70,6 +70,7 @@ CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer; CustomTapGestureRecognizer *_oscLayoutTapRecoginizer; LayoutOnScreenControlsViewController *_layoutOnScreenControlsVC; + CommandManagerViewController* cmdManViewController; #endif } @@ -79,6 +80,7 @@ - (void)configOscLayoutTool{ if([self isOscLayoutToolEnabled]){ + if(![cmdManViewController.specialEntries containsObject:@"widgetTool"]) [cmdManViewController.specialEntries insertObject:@"widgetTool" atIndex:0]; _oscLayoutTapRecoginizer = [[CustomTapGestureRecognizer alloc] initWithTarget:self action:@selector(layoutOSC)]; _oscLayoutTapRecoginizer.numberOfTouchesRequired = _settings.oscLayoutToolFingers.intValue; //tap a predefined number of fingers to open osc layout tool _oscLayoutTapRecoginizer.tapDownTimeThreshold = 0.2; @@ -102,10 +104,10 @@ _layoutOnScreenControlsVC.view.backgroundColor = UIColor.clearColor; _layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationOverCurrentContext; } + else [cmdManViewController.specialEntries removeObject:@"widgetTool"]; } - (void)presentCommandManagerViewController{ - CommandManagerViewController* cmdManViewController = [[CommandManagerViewController alloc] init]; [self presentViewController:cmdManViewController animated:YES completion:nil]; } @@ -240,7 +242,11 @@ selector:@selector(expandSettingsView) // //force expand settings view to update resolution table, and all setting includes current fullscreen resolution will be updated. name:@"SettingsOverlayButtonPressedNotification" object:nil]; - + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(layoutOSC) // //force expand settings view to update resolution table, and all setting includes current fullscreen resolution will be updated. + name:@"OscLayoutToolOpenedFromCmdToolNotification" + object:nil]; #endif } @@ -291,6 +297,8 @@ _streamView = [[StreamView alloc] initWithFrame:self.view.frame]; + cmdManViewController = [[CommandManagerViewController alloc] init]; + /* _settings.externalDisplayMode.intValue: 0 - stage manager diff --git a/Limelight/mul.lproj/iPad.xcstrings b/Limelight/mul.lproj/iPad.xcstrings index d960aef9..6b5f12ee 100644 --- a/Limelight/mul.lproj/iPad.xcstrings +++ b/Limelight/mul.lproj/iPad.xcstrings @@ -721,18 +721,6 @@ } } }, - "bMH-Yb-sb6.normalTitle" : { - "comment" : "Class = \"UIButton\"; normalTitle = \"+\"; ObjectID = \"bMH-Yb-sb6\";", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "+" - } - } - } - }, "bw9-4S-s2V.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; bw9-4S-s2V.segmentTitles[0] = \"No\"; ObjectID = \"bw9-4S-s2V\";", "extractionState" : "stale", @@ -1417,6 +1405,18 @@ } } }, + "fKN-ja-TRi.normalTitle" : { + "comment" : "Class = \"UIButton\"; normalTitle = \"Add\"; ObjectID = \"fKN-ja-TRi\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add" + } + } + } + }, "FwK-oG-vuz.text" : { "comment" : "Class = \"UILabel\"; text = \"Reverse Wheel Direction for Physical Mouse \"; ObjectID = \"FwK-oG-vuz\";", "extractionState" : "extracted_with_value", @@ -3127,6 +3127,18 @@ } } }, + "R45-lM-YPl.normalTitle" : { + "comment" : "Class = \"UIButton\"; normalTitle = \"Edit\"; ObjectID = \"R45-lM-YPl\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit" + } + } + } + }, "RRk-03-hIr.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; RRk-03-hIr.segmentTitles[0] = \"No\"; ObjectID = \"RRk-03-hIr\";", "extractionState" : "extracted_with_value", diff --git a/Limelight/mul.lproj/iPhone.xcstrings b/Limelight/mul.lproj/iPhone.xcstrings index 0aeec072..c6268720 100644 --- a/Limelight/mul.lproj/iPhone.xcstrings +++ b/Limelight/mul.lproj/iPhone.xcstrings @@ -301,6 +301,18 @@ } } }, + "4pE-po-Zn1.normalTitle" : { + "comment" : "Class = \"UIButton\"; normalTitle = \"Edit\"; ObjectID = \"4pE-po-Zn1\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit" + } + } + } + }, "4Qy-hB-oa5.text" : { "comment" : "Class = \"UILabel\"; text = \"External Display Mode\"; ObjectID = \"4Qy-hB-oa5\";", "extractionState" : "extracted_with_value", @@ -2329,6 +2341,18 @@ } } }, + "Up4-28-sgF.normalTitle" : { + "comment" : "Class = \"UIButton\"; normalTitle = \"Add\"; ObjectID = \"Up4-28-sgF\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add" + } + } + } + }, "VGn-Oc-mKc.text" : { "comment" : "Class = \"UILabel\"; text = \"Local Mouse Pointer\"; ObjectID = \"VGn-Oc-mKc\";", "extractionState" : "extracted_with_value",