mirror of
https://github.com/utmapp/UTM.git
synced 2026-10-12 02:24:01 +00:00
ios: keep the Start/Resume button and headless notice out of the fold
A partially folded iPhone Duo reports the fold as a reserved division region. Alerts, menus and sheets already move out of it, but a control centred in the VM window lands right in the curve: the Start/Resume button shown while the VM is stopped or paused, and the notice shown when no device is selected. Place them in the largest part of the window that no active division runs through; without a fold that is the whole window, so nothing changes elsewhere. The query only exists in the iOS 27.1 SDK, so the code is compiled in only when the SwiftUI module is at least that version and the project still builds with Xcode 27.0. Assisted-by: Claude:claude-fable-5-1
This commit is contained in:
@@ -98,6 +98,7 @@ struct VMWindowView: View {
|
||||
.font(.system(size: 128))
|
||||
.vibrancyEffect()
|
||||
.vibrancyEffectStyle(.label)
|
||||
.clearOfReservedDivisions()
|
||||
}
|
||||
}.background(Color.black)
|
||||
.ignoresSafeArea()
|
||||
@@ -298,10 +299,69 @@ private struct HeadlessView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 25.0, style: .continuous))
|
||||
.vibrancyEffect()
|
||||
.vibrancyEffectStyle(.label)
|
||||
.clearOfReservedDivisions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Folding devices
|
||||
|
||||
#if !os(visionOS) && canImport(SwiftUI, _version: 8.0.85)
|
||||
@available(iOS 27.1, *)
|
||||
extension GeometryProxy {
|
||||
/// The largest part of the view that no active fold runs through, or all of it.
|
||||
var frameClearOfDivisions: CGRect {
|
||||
var frame = CGRect(origin: .zero, size: size)
|
||||
for region in reservedRegions(kind: .division) where region.isActive {
|
||||
let division = region.frame
|
||||
let parts = [
|
||||
frame.part(maxY: division.minY),
|
||||
frame.part(minY: division.maxY),
|
||||
frame.part(maxX: division.minX),
|
||||
frame.part(minX: division.maxX),
|
||||
]
|
||||
frame = parts.max { $0.width * $0.height < $1.width * $1.height }!
|
||||
}
|
||||
return frame
|
||||
}
|
||||
}
|
||||
|
||||
private extension CGRect {
|
||||
/// The rectangle cut down to the given edges, empty when nothing is left.
|
||||
func part(minX: CGFloat? = nil, minY: CGFloat? = nil, maxX: CGFloat? = nil, maxY: CGFloat? = nil) -> CGRect {
|
||||
let left = max(self.minX, minX ?? self.minX)
|
||||
let top = max(self.minY, minY ?? self.minY)
|
||||
let right = min(self.maxX, maxX ?? self.maxX)
|
||||
let bottom = min(self.maxY, maxY ?? self.maxY)
|
||||
return CGRect(x: left, y: top, width: max(0, right - left), height: max(0, bottom - top))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private extension View {
|
||||
/// Keeps centred interactive content out of the fold of a partially folded device.
|
||||
///
|
||||
/// Alerts, menus and sheets move out of the fold by themselves; a control centred in the
|
||||
/// window does not.
|
||||
@ViewBuilder
|
||||
func clearOfReservedDivisions() -> some View {
|
||||
#if !os(visionOS) && canImport(SwiftUI, _version: 8.0.85)
|
||||
if #available(iOS 27.1, *) {
|
||||
GeometryReader { proxy in
|
||||
let frame = proxy.frameClearOfDivisions
|
||||
self
|
||||
.frame(width: frame.width, height: frame.height)
|
||||
.offset(x: frame.minX, y: frame.minY)
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
#else
|
||||
self
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
/// Stub for non-Vision platforms
|
||||
fileprivate struct VMToolbarOrnamentModifier: ViewModifier {
|
||||
|
||||
Reference in New Issue
Block a user