mirror of
https://github.com/sindresorhus/touch-bar-simulator
synced 2025-02-17 13:18:24 +00:00
Cleanup
This commit is contained in:
parent
664bf278ce
commit
f011775549
10 changed files with 97 additions and 50 deletions
|
@ -122,6 +122,7 @@ whitelist_rules:
|
|||
- xct_specific_matcher
|
||||
- xctfail_message
|
||||
- yoda_condition
|
||||
- todo
|
||||
analyzer_rules:
|
||||
- unused_import
|
||||
- unused_private_declaration
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
3C37035321FBEBD300177657 /* Defaults.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Defaults.framework; path = Carthage/Build/Mac/Defaults.framework; sourceTree = "<group>"; };
|
||||
3C8493AC21FD3B7F00F12966 /* Glue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Glue.swift; sourceTree = "<group>"; };
|
||||
3C8493AC21FD3B7F00F12966 /* Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Glue.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
AF6C7BC51E7FAF38004A27E0 /* ToolbarSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ToolbarSlider.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E30988DE1E88DD060078CA9E /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Carthage/Build/Mac/Sparkle.framework; sourceTree = "<group>"; };
|
||||
E34D6548214BBDAE00786C24 /* Touch Bar Simulator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Touch Bar Simulator.entitlements"; sourceTree = "<group>"; };
|
||||
|
@ -116,8 +116,8 @@
|
|||
E35579EA21595EDC001CB642 /* TouchBarWindow.swift */,
|
||||
E39A158D214D0C4F00F86D5D /* TouchBarView.swift */,
|
||||
AF6C7BC51E7FAF38004A27E0 /* ToolbarSlider.swift */,
|
||||
3C8493AC21FD3B7F00F12966 /* Glue.swift */,
|
||||
E3930B0F216625BE00F66410 /* Constants.swift */,
|
||||
3C8493AC21FD3B7F00F12966 /* Glue.swift */,
|
||||
E35831A31F4D7EE0003BE371 /* util.swift */,
|
||||
E3FE2CC41E726CE800C6713A /* Assets.xcassets */,
|
||||
E356A16421028DAB000148AD /* Other */,
|
||||
|
|
|
@ -77,7 +77,7 @@ extension AppDelegate: NSMenuDelegate {
|
|||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
menu.addItem(NSMenuItem("Take Screenshot", keyEquivalent: "6", keyModifiers: [.shift, .command]) { _ in
|
||||
menu.addItem(NSMenuItem("Capture Screenshot", keyEquivalent: "6", keyModifiers: [.shift, .command]) { _ in
|
||||
self.captureScreenshot()
|
||||
})
|
||||
|
||||
|
@ -108,6 +108,9 @@ extension AppDelegate: NSMenuDelegate {
|
|||
|
||||
private func statusItemButtonClicked() {
|
||||
toggleView()
|
||||
if window.isVisible { window.orderFront(nil) }
|
||||
|
||||
if window.isVisible {
|
||||
window.orderFront(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import Foundation
|
||||
import Defaults
|
||||
|
||||
// TODO: Upstream all these to https://github.com/sindresorhus/Defaults
|
||||
|
||||
extension Defaults {
|
||||
@discardableResult
|
||||
func observe<T: Codable, Weak: AnyObject>(_ key: Key<T>, tiedToLifetimeOf weaklyHeldObject: Weak, options: NSKeyValueObservingOptions = [.initial, .new, .old], handler: @escaping (KeyChange<T>) -> Void) -> DefaultsObservation {
|
||||
func observe<T: Codable, Weak: AnyObject>(
|
||||
_ key: Key<T>,
|
||||
tiedToLifetimeOf weaklyHeldObject: Weak,
|
||||
options: NSKeyValueObservingOptions = [.initial, .new, .old],
|
||||
handler: @escaping (KeyChange<T>) -> Void
|
||||
) -> DefaultsObservation {
|
||||
var observation: DefaultsObservation!
|
||||
observation = observe(key, options: options) { [weak weaklyHeldObject] change in
|
||||
guard let temporaryStrongReference = weaklyHeldObject else {
|
||||
|
@ -12,9 +19,11 @@ extension Defaults {
|
|||
observation.invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
_ = temporaryStrongReference
|
||||
handler(change)
|
||||
}
|
||||
|
||||
return observation
|
||||
}
|
||||
}
|
||||
|
@ -26,19 +35,22 @@ extension NSMenuItem {
|
|||
`key`.
|
||||
|
||||
```
|
||||
let menuItem = NSMenuItem(title: "Invert Colors").streamState(to: .invertColors)
|
||||
let menuItem = NSMenuItem(title: "Invert Colors").bindState(to: .invertColors)
|
||||
```
|
||||
*/
|
||||
func bindState(to key: Defaults.Key<Bool>) -> Self {
|
||||
self.addAction { _ in
|
||||
addAction { _ in
|
||||
defaults[key].toggle()
|
||||
}
|
||||
|
||||
defaults.observe(key, tiedToLifetimeOf: self) { [unowned self] change in
|
||||
self.isChecked = change.newValue
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// TODO: The doc comments here are out of date
|
||||
/**
|
||||
Adds an action to this menu item that sets the value of `key` in the
|
||||
defaults system to `value`, and initializes this item's state based on
|
||||
|
@ -48,37 +60,44 @@ extension NSMenuItem {
|
|||
enum BillingType {
|
||||
case paper, electronic, duck
|
||||
}
|
||||
let menuItem = NSMenuItem(title: "Duck").streamChoice(to: .billingType, value: .duck)
|
||||
|
||||
let menuItem = NSMenuItem(title: "Duck").bindChecked(to: .billingType, value: .duck)
|
||||
```
|
||||
*/
|
||||
func bindChecked<Value: Equatable>(to key: Defaults.Key<Value>, value: Value) -> Self {
|
||||
self.addAction { _ in
|
||||
addAction { _ in
|
||||
defaults[key] = value
|
||||
}
|
||||
|
||||
defaults.observe(key, tiedToLifetimeOf: self) { [unowned self] change in
|
||||
self.isChecked = (change.newValue == value)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Generalize this to all `NSControl`s, or maybe even all things with a `.action` and `.doubleValue`?
|
||||
extension NSSlider {
|
||||
// TODO: The doc comments here are out of date
|
||||
/**
|
||||
Adds an action to this slider that sets the value of `key` in the defaults
|
||||
system to the slider's `doubleValue`, and initializes its value to the
|
||||
current value of `key`.
|
||||
|
||||
```
|
||||
let slider = NSSlider().streamDoubleValue(to: .transparency)
|
||||
let slider = NSSlider().bindDoubleValue(to: .transparency)
|
||||
```
|
||||
*/
|
||||
func bindDoubleValue(to key: Defaults.Key<Double>) -> Self {
|
||||
self.addAction { sender in
|
||||
addAction { sender in
|
||||
defaults[key] = sender.doubleValue
|
||||
}
|
||||
|
||||
defaults.observe(key, tiedToLifetimeOf: self) { [unowned self] change in
|
||||
self.doubleValue = change.newValue
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,24 +23,24 @@ private final class ToolbarSliderCell: NSSliderCell {
|
|||
if let shadow = self.shadow {
|
||||
// Make room on either side of the view for the shadow to spill into,
|
||||
// rather than clip on the edges
|
||||
frame.origin.x *= ((self.barRect.width - shadow.shadowBlurRadius * 2) / self.barRect.width)
|
||||
frame.origin.x *= ((barRect.width - shadow.shadowBlurRadius * 2) / barRect.width)
|
||||
frame.origin.x += shadow.shadowBlurRadius
|
||||
}
|
||||
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
|
||||
self.shadow?.set()
|
||||
shadow?.set()
|
||||
|
||||
// Circle
|
||||
let path = NSBezierPath(roundedRect: frame, xRadius: 4, yRadius: 12)
|
||||
self.fillColor.set()
|
||||
fillColor.set()
|
||||
path.fill()
|
||||
|
||||
// Border should not draw a shadow
|
||||
NSShadow().set()
|
||||
|
||||
// Border
|
||||
self.borderColor.set()
|
||||
borderColor.set()
|
||||
path.lineWidth = 0.8
|
||||
path.stroke()
|
||||
|
||||
|
@ -50,14 +50,16 @@ private final class ToolbarSliderCell: NSSliderCell {
|
|||
private var barRect = CGRect.zero
|
||||
|
||||
override func drawBar(inside rect: CGRect, flipped: Bool) {
|
||||
self.barRect = rect
|
||||
barRect = rect
|
||||
|
||||
// A knob shadow requires a small skew in the origin of the knob (see above),
|
||||
// which causes the knob to not go all the way to the ends of the bar
|
||||
// Fix this by shortening the bar
|
||||
// which causes the knob to not go all the way to the ends of the bar.
|
||||
// Fix this by shortening the bar.
|
||||
if let shadow = self.shadow {
|
||||
self.barRect = self.barRect.insetBy(dx: shadow.shadowBlurRadius * 2, dy: 0)
|
||||
barRect = barRect.insetBy(dx: shadow.shadowBlurRadius * 2, dy: 0)
|
||||
}
|
||||
super.drawBar(inside: self.barRect, flipped: flipped)
|
||||
|
||||
super.drawBar(inside: barRect, flipped: flipped)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,13 +68,14 @@ extension NSSlider {
|
|||
// from moving a knob that draws a shadow
|
||||
// However, only do so if its value has changed, because if a
|
||||
// redisplay is attempted without a change, then the slider draws
|
||||
// itself brighter for some reason
|
||||
// itself brighter for some reason.
|
||||
func alwaysRedisplayOnValueChanged() -> Self {
|
||||
self.addAction { sender in
|
||||
addAction { sender in
|
||||
if (defaults[.windowTransparency] - sender.doubleValue) != 0 {
|
||||
sender.needsDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ final class TouchBarView: NSView {
|
|||
}
|
||||
|
||||
self.layer!.contents = frameSurface
|
||||
} .takeUnretainedValue()
|
||||
}.takeUnretainedValue()
|
||||
|
||||
DFRSetStatus(2)
|
||||
stream?.start()
|
||||
|
|
|
@ -48,9 +48,11 @@ final class TouchBarWindow: NSPanel {
|
|||
func addTitlebar() {
|
||||
styleMask.insert(.titled)
|
||||
title = "Touch Bar Simulator"
|
||||
|
||||
guard let toolbarView = self.toolbarView else {
|
||||
return
|
||||
}
|
||||
|
||||
toolbarView.addSubviews(
|
||||
makeScreenshotButton(toolbarView),
|
||||
makeTransparencySlider(toolbarView)
|
||||
|
@ -92,32 +94,37 @@ final class TouchBarWindow: NSPanel {
|
|||
private var defaultsObservations: [DefaultsObservation] = []
|
||||
|
||||
func setUp() {
|
||||
let view = self.contentView!
|
||||
let view = contentView!
|
||||
view.wantsLayer = true
|
||||
view.layer?.backgroundColor = NSColor.black.cgColor
|
||||
|
||||
let touchBarView = TouchBarView()
|
||||
self.setContentSize(touchBarView.bounds.adding(padding: 5).size)
|
||||
setContentSize(touchBarView.bounds.adding(padding: 5).size)
|
||||
touchBarView.frame = touchBarView.frame.centered(in: view.bounds)
|
||||
view.addSubview(touchBarView)
|
||||
|
||||
// TODO: These could use the `observe` method with `tiedToLifetimeOf` so we don't have to manually invalidate them.
|
||||
defaultsObservations.append(defaults.observe(.windowTransparency) { change in
|
||||
self.alphaValue = CGFloat(change.newValue)
|
||||
})
|
||||
|
||||
defaultsObservations.append(defaults.observe(.windowDocking) { change in
|
||||
self.docking = change.newValue
|
||||
})
|
||||
|
||||
// TODO: We could maybe simplify this by creating another `Default` extension to bind a default to a KeyPath:
|
||||
// `defaults.bind(.showOnAllDesktops, to: \.showOnAllDesktop)`
|
||||
defaultsObservations.append(defaults.observe(.showOnAllDesktops) { change in
|
||||
self.showOnAllDesktops = change.newValue
|
||||
})
|
||||
|
||||
self.center()
|
||||
self.setFrameOrigin(CGPoint(x: self.frame.origin.x, y: 100))
|
||||
center()
|
||||
setFrameOrigin(CGPoint(x: frame.origin.x, y: 100))
|
||||
|
||||
self.setFrameUsingName(Constants.windowAutosaveName)
|
||||
self.setFrameAutosaveName(Constants.windowAutosaveName)
|
||||
setFrameUsingName(Constants.windowAutosaveName)
|
||||
setFrameAutosaveName(Constants.windowAutosaveName)
|
||||
|
||||
self.orderFront(nil)
|
||||
orderFront(nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
|
@ -51,6 +51,7 @@ extension NSWindow {
|
|||
enum MoveXPositioning {
|
||||
case left, center, right
|
||||
}
|
||||
|
||||
enum MoveYPositioning {
|
||||
case top, center, bottom
|
||||
}
|
||||
|
@ -100,12 +101,21 @@ extension NSMenuItem {
|
|||
}
|
||||
|
||||
extension NSMenuItem {
|
||||
convenience init(_ title: String, keyEquivalent: String = "", keyModifiers: NSEvent.ModifierFlags? = nil, isChecked: Bool = false, action: ((NSMenuItem) -> Void)? = nil) {
|
||||
convenience init(
|
||||
_ title: String,
|
||||
keyEquivalent: String = "",
|
||||
keyModifiers: NSEvent.ModifierFlags? = nil,
|
||||
isChecked: Bool = false,
|
||||
action: ((NSMenuItem) -> Void)? = nil
|
||||
) {
|
||||
self.init(title: title, action: nil, keyEquivalent: keyEquivalent)
|
||||
|
||||
if let keyModifiers = keyModifiers {
|
||||
self.keyEquivalentModifierMask = keyModifiers
|
||||
}
|
||||
|
||||
self.isChecked = isChecked
|
||||
|
||||
if let action = action {
|
||||
self.onAction = action
|
||||
}
|
||||
|
@ -144,7 +154,7 @@ private final class ActionTrampoline<Sender>: NSObject {
|
|||
@objc
|
||||
fileprivate func performAction(_ sender: TargetActionSender) {
|
||||
action(sender as! Sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TargetActionSenderAssociatedKeys {
|
||||
|
@ -153,7 +163,7 @@ private struct TargetActionSenderAssociatedKeys {
|
|||
|
||||
extension TargetActionSender {
|
||||
/**
|
||||
Closure version of `.action`
|
||||
Closure version of `.action`.
|
||||
|
||||
```
|
||||
let menuItem = NSMenuItem(title: "Unicorn")
|
||||
|
@ -168,20 +178,23 @@ extension TargetActionSender {
|
|||
}
|
||||
set {
|
||||
guard let newValue = newValue else {
|
||||
self.target = nil
|
||||
self.action = nil
|
||||
target = nil
|
||||
action = nil
|
||||
TargetActionSenderAssociatedKeys.trampoline[self] = nil
|
||||
return
|
||||
}
|
||||
|
||||
let trampoline = ActionTrampoline(action: newValue)
|
||||
TargetActionSenderAssociatedKeys.trampoline[self] = trampoline
|
||||
self.target = trampoline
|
||||
self.action = #selector(ActionTrampoline<Self>.performAction)
|
||||
target = trampoline
|
||||
action = #selector(ActionTrampoline<Self>.performAction)
|
||||
}
|
||||
}
|
||||
|
||||
func addAction(_ action: @escaping ((Self) -> Void)) {
|
||||
let lastAction = self.onAction
|
||||
self.onAction = { sender in
|
||||
// TODO: The problem with doing it like this is that there's no way to add ability to remove an action. I think a better solution would be to store an array of action handlers using associated object.
|
||||
let lastAction = onAction
|
||||
onAction = { sender in
|
||||
lastAction?(sender)
|
||||
action(sender)
|
||||
}
|
||||
|
@ -198,6 +211,7 @@ extension NSApplication {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Find a namespace to put this onto. I don't like free-floating functions.
|
||||
func pressKey(keyCode: CGKeyCode, flags: CGEventFlags = []) {
|
||||
let eventSource = CGEventSource(stateID: .hidSystemState)
|
||||
let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: keyCode, keyDown: true)
|
||||
|
|
22
readme.md
22
readme.md
|
@ -4,29 +4,23 @@
|
|||
|
||||
Launch the Touch Bar simulator from anywhere without needing to have Xcode installed, whereas Apple requires you to launch it from inside Xcode. It also comes with a handy transparency slider, a screenshot button, and a menu bar icon and system service to toggle the Touch Bar with a click or keyboard shortcut.
|
||||
|
||||
Right- or option-clicking the menu bar icon displays a menu with options to dock the window to the top or bottom of the screen, make it show on all desktops at once, access toolbar features in docked mode, or quit the app.
|
||||
<img src="screenshot-menu-bar.png" width="379" height="283" align="left">
|
||||
|
||||
Clicking the menu bar icon toggles the Touch Bar window.
|
||||
|
||||
Right-clicking or option-clicking the menu bar icon displays a menu with options to dock the window to the top or bottom of the screen, make it show on all desktops at once, access toolbar features in docked mode, or quit the app.
|
||||
|
||||
You can add a toggle shortcut in `System Preferences` → `Keyboard` → `Shortcuts` → `Services` → `Toggle Touch Bar`.
|
||||
|
||||
**Important:** If clicking in the simulator or the screenshot button is not working, you need to go to "System Preferences" → "Security & Privacy" → "Accessibility", and ensure "Touch Bar Simulator.app" is checked. If it's already checked, try unchecking and checking it again.
|
||||
|
||||
**[Website](https://sindresorhus.com/touch-bar-simulator/)** **[Discuss it on Product Hunt](https://www.producthunt.com/posts/touch-bar-simulator)**
|
||||
|
||||
<img src="screenshot.png" width="1129">
|
||||
|
||||
*Check out my other [macOS apps](https://sindresorhus.com/apps)*
|
||||
|
||||
<a href="https://www.patreon.com/sindresorhus">
|
||||
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
|
||||
</a>
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
#### [Download the latest release](https://sindresorhus.com/touch-bar-simulator)
|
||||
|
||||
---
|
||||
|
||||
Or install it with [Homebrew-Cask](https://caskroom.github.io):
|
||||
|
||||
```
|
||||
|
@ -36,6 +30,10 @@ $ brew cask install touch-bar-simulator
|
|||
|
||||
*Requires macOS 10.14 or later.*
|
||||
|
||||
<a href="https://www.patreon.com/sindresorhus">
|
||||
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
|
||||
</a>
|
||||
|
||||
|
||||
## Screenshot
|
||||
|
||||
|
@ -68,6 +66,8 @@ Xcode 10 moved the required private symbols needed to trigger the Touch Bar simu
|
|||
|
||||
## Related
|
||||
|
||||
- [Website](https://sindresorhus.com/touch-bar-simulator/)
|
||||
- [Product Hunt submission](https://www.producthunt.com/posts/touch-bar-simulator)
|
||||
- [Gifski](https://github.com/sindresorhus/gifski-app) - Convert videos to high-quality GIFs on your Mac
|
||||
- [More apps…](https://sindresorhus.com/#apps)
|
||||
|
||||
|
|
BIN
screenshot-menu-bar.png
Normal file
BIN
screenshot-menu-bar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 400 KiB |
Loading…
Add table
Reference in a new issue