[vlc-commits] [Git][videolan/vlc][master] 2 commits: macosx: Bring home view headers in line with other macOS apps

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Tue Oct 20 17:47:34 UTC 2026



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
d694a5b8 by Claudio Cambra at 2026-10-20T14:12:50+02:00
macosx: Bring home view headers in line with other macOS apps

Since there is no content sliding beneath the headers, the glass headers are too much. Standard titles with a chevron are plenty

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
47cb0cdd by Claudio Cambra at 2026-10-20T14:12:50+02:00
macosx: Place home header item counts beside titles

Align the count at the trailing edge of the title row, preserving its
width when space is limited and truncating the title first. Update the
layout tests to cover alignment and changing item counts.

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -


2 changed files:

- modules/gui/macosx/library/home-library/VLCLibraryHomeViewSectionHeaderView.m
- modules/gui/macosx/tests/VLCLibraryHomeViewSectionHeaderViewTest.m


Changes:

=====================================
modules/gui/macosx/library/home-library/VLCLibraryHomeViewSectionHeaderView.m
=====================================
@@ -24,14 +24,24 @@
 
 #import "extensions/NSFont+VLCAdditions.h"
 #import "extensions/NSString+Helpers.h"
-#import "extensions/NSView+VLCAdditions.h"
 
 #import "views/VLCUIUnits.h"
-#import "views/VLCVisualEffectView.h"
+
+ at interface VLCLibraryHomeViewSectionHeaderButtonCell : NSButtonCell
+ at end
+
+ at implementation VLCLibraryHomeViewSectionHeaderButtonCell
+
+- (void)setHighlighted:(BOOL)highlighted
+{
+    [super setHighlighted:highlighted];
+    self.controlView.alphaValue = highlighted ? 0.5 : 1.0;
+}
+
+ at end
 
 @interface VLCLibraryHomeViewSectionHeaderView ()
 
- at property (readonly) NSTextField *titleField;
 @property (readonly) NSTextField *countField;
 
 @end
@@ -58,46 +68,47 @@
 
 - (void)setupView
 {
-    VLCVisualEffectView * const backgroundView = [[VLCVisualEffectView alloc] initWithFrame:self.bounds];
-    if (!backgroundView.usesGlassEffect) {
-        backgroundView.cornerRadius = VLCUIUnits.cornerRadius;
-    }
-    backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
-    [self addSubview:backgroundView];
-    [backgroundView applyConstraintsToFillSuperview];
-    NSView * const contentView = backgroundView.contentView;
-
-    _titleField = [self labelWithFont:NSFont.VLClibrarySectionHeaderFont textColor:NSColor.labelColor];
     _countField = [self labelWithFont:NSFont.VLCLibrarySubsectionSubheaderFont textColor:NSColor.secondaryLabelColor];
-    _libraryButton = [NSButton buttonWithTitle:_NS("Show all") target:nil action:nil];
+    _libraryButton = [NSButton buttonWithTitle:@"" target:nil action:nil];
+    self.libraryButton.cell = [[VLCLibraryHomeViewSectionHeaderButtonCell alloc] initTextCell:@""];
+    [self.libraryButton setButtonType:NSButtonTypeMomentaryPushIn];
     self.libraryButton.translatesAutoresizingMaskIntoConstraints = NO;
-    self.libraryButton.bezelStyle = NSBezelStyleRounded;
-    [self.libraryButton setContentHuggingPriority:NSLayoutPriorityRequired
+    self.libraryButton.bordered = NO;
+    if (@available(macOS 10.14, *)) {
+        self.libraryButton.contentTintColor = NSColor.labelColor;
+    }
+    self.libraryButton.font = NSFont.VLClibrarySectionHeaderFont;
+    self.libraryButton.alignment = NSTextAlignmentNatural;
+    self.libraryButton.lineBreakMode = NSLineBreakByTruncatingTail;
+    self.libraryButton.imagePosition = NSImageTrailing;
+    self.libraryButton.imageScaling = NSImageScaleProportionallyDown;
+    NSImage *chevron = nil;
+    if (@available(macOS 11.0, *)) {
+        chevron = [NSImage imageWithSystemSymbolName:@"chevron.forward" accessibilityDescription:nil];
+    }
+    if (chevron == nil) {
+        chevron = [NSImage imageNamed:self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft
+                    ? NSImageNameGoLeftTemplate : NSImageNameGoRightTemplate];
+    }
+    self.libraryButton.image = chevron;
+    [self.libraryButton setContentHuggingPriority:NSLayoutPriorityDefaultHigh
                                  forOrientation:NSLayoutConstraintOrientationHorizontal];
-    [self.libraryButton setContentCompressionResistancePriority:NSLayoutPriorityRequired
+    [self.libraryButton setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow
                                                forOrientation:NSLayoutConstraintOrientationHorizontal];
-
-    NSStackView * const labelsView = [NSStackView stackViewWithViews:@[self.titleField, self.countField]];
-    labelsView.translatesAutoresizingMaskIntoConstraints = NO;
-    labelsView.orientation = NSUserInterfaceLayoutOrientationVertical;
-    labelsView.alignment = NSLayoutAttributeLeading;
-    labelsView.spacing = VLCUIUnits.smallSpacing;
-    [labelsView setContentHuggingPriority:NSLayoutPriorityDefaultLow
-                         forOrientation:NSLayoutConstraintOrientationHorizontal];
-    [labelsView setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow
-                                       forOrientation:NSLayoutConstraintOrientationHorizontal];
-    [contentView addSubview:labelsView];
-    [contentView addSubview:self.libraryButton];
+    [self.countField setContentCompressionResistancePriority:NSLayoutPriorityRequired
+                                             forOrientation:NSLayoutConstraintOrientationHorizontal];
+    [self addSubview:self.libraryButton];
+    [self addSubview:self.countField];
 
     [NSLayoutConstraint activateConstraints:@[
-        [labelsView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:VLCUIUnits.mediumSpacing],
-        [labelsView.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:VLCUIUnits.smallSpacing],
-        [labelsView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor constant:-VLCUIUnits.smallSpacing],
-        [self.libraryButton.leadingAnchor constraintGreaterThanOrEqualToAnchor:labelsView.trailingAnchor constant:VLCUIUnits.largeSpacing],
-        [self.libraryButton.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-VLCUIUnits.mediumSpacing],
-        [self.libraryButton.centerYAnchor constraintEqualToAnchor:contentView.centerYAnchor],
-        [self.libraryButton.topAnchor constraintGreaterThanOrEqualToAnchor:contentView.topAnchor constant:VLCUIUnits.smallSpacing],
-        [self.libraryButton.bottomAnchor constraintLessThanOrEqualToAnchor:contentView.bottomAnchor constant:-VLCUIUnits.smallSpacing],
+        [self.libraryButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
+        [self.libraryButton.trailingAnchor constraintLessThanOrEqualToAnchor:self.countField.leadingAnchor constant:-VLCUIUnits.largeSpacing],
+        [self.libraryButton.topAnchor constraintEqualToAnchor:self.topAnchor],
+        [self.libraryButton.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
+        [self.countField.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
+        [self.countField.centerYAnchor constraintEqualToAnchor:self.libraryButton.centerYAnchor],
+        [self.countField.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor],
+        [self.countField.bottomAnchor constraintLessThanOrEqualToAnchor:self.bottomAnchor],
     ]];
 }
 
@@ -117,8 +128,7 @@
 - (void)setTitle:(NSString *)title
 {
     _title = [title copy];
-    self.titleField.stringValue = title;
-    self.titleField.toolTip = title;
+    self.libraryButton.title = title;
     self.libraryButton.accessibilityLabel = [NSString stringWithFormat:_NS("Open %@ in the library"), title];
     self.libraryButton.toolTip = self.libraryButton.accessibilityLabel;
 }


=====================================
modules/gui/macosx/tests/VLCLibraryHomeViewSectionHeaderViewTest.m
=====================================
@@ -17,7 +17,6 @@
 #import "library/home-library/VLCLibraryHomeViewSectionHeaderView.h"
 
 @interface VLCLibraryHomeViewSectionHeaderView (TestAccess)
- at property (readonly) NSTextField *titleField;
 @property (readonly) NSTextField *countField;
 @end
 
@@ -71,36 +70,48 @@
 
 - (void)assertContentFitsHeader
 {
-    for (NSView *view in @[self.header.titleField, self.header.countField, self.header.libraryButton]) {
-        const NSRect contentRect = [view convertRect:view.bounds toView:self.header];
-        XCTAssertTrue(NSContainsRect(self.header.bounds, contentRect));
+    for (NSView *view in @[self.header.countField, self.header.libraryButton]) {
+        // Native controls include drawing margins outside their alignment rect.
+        const NSRect alignmentRect = [view alignmentRectForFrame:view.frame];
+        const NSRect contentRect = [view.superview convertRect:alignmentRect toView:self.header];
+        XCTAssertTrue(NSContainsRect(self.header.bounds, contentRect), @"%@ frame %@ in header %@",
+                      view.class, NSStringFromRect(contentRect), NSStringFromRect(self.header.bounds));
         XCTAssertFalse(view.hasAmbiguousLayout);
         XCTAssertGreaterThanOrEqual(view.frame.size.height, view.intrinsicContentSize.height);
     }
     XCTAssertFalse(self.header.hasAmbiguousLayout);
 }
 
-- (void)testTitleIsCopiedAndUpdatesLabelAndAccessibility
+- (NSData *)renderedButtonImage
+{
+    NSButton * const button = self.header.libraryButton;
+    NSView * const contentView = self.window.contentView;
+    const NSRect buttonRect = [button convertRect:button.bounds toView:contentView];
+    NSBitmapImageRep * const bitmap = [contentView bitmapImageRepForCachingDisplayInRect:buttonRect];
+    [contentView cacheDisplayInRect:buttonRect toBitmapImageRep:bitmap];
+    return [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
+}
+
+- (void)testTitleIsCopiedAndUpdatesButtonAndAccessibility
 {
     NSMutableString * const title = [@"Recent music" mutableCopy];
     self.header.title = title;
     [title appendString:@" changed after assignment"];
     XCTAssertEqualObjects(self.header.title, @"Recent music");
-    XCTAssertEqualObjects(self.header.titleField.stringValue, @"Recent music");
-    XCTAssertEqualObjects(self.header.titleField.toolTip, @"Recent music");
+    XCTAssertEqualObjects(self.header.libraryButton.title, @"Recent music");
     NSString * const accessibilityLabel =
         [NSString stringWithFormat:_NS("Open %@ in the library"), @"Recent music"];
     XCTAssertEqualObjects(self.header.libraryButton.accessibilityLabel, accessibilityLabel);
     XCTAssertEqualObjects(self.header.libraryButton.toolTip, accessibilityLabel);
 
     self.header.title = @"Library";
-    XCTAssertEqualObjects(self.header.titleField.stringValue, @"Library");
+    XCTAssertEqualObjects(self.header.libraryButton.title, @"Library");
     NSString * const libraryAccessibilityLabel =
         [NSString stringWithFormat:_NS("Open %@ in the library"), @"Library"];
     XCTAssertEqualObjects(self.header.libraryButton.accessibilityLabel, libraryAccessibilityLabel);
 }
 
-- (void)testCountUpdatesSubtitleIncludingZeroAndSingular
+- (void)testCountUpdatesIncludingZeroAndSingular
 {
     for (NSNumber *count in @[@0, @1, @2, @123, @1, @0]) {
         const NSUInteger itemCount = count.unsignedIntegerValue;
@@ -120,7 +131,33 @@
     XCTAssertEqual(self.actionSender, self.header.libraryButton);
 }
 
-- (void)testNarrowHeaderTruncatesTitleWithoutOverlappingButton
+- (void)testPressedTitleChangesRenderingAndRestoresOnRelease
+{
+    NSData * const normalImage = [self renderedButtonImage];
+    XCTAssertNotNil(normalImage);
+    [self.header.libraryButton highlight:YES];
+    NSData * const pressedImage = [self renderedButtonImage];
+    XCTAssertNotEqualObjects(pressedImage, normalImage);
+    [self.header.libraryButton highlight:NO];
+    XCTAssertEqualObjects([self renderedButtonImage], normalImage);
+    XCTAssertEqual(self.header.libraryButton.state, NSControlStateValueOff);
+}
+
+- (void)testTitleButtonHasTrailingChevronAndNoBackground
+{
+    XCTAssertFalse(self.header.libraryButton.bordered);
+    if (@available(macOS 10.14, *)) {
+        XCTAssertEqualObjects(self.header.libraryButton.contentTintColor, NSColor.labelColor);
+    }
+    XCTAssertNotNil(self.header.libraryButton.image);
+    XCTAssertEqual(self.header.libraryButton.imagePosition, NSImageTrailing);
+    XCTAssertEqual(self.header.subviews.count, (NSUInteger)2);
+    XCTAssertEqual(self.header.libraryButton.superview, self.header);
+    XCTAssertEqual(self.header.countField.superview, self.header);
+    XCTAssertLessThan(self.header.libraryButton.frame.size.width, self.header.frame.size.width);
+}
+
+- (void)testNarrowHeaderTruncatesTitleAndKeepsCountAtTrailingEdge
 {
     self.header.title = @"A very long recent video collection section title";
     self.widthConstraint.constant = 320;
@@ -129,35 +166,49 @@
 
     const NSRect buttonRect = [self.header.libraryButton convertRect:self.header.libraryButton.bounds
                                                             toView:self.header];
-    for (NSTextField *label in @[self.header.titleField, self.header.countField]) {
-        const NSRect labelRect = [label convertRect:label.bounds toView:self.header];
-        XCTAssertLessThanOrEqual(NSMaxX(labelRect), NSMinX(buttonRect));
-    }
-    XCTAssertLessThan(self.header.titleField.frame.size.width,
-                      self.header.titleField.intrinsicContentSize.width);
-    XCTAssertEqual(self.header.titleField.lineBreakMode, NSLineBreakByTruncatingTail);
-    XCTAssertGreaterThanOrEqual(self.header.libraryButton.frame.size.width,
-                                self.header.libraryButton.intrinsicContentSize.width);
+    const NSRect countRect = [self.header.countField alignmentRectForFrame:self.header.countField.frame];
+    XCTAssertLessThanOrEqual(NSMaxX(buttonRect), NSMinX(countRect));
+    XCTAssertEqualWithAccuracy(NSMidY(countRect), NSMidY(buttonRect), 0.5);
+    XCTAssertEqualWithAccuracy(NSMaxX(countRect), NSMaxX(self.header.bounds), 0.5);
+    XCTAssertGreaterThanOrEqual(countRect.size.width, self.header.countField.intrinsicContentSize.width);
+    XCTAssertLessThan(self.header.libraryButton.frame.size.width,
+                      self.header.libraryButton.intrinsicContentSize.width);
+    XCTAssertEqual(self.header.libraryButton.lineBreakMode, NSLineBreakByTruncatingTail);
 
     self.widthConstraint.constant = 800;
     [self.window.contentView layoutSubtreeIfNeeded];
     [self assertContentFitsHeader];
 }
 
+- (void)testCountRemainsVisibleWhenItsWidthChanges
+{
+    self.header.title = @"A very long recent video collection section title";
+    self.widthConstraint.constant = 320;
+    for (NSNumber *count in @[@0, @1, @(NSUIntegerMax), @2]) {
+        self.header.itemCount = count.unsignedIntegerValue;
+        [self.window.contentView layoutSubtreeIfNeeded];
+        [self assertContentFitsHeader];
+        const NSRect countRect = [self.header.countField alignmentRectForFrame:self.header.countField.frame];
+        XCTAssertEqualWithAccuracy(NSMaxX(countRect), NSMaxX(self.header.bounds), 0.5);
+        XCTAssertGreaterThanOrEqual(countRect.size.width, self.header.countField.intrinsicContentSize.width);
+        XCTAssertLessThanOrEqual(NSMaxX(self.header.libraryButton.frame), NSMinX(countRect));
+    }
+}
+
 - (void)testHeightFollowsContentAndLargerFonts
 {
     const CGFloat originalHeight = self.header.frame.size.height;
     XCTAssertGreaterThan(originalHeight, 0);
     XCTAssertEqualWithAccuracy(originalHeight, self.header.fittingSize.height, 0.5);
 
-    NSFont * const originalFont = self.header.titleField.font;
-    self.header.titleField.font = [NSFont boldSystemFontOfSize:originalFont.pointSize + 12];
+    NSFont * const originalFont = self.header.libraryButton.font;
+    self.header.libraryButton.font = [NSFont boldSystemFontOfSize:originalFont.pointSize + 12];
     [self.window.contentView layoutSubtreeIfNeeded];
     XCTAssertGreaterThan(self.header.frame.size.height, originalHeight);
     XCTAssertEqualWithAccuracy(self.header.frame.size.height, self.header.fittingSize.height, 0.5);
     [self assertContentFitsHeader];
 
-    self.header.titleField.font = originalFont;
+    self.header.libraryButton.font = originalFont;
     [self.window.contentView layoutSubtreeIfNeeded];
     XCTAssertEqualWithAccuracy(self.header.frame.size.height, originalHeight, 0.5);
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6050eca3f959eb84316083d7fb2671b7e05cc8b5...47cb0cdd345b36f1c2362128c25717c18e579e18

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6050eca3f959eb84316083d7fb2671b7e05cc8b5...47cb0cdd345b36f1c2362128c25717c18e579e18
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list