Tweaks
This commit is contained in:
parent
4e5a5fdfff
commit
d83c83ba4e
|
@ -0,0 +1,4 @@
|
|||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: ['DockProgress']
|
|
@ -1,6 +1,7 @@
|
|||
only_rules:
|
||||
- anyobject_protocol
|
||||
- accessibility_trait_for_button
|
||||
- array_init
|
||||
- blanket_disable_command
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
- closing_brace
|
||||
|
@ -10,6 +11,7 @@ only_rules:
|
|||
- collection_alignment
|
||||
- colon
|
||||
- comma
|
||||
- comma_inheritance
|
||||
- compiler_protocol_init
|
||||
- computed_accessors_order
|
||||
- conditional_returns_on_newline
|
||||
|
@ -20,6 +22,7 @@ only_rules:
|
|||
- control_statement
|
||||
- custom_rules
|
||||
- deployment_target
|
||||
- direct_return
|
||||
- discarded_notification_center_observer
|
||||
- discouraged_assert
|
||||
- discouraged_direct_init
|
||||
|
@ -27,6 +30,7 @@ only_rules:
|
|||
- discouraged_object_literal
|
||||
- discouraged_optional_boolean
|
||||
- discouraged_optional_collection
|
||||
- duplicate_conditions
|
||||
- duplicate_enum_cases
|
||||
- duplicate_imports
|
||||
- duplicated_key_in_dictionary_literal
|
||||
|
@ -52,7 +56,7 @@ only_rules:
|
|||
- implicit_getter
|
||||
- implicit_return
|
||||
- inclusive_language
|
||||
- inert_defer
|
||||
- invalid_swiftlint_command
|
||||
- is_disjoint
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
|
@ -68,7 +72,6 @@ only_rules:
|
|||
- lower_acl_than_parent
|
||||
- mark
|
||||
- modifier_order
|
||||
- multiline_arguments
|
||||
- multiline_function_chains
|
||||
- multiline_literal_brackets
|
||||
- multiline_parameters
|
||||
|
@ -78,13 +81,14 @@ only_rules:
|
|||
- no_fallthrough_only
|
||||
- no_space_in_method_call
|
||||
- notification_center_detachment
|
||||
- ns_number_init_as_function_reference
|
||||
- nsobject_prefer_isequal
|
||||
- number_separator
|
||||
- opening_brace
|
||||
- operator_usage_whitespace
|
||||
- operator_whitespace
|
||||
- orphaned_doc_comment
|
||||
- overridden_super_call
|
||||
- prefer_self_in_static_references
|
||||
- prefer_self_type_over_type_of_self
|
||||
- prefer_zero_over_explicit_init
|
||||
- private_action
|
||||
|
@ -105,12 +109,17 @@ only_rules:
|
|||
- redundant_void_return
|
||||
- required_enum_case
|
||||
- return_arrow_whitespace
|
||||
- return_value_from_void_function
|
||||
- self_binding
|
||||
- self_in_property_initialization
|
||||
- shorthand_operator
|
||||
- shorthand_optional_binding
|
||||
- sorted_first_last
|
||||
- statement_position
|
||||
- static_operator
|
||||
- strong_iboutlet
|
||||
- superfluous_disable_command
|
||||
- superfluous_else
|
||||
- switch_case_alignment
|
||||
- switch_case_on_newline
|
||||
- syntactic_sugar
|
||||
|
@ -121,12 +130,12 @@ only_rules:
|
|||
- trailing_newline
|
||||
- trailing_semicolon
|
||||
- trailing_whitespace
|
||||
- unavailable_condition
|
||||
- unavailable_function
|
||||
- unneeded_break_in_switch
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unowned_variable_capture
|
||||
- untyped_error_in_catch
|
||||
- unused_capture_list
|
||||
- unused_closure_parameter
|
||||
- unused_control_flow_label
|
||||
- unused_enumerated
|
||||
|
@ -134,9 +143,9 @@ only_rules:
|
|||
- unused_setter_value
|
||||
- valid_ibinspectable
|
||||
- vertical_parameter_alignment
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- vertical_whitespace_opening_braces
|
||||
- void_function_in_ternary
|
||||
- void_return
|
||||
- xct_specific_matcher
|
||||
- xctfail_message
|
||||
|
@ -145,6 +154,9 @@ analyzer_rules:
|
|||
- capture_variable
|
||||
- unused_declaration
|
||||
- unused_import
|
||||
- typesafe_array_init
|
||||
for_where:
|
||||
allow_for_as_filter: true
|
||||
number_separator:
|
||||
minimum_length: 5
|
||||
identifier_name:
|
||||
|
@ -154,7 +166,6 @@ identifier_name:
|
|||
min_length:
|
||||
warning: 2
|
||||
error: 2
|
||||
validates_start_with_lowercase: false
|
||||
allowed_symbols:
|
||||
- '_'
|
||||
excluded:
|
||||
|
@ -199,3 +210,6 @@ custom_rules:
|
|||
final_class:
|
||||
regex: '^class [a-zA-Z\d]+[^{]+\{'
|
||||
message: 'Classes should be marked as final whenever possible. If you actually need it to be subclassable, just add `// swiftlint:disable:next final_class`.'
|
||||
no_alignment_center:
|
||||
regex: '\b\(alignment: .center\b'
|
||||
message: 'This alignment is the default.'
|
||||
|
|
|
@ -20,7 +20,7 @@ final class AppState: ObservableObject {
|
|||
.bar,
|
||||
.squircle(color: .systemGray),
|
||||
.circle(radius: 30, color: .white),
|
||||
.badge(color: .systemBlue) { Int(DockProgress.animatedProgress * 12) },
|
||||
.badge(color: .systemBlue) { Int(DockProgress.displayedProgress * 12) },
|
||||
.pie(color: .systemBlue)
|
||||
]
|
||||
|
||||
|
@ -30,15 +30,17 @@ final class AppState: ObservableObject {
|
|||
DockProgress.resetProgress()
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
||||
DockProgress.progress += 0.2
|
||||
Task { @MainActor in
|
||||
DockProgress.progress += 0.2
|
||||
|
||||
if DockProgress.animatedProgress >= 1 {
|
||||
if let style = stylesIterator.next() {
|
||||
DockProgress.resetProgress()
|
||||
DockProgress.style = style
|
||||
} else {
|
||||
// Reset iterator when all is looped.
|
||||
stylesIterator = styles.makeIterator()
|
||||
if DockProgress.displayedProgress >= 1 {
|
||||
if let style = stylesIterator.next() {
|
||||
DockProgress.resetProgress()
|
||||
DockProgress.style = style
|
||||
} else {
|
||||
// Reset iterator when all is looped.
|
||||
stylesIterator = styles.makeIterator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// swift-tools-version:5.7
|
||||
// swift-tools-version:5.8
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
import Cocoa
|
||||
|
||||
/**
|
||||
Show progress in your app's Dock icon.
|
||||
|
||||
Use either ``progress`` or ``progressInstance``.
|
||||
*/
|
||||
@MainActor
|
||||
public enum DockProgress {
|
||||
private static var progressObserver: NSKeyValueObservation?
|
||||
private static var finishedObserver: NSKeyValueObservation?
|
||||
|
||||
private static var elapsedTimeSinceLastRefresh = 0.0
|
||||
private static var displayLinkObserver = DisplayLinkObserver { (displayLinkObserver, refreshPeriod) in
|
||||
|
||||
private static var displayLinkObserver = DisplayLinkObserver { displayLinkObserver, refreshPeriod in
|
||||
DispatchQueue.main.async {
|
||||
let speed = 1.0
|
||||
|
||||
elapsedTimeSinceLastRefresh += speed * refreshPeriod
|
||||
if (animatedProgress - progress).magnitude <= 0.01 {
|
||||
animatedProgress = progress
|
||||
|
||||
if (displayedProgress - progress).magnitude <= 0.01 {
|
||||
displayedProgress = progress
|
||||
elapsedTimeSinceLastRefresh = 0
|
||||
displayLinkObserver.stop()
|
||||
} else {
|
||||
animatedProgress = Easing.lerp(animatedProgress, progress, Easing.easeInOut(elapsedTimeSinceLastRefresh));
|
||||
displayedProgress = Easing.linearInterpolation(
|
||||
start: displayedProgress,
|
||||
end: progress,
|
||||
progress: Easing.easeInOut(progress: elapsedTimeSinceLastRefresh)
|
||||
)
|
||||
}
|
||||
|
||||
updateDockIcon()
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +37,23 @@ public enum DockProgress {
|
|||
NSApp.dockTile.contentView = $0
|
||||
}
|
||||
|
||||
/**
|
||||
Assign a [`Progress`](https://developer.apple.com/documentation/foundation/progress) instance to track the progress status.
|
||||
|
||||
When set to `nil`, the progress will be reset.
|
||||
|
||||
The given `Progress` instance is weakly stored. It's up to you to retain it.
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import DockProgress
|
||||
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
progress?.becomeCurrent(withPendingUnitCount: 1)
|
||||
|
||||
DockProgress.progressInstance = progress
|
||||
```
|
||||
*/
|
||||
public static weak var progressInstance: Progress? {
|
||||
didSet {
|
||||
guard let progressInstance else {
|
||||
|
@ -63,6 +92,17 @@ public enum DockProgress {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates the current progress from 0.0 to 1.0. Setting this value will start the animation towards the set value.
|
||||
|
||||
```swift
|
||||
import DockProgress
|
||||
|
||||
foo.onUpdate = { progress in
|
||||
DockProgress.progress = progress
|
||||
}
|
||||
```
|
||||
*/
|
||||
public static var progress: Double = 0 {
|
||||
didSet {
|
||||
if progress > 0 {
|
||||
|
@ -74,46 +114,120 @@ public enum DockProgress {
|
|||
}
|
||||
|
||||
/**
|
||||
The currently displayed progress (readonly). Animates towards `progress`
|
||||
The currently displayed progress. Animates towards ``progress``.
|
||||
*/
|
||||
public private(set) static var animatedProgress = 0.0
|
||||
public private(set) static var displayedProgress = 0.0
|
||||
|
||||
/**
|
||||
Reset the `progress` without animating.
|
||||
Reset the progress without animating.
|
||||
*/
|
||||
public static func resetProgress() {
|
||||
displayLinkObserver.stop()
|
||||
progress = 0
|
||||
animatedProgress = 0
|
||||
elapsedTimeSinceLastRefresh = 0;
|
||||
displayedProgress = 0
|
||||
elapsedTimeSinceLastRefresh = 0
|
||||
updateDockIcon()
|
||||
}
|
||||
|
||||
/**
|
||||
The available progress styles.
|
||||
|
||||
- `.bar` 
|
||||
- `.squircle` 
|
||||
- `.circle` 
|
||||
- `.badge` 
|
||||
- `.pie` 
|
||||
*/
|
||||
public enum Style {
|
||||
/**
|
||||
Progress bar style.
|
||||
|
||||

|
||||
*/
|
||||
case bar
|
||||
|
||||
/**
|
||||
Progress line animating around the edges of the app icon.
|
||||
|
||||
- Parameters:
|
||||
- inset: Inset value to adjust the squircle shape. By default, it should fit a normal macOS icon.
|
||||
- color: The color of the progress.
|
||||
|
||||

|
||||
*/
|
||||
case squircle(inset: Double? = nil, color: NSColor = .controlAccentColor)
|
||||
|
||||
/**
|
||||
Circle style.
|
||||
|
||||
- Parameters:
|
||||
- radius: The radius of the circle.
|
||||
- color: The color of the progress.
|
||||
|
||||

|
||||
*/
|
||||
case circle(radius: Double, color: NSColor = .controlAccentColor)
|
||||
|
||||
/**
|
||||
Badge style.
|
||||
|
||||
- Parameters:
|
||||
- color: The color of the badge.
|
||||
- badgeValue: A closure that returns the badge value as an integer.
|
||||
|
||||
- Note: It is not meant to be used as a numeric percentage. It's for things like count of downloads, number of files being converted, etc.
|
||||
|
||||
Large badge value numbers will be written in kilo short notation, for example, `1012` → `1k`.
|
||||
|
||||

|
||||
*/
|
||||
case badge(color: NSColor = .controlAccentColor, badgeValue: () -> Int)
|
||||
|
||||
/**
|
||||
Pie style.
|
||||
|
||||
- Parameters:
|
||||
- color: The color of the pie.
|
||||
|
||||

|
||||
*/
|
||||
case pie(color: NSColor = .controlAccentColor)
|
||||
|
||||
|
||||
/**
|
||||
Custom style.
|
||||
|
||||
- Parameters:
|
||||
- drawHandler: A closure that is responsible for drawing the custom progress.
|
||||
*/
|
||||
case custom(drawHandler: (_ rect: CGRect) -> Void)
|
||||
}
|
||||
|
||||
/**
|
||||
The style to be used for displaying progress.
|
||||
|
||||
The default style is `.bar`.
|
||||
|
||||
Check out the example app in the Xcode project for a demo of the styles.
|
||||
*/
|
||||
public static var style = Style.bar
|
||||
|
||||
// TODO: Make the progress smoother by also animating the steps between each call to `updateDockIcon()`
|
||||
private static func updateDockIcon() {
|
||||
dockContentView.needsDisplay = true;
|
||||
dockContentView.needsDisplay = true
|
||||
NSApp.dockTile.display()
|
||||
}
|
||||
|
||||
private class ContentView: NSView {
|
||||
private final class ContentView: NSView {
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
NSGraphicsContext.current?.imageInterpolation = .high
|
||||
|
||||
NSApp.applicationIconImage?.draw(in: dirtyRect)
|
||||
|
||||
// TODO: If the `progress` is 1, draw the full circle, then schedule another draw in n milliseconds to hide it
|
||||
if (animatedProgress <= 0 || animatedProgress >= 1) {
|
||||
guard
|
||||
displayedProgress > 0,
|
||||
displayedProgress < 1
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -148,7 +262,7 @@ public enum DockProgress {
|
|||
roundedRect(barInnerBg)
|
||||
|
||||
var barProgress = bar.insetBy(dx: 1, dy: 1)
|
||||
barProgress.size.width = barProgress.width * animatedProgress
|
||||
barProgress.size.width = barProgress.width * displayedProgress
|
||||
NSColor.white.set()
|
||||
roundedRect(barProgress)
|
||||
}
|
||||
|
@ -169,7 +283,7 @@ public enum DockProgress {
|
|||
let progressSquircle = ProgressSquircleShapeLayer(rect: rect)
|
||||
progressSquircle.strokeColor = color.cgColor
|
||||
progressSquircle.lineWidth = 5
|
||||
progressSquircle.progress = animatedProgress
|
||||
progressSquircle.progress = displayedProgress
|
||||
progressSquircle.render(in: cgContext)
|
||||
}
|
||||
|
||||
|
@ -181,7 +295,7 @@ public enum DockProgress {
|
|||
let progressCircle = ProgressCircleShapeLayer(radius: radius, center: dstRect.center)
|
||||
progressCircle.strokeColor = color.cgColor
|
||||
progressCircle.lineWidth = 4
|
||||
progressCircle.progress = animatedProgress
|
||||
progressCircle.progress = displayedProgress
|
||||
progressCircle.render(in: cgContext)
|
||||
}
|
||||
|
||||
|
@ -209,7 +323,7 @@ public enum DockProgress {
|
|||
progressCircle.strokeColor = color.cgColor
|
||||
progressCircle.lineWidth = lineWidth
|
||||
progressCircle.lineCap = .butt
|
||||
progressCircle.progress = animatedProgress
|
||||
progressCircle.progress = displayedProgress
|
||||
|
||||
// Label
|
||||
if !isPie {
|
||||
|
@ -246,11 +360,13 @@ public enum DockProgress {
|
|||
|
||||
if absNumber < 1000 {
|
||||
return "\(number)"
|
||||
} else if absNumber < 10_000 {
|
||||
return "\(sign * Int(absNumber / 1000))k"
|
||||
} else {
|
||||
return "\(sign * 9)k+"
|
||||
}
|
||||
|
||||
if absNumber < 10_000 {
|
||||
return "\(sign * Int(absNumber / 1000))k"
|
||||
}
|
||||
|
||||
return "\(sign * 9)k+"
|
||||
}
|
||||
|
||||
private static func scaledBadgeFontSize(text: String) -> Double {
|
||||
|
|
|
@ -271,56 +271,112 @@ final class VerticallyCenteredTextLayer: CATextLayer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Provides functions for linear interpolation and easing effects.
|
||||
|
||||
These functions are useful for animations and transitions, or anywhere you want to smoothly transition between two values.
|
||||
*/
|
||||
enum Easing {
|
||||
static func lerp(_ start: Double, _ end: Double, _ t: Double) -> Double {
|
||||
return Double(simd_mix(Float(start), Float(end), Float(t)))
|
||||
/**
|
||||
Linearly interpolates between two values.
|
||||
|
||||
Also known as `lerp`.
|
||||
|
||||
- Parameters:
|
||||
- start: The start value.
|
||||
- end: The end value.
|
||||
- progress: The interpolation progress as a decimal between 0.0 and 1.0.
|
||||
|
||||
- Returns: The interpolated value.
|
||||
*/
|
||||
static func linearInterpolation(start: Double, end: Double, progress: Double) -> Double {
|
||||
assert(0...1 ~= progress, "Progress must be between 0.0 and 1.0")
|
||||
return Double(simd_mix(Float(start), Float(end), Float(progress)))
|
||||
}
|
||||
|
||||
static private func easeIn(_ t: Double) -> Double {
|
||||
return Double(simd_smoothstep(0.0, 1.0, Float(t)))
|
||||
/**
|
||||
Provides an ease-in effect.
|
||||
|
||||
- Parameter progress: The progress as a decimal between 0.0 and 1.0.
|
||||
|
||||
- Returns: The eased value.
|
||||
*/
|
||||
static private func easeIn(progress: Double) -> Double {
|
||||
assert(0...1 ~= progress, "Progress must be between 0.0 and 1.0")
|
||||
return Double(simd_smoothstep(0.0, 1.0, Float(progress)))
|
||||
}
|
||||
|
||||
static private func easeOut(_ t: Double) -> Double {
|
||||
return 1 - easeIn(1 - t)
|
||||
/**
|
||||
Provides an ease-out effect.
|
||||
|
||||
- Parameter progress: The progress as a decimal between 0.0 and 1.0.
|
||||
|
||||
- Returns: The eased value.
|
||||
*/
|
||||
static private func easeOut(progress: Double) -> Double {
|
||||
assert(0...1 ~= progress, "Progress must be between 0.0 and 1.0")
|
||||
return 1 - easeIn(progress: 1 - progress)
|
||||
}
|
||||
|
||||
static func easeInOut(_ t: Double) -> Double {
|
||||
return lerp(easeIn(t), easeOut(t), t)
|
||||
/**
|
||||
Provides an ease-in-out effect.
|
||||
|
||||
- Parameter progress: The progress as a decimal between 0.0 and 1.0.
|
||||
|
||||
- Returns: The eased value.
|
||||
*/
|
||||
static func easeInOut(progress: Double) -> Double {
|
||||
assert(0...1 ~= progress, "Progress must be between 0.0 and 1.0")
|
||||
|
||||
return linearInterpolation(
|
||||
start: easeIn(progress: progress),
|
||||
end: easeOut(progress: progress),
|
||||
progress: progress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
typealias DisplayLinkObserverCallback = (DisplayLinkObserver, Double) -> Void;
|
||||
|
||||
class DisplayLinkObserver {
|
||||
/**
|
||||
An observer that invokes a callback for each screen refresh.
|
||||
|
||||
This is useful for creating smooth animations that synchronize with the screen's refresh rate.
|
||||
*/
|
||||
final class DisplayLinkObserver {
|
||||
private var displayLink: CVDisplayLink?
|
||||
var callback: DisplayLinkObserverCallback
|
||||
fileprivate let callback: (DisplayLinkObserver, Double) -> Void
|
||||
|
||||
init(_ callback: @escaping DisplayLinkObserverCallback) {
|
||||
init(_ callback: @escaping (DisplayLinkObserver, Double) -> Void) {
|
||||
self.callback = callback
|
||||
let result = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
|
||||
assert(result == kCVReturnSuccess, "Failed to create CVDisplayLink")
|
||||
assert(CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) == kCVReturnSuccess, "Failed to create CVDisplayLink")
|
||||
}
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
|
||||
func start() {
|
||||
if let displayLink {
|
||||
let result = CVDisplayLinkSetOutputCallback(
|
||||
displayLink,
|
||||
displayLinkOutputCallback,
|
||||
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
||||
)
|
||||
assert(result == kCVReturnSuccess, "Failed to set CVDisplayLink output callback")
|
||||
if (CVDisplayLinkStart(displayLink) != kCVReturnSuccess) {
|
||||
print("Warning: CVDisplayLink already running")
|
||||
}
|
||||
guard let displayLink else {
|
||||
return
|
||||
}
|
||||
|
||||
let result = CVDisplayLinkSetOutputCallback(
|
||||
displayLink,
|
||||
displayLinkOutputCallback,
|
||||
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
||||
)
|
||||
assert(result == kCVReturnSuccess, "Failed to set CVDisplayLink output callback")
|
||||
|
||||
CVDisplayLinkStart(displayLink)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
if let displayLink {
|
||||
if (CVDisplayLinkStop(displayLink) != kCVReturnSuccess) {
|
||||
print("Warning: CVDisplayLink already stopped")
|
||||
}
|
||||
guard let displayLink else {
|
||||
return
|
||||
}
|
||||
|
||||
CVDisplayLinkStop(displayLink)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,11 +389,14 @@ private func displayLinkOutputCallback(
|
|||
displayLinkContext: UnsafeMutableRawPointer?
|
||||
) -> CVReturn {
|
||||
let observer = unsafeBitCast(displayLinkContext, to: DisplayLinkObserver.self)
|
||||
|
||||
var refreshPeriod = CVDisplayLinkGetActualOutputVideoRefreshPeriod(displayLink)
|
||||
if (refreshPeriod == 0) {
|
||||
print("Warning: CVDisplayLinkGetActualOutputVideoRefreshPeriod failed. Assuming 60 Hz...")
|
||||
refreshPeriod = 1.0 / 60.0
|
||||
}
|
||||
|
||||
observer.callback(observer, refreshPeriod)
|
||||
|
||||
return kCVReturnSuccess
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<img src="screenshot.gif" width="485">
|
||||
|
||||
This package is used in production by the [Gifski app](https://github.com/sindresorhus/Gifski). You might also like some of my [other apps](https://sindresorhus.com/apps).
|
||||
This package is used in production by the [Gifski app](https://github.com/sindresorhus/Gifski). You may also like some of my [other apps](https://sindresorhus.com/apps).
|
||||
|
||||
## Requirements
|
||||
|
||||
|
@ -84,8 +84,6 @@ import DockProgress
|
|||
DockProgress.style = .circle(radius: 55, color: .systemBlue)
|
||||
```
|
||||
|
||||
Make sure to set a `radius` that matches your app icon.
|
||||
|
||||
### Badge
|
||||
|
||||

|
||||
|
@ -113,8 +111,6 @@ DockProgress.style = .pie(color: .systemBlue)
|
|||
## Related
|
||||
|
||||
- [Defaults](https://github.com/sindresorhus/Defaults) - Swifty and modern UserDefaults
|
||||
- [Preferences](https://github.com/sindresorhus/Preferences) - Add a preferences window to your macOS app in minutes
|
||||
- [KeyboardShortcuts](https://github.com/sindresorhus/KeyboardShortcuts) - Add user-customizable global keyboard shortcuts to your macOS app
|
||||
- [LaunchAtLogin](https://github.com/sindresorhus/LaunchAtLogin) - Add "Launch at Login" functionality to your macOS app
|
||||
- [Regex](https://github.com/sindresorhus/Regex) - Swifty regular expressions
|
||||
- [More…](https://github.com/search?q=user%3Asindresorhus+language%3Aswift)
|
||||
|
|
Loading…
Reference in New Issue