Skip to content

Add SF Symbol support for tool bar, Touch Bar, menu icons #1329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions runtime/doc/gui.txt
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,9 @@ level. Vim interprets the items in this menu as follows:
A space in the file name must be escaped with a backslash.
A menu priority must come _after_ the icon argument: >
:amenu icon=foo 1.42 ToolBar.Foo :echo "42!"<CR>
<
In MacVim, the "icon=" argument can also be used to specify SF symbols or
system named images. See |macvim-toolbar-icon| for more details.
2) An item called 'BuiltIn##', where ## is a number, is taken as number ## of
the built-in bitmaps available in Vim. Currently there are 31 numbered
from 0 to 30 which cover most common editing operations |builtin-tools|. >
Expand Down
64 changes: 47 additions & 17 deletions runtime/doc/gui_mac.txt
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ Default Menus~

See |macvim-default-menus|.

Icons~

Unlike regular Vim, MacVim menus can be customized with an icon. Simply use
the "icon=" parameter similar to toolbar. See |macvim-toolbar-icon| for usage.

Customization~

Menus in macOS behave slightly different from other platforms. For that
Expand Down Expand Up @@ -554,16 +559,40 @@ empty space which will shink or expand so that the items to the right of it
are right-aligned. A space (flexspace) will be created for any toolbar item
whose name begins with "-space" ("-flexspace") and ends with "-"

Toolbar icons should be tiff, png, icns, or heic, of dimension 32x32 or 24x24
pixels. The larger size is used when 'tbis' is "medium" or "large", otherwise
the smaller size is used (which is the default). If the icon file only
contains one dimension then macOS will scale the icon to the appropriate
dimension if necessary. To avoid this, use a file format which supports
multiple resolutions (such as icns) and provide both 32x32 and 24x24 versions
of the icon.
*macvim-toolbar-icon*
In regular Vim, the "icon=" argument (see |toolbar-icon|) can be used to
specify an image file by file path. In MacVim, the argument could also be
used for specifying an SF symbol or a macOS system image. Simply use the SF
symbol name or the system image name and MacVim will use load them instead of
an image file. Below are examples for using an SF symbol "gearshape.2" and a
macOS system image named "NSAdvanced": >
:an icon=gearshape.2 ToolBar.Setting1 <Nop>
:an icon=NSAdvanced ToolBar.Setting2 <Nop>
Some SF symbols in macOS can be customized with different styles. You can do
so by using colon-delimited options (most of them require macOS 13 Ventura).
The available options are `monochrome`, `hierarchical`, `palette`,
`multicolor`, and `variable-0.5` (where 0.5 can be substituted with any number
between 0 and 1). Download Apple's SF Symbols app to find out what the symbol
names are and what styling options each one supports. Some examples below: >
:an icon=bolt.circle:hierarchical ToolBar.Bolt :echo '⚡️'<CR>
:an icon=cloud.sun.rain.fill:multicolor ToolBar.Cloud :echo '🌦️'<CR>
:an icon=homekit:variable-0.4:palette ToolBar.Home :echo '🏠'<CR>
<
If your icon image is a template image (meaning that it is a grayscale image
designed to be mapped to whatever foreground color is), you can add
`:template` to the end of an image name, which will mark it as a template
image to macOS: >
:an icon=/a/b/black-and-white.png:template ToolBar.Foo <Nop>
<
Supported image formats depend on the version of macOS. Safe formats include
png, icns, ico. Later macOS versions also support heic and webp.

Note: Only a subset of the builtin toolbar items presently have icons. If no
icon can be found a warning triangle is displayed instead.
Toolbar icons should be of dimension 32x32 or 24x24 pixels. The larger size
is used when 'tbis' is "medium" or "large", otherwise the smaller size is used
(which is the default). If the icon file only contains one dimension then
macOS will scale the icon to the appropriate dimension if necessary. To avoid
this, use a file format which supports multiple resolutions (such as icns) and
provide both 32x32 and 24x24 versions of the icon.

