Skip to content

Instantly share code, notes, and snippets.

@gwl
Last active May 3, 2023 04:59
Show Gist options
  • Select an option

  • Save gwl/848eb9e3deb481f9a9d4cce17985a17d to your computer and use it in GitHub Desktop.

Select an option

Save gwl/848eb9e3deb481f9a9d4cce17985a17d to your computer and use it in GitHub Desktop.
ContentView for SaverDebugger app
//
// ContentView.swift
// SaverDebugger
//
// Created by Gary W. Longsine on 4/9/23.
//
import SwiftUI
import ScreenSaver
import os
struct windowSize {
// changes let to static - read comments
var minWidth : CGFloat = 640
var minHeight : CGFloat = 400
var maxWidth : CGFloat = 5120
var maxHeight : CGFloat = 2880
}
struct ContentView: View {
@State private var currentScreenSaverView: ScreenSaverView? = nil
@State private var selectedSaver: Int = 2
@State private var selectedWindowSize: Int = 0
// @State private var selectedWindowSize = "MacBook"
@State private var currentSaver: AnyView? = nil
@State private var timer: Timer? = nil
@State private var recordingManager = RecordingManager()
let logger = Logger(subsystem: "com.illumineX.SaverDebugger", category: "SaverDebugger")
let saverOptions = ["iX Circles", "iX Game of Life", "iX Hyperspace", "iX Rings", "iX Stars"]
// window sizes for testing
let windowSizes = ["Record Video": CGSize(width: 640, height: 400),
"MacBook": CGSize(width: 1280, height: 800),
"MacBook 16:10 (640x400)": CGSize(width: 640, height: 400),
"MacBook (Retina, 12-inch, 226 ppi)": CGSize(width: 2304 / 2, height: 1440 / 2),
"MacBook Pro": CGSize(width: 1440, height: 900),
"MacBook Pro (17-inch)": CGSize(width: 1920, height: 1200),
"MacBook Pro (Retina, 13.3-inch, 113 ppi)": CGSize(width: 1280, height: 800),
"MacBook Pro (Retina, 13.3-inch, 227 ppi)": CGSize(width: 2560 / 2, height: 1600 / 2),
"MacBook Pro (Retina, 15.4-inch, 220 ppi)": CGSize(width: 2880 / 2, height: 1800 / 2),
"MacBook Air (13.3-inch, 127 ppi)": CGSize(width: 1440, height: 900),
"MacBook Air (11.6-inch, 135 ppi)": CGSize(width: 1366, height: 768),
"iMac (21.5-inch, 102 ppi)": CGSize(width: 1920, height: 1080),
"iMac (Retina, 21.5-inch, 219 ppi)": CGSize(width: 2650 / 2, height: 1440 / 2),
"iMac (Retina 5K, 27-inch, 218 ppi)": CGSize(width: 5120 / 2, height: 2650 / 2),
"Apple Studio display 5k (27-inch, 218 ppi)": CGSize(width: 5120 / 2, height: 2880 / 2),
"Apple Thunderbolt display (27-inch, 109 ppi)": CGSize(width: 2560, height: 1440)]
var body: some View {
VStack {
VStack {
controlsSection(selectedSaver: $selectedSaver, selectedWindowSize: $selectedWindowSize, saverOptions: saverOptions, windowSizes: windowSizes)
}
currentSaver
// .frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(minWidth: windowSize().minWidth, minHeight: windowSize().minHeight)
.frame(maxWidth: windowSize().maxWidth, maxHeight: windowSize().maxHeight)
.border(Color.purple, width: 3)
}
.onAppear {
loadScreensaver()
//updateWindowSize()
if let screenSaverView = currentScreenSaverView {
recordingManager.startRecording(for: currentScreenSaverView!)
}
logger.info("SaverDebugger: loadSaver() called by onAppear")
}
.onChange(of: selectedSaver) { _ in
loadScreensaver()
// updateWindowSize()
logger.info("SaverDebugger: loadSaver() called by onChange selectedSaver")
}
.onChange(of: selectedWindowSize) { _ in
//loadScreensaver()
updateWindowSize()
logger.info("SaverDebugger: loadSaver() called by onChange selectedWindowSize")
}
}
func loadScreensaver() {
timer?.invalidate()
let sortedSizes = windowSizes.values.sorted {
$0.width * $0.height < $1.width * $1.height
}
let screenSize = windowSizes[Array(windowSizes.keys.sorted())[selectedWindowSize]]!
let defaultWidth = 1280
let defaultHeight = 800
switch selectedSaver {
case 0:
let view = IXCirclesView(frame: NSRect(origin: .zero, size: screenSize ))
// currentSaver = AnyView(view) // Use this instead for a SwiftUI screenaver view?
currentSaver = AnyView(NSViewWrapper(view)) // for an AppKit NSView screensaver view
currentScreenSaverView = view
//logger.info("SaverDebugger: IXCirclesView selected")
case 1:
let view = IXGameOfLifeView(frame: NSRect(origin: .zero, size: screenSize ))
currentSaver = AnyView(NSViewWrapper(view))
currentScreenSaverView = view
//logger.info("SaverDebugger: IXGameOfLifeView selected")
case 2:
let view = IXHyperspaceView(frame: NSRect(origin: .zero, size: screenSize ))
currentSaver = AnyView(NSViewWrapper(view))
currentScreenSaverView = view
//logger.info("SaverDebugger: IXHyperspaceView selected")
case 3:
let view = IXRingsView(frame: NSRect(origin: .zero, size: screenSize ))
currentSaver = AnyView(NSViewWrapper(view))
currentScreenSaverView = view
//logger.info("SaverDebugger: IXHyperspaceView selected")
case 4:
let view = IXStarsSaverView(frame: NSRect(origin: .zero, size: screenSize ))
currentSaver = AnyView(NSViewWrapper(view))
currentScreenSaverView = view
//logger.info("SaverDebugger: IXHyperspaceView selected")
default:
break
}
if let screensaverView = currentScreenSaverView {
timer = Timer.scheduledTimer(withTimeInterval: screensaverView.animationTimeInterval, repeats: true) { _ in
screensaverView.animateOneFrame()
}
}
if let window = currentWindow() {
let contentRect = NSRect(origin: .zero, size: screenSize )
let frameRect = window.frameRect(forContentRect: contentRect)
let newOrigin = CGPoint(x: window.frame.origin.x, y: window.frame.origin.y + window.frame.size.height - frameRect.size.height)
window.setFrame(NSRect(origin: newOrigin, size: frameRect.size), display: true, animate: true)
// Update the window's contentView
let contentView = NSHostingView(rootView: self.body)
contentView.frame = contentRect
window.contentView = contentView
}
}
// a function to provide the controls view
func controlsSection(selectedSaver: Binding<Int>, selectedWindowSize: Binding<Int>, saverOptions: [String], windowSizes: [String: CGSize]) -> some View {
HStack {
Picker("Screensaver", selection: selectedSaver) {
ForEach(0..<saverOptions.count, id: \.self) {
Text(saverOptions[$0]).tag($0)
}
}
.onChange(of: selectedSaver.wrappedValue) { newValue in
updateWindowSize()
logger.info("SaverDebugger: \(saverOptions[newValue]) selected by Picker")
}
.padding()
Picker("Window Size", selection: $selectedWindowSize) {
ForEach(windowSizes.keys.sorted(), id: \.self) { key in
Text(key).tag(windowSizes.keys.sorted().firstIndex(of: key)!)
}
}
}
}
// two functions and one extention to support window resizing
// a helper function to get the current window
func currentWindow() -> NSWindow? {
logger.info("SaverDebugger: currentWindow()")
return NSApplication.shared.windows.first { window in
return window.contentView?.superview is NSHostingView<ContentView>
}
}
// two functions to support window resizing
private func updateWindowSize() {
guard let window = NSApp.windows.first,
// let selectedSize = windowSizes[selectedSize]
let newSize = windowSizes[Array(windowSizes.keys.sorted())[selectedWindowSize]]
else { return }
let newWindowWidth = newSize.width
let newWindowHeight = newSize.height + 44 + window.titleBarHeight
let currentFrame = window.frame
// Calculate the new frame origin
let newX = currentFrame.origin.x
let newY = currentFrame.origin.y + currentFrame.size.height - newWindowHeight
let newFrame = CGRect(x: newX, y: newY, width: newWindowWidth, height: newWindowHeight)
window.setFrame(newFrame, display: true, animate: true)
}
private func controlsSectionHeight() -> CGFloat {
return 44.0
}
}
// an extension to support window resizing
extension NSWindow {
var titleBarHeight: CGFloat {
return contentLayoutRect.height - contentRect(forFrameRect: frame).height
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment