From 321c80a6b08bb7aae8b393f1e74d07d8b3abac59 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Tue, 23 Mar 2021 12:51:59 -0400 Subject: [PATCH 1/3] Move the dragging prompt away from being drawn every time the outline view draws, to a separate proper text field subview of the scroll view. This prevents the prompt text from scrolling along with the content, and alleviates problems with stale drawing sticking around sometimes when the scrollview has cached its image. --- IMBOutlineView.h | 3 +- IMBOutlineView.m | 143 +++++++++++++++++++++++++++++------------------ 2 files changed, 91 insertions(+), 55 deletions(-) diff --git a/IMBOutlineView.h b/IMBOutlineView.h index 1e8b87d44..3d8e12bc7 100644 --- a/IMBOutlineView.h +++ b/IMBOutlineView.h @@ -66,8 +66,7 @@ extern NSString* IMBIsDefaultAppearanceAttributeName; IMBTableViewAppearance *_appearance; } -@property (retain) NSString* draggingPrompt; -@property (retain) IMBTextFieldCell* textCell; +@property (retain) NSTextField* draggingPromptTextField; @property (readonly) IMBTableViewAppearance *imb_Appearance; - (IMBNode*) nodeAtRow:(NSInteger)inRow; diff --git a/IMBOutlineView.m b/IMBOutlineView.m index 58834b8ee..9dd477ad0 100644 --- a/IMBOutlineView.m +++ b/IMBOutlineView.m @@ -69,8 +69,7 @@ of this software and associated documentation files (the "Software"), to deal @implementation IMBOutlineView -@synthesize draggingPrompt = _draggingPrompt; -@synthesize textCell = _textCell; +@synthesize draggingPromptTextField = _draggingPromptTextField; @synthesize imb_Appearance = _appearance; - (void)setImb_Appearance:(IMBTableViewAppearance *)inAppearance @@ -119,9 +118,8 @@ - (void) dealloc [[NSNotificationCenter defaultCenter] removeObserver:self]; IMBRelease(_subviewsInVisibleRows); - IMBRelease(_draggingPrompt); - IMBRelease(_textCell); - + IMBRelease(_draggingPromptTextField); + if (_appearance) { [_appearance unsetView]; @@ -137,20 +135,7 @@ - (void) dealloc - (void) awakeFromNib { - self.draggingPrompt = NSLocalizedStringWithDefaultValue( - @"IMBOutlineView.draggingPrompt", - nil,IMBBundle(), - @"Drag additional folders here", - @"String that is displayed in the IMBOutlineView"); - - CGFloat size = [NSFont systemFontSizeForControlSize:NSSmallControlSize]; - NSFont* font = [NSFont boldSystemFontOfSize:size]; - - self.textCell = [[[IMBTextFieldCell alloc] initTextCell:@""] autorelease]; - [self.textCell setAlignment:NSCenterTextAlignment]; - [self.textCell setVerticalAlignment:kIMBBottomTextAlignment]; - [self.textCell setFont:font]; - [self.textCell setTextColor:[NSColor grayColor]]; + [self updateDraggingPrompt]; // We need to save preferences before tha app quits... @@ -167,6 +152,87 @@ - (void) awakeFromNib object:nil]; } +- (void) updateDraggingPrompt +{ + BOOL acceptsFiles = [[self registeredDraggedTypes] containsObject:NSFilenamesPboardType]; + NSScrollView* scrollView = self.enclosingScrollView; + + // Draw the prompt only when the content doesn't fill the view + const CGFloat MARGIN_BELOW_DATA = 20.0; + const CGFloat FADE_AREA = 20.0; + CGFloat viewHeight = scrollView.contentView.bounds.size.height; + CGFloat dataHeight = self.rowHeight * self.numberOfRows; + BOOL shouldPrompt = acceptsFiles && (dataHeight+MARGIN_BELOW_DATA <= viewHeight); + if ((shouldPrompt == NO) && (self.draggingPromptTextField != nil)) { + [self.draggingPromptTextField removeFromSuperview]; + [self.draggingPromptTextField release]; + self.draggingPromptTextField = nil; + } else if (shouldPrompt) { + // Create the text field as needed + if (self.draggingPromptTextField == nil) { + NSString* promptText = NSLocalizedStringWithDefaultValue( + @"IMBOutlineView.draggingPrompt", + nil,IMBBundle(), + @"Drag additional folders here to add them to the media browser.", + @"String that is displayed in the IMBOutlineView"); + + CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSControlSizeSmall]; + NSFont* promptFont = [NSFont boldSystemFontOfSize:fontSize]; + + NSRect draggingPromptFrame = [[self enclosingScrollView] bounds]; + draggingPromptFrame.size.height = [promptText sizeWithAttributes:@{NSFontAttributeName: promptFont}].height; + self.draggingPromptTextField = [[NSTextField alloc] initWithFrame:draggingPromptFrame]; + + IMBTextFieldCell* textCell = [[[IMBTextFieldCell alloc] initTextCell:promptText] autorelease]; + [textCell setAlignment:NSTextAlignmentCenter]; + [textCell setVerticalAlignment:kIMBBottomTextAlignment]; + [textCell setFont:promptFont]; + [textCell setTextColor:[NSColor grayColor]]; + self.draggingPromptTextField.cell = textCell; + + self.draggingPromptTextField.stringValue = promptText; + + const CGFloat MARGIN_FROM_BOTTOM = 10.0; + [scrollView addFloatingSubview:self.draggingPromptTextField forAxis:NSEventGestureAxisVertical]; + self.draggingPromptTextField.translatesAutoresizingMaskIntoConstraints = NO; + [scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.draggingPromptTextField attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; + [scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.draggingPromptTextField attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-MARGIN_FROM_BOTTOM]]; + } + + NSColor* draggingPromptColor = [NSColor grayColor]; + + CGFloat fadeHeight = MIN(viewHeight-dataHeight,MARGIN_BELOW_DATA+FADE_AREA) - MARGIN_BELOW_DATA; + CGFloat alpha = (float)fadeHeight / FADE_AREA; + + // If header has a customized color then use it but with 0.6 of its alpha value + + NSColor* appearanceTextColor = [self.imb_Appearance.sectionHeaderTextAttributes objectForKey:NSForegroundColorAttributeName]; + if (appearanceTextColor) { + CGFloat appearanceAlpha = [appearanceTextColor alphaComponent]; + draggingPromptColor = [appearanceTextColor colorWithAlphaComponent:appearanceAlpha * 0.6 * alpha]; + } else { + CGFloat whiteValue = 0.66667; + if (@available(macOS 10.14, *)) { + BOOL isDarkMode = [[[self effectiveAppearance] bestMatchFromAppearancesWithNames:@[NSAppearanceNameDarkAqua, NSAppearanceNameAqua]] isEqualToString:@"NSAppearanceNameDarkAqua"]; + whiteValue = isDarkMode ? 1.0 : 0.66667; + } + draggingPromptColor = [NSColor colorWithCalibratedWhite:whiteValue alpha:alpha]; + } + self.draggingPromptTextField.textColor = draggingPromptColor; + } +} + +- (void)registerForDraggedTypes:(NSArray *)newTypes +{ + [super registerForDraggedTypes:newTypes]; + [self updateDraggingPrompt]; +} + +- (void)unregisterDraggedTypes +{ + [super unregisterDraggedTypes]; + [self updateDraggingPrompt]; +} //---------------------------------------------------------------------------------------------------------------------- @@ -211,6 +277,11 @@ - (void) viewWillDraw [self showProgressWheels]; } +- (void)resizeSubviewsWithOldSize:(NSSize)oldSize +{ + [super resizeSubviewsWithOldSize:oldSize]; + [self updateDraggingPrompt]; +} //---------------------------------------------------------------------------------------------------------------------- @@ -329,40 +400,6 @@ - (void) drawRect:(NSRect)inRect // First draw the NSOutlineView... [super drawRect:inRect]; - - // Then draw the prompt string at the bottom if required... - - if ([[self registeredDraggedTypes] containsObject:NSFilenamesPboardType]) - { - const CGFloat MARGIN_BELOW = 20.0; - const CGFloat FADE_AREA = 20.0; - CGFloat viewHeight = self.bounds.size.height; - CGFloat dataHeight = self.rowHeight * self.numberOfRows; - - if (dataHeight+MARGIN_BELOW <= viewHeight) - { - CGFloat fadeHeight = MIN(viewHeight-dataHeight,MARGIN_BELOW+FADE_AREA) - MARGIN_BELOW; - CGFloat alpha = (float)fadeHeight / FADE_AREA; - - NSTextFieldCell* textCell = self.textCell; - [textCell setStringValue:self.draggingPrompt]; - NSColor* draggingPromptColor = nil; - - // If header has a customized color then use it but with 0.6 of its alpha value - - NSColor* appearanceTextColor = [self.imb_Appearance.sectionHeaderTextAttributes objectForKey:NSForegroundColorAttributeName]; - if (appearanceTextColor) { - CGFloat appearanceAlpha = [appearanceTextColor alphaComponent]; - draggingPromptColor = [appearanceTextColor colorWithAlphaComponent:appearanceAlpha * 0.6 * alpha]; - } else { - draggingPromptColor = [NSColor colorWithCalibratedWhite:0.66667 alpha:alpha]; - } - [textCell setTextColor:draggingPromptColor]; - - NSRect textRect = NSInsetRect([self visibleRect],12.0,8.0); - [textCell drawWithFrame:textRect inView:self]; - } - } } From e138746a15342957fe6ae63d2b6383747d0cb93f Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Wed, 24 Mar 2021 10:48:02 -0400 Subject: [PATCH 2/3] Adding and removing the prompt text view as needed seemed to complicate the nextKeyView loop for the view. Just add it once the first time it's needed and rely upon alpha settings to make it invisible when wanted. --- IMBOutlineView.m | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/IMBOutlineView.m b/IMBOutlineView.m index 9dd477ad0..ac4dc4dd6 100644 --- a/IMBOutlineView.m +++ b/IMBOutlineView.m @@ -163,11 +163,13 @@ - (void) updateDraggingPrompt CGFloat viewHeight = scrollView.contentView.bounds.size.height; CGFloat dataHeight = self.rowHeight * self.numberOfRows; BOOL shouldPrompt = acceptsFiles && (dataHeight+MARGIN_BELOW_DATA <= viewHeight); - if ((shouldPrompt == NO) && (self.draggingPromptTextField != nil)) { - [self.draggingPromptTextField removeFromSuperview]; - [self.draggingPromptTextField release]; - self.draggingPromptTextField = nil; - } else if (shouldPrompt) { +// if ((shouldPrompt == NO) && (self.draggingPromptTextField != nil)) { +// [self.draggingPromptTextField removeFromSuperview]; +// [self.draggingPromptTextField release]; +// self.draggingPromptTextField = nil; +// [[self window] recalculateKeyViewLoop]; +// } else if (shouldPrompt) { + if (shouldPrompt || (self.draggingPromptTextField != nil)) { // Create the text field as needed if (self.draggingPromptTextField == nil) { NSString* promptText = NSLocalizedStringWithDefaultValue( @@ -191,6 +193,8 @@ - (void) updateDraggingPrompt self.draggingPromptTextField.cell = textCell; self.draggingPromptTextField.stringValue = promptText; + self.draggingPromptTextField.editable = NO; + self.draggingPromptTextField.selectable = NO; const CGFloat MARGIN_FROM_BOTTOM = 10.0; [scrollView addFloatingSubview:self.draggingPromptTextField forAxis:NSEventGestureAxisVertical]; @@ -202,7 +206,7 @@ - (void) updateDraggingPrompt NSColor* draggingPromptColor = [NSColor grayColor]; CGFloat fadeHeight = MIN(viewHeight-dataHeight,MARGIN_BELOW_DATA+FADE_AREA) - MARGIN_BELOW_DATA; - CGFloat alpha = (float)fadeHeight / FADE_AREA; + CGFloat alpha = shouldPrompt ? (float)fadeHeight / FADE_AREA : 0.0; // If header has a customized color then use it but with 0.6 of its alpha value From 7e64940b65cbcc27073d961de51dd876838cd430 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Wed, 24 Mar 2021 10:50:33 -0400 Subject: [PATCH 3/3] Remove commented-out cruft. --- IMBOutlineView.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/IMBOutlineView.m b/IMBOutlineView.m index ac4dc4dd6..1d6d3c99c 100644 --- a/IMBOutlineView.m +++ b/IMBOutlineView.m @@ -163,12 +163,6 @@ - (void) updateDraggingPrompt CGFloat viewHeight = scrollView.contentView.bounds.size.height; CGFloat dataHeight = self.rowHeight * self.numberOfRows; BOOL shouldPrompt = acceptsFiles && (dataHeight+MARGIN_BELOW_DATA <= viewHeight); -// if ((shouldPrompt == NO) && (self.draggingPromptTextField != nil)) { -// [self.draggingPromptTextField removeFromSuperview]; -// [self.draggingPromptTextField release]; -// self.draggingPromptTextField = nil; -// [[self window] recalculateKeyViewLoop]; -// } else if (shouldPrompt) { if (shouldPrompt || (self.draggingPromptTextField != nil)) { // Create the text field as needed if (self.draggingPromptTextField == nil) {