==============================================================================
8. Touch Bar *macvim-touchbar*
Expand All @@ -573,6 +602,9 @@ Touch Bar in MacVim is configurable, and works similar to the toolbar (see
instead of "ToolBar": >
:an TouchBar.Hello :echo "Hello"<CR>
<
This feature only works on Mac devices that come with Touch Bars. On the ones
that don't, nothing will show up.

You can also create submenus. Due to macOS restrictions, submenus can only be
one level deep: >
:an TouchBar.Navigate.Next :next<CR>
Expand All @@ -593,13 +625,14 @@ begin with "-flexspace" and ends with "-".

*macvim-touchbar-icon*
You can specify icons for Touch Bar buttons the same way for toolbar icons
(see |macvim-toolbar|). When a button has an icon, it won't show the menu
(see |macvim-toolbar-icon|). When a button has an icon, it won't show the menu
name. Touch Bar icons should ideally be 36x36 pixels, and no larger than
44x44 pixels. >
:an icon=/home/foo/bar.png TouchBar.DoSomething :echo 'Do'<CR>
You can also use default template icons provided by Apple by using their
template names. An example: >
:an icon=/home/foo/bar.png TouchBar.DoThing :echo 'Do'<CR>
You can use any image for the icon, but macOS comes with a few default
template images designed for use with Touch Bar. Some examples: >
:an icon=NSTouchBarListViewTemplate TouchBar.ShowList :ls<CR>
:an icon=NSTouchBarRefreshTemplate TouchBar.Refresh :e!<CR>
<
*macvim-touchbar-title*
By default, the Touch Bar buttons will use the menu names as the title. If an
Expand All @@ -614,10 +647,7 @@ the icon. Example: >
You can also insert emojis by adding a character picker button (specified by
using a name that begin wtih "-characterpicker" and ends with "-"): >
:inoremenu TouchBar.-characterpicker- <Nop>

This feature only works on Mac devices that come with Touch Bars. On the ones
that don't, nothing will show up.

<
*macvim-touchbar-defaults*
Here is a list of default Touch Bar buttons that MacVim sets up:

Expand Down
1 change: 1 addition & 0 deletions runtime/doc/tags
Original file line number Diff line number Diff line change
Expand Up @@ -8410,6 +8410,7 @@ macvim-start gui_mac.txt /*macvim-start*
macvim-tablabel gui_mac.txt /*macvim-tablabel*
macvim-todo gui_mac.txt /*macvim-todo*
macvim-toolbar gui_mac.txt /*macvim-toolbar*
macvim-toolbar-icon gui_mac.txt /*macvim-toolbar-icon*
macvim-touchbar gui_mac.txt /*macvim-touchbar*
macvim-touchbar-characterpicker gui_mac.txt /*macvim-touchbar-characterpicker*
macvim-touchbar-defaults gui_mac.txt /*macvim-touchbar-defaults*
Expand Down
2 changes: 1 addition & 1 deletion src/MacVim/MMPreferenceController.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ - (IBAction)showWindow:(id)sender

- (void)setupToolbar
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_0
if (@available(macos 11.0, *)) {
// Use SF Symbols for versions of the OS that supports it to be more unified with OS appearance.
[self addView:generalPreferences
Expand Down
169 changes: 156 additions & 13 deletions src/MacVim/MMVimController.m
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ - (void)addMenuItemWithDescriptor:(NSArray *)desc
- (void)removeMenuItemWithDescriptor:(NSArray *)desc;
- (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
- (void)updateMenuItemTooltipWithDescriptor:(NSArray *)desc tip:(NSString *)tip;
- (NSImage*)findToolbarIcon:(NSString*)icon;
- (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
toolTip:(NSString *)tip icon:(NSString *)icon;
- (void)addToolbarItemWithLabel:(NSString *)label
Expand Down Expand Up @@ -1547,6 +1548,11 @@ - (void)addMenuItemWithDescriptor:(NSArray *)desc
}
[item setAlternate:isAlternate];

NSImage *img = [self findToolbarIcon:icon];
if (img) {
[item setImage: img];
}

// The tag is used to indicate whether Vim thinks a menu item should be
// enabled or disabled. By default Vim thinks menu items are enabled.
[item setTag:1];
Expand Down Expand Up @@ -1700,6 +1706,154 @@ - (void)updateMenuItemTooltipWithDescriptor:(NSArray *)desc
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
}

/// Load an icon image for the provided name. This will try multiple things to find the best image that fits the name.
/// @param icon Can be an SF Symbol name (with colon-separated formatting strings), named system image, or just a file.
- (NSImage*)findToolbarIcon:(NSString*)icon
{
if ([icon length] == 0) {
return nil;
}
NSImage *img = nil;

// Detect whether this is explicitly specified to be a template image, via a ":template" configuration suffix.
BOOL template = NO;
if ([icon hasSuffix:@":template"]) {
icon = [icon substringToIndex:([icon length] - 9)];
template = YES;
}

// Attempt 1: Load an SF Symbol image. This is first try because it's what Apple is pushing for and also likely
// what our users are going to want to use. We also allows for customization of the symbol.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_0
if (@available(macos 11.0, *)) {
// All SF Symbol functionality were introduced in macOS 11.0.
NSString *sfSymbolName = icon;

BOOL monochrome = NO, hierarchical = NO, palette = NO, multicolor = NO;
double variableValue = -1;

if ([sfSymbolName rangeOfString:@":"].location != NSNotFound) {
// We support using colon-separated strings to customize the symbol. First item is the icon name itself.
NSArray<NSString*> *splitComponents = [sfSymbolName componentsSeparatedByString:@":"];
sfSymbolName = splitComponents[0];

for (int i = 1, count = splitComponents.count; i < count; i++) {
NSString *component = splitComponents[i];
if ([component isEqualToString:@"monochrome"]) {
monochrome = YES;
} else if ([component isEqualToString:@"hierarchical"]) {
hierarchical = YES;
} else if ([component isEqualToString:@"palette"]) {
palette = YES;
} else if ([component isEqualToString:@"multicolor"]) {
multicolor = YES;
} else if ([component hasPrefix:@"variable-"]) {
NSString *variableString = [component substringFromIndex:9];
variableValue = [variableString floatValue];
}
}
}

#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
if (@available(macos 13.0, *)) {
if (variableValue >= 0.0 && variableValue <= 1.0) {
img = [NSImage imageWithSystemSymbolName:sfSymbolName variableValue:variableValue accessibilityDescription:nil];
}
}
#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0

if (img == nil) {
img = [NSImage imageWithSystemSymbolName:sfSymbolName accessibilityDescription:nil];
}

// Apply style customization to the symbol. This feature was added in macOS 12.
if (img) {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
if (@available(macos 12.0, *)) {
NSImageSymbolConfiguration *config = nil;
if (monochrome) {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
if (@available(macos 13.0, *)) {
config = [NSImageSymbolConfiguration configurationPreferringMonochrome];
}
#endif
}
if (hierarchical) {
NSImageSymbolConfiguration *config2;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
if (@available(macos 13.0, *))
{
// This version is preferred as it seems to set the color up automatically and therefore will use the correct ones.
config2 = [NSImageSymbolConfiguration configurationPreferringHierarchical];
}
else
#endif
{
// Just guess which color to use. AppKit doesn't really give you a color that you can pick so we just guess one.
config2 = [NSImageSymbolConfiguration configurationWithHierarchicalColor:NSColor.controlTextColor];
}
if (config) {
config = [config configurationByApplyingConfiguration:config2];
} else {
config = config2;
}
}
if (palette) {
// The palette colors aren't completely correct. It doesn't appear for there to be a good way to query the primary colors
// for Touch Bar, tool bar, etc, so we just use controlTextColor. It would be nice if Apple just provides a "Preferring"
// version of this API like the other ones.
NSImageSymbolConfiguration *config2 = [NSImageSymbolConfiguration configurationWithPaletteColors:@[NSColor.controlTextColor, NSColor.controlAccentColor]];
if (config) {
config = [config configurationByApplyingConfiguration:config2];
} else {
config = config2;
}
}
if (multicolor) {
NSImageSymbolConfiguration *config2 = [NSImageSymbolConfiguration configurationPreferringMulticolor];
if (config) {
config = [config configurationByApplyingConfiguration:config2];
} else {
config = config2;
}
}

if (config) {
NSImage *img2 = [img imageWithSymbolConfiguration:config];
if (img2) {
img = img2;
}
}
}
#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0

// Just mark them as used so compiling on older SDKs won't complain about unused variables.
(void)multicolor;
(void)hierarchical;
(void)palette;
(void)variableValue;
}
}
#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_0

// Attempt 2: Load a named image.
if (!img) {
img = [NSImage imageNamed:icon];
}

// Attempt 3: Load from a file.
if (!img) {
img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
if (!(img && [img isValid]))
img = nil;
}

if (img && template) {
[img setTemplate:YES];
}
return img;
}

- (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
toolTip:(NSString *)tip
icon:(NSString *)icon
Expand All @@ -1717,12 +1871,7 @@ - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
[item setAction:@selector(vimToolbarItemAction:)];
[item setAutovalidates:NO];

NSImage *img = [NSImage imageNamed:icon];
if (!img) {
img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
if (!(img && [img isValid]))
img = nil;
}
NSImage *img = [self findToolbarIcon:icon];
if (!img) {
ASLogNotice(@"Could not find image with name '%@' to use as toolbar"
" image for identifier '%@';"
Expand All @@ -1732,7 +1881,6 @@ - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
img = [NSImage imageNamed:MMDefaultToolbarImageName];
}

[img setTemplate:YES];
[item setImage:img];

[toolbarItemDict setObject:item forKey:title];
Expand Down Expand Up @@ -1809,13 +1957,8 @@ - (void)addTouchbarItemWithLabel:(NSString *)label
[button setDesc:desc];
NSCustomTouchBarItem *item =
[[[NSCustomTouchBarItem alloc] initWithIdentifier:label] autorelease];
NSImage *img = [NSImage imageNamed:icon];

if (!img) {
img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
if (!(img && [img isValid]))
img = nil;
}
NSImage *img = [self findToolbarIcon:icon];;
if (img) {
[button setImage: img];
if (useTip) {
Expand Down
6 changes: 6 additions & 0 deletions src/MacVim/MacVim.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@
#ifndef MAC_OS_X_VERSION_10_14
# define MAC_OS_X_VERSION_10_14 101400
#endif
#ifndef MAC_OS_VERSION_11_0
# define MAC_OS_VERSION_11_0 110000
#endif
#ifndef MAC_OS_VERSION_12_0
# define MAC_OS_VERSION_12_0 120000
#endif
#ifndef MAC_OS_VERSION_13_0
# define MAC_OS_VERSION_13_0 130000
#endif

#ifndef NSAppKitVersionNumber10_10
# define NSAppKitVersionNumber10_10 1343
Expand Down
Loading