1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-16 18:30:32 +00:00

Merge pull request #973 from TomHarte/TransientActivity

Converts activity indicators to transient in-window presentation.
This commit is contained in:
Thomas Harte 2021-07-15 20:15:33 -04:00 committed by GitHub
commit fc248951cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 279 additions and 225 deletions

View File

@ -27,6 +27,8 @@ class Observer {
virtual void register_led([[maybe_unused]] const std::string &name) {}
/// Announces to the receiver that there is a drive of name @c name.
///
/// If a drive has the same name as an LED, that LED goes with this drive.
virtual void register_drive([[maybe_unused]] const std::string &name) {}
/// Informs the receiver of the new state of the LED with name @c name.

View File

@ -1,113 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="activityPanel" destination="ZW7-Bw-4RP" id="GRG-Q6-RQU"/>
<outlet property="activityView" destination="tpZ-0B-QQu" id="afo-Oq-Nyl"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Activity" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="300" y="76" width="200" height="131"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="131"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ySY-ir-hzb" userLabel="First indicator">
<rect key="frame" x="20" y="95" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="ySY-ir-hzb" secondAttribute="height" multiplier="1:1" id="UX0-hT-7Td"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="DhQ-Di-tRT"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tah-UQ-vdf">
<rect key="frame" x="44" y="94" width="59" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 1" id="a5P-Ci-RzC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncQ-wN-C61" userLabel="Second indicator">
<rect key="frame" x="20" y="70" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="ncQ-wN-C61" secondAttribute="height" multiplier="1:1" id="176-v3-mVW"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="jlb-bk-FPd"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="14O-Lq-Npx">
<rect key="frame" x="44" y="69" width="61" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 2" id="NE1-CO-pGI">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0rV-Th-Zwt" userLabel="Third indicator">
<rect key="frame" x="20" y="45" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="0rV-Th-Zwt" secondAttribute="height" multiplier="1:1" id="Ai8-b3-Nn5"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="CJy-Jn-eCL"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acy-tT-OFH">
<rect key="frame" x="44" y="44" width="61" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 3" id="FSR-y6-7WE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bvH-EJ-TYb" userLabel="Fourth indicator">
<rect key="frame" x="20" y="20" width="17" height="16"/>
<constraints>
<constraint firstAttribute="width" secondItem="bvH-EJ-TYb" secondAttribute="height" multiplier="1:1" id="cKc-q1-2Q4"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="eoN-hl-30l"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R0g-Oa-VB5">
<rect key="frame" x="44" y="19" width="62" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 4" id="aGr-cd-jC0">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="14O-Lq-Npx" firstAttribute="centerY" secondItem="ncQ-wN-C61" secondAttribute="centerY" id="0Ht-U2-sPg"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="top" secondItem="0rV-Th-Zwt" secondAttribute="bottom" constant="9" id="0xw-qA-6vP"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="14O-Lq-Npx" secondAttribute="trailing" constant="20" id="5eo-XI-a3W"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="centerY" secondItem="ySY-ir-hzb" secondAttribute="centerY" id="6Hn-ts-mTi"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="leading" secondItem="bvH-EJ-TYb" secondAttribute="trailing" constant="10" id="Dgy-JI-nA1"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="centerY" secondItem="bvH-EJ-TYb" secondAttribute="centerY" id="Gfq-mB-Y1z"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="centerY" secondItem="0rV-Th-Zwt" secondAttribute="centerY" id="ImF-rK-oOr"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="leading" secondItem="0rV-Th-Zwt" secondAttribute="trailing" constant="10" id="JSU-pZ-l9Q"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="KMh-EO-rxE"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="top" secondItem="ncQ-wN-C61" secondAttribute="bottom" constant="9" id="Q2g-yM-nlJ"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="QUI-Hc-Bcl"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="bKh-4L-mqj"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="cPA-Ls-fLj"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Tah-UQ-vdf" secondAttribute="trailing" constant="20" id="igX-7U-TeE"/>
<constraint firstItem="14O-Lq-Npx" firstAttribute="leading" secondItem="ncQ-wN-C61" secondAttribute="trailing" constant="10" id="jjP-qH-Pqg"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="leading" secondItem="ySY-ir-hzb" secondAttribute="trailing" constant="10" id="lux-Nz-K7E"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Acy-tT-OFH" secondAttribute="trailing" constant="20" id="mEe-VT-dNr"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="top" secondItem="ySY-ir-hzb" secondAttribute="bottom" constant="9" id="mSc-jj-amw"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="R0g-Oa-VB5" secondAttribute="trailing" constant="20" id="sR8-Ph-suC"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="wbj-48-DYq"/>
</constraints>
</view>
<point key="canvasLocation" x="84" y="115"/>
</window>
<visualEffectView hidden="YES" wantsLayer="YES" appearanceType="vibrantDark" blendingMode="withinWindow" material="HUDWindow" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="131"/>
<subviews>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ySY-ir-hzb" userLabel="First indicator">
<rect key="frame" x="8" y="105" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="ySY-ir-hzb" secondAttribute="height" multiplier="1:1" id="UX0-hT-7Td"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="DhQ-Di-tRT"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tah-UQ-vdf">
<rect key="frame" x="30" y="107" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 1" id="a5P-Ci-RzC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncQ-wN-C61" userLabel="Second indicator">
<rect key="frame" x="8" y="81" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="ncQ-wN-C61" secondAttribute="height" multiplier="1:1" id="176-v3-mVW"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="jlb-bk-FPd"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="14O-Lq-Npx">
<rect key="frame" x="30" y="83" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 2" id="NE1-CO-pGI">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0rV-Th-Zwt" userLabel="Third indicator">
<rect key="frame" x="8" y="57" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="0rV-Th-Zwt" secondAttribute="height" multiplier="1:1" id="Ai8-b3-Nn5"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="CJy-Jn-eCL"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acy-tT-OFH">
<rect key="frame" x="30" y="59" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 3" id="FSR-y6-7WE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bvH-EJ-TYb" userLabel="Fourth indicator">
<rect key="frame" x="8" y="33" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="bvH-EJ-TYb" secondAttribute="height" multiplier="1:1" id="cKc-q1-2Q4"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="eoN-hl-30l"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R0g-Oa-VB5">
<rect key="frame" x="30" y="35" width="62" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 4" id="aGr-cd-jC0">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="14O-Lq-Npx" firstAttribute="centerY" secondItem="ncQ-wN-C61" secondAttribute="centerY" id="0Ht-U2-sPg"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="top" secondItem="0rV-Th-Zwt" secondAttribute="bottom" constant="8" symbolic="YES" id="0xw-qA-6vP"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="14O-Lq-Npx" secondAttribute="trailing" constant="8" id="5eo-XI-a3W"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="centerY" secondItem="ySY-ir-hzb" secondAttribute="centerY" id="6Hn-ts-mTi"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="leading" secondItem="bvH-EJ-TYb" secondAttribute="trailing" constant="8" symbolic="YES" id="Dgy-JI-nA1"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="centerY" secondItem="bvH-EJ-TYb" secondAttribute="centerY" id="Gfq-mB-Y1z"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="centerY" secondItem="0rV-Th-Zwt" secondAttribute="centerY" id="ImF-rK-oOr"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="leading" secondItem="0rV-Th-Zwt" secondAttribute="trailing" constant="8" symbolic="YES" id="JSU-pZ-l9Q"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="KMh-EO-rxE"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="top" secondItem="ncQ-wN-C61" secondAttribute="bottom" constant="8" symbolic="YES" id="Q2g-yM-nlJ"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="QUI-Hc-Bcl"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="bKh-4L-mqj"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="cPA-Ls-fLj"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Tah-UQ-vdf" secondAttribute="trailing" constant="8" id="igX-7U-TeE"/>
<constraint firstItem="14O-Lq-Npx" firstAttribute="leading" secondItem="ncQ-wN-C61" secondAttribute="trailing" constant="8" symbolic="YES" id="jjP-qH-Pqg"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="leading" secondItem="ySY-ir-hzb" secondAttribute="trailing" constant="8" symbolic="YES" id="lux-Nz-K7E"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Acy-tT-OFH" secondAttribute="trailing" constant="8" id="mEe-VT-dNr"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="top" secondItem="ySY-ir-hzb" secondAttribute="bottom" constant="8" symbolic="YES" id="mSc-jj-amw"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="R0g-Oa-VB5" secondAttribute="trailing" constant="8" id="sR8-Ph-suC"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="8" id="wbj-48-DYq"/>
</constraints>
<point key="canvasLocation" x="57" y="80"/>
</visualEffectView>
</objects>
</document>

View File

@ -8,6 +8,7 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsController" destination="Lt7-Vu-Vry" id="Tvy-Sa-5oy"/>
<outlet property="optionsView" destination="tpZ-0B-QQu" id="Sat-aY-EKi"/>
</connections>
</customObject>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -141,25 +141,6 @@
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Activity" keyEquivalent="a" id="WCd-6R-baV">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="showActivity:" target="-1" id="oeF-uJ-cOS"/>
</connections>
</menuItem>
<menuItem title="Show Options" keyEquivalent="o" id="GtG-CV-Uro">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="showOptions:" target="-1" id="M6T-DE-Duo"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Input" id="5bL-VY-cxd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Input" id="6yv-Cf-E9r">

View File

@ -8,6 +8,7 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsController" destination="cWS-AS-crO" id="KeQ-V1-s46"/>
<outlet property="optionsView" destination="e1J-pw-zGw" id="ALr-SV-afm"/>
</connections>
</customObject>

View File

@ -51,12 +51,7 @@ class MachineDocument:
@IBOutlet var optionsController: MachineController!
/// The activity panel, if one is deemed appropriate.
@IBOutlet var activityPanel: NSPanel!
/// An action to display the activity panel, if there is one.
@IBAction func showActivity(_ sender: AnyObject!) {
activityPanel.setIsVisible(true)
}
@IBOutlet var activityView: NSView!
/// The volume view.
@IBOutlet var volumeView: NSView!
@ -83,11 +78,6 @@ class MachineDocument:
}
}
private func dismissPanels() {
activityPanel?.setIsVisible(false)
activityPanel = nil
}
override func close() {
// Close any dangling sheets.
//
@ -105,9 +95,6 @@ class MachineDocument:
// Stop the machine, if any.
machine?.stop()
// Dismiss panels.
dismissPanels()
// End the update cycle.
actionLock.lock()
drawLock.lock()
@ -166,9 +153,10 @@ class MachineDocument:
func windowDidUpdate(_ notification: Notification) {
if self.windowControllers.count > 0, let window = self.windowControllers[0].window, window.isVisible {
// Grab the regular window title, if it's not already stored.
if self.unadornedWindowTitle.count == 0 {
if self.unadornedWindowTitle == "" {
self.unadornedWindowTitle = window.title
}
updateWindowTitle()
// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing.
if self.interactionMode == .notStarted {
@ -195,7 +183,11 @@ class MachineDocument:
}
}
// MARK: - Connections Between Machine and the Outside World
func windowDidEnterFullScreen(_ notification: Notification) {
updateActivityViewVisibility()
}
// MARK: - Connections Between Machine and the Outside World.
private func setupMachineOutput() {
if let machine = self.machine, let scanTargetView = self.scanTargetView, machine.view != scanTargetView {
@ -203,9 +195,6 @@ class MachineDocument:
let aspectRatio = self.aspectRatio()
machine.setView(scanTargetView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
// Get rid of all existing accessory panels.
dismissPanels()
// Attach an options panel if one is available.
if let optionsNibName = self.machineDescription?.optionsNibName {
Bundle.main.loadNibNamed(optionsNibName, owner: self, topLevelObjects: nil)
@ -229,6 +218,16 @@ class MachineDocument:
}
}
// Set up a fader for the volume and options.
var fadingViews: [NSView] = []
if let optionsView = self.optionsView {
fadingViews.append(optionsView)
}
if let volumeView = self.volumeView {
fadingViews.append(volumeView)
}
optionsFader = ViewFader(views: fadingViews)
// Create and populate an activity display if required.
setupActivityDisplay()
@ -400,6 +399,7 @@ class MachineDocument:
}
// MARK: - MachinePicker Outlets and Actions
@IBOutlet var machinePicker: MachinePicker?
@IBOutlet var machinePickerPanel: NSWindow?
@IBAction func createMachine(_ sender: NSButton?) {
@ -418,6 +418,7 @@ class MachineDocument:
}
// MARK: - ROMRequester Outlets and Actions
@IBOutlet var romRequesterPanel: NSWindow?
@IBOutlet var romRequesterText: NSTextField?
@IBOutlet var romReceiverErrorField: NSTextField?
@ -522,7 +523,8 @@ class MachineDocument:
}
}
// MARK: Joystick-via-the-keyboard selection
// MARK: - Joystick-via-the-keyboard selection.
@IBAction func useKeyboardAsPhysicalKeyboard(_ sender: NSMenuItem?) {
machine.inputMode = .keyboardPhysical
}
@ -568,9 +570,6 @@ class MachineDocument:
menuItem.state = machine.inputMode == .joystick ? .on : .off
return true
case #selector(self.showActivity(_:)):
return self.activityPanel != nil
case #selector(self.insertMedia(_:)):
return self.machine != nil && self.machine.canInsertMedia
@ -580,6 +579,8 @@ class MachineDocument:
return super.validateUserInterfaceItem(item)
}
// MARK: - Screenshots.
/// Saves a screenshot of the machine's current display.
@IBAction func saveScreenshot(_ sender: AnyObject!) {
// Grab a date formatter and form a file name.
@ -601,13 +602,30 @@ class MachineDocument:
}
// MARK: - Window Title Updates.
private var unadornedWindowTitle = ""
private var mouseIsCaptured = false
private var windowTitleSuffix = ""
private func updateWindowTitle() {
var title = self.unadornedWindowTitle
if windowTitleSuffix != "" {
title += windowTitleSuffix
}
if mouseIsCaptured {
title += " (press ⌘+control to release mouse)"
}
self.windowControllers[0].window?.title = title
}
internal func scanTargetViewDidCaptureMouse(_ view: CSScanTargetView) {
self.windowControllers[0].window?.title = self.unadornedWindowTitle + " (press ⌘+control to release mouse)"
mouseIsCaptured = true
updateWindowTitle()
}
internal func scanTargetViewDidReleaseMouse(_ view: CSScanTargetView) {
self.windowControllers[0].window?.title = self.unadornedWindowTitle
mouseIsCaptured = false
updateWindowTitle()
}
// MARK: - Activity Display.
@ -621,18 +639,18 @@ class MachineDocument:
var isBlinking = false
}
private var leds: [String: LED] = [:]
private var activityFader: ViewFader! = nil
func setupActivityDisplay() {
var leds = machine.leds
if leds.count > 0 {
Bundle.main.loadNibNamed("Activity", owner: self, topLevelObjects: nil)
showActivity(nil)
// Inspect the activity panel for indicators.
var activityIndicators: [NSLevelIndicator] = []
var textFields: [NSTextField] = []
if let contentView = self.activityPanel.contentView {
for view in contentView.subviews {
if let activityView = self.activityView {
for view in activityView.subviews {
if let levelIndicator = view as? NSLevelIndicator {
activityIndicators.append(levelIndicator)
}
@ -660,16 +678,30 @@ class MachineDocument:
self.leds[leds[c]] = LED(levelIndicator: activityIndicators[c])
}
// Add a constraints to minimise window height.
let heightConstraint = NSLayoutConstraint(
item: self.activityPanel.contentView!,
attribute: .bottom,
relatedBy: .equal,
toItem: activityIndicators[leds.count-1],
attribute: .bottom,
multiplier: 1.0,
constant: 20.0)
self.activityPanel.contentView?.addConstraint(heightConstraint)
// Create a fader.
activityFader = ViewFader(views: [self.activityView!])
// Add view to window, and constrain.
if let superview = activityIndicators[leds.count-1].superview {
superview.addConstraint(
activityIndicators[leds.count-1].bottomAnchor.constraint(equalTo: activityIndicators[leds.count-1].superview!.bottomAnchor, constant: -8.0)
)
}
if let windowView = self.volumeView.superview {
windowView.addSubview(self.activityView)
let constraints = [
self.activityView.rightAnchor.constraint(equalTo: windowView.rightAnchor),
self.activityView.topAnchor.constraint(equalTo: windowView.topAnchor),
]
windowView.addConstraints(constraints)
activityView.layer!.cornerRadius = 5.0
activityView.layer!.maskedCorners = [.layerMinXMinYCorner]
}
// Show or hide activity view as per current state.
updateActivityViewVisibility(true)
}
}
@ -679,7 +711,7 @@ class MachineDocument:
// pile up allow there to be only one in flight at a time.
if let led = leds[ledName] {
DispatchQueue.main.async {
if !led.isBlinking {
if !led.isBlinking && led.isLit {
led.levelIndicator.floatValue = 0.0
led.isBlinking = true
@ -687,6 +719,10 @@ class MachineDocument:
led.levelIndicator.floatValue = led.isLit ? 1.0 : 0.0
led.isBlinking = false
}
// Treat a new blink as potentially re-showing the activity
// indicators, given windowed-mode behaviour.
self.updateActivityViewVisibility()
}
}
}
@ -695,14 +731,120 @@ class MachineDocument:
func machine(_ machine: CSMachine, led ledName: String, didChangeToLit isLit: Bool) {
// If there is such an LED, switch it appropriately.
if let led = leds[ledName] {
DispatchQueue.main.async {
DispatchQueue.main.async { [self] in
// Do nothing for no change of state.
if led.isLit == isLit {
return
}
led.levelIndicator.floatValue = isLit ? 1.0 : 0.0
led.isLit = isLit
// Possibly show or hide the activity subview.
self.updateActivityViewVisibility()
}
}
}
private func updateActivityViewVisibility(_ isAppLaunch : Bool = false) {
if let window = self.windowControllers.first?.window, let activityFader = self.activityFader {
// If in a window, show the activity view transiently to
// acknowledge changes of state. In full screen show it
// permanently as long as at least one LED is lit.
if window.styleMask.contains(.fullScreen) {
let litLEDs = self.leds.filter { $0.value.isLit }
if litLEDs.isEmpty{
activityFader.animateOut(delay: 0.2)
} else {
activityFader.animateIn()
}
} else if !isAppLaunch {
activityFader.showTransiently(for: 1.0)
}
let litLEDs = self.leds.filter { $0.value.isLit }
if litLEDs.isEmpty || !window.styleMask.contains(.fullScreen) {
activityFader.animateOut(delay: window.styleMask.contains(.fullScreen) ? 0.2 : 0.0)
} else {
activityFader.animateIn()
}
}
}
// MARK: - In-window panels (i.e. options, volume).
private var optionsFader: ViewFader! = nil
internal func scanTargetViewDidShowOSMouseCursor(_ view: CSScanTargetView) {
// The OS mouse cursor became visible, so show the volume controls.
optionsFader.animateIn()
}
internal func scanTargetViewWillHideOSMouseCursor(_ view: CSScanTargetView) {
// The OS mouse cursor will be hidden, so hide the volume controls.
optionsFader.animateOut(delay: 0.0)
}
// MARK: - Helpers for fading things in and out.
/// Maintains a list of views and offers in-and-out animations on those,
/// testing current state as necessary and otherwise coordinating with
/// CoreAnimation.
private class ViewFader: NSObject, CAAnimationDelegate {
private var views: [NSView]
init(views: [NSView]) {
self.views = views
for view in views {
view.isHidden = true
}
}
func animationDidStop(_ animation: CAAnimation, finished: Bool) {
if finished {
for view in views {
view.isHidden = true
}
}
}
func animateIn() {
for view in views {
view.layer?.removeAllAnimations()
view.isHidden = false
}
}
func animateOut(delay : TimeInterval) {
// Do nothing if already animating out or invisible.
if views[0].isHidden || views[0].layer?.animation(forKey: "opacity") != nil {
return
}
for view in views {
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.beginTime = CACurrentMediaTime() + delay
fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.duration = 0.2
fadeAnimation.delegate = self
fadeAnimation.fillMode = .forwards
fadeAnimation.isRemovedOnCompletion = false
view.layer?.removeAllAnimations()
view.layer!.add(fadeAnimation, forKey: "opacity")
}
}
func showTransiently(for period: TimeInterval) {
animateIn()
animateOut(delay: period)
}
}
// MARK: - Volume Control.
@IBAction func setVolume(_ sender: NSSlider!) {
if let machine = self.machine {
let linearValue = log2(sender.floatValue)
@ -711,71 +853,6 @@ class MachineDocument:
}
}
// This class is pure nonsense to work around Xcode's opaque behaviour.
// If I make the main class a sub of CAAnimationDelegate then the compiler
// generates a bridging header that doesn't include QuartzCore and therefore
// can't find a declaration of the CAAnimationDelegate protocol. Doesn't
// seem to matter what I add explicitly to the link stage, which version of
// macOS I set as the target, etc.
//
// So, the workaround: make my CAAnimationDelegate something that doesn't
// appear in the bridging header.
fileprivate class ViewFader: NSObject, CAAnimationDelegate {
var views: [NSView]
init(views: [NSView]) {
self.views = views
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
for view in views {
view.isHidden = true
}
}
}
fileprivate var animationFader: ViewFader? = nil
var fadingViews: [NSView] {
get {
var views: [NSView] = []
if let optionsView = self.optionsView {
views.append(optionsView)
}
if let volumeView = self.volumeView {
views.append(volumeView)
}
return views
}
}
internal func scanTargetViewDidShowOSMouseCursor(_ view: CSScanTargetView) {
// The OS mouse cursor became visible, so show the volume controls.
animationFader = nil
for view in self.fadingViews {
view.layer?.removeAllAnimations()
view.isHidden = false
view.layer?.opacity = 1.0
}
}
internal func scanTargetViewWillHideOSMouseCursor(_ view: CSScanTargetView) {
// The OS mouse cursor will be hidden, so hide the volume controls.
let fadingViews = self.fadingViews
if !fadingViews[0].isHidden && fadingViews[0].layer?.animation(forKey: "opacity") == nil {
for view in self.fadingViews {
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.duration = 0.2
fadeAnimation.delegate = animationFader
view.layer?.add(fadeAnimation, forKey: "opacity")
view.layer?.opacity = 0.0
}
animationFader = ViewFader(views: fadingViews)
}
}
// The user's selected volume is stored as 1 - volume in the user defaults in order
// to take advantage of the default value being 0.
private func userDefaultsVolume() -> Float {