Merge remote-tracking branch 'refs/remotes/upstream/Integration' into feat/frame_pacing_metal

This commit is contained in:
Acaki
2025-07-20 17:07:27 +08:00
31 changed files with 897 additions and 477 deletions
+8
View File
@@ -10,6 +10,8 @@
1829CE3F2E2154DF0074A99A /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1829CE3E2E2154DF0074A99A /* SceneDelegate.m */; };
18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */; };
187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */; };
1888C1922E24C33100108730 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1888C1912E24C33100108730 /* AboutViewController.swift */; };
1888C1932E24C41000108730 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1888C1902E24C2C800108730 /* AboutView.swift */; };
189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; };
840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */; };
841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 841BAC912DE35D6B00DA639E /* ThemeManager.m */; };
@@ -156,6 +158,8 @@
18341A032DEB662C0094E5F2 /* FixedTintImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedTintImageView.m; sourceTree = "<group>"; };
187F4B232E04633100EECC59 /* WidgetPanelStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPanelStackView.h; sourceTree = "<group>"; };
187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WidgetPanelStackView.m; sourceTree = "<group>"; };
1888C1902E24C2C800108730 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
1888C1912E24C33100108730 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
189AD2962DE743B700E24B88 /* HostCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HostCollectionViewController.h; sourceTree = "<group>"; };
189AD2992DE7446000E24B88 /* HostCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HostCollectionViewController.m; sourceTree = "<group>"; };
18BF141D2E0D7A00009DEE4F /* VoidLink v1.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "VoidLink v1.0.xcdatamodel"; sourceTree = "<group>"; };
@@ -617,6 +621,8 @@
FBDE86DF19F7A837001C18A8 /* UIComputerView.m */,
FBDE86E419F82297001C18A8 /* UIAppView.h */,
FBDE86E519F82297001C18A8 /* UIAppView.m */,
1888C1902E24C2C800108730 /* AboutView.swift */,
1888C1912E24C33100108730 /* AboutViewController.swift */,
FB89463719F6473800339C8A /* Launch Screen.xib */,
FB290D0819B2C406004C83CF /* Images.xcassets */,
FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */,
@@ -1051,6 +1057,7 @@
DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */,
FB89462E19F646E200339C8A /* PairManager.m in Sources */,
FB9AFD371A7E02DB00872C98 /* HttpRequest.m in Sources */,
1888C1932E24C41000108730 /* AboutView.swift in Sources */,
FB4678ED1A50C40900377732 /* OnScreenControls.m in Sources */,
84E1CDFA2C4FE83400547904 /* SwiftLocalizationHelper.swift in Sources */,
FB290D0019B2C406004C83CF /* main.m in Sources */,
@@ -1060,6 +1067,7 @@
84351B0E2C29ACCB00A77989 /* LayoutOnScreenControls.m in Sources */,
FB89462A19F646E200339C8A /* ControllerSupport.m in Sources */,
FB9AFD3D1A7E111600872C98 /* AppAssetResponse.m in Sources */,
1888C1922E24C33100108730 /* AboutViewController.swift in Sources */,
FBD349621A0089F6002D2A60 /* DataManager.m in Sources */,
97E20DE72E216510000D5DCA /* ImGuiRenderer.mm in Sources */,
FB4A23B81A9D3637004D2EF2 /* LoadingFrameViewController.m in Sources */,
BIN
View File
Binary file not shown.
+100
View File
@@ -0,0 +1,100 @@
//
// AboutView.swift
// VoidLink
//
// Created by True砖家 on 5/18/25.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
import SwiftUI
@available(iOS 13.0, *)
public struct AboutView: View {
@Environment(\.presentationMode) var presentationMode
public var body: some View {
VStack(spacing: 10) {
// App 图标
Image(uiImage: UIImage(named: "AppIconMedium") ?? UIImage())
.resizable()
.frame(width: 80, height: 80)
.clipShape(RoundedRectangle(cornerRadius: 36))
// App 名称
Text(Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "App Name")
.font(.title)
.bold()
// 版本号
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
Text("Version \(version)")
.font(.subheadline)
.foregroundColor(.gray)
}
// 说明文字
Text(SwiftLocalizationHelper.localizedString(forKey: "From the player community, to the player community."))
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
if Locale.preferredLanguages.first!.hasPrefix("zh-Hans") {
if #available(iOS 14.0, *) {
Text("闽ICP备17012590号-3A\n主办单位:福州创图信息技术有限公司").font(.caption2)
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
.padding()
} else {
Text("闽ICP备17012590号-3A\n主办单位:福州创图信息技术有限公司").font(.caption)
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
.padding()
}
}
// 链接按钮
if #available(iOS 14.0, *) {
Link(SwiftLocalizationHelper.localizedString(forKey: "Join us"), destination: URL(string: "https://example.com")!)
.padding(.top, 10)
Spacer()
// OK 按钮
Button("OK") {
presentationMode.wrappedValue.dismiss()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.frame(height: 46)
//.frame(width: 100)
.cornerRadius(12)
} else {
HStack(spacing: 20) {
Button(SwiftLocalizationHelper.localizedString(forKey: "Join us")) {
// 打开链接
if let url = URL(string: "https://example.com") {
UIApplication.shared.open(url)
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.frame(minWidth: 100)
Button("OK") {
presentationMode.wrappedValue.dismiss()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.frame(minWidth: 100)
}
.padding(.top, 10)
}
}
.padding()
}
}
+41
View File
@@ -0,0 +1,41 @@
//
// AboutViewController.swift
// VoidLink
//
// Created by Weimin on 2025/7/14.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
import SwiftUI
@available(iOS 13.0, *)
@objc class AboutViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let hostingVC = UIHostingController(rootView: AboutView())
addChild(hostingVC)
hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingVC.view)
hostingVC.didMove(toParent: self)
NSLayoutConstraint.activate([
hostingVC.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
// 🎯 设置弹窗大小
preferredContentSize = CGSize(width: 600, height: 450)
//preferredContentSize = CGSize(width:150, height:120 )
// ⚠️ 一定要设置以下样式才会生效 preferredContentSize
modalPresentationStyle = .formSheet
}
}
func isIPhone() -> Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
+7 -6
View File
@@ -1349,7 +1349,7 @@
</constraints>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hKp-ds-86I" userLabel="Chevron View">
<rect key="frame" x="624" y="50" width="52" height="30"/>
<rect key="frame" x="724" y="50" width="52" height="30"/>
<subviews>
<imageView clipsSubviews="YES" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ChevronCompactUp" translatesAutoresizingMaskIntoConstraints="NO" id="iVZ-Fd-dY7">
<rect key="frame" x="0.0" y="0.0" width="52" height="30"/>
@@ -1391,7 +1391,7 @@
<constraint firstItem="hKp-ds-86I" firstAttribute="top" secondItem="YLK-IE-ajS" secondAttribute="bottom" id="RRf-mC-xx8"/>
<constraint firstAttribute="height" constant="50" id="gfG-JH-DGG"/>
<constraint firstAttribute="bottom" secondItem="YLK-IE-ajS" secondAttribute="bottom" id="gkv-Gt-6iw"/>
<constraint firstItem="hKp-ds-86I" firstAttribute="centerX" secondItem="fZU-G0-fgM" secondAttribute="centerX" id="uZV-o0-RdA"/>
<constraint firstItem="hKp-ds-86I" firstAttribute="centerX" secondItem="fZU-G0-fgM" secondAttribute="centerX" constant="100" id="uZV-o0-RdA"/>
<constraint firstItem="YLK-IE-ajS" firstAttribute="centerX" secondItem="fZU-G0-fgM" secondAttribute="centerX" id="xm7-a0-8G7"/>
</constraints>
</view>
@@ -1593,6 +1593,7 @@
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.5" colorSpace="custom" customColorSpace="displayP3"/>
<constraints>
<constraint firstAttribute="width" constant="650" id="Rav-Dp-9pI"/>
</constraints>
@@ -1748,7 +1749,7 @@
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<image name="video.and.waveform" catalog="system" width="128" height="94"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="secondarySystemGroupedBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1757,13 +1758,13 @@
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411764705879" green="0.51764705882352946" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray3Color">
<color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+6 -5
View File
@@ -314,7 +314,7 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="7" translatesAutoresizingMaskIntoConstraints="NO" id="BVp-3O-vZ7" userLabel="widgetPanelStack" customClass="WidgetPanelStackView">
<rect key="frame" x="325" y="226" width="550" height="298.5"/>
<rect key="frame" x="375" y="132" width="550" height="298.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Profile:" textAlignment="center" lineBreakMode="tailTruncation" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f46-Pt-0Pv">
<rect key="frame" x="0.0" y="0.0" width="550" height="28"/>
@@ -566,6 +566,7 @@
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="550" id="fNg-fG-rV5"/>
</constraints>
@@ -1683,16 +1684,16 @@
<image name="trash.fill" catalog="system" width="117" height="128"/>
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411764705879" green="0.51764705882352946" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray2Color">
<color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+23 -11
View File
@@ -50,13 +50,16 @@
static const float REFRESH_CYCLE = 2.0f;
- (BOOL)isIPhone{
return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
buttonLabelFontSize = 15*_sizeFactor;
longPressFired = false;
computerIconMonitorCenterYOffset = -4.3523*_sizeFactor;
computerIconMonitorCenterYOffset = [self isIPhone] ? -2.75*_sizeFactor : -3.2*_sizeFactor;
iconAndButtonSpacing = 37*_sizeFactor;
buttonHeight = 39*_sizeFactor;
defaultBlue = [ThemeManager appPrimaryColor];
@@ -94,6 +97,8 @@ static const float REFRESH_CYCLE = 2.0f;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
@@ -190,7 +195,7 @@ static const float REFRESH_CYCLE = 2.0f;
self.hostIconView.translatesAutoresizingMaskIntoConstraints = NO;
self.hostIconView.contentMode = UIViewContentModeScaleAspectFit;
if (@available(iOS 13.0, *)) {
self.hostIconView.image = [UIImage systemImageNamed:@"display"];
self.hostIconView.image = [[UIImage imageNamed:@"display"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
} else {
self.hostIconView.image = [UIImage imageNamed:@"Computer"];
[NSLayoutConstraint activateConstraints:@[
@@ -203,8 +208,8 @@ static const float REFRESH_CYCLE = 2.0f;
[NSLayoutConstraint activateConstraints:@[
[self.hostIconView.centerXAnchor constraintEqualToAnchor:self.iconBackgroundView.centerXAnchor constant:0],
[self.hostIconView.centerYAnchor constraintEqualToAnchor:self.iconBackgroundView.centerYAnchor constant:0],
[self.hostIconView.heightAnchor constraintEqualToConstant:63*_sizeFactor],
[self.hostIconView.widthAnchor constraintEqualToConstant:63*_sizeFactor],
[self.hostIconView.heightAnchor constraintEqualToConstant:53.9*_sizeFactor],
[self.hostIconView.widthAnchor constraintEqualToConstant:53.9*_sizeFactor],
]];
// [self.iconBackgroundView layoutIfNeeded];
// [self.iconImageView layoutIfNeeded];
@@ -231,8 +236,9 @@ static const float REFRESH_CYCLE = 2.0f;
// lockIcon
lockIconView =[[UIImageView alloc] init];
lockIconView.translatesAutoresizingMaskIntoConstraints = NO;
lockIconView.contentMode = UIViewContentModeScaleAspectFit;
if (@available(iOS 13.0, *)) {
lockIconView.image = [UIImage systemImageNamed:@"lock.fill"];
lockIconView.image = [[UIImage imageNamed:@"lock.fill"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
} else {
lockIconView.image = [UIImage imageNamed:@"LockedOverlayIcon"];
computerIconMonitorCenterYOffset = -5 * _sizeFactor;
@@ -242,8 +248,8 @@ static const float REFRESH_CYCLE = 2.0f;
[NSLayoutConstraint activateConstraints:@[
[lockIconView.centerXAnchor constraintEqualToAnchor:_iconBackgroundView.centerXAnchor constant:0],
[lockIconView.centerYAnchor constraintEqualToAnchor:_iconBackgroundView.centerYAnchor constant:computerIconMonitorCenterYOffset],
[lockIconView.widthAnchor constraintEqualToConstant:22*_sizeFactor],
[lockIconView.heightAnchor constraintEqualToConstant:22*_sizeFactor]
[lockIconView.widthAnchor constraintEqualToConstant:18.5*_sizeFactor],
[lockIconView.heightAnchor constraintEqualToConstant:18.5*_sizeFactor]
]];
lockIconView.hidden = true;
@@ -567,8 +573,12 @@ static const float REFRESH_CYCLE = 2.0f;
} else {
self.statusIcon.hidden = YES;
}
bool hostPaired = host.pairState == PairStatePaired;
_hostIconView.tintColor = [ThemeManager lowProfileGray];
lockIconView.hidden = YES;
lockIconView.tintColor = [ThemeManager lowProfileGray];
lockIconView.hidden = hostPaired;
_appButton.hidden = YES;
_launchButton.hidden = YES;
_pairButton.hidden = YES;
@@ -577,13 +587,15 @@ static const float REFRESH_CYCLE = 2.0f;
if (@available(iOS 13.0, *)) {
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.45 weight:UIImageSymbolWeightBold];
UIImage *templateImage = [UIImage systemImageNamed:@"power" withConfiguration:config];
UIImage *coloredImage = [templateImage imageWithTintColor:defaultBlue renderingMode:UIImageRenderingModeAlwaysOriginal];
UIImage *coloredImage = [templateImage imageWithTintColor:hostPaired ? defaultBlue : [ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal];
[self.wakeupButton setImage:coloredImage forState:UIControlStateNormal];
} else {
// Fallback on earlier versions
}
self.wakeupButton.backgroundColor = [ThemeManager textTintColorWithAlpha];
[self.wakeupButton setTitleColor:defaultBlue forState:UIControlStateNormal];
self.wakeupButton.backgroundColor = hostPaired ? [ThemeManager textTintColorWithAlpha] : [[ThemeManager textColorGray] colorWithAlphaComponent:0.2];
[self.wakeupButton setTitleColor: hostPaired ? defaultBlue : [ThemeManager textColorGray] forState:UIControlStateNormal];
break;
case StateUnknown:
_hostSpinner.color = [UIColor whiteColor];
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AppIconMedium.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "display.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 341-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 232.812 203.613">
<g>
<rect height="203.613" opacity="0" width="232.812" x="0" y="0"/>
<path d="M22.7539 153.223C18.457 153.223 15.7227 150.488 15.7227 146.191L15.7227 29.5898C15.7227 25.1953 18.457 22.5586 22.7539 22.5586L210.059 22.5586C214.355 22.5586 217.09 25.1953 217.09 29.5898L217.09 146.191C217.09 150.488 214.355 153.223 210.059 153.223Z" fill="white" fill-opacity="0.25"/>
<path d="M22.4609 168.945L210.254 168.945C224.316 168.945 232.812 160.449 232.812 146.387L232.812 29.2969C232.812 15.2344 224.316 6.83594 210.254 6.83594L22.4609 6.83594C8.49609 6.83594 0 15.2344 0 29.2969L0 146.387C0 160.449 8.49609 168.945 22.4609 168.945ZM22.7539 153.223C18.457 153.223 15.7227 150.488 15.7227 146.191L15.7227 29.5898C15.7227 25.1953 18.457 22.5586 22.7539 22.5586L210.059 22.5586C214.355 22.5586 217.09 25.1953 217.09 29.5898L217.09 146.191C217.09 150.488 214.355 153.223 210.059 153.223ZM85.5469 192.188L147.266 192.188L147.266 167.676L85.5469 167.676ZM84.9609 203.613L147.852 203.613C152.148 203.613 155.664 200.098 155.664 195.703C155.664 191.309 152.148 187.793 147.852 187.793L84.9609 187.793C80.6641 187.793 77.0508 191.309 77.0508 195.703C77.0508 200.098 80.6641 203.613 84.9609 203.613Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon-pc-app.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 34C26.4183 34 30 37.5817 30 42V48C30 52.4183 26.4183 56 22 56H16C11.7199 56 8.22523 52.6389 8.01074 48.4121L8 48V42C8 37.5817 11.5817 34 16 34H22ZM16 38C13.7909 38 12 39.7909 12 42V48L12.0049 48.2061C12.1087 50.2512 13.7488 51.8913 15.7939 51.9951L16 52H22L22.2061 51.9951C24.3194 51.8879 26 50.14 26 48V42C26 39.7909 24.2091 38 22 38H16Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48 34C52.4183 34 56 37.5817 56 42V48C56 52.4183 52.4183 56 48 56H42C37.7199 56 34.2252 52.6389 34.0107 48.4121L34 48V42C34 37.5817 37.5817 34 42 34H48ZM42 38C39.7909 38 38 39.7909 38 42V48L38.0049 48.2061C38.1087 50.2512 39.7488 51.8913 41.7939 51.9951L42 52H48L48.2061 51.9951C50.3194 51.8879 52 50.14 52 48V42C52 39.7909 50.2091 38 48 38H42Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 8C26.4183 8 30 11.5817 30 16V22C30 26.4183 26.4183 30 22 30H16C11.7199 30 8.22523 26.6389 8.01074 22.4121L8 22V16C8 11.5817 11.5817 8 16 8H22ZM16 12C13.7909 12 12 13.7909 12 16V22L12.0049 22.2061C12.1087 24.2512 13.7488 25.8913 15.7939 25.9951L16 26H22L22.2061 25.9951C24.3194 25.8879 26 24.14 26 22V16C26 13.7909 24.2091 12 22 12H16Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48 8C52.4183 8 56 11.5817 56 16V22C56 26.4183 52.4183 30 48 30H42C37.7199 30 34.2252 26.6389 34.0107 22.4121L34 22V16C34 11.5817 37.5817 8 42 8H48ZM42 12C39.7909 12 38 13.7909 38 16V22L38.0049 22.2061C38.1087 24.2512 39.7488 25.8913 41.7939 25.9951L42 26H48L48.2061 25.9951C50.3194 25.8879 52 24.14 52 22V16C52 13.7909 50.2091 12 48 12H42Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "lock.fill.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 341-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 133.301 196.777">
<g>
<rect height="196.777" opacity="0" width="133.301" x="0" y="0"/>
<path d="M21.9727 191.602L111.328 191.602C125.684 191.602 133.301 183.789 133.301 168.359L133.301 101.074C133.301 85.6445 125.684 77.9297 111.328 77.9297L21.9727 77.9297C7.61719 77.9297 0 85.6445 0 101.074L0 168.359C0 183.789 7.61719 191.602 21.9727 191.602ZM17.0898 85.4492L32.6172 85.4492L32.6172 52.4414C32.6172 27.832 48.3398 14.7461 66.6016 14.7461C84.8633 14.7461 100.781 27.832 100.781 52.4414L100.781 85.4492L116.211 85.4492L116.211 54.5898C116.211 17.8711 92.1875 0 66.6016 0C41.1133 0 17.0898 17.8711 17.0898 54.5898Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 947 B

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "play.circle.fill.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 341-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 21.6992 21.3379">
<g>
<rect height="21.3379" opacity="0" width="21.6992" x="0" y="0"/>
<path d="M21.3379 10.6641C21.3379 16.5527 16.5625 21.3281 10.6641 21.3281C4.77539 21.3281 0 16.5527 0 10.6641C0 4.77539 4.77539 0 10.6641 0C16.5625 0 21.3379 4.77539 21.3379 10.6641ZM7.31445 7.51953L7.31445 13.7793C7.31445 14.8926 8.38867 15.3809 9.31641 14.834L14.4922 11.7871C15.3711 11.2598 15.3613 10.0293 14.4922 9.52148L9.31641 6.47461C8.36914 5.9082 7.31445 6.40625 7.31445 7.51953Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 829 B

+1 -1
View File
@@ -47,7 +47,7 @@
self->streamView = view;
// upper screen check
EDGE_TOLERANCE = 15.0;
EDGE_TOLERANCE = 10.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
self->touchPointSpawnedAtUpperScreenEdge = false;
+15 -17
View File
@@ -19,7 +19,6 @@
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
bool touchPointSpawnedAtUpperScreenEdge;
CGFloat pointerVelocityDividerLocationByPoints;
uint16_t touchMoveEventIntervalUs;
@@ -35,7 +34,8 @@
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
NSMutableSet<NSNumber *> *unassignedPointerIds;
NSMutableSet *blacklistedTouches;
NSMutableDictionary *pointerObjDict;
CGFloat slideGestureVerticalThreshold;
@@ -59,9 +59,8 @@
[self->pointerIdPool addObject:@(i)];
}
self->activePointerIds = [NSMutableSet set];
// self->excludedPointerIds = [[NSMutableSet alloc] init];
self->touchPointSpawnedAtUpperScreenEdge = false;
self->blacklistedTouches = [NSMutableSet set];
self->asyncNativeTouch = settings.asyncNativeTouchPriority.intValue != AsyncNativeTouchOff;
switch(settings.asyncNativeTouchPriority.intValue){
@@ -86,7 +85,7 @@
self->pointerObjDict = [NSMutableDictionary dictionary];
EDGE_TOLERANCE = 15.0;
EDGE_TOLERANCE = 10.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
@@ -156,28 +155,27 @@
// generate & populate pointerId into NSDict & NSSet, called in touchesBegan
- (void)handleTouchDown:(UITouch*)touch{
//populate pointerId
uintptr_t memAddrValue = (uintptr_t)touch;
NSNumber* touchAddrObj = @((uintptr_t)touch);
unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds
[unassignedPointerIds minusSet:activePointerIds];
uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue];
[pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)];
[pointerIdDict setObject:@(pointerId) forKey:touchAddrObj];
[activePointerIds addObject:@(pointerId)];
//check if touch point is spawned on the left or right upper half screen edges, event to remote PC. this is for better handling in-stream slide gesture
CGPoint initialPoint = [touch locationInView:self->streamView];
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
self->touchPointSpawnedAtUpperScreenEdge = true;
// [excludedPointerIds addObject:@(pointerId)];
[blacklistedTouches addObject:touchAddrObj];
}
}
// remove pointerId in touchesEnded or touchesCancelled
- (void)removePointerId:(UITouch*)touch{
uintptr_t memAddrValue = (uintptr_t)touch;
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)];
NSNumber* touchAddrObj = @((uintptr_t)touch);
NSNumber* pointerIdObj = [pointerIdDict objectForKey:touchAddrObj];
if(pointerIdObj != nil){
[activePointerIds removeObject:pointerIdObj];
[pointerIdDict removeObjectForKey:@(memAddrValue)];
[pointerIdDict removeObjectForKey:touchAddrObj];
// if([excludedPointerIds containsObject:pointerIdObj]) [excludedPointerIds removeObject:pointerIdObj]; // remove pointer id from excludedPointerId NSSet
}
}
@@ -190,7 +188,8 @@
- (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash!
//if(touchPointSpawnedAtUpperScreenEdge && touchType != LI_TOUCH_EVENT_UP) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash
if([blacklistedTouches containsObject:@((uintptr_t)touch)]) return;
CGPoint targetCoords;
//NSLog(@"selecting coords: %d", touch.phase == UITouchPhaseMoved);
@@ -263,8 +262,9 @@
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
[self->blacklistedTouches removeObject:@((uintptr_t)touch)];
}
if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) self->touchPointSpawnedAtUpperScreenEdge = false; //bugbugbugbugbugbugbugbug
//if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count])
});
else{
for (UITouch* touch in touches){
@@ -274,7 +274,6 @@
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
}
if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) self->touchPointSpawnedAtUpperScreenEdge = false;
}
}
@@ -308,7 +307,6 @@
- (CGPoint)selectCoordsFor:(UITouch *)touch{
NativeTouchPointer *pointer = [pointerObjDict objectForKey:@((uintptr_t)touch)];
if(pointer == nil) return CGPointMake(0, 0);
// NSLog(@"using relative Coords: intialX %f, divider loc %f", pointer.initialPoint.x, pointerVelocityDividerLocationByPoints);
return pointer.useRelativeCoords ? pointer.latestRelativePoint : pointer.latestPoint;
}
+8 -3
View File
@@ -1134,10 +1134,15 @@ import UIKit
let currentLocation: CGPoint
if OnScreenWidgetView.editMode {currentLocation = touch.location(in: superview)}
else {currentLocation = touch.location(in: self)}
let offsetX = currentLocation.x - latestTouchLocation.x;
let offsetY = currentLocation.y - latestTouchLocation.y;
let outOfBoundsX = center.x+offsetX > (self.superview?.bounds.width)! || center.x+offsetX < 0
let outOfBoundsY = center.y+offsetY > (self.superview?.bounds.height)! || center.y+offsetY < 0
center = CGPoint(x: outOfBoundsX ? center.x : center.x+offsetX, y: outOfBoundsY ? center.y : center.y+offsetY)
let offsetX = currentLocation.x - latestTouchLocation.x
let offsetY = currentLocation.y - latestTouchLocation.y
center = CGPoint(x: center.x + offsetX, y: center.y + offsetY)
latestTouchLocation = currentLocation
// center = currentLocation;
//NSLog("x coord: %f, y coord: %f", self.frame.origin.x, self.frame.origin.y)
+14 -16
View File
@@ -18,7 +18,6 @@
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
bool touchPointSpawnedAtUpperScreenEdge;
CGFloat pointerVelocityDividerLocationByPoints;
uint16_t touchMoveEventIntervalUs;
@@ -34,7 +33,8 @@
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
NSMutableSet<NSNumber *> *unassignedPointerIds;
NSMutableSet *blacklistedTouches;
NSMutableDictionary *pointerObjDict;
CGFloat slideGestureVerticalThreshold;
@@ -58,9 +58,8 @@
[self->pointerIdPool addObject:@(i)];
}
self->activePointerIds = [NSMutableSet set];
// self->excludedPointerIds = [[NSMutableSet alloc] init];
self->touchPointSpawnedAtUpperScreenEdge = false;
self->blacklistedTouches = [NSMutableSet set];
self->asyncNativeTouch = settings.asyncNativeTouchPriority.intValue != AsyncNativeTouchOff;
switch(settings.asyncNativeTouchPriority.intValue){
@@ -85,7 +84,7 @@
self->pointerObjDict = [NSMutableDictionary dictionary];
EDGE_TOLERANCE = 15.0;
EDGE_TOLERANCE = 10.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
@@ -155,28 +154,27 @@
// generate & populate pointerId into NSDict & NSSet, called in touchesBegan
- (void)handleTouchDown:(UITouch*)touch{
//populate pointerId
uintptr_t memAddrValue = (uintptr_t)touch;
NSNumber* touchAddrObj = @((uintptr_t)touch);
unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds
[unassignedPointerIds minusSet:activePointerIds];
uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue];
[pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)];
[pointerIdDict setObject:@(pointerId) forKey:touchAddrObj];
[activePointerIds addObject:@(pointerId)];
//check if touch point is spawned on the left or right upper half screen edges, event to remote PC. this is for better handling in-stream slide gesture
CGPoint initialPoint = [touch locationInView:self->streamView];
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
self->touchPointSpawnedAtUpperScreenEdge = true;
// [excludedPointerIds addObject:@(pointerId)];
[blacklistedTouches addObject:touchAddrObj];
}
}
// remove pointerId in touchesEnded or touchesCancelled
- (void)removePointerId:(UITouch*)touch{
uintptr_t memAddrValue = (uintptr_t)touch;
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)];
NSNumber* touchAddrObj = @((uintptr_t)touch);
NSNumber* pointerIdObj = [pointerIdDict objectForKey:touchAddrObj];
if(pointerIdObj != nil){
[activePointerIds removeObject:pointerIdObj];
[pointerIdDict removeObjectForKey:@(memAddrValue)];
[pointerIdDict removeObjectForKey:touchAddrObj];
// if([excludedPointerIds containsObject:pointerIdObj]) [excludedPointerIds removeObject:pointerIdObj]; // remove pointer id from excludedPointerId NSSet
}
}
@@ -189,7 +187,8 @@
- (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash!
//if(touchPointSpawnedAtUpperScreenEdge && touchType != LI_TOUCH_EVENT_UP) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash
if([blacklistedTouches containsObject:@((uintptr_t)touch)]) return;
CGPoint targetCoords;
//NSLog(@"selecting coords: %d", touch.phase == UITouchPhaseMoved);
@@ -252,8 +251,8 @@
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
[self->blacklistedTouches removeObject:@((uintptr_t)touch)];
}
if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) self->touchPointSpawnedAtUpperScreenEdge = false;
});
else{
for (UITouch* touch in touches){
@@ -261,7 +260,6 @@
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
}
if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) self->touchPointSpawnedAtUpperScreenEdge = false;
}
}
-1
View File
@@ -19,7 +19,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface RelativeTouchHandler : UIResponder
@property (nonatomic, readonly) CustomTapGestureRecognizer* mouseRightClickTapRecognizer; // this object will be passed to onscreencontrol class for areVirtualControllerTaps flag setting
@property (nonatomic, strong) CADisplayLink *displayLink;
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings;
+3 -5
View File
@@ -12,9 +12,6 @@
#include <Limelight.h>
static const int REFERENCE_WIDTH = 1280;
static const int REFERENCE_HEIGHT = 720;
static const float QUICK_TAP_TIME_INTERVAL = 0.2;
@implementation RelativeTouchHandler {
@@ -63,7 +60,7 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2;
mousePointerTimestamp = 0;
// upper screen check
EDGE_TOLERANCE = 15.0;
EDGE_TOLERANCE = 10.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
self->touchPointSpawnedAtUpperScreenEdge = false;
@@ -238,8 +235,9 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2;
touchLockedForMouseMove = nil;
mousePointerMoved = false; // need to reset this anyway
[self resetAllPressedFlagsForOnScreenWidgetViews]; // reset all pressed flag for on-screen widget views after all fingers lifted from screen.
touchPointSpawnedAtUpperScreenEdge = false;
}
touchPointSpawnedAtUpperScreenEdge = false;
}
File diff suppressed because it is too large Load Diff
+8
View File
@@ -9,6 +9,11 @@
#import <UIKit/UIKit.h>
#import "TemporaryApp.h"
@protocol AppViewUpdateLoopDelegate <NSObject>
- (bool)isInAppView;
@end
@protocol AppCallback <NSObject>
- (void) appClicked:(TemporaryApp*)app view:(UIView*)view;
@@ -22,6 +27,9 @@
@interface UIAppView : UIButton
#endif
@property (nonatomic, weak) id<AppViewUpdateLoopDelegate> updateLoopDelegate; // Delegate property
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback;
- (void) updateAppImage;
+63 -32
View File
@@ -5,6 +5,10 @@
// Created by Diego Waxemberg on 10/22/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
// Created by True砖家 on 2025.7.20.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
#import "UIAppView.h"
#import "AppAssetManager.h"
@@ -29,6 +33,9 @@ static UIImage* noImage;
_callback = callback;
_artCache = cache;
self.layer.cornerRadius = 16;
self.clipsToBounds = YES;
// Cache the NoAppImage ourselves to avoid
// having to load it each time
if (noImage == nil) {
@@ -45,6 +52,7 @@ static UIImage* noImage;
_appImage = [[UIImageView alloc] initWithFrame:self.frame];
[_appImage setImage:noImage];
[self addSubview:_appImage];
// Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer
@@ -115,6 +123,10 @@ static UIImage* noImage;
}
#endif
- (bool)isCurrentApp{
return [_app.id isEqualToString:_app.host.currentGame];
}
- (void) updateAppImage {
if (_appOverlay != nil) {
@@ -129,7 +141,8 @@ static UIImage* noImage;
BOOL noAppImage = false;
// First check the memory cache
UIImage* appImage = [_artCache objectForKey:_app];
//UIImage* appImage = [_artCache objectForKey:_app];
UIImage* appImage = nil;
// NSLog(@"appImage instance: %lu", (uintptr_t)appImage);
if (appImage == nil) {
// Next try to load from the on disk cache
@@ -138,6 +151,8 @@ static UIImage* noImage;
[_artCache setObject:appImage forKey:_app];
}
}
// [_artCache setObject:appImage forKey:_app];
if (appImage != nil) {
@@ -156,12 +171,15 @@ static UIImage* noImage;
noAppImage = true;
}
if ([_app.id isEqualToString:_app.host.currentGame]) {
if([self isCurrentApp]) {
// Only create the app overlay if needed
if (@available(iOS 13.0, *)) {
UIImageView* playIcon = [[UIImageView alloc] initWithImage:[[UIImage systemImageNamed:@"play.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
playIcon.tintColor = [[UIColor systemTealColor] colorWithAlphaComponent:0.8];
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:23];
UIImageView* playIcon = [[UIImageView alloc] initWithImage:[[UIImage systemImageNamed:@"play.circle.fill" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
//playIcon.tintColor = [[ThemeManager widgetBackgroundColor] colorWithAlphaComponent:0.85];
playIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.55];
_appOverlay = playIcon;
} else {
@@ -170,23 +188,37 @@ static UIImage* noImage;
}
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
_appOverlay.layer.shadowOffset = CGSizeMake(1, 1);
_appOverlay.layer.shadowOpacity = 1;
_appOverlay.layer.shadowRadius = 1.3;
_appOverlay.contentMode = UIViewContentModeScaleAspectFit;
//_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
//_appOverlay.layer.shadowOffset = CGSizeMake(1, 1);
//_appOverlay.layer.shadowOpacity = 1;
//_appOverlay.layer.shadowRadius = 1.3;
//_appOverlay.contentMode = UIViewContentModeScaleAspectFit;
}
else if(noAppImage){
if (@available(iOS 13.0, *)) {
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:70];
UIImage* appIconImage = [[UIImage imageNamed:@"icon-pc-app"] imageWithConfiguration:config];
UIImageView* appIcon = [[UIImageView alloc] initWithImage:[appIconImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
//layIcon.tintColor = [[ThemeManager widgetBackgroundColor] colorWithAlphaComponent:0.85];
appIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
_appOverlay = appIcon;
}
}
if (noAppImage) {
if(true) {
_appLabel = [[UILabel alloc] init];
[_appLabel setTextColor:[[UIColor blackColor] colorWithAlphaComponent:0.75]];
_appLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.55];
[_appLabel setTextColor:[[UIColor whiteColor] colorWithAlphaComponent:1]];
//_appLabel.shadowColor = [UIColor blackColor];
[_appLabel setText:_app.name];
[_appLabel setFont:[UIFont systemFontOfSize:20]];
[_appLabel setText:[_app.name isEqualToString:@"Steam Big Picture"] ? @"Steam" : _app.name];
[_appLabel setFont:[UIFont systemFontOfSize:15]];
[_appLabel setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
[_appLabel setTextAlignment:NSTextAlignmentCenter];
[_appLabel setLineBreakMode:NSLineBreakByWordWrapping];
[_appLabel setNumberOfLines:0];
[_appLabel setNumberOfLines:2];
_appLabel.lineBreakMode = NSLineBreakByTruncatingTail;
}
[self positionSubviews];
@@ -195,8 +227,8 @@ static UIImage* noImage;
[_appImage.overlayContentView addSubview:_appLabel];
[_appImage.overlayContentView addSubview:_appOverlay];
#else
[self addSubview:_appLabel];
[self addSubview:_appOverlay];
[self addSubview:_appLabel];
#endif
}
@@ -208,25 +240,21 @@ static UIImage* noImage;
}
- (void) positionSubviews {
CGFloat padding = 5.f;
// CGFloat padding = 28;
CGFloat verticalPadding = 10;
CGSize frameSize = _appImage.frame.size;
CGPoint center = _appImage.center;
// CGPoint center = _appImage.center;
[_appLabel setFrame:CGRectMake(0, frameSize.height-43, frameSize.width, 43)];
//[_appLabel setFrame:CGRectMake(0, 0, 30, 12)];
if (_appLabel != nil) {
if (_appOverlay != nil) {
_appOverlay.frame = CGRectMake(0, 0, frameSize.width / 3, frameSize.width / 3);
_appOverlay.center = CGPointMake(frameSize.width / 2, padding + _appOverlay.frame.size.height / 2);
_appOverlay.frame = [self isCurrentApp] ? CGRectMake(0, 0, frameSize.width / 2.39, frameSize.width / 2.39) : CGRectMake(0, 0, frameSize.width / 2, frameSize.width / 2);
_appOverlay.center = CGPointMake(frameSize.width/2, frameSize.height/2 - 2 * verticalPadding);
[_appLabel setFrame:CGRectMake(padding, _appOverlay.frame.size.height + padding, frameSize.width - 2 * padding, frameSize.height - _appOverlay.frame.size.height - 2 * padding)];
//[_appLabel setFrame:CGRectMake(padding, _appOverlay.frame.size.height + padding, frameSize.width - 2 * padding, frameSize.height - _appOverlay.frame.size.height - 2 * padding)];
}
else {
[_appLabel setFrame:CGRectMake(padding, padding, frameSize.width - 2 * padding, frameSize.height - 2 * padding)];
}
}
else if (_appOverlay != nil) {
_appOverlay.frame = CGRectMake(0, 0, frameSize.width / 2, frameSize.width / 2);
_appOverlay.center = center;
}
}
- (void) updateLoop {
@@ -235,6 +263,8 @@ static UIImage* noImage;
return;
}
NSLog(@"appview update loop %f", CACurrentMediaTime());
// Update the app image if neccessary
if ((_appOverlay != nil && ![_app.id isEqualToString:_app.host.currentGame]) ||
(_appOverlay == nil && [_app.id isEqualToString:_app.host.currentGame])) {
@@ -244,13 +274,14 @@ static UIImage* noImage;
// Show no shadow for hidden apps. Because we adjust the opacity of the
// cells for hidden apps, it makes them look bad when the shadow draws
// through the app tile.
self.superview.layer.shadowOpacity = _app.hidden ? 0.0f : 0.5f;
// self.superview.layer.shadowOpacity = _app.hidden ? 0.0f : 0.5f;
self.superview.layer.shadowOpacity = 0;
// Update opacity if neccessary
[self setAlpha:_app.hidden ? 0.4 : 1.0];
// Queue the next refresh cycle
[self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
if([self.updateLoopDelegate isInAppView]) [self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
}
@end
@@ -20,7 +20,7 @@ static CGFloat screenWidthInPoints;
self = [super initWithTarget:target action:action];
screenWidthInPoints = CGRectGetWidth([UIApplication.sharedApplication.windows.firstObject.screen bounds]); // Get the screen's bounds (in points)
_immediateTriggering = false;
_EDGE_TOLERANCE = 15.0f;
_EDGE_TOLERANCE = 10.0f;
return self;
}
@@ -370,6 +370,7 @@
UIAlertAction *readInstruction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Read Widget Instruction"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action){
[self saveTapped:nil];
NSURL *url = [NSURL URLWithString:@"https://b23.tv/J8qEXOr"];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
@@ -609,7 +610,7 @@
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Current profile updated successfully"] preferredStyle:UIAlertControllerStyleAlert];
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
if(sender) [self presentViewController:savedAlertController animated:YES completion:nil];
}
else{
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Profile Default can not be overwritten"] preferredStyle:UIAlertControllerStyleAlert];
@@ -21,7 +21,7 @@
#import "HostCollectionViewController.h"
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, HostCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate, HostCardActionDelegate>
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, HostCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate, HostCardActionDelegate, AppViewUpdateLoopDelegate>
@property (nonatomic, strong) IBOutlet UIBarButtonItem *settingsButton;
#if !TARGET_OS_TV
@@ -374,6 +374,8 @@ static NSMutableSet* hostList;
self.collectionView.backgroundColor = [ThemeManager appBackgroundColor];
//self.view.backgroundColor = [ThemeManager appBackgroundColor];
[self.collectionView setContentOffset:CGPointZero animated:NO];
[self attachWaterMark];
self.navigationItem.rightBarButtonItems = @[_upButton];
self.revealViewController.mainFrameIsInHostView = false;
@@ -385,11 +387,12 @@ static NSMutableSet* hostList;
//[self applyNavBarAppearance:navBarAppearance];
}
- (void)appButtonTappedForHost:(TemporaryHost *)host {
- (void)appButtonTappedForHost:(TemporaryHost *)host{
if (host.state != StateOnline) return;
_selectedHost = host;
if (host.state == StateOnline && host.pairState == PairStatePaired && host.appList.count > 0) {
[self switchToAppView];
if(host.state == StateOnline && host.pairState == PairStatePaired){
[self updateAppsForHost:host];
if(host.appList.count>0) [self switchToAppView];
}
}
@@ -490,7 +493,7 @@ static NSMutableSet* hostList;
- (void) noneUserInitiatedHostAction:(TemporaryHost *)host view:(UIView *)view {
Log(LOG_D, @"Clicked host: %@", host.name);
NSLog(@"Clicked host: %@", host.name);
_selectedHost = host;
//_appManager = [[AppAssetManager alloc] initWithCallback:self];
[self.collectionView setCollectionViewLayout:self.collectionViewLayout];
@@ -1345,13 +1348,23 @@ static NSMutableSet* hostList;
button.frame = CGRectMake(0, 0, buttonHeight, buttonHeight);
// 添加点击事件
// [button addTarget:self action:@selector(addDeviceTapped) forControlEvents:UIControlEventTouchUpInside];
[button addTarget:self action:@selector(helpButtonTapped) forControlEvents:UIControlEventTouchUpInside];
// 创建 UIBarButtonItem
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:button];
return barItem;
}
- (void)helpButtonTapped{
if (@available(iOS 13.0, *)) {
AboutViewController *aboutVC = [[AboutViewController alloc] init];
aboutVC.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:aboutVC animated:YES completion:nil];
} else {
// Fallback on earlier versions
}
}
- (CGFloat)getStandardNavBarHeight{
return [self isIPhone] ? UINavigationBarHeightIPhone : UINavigationBarHeightIPad;
}
@@ -1531,10 +1544,11 @@ static NSMutableSet* hostList;
#endif
[self updateTitle];
// [self.view addSubview:hostScrollView];
// if ([hostList count] == 1) [self hostClicked:[hostList anyObject] view:nil]; // auto click for single host
[self retrieveSavedHosts];
_discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self];
//if([SettingsViewController isLandscapeNow] != _streamConfig.width > _streamConfig.height)
//[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app.
@@ -1796,10 +1810,6 @@ static NSMutableSet* hostList;
[self.view addSubview:self.collectionView];
[self initHostCollection];
if(!_enteredAppView) [self switchToHostView];
[self retrieveSavedHosts];
_discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self];
}
- (void)viewDidDisappear:(BOOL)animated
@@ -1889,7 +1899,8 @@ static NSMutableSet* hostList;
NSArray* sortedHostList = [[hostList allObjects] sortedArrayUsingSelector:@selector(compareName:)];
for (TemporaryHost* comp in sortedHostList) {
if(comp.state == StateOnline || comp.pairState == PairStatePaired || comp.pairState == PairStateUnknown) [self.hostCollectionVC addHost:comp];
// if(comp.state == StateOnline || comp.pairState == PairStatePaired || comp.pairState == PairStateUnknown) [self.hostCollectionVC addHost:comp];
[self.hostCollectionVC addHost:comp];
// Start jobs to decode the box art in advance
for (TemporaryApp* app in comp.appList) {
@@ -1991,16 +2002,21 @@ static NSMutableSet* hostList;
[self.collectionView reloadData];
}
- (bool)isInAppView{
return !self.revealViewController.isStreaming && _enteredAppView;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath];
TemporaryApp* app = _sortedAppList[indexPath.row];
UIAppView* appView = [[UIAppView alloc] initWithApp:app cache:_boxArtCache andCallback:self];
appView.updateLoopDelegate = (id<AppViewUpdateLoopDelegate>)self;
if (appView.bounds.size.width > 10.0) {
CGFloat scale = cell.bounds.size.width / appView.bounds.size.width;
[appView setCenter:CGPointMake(appView.bounds.size.width / 2 * scale, appView.bounds.size.height / 2 * scale)];
appView.transform = CGAffineTransformMakeScale(scale, scale);
appView.transform = CGAffineTransformMakeScale(scale, scale); // view resize
}
[cell.subviews.firstObject removeFromSuperview]; // Remove a view that was previously added
@@ -2008,15 +2024,15 @@ static NSMutableSet* hostList;
// [self.settingsButton setEnabled:![self isIPhonePortrait]]; // update settings button after host is clicked & appview loaded
// Shadow opacity is controlled inside UIAppView based on whether the app
// is hidden or not during the update cycle.
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:cell.bounds];
cell.layer.masksToBounds = NO;
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowOffset = CGSizeMake(1.0f, 5.0f);
cell.layer.shadowPath = shadowPath.CGPath;
//UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:cell.bounds];
//cell.layer.masksToBounds = YES;
//cell.layer.shadowColor = [UIColor blackColor].CGColor;
//cell.layer.shadowOffset = CGSizeMake(1.0f, 5.0f);
//cell.layer.shadowPath = shadowPath.CGPath;
#if !TARGET_OS_TV
cell.layer.borderWidth = 1;
cell.layer.borderColor = [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3f] CGColor];
//cell.layer.borderWidth = 1;
//cell.layer.borderColor = [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3f] CGColor];
cell.exclusiveTouch = YES;
#endif
@@ -1313,6 +1313,12 @@ BOOL isCustomResolution(CGSize res) {
[self.gyroSensitivitySlider addTarget:self action:@selector(gyroSensitivitySliderMoved:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self gyroSensitivitySliderMoved:self.gyroSensitivitySlider];
if (@available(iOS 14.0, tvOS 14.0, *)) nil;
else{
[self.gyroModeSelector setEnabled:false forSegmentAtIndex:1];
[self.gyroModeSelector setEnabled:false forSegmentAtIndex:3];
}
[self.emulatedControllerTypeSelector setSelectedSegmentIndex:[self controllerTypeToSegmentIndex:currentSettings.emulatedControllerType.intValue]];
[self.emulatedControllerTypeSelector addTarget:self action:@selector(emulatedControllerTypeChanged:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self emulatedControllerTypeChanged:self.emulatedControllerTypeSelector];