Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01ee69b8c5 | |||
| 72d9b1fbd5 | |||
| b435de87a2 | |||
| 372c8ce90b | |||
| 0d36867f6b | |||
| 3fff6edff0 |
@@ -15,8 +15,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
public static let shared = LCManager()
|
||||
|
||||
/// Set the font size. The font can be set to a minimum value of 5.0 and a maximum value of 20.0. The default value is 7.5.
|
||||
public var fontSize: CGFloat = 7.5 {
|
||||
/// Set the font size. The font can be set to a minimum value of 5.0 and a maximum value of 20.0. The default value is 8.
|
||||
public var fontSize: CGFloat = 8 {
|
||||
didSet {
|
||||
guard fontSize >= 4 else { fontSize = 4; return }
|
||||
guard fontSize <= 20 else { fontSize = 20; return }
|
||||
@@ -44,7 +44,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let defaultConsoleSize = CGSize(width: 228, height: 142)
|
||||
let defaultConsoleSize = CGSize(width: 240, height: 148)
|
||||
|
||||
lazy var borderView = UIView()
|
||||
|
||||
@@ -73,7 +73,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
return lumaView
|
||||
}()
|
||||
|
||||
lazy var hideButton: UIButton = {
|
||||
lazy var unhideButton: UIButton = {
|
||||
let button = UIButton()
|
||||
|
||||
button.addAction(UIAction(handler: { [self] _ in
|
||||
@@ -81,6 +81,9 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.center = nearestTargetTo(consoleView.center, possibleTargets: possibleEndpoints.dropLast())
|
||||
}.startAnimation()
|
||||
grabberMode = false
|
||||
|
||||
UserDefaults.standard.set(consoleView.center.x, forKey: "LocalConsole_X")
|
||||
UserDefaults.standard.set(consoleView.center.y, forKey: "LocalConsole_Y")
|
||||
}), for: .touchUpInside)
|
||||
|
||||
consoleView.addSubview(button)
|
||||
@@ -106,22 +109,22 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
// Update text view width.
|
||||
if consoleView.frame.size.width > ResizeController.kMaxConsoleWidth {
|
||||
consoleTextView.frame.size.width = ResizeController.kMaxConsoleWidth - 4
|
||||
consoleTextView.frame.size.width = ResizeController.kMaxConsoleWidth - 2
|
||||
} else if consoleView.frame.size.width < ResizeController.kMinConsoleWidth {
|
||||
consoleTextView.frame.size.width = ResizeController.kMinConsoleWidth - 4
|
||||
consoleTextView.frame.size.width = ResizeController.kMinConsoleWidth - 2
|
||||
} else {
|
||||
consoleTextView.frame.size.width = consoleSize.width - 4
|
||||
consoleTextView.frame.size.width = consoleSize.width - 2
|
||||
}
|
||||
|
||||
// Update text view height.
|
||||
if consoleView.frame.size.height > ResizeController.kMaxConsoleHeight {
|
||||
consoleTextView.frame.size.height = ResizeController.kMaxConsoleHeight - 4
|
||||
consoleTextView.frame.size.height = ResizeController.kMaxConsoleHeight - 2
|
||||
+ (consoleView.frame.size.height - ResizeController.kMaxConsoleHeight) * 2 / 3
|
||||
} else if consoleView.frame.size.height < ResizeController.kMinConsoleHeight {
|
||||
consoleTextView.frame.size.height = ResizeController.kMinConsoleHeight - 4
|
||||
consoleTextView.frame.size.height = ResizeController.kMinConsoleHeight - 2
|
||||
+ (consoleView.frame.size.height - ResizeController.kMinConsoleHeight) * 2 / 3
|
||||
} else {
|
||||
consoleTextView.frame.size.height = consoleSize.height - 4
|
||||
consoleTextView.frame.size.height = consoleSize.height - 2
|
||||
}
|
||||
|
||||
consoleTextView.contentOffset.y = consoleTextView.contentSize.height - consoleTextView.bounds.size.height
|
||||
@@ -136,12 +139,14 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
/// Strong reference keeps the window alive.
|
||||
var consoleWindow: ConsoleWindow?
|
||||
|
||||
// The console needs a parent view controller in order to display context menus.
|
||||
lazy var viewController = UIViewController()
|
||||
lazy var consoleView = viewController.view!
|
||||
/// Enables rotation.
|
||||
lazy var viewController = ConsoleViewController()
|
||||
|
||||
/// Note: The console always needs a parent view controller in order to display context menus. In this case, the parent controller will be the viewController.
|
||||
lazy var consoleView = UIView()
|
||||
|
||||
/// Text view that displays printed items.
|
||||
lazy var consoleTextView = InvertedTextView()
|
||||
lazy var consoleTextView = InvertedTextView()
|
||||
|
||||
/// Button that reveals menu.
|
||||
lazy var menuButton = UIButton()
|
||||
@@ -158,22 +163,38 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
/// Gesture endpoints. Each point represents a corner of the screen. TODO: Handle screen rotation.
|
||||
var possibleEndpoints: [CGPoint] {
|
||||
|
||||
if consoleSize.width < UIScreen.portraitSize.width - 112 {
|
||||
let screenSize = viewController.view.frame.size
|
||||
|
||||
// Must check for portrait mode manually here. UIDevice was reporting orientation incorrectly before.
|
||||
let isPortraitNotchedPhone = UIDevice.current.hasNotch && viewController.view.frame.size.width < viewController.view.frame.size.height
|
||||
|
||||
// Fix incorrect reported orientation on phone.
|
||||
let isLandscapePhone = (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) && UIDevice.current.userInterfaceIdiom == .phone
|
||||
|
||||
let isLandscapeLeftNotchedPhone = UIDevice.current.orientation == .landscapeLeft
|
||||
&& UIDevice.current.userInterfaceIdiom == .phone
|
||||
&& UIDevice.current.hasNotch
|
||||
|
||||
let isLandscapeRightNotchedPhone = UIDevice.current.orientation == .landscapeRight
|
||||
&& UIDevice.current.userInterfaceIdiom == .phone
|
||||
&& UIDevice.current.hasNotch
|
||||
|
||||
if consoleSize.width < screenSize.width - 112 {
|
||||
|
||||
// Four endpoints, one for each corner.
|
||||
var endpoints = [
|
||||
|
||||
// Top endpoints.
|
||||
CGPoint(x: consoleSize.width / 2 + 12,
|
||||
y: (UIScreen.hasRoundedCorners ? 44 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: UIScreen.portraitSize.width - consoleSize.width / 2 - 12,
|
||||
y: (UIScreen.hasRoundedCorners ? 44 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: consoleSize.width / 2 + 12 + (isLandscapeLeftNotchedPhone ? 40 : isLandscapePhone ? 12 : 0),
|
||||
y: (isPortraitNotchedPhone ? 38 : isLandscapePhone ? 0 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: screenSize.width - consoleSize.width / 2 - 12 - (isLandscapeRightNotchedPhone ? 40 : isLandscapePhone ? 12 : 0),
|
||||
y: (isPortraitNotchedPhone ? 38 : isLandscapePhone ? 0 : 16) + consoleSize.height / 2 + 12),
|
||||
|
||||
// Bottom endpoints.
|
||||
CGPoint(x: consoleSize.width / 2 + 12,
|
||||
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12),
|
||||
CGPoint(x: UIScreen.portraitSize.width - consoleSize.width / 2 - 12,
|
||||
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
CGPoint(x: consoleSize.width / 2 + 12 + (isLandscapeLeftNotchedPhone ? 40 : isLandscapePhone ? 12 : 0),
|
||||
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12),
|
||||
CGPoint(x: screenSize.width - consoleSize.width / 2 - 12 - (isLandscapeRightNotchedPhone ? 40 : isLandscapePhone ? 12 : 02),
|
||||
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
|
||||
if consoleView.frame.minX <= 0 {
|
||||
|
||||
@@ -181,25 +202,29 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
endpoints = [endpoints[0], endpoints[2]]
|
||||
|
||||
// Left edge hiding endpoints.
|
||||
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
|
||||
y: endpoints[0].y))
|
||||
} else {
|
||||
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
|
||||
y: endpoints[1].y))
|
||||
if !isLandscapeLeftNotchedPhone {
|
||||
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
|
||||
y: endpoints[0].y))
|
||||
} else {
|
||||
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
|
||||
y: endpoints[1].y))
|
||||
}
|
||||
}
|
||||
} else if consoleView.frame.maxX >= UIScreen.portraitSize.width {
|
||||
} else if consoleView.frame.maxX >= screenSize.width {
|
||||
|
||||
// Right edge endpoints.
|
||||
endpoints = [endpoints[1], endpoints[3]]
|
||||
|
||||
// Right edge hiding endpoints.
|
||||
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
|
||||
y: endpoints[0].y))
|
||||
} else {
|
||||
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
|
||||
y: endpoints[1].y))
|
||||
if !isLandscapeRightNotchedPhone {
|
||||
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
|
||||
y: endpoints[0].y))
|
||||
} else {
|
||||
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
|
||||
y: endpoints[1].y))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,29 +233,29 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
} else {
|
||||
|
||||
// Two endpoints, one for the top, one for the bottom..
|
||||
var endpoints = [CGPoint(x: UIScreen.portraitSize.width / 2,
|
||||
y: (UIScreen.hasRoundedCorners ? 44 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: UIScreen.portraitSize.width / 2,
|
||||
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
var endpoints = [CGPoint(x: screenSize.width / 2,
|
||||
y: (UIScreen.hasRoundedCorners ? 38 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: screenSize.width / 2,
|
||||
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
|
||||
if consoleView.frame.minX <= 0 {
|
||||
|
||||
// Left edge hiding endpoints.
|
||||
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
|
||||
y: endpoints[0].y))
|
||||
} else {
|
||||
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
|
||||
y: endpoints[1].y))
|
||||
}
|
||||
} else if consoleView.frame.maxX >= UIScreen.portraitSize.width {
|
||||
} else if consoleView.frame.maxX >= screenSize.width {
|
||||
|
||||
// Right edge hiding endpoints.
|
||||
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
|
||||
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
|
||||
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
|
||||
y: endpoints[0].y))
|
||||
} else {
|
||||
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
|
||||
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
|
||||
y: endpoints[1].y))
|
||||
}
|
||||
}
|
||||
@@ -251,7 +276,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
consoleView.alpha = 0
|
||||
|
||||
consoleView.layer.cornerRadius = 22
|
||||
consoleView.layer.cornerRadius = 24
|
||||
consoleView.layer.cornerCurve = .continuous
|
||||
|
||||
let _ = lumaView
|
||||
@@ -267,10 +292,10 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.addSubview(borderView)
|
||||
|
||||
// Configure text view.
|
||||
consoleTextView.frame = CGRect(x: 2, y: 2, width: consoleSize.width - 4, height: consoleSize.height - 4)
|
||||
consoleTextView.frame = CGRect(x: 1, y: 1, width: consoleSize.width - 2, height: consoleSize.height - 2)
|
||||
consoleTextView.isEditable = false
|
||||
consoleTextView.backgroundColor = .clear
|
||||
consoleTextView.textContainerInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
|
||||
consoleTextView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||
|
||||
consoleTextView.isSelectable = false
|
||||
consoleTextView.showsVerticalScrollIndicator = false
|
||||
@@ -294,7 +319,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.addGestureRecognizer(longPressRecognizer)
|
||||
|
||||
// Prepare menu button.
|
||||
let diameter = CGFloat(28)
|
||||
let diameter = CGFloat(30)
|
||||
|
||||
// This tuned button frame is used to adjust where the menu appears.
|
||||
menuButton = UIButton(frame: CGRect(x: consoleView.bounds.width - 44,
|
||||
@@ -315,7 +340,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
menuButton.addSubview(circle)
|
||||
|
||||
let ellipsisImage = UIImageView(image: UIImage(systemName: "ellipsis",
|
||||
withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .medium)))
|
||||
withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .medium)))
|
||||
ellipsisImage.frame.size = circle.bounds.size
|
||||
ellipsisImage.contentMode = .center
|
||||
circle.addSubview(ellipsisImage)
|
||||
@@ -325,7 +350,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
menuButton.showsMenuAsPrimaryAction = true
|
||||
consoleView.addSubview(menuButton)
|
||||
|
||||
let _ = hideButton
|
||||
let _ = unhideButton
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||
@@ -337,13 +362,9 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
// Update console cached based on last-cached origin.
|
||||
func updateConsoleOrigin() {
|
||||
let cachedConsolePosition = CGPoint(x: UserDefaults.standard.object(forKey: "LocalConsole_X") as? CGFloat ?? possibleEndpoints.first!.x,
|
||||
y: UserDefaults.standard.object(forKey: "LocalConsole_Y") as? CGFloat ?? possibleEndpoints.first!.y)
|
||||
snapToCachedEndpoint()
|
||||
|
||||
consoleView.center = cachedConsolePosition // Update console center so possibleEndpoints are calculated correctly.
|
||||
consoleView.center = nearestTargetTo(cachedConsolePosition, possibleTargets: possibleEndpoints)
|
||||
|
||||
if consoleView.center.x < 0 || consoleView.center.x > UIScreen.portraitSize.width {
|
||||
if consoleView.center.x < 0 || consoleView.center.x > viewController.view.frame.size.width {
|
||||
grabberMode = true
|
||||
scrollLocked = !grabberMode
|
||||
|
||||
@@ -369,7 +390,12 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleWindow?.frame = UIScreen.main.bounds
|
||||
consoleWindow?.windowLevel = UIWindow.Level.statusBar
|
||||
consoleWindow?.isHidden = false
|
||||
consoleWindow?.addSubview(consoleView)
|
||||
|
||||
viewController = ConsoleViewController()
|
||||
|
||||
consoleWindow?.rootViewController = viewController
|
||||
|
||||
viewController.view.addSubview(consoleView)
|
||||
|
||||
UIWindow.swizzleStatusBarAppearanceOverride
|
||||
|
||||
@@ -397,6 +423,14 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func snapToCachedEndpoint() {
|
||||
let cachedConsolePosition = CGPoint(x: UserDefaults.standard.object(forKey: "LocalConsole_X") as? CGFloat ?? possibleEndpoints.first!.x,
|
||||
y: UserDefaults.standard.object(forKey: "LocalConsole_Y") as? CGFloat ?? possibleEndpoints.first!.y)
|
||||
|
||||
consoleView.center = cachedConsolePosition // Update console center so possibleEndpoints are calculated correctly.
|
||||
consoleView.center = nearestTargetTo(cachedConsolePosition, possibleTargets: possibleEndpoints)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
public var isVisible = false {
|
||||
@@ -406,9 +440,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
if isVisible {
|
||||
|
||||
if !isConsoleConfigured {
|
||||
configureWindow()
|
||||
configureConsole()
|
||||
isConsoleConfigured = true
|
||||
DispatchQueue.main.async { [self] in
|
||||
configureWindow()
|
||||
configureConsole()
|
||||
isConsoleConfigured = true
|
||||
}
|
||||
}
|
||||
|
||||
commitTextChanges(requestMenuUpdate: true)
|
||||
@@ -469,7 +505,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}.startAnimation(afterDelay: 0.06)
|
||||
|
||||
consoleTextView.isUserInteractionEnabled = false
|
||||
hideButton.isHidden = false
|
||||
unhideButton.isHidden = false
|
||||
|
||||
} else {
|
||||
|
||||
@@ -491,7 +527,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}.startAnimation()
|
||||
|
||||
consoleTextView.isUserInteractionEnabled = true
|
||||
hideButton.isHidden = true
|
||||
unhideButton.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,8 +564,6 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
if consoleView.center != possibleEndpoints[0] && consoleView.center != possibleEndpoints[1] {
|
||||
let nearestTargetPosition = nearestTargetTo(consoleView.center, possibleTargets: possibleEndpoints.suffix(2))
|
||||
|
||||
Swift.print(possibleEndpoints.suffix(2))
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.55, dampingRatio: 1) {
|
||||
self.consoleView.center = nearestTargetPosition
|
||||
}.startAnimation()
|
||||
@@ -701,6 +735,14 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
})
|
||||
|
||||
// If device is phone in landscape, disable resize controller.
|
||||
if UIDevice.current.userInterfaceIdiom == .phone && viewController.view.frame.width > viewController.view.frame.height {
|
||||
resize.attributes = .disabled
|
||||
if #available(iOS 15, *) {
|
||||
resize.subtitle = "Portrait Orientation Only"
|
||||
}
|
||||
}
|
||||
|
||||
let clear = UIAction(title: "Clear Console",
|
||||
image: UIImage(systemName: "xmark.square"), handler: { _ in
|
||||
self.clear()
|
||||
@@ -825,9 +867,13 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let consolePiPPanner_frameRateRequest = FrameRateRequest()
|
||||
|
||||
@objc func consolePiPPanner(recognizer: UIPanGestureRecognizer) {
|
||||
|
||||
if recognizer.state == .began {
|
||||
consolePiPPanner_frameRateRequest.isActive = true
|
||||
|
||||
initialViewLocation = consoleView.center
|
||||
}
|
||||
|
||||
@@ -844,14 +890,13 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
y: initialViewLocation.y + translation.y)
|
||||
}.startAnimation()
|
||||
|
||||
if consoleView.frame.maxX > 30 && consoleView.frame.minX < UIScreen.portraitSize.width - 30 {
|
||||
grabberMode = false
|
||||
} else {
|
||||
grabberMode = true
|
||||
}
|
||||
reassessGrabberMode()
|
||||
|
||||
case .ended, .cancelled:
|
||||
|
||||
consolePiPPanner_frameRateRequest.isActive = false
|
||||
FrameRateRequest().perform(duration: 0.5)
|
||||
|
||||
// After the PiP is thrown, determine the best corner and re-target it there.
|
||||
let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
|
||||
|
||||
@@ -878,7 +923,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
UserDefaults.standard.set(nearestTargetPosition.y, forKey: "LocalConsole_Y")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
|
||||
self.grabberMode = nearestTargetPosition.x < 0 || nearestTargetPosition.x > UIScreen.portraitSize.width
|
||||
self.reassessGrabberMode()
|
||||
self.scrollLocked = !self.grabberMode
|
||||
}
|
||||
|
||||
@@ -886,6 +931,14 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func reassessGrabberMode() {
|
||||
if consoleView.frame.maxX > 30 && consoleView.frame.minX < viewController.view.frame.size.width - 30 {
|
||||
grabberMode = false
|
||||
} else {
|
||||
grabberMode = true
|
||||
}
|
||||
}
|
||||
|
||||
// Animate touch down.
|
||||
func consolePiPTouchDown() {
|
||||
guard !grabberMode else { return }
|
||||
@@ -904,7 +957,9 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
if !grabberMode {
|
||||
consoleTextView.alpha = 1
|
||||
menuButton.alpha = 1
|
||||
if !ResizeController.shared.isActive {
|
||||
menuButton.alpha = 1
|
||||
}
|
||||
}
|
||||
}.startAnimation()
|
||||
}
|
||||
@@ -1093,6 +1148,12 @@ class InvertedTextView: UITextView {
|
||||
}
|
||||
}
|
||||
|
||||
extension UIDevice {
|
||||
var hasNotch: Bool {
|
||||
return UIApplication.shared.windows[0].safeAreaInsets.bottom > 0
|
||||
}
|
||||
}
|
||||
|
||||
extension TimeInterval {
|
||||
var formattedString: String? {
|
||||
let formatter = DateComponentsFormatter()
|
||||
@@ -1104,3 +1165,112 @@ extension TimeInterval {
|
||||
fileprivate func _debugPrint(_ items: Any) {
|
||||
print(items)
|
||||
}
|
||||
|
||||
// Support for auto-rotate.
|
||||
class ConsoleViewController: UIViewController {
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
|
||||
// Cancel the panner console is being panned to allow for location manipulation.
|
||||
[LCManager.shared.panRecognizer, LCManager.shared.longPressRecognizer].forEach {
|
||||
$0.isEnabled.toggle(); $0.isEnabled.toggle()
|
||||
}
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom != .pad && ResizeController.shared.isActive {
|
||||
ResizeController.shared.isActive = false
|
||||
ResizeController.shared.platterView.dismiss()
|
||||
}
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad && ResizeController.shared.isActive {
|
||||
DispatchQueue.main.async {
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = ResizeController.shared.consoleCenterPoint
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
}
|
||||
|
||||
} else {
|
||||
let consoleView = LCManager.shared.consoleView
|
||||
let oldSize = LCManager.shared.viewController.view.frame.size
|
||||
|
||||
let targetLocationEstimate: CGPoint = {
|
||||
var xPosition = consoleView.center.x
|
||||
var yPosition = consoleView.center.y
|
||||
|
||||
if consoleView.center.x > oldSize.width / 2 {
|
||||
xPosition += size.width - oldSize.width
|
||||
}
|
||||
|
||||
if consoleView.center.y > oldSize.height / 2 {
|
||||
yPosition += size.height - oldSize.height
|
||||
}
|
||||
|
||||
return CGPoint(x: xPosition, y: yPosition)
|
||||
}()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
consoleView.center = targetLocationEstimate
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Update portrait orientation menu option for resize controller.
|
||||
LCManager.shared.menuButton.menu = LCManager.shared.makeMenu()
|
||||
|
||||
// Reassess center of console based on target location estimate.
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
consoleView.center = nearestTargetTo(consoleView.center, possibleTargets: LCManager.shared.possibleEndpoints)
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
|
||||
LCManager.shared.reassessGrabberMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Frame Rate Request
|
||||
/**
|
||||
An object that allows you to manually request an increased display refresh rate on ProMotion devices.
|
||||
|
||||
*The display refresh rate does not exceed 60 Hz when low power mode is enabled.*
|
||||
|
||||
**Do not set an excessive duration. Doing so will negatively impact battery life.**
|
||||
|
||||
```
|
||||
// Example
|
||||
let request = FrameRateRequest(preferredFrameRate: 120,
|
||||
duration: 0.4)
|
||||
request.perform()
|
||||
```
|
||||
*/
|
||||
class FrameRateRequest {
|
||||
|
||||
lazy private var displayLink = CADisplayLink(target: self, selector: #selector(dummyFunction))
|
||||
|
||||
var isActive: Bool = false {
|
||||
didSet {
|
||||
guard #available(iOS 15, *) else { return }
|
||||
guard isActive != oldValue else { return }
|
||||
|
||||
if isActive {
|
||||
displayLink.add(to: .current, forMode: .common)
|
||||
} else {
|
||||
displayLink.remove(from: .current, forMode: .common)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares your frame rate request parameters.
|
||||
init(preferredFrameRate: Float = Float(UIScreen.main.maximumFramesPerSecond)) {
|
||||
if #available(iOS 15, *) {
|
||||
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 30, maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: preferredFrameRate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform frame rate request.
|
||||
func perform(duration: Double) {
|
||||
isActive = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration) { [self] in
|
||||
isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func dummyFunction() {}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,13 @@ class ResizeController {
|
||||
|
||||
lazy var platterView = PlatterView(frame: .zero)
|
||||
|
||||
lazy var consoleCenterPoint = CGPoint(x: (UIScreen.main.nativeBounds.width / 2).rounded() / UIScreen.main.scale,
|
||||
y: (UIScreen.main.nativeBounds.height / 2).rounded() / UIScreen.main.scale
|
||||
+ (UIScreen.hasRoundedCorners ? 0 : 24))
|
||||
var consoleCenterPoint: CGPoint {
|
||||
let containerViewSize = LCManager.shared.viewController.view.frame.size
|
||||
|
||||
return CGPoint(x: (containerViewSize.width * UIScreen.main.scale / 2).rounded() / UIScreen.main.scale,
|
||||
y: (containerViewSize.height * UIScreen.main.scale / 2).rounded() / UIScreen.main.scale
|
||||
+ (UIScreen.hasRoundedCorners ? 0 : 24))
|
||||
}
|
||||
|
||||
lazy var consoleOutlineView: UIView = {
|
||||
|
||||
@@ -115,6 +119,8 @@ class ResizeController {
|
||||
// Ensure initial autolayout is performed unanimated.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
|
||||
FrameRateRequest().perform(duration: 1.5)
|
||||
|
||||
if isActive {
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.75, dampingRatio: 1) {
|
||||
@@ -170,7 +176,7 @@ class ResizeController {
|
||||
LCManager.shared.consoleView.layer.shadowOpacity = 0.5
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = LCManager.shared.possibleEndpoints.first!
|
||||
LCManager.shared.snapToCachedEndpoint()
|
||||
|
||||
// Update grabbers (layout constraints)
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
@@ -201,6 +207,8 @@ class ResizeController {
|
||||
static let kMinConsoleHeight: CGFloat = 108
|
||||
static let kMaxConsoleHeight: CGFloat = 346
|
||||
|
||||
let verticalPanner_frameRateRequest = FrameRateRequest()
|
||||
|
||||
@objc func verticalPanner(recognizer: UIPanGestureRecognizer) {
|
||||
|
||||
let translation = recognizer.translation(in: bottomGrabber.superview)
|
||||
@@ -210,6 +218,8 @@ class ResizeController {
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
verticalPanner_frameRateRequest.isActive = true
|
||||
|
||||
initialHeight = LCManager.shared.consoleSize.height
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
@@ -241,6 +251,10 @@ class ResizeController {
|
||||
LCManager.shared.consoleView.center.y = consoleCenterPoint.y
|
||||
|
||||
case .ended, .cancelled:
|
||||
verticalPanner_frameRateRequest.isActive = false
|
||||
|
||||
FrameRateRequest().perform(duration: 0.4)
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.7) {
|
||||
if LCManager.shared.consoleSize.height > maxHeight {
|
||||
LCManager.shared.consoleSize.height = maxHeight
|
||||
@@ -268,7 +282,9 @@ class ResizeController {
|
||||
var initialWidth = CGFloat.zero
|
||||
|
||||
static let kMinConsoleWidth: CGFloat = 112
|
||||
static let kMaxConsoleWidth: CGFloat = UIScreen.portraitSize.width - 56
|
||||
static let kMaxConsoleWidth: CGFloat = [UIScreen.portraitSize.width, UIScreen.portraitSize.height].min()! - 56
|
||||
|
||||
let horizontalPanner_frameRateRequest = FrameRateRequest()
|
||||
|
||||
@objc func horizontalPanner(recognizer: UIPanGestureRecognizer) {
|
||||
|
||||
@@ -279,6 +295,8 @@ class ResizeController {
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
horizontalPanner_frameRateRequest.isActive = true
|
||||
|
||||
initialWidth = LCManager.shared.consoleSize.width
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
@@ -310,6 +328,10 @@ class ResizeController {
|
||||
|
||||
case .ended, .cancelled:
|
||||
|
||||
horizontalPanner_frameRateRequest.isActive = false
|
||||
|
||||
FrameRateRequest().perform(duration: 0.4)
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.7) {
|
||||
if LCManager.shared.consoleSize.width > maxWidth {
|
||||
LCManager.shared.consoleSize.width = maxWidth
|
||||
@@ -339,12 +361,6 @@ class PlatterView: UIView {
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.frame.size = UIScreen.portraitSize
|
||||
// Make sure bottom doesn't show on upwards pan.
|
||||
self.frame.size.height += 50
|
||||
self.frame.origin = possibleEndpoints[1]
|
||||
autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
layer.shadowRadius = 10
|
||||
layer.shadowOpacity = 0.125
|
||||
layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
@@ -361,11 +377,12 @@ class PlatterView: UIView {
|
||||
blurView.clipsToBounds = true
|
||||
|
||||
blurView.frame = bounds
|
||||
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
addSubview(blurView)
|
||||
|
||||
LCManager.shared.consoleWindow?.addSubview(self)
|
||||
LCManager.shared.consoleWindow?.sendSubviewToBack(self)
|
||||
LCManager.shared.viewController.view.addSubview(self)
|
||||
LCManager.shared.viewController.view.sendSubviewToBack(self)
|
||||
|
||||
_ = backgroundButton
|
||||
|
||||
@@ -376,7 +393,7 @@ class PlatterView: UIView {
|
||||
let grabber = UIView()
|
||||
grabber.frame.size = CGSize(width: 36, height: 5)
|
||||
grabber.frame.origin.y = 10
|
||||
grabber.center.x = bounds.width / 2
|
||||
grabber.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
|
||||
grabber.backgroundColor = .label
|
||||
grabber.alpha = 0.1
|
||||
grabber.layer.cornerRadius = 2.5
|
||||
@@ -387,9 +404,8 @@ class PlatterView: UIView {
|
||||
titleLabel.text = "Resize Console"
|
||||
titleLabel.font = .systemFont(ofSize: 30, weight: .bold)
|
||||
titleLabel.sizeToFit()
|
||||
titleLabel.center.x = bounds.width / 2
|
||||
titleLabel.frame.origin.y = 28
|
||||
titleLabel.roundOriginToPixel()
|
||||
titleLabel.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
|
||||
addSubview(titleLabel)
|
||||
|
||||
let subtitleLabel = UILabel()
|
||||
@@ -397,20 +413,36 @@ class PlatterView: UIView {
|
||||
subtitleLabel.font = .systemFont(ofSize: 17, weight: .medium)
|
||||
subtitleLabel.sizeToFit()
|
||||
subtitleLabel.alpha = 0.5
|
||||
subtitleLabel.center.x = bounds.width / 2
|
||||
subtitleLabel.frame.origin.y = titleLabel.frame.maxY + 8
|
||||
subtitleLabel.roundOriginToPixel()
|
||||
subtitleLabel.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
|
||||
addSubview(subtitleLabel)
|
||||
|
||||
addSubview(resetButton)
|
||||
resetButton.center = CGPoint(x: UIScreen.portraitSize.width / 2 - 74,
|
||||
y: UIScreen.portraitSize.height - possibleEndpoints[0].y * 2)
|
||||
resetButton.roundOriginToPixel()
|
||||
let buttonContainerView = UIView()
|
||||
buttonContainerView.addSubview(resetButton)
|
||||
buttonContainerView.addSubview(doneButton)
|
||||
addSubview(buttonContainerView)
|
||||
|
||||
addSubview(doneButton)
|
||||
doneButton.center = CGPoint(x: UIScreen.portraitSize.width / 2 + 74,
|
||||
y: UIScreen.portraitSize.height - possibleEndpoints[0].y * 2)
|
||||
doneButton.roundOriginToPixel()
|
||||
buttonContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
resetButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
doneButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
|
||||
buttonContainerView.widthAnchor.constraint(equalToConstant: 264),
|
||||
buttonContainerView.heightAnchor.constraint(equalToConstant: 52),
|
||||
buttonContainerView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
buttonContainerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -possibleEndpoints[0].y * 2),
|
||||
|
||||
resetButton.widthAnchor.constraint(equalToConstant: 116),
|
||||
resetButton.heightAnchor.constraint(equalToConstant: 52),
|
||||
resetButton.leadingAnchor.constraint(equalTo: buttonContainerView.leadingAnchor),
|
||||
resetButton.topAnchor.constraint(equalTo: buttonContainerView.topAnchor),
|
||||
|
||||
doneButton.widthAnchor.constraint(equalToConstant: 116),
|
||||
doneButton.heightAnchor.constraint(equalToConstant: 52),
|
||||
doneButton.trailingAnchor.constraint(equalTo: buttonContainerView.trailingAnchor),
|
||||
doneButton.topAnchor.constraint(equalTo: buttonContainerView.topAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
lazy var backgroundButton: UIButton = {
|
||||
@@ -430,7 +462,6 @@ class PlatterView: UIView {
|
||||
button.setTitle("Done", for: .normal)
|
||||
button.setTitleColor(.white, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .medium)
|
||||
button.frame.size = CGSize(width: 116, height: 52)
|
||||
button.layer.cornerRadius = 20
|
||||
button.layer.cornerCurve = .continuous
|
||||
|
||||
@@ -465,7 +496,6 @@ class PlatterView: UIView {
|
||||
button.setTitle("Reset", for: .normal)
|
||||
button.setTitleColor(.label, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .medium)
|
||||
button.frame.size = CGSize(width: 116, height: 52)
|
||||
button.layer.cornerRadius = 20
|
||||
button.layer.cornerCurve = .continuous
|
||||
|
||||
@@ -498,18 +528,35 @@ class PlatterView: UIView {
|
||||
return button
|
||||
}()
|
||||
|
||||
func configureFrame() {
|
||||
self.frame.size = LCManager.shared.viewController.view.frame.size
|
||||
// Make sure bottom doesn't show on upwards pan.
|
||||
self.frame.size.height += 50
|
||||
self.frame.origin = possibleEndpoints[1]
|
||||
autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
}
|
||||
|
||||
func reveal() {
|
||||
|
||||
configureFrame()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
self.frame.origin = self.possibleEndpoints[0]
|
||||
}.startAnimation()
|
||||
|
||||
backgroundButton.isHidden = false
|
||||
|
||||
isHidden = false
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
self.frame.origin = self.possibleEndpoints[1]
|
||||
}.startAnimation()
|
||||
}
|
||||
animator.addCompletion { _ in
|
||||
self.isHidden = true
|
||||
}
|
||||
animator.startAnimation()
|
||||
|
||||
backgroundButton.isHidden = true
|
||||
}
|
||||
@@ -530,7 +577,9 @@ class PlatterView: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
lazy var possibleEndpoints = [CGPoint(x: 0, y: (UIScreen.hasRoundedCorners ? 44 : -8) + 63), CGPoint(x: 0, y: UIScreen.portraitSize.height + 5)]
|
||||
var possibleEndpoints: [CGPoint] { return [CGPoint(x: 0, y: (UIScreen.hasRoundedCorners ? 44 : -8) + 63),
|
||||
CGPoint(x: 0, y: LCManager.shared.viewController.view.frame.size.height + 5)]
|
||||
}
|
||||
|
||||
var initialPlatterOriginY = CGFloat.zero
|
||||
|
||||
@@ -612,15 +661,20 @@ class PlatterView: UIView {
|
||||
$0.transform = .identity
|
||||
}
|
||||
}
|
||||
positionAnimator.startAnimation()
|
||||
|
||||
if nearestTargetPosition == possibleEndpoints[1] {
|
||||
ResizeController.shared.isActive = false
|
||||
backgroundButton.isHidden = true
|
||||
|
||||
positionAnimator.addCompletion { _ in
|
||||
self.isHidden = true
|
||||
}
|
||||
} else {
|
||||
ResizeController.shared.isActive = true
|
||||
}
|
||||
|
||||
positionAnimator.startAnimation()
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user