From c7911f3f5c6ca0805a568e4238a309d70fd188af Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Tue, 10 Aug 2021 00:22:50 +0900 Subject: [PATCH] feat: support voiceover + "speak items under the cursor" (closes #1070) --- src/logic/Windows.swift | 9 +++++++++ src/ui/App.swift | 1 + src/ui/main-window/ThumbnailView.swift | 12 ++++++++++++ src/ui/main-window/ThumbnailsPanel.swift | 4 ++-- src/ui/main-window/WindowControlView.swift | 2 ++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index 5731ee43c..cda982a66 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -72,6 +72,15 @@ class Windows { let focusedView = ThumbnailsView.recycledViews[focusedWindowIndex] focusedView.highlight(true) App.app.thumbnailsPanel.thumbnailsView.scrollView.contentView.scrollToVisible(focusedView.frame) + voiceOverFocusedWindow() + } + + static func voiceOverFocusedWindow() { + guard App.app.appIsBeingUsed && App.app.thumbnailsPanel.isKeyWindow else { return } + let window = ThumbnailsView.recycledViews[focusedWindowIndex] + if window.window_ != nil && window.window != nil { + App.app.thumbnailsPanel.makeFirstResponder(window) + } } static func focusedWindow() -> Window? { diff --git a/src/ui/App.swift b/src/ui/App.swift index d43295be2..754b60608 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -217,6 +217,7 @@ class App: AppCenterApplication, NSApplicationDelegate { thumbnailsPanel.thumbnailsView.updateItemsAndLayout(currentScreen) guard appIsBeingUsed else { return } thumbnailsPanel.setContentSize(thumbnailsPanel.thumbnailsView.frame.size) + Windows.voiceOverFocusedWindow() // at this point ThumbnailViews are assigned to the window, and ready thumbnailsPanel.display() guard appIsBeingUsed else { return } currentScreen.repositionPanel(thumbnailsPanel, .appleCentered) diff --git a/src/ui/main-window/ThumbnailView.swift b/src/ui/main-window/ThumbnailView.swift index de8d6478d..f9f758a83 100644 --- a/src/ui/main-window/ThumbnailView.swift +++ b/src/ui/main-window/ThumbnailView.swift @@ -22,6 +22,12 @@ class ThumbnailView: NSStackView { var isShowingWindowControls = false var windowlessIcon = FontIcon(.newWindow) + // for VoiceOver cursor + override var canBecomeKeyView: Bool { true } + override var acceptsFirstResponder: Bool { true } + + override func isAccessibilityElement() -> Bool { true } + convenience init() { self.init(frame: .zero) setupView() @@ -44,6 +50,7 @@ class ThumbnailView: NSStackView { addWindowControls() addDockLabelIcon() thumbnail.addSubview(windowlessIcon, positioned: .above, relativeTo: nil) + setAccessibilityChildren([]) } private func addDockLabelIcon() { @@ -109,6 +116,7 @@ class ThumbnailView: NSStackView { let thumbnailSize = NSSize(width: thumbnailWidth.rounded(), height: thumbnailHeight.rounded()) thumbnail.image?.size = thumbnailSize thumbnail.frame.size = thumbnailSize + thumbnail.setAccessibilityLabel(element.title) } assignIfDifferent(&spacing, Preferences.hideThumbnails ? 0 : Preferences.intraCellPadding) assignIfDifferent(&hStackView.spacing, Preferences.fontHeight == 0 ? 0 : Preferences.intraCellPadding) @@ -117,12 +125,14 @@ class ThumbnailView: NSStackView { let appIconSize = NSSize(width: Preferences.iconSize, height: Preferences.iconSize) appIcon.image?.size = appIconSize appIcon.frame.size = appIconSize + appIcon.setAccessibilityTitle(element.application.runningApplication.localizedName) } let labelChanged = label.string != element.title if labelChanged { label.string = element.title // workaround: setting string on NSTextView changes the font (most likely a Cocoa bug) label.font = Preferences.font + setAccessibilityTitle(element.title) } assignIfDifferent(&hiddenIcon.isHidden, !element.isHidden || Preferences.hideStatusIcons) assignIfDifferent(&fullscreenIcon.isHidden, !element.isFullscreen || Preferences.hideStatusIcons) @@ -169,8 +179,10 @@ class ThumbnailView: NSStackView { let view = dockLabelIcon.subviews[1] as! ThumbnailFontIconView if dockLabel > 30 { view.setFilledStar() + view.setAccessibilityLabel("Red badge with star") } else { view.setNumber(dockLabel, true) + view.setAccessibilityLabel("Red badge with number \(dockLabel)") } dockLabelIcon.setFrameOrigin(NSPoint(x: appIcon.frame.maxX - dockLabelIcon.fittingSize.width - 1, y: appIcon.frame.maxY - dockLabelIcon.fittingSize.height + 4)) } diff --git a/src/ui/main-window/ThumbnailsPanel.swift b/src/ui/main-window/ThumbnailsPanel.swift index 8e5a3664a..cac486beb 100644 --- a/src/ui/main-window/ThumbnailsPanel.swift +++ b/src/ui/main-window/ThumbnailsPanel.swift @@ -14,8 +14,6 @@ class ThumbnailsPanel: NSPanel, NSWindowDelegate { titleVisibility = .hidden backgroundColor = .clear contentView!.addSubview(thumbnailsView) - preservesContentDuringLiveResize = false - disableSnapshotRestoration() // triggering AltTab before or during Space transition animation brings the window on the Space post-transition collectionBehavior = .canJoinAllSpaces // 2nd highest level possible; this allows the app to go on top of context menus @@ -23,6 +21,8 @@ class ThumbnailsPanel: NSPanel, NSWindowDelegate { level = .popUpMenu // helps filter out this window from the thumbnails setAccessibilitySubrole(.unknown) + // for VoiceOver + setAccessibilityLabel(App.name) } func windowDidResignKey(_ notification: Notification) { diff --git a/src/ui/main-window/WindowControlView.swift b/src/ui/main-window/WindowControlView.swift index be5312cb9..5f3af1007 100644 --- a/src/ui/main-window/WindowControlView.swift +++ b/src/ui/main-window/WindowControlView.swift @@ -14,6 +14,8 @@ class WindowControlView: NSImageView { originalImage = image hoveredImage = image.tinted(.init(white: 0, alpha: 0.25)) hovered(false) + setAccessibilityLabel(imageName) + setAccessibilityRole(.button) } func hovered(_ isHovered: Bool) {