Skip to content

Commit 60d3b6c

Browse files
committed
feat(tahoe): add tint color variants
1 parent b5e2180 commit 60d3b6c

187 files changed

Lines changed: 235 additions & 33 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ By default, `folderify` uses your system's current light/dark mode. Use `--color
4141
folderify --color-scheme dark mask.png
4242
```
4343

44+
On macOS Tahoe, the default folder color is `multicolor`. Use `--folder-color` to switch to one of the tinted Tahoe folder variants:
45+
46+
```shell
47+
folderify --macOS 26 --folder-color blue mask.png
48+
```
49+
4450
Note:
4551

4652
- There is currently no simple way to set an icon that will automatically switch between light and dark when you switch the entire OS. You can only assign one version of an icon to a folder.
@@ -168,6 +174,13 @@ Options:
168174
[default: auto]
169175
[possible values: auto, light, dark]
170176
177+
--folder-color <FOLDER_COLOR>
178+
Tahoe folder color. `multicolor` keeps the default macOS folder look,
179+
while the tinted variants use Tahoe's tinted folder rendering
180+
181+
[default: multicolor]
182+
[possible values: multicolor, blue, graphite, green, orange, pink, purple, red, yellow]
183+
171184
--no-trim
172185
Don't trim margins from the mask.
173186
By default (i.e. without this flag), transparent margins are trimmed from all 4 sides.

src/args.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ struct FolderifyArgs {
5858
#[clap(long, value_enum, default_value_t = ColorSchemeOrAuto::Auto)]
5959
color_scheme: ColorSchemeOrAuto,
6060

61+
/// Tahoe folder color. `multicolor` keeps the default macOS folder look,
62+
/// while the tinted variants use Tahoe's tinted folder rendering.
63+
#[clap(long, value_enum, default_value_t = FolderColor::Multicolor)]
64+
folder_color: FolderColor,
65+
6166
/// Don't trim margins from the mask.
6267
/// By default (i.e. without this flag), transparent margins are trimmed from all 4 sides.
6368
#[clap(long, verbatim_doc_comment)]
@@ -94,6 +99,19 @@ pub enum ColorScheme {
9499
Dark,
95100
}
96101

102+
#[derive(ValueEnum, Clone, Debug, PartialEq, Copy)]
103+
pub enum FolderColor {
104+
Multicolor,
105+
Blue,
106+
Graphite,
107+
Green,
108+
Orange,
109+
Pink,
110+
Purple,
111+
Red,
112+
Yellow,
113+
}
114+
97115
#[derive(ValueEnum, Clone, Debug, PartialEq, Copy)]
98116
pub enum Badge {
99117
Alias,
@@ -113,6 +131,28 @@ impl Display for ColorScheme {
113131
}
114132
}
115133

134+
impl FolderColor {
135+
pub fn as_str(&self) -> &'static str {
136+
match self {
137+
Self::Multicolor => "multicolor",
138+
Self::Blue => "blue",
139+
Self::Graphite => "graphite",
140+
Self::Green => "green",
141+
Self::Orange => "orange",
142+
Self::Pink => "pink",
143+
Self::Purple => "purple",
144+
Self::Red => "red",
145+
Self::Yellow => "yellow",
146+
}
147+
}
148+
}
149+
150+
impl Display for FolderColor {
151+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152+
write!(f, "{}", self.as_str())
153+
}
154+
}
155+
116156
#[derive(ValueEnum, Clone, Debug, PartialEq)]
117157
enum ColorSchemeOrAuto {
118158
Auto,
@@ -141,6 +181,7 @@ enum SetIconUsingOrAuto {
141181
pub struct Options {
142182
pub mask_path: PathBuf,
143183
pub color_scheme: ColorScheme,
184+
pub folder_color: FolderColor,
144185
pub no_trim: bool,
145186
pub target: Option<PathBuf>,
146187
pub folder_style: FolderStyle,
@@ -259,9 +300,18 @@ pub fn get_options() -> Options {
259300
Some(SetIconUsingOrAuto::Fileicon) => SetIconUsing::Fileicon,
260301
_ => SetIconUsing::Osascript,
261302
};
303+
if folder_style != FolderStyle::Tahoe
304+
&& args.folder_color != FolderColor::Multicolor
305+
{
306+
eprintln!(
307+
"Folder tint variants are only available for Tahoe. \
308+
Ignoring `--folder-color`."
309+
);
310+
}
262311
Options {
263312
mask_path: mask,
264313
color_scheme: map_color_scheme_auto(args.color_scheme, folder_style),
314+
folder_color: normalized_folder_color(folder_style, args.folder_color),
265315
no_trim: args.no_trim,
266316
target: args.target,
267317
folder_style,
@@ -277,6 +327,17 @@ pub fn get_options() -> Options {
277327
}
278328
}
279329

330+
fn normalized_folder_color(
331+
folder_style: FolderStyle,
332+
folder_color: FolderColor,
333+
) -> FolderColor {
334+
if folder_style == FolderStyle::Tahoe {
335+
folder_color
336+
} else {
337+
FolderColor::Multicolor
338+
}
339+
}
340+
280341
fn map_color_scheme_auto(
281342
color_scheme: ColorSchemeOrAuto,
282343
folder_style: FolderStyle,

src/icon_conversion.rs

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl ProgressBarType {
3535
}
3636

3737
use crate::{
38-
args::{Badge, ColorScheme, FolderStyle, Options, SetIconUsing},
38+
args::{Badge, ColorScheme, FolderColor, FolderStyle, Options, SetIconUsing},
3939
command::{
4040
run_command, run_magick, DEREZ_COMMAND, FILEICON_COMMAND, ICONUTIL_COMMAND,
4141
OSASCRIPT_COMMAND, REZ_COMMAND, SETFILE_COMMAND, SIPS_COMMAND,
@@ -72,6 +72,24 @@ struct MaskProfile {
7272
engraving: EngravingInputs,
7373
}
7474

75+
struct TintedPalette {
76+
fill: RGBColor,
77+
top_bezel: RGBColor,
78+
bottom_bezel: RGBColor,
79+
}
80+
81+
fn tinted_palette(
82+
fill: [u8; 3],
83+
top_bezel: [u8; 3],
84+
bottom_bezel: [u8; 3],
85+
) -> TintedPalette {
86+
TintedPalette {
87+
fill: RGBColor::from_components(fill),
88+
top_bezel: RGBColor::from_components(top_bezel),
89+
bottom_bezel: RGBColor::from_components(bottom_bezel),
90+
}
91+
}
92+
7593
#[derive(Debug)]
7694
pub struct WorkingDir {
7795
working_dir: Temp,
@@ -244,40 +262,101 @@ impl IconResolution {
244262
}
245263
}
246264

247-
fn tahoe_mask_profile(resolution: &IconResolution) -> MaskProfile {
265+
fn tahoe_multicolor_mask_profile(resolution: &IconResolution) -> MaskProfile {
248266
let size = resolution.size();
249267
MaskProfile {
250268
mask_dimensions: Dimensions {
251269
width: size * 3 / 4,
252270
height: size / 2,
253271
},
254-
offset_y: resolution.offset_y() - (size as i32 / 160),
272+
offset_y: resolution.offset_y(),
255273
engraving: EngravingInputs {
256-
fill_color: RGBColor::new(52, 104, 148),
257-
fill_opacity: 0.84,
274+
fill_color: RGBColor::new(74, 141, 172),
275+
fill_opacity: 0.5,
258276
top_bezel: BezelInputs {
259-
color: RGBColor::new(48, 96, 136),
277+
color: RGBColor::new(58, 152, 208),
260278
blur: BlurDown {
261279
spread_px: 0,
262-
page_y: 1,
280+
page_y: 2,
263281
},
264-
mask_operation: CompositingOperation::Dst_Out,
265-
opacity: 0.18,
282+
mask_operation: CompositingOperation::Dst_In,
283+
opacity: 0.5,
266284
},
267285
bottom_bezel: BezelInputs {
268-
color: RGBColor::new(102, 138, 170),
286+
color: RGBColor::new(174, 225, 253),
269287
blur: resolution.bottom_bezel_blur_down(),
270-
mask_operation: CompositingOperation::Dst_In,
271-
opacity: 0.2,
288+
mask_operation: CompositingOperation::Dst_Out,
289+
opacity: resolution.bottom_bezel_alpha(),
272290
},
273291
},
274292
}
275293
}
276294

277-
fn big_sur_mask_profile(
295+
fn tahoe_tinted_palette(folder_color: FolderColor) -> TintedPalette {
296+
match folder_color {
297+
FolderColor::Multicolor => unreachable!(),
298+
FolderColor::Blue => {
299+
tinted_palette([14, 117, 243], [10, 92, 201], [50, 131, 255])
300+
}
301+
FolderColor::Graphite => {
302+
tinted_palette([129, 128, 133], [104, 103, 108], [142, 141, 147])
303+
}
304+
FolderColor::Green => {
305+
tinted_palette([44, 195, 68], [27, 156, 49], [68, 207, 86])
306+
}
307+
FolderColor::Orange => {
308+
tinted_palette([241, 121, 36], [205, 92, 16], [253, 135, 62])
309+
}
310+
FolderColor::Pink => {
311+
tinted_palette([240, 25, 73], [205, 12, 54], [252, 55, 92])
312+
}
313+
FolderColor::Purple => {
314+
tinted_palette([198, 0, 229], [154, 0, 181], [210, 44, 240])
315+
}
316+
FolderColor::Red => {
317+
tinted_palette([240, 38, 51], [207, 18, 30], [252, 63, 73])
318+
}
319+
FolderColor::Yellow => {
320+
tinted_palette([243, 198, 9], [208, 163, 0], [254, 210, 49])
321+
}
322+
}
323+
}
324+
325+
fn tahoe_tinted_mask_profile(
278326
resolution: &IconResolution,
279-
color_scheme: ColorScheme,
327+
folder_color: FolderColor,
280328
) -> MaskProfile {
329+
let size = resolution.size();
330+
let palette = tahoe_tinted_palette(folder_color);
331+
MaskProfile {
332+
mask_dimensions: Dimensions {
333+
width: size * 3 / 4,
334+
height: size / 2,
335+
},
336+
offset_y: resolution.offset_y() - (size as i32 / 160),
337+
engraving: EngravingInputs {
338+
fill_color: palette.fill,
339+
fill_opacity: 0.5,
340+
top_bezel: BezelInputs {
341+
color: palette.top_bezel,
342+
blur: BlurDown {
343+
spread_px: 0,
344+
page_y: 2,
345+
},
346+
mask_operation: CompositingOperation::Dst_In,
347+
opacity: 0.5,
348+
},
349+
bottom_bezel: BezelInputs {
350+
color: palette.bottom_bezel,
351+
blur: resolution.bottom_bezel_blur_down(),
352+
mask_operation: CompositingOperation::Dst_Out,
353+
opacity: resolution.bottom_bezel_alpha(),
354+
},
355+
},
356+
}
357+
}
358+
359+
fn big_sur_mask_profile(resolution: &IconResolution, color_scheme: ColorScheme) -> MaskProfile {
281360
MaskProfile {
282361
mask_dimensions: Dimensions {
283362
width: resolution.size() * 3 / 4,
@@ -433,9 +512,10 @@ impl IconConversion {
433512
)?;
434513

435514
self.step("Setting fill opacity");
436-
let fill = self.simple_operation(&fill_colorized, "2.2_FILL", |args: &mut CommandArgs| {
437-
args.opacity(inputs.fill_opacity);
438-
})?;
515+
let fill =
516+
self.simple_operation(&fill_colorized, "2.2_FILL", |args: &mut CommandArgs| {
517+
args.opacity(inputs.fill_opacity);
518+
})?;
439519

440520
self.step("Complementing mask for top bezel");
441521
let top_bezel_complement = self.simple_operation(
@@ -562,9 +642,14 @@ impl IconConversion {
562642
// println!("[Starting] {}", inputs.resolution);
563643
// }
564644

565-
let mask_profile = match icon_inputs.folder_style {
566-
FolderStyle::Tahoe => tahoe_mask_profile(&icon_inputs.resolution),
567-
FolderStyle::BigSur => {
645+
let mask_profile = match (icon_inputs.folder_style, icon_inputs.folder_color) {
646+
(FolderStyle::Tahoe, FolderColor::Multicolor) => {
647+
tahoe_multicolor_mask_profile(&icon_inputs.resolution)
648+
}
649+
(FolderStyle::Tahoe, folder_color) => {
650+
tahoe_tinted_mask_profile(&icon_inputs.resolution, folder_color)
651+
}
652+
(FolderStyle::BigSur, _) => {
568653
big_sur_mask_profile(&icon_inputs.resolution, icon_inputs.color_scheme)
569654
}
570655
};

src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ fn main() {
3434
options.mask_path.display(),
3535
options.color_scheme
3636
);
37+
if options.folder_style == args::FolderStyle::Tahoe {
38+
println!(
39+
"[{}] Using folder color: {}",
40+
options.mask_path.display(),
41+
options.folder_color
42+
);
43+
}
3744

3845
let working_dir = WorkingDir::new();
3946
if options.debug {
@@ -83,6 +90,7 @@ fn main() {
8390
&IconInputs {
8491
folder_style: options.folder_style,
8592
color_scheme: options.color_scheme,
93+
folder_color: options.folder_color,
8694
resolution,
8795
empty_folder: options.empty_folder,
8896
},

src/primitives.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ impl RGBColor {
3232
pub fn new(r: u8, g: u8, b: u8) -> Self {
3333
Self { r, g, b }
3434
}
35+
36+
pub fn from_components(components: [u8; 3]) -> Self {
37+
Self::new(components[0], components[1], components[2])
38+
}
3539
}
3640

3741
impl Display for RGBColor {

0 commit comments

Comments
 (0)