Skip to content

Commit

Permalink
Redesign window toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
saagarjha committed Mar 2, 2024
1 parent e2d6f06 commit 2a3ab6a
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 25 deletions.
4 changes: 4 additions & 0 deletions Ensemble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
49B352C72AE53A9300BCE03D /* Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B352C62AE53A9300BCE03D /* Frame.swift */; };
49B352C82AE53A9300BCE03D /* Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B352C62AE53A9300BCE03D /* Frame.swift */; };
49B352CB2AE593C300BCE03D /* FrameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B352C92AE593C300BCE03D /* FrameView.swift */; };
49C6E9282B9366BB007A9706 /* WindowToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C6E9272B9366BB007A9706 /* WindowToolbarView.swift */; };
49CC03D72B87770C00F9F672 /* disable_accessibility.c in Sources */ = {isa = PBXBuildFile; fileRef = 49CC03D62B87770C00F9F672 /* disable_accessibility.c */; };
49E09B572AD2EE5000B56CD3 /* EnsembleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E09B562AD2EE5000B56CD3 /* EnsembleApp.swift */; };
49E09B592AD2EE5000B56CD3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E09B582AD2EE5000B56CD3 /* ContentView.swift */; };
Expand Down Expand Up @@ -85,6 +86,7 @@
4992A6002B68291900844A16 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; };
49B352C62AE53A9300BCE03D /* Frame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Frame.swift; sourceTree = "<group>"; };
49B352C92AE593C300BCE03D /* FrameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameView.swift; sourceTree = "<group>"; };
49C6E9272B9366BB007A9706 /* WindowToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowToolbarView.swift; sourceTree = "<group>"; };
49CC03D62B87770C00F9F672 /* disable_accessibility.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = disable_accessibility.c; sourceTree = "<group>"; };
49E09B532AD2EE5000B56CD3 /* Ensemble.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ensemble.app; sourceTree = BUILT_PRODUCTS_DIR; };
49E09B562AD2EE5000B56CD3 /* EnsembleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnsembleApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -220,6 +222,7 @@
49226A322AE45D710044CFC9 /* RootWindowView.swift */,
49E09BD22AD5390500B56CD3 /* WindowPickerView.swift */,
4978BAAE2AD55D71000C549C /* WindowPreviewView.swift */,
49C6E9272B9366BB007A9706 /* WindowToolbarView.swift */,
4978BAB02AD55E8B000C549C /* WindowView.swift */,
49E09B9C2AD3237E00B56CD3 /* Assets.xcassets */,
49E09B9E2AD3237E00B56CD3 /* Preview Content */,
Expand Down Expand Up @@ -436,6 +439,7 @@
4989D3412B0B9393005E2E7A /* shut_up_logging.c in Sources */,
495A8AB32B6478AE00520461 /* Bundle.swift in Sources */,
495E8E3B2AD5CE2400946419 /* ImageBufferView.swift in Sources */,
49C6E9282B9366BB007A9706 /* WindowToolbarView.swift in Sources */,
49E09B9B2AD3237D00B56CD3 /* ContentView.swift in Sources */,
49E09BB12AD3FDCC00B56CD3 /* ConnectionView.swift in Sources */,
4901A14D2B7246760040D2EE /* Preference.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Shared/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum Messages: UInt8, CaseIterable {
case dragChanged
case dragEnded
case typed
case appIcon
}

protocol Message {
Expand Down
22 changes: 22 additions & 0 deletions Shared/macOSInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protocol macOSInterface {
func _dragChanged(parameters: M.DragChanged.Request) async throws -> M.DragChanged.Reply
func _dragEnded(parameters: M.DragEnded.Request) async throws -> M.DragEnded.Reply
func _typed(parameters: M.Typed.Request) async throws -> M.Typed.Reply
func _appIcon(parameters: M.AppIcon.Request) async throws -> M.AppIcon.Reply
}

struct Window: Codable, Identifiable {
Expand Down Expand Up @@ -233,4 +234,25 @@ enum macOSInterfaceMessages {

typealias Reply = SerializableVoid
}

struct AppIcon: Message {
static let id = Messages.appIcon

struct Request: Serializable, Codable {
let windowID: Window.ID
let size: CGSize
}

struct Reply: Serializable {
let image: Data

func encode() -> Data {
image
}

static func decode(_ data: Data) -> Self {
.init(image: data)
}
}
}
}
19 changes: 18 additions & 1 deletion macOS/Local.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
// Created by Saagar Jha on 10/9/23.
//

import AVFoundation
import Accelerate
import AppKit
import CryptoKit
import Foundation
import SystemConfiguration
Expand Down Expand Up @@ -98,6 +100,8 @@ class Local: LocalInterface, macOSInterface {
return try await _dragEnded(parameters: .decode(data)).encode()
case .typed:
return try await _typed(parameters: .decode(data)).encode()
case .appIcon:
return try await _appIcon(parameters: .decode(data)).encode()
default:
return nil
}
Expand Down Expand Up @@ -162,7 +166,7 @@ class Local: LocalInterface, macOSInterface {

func _startWatchingForChildWindows(parameters: M.StartWatchingForChildWindows.Request) async throws -> M.StartWatchingForChildWindows.Reply {
childObservers[parameters.windowID] = Task {
for try await children in await windowManager.childrenOfWindow(idenitifiedBy: parameters.windowID) {
for try await children in try await windowManager.childrenOfWindow(identifiedBy: parameters.windowID) {
try await remote.childWindows(parent: parameters.windowID, children: children)
}
}
Expand Down Expand Up @@ -231,4 +235,17 @@ class Local: LocalInterface, macOSInterface {

return .init()
}

func _appIcon(parameters: M.AppIcon.Request) async throws -> M.AppIcon.Reply {
let icon = try await windowManager.lookupApplication(forWindowID: parameters.windowID)!.icon!
let size = AVMakeRect(aspectRatio: icon.size, insideRect: .init(origin: .zero, size: .init(width: parameters.size.width, height: parameters.size.height))).size
let representation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0)!

NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: representation)
icon.draw(in: NSRect(origin: .zero, size: size), from: NSRect(origin: .zero, size: icon.size), operation: .copy, fraction: 1)
NSGraphicsContext.restoreGraphicsState()

return .init(image: representation.representation(using: .png, properties: [:])!)
}
}
35 changes: 23 additions & 12 deletions macOS/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,23 @@ actor WindowManager {
applications = newApplications
}

func childrenOfWindow(idenitifiedBy windowID: CGWindowID) -> AsyncThrowingStream<[CGWindowID], Error> {
let window = windows[windowID]!
let application = window.application!
var iterator = application.windowUpdates.makeAsyncIterator()
return AsyncThrowingStream {
await iterator.next()
try await self.updateWindows()
return application.childWindows(of: window)
func _lookupWindow(byID id: CGWindowID) async throws -> Window? {
guard let window = windows[id] else {
try await updateWindows()
return windows[id]
}
return window
}

func lookupWindow(byID id: CGWindowID) async throws -> SCWindow? {
guard let window = windows[id]?.window else {
try await updateWindows()
return windows[id]?.window
try await _lookupWindow(byID: id)?.window
}

func lookupApplication(forWindowID id: CGWindowID) async throws -> NSRunningApplication? {
guard let pid = try await _lookupWindow(byID: id)?.application.application.processID else {
return nil
}
return window
return NSRunningApplication(processIdentifier: pid)
}

var allWindows: [SCWindow] {
Expand All @@ -124,6 +124,17 @@ actor WindowManager {
return windows.values.map(\.window)
}
}

func childrenOfWindow(identifiedBy windowID: CGWindowID) async throws -> AsyncThrowingStream<[CGWindowID], Error> {
let window = try await _lookupWindow(byID: windowID)!
let application = window.application!
var iterator = application.windowUpdates.makeAsyncIterator()
return AsyncThrowingStream {
await iterator.next()
try await self.updateWindows()
return application.childWindows(of: window)
}
}
}

extension AXObserver {
Expand Down
11 changes: 0 additions & 11 deletions visionOS/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ struct ContentView: View {
@Binding
var selectedWindow: Window?

@Environment(\.openWindow) private var openWindow

var body: some View {
Group {
if let selectedWindow {
Expand All @@ -24,14 +22,5 @@ struct ContentView: View {
WindowPickerView(remote: remote, selectedWindow: $selectedWindow)
}
}
.toolbar {
ToolbarItem(placement: .bottomOrnament) {
Button(action: {
openWindow(id: "window")
}) {
Image(systemName: "plus")
}
}
}
}
}
8 changes: 8 additions & 0 deletions visionOS/Remote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ struct Remote: macOSInterface {
return stream
}

func appIcon(for windowID: Window.ID, size: CGSize) async throws -> Data {
try await _appIcon(parameters: .init(windowID: windowID, size: size)).image
}

func _startWatchingForChildWindows(parameters: M.StartWatchingForChildWindows.Request) async throws -> M.StartWatchingForChildWindows.Reply {
try await M.StartWatchingForChildWindows.send(parameters, through: connection)
}
Expand Down Expand Up @@ -129,4 +133,8 @@ struct Remote: macOSInterface {
func _typed(parameters: M.Typed.Request) async throws -> M.Typed.Reply {
try await M.Typed.send(parameters, through: connection)
}

func _appIcon(parameters: M.AppIcon.Request) async throws -> M.AppIcon.Reply {
try await M.AppIcon.send(parameters, through: connection)
}
}
11 changes: 10 additions & 1 deletion visionOS/RootWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ struct RootWindowView: View {
let remote: Remote
let window: Window


@State
var children = [Window]()

@State
var appIcon: Data?

var body: some View {
GeometryReader { geometry in
ZStack {
Expand All @@ -33,11 +35,18 @@ struct RootWindowView: View {
}
}
}
.ornament(attachmentAnchor: .scene(.init(x: 0.5, y: 1 + 64 / geometry.size.height))) {
WindowToolbarView(title: window.title!, icon: appIcon)
}
}
.background {
AspectRatioConstrainingView(size: window.frame.size)
}
.task {
do {
// TODO: Scale appropriately
appIcon = try await remote.appIcon(for: window.id, size: .init(width: 128, height: 128))
} catch {}
do {
for await children in try await remote.children(of: window.id) {
let windows = try await remote.windows
Expand Down
56 changes: 56 additions & 0 deletions visionOS/WindowToolbarView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// WindowToolbarView.swift
// visionOS
//
// Created by Saagar Jha on 3/2/24.
//

import SwiftUI

struct WindowToolbarView: View {
let title: String
let icon: Data?

@Environment(\.openWindow) private var openWindow

var body: some View {
HStack {
if let icon {
Image(uiImage: UIImage(data: icon)!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxHeight: 64)
.padding()
} else {
Image(systemName: "questionmark.app")
.resizable()
.aspectRatio(contentMode: .fit)
.padding()
}
Text("\(title)")
.font(.title)
.padding(.trailing)
.lineLimit(1)
.fixedSize()
Divider()
.padding(.vertical, 20)
Button(action: {
openWindow(id: "window")
}) {
Image(systemName: "plus")
.padding()
}
.buttonBorderShape(.circle)
.tint(.clear)
.padding()
}
.glassBackgroundEffect(in: .capsule)
}
}

#Preview {
Rectangle()
.ornament(attachmentAnchor: .scene(.bottom)) {
WindowToolbarView(title: "Window Title", icon: nil)
}
}

0 comments on commit 2a3ab6a

Please sign in to comment.