diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-09-04 11:11:37 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-09-04 11:11:37 +0200 |
| commit | bb5daa2e90623691a5259700b9d388d2d21f46d3 (patch) | |
| tree | f5629e81bedc6cdd35ee9a79628997c5e14ffa7c /Liquid Glass Bezel | |
| parent | 3dfeb86c5c3b72acc59919c7a198fcf76a54c35c (diff) | |
Rename to Liquid Glass Bezel, add preferences
Diffstat (limited to 'Liquid Glass Bezel')
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelInterface.h | 9 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelInterface.m | 103 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelInterface.xib | 102 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.h | 6 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.m | 10 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelPrefs.h | 10 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelPrefs.m | 13 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBLiquidGlassBezelPrefs.xib | 56 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBObjectCell.h | 4 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BBObjectCell.m | 211 | ||||
| -rw-r--r-- | Liquid Glass Bezel/BackgroundView.swift | 88 | ||||
| -rw-r--r-- | Liquid Glass Bezel/LiquidGlassBezel-Bridging-Header.h | 2 | ||||
| -rw-r--r-- | Liquid Glass Bezel/LiquidGlassBezelCollectingSearchObjectView.swift | 38 | ||||
| -rw-r--r-- | Liquid Glass Bezel/LiquidGlassBezelObjectCell.swift | 164 | ||||
| -rw-r--r-- | Liquid Glass Bezel/LiquidGlassBezelSearchObjectView.swift | 6 | ||||
| -rw-r--r-- | Liquid Glass Bezel/en.lproj/InfoPlist.strings | 2 |
16 files changed, 824 insertions, 0 deletions
diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelInterface.h b/Liquid Glass Bezel/BBLiquidGlassBezelInterface.h new file mode 100644 index 0000000..d4671fd --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelInterface.h @@ -0,0 +1,9 @@ +@interface BBLiquidGlassBezelInterface : QSResizingInterfaceController { + NSRect standardRect; +} + +@end + +@interface NSWindow (QSBCInterfaceController) +- (NSTimeInterval)animationResizeTime:(NSRect)newWindowFrame; +@end diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelInterface.m b/Liquid Glass Bezel/BBLiquidGlassBezelInterface.m new file mode 100644 index 0000000..335eacd --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelInterface.m @@ -0,0 +1,103 @@ +#import "BBLiquidGlassBezelInterface.h" + +@interface BBLiquidGlassBezelInterface () { + CGRect initialRect; +} +@end + +@implementation BBLiquidGlassBezelInterface + +- (id)init { + return [self initWithWindowNibName:@"BBLiquidGlassBezelInterface"]; +} + +- (void)windowDidLoad { + initialRect = centerRectInRect([[self window] frame], [[NSScreen mainScreen] frame]); + + [super windowDidLoad]; + + QSWindow *window = (QSWindow *)[self window]; + [window setLevel:NSPopUpMenuWindowLevel]; + [window setBackgroundColor:[NSColor clearColor]]; + [window setOpaque:NO]; + + [window setHideOffset:NSMakePoint(0, 0)]; + [window setShowOffset:NSMakePoint(0, 0)]; + + [window setMovableByWindowBackground:NO]; + [window setFastShow:YES]; + [window setHasShadow:YES]; + + [window setFrame:standardRect display:YES]; + + NSArray *theControls = [NSArray arrayWithObjects:dSelector, aSelector, iSelector, nil]; + for(QSSearchObjectView *theControl in theControls) { + QSObjectCell *theCell = [theControl cell]; + + [theControl setPreferredEdge:NSMinYEdge]; + [(QSWindow *)[(theControl)->resultController window] setHideOffset:NSMakePoint(0, NSMinY([iSelector frame]))]; + [(QSWindow *)[(theControl)->resultController window] setShowOffset:NSMakePoint(0, NSMinY([dSelector frame]))]; + + [theCell setBackgroundColor:[NSColor clearColor]]; + [theCell setFont:[NSFont systemFontOfSize:11 weight:NSFontWeightLight]]; + [theCell setCellRadiusFactor:32]; + + [theCell setHighlightColor:[NSColor colorWithRed:1 green:1 blue:1 alpha:.15]]; + [theCell setTextColor:[NSColor textColor]]; + [theCell setShowDetails:NO]; + [theCell setState:NSControlStateValueOn]; + [theCell setIconSize:QSSize128]; + [theCell setImagePosition:NSImageAbove]; + } + + [self contractWindow:nil]; +} + +- (NSSize) maxIconSize { + return QSSize32; +} + +- (void)showMainWindow:(id)sender { + [[self window] setFrame:[self rectForState:[self expanded]] display:YES]; + if ([[self window] isVisible]) [[self window] pulse:self]; + [super showMainWindow:sender]; + // Does this need to be here? + [[[self window] contentView] setNeedsDisplay:YES]; +} + +- (void)expandWindow:(id)sender { + if (![self expanded]) + [[self window] setFrame:[self rectForState:YES] display:YES animate:YES]; + [super expandWindow:sender]; +} + +- (void)contractWindow:(id)sender { + if ([self expanded]) + [[self window] setFrame:[self rectForState:NO] display:YES animate:YES]; + [super contractWindow:sender]; +} + +- (NSRect)rectForState:(BOOL)shouldExpand { + NSRect newRect = initialRect; + NSRect screenRect = [[NSScreen mainScreen] frame]; + + if (!shouldExpand) { + newRect.size.width -= 192; + } + + return NSOffsetRect(centerRectInRect(newRect, screenRect), 0, (NSHeight(screenRect) / 5)); +} + +- (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect { + return NSOffsetRect(NSInsetRect(rect, 8, 0), 0, 0); +} + +- (NSTimeInterval)animationResizeTime:(NSRect)newWindowFrame { + return 0.01; +} + +- (IBAction)customize:(id)sender{ + [[NSClassFromString(@"QSPreferencesController") sharedInstance] showPaneWithIdentifier:@"BBLiquidGlassBezelPrefs"]; +} + +@end diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelInterface.xib b/Liquid Glass Bezel/BBLiquidGlassBezelInterface.xib new file mode 100644 index 0000000..db6f979 --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelInterface.xib @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="BBLiquidGlassBezelInterface"> + <connections> + <outlet property="aSelector" destination="69" id="79"/> + <outlet property="dSelector" destination="68" id="75"/> + <outlet property="iSelector" destination="70" id="80"/> + <outlet property="menuButton" destination="209" id="210"/> + <outlet property="progressIndicator" destination="192" id="211"/> + <outlet property="window" destination="9" id="78"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <window title="Bezel Interface" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="9" userLabel="mainWindow" customClass="QSBorderlessWindow"> + <windowStyleMask key="styleMask" titled="YES" closable="YES" nonactivatingPanel="YES"/> + <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> + <rect key="contentRect" x="530" y="653" width="608" height="200"/> + <rect key="screenRect" x="0.0" y="0.0" width="1512" height="948"/> + <value key="minSize" type="size" width="440" height="200"/> + <view key="contentView" id="6" customClass="BackgroundView" customModule="Liquid_Glass_Bezel" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="608" height="200"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <customView translatesAutoresizingMaskIntoConstraints="NO" id="68" customClass="LiquidGlassBezelCollectingSearchObjectView" customModule="Liquid_Glass_Bezel" customModuleProvider="target"> + <rect key="frame" x="8" y="8" width="184" height="184"/> + <constraints> + <constraint firstAttribute="height" constant="184" id="SwQ-fC-3aw"/> + <constraint firstAttribute="width" constant="184" id="Zol-hr-3Bk"/> + </constraints> + <connections> + <outlet property="nextKeyView" destination="69" id="105"/> + </connections> + </customView> + <customView translatesAutoresizingMaskIntoConstraints="NO" id="69" customClass="LiquidGlassBezelSearchObjectView" customModule="Liquid_Glass_Bezel" customModuleProvider="target"> + <rect key="frame" x="200" y="8" width="184" height="184"/> + <constraints> + <constraint firstAttribute="width" constant="184" id="O3w-M3-b05"/> + <constraint firstAttribute="height" constant="184" id="XPL-72-l7r"/> + </constraints> + <connections> + <outlet property="nextKeyView" destination="70" id="106"/> + </connections> + </customView> + <customView translatesAutoresizingMaskIntoConstraints="NO" id="70" customClass="LiquidGlassBezelCollectingSearchObjectView" customModule="Liquid_Glass_Bezel" customModuleProvider="target"> + <rect key="frame" x="392" y="8" width="184" height="184"/> + <constraints> + <constraint firstAttribute="width" constant="184" id="Jbn-VW-sSJ"/> + <constraint firstAttribute="height" constant="184" id="X3X-S6-KrY"/> + </constraints> + <connections> + <outlet property="nextKeyView" destination="68" id="104"/> + </connections> + </customView> + <progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="192"> + <rect key="frame" x="584" y="8" width="16" height="16"/> + <constraints> + <constraint firstAttribute="width" constant="16" id="Py4-hV-7HG"/> + </constraints> + </progressIndicator> + <button translatesAutoresizingMaskIntoConstraints="NO" id="209" customClass="QSMenuButton"> + <rect key="frame" x="584" y="173.5" width="16" height="21"/> + <buttonCell key="cell" type="bevel" bezelStyle="rounded" image="arrowtriangle.down.square.fill" catalog="system" imagePosition="only" alignment="center" inset="2" id="216"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <color key="contentTintColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/> + <constraints> + <constraint firstAttribute="height" constant="16" id="cvg-xh-bvD"/> + <constraint firstAttribute="width" constant="16" id="w12-Bf-x2f"/> + </constraints> + </button> + </subviews> + <constraints> + <constraint firstItem="209" firstAttribute="top" secondItem="6" secondAttribute="top" constant="8" id="1uj-pU-uJB"/> + <constraint firstItem="70" firstAttribute="leading" secondItem="69" secondAttribute="trailing" constant="8" id="3iK-bw-ky1"/> + <constraint firstAttribute="bottom" secondItem="192" secondAttribute="bottom" constant="8" id="ASY-5E-0bJ"/> + <constraint firstAttribute="trailing" secondItem="192" secondAttribute="trailing" constant="8" id="S36-iH-Apn"/> + <constraint firstItem="69" firstAttribute="leading" secondItem="68" secondAttribute="trailing" constant="8" id="VOZ-kp-hVQ"/> + <constraint firstAttribute="trailing" secondItem="209" secondAttribute="trailing" constant="8" id="ZBS-80-x88"/> + <constraint firstItem="69" firstAttribute="top" secondItem="6" secondAttribute="top" constant="8" id="Zec-BE-M9X"/> + <constraint firstItem="70" firstAttribute="top" secondItem="6" secondAttribute="top" constant="8" id="jQN-Vd-Qm0"/> + <constraint firstItem="68" firstAttribute="leading" secondItem="6" secondAttribute="leading" constant="8" id="m72-ls-RGc"/> + <constraint firstItem="68" firstAttribute="top" secondItem="6" secondAttribute="top" constant="8" id="msf-m2-n1U"/> + </constraints> + </view> + <connections> + <outlet property="delegate" destination="-2" id="81"/> + <outlet property="initialFirstResponder" destination="68" id="111"/> + </connections> + <point key="canvasLocation" x="76" y="-42"/> + </window> + </objects> + <resources> + <image name="arrowtriangle.down.square.fill" catalog="system" width="15" height="14"/> + </resources> +</document> diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.h b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.h new file mode 100644 index 0000000..36c5177 --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.h @@ -0,0 +1,6 @@ +#import <QSInterface/QSInterface.h> +NS_ASSUME_NONNULL_BEGIN +@interface BBLiquidGlassBezelPrefs : QSPreferencePane { +} +@end +NS_ASSUME_NONNULL_END
\ No newline at end of file diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.m b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.m new file mode 100644 index 0000000..4e57c7a --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs 2.m @@ -0,0 +1,10 @@ +// +// BBLiquidGlassBezelPrefs.h +// Liquid Glass Bezel +// +// Created by Ruben Beltran del Rio on 2025-09-04. +// Copyright © 2025 BRNBW. All rights reserved. +// +#import "BBLiquidGlassBezelPrefs.h" +@implementation BBLiquidGlassBezelPrefs +@end
\ No newline at end of file diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.h b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.h new file mode 100644 index 0000000..2ce6bb6 --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.h @@ -0,0 +1,10 @@ +#import <QSInterface/QSInterface.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface BBLiquidGlassBezelPrefs : QSPreferencePane { + +} +@end + +NS_ASSUME_NONNULL_END diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.m b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.m new file mode 100644 index 0000000..1deb432 --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.m @@ -0,0 +1,13 @@ +// +// BBLiquidGlassBezelPrefs.h +// Liquid Glass Bezel +// +// Created by Ruben Beltran del Rio on 2025-09-04. +// Copyright © 2025 BRNBW. All rights reserved. +// + +#import "BBLiquidGlassBezelPrefs.h" + +@implementation BBLiquidGlassBezelPrefs + +@end diff --git a/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.xib b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.xib new file mode 100644 index 0000000..41e51fd --- /dev/null +++ b/Liquid Glass Bezel/BBLiquidGlassBezelPrefs.xib @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="BBLiquidGlassBezelPrefs"> + <connections> + <outlet property="_window" destination="QvC-M9-y7g" id="cN0-9e-T8O"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" deferred="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g"> + <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/> + <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> + <rect key="contentRect" x="196" y="240" width="389" height="273"/> + <rect key="screenRect" x="0.0" y="0.0" width="1512" height="948"/> + <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> + <rect key="frame" x="0.0" y="0.0" width="389" height="273"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="m0T-dG-aXE"> + <rect key="frame" x="12" y="237" width="125" height="16"/> + <autoresizingMask key="autoresizingMask"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="Choose Glass Style:" id="bXE-zS-DgD"> + <font key="font" usesAppearanceFont="YES"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5P5-iQ-pZN"> + <rect key="frame" x="144" y="231" width="120" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <popUpButtonCell key="cell" type="push" title="Regular" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="bjs-2K-21R" id="aqc-Gf-EiA"> + <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="message"/> + <menu key="menu" id="6kp-fp-sI3"> + <items> + <menuItem title="Regular" state="on" id="bjs-2K-21R"/> + <menuItem title="Clear" tag="1" id="iVE-75-QeO"/> + </items> + </menu> + </popUpButtonCell> + <connections> + <binding destination="od0-QH-X0x" name="selectedTag" keyPath="values.QSLiquidGlassBezelGlassStyle" id="8w3-oM-wU3"/> + </connections> + </popUpButton> + </subviews> + </view> + <point key="canvasLocation" x="94.5" y="148.5"/> + </window> + <userDefaultsController representsSharedInstance="YES" id="od0-QH-X0x"/> + </objects> +</document> diff --git a/Liquid Glass Bezel/BBObjectCell.h b/Liquid Glass Bezel/BBObjectCell.h new file mode 100644 index 0000000..3fa67c6 --- /dev/null +++ b/Liquid Glass Bezel/BBObjectCell.h @@ -0,0 +1,4 @@ +#import <QSInterface/QSInterface.h> + +@interface BBObjectCell : QSObjectCell +@end diff --git a/Liquid Glass Bezel/BBObjectCell.m b/Liquid Glass Bezel/BBObjectCell.m new file mode 100644 index 0000000..7bf1b3c --- /dev/null +++ b/Liquid Glass Bezel/BBObjectCell.m @@ -0,0 +1,211 @@ +#import "BBObjectCell.h" + +@implementation BBObjectCell + +- (NSCellImagePosition)preferredImagePosition { + return NSImageAbove; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + BOOL isFirstResponder = [[controlView window] firstResponder] == controlView && ![controlView isKindOfClass:[NSTableView class]]; + BOOL dropTarget = ([self isHighlighted] && ([self highlightsBy] & NSChangeBackgroundCellMask) && ![self isBezeled]); + + NSColor *fillColor; + NSColor *strokeColor = [NSColor clearColor]; + + if (isFirstResponder) { + fillColor = [NSColor selectedTextBackgroundColor]; + } else { + fillColor = [NSColor clearColor]; + } + + if (dropTarget) { + fillColor = [NSColor controlAccentColor]; + } + + [fillColor setFill]; + [strokeColor setStroke]; + + NSBezierPath *roundRect = [NSBezierPath bezierPath]; + [roundRect appendBezierPathWithRoundedRectangle:cellFrame withRadius:NSHeight(cellFrame)/cellRadiusFactor]; + [roundRect fill]; + + [self drawInteriorWithFrame:[self drawingRectForBounds:cellFrame] inView:controlView]; +} + +- (NSRect)titleRectForBounds:(NSRect)theRect +{ + NSRect rect = theRect; + rect = NSOffsetRect(rect, 0, -4); + return [super titleRectForBounds: rect]; +} + +- (void)drawTextForObject:(QSObject *)drawObject withFrame:(NSRect)cellFrame inView:(NSView *)controlView { + if ([self imagePosition] == NSImageOnly) return; + + NSString *abbrString = nil; + if ([controlView respondsToSelector:@selector(matchedString)]) + abbrString = [(QSSearchObjectView *)controlView matchedString]; + + NSString *nameString = nil; + NSIndexSet *hitMask = nil; + + id ranker = [drawObject ranker]; + if (ranker && abbrString) + nameString = [ranker matchedStringForAbbreviation:abbrString hitmask:&hitMask inContext:nil]; + + if (!nameString) + nameString = [drawObject displayName]; + + BOOL rankedStringIsName = [nameString isEqualToString:[drawObject displayName]] || nameString == nil; + if (!nameString) { + // fall back to the identifier if no reasonable name can be found + nameString = [drawObject identifier]; + } + if (!nameString) { + // Couldn't find anything sensible to use for the name, fallback to avoid a crash + nameString = @"Unknown"; + } + + BOOL useAlternateColor = [controlView isKindOfClass:[NSTableView class]] && [(NSTableView *)controlView isRowSelected:[(NSTableView *)controlView rowAtPoint:cellFrame.origin]]; + NSColor *mainColor = (textColor ? textColor : (useAlternateColor ? [NSColor alternateSelectedControlTextColor] : [NSColor controlTextColor])); + NSColor *fadedColor = [mainColor colorWithAlphaComponent:0.50]; + NSRect textDrawRect = [self titleRectForBounds:cellFrame]; + + NSMutableAttributedString *titleString = [[[NSMutableAttributedString alloc] initWithString:nameString] autorelease]; + [titleString setAttributes:rankedStringIsName ? nameAttributes : detailsAttributes range:NSMakeRange(0, [titleString length])]; + + // Bring out the matched letters + if (abbrString && ![abbrString hasPrefix:@"QSActionMnemonic"]) { + [titleString addAttribute:NSForegroundColorAttributeName value:rankedStringIsName ? fadedColor : [fadedColor colorWithAlphaComponent:0.8] range:NSMakeRange(0, [titleString length])]; + + NSUInteger i = 0; + NSUInteger j = 0; + NSUInteger hits[[titleString length]]; + NSUInteger count = [hitMask getIndexes:(NSUInteger *)&hits maxCount:[titleString length] inIndexRange:nil]; + NSDictionary *attributes = @{ + NSForegroundColorAttributeName: rankedStringIsName ? mainColor : fadedColor + }; + for(i = 0; i<count; i += j) { + for (j = 1; i+j<count && hits[i+j-1] +1 == hits[i+j]; j++); + [titleString addAttributes:attributes range:NSMakeRange(hits[i], j)]; + } + } else { + [titleString addAttribute:NSBaselineOffsetAttributeName value:[NSNumber numberWithDouble:-1.0] range:NSMakeRange(0, [titleString length])]; + } +// +// // Ranked string and nameString aren't the same. Show 'nameString ⟷ rankedString' in the UI +// if (!rankedStringIsName && [drawObject displayName].length) { +// [titleString addAttribute:NSFontAttributeName value:detailsFont range:NSMakeRange(0,[titleString length])]; +// NSMutableAttributedString *attributedNameString = [[NSMutableAttributedString alloc] initWithString:[drawObject displayName]]; +// [attributedNameString setAttributes:nameAttributes range:NSMakeRange(0, [[drawObject displayName] length])]; +// +// [attributedNameString appendAttributedString:[[[NSAttributedString alloc] initWithString:@" ⟷ " attributes:rankedNameAttributes] autorelease]]; +// // the replaceCharacters... method inserts the new string into the receiver at the start of the work (range.location and range.length are 0) +// [titleString replaceCharactersInRange:NSMakeRange(0,0) withAttributedString:attributedNameString]; +// [attributedNameString release]; +// } + + if (showDetails) { + NSString *detailsString = [drawObject details]; + + NSRange returnRange = [detailsString rangeOfString:@"\n"]; + if (returnRange.location != NSNotFound) { + detailsString = [detailsString substringToIndex:returnRange.location]; + } + + detailsAttributes = [detailsAttributes mutableCopy]; + [detailsAttributes setValue:[NSColor grayColor] forKey:NSForegroundColorAttributeName]; + + if (detailsString && detailsString.length && ![detailsString isEqualToString:nameString]) { + [titleString appendAttributedString:[[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\n%@",detailsString] attributes:detailsAttributes] autorelease]]; + } + } + + NSRect centerRect = rectFromSize([titleString size]); + centerRect.size.width = NSWidth(textDrawRect); + centerRect.size.height = MIN(NSHeight(textDrawRect), centerRect.size.height); + [titleString drawInRect:centerRectInRect(centerRect, textDrawRect)]; +} + +- (void)drawSearchPlaceholderWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + NSString *defaultText = NSLocalizedStringWithDefaultValue(@"Type to search", nil, [NSBundle mainBundle], @"Type to search", @"Hint that appears in the first pane of the QS interface when it's empty."); + NSSize textSize = [defaultText sizeWithAttributes:nameAttributes]; + NSRect textRect = centerRectInRect(rectFromSize(textSize), cellFrame); + BOOL isFirstResponder = [[controlView window] firstResponder] == controlView && ![controlView isKindOfClass:[NSTableView class]]; + + if (isFirstResponder && [controlView isKindOfClass:[QSSearchObjectView class]]) { + NSImage *find = [NSImage imageWithSystemSymbolName:@"magnifyingglass.circle.fill" accessibilityDescription:nil]; + + + [find setSize:QSSize16]; + NSRect findImageRect = expelRectFromRectOnEdge(centerRectInRect(rectFromSize([find size]), cellFrame), textRect, NSRectEdgeMinX, -2); + + + NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; + [graphicsContext saveGraphicsState]; + CGContextRef context = [graphicsContext CGContext]; + CGContextBeginTransparencyLayerWithRect(context, findImageRect, nil); + CGContextSetBlendMode(context, kCGBlendModeNormal); + [find drawInRect:findImageRect fromRect:rectFromSize([find size]) operation:NSCompositingOperationSourceOver fraction:1]; + CGContextSetBlendMode(context, kCGBlendModeSourceIn); + CGContextSetFillColorWithColor(context, [[NSColor textColor] CGColor]); + CGContextFillRect(context, findImageRect); + CGContextEndTransparencyLayer(context); + + [defaultText drawInRect:textRect withAttributes:nameAttributes]; + } +} + +- (void)drawIconForObject:(QSObject *)object withFrame:(NSRect)cellFrame inView:(NSView *)controlView { + NSImage *image = [object icon]; + NSString *iconName = nil; + NSImageSymbolConfiguration *iconConfig = nil; + if ([[image name] isEqualToString: @"defaultAction"]) { + iconName = @"gearshape.circle"; + } + else if ([[image name] isEqualToString: @"Find"]) { + iconName = @"magnifyingglass"; + } + else if ([[image name] isEqualToString: @"Object"]) { + iconName = @"cube.transparent"; + iconConfig = [NSImageSymbolConfiguration configurationWithHierarchicalColor:[NSColor controlAccentColor]]; + } + else if ([[image name] isEqualToString: @"ContactAddress"]) { + iconName = @"mappin.circle"; + } + else if ([[image name] isEqualToString: @"ContactEmail"]) { + iconName = @"envelope.circle"; + } + else if ([[image name] isEqualToString: @"ContactPhone"]) { + iconName = @"phone.circle"; + } + else if ([[image name] isEqualToString: @"DefaultBookmarkIcon"]) { + iconName = @"link.circle"; + } + else if ([[image name] isEqualToString: @"Catalog"]) { + iconName = @"sparkles.rectangle.stack"; + } + else if ([[image name] isEqualToString: @"Triggers"]) { + iconName = @"bolt.fill"; + } + + + if (iconName != nil) { + NSImage *newIcon = [NSImage imageWithSystemSymbolName:iconName accessibilityDescription:[image name]]; + + if (iconConfig == nil) { + NSArray *colors = [NSArray arrayWithObjects:[NSColor textColor], [NSColor controlAccentColor], nil]; + iconConfig = [NSImageSymbolConfiguration configurationWithPaletteColors:colors]; + } + + QSObject *newObject = [QSObject objectWithName:[object name]]; + [newObject setIcon:[newIcon imageWithSymbolConfiguration:iconConfig]]; + [newObject setIconLoaded:[object iconLoaded]]; + return [super drawIconForObject: newObject withFrame:cellFrame inView:controlView]; + } + + [super drawIconForObject: object withFrame:cellFrame inView:controlView]; +} + +@end diff --git a/Liquid Glass Bezel/BackgroundView.swift b/Liquid Glass Bezel/BackgroundView.swift new file mode 100644 index 0000000..e5f4c86 --- /dev/null +++ b/Liquid Glass Bezel/BackgroundView.swift @@ -0,0 +1,88 @@ +import Foundation +import Cocoa + + +class BackgroundView: QSBezelBackgroundView { + + private var glassView: NSGlassEffectView? + + override func awakeFromNib() { + super.awakeFromNib() + configureParentView() + setupGlassEffect() + } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + configureParentView() + setupGlassEffect() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configureParentView() + setupGlassEffect() + } + + private func configureParentView() { + // Make the parent background much more transparent so glass effect shows through + setColor(NSColor.clear) // Try completely transparent first + setIsGlass(NSNumber(value: false)) // Disable the parent's glass rendering + + // Alternative: if clear doesn't work, try a very transparent background + // setColor(NSColor.black.withAlphaComponent(0.1)) + } + + private func setupGlassEffect() { + let glassView = NSGlassEffectView(frame: bounds) + glassView.autoresizingMask = [.width, .height] + glassView.cornerRadius = 12.0 + glassView.style = .clear + + // Create a content view for the glass effect + let contentView = NSView(frame: glassView.bounds) + contentView.autoresizingMask = [.width, .height] + glassView.contentView = contentView + + // Add subtle tint based on appearance + updateGlassTint() + + // Insert the glass view at the bottom of the view hierarchy + addSubview(glassView, positioned: .below, relativeTo: nil) + self.glassView = glassView + } + + private func updateGlassTint() { + guard let glassView = glassView else { return } + + let darkMode = effectiveAppearance.name == .darkAqua + if darkMode { + // Very subtle tint for dark mode to maintain translucency + glassView.tintColor = NSColor.black.withAlphaComponent(0.05) + } else { + // Even lighter tint for light mode + glassView.tintColor = NSColor.white.withAlphaComponent(0.02) + } + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + + // Ensure the window is configured for transparency + if let window = self.window { + window.backgroundColor = NSColor.clear + window.isOpaque = false + } + } + + override func draw(_ rect: NSRect) { + // Don't call super.draw(rect) to avoid the parent's opaque background + // The glass effect will handle the visual appearance + return + } + + override func viewDidChangeEffectiveAppearance() { + super.viewDidChangeEffectiveAppearance() + updateGlassTint() + } +} diff --git a/Liquid Glass Bezel/LiquidGlassBezel-Bridging-Header.h b/Liquid Glass Bezel/LiquidGlassBezel-Bridging-Header.h new file mode 100644 index 0000000..f216cf4 --- /dev/null +++ b/Liquid Glass Bezel/LiquidGlassBezel-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "Quicksilver.pch" +#import "BBObjectCell.h" diff --git a/Liquid Glass Bezel/LiquidGlassBezelCollectingSearchObjectView.swift b/Liquid Glass Bezel/LiquidGlassBezelCollectingSearchObjectView.swift new file mode 100644 index 0000000..a7c8a90 --- /dev/null +++ b/Liquid Glass Bezel/LiquidGlassBezelCollectingSearchObjectView.swift @@ -0,0 +1,38 @@ +class LiquidGlassBezelCollectingSearchObjectView: QSCollectingSearchObjectView { + override static var cellClass : AnyClass? { + get { return BBObjectCell.self } + set { super.cellClass = newValue } + } + + override func draw(_ rect: NSRect) { + var frame = frame + + // These properties aren't available to Swift, so they need to be accessed in this way. + let collection = (value(forKey: "collection") as! NSMutableArray) + let collecting = (value(forKey: "collecting") as! Bool) + + let count = collection.count + if self.currentEditor() == nil && count != 0 { + frame.origin = NSZeroPoint + cell?.draw(withFrame: frame, in: self) + let iconSize = collectionSpace() != 0 ? collectionSpace() : 16 + let opacity = collecting ? 1.0 : 0.75 + let totalWidth = iconSize + 2 + for i in 0..<count { + let object = collection.object(at: i) as! QSObject + var icon = object.icon() + let size = NSMakeSize(16, 16) + icon?.size = size + let inRect = NSMakeRect( + frame.size.width - totalWidth * CGFloat(count - i), + frame.origin.y + 2, + iconSize, + iconSize + ) + icon?.draw(in: inRect, from: rectFromSize(size), operation: .sourceOver, fraction: opacity) + } + } else { + super.draw(rect) + } + } +} diff --git a/Liquid Glass Bezel/LiquidGlassBezelObjectCell.swift b/Liquid Glass Bezel/LiquidGlassBezelObjectCell.swift new file mode 100644 index 0000000..dfffce6 --- /dev/null +++ b/Liquid Glass Bezel/LiquidGlassBezelObjectCell.swift @@ -0,0 +1,164 @@ +/* +import Foundation + +class LiquidGlassBezelObjectCell: QSObjectCell { + override func preferredImagePosition() -> NSControl.ImagePosition { + return .imageAbove + } + + override func draw(withFrame cellFrame: NSRect, in controlView: NSView?) { + if let controlView { + + let isFirstResponder = controlView.window?.firstResponder == controlView && !(controlView is NSTableView) + let dropTarget = isHighlighted && ((highlightsBy.rawValue & NSCell.StyleMask.changeBackgroundCellMask.rawValue) != 0) && !isBezeled + + var fillColor: NSColor? + let strokeColor = NSColor.clear + + if isFirstResponder { + fillColor = self.highlightColor() + } else { + fillColor = self.backgroundColor + } + + if dropTarget { + fillColor = NSColor(red: 0.77, green: 0.91, blue: 0.96, alpha: 1) + } + + fillColor?.setFill() + strokeColor.setStroke() + + let roundRect = NSBezierPath() + roundRect.append(withRoundedRectangle: cellFrame, withRadius: cellRadiusFactor()) + roundRect.fill() + + drawInterior(withFrame: drawingRect(forBounds: cellFrame), in: controlView) + } + } + + override func titleRect(forBounds _rect: NSRect) -> NSRect { + var rect = _rect + rect = NSOffsetRect(rect, 0, -4) + return super.titleRect(forBounds: rect) + } + + override func drawText(for drawObject: QSObject!, withFrame cellFrame: NSRect, in controlView: NSView!) { + if imagePosition == .imageOnly { + return + } + + var abbrString: String? = nil + + if controlView.responds(to: #selector(QSSearchObjectView.matchedString)) { + abbrString = (controlView as! QSSearchObjectView).matchedString() + } + + var nameString: String? = nil + var hitMask: NSIndexSet? = nil + + let ranker = drawObject.ranker() + if ranker != nil && abbrString != nil { + nameString = ranker?.matchedString(forAbbreviation: abbrString, hitmask: &hitMask, inContext: nil) + } + + if (nameString == nil) { + nameString = drawObject.displayName() + } + + let rankedStringIsName = nameString == drawObject.displayName() || nameString == nil + + if nameString == nil { + nameString = drawObject.identifier() ?? "Unknown" + } + + let useAlternateColor = controlView is NSTableView && (controlView as! NSTableView).isRowSelected((controlView as! NSTableView).row(at: cellFrame.origin)) + let mainColor = textColor() ?? (useAlternateColor ? .alternateSelectedControlTextColor : .controlTextColor) + let fadedColor = mainColor.withAlphaComponent(0.50) + let textDrawRect = titleRect(forBounds: cellFrame) + + + let titleString = NSMutableAttributedString(string: nameString!) + let nameAttributes = (value(forKey: "nameAttributes") as? [NSAttributedString.Key : Any]) + let detailsAttributes = (value(forKey: "detailsAttributes") as? [NSAttributedString.Key : Any]) + titleString.setAttributes(rankedStringIsName ? nameAttributes : detailsAttributes, range: NSMakeRange(0, titleString.length)) + + if abbrString != nil && abbrString!.hasPrefix("QSActionMnemonic") { + titleString.addAttribute(.foregroundColor, value: rankedStringIsName ? fadedColor : fadedColor.withAlphaComponent(0.8), range: NSMakeRange(0, titleString.length)) + + var hits: Int = 0 + let count = hitMask?.getIndexes(&hits, maxCount: titleString.length, inIndexRange: nil) + for i in 0..<(count ?? 0) { + for j in 1.. { + } + } + } +} + + NSUInteger i = 0; + NSUInteger j = 0; + NSUInteger hits[[titleString length]]; + NSUInteger count = [hitMask getIndexes:(NSUInteger *)&hits maxCount:[titleString length] inIndexRange:nil]; + NSDictionary *attributes = @{ + NSForegroundColorAttributeName: rankedStringIsName ? mainColor : fadedColor + }; + for(i = 0; i<count; i += j) { + for (j = 1; i+j<count && hits[i+j-1] +1 == hits[i+j]; j++); + [titleString addAttributes:attributes range:NSMakeRange(hits[i], j)]; + } + } else { + [titleString addAttribute:NSBaselineOffsetAttributeName value:[NSNumber numberWithDouble:-1.0] range:NSMakeRange(0, [titleString length])]; + } + + if (showDetails) { + NSString *detailsString = [drawObject details]; + + NSRange returnRange = [detailsString rangeOfString:@"\n"]; + if (returnRange.location != NSNotFound) { + detailsString = [detailsString substringToIndex:returnRange.location]; + } + + detailsAttributes = [detailsAttributes mutableCopy]; + [detailsAttributes setValue:[NSColor grayColor] forKey:NSForegroundColorAttributeName]; + + if (detailsString && detailsString.length && ![detailsString isEqualToString:nameString]) { + [titleString appendAttributedString:[[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\n%@",detailsString] attributes:detailsAttributes] autorelease]]; + } + } + + NSRect centerRect = rectFromSize([titleString size]); + centerRect.size.width = NSWidth(textDrawRect); + centerRect.size.height = MIN(NSHeight(textDrawRect), centerRect.size.height); + [titleString drawInRect:centerRectInRect(centerRect, textDrawRect)]; +} + +- (void)drawSearchPlaceholderWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + NSString *defaultText = NSLocalizedStringWithDefaultValue(@"Type to search", nil, [NSBundle mainBundle], @"Type to search", @"Hint that appears in the first pane of the QS interface when it's empty."); + NSSize textSize = [defaultText sizeWithAttributes:nameAttributes]; + NSRect textRect = centerRectInRect(rectFromSize(textSize), cellFrame); + BOOL isFirstResponder = [[controlView window] firstResponder] == controlView && ![controlView isKindOfClass:[NSTableView class]]; + + if (isFirstResponder && [controlView isKindOfClass:[QSSearchObjectView class]]) { + NSImage *find = [NSImage imageWithSystemSymbolName:@"magnifyingglass.circle.fill" accessibilityDescription:nil]; + + + [find setSize:QSSize16]; + NSRect findImageRect = expelRectFromRectOnEdge(centerRectInRect(rectFromSize([find size]), cellFrame), textRect, NSRectEdgeMinX, -2); + + + NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; + [graphicsContext saveGraphicsState]; + CGContextRef context = [graphicsContext CGContext]; + CGContextBeginTransparencyLayerWithRect(context, findImageRect, nil); + CGContextSetBlendMode(context, kCGBlendModeNormal); + [find drawInRect:findImageRect fromRect:rectFromSize([find size]) operation:NSCompositingOperationSourceOver fraction:1]; + CGContextSetBlendMode(context, kCGBlendModeSourceIn); + CGContextSetFillColorWithColor(context, [[NSColor textColor] CGColor]); + CGContextFillRect(context, findImageRect); + CGContextEndTransparencyLayer(context); + + [defaultText drawInRect:textRect withAttributes:nameAttributes]; + } +} + +@end +*/ diff --git a/Liquid Glass Bezel/LiquidGlassBezelSearchObjectView.swift b/Liquid Glass Bezel/LiquidGlassBezelSearchObjectView.swift new file mode 100644 index 0000000..825b2d3 --- /dev/null +++ b/Liquid Glass Bezel/LiquidGlassBezelSearchObjectView.swift @@ -0,0 +1,6 @@ +class LiquidGlassBezelSearchObjectView: QSSearchObjectView { + override static var cellClass : AnyClass? { + get { return BBObjectCell.self } + set { super.cellClass = newValue } + } +} diff --git a/Liquid Glass Bezel/en.lproj/InfoPlist.strings b/Liquid Glass Bezel/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/Liquid Glass Bezel/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + |