Skip to content

Commit

Permalink
Merge pull request #5 from auramagi/feature/documentation
Browse files Browse the repository at this point in the history
Update README
  • Loading branch information
auramagi authored Jun 3, 2022
2 parents 44835cf + eebe3d7 commit 42ebab3
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 35 deletions.
Binary file added Assets/Header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets/NewHueSample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets/TemplateEdit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets/TrashSample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 67 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,79 @@
<p align=center><img width=750 src=./Assets/Header.png></p>

# NewIcon

A command line tool to overlay text over file and directory icons on macOS.
NewIcon is a command line tool to modify and manage icons for applicatons, files, and directories on macOS.

### Features

### Installation
- A quick way to overlay some text over an icon
- Reset to the original icon
- Use any SwiftUI View as a template

## Installation

Install using [Mint](https://github.com/yonaskolb/Mint).
```sh
$ mint install auramagi/NewIcon
```

### Usage
## Usage

### Overlay text over the original icon
```sh
$ new-icon text FILE TEXT

# Add version number to Xcode app icon
$ new-icon text /Applications/Xcode.app 13.3.1
```
- Tip: when changing Xcode icon, don't forget about the Simulator icon! It's located inside the Xcode bundle: `/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app`

### Revert an icon to the original
```sh
$ new-icon reset FILE
```

## Usage with SwiftUI template files

### Use a SwiftUI template to modify an icon
```sh
$ new-icon template icon TEMPLATE FILE CONTENT
```

See [SampleTemplates folder](./SampleTemplates) for some inspiration.

| File | Command | Output |
|:--|:--|---|
| [NewHue.swift](./SampleTemplates/NewHue.swift) | `new-icon template icon NewHue.swift /Applications/Xcode.app mint` | <img width=128 src=./Assets/NewHueSample.png> |
| [Trash.swift](./SampleTemplates/Trash.swift) | `new-icon template icon Trash.swift /Applications/Xcode.app` | <img width=128 src=./Assets/TrashSample.png> |

### Create a sample template file

- Overlay text over the original icon for a file or directory
```sh
$ new-icon text FILE TEXT

# Add version number to Xcode app icon
$ new-icon text /Applications/Xcode.app 13.3.1
```
If you need a place to start with writing your own SwiftUI template files, run this command to get a sample template.

```sh
$ new-icon template init
```

### Open a template file in Xcode

NewIcon allows you to edit a template file with Xcode by creating and opening a temporary SwiftPM package. Edit `Template/Sources/Template/Template.swift` inside, and any changes will be synced to the original file. After you are done editing close Xcode and press return to delete this temporary package.

```sh
$ new-icon template edit TEMPLATE
```

![Editing the sample template](./Assets/TemplateEdit.png)


### Clear build cache for templates

Template files need to be built before they can be used. At the very least, this takes several seconds, so NewIcon caches build product inside `~/.new-icon/cache` to speed up any subsequent use of the same template. There is a small command to clear this cache.

```sh
$ new-icon template cache clear
```

- Revert file or directory icon to the original
```sh
$ new-icon reset FILE
```
## Detailed command info

Run `new-icon --help` to see all options.
Run `new-icon help` to see all options.
65 changes: 65 additions & 0 deletions SampleTemplates/NewHue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import SwiftUI
import TemplateSupport

/// Apply of the several supported colors as the new hue for the image
struct NewHue: IconTemplate, PreviewProvider {
let icon: NSImage

// Content can be any Decodable type
let content: HueColor

enum HueColor: String, Decodable {
case red, orange, yellow, green, mint,
teal, cyan, blue, indigo, purple,
pink, black
}

// This function is needed to provide previews while using
// a custom `Decodable` type for `content`
static func makePreview(image: NSImage) -> NewHue {
.init(icon: image, content: .mint) // minty green previews 🌱✨
}

// Expect size to be 1024x1024
var body: some View {
Image(nsImage: icon)
.resizable()
.scaledToFit()
.hue(content.color)
}
}

extension NewHue.HueColor {
var color: Color {
switch self {
case .red: return .red
case .orange: return .orange
case .yellow: return .yellow
case .green: return .green
case .mint: return .mint
case .teal: return .teal
case .cyan: return .cyan
case .blue: return .blue
case .indigo: return .indigo
case .purple: return .purple
case .pink: return .pink
case .black: return .black
}
}
}

extension View {
/// Hue change modifier that works around blendMode weirdness when used together with drawingGroup
@ViewBuilder public func hue(_ color: Color) -> some View {
Color(white: 0)
.overlay(self)
.overlay(
color
.blendMode(.hue)
)
.drawingGroup()
.mask(self)
}
}


27 changes: 27 additions & 0 deletions SampleTemplates/Trash.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import SwiftUI
import TemplateSupport

/// Make grayscale and overlay a red trash icon.
struct Trash: IconTemplate, PreviewProvider {
let icon: NSImage

// Mark this template as not needing any additional content
typealias Content = Never

// Expect size to be 1024x1024
var body: some View {
Image(nsImage: icon)
.resizable()
.scaledToFit()
.grayscale(1)
.overlay(alignment: .center) {
Image(systemName: "trash")
.resizable()
.scaledToFit()
.symbolVariant(.circle.fill)
.symbolRenderingMode(.hierarchical)
.foregroundColor(.red)
.frame(width: 384, height: 384)
}
}
}
11 changes: 10 additions & 1 deletion Sources/NewIcon/PluginTemplate/Template-Template-swift.template
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import SwiftUI
import TemplateSupport

struct ImageTemplateView: IconTemplate, PreviewProvider {
/// Overlay text with a fixed-width semi-translucent background.
struct TextOverlay: IconTemplate, PreviewProvider {
/// An image (icon) provided to the template.
let icon: NSImage

/// Additional content. Content can be one of the following:
/// - `String` or `String?`
/// - Any `Decodable` type
/// - In which case you need to provide sample content for previews by defining `static func makePreview(icon: NSImage) -> Self`
/// - The type should be decodable with a `JSONDecoder`. You can customize the decoder by providing custom `static var decoder: JSONDecoder`
/// - `Never` if the template doesn't take any additional content
/// - Configure by adding `typealias Content = Never`
let content: String

// Expect size to be 1024x1024
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ public protocol _IconTemplate {
public protocol IconTemplate: View, _IconTemplate {
associatedtype Content

/// Used when `Content` is typealised to `Never`.
init(icon: NSImage)

/// Used when `Content` is `Decodable`.
init(icon: NSImage, content: Content)

static func makePreview(icon: NSImage) -> Self
/// Decoder used for `Decodable` content.
static var decoder: JSONDecoder { get }

/// Create a sample template to use in previews.
static func makePreview(image: NSImage) -> Self
}

public extension IconTemplate where Content: Decodable {
static var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.allowsJSON5 = true
return decoder
}

init(icon: NSImage) {
fatalError("Template expects non-nil content.")
}
Expand Down Expand Up @@ -84,12 +84,18 @@ public extension IconTemplate where Content == Never {
}

extension IconTemplate {
public static var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.allowsJSON5 = true
return decoder
}

public var asAnyView: AnyView {
AnyView(self)
}

static func makePreview(icon: PreviewData.Icon) -> Self {
makePreview(icon: icon.image)
makePreview(image: icon.image)
}
}

Expand All @@ -111,20 +117,20 @@ internal extension IconTemplate {
}

public extension IconTemplate where Content == String {
static func makePreview(icon: NSImage) -> Self {
.init(icon: icon, content: PreviewData.text)
static func makePreview(image: NSImage) -> Self {
.init(icon: image, content: PreviewData.text)
}
}

public extension IconTemplate where Content == String? {
static func makePreview(icon: NSImage) -> Self {
.init(icon: icon, content: PreviewData.text)
static func makePreview(image: NSImage) -> Self {
.init(icon: image, content: PreviewData.text)
}
}

public extension IconTemplate where Content == Never {
static func makePreview(icon: NSImage) -> Self {
.init(icon: icon)
static func makePreview(image: NSImage) -> Self {
.init(icon: image)
}
}

Expand Down
11 changes: 6 additions & 5 deletions Sources/NewIcon/Subcommands/TextCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,25 @@ struct TextCommand: AsyncParsableCommand {
}

if output != nil {
print("Icon was successfully changed.")
} else {
print("Image was successfully saved.")
} else {
print("Icon was successfully changed.")
}
}

private func buildTemplate() async throws -> Template<Input> {
Self.builder.build {
ImageTemplateView(
TextOverlay(
icon: $0.0,
content: text
)
}
}
}

/// Text Template. Also the sample custom template, provided via PluginTemplate/Template-Template-swift.template
private struct ImageTemplateView: View {
/// Overlay text with a fixed-width semi-translucent background.
/// This is also the sample template we provide with `template init` via PluginTemplate/Template-Template-swift.template
private struct TextOverlay: View {
let icon: NSImage

let content: String
Expand Down

0 comments on commit 42ebab3

Please sign in to comment.