I am making a taskbar thumbnail preview program that mimics Windows. I have completed many parts. First, when the width of my entire frame is greater than the screen width, the entire frame will be reduced in equal proportion to ensure that the reduced width of the frame is equal to the screen width. When I delete a subview, I want to re-adapt the entire frame to ensure that the reduced width of the frame is equal to the screen width again. In fact, I have finished this part, but the problem is:
I cannot zoom in (re-adapt the scale) and move other undeleted views to the empty space at the same time. For example, the left sub-view of the deleted view moves to the right, while the right sub-view moves to the left. I can't achieve this kind of animation.
Here are some of my implementations:
@interface ThumbnailView : NSView ...
...
@end
@implementation ThumbnailView
- (instancetype)initWithFrameSize:(FrameSize&)frameSize thumbnailRectSize:(ThumbnailRectSize&)thumbnailRectSize image:(Image&)image windowElement:(AxUiElement&)element delegate:(__weak id<ViewRemovalDelegate>)delegate
{
self = [super initWithFrame:{NSMakePoint(0, 0),frameSize}];
if (self) {
[self _setVariable:frameSize thumbnailRectSize:thumbnailRectSize image:image windowElement: element];
self.delegate=delegate;
}
return self;
}
- (void)setFrameSize:(NSSize)newSize{
[super setFrameSize:newSize];
// self.widthConstraint.constant=newSize.width;
}
- (void)updateWithFrameSize:(FrameSize&)frameSize thumbnailRectSize:(ThumbnailRectSize&)thumbnailRectSize image:(Image&)image windowElement:(AxUiElement&)element
{
[self setFrame:{NSMakePoint(0, 0),frameSize}];
[self _setVariable:frameSize thumbnailRectSize:thumbnailRectSize image:image windowElement: element];
}
- (void)_setVariable:(FrameSize&)frameSize thumbnailRectSize:(ThumbnailRectSize&)thumbnailRectSize image:(Image&)image windowElement:(AxUiElement&)element{
CGImageRetain((CGImageRef&)image);
self->_imageView=[[NSImageView alloc] initWithFrame:{{(frameSize.width-thumbnailRectSize.width)/2,constant::rectSpacerPtLength},thumbnailRectSize}];
self->_imageView.image = [[NSImage alloc] initWithCGImage:(CGImageRef&)image size:image.size()];
[self addSubview:_imageView];
self.widthConstraint=[self.widthAnchor constraintEqualToConstant:frameSize.width];
self.widthConstraint.active=true;
NSPoint closeButtonPosition=constant::closeButtonRelativePtPosition;
closeButtonPosition.y=frameSize.height-closeButtonPosition.y;
CloseButtonView* closeButtonView=[[CloseButtonView alloc] initWithPoint:closeButtonPosition delegate:self];
[self addSubview:closeButtonView];
self.backgroundColor = [NSColor clearColor];
self->_windowElement=std::move(element);
self->_hasUpdated = true;
}
- (size_t)removeSelf{
return [_delegate removeView:self];
}
- (void)updateTrackingAreas {
[super updateTrackingAreas];
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
...
@end
@interface CollectionView : NSStackView ...
...
@end
@implementation CollectionView
- (instancetype)initWithDockItem:(const DockItem&)item{
self = [self init];
if (self) {
super.orientation = NSUserInterfaceLayoutOrientationHorizontal;
super.alignment = NSLayoutAttributeCenterY;
super.spacing=0;
self->dockItem = item;
_hasUpdated = false;
}
return self;
}
- (void)_initWindowThumbnails{
std::vector<FrameSize> frameSizeSeq;
std::vector<ThumbnailRectSize> thumbnailRectSizeSeq;
auto [windowElementSeq,imageSeq]=createSpecificAppWindowMapping(dockItem.applicationPid());
std::tie(_windowFrame,frameSizeSeq,thumbnailRectSizeSeq)=createWindowThumbnailsLayout(imageSeq,dockItem);
const CGFloat screenWidth=[NSScreen mainScreen].frame.size.width;
self._ratioBeyondScreen=screenWidth/_windowFrame.size.width;
if(self._ratioBeyondScreen>1){
self._ratioBeyondScreen=1;
}
[self setFrame: {{0,0},_windowFrame.size}];
const size_t length=imageSeq.size();
for(int i=0;i<length;++i){
ThumbnailView* thumbnailView=[[ThumbnailView alloc] initWithFrameSize:frameSizeSeq[i] thumbnailRectSize:thumbnailRectSizeSeq[i] image:imageSeq[i] windowElement:windowElementSeq[i] delegate:self];
[super addArrangedSubview:thumbnailView];
}
[self scaleBy: self._ratioBeyondScreen];
_multiplyRatio(self->_windowFrame.size, self._ratioBeyondScreen);
_windowFrame.origin.x=std::clamp<CGFloat>(dockItem.location().x+dockItem.size().width/2-_windowFrame.size.width/2.0,0,screenWidth-_windowFrame.size.width);
}
- (void)scaleBy:(CGFloat)factor {
// Scale the view and its subviews
[self scaleUnitSquareToSize:{factor,factor}];
}
- (size_t)removeView:(NSView*)subviewToRemove{
CGFloat subviewWidth = subviewToRemove.frame.size.width;
__block bool hasRemoved=false;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setDuration:0.15];
NSWindow* superWindow=super.window;
NSRect& superFrame = _windowFrame;
const CGFloat screenWidth=[NSScreen mainScreen].frame.size.width;
if(self._ratioBeyondScreen>=1){
self.widthConstraint.constant=0;
[[subviewToRemove animator] setFrameSize:{0,0}];
superFrame.size.width-=subviewWidth;
superFrame.origin.x=std::clamp<CGFloat>(dockItem.location().x+dockItem.size().width/2-superFrame.size.width/2.0,0,screenWidth-superFrame.size.width);
}else{
[subviewToRemove removeFromSuperview];
hasRemoved=true;
superFrame.size=[self frame].size;
superFrame.size.width-=subviewWidth;
[[self animator] setFrame:{{0,0},superFrame.size}];
CGFloat newRatioBeyondScreen=screenWidth/(superFrame.size.width);
if(newRatioBeyondScreen>1){
newRatioBeyondScreen=1;
}
_multiplyRatio(superFrame.size, newRatioBeyondScreen);
const CGFloat diffRatioBeyondScreen=newRatioBeyondScreen/self._ratioBeyondScreen;
[[self animator] scaleBy: diffRatioBeyondScreen];
superFrame.origin.x=std::clamp<CGFloat>(dockItem.location().x+dockItem.size().width/2-superFrame.size.width/2.0,0,screenWidth-superFrame.size.width);
self._ratioBeyondScreen=newRatioBeyondScreen;
}
if([[self arrangedSubviews] count]==1)
superFrame.size.width=0;
[[superWindow animator] setFrame:superFrame display:YES];
} completionHandler:^{
if(!hasRemoved)
[subviewToRemove removeFromSuperview];
}];
return 0;
}
...
@end
A snapshot:
"I cannot zoom in (re-adapt the scale) and move other undeleted views to the empty space at the same time. For example, the left sub-view of the deleted view moves to the right, while the right sub-view moves to the left. I can't achieve this kind of animation."
The result is the animation didn't happen but in a sudden.
And another thing is that I cannot arrange these sub-views horizontally without adding any constraints. I must add width constraints (widthAnchor), otherwise only the first subview will be displayed.