Skip to content

Commit 6917d06

Browse files
committed
Add initial support for Tahoe.
1 parent 195ed28 commit 6917d06

25 files changed

+136
-39
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ Options:
159159
--macOS <MACOS_VERSION>
160160
Version of the macOS folder icon, e.g. "14.2.1". Defaults to the version currently running
161161
162+
--empty-folder
163+
Render the folder as empty
164+
162165
--color-scheme <COLOR_SCHEME>
163166
Color scheme — auto matches the current system value
164167

src/icon_conversion.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ use crate::{
4141
},
4242
error::{FolderifyError, GeneralError},
4343
magick::{density, BlurDown, CommandArgs, CompositingOperation},
44-
options::{Badge, ColorScheme, Options, SetIconUsing},
44+
options::{Badge, ColorScheme, FolderStyle, Options, SetIconUsing},
4545
primitives::{Dimensions, Extent, Offset, RGBColor},
46-
resources::{get_badge_icon, get_folder_icon},
46+
resources::{get_badge_icon, get_folder_icon, IconInputs},
4747
};
4848

4949
pub struct ScaledMaskInputs {
@@ -264,11 +264,6 @@ pub struct IconConversion {
264264
pub progress_bar: Option<ProgressBar>,
265265
}
266266

267-
pub struct IconInputs {
268-
pub color_scheme: ColorScheme,
269-
pub resolution: IconResolution,
270-
}
271-
272267
impl IconConversion {
273268
pub fn step_unincremented(&self, step_description: &str) {
274269
if let Some(progress_bar) = &self.progress_bar {
@@ -490,14 +485,14 @@ impl IconConversion {
490485
options: &Options,
491486
full_mask_path: &Path,
492487
output_path: &Path,
493-
inputs: &IconInputs,
488+
icon_inputs: &IconInputs,
494489
) -> Result<(), FolderifyError> {
495490
// if options.verbose {
496491
// println!("[Starting] {}", inputs.resolution);
497492
// }
498493

499-
let size = inputs.resolution.size();
500-
let offset_y = inputs.resolution.offset_y();
494+
let size = icon_inputs.resolution.size();
495+
let offset_y = icon_inputs.resolution.offset_y();
501496

502497
self.step_unincremented("Sizing mask");
503498
let sized_mask_path = self
@@ -515,11 +510,12 @@ impl IconConversion {
515510
.unwrap();
516511

517512
// TODO
518-
let template_icon = get_folder_icon(inputs.color_scheme, &inputs.resolution);
513+
let template_icon = get_folder_icon(icon_inputs);
519514

520-
let fill_color = match inputs.color_scheme {
521-
ColorScheme::Light => RGBColor::new(8, 134, 206),
522-
ColorScheme::Dark => RGBColor::new(6, 111, 194),
515+
let fill_color = match (icon_inputs.folder_style, icon_inputs.color_scheme) {
516+
(FolderStyle::Tahoe, _) => RGBColor::new(74, 141, 172),
517+
(_, ColorScheme::Light) => RGBColor::new(8, 134, 206),
518+
(_, ColorScheme::Dark) => RGBColor::new(6, 111, 194),
523519
};
524520

525521
let engraved = self.engrave(
@@ -539,20 +535,24 @@ impl IconConversion {
539535
},
540536
bottom_bezel: BezelInputs {
541537
color: RGBColor::new(174, 225, 253),
542-
blur: inputs.resolution.bottom_bezel_blur_down(),
538+
blur: icon_inputs.resolution.bottom_bezel_blur_down(),
543539
mask_operation: CompositingOperation::Dst_Out,
544-
opacity: inputs.resolution.bottom_bezel_alpha(),
540+
opacity: icon_inputs.resolution.bottom_bezel_alpha(),
545541
},
546542
},
547543
);
548544
if let Some(badge) = options.badge {
549-
self.badge_in_place(output_path, badge, &inputs.resolution)?;
545+
self.badge_in_place(output_path, badge, &icon_inputs.resolution)?;
550546
};
551547

552548
self.step("");
553549

554550
if options.verbose {
555-
println!("[{}] {}", options.mask_path.display(), inputs.resolution);
551+
println!(
552+
"[{}] {}",
553+
options.mask_path.display(),
554+
icon_inputs.resolution
555+
);
556556
}
557557
engraved
558558
}

src/main.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::thread::{self, JoinHandle};
22

33
use command::{run_command, OPEN_COMMAND};
4-
use icon_conversion::{IconInputs, IconResolution, WorkingDir};
4+
use icon_conversion::{IconResolution, WorkingDir};
55
use indicatif::MultiProgress;
66
use magick::CommandArgs;
77

8-
use crate::{output_paths::PotentialOutputPaths, primitives::Dimensions};
8+
use crate::{output_paths::PotentialOutputPaths, primitives::Dimensions, resources::IconInputs};
99

1010
mod command;
1111
mod error;
@@ -22,8 +22,9 @@ fn main() {
2222
let potential_output_paths = PotentialOutputPaths::new(&options);
2323

2424
println!(
25-
"[{}] Using folder style: BigSur",
26-
options.mask_path.display()
25+
"[{}] Using folder style: {}",
26+
options.mask_path.display(),
27+
options.folder_style
2728
);
2829
println!(
2930
"[{}] Using color scheme: {}",
@@ -77,8 +78,10 @@ fn main() {
7778
&full_mask_path,
7879
&output_path,
7980
&IconInputs {
81+
folder_style: options.folder_style,
8082
color_scheme: options.color_scheme,
8183
resolution,
84+
empty_folder: options.empty_folder,
8285
},
8386
)
8487
.unwrap();

src/options.rs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use clap_complete::generator::generate;
33
use clap_complete::{Generator, Shell};
44
use std::io::stdout;
55
use std::process::exit;
6+
use std::str::from_utf8;
67
use std::{env::var, fmt::Display, path::PathBuf, process::Command};
78

89
/// Generate a native-style macOS folder icon from a mask file.
@@ -47,6 +48,10 @@ struct FolderifyArgs {
4748
#[clap(long = "macOS", alias = "osx", short_alias = 'x', id = "MACOS_VERSION")]
4849
mac_os: Option<String>, // TODO: enum, default?
4950

51+
/// Render the folder as empty.
52+
#[clap(long, default_value_t = false)]
53+
empty_folder: bool,
54+
5055
/// Color scheme — auto matches the current system value.
5156
#[clap(long, value_enum, default_value_t = ColorSchemeOrAuto::Auto)]
5257
color_scheme: ColorSchemeOrAuto,
@@ -136,6 +141,8 @@ pub struct Options {
136141
pub color_scheme: ColorScheme,
137142
pub no_trim: bool,
138143
pub target: Option<PathBuf>,
144+
pub folder_style: FolderStyle,
145+
pub empty_folder: bool,
139146
pub output_icns: Option<PathBuf>,
140147
pub output_iconset: Option<PathBuf>,
141148
pub set_icon_using: SetIconUsing,
@@ -150,17 +157,41 @@ fn completions_for_shell(cmd: &mut clap::Command, generator: impl Generator) {
150157
generate(generator, cmd, "folderify", &mut stdout());
151158
}
152159

153-
fn known_mac_os_version(mac_os: &str) -> bool {
154-
for major_version_string in ["15", "14", "13", "12", "11"] {
160+
fn is_major_macos_version_one_of(mac_os: &str, versions: &[&str]) -> bool {
161+
for major_version_string in versions {
155162
if mac_os.starts_with(&format!("{}.", major_version_string))
156-
|| mac_os == major_version_string
163+
|| mac_os == *major_version_string
157164
{
158165
return true;
159166
}
160167
}
161168
false
162169
}
163170

171+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172+
pub enum FolderStyle {
173+
BigSur,
174+
Tahoe,
175+
}
176+
177+
impl Display for FolderStyle {
178+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179+
match self {
180+
FolderStyle::BigSur => write!(f, "Big Sur"),
181+
FolderStyle::Tahoe => write!(f, "Tahoe"),
182+
}
183+
}
184+
}
185+
186+
impl FolderStyle {
187+
pub fn dark_mode_and_light_mode_are_identical(&self) -> bool {
188+
match self {
189+
FolderStyle::BigSur => false,
190+
FolderStyle::Tahoe => true,
191+
}
192+
}
193+
}
194+
164195
pub fn get_options() -> Options {
165196
let mut command = FolderifyArgs::command();
166197

@@ -178,8 +209,12 @@ pub fn get_options() -> Options {
178209
}
179210
};
180211

181-
if let Some(mac_os) = &args.mac_os {
182-
let mac_os: &str = mac_os;
212+
let folder_style = {
213+
let mac_os: String = args
214+
.mac_os
215+
.map(|s| s.to_owned())
216+
.unwrap_or_else(current_macOS_version);
217+
let mac_os = mac_os.as_str();
183218
// macOS 11.0 reports itself as macOS 10.16 in some APIs. Someone might pass such a value on to `folderify`, so we can't just check for major version 10.
184219
// Instead, we denylist the versions that previously had different folder icons, so that we don't accidentally apply the Big Sur style when one of these versions was specified.
185220
if matches!(
@@ -199,10 +234,21 @@ pub fn get_options() -> Options {
199234
eprintln!("Error: OS X / macOS 10 was specified. This is no longer supported by folderify v3.\nTo generate these icons, please use folderify v2: https://github.com/lgarron/folderify/tree/main#os-x-macos-10");
200235
exit(1)
201236
}
202-
if !known_mac_os_version(mac_os) {
203-
eprintln!("Warning: Unknown macOS version specified. Assuming macOS 11 or later");
237+
if is_major_macos_version_one_of(mac_os, &["15", "14", "13", "12", "11"]) {
238+
FolderStyle::BigSur
239+
} else if is_major_macos_version_one_of(
240+
mac_os,
241+
&["26"], // Note: macOS 16 through 25 do not exist.
242+
) {
243+
eprintln!("Warning: macOS Tahoe is still in beta. The icon may not match the final macOS 26 release.");
244+
FolderStyle::Tahoe
245+
} else {
246+
eprintln!(
247+
"Warning: Unknown macOS version specified. Assuming Big Sur (macOS 11 through 15)."
248+
);
249+
FolderStyle::BigSur
204250
}
205-
}
251+
};
206252
let debug = var("FOLDERIFY_DEBUG") == Ok("1".into());
207253
let verbose = args.verbose || debug;
208254
let show_progress = !args.no_progress && !args.verbose;
@@ -213,9 +259,11 @@ pub fn get_options() -> Options {
213259
};
214260
Options {
215261
mask_path: mask,
216-
color_scheme: map_color_scheme_auto(args.color_scheme),
262+
color_scheme: map_color_scheme_auto(args.color_scheme, folder_style),
217263
no_trim: args.no_trim,
218264
target: args.target,
265+
folder_style,
266+
empty_folder: args.empty_folder,
219267
output_icns: args.output_icns,
220268
output_iconset: args.output_iconset,
221269
badge: args.badge,
@@ -227,7 +275,20 @@ pub fn get_options() -> Options {
227275
}
228276
}
229277

230-
fn map_color_scheme_auto(color_scheme: ColorSchemeOrAuto) -> ColorScheme {
278+
fn map_color_scheme_auto(
279+
color_scheme: ColorSchemeOrAuto,
280+
folder_style: FolderStyle,
281+
) -> ColorScheme {
282+
if folder_style.dark_mode_and_light_mode_are_identical() {
283+
match color_scheme {
284+
ColorSchemeOrAuto::Auto => {}
285+
_ => {
286+
eprintln!("Dark and light mode folder icons are identical for the folder style of this macOS version. Ignoring the `--color-scheme` argument.");
287+
}
288+
}
289+
return ColorScheme::Light;
290+
}
291+
231292
match color_scheme {
232293
ColorSchemeOrAuto::Dark => return ColorScheme::Dark,
233294
ColorSchemeOrAuto::Light => return ColorScheme::Light,
@@ -251,3 +312,18 @@ fn map_color_scheme_auto(color_scheme: ColorSchemeOrAuto) -> ColorScheme {
251312
}
252313
}
253314
}
315+
316+
const DEFAULT_MACOS_VERSION: &str = "15.0";
317+
318+
#[allow(non_snake_case)]
319+
fn current_macOS_version() -> String {
320+
let Ok(o) = Command::new("sw_vers").args(["-productVersion"]).output() else {
321+
return DEFAULT_MACOS_VERSION.to_owned();
322+
};
323+
324+
let Ok(stdout) = from_utf8(&o.stdout) else {
325+
return DEFAULT_MACOS_VERSION.to_owned();
326+
};
327+
328+
stdout.to_owned()
329+
}

src/resources.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,34 @@ use include_dir::{include_dir, Dir};
44

55
use crate::{
66
icon_conversion::IconResolution,
7-
options::{Badge, ColorScheme},
7+
options::{Badge, ColorScheme, FolderStyle},
88
};
99

1010
static RESOURCES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/resources");
1111

12-
pub fn get_folder_icon(color_scheme: ColorScheme, resolution: &IconResolution) -> &'static [u8] {
12+
pub struct IconInputs {
13+
pub folder_style: FolderStyle,
14+
pub color_scheme: ColorScheme,
15+
pub resolution: IconResolution,
16+
pub empty_folder: bool,
17+
}
18+
19+
pub fn get_folder_icon(icon_inputs: &IconInputs) -> &'static [u8] {
1320
let mut path = PathBuf::new();
1421
path.push("folders");
15-
path.push(match color_scheme {
16-
ColorScheme::Light => "GenericFolderIcon.BigSur.iconset",
17-
ColorScheme::Dark => "GenericFolderIcon.BigSur.dark.iconset",
18-
});
19-
path.push(resolution.icon_file());
22+
path.push(
23+
match (
24+
icon_inputs.color_scheme,
25+
icon_inputs.folder_style,
26+
icon_inputs.empty_folder,
27+
) {
28+
(ColorScheme::Light, FolderStyle::BigSur, _) => "GenericFolderIcon.BigSur.iconset",
29+
(ColorScheme::Dark, FolderStyle::BigSur, _) => "GenericFolderIcon.BigSur.dark.iconset",
30+
(_, FolderStyle::Tahoe, true) => "GenericFolderIcon.empty.Tahoe.iconset",
31+
(_, FolderStyle::Tahoe, false) => "GenericFolderIcon.non-empty.Tahoe.iconset",
32+
},
33+
);
34+
path.push(icon_inputs.resolution.icon_file());
2035
RESOURCES_DIR.get_file(&path).unwrap().contents()
2136
}
2237

3.51 KB
Loading
8.13 KB
Loading
508 Bytes
Loading
926 Bytes
Loading
9.05 KB
Loading

0 commit comments

Comments
 (0)