The NSScroller automatically expands it's width when the user hovers over it.
However, the document view has pretty little space, and this is why the scroller should not expand.
How can I disable this behaviour?
This maybe a little too late, but something like this might help?
1) Create custom scroller for your vertical scrollbar.
2) Override -drawKnob to force draw knob the default size even when it is to be drawn 'expanded'.
-(void)drawKnob
{
NSRect knobSlot = [self rectForPart:NSScrollerKnob];
if(sFlags.isHoriz)
{
knobSlot.size.height = 9;
knobSlot.origin.y = 6;
}
else
{
knobSlot.size.width = 9;
knobSlot.origin.x = 6;
}
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:knobSlot xRadius:5 yRadius:5];
[[NSColor scrollBarColor] set];
[path fill];
}
3) Depending on if you still want the knob slot or not, override -drawKnobSlotInRect:
-(void)drawKnobSlotInRect:(NSRect)slotRect highlight:(BOOL)flag
{
NSRect newRect = slotRect;
if(sFlags.isHoriz)
newRect.origin.y = 4;
else
newRect.origin.x = 4;
[super drawKnobSlotInRect:newRect highlight:flag];
}
Related
Ok, so I'm trying to draw a dashed box that's subdivided into sections where I'm going to put content. Here's my code:
NSBezierPath *path = [NSBezierPath bezierPathWithRect:dirtyRect];
[path setLineWidth:3];
CGFloat pattern[2] = {5, 5};
[path setLineDash:pattern count:2 phase:0];
CGFloat totalHeight = header.frame.origin.y - 10;
CGFloat sectionOffset = 0;
if([game getNumPlayers] == 2) {
sectionOffset = totalHeight / 2;
} else if([game getNumPlayers] == 3) {
sectionOffset = totalHeight / 3;
} else if([game getNumPlayers] == 4) {
sectionOffset = totalHeight / 4;
}
for(int i = 0; i < [[game getPlayers] count]; i++) {
[path moveToPoint:NSMakePoint(0, totalHeight - (sectionOffset * i))];
[path lineToPoint:NSMakePoint(dirtyRect.size.width, totalHeight - (sectionOffset * i))];
}
[path stroke];
This is contained within my custom view's drawRect method, so dirtyRect is an NSRect equivalent to the bounds of the view. The variable header refers to another view in the superview, which I'm basing the location of the lines off of.
Here's a screenshot of what this code actually draws (except the label obviously):
As you can see, unless we're dealing with a very unfortunate optically illusion, which I doubt, the dividers contained in the box appear to be thicker than the outline of the box. I've explicitly set the lineWidth of the path object to be three, so I'm not sure why this is. I would be much appreciative of any suggestions that can be provided.
OK, I think the problem is that your outer box is just getting clipped by the edges of its view. You ask for a line that’s 3 points wide, so if your dirtyRect is the actual bounds of the view, then 1.5 points of the enclosing box will be outside the view, so you’ll only see 1.5 points of the edge lines.
The inner lines are showing the full 3-point thickness.
You can fix this by doing something like:
const CGFloat lineWidth = 3;
NSBezierPath *const path = [NSBezierPath bezierPathWithRect:NSInsetRect(dirtyRect, lineWidth/2, lineWidth/2)];
path.lineWidth = lineWidth;
I'm having a bit of difficulty getting a NSSplitView to behave itself.
What I have at the moment is:
NSWindow
NSView
NSSplitView
navView <NSView>
contentView <NSView>
The problem I'm having is with the splitter shifting position when I resize the window.
In the split view delegate I've already got:
-(CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200;
}
-(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200;
}
Despite this the splitter still moved when I resize the window.
if I click ont he splitter, it snaps back to 200px as it should. How can I stop this from moving?
I've tried Autolayout, which is a bit of a nightmare to use, so I've literally disabled it and manually tried to do it with no joy..
Any ideas?
I wrote the above code for Swift and extended it with the possibility to define, whether the left or the right view has to be preferred:
var preferringLeftSideOfSplitView = true
func splitView(splitView: NSSplitView, resizeSubviewsWithOldSize oldSize: NSSize) {
var dividerThickness = splitView.dividerThickness
var leftRect = splitView.subviews[0].frame
var rightRect = splitView.subviews[1].frame
// Resizing and placing the left view
splitView.subviews[0].setFrameOrigin(NSMakePoint(0, 0))
if self.preferringLeftSideOfSplitView == true {
splitView.subviews[0].setFrameSize(NSMakeSize(leftRect.width, splitView.frame.size.height))
} else {
splitView.subviews[0].setFrameSize(NSMakeSize(splitView.frame.size.width - rightRect.width - dividerThickness, splitView.frame.size.height))
}
// Resizing and placing the right view
if self.preferringLeftSideOfSplitView == true {
splitView.subviews[1].setFrameOrigin(NSMakePoint(leftRect.size.width + dividerThickness, 0))
splitView.subviews[1].setFrameSize(NSMakeSize(splitView.frame.size.width - leftRect.size.width - dividerThickness, splitView.frame.size.height))
} else {
splitView.subviews[1].setFrameOrigin(NSMakePoint(splitView.frame.size.width - rightRect.width, 0))
splitView.subviews[1].setFrameSize(NSMakeSize(rightRect.size.width, splitView.frame.size.height))
}
}
I've worked it out...
-(void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
{
CGFloat dividerThickness = [sender dividerThickness];
NSRect leftRect = [[[sender subviews] objectAtIndex:0] frame];
NSRect rightRect = [[[sender subviews] objectAtIndex:1] frame];
NSRect newFrame = [sender frame];
leftRect.size.height = newFrame.size.height;
leftRect.origin = NSMakePoint(0, 0);
rightRect.size.width = newFrame.size.width - leftRect.size.width
- dividerThickness;
rightRect.size.height = newFrame.size.height;
rightRect.origin.x = leftRect.size.width + dividerThickness;
[[[sender subviews] objectAtIndex:0] setFrame:leftRect];
[[[sender subviews] objectAtIndex:1] setFrame:rightRect];
}
- (BOOL)splitView:(NSSplitView *)aSplitView shouldAdjustSizeOfSubview:(NSView *)subview
{
return subview == rightView;
}
add this to your delegate.
if your NSSplitView would contain lefView and rightView, this keeps leftView at a fixed width, and rightView will resize, when resizing the main window.
I have a NSTextView and here's the normal size of the scroller:
And here's what happens when I hover the scroller of the textview:
However, I don't want to have this 'expand' effect. How can I remove it? I've tried to search around on how to perform this, but I couldn't find anything. I just want to have the regular scroller size (the thinner one) all the time, even if the user hovers it. Is this possible?
Thanks
I recommend subclassing the NSScroller and override – drawArrow:highlight: / – drawKnobSlotInRect:highlight: / – drawKnob methods so you have a stable scroller appearance.
P.S. Don't forget to set your new scroller class in XIB-file for the scrollers.
UPDATE
Here is the sample code:
- (void)drawKnob
{
// call the default implementation for Overlay Scrollers
if (self.scrollerStyle == NSScrollerStyleOverlay)
{
[super drawKnob];
return;
}
if (_style == NSScrollerKnobStyleLight || _style == NSScrollerKnobStyleDefault)
[[NSColor colorWithCalibratedWhite:1.0 alpha:0.8] setFill];
else [[NSColor colorWithCalibratedWhite:0 alpha:0.4] setFill];
// Note: you can specify the rect with fixed width here
NSRect knobRect = [self rectForPart:NSScrollerKnob];
// VERTICAL SCROLLER
NSInteger fullWidth = knobRect.size.width;
knobRect.size.width = round(knobRect.size.width/2);
knobRect.origin.x += (NSInteger)((fullWidth - knobRect.size.width)/2);
// draw...
NSBezierPath * thePath = [NSBezierPath bezierPath];
[thePath appendBezierPathWithRoundedRect:knobRect xRadius:4 yRadius:4];
[thePath fill];
}
//---------------------------------------------------------------
- (void)drawKnobSlotInRect:(NSRect)slotRect highlight:(BOOL)flag
{
// call the default implementation for Overlay Scrollers
// draw nothing for usual
if (self.scrollerStyle == NSScrollerStyleOverlay)
{
[super drawKnobSlotInRect:slotRect highlight:flag];
}
}
//---------------------------------------------------------------
- (void)drawArrow:(NSScrollerArrow)whichArrow highlight:(BOOL)flag
{
// call the default implementation for Overlay Scrollers
// draw nothing for usual
if (self.scrollerStyle == NSScrollerStyleOverlay)
{
[super drawArrow:whichArrow highlight:flag];
}
}
I don't know what exact style you want, but this category might help you.
#implementation NSScrollView (SetScrollStyle)
- (void) setHidingScroll
{
[self setScrollerStyle:NSScrollerStyleOverlay];
[[self verticalScroller] setControlSize: NSSmallControlSize];
[[self verticalScroller] setKnobStyle:NSScrollerKnobStyleDark];
[self setScrollerKnobStyle:NSScrollerKnobStyleDark];
[[self verticalScroller] setScrollerStyle:NSScrollerStyleOverlay];
}
and usage
[scrollView setHidingScroll];
I'm trying to learn CoreGraphics and I have encountered a strange behavior.
I draw a rectangle and in it I draw given amount of diamonds,the shapes can be drawn with different filling (empty, filled and with stripes) my draw rect function looks like this:
- (void)drawRect:(CGRect)rect
{
UIBezierPath* roundedRect = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:9.0];
//don't draw where the round corners "cut" the rectangle
[roundedRect addClip];
//set a white background
[[UIColor whiteColor] setFill];
UIRectFill(self.bounds);
//set a black frame
[[UIColor darkGrayColor] setStroke];
[roundedRect stroke];
self.shade = STRIPED;
self.color = [UIColor greenColor];
self.number = 3;
rectOffset = self.bounds.size.width / (self.number * 2);
[self drawDiamondNumberOfTimes:self.number startOrigin:self.bounds.origin];
}
drawDiamondNumberOfTimes:startOrigin: is a recursive function that calculates the rectangle in which the shape will be drawing and draw the diamond boundaries using stroke
- (void) drawDiamondNumberOfTimes:(int) p_times startOrigin:(CGPoint) p_origin
{
if( p_times > 0)
{
CGRect drawArea;
drawArea.origin = CGPointMake(p_origin.x + rectOffset-shapeSize.width/2, self.bounds.size.height/4);
drawArea.size = shapeSize;
UIBezierPath *diamond = [[UIBezierPath alloc] init];
[diamond moveToPoint:CGPointMake(drawArea.origin.x, drawArea.origin.y+ shapeSize.height/2)];
[diamond addLineToPoint:CGPointMake(drawArea.origin.x+shapeSize.width/2, drawArea.origin.y)];
[diamond addLineToPoint:CGPointMake(drawArea.origin.x+shapeSize.width, drawArea.origin.y+ shapeSize.height/2)];
[diamond addLineToPoint:CGPointMake(drawArea.origin.x+shapeSize.width/2, drawArea.origin.y+ shapeSize.height)];
[diamond closePath];
[self.color setStroke];
[diamond stroke];
[self drawShadeOfDraw:diamond atRect:drawArea];
drawArea.origin.x += rectOffset + shapeSize.width/2;
[self drawDiamondNumberOfTimes:p_times-1 startOrigin:drawArea.origin ];
}
}
drawShadeOfDraw:atRect: set the different filling, where the strange behavior occurs.
With empty and solid fills it works perfect but with stripes, if I write [p_symbol addClip] then I get always one diamond striped even if self.number is set to 2 or 3. Without [p_symbol addClip] I get the correct number of diamonds but, of course, the stripes are all over the rectangle, here is the code for drawShadeOfDraw:atRect:
- (void)drawShadeOfDraw:(UIBezierPath*)p_symbol atRect:(CGRect)p_drawArea
{
switch (self.shade)
{
case STRIPED:
{
[p_symbol addClip];
for( int y = p_drawArea.origin.y; y < p_drawArea.origin.y+ p_drawArea.size.height; y+= 6)
{
[p_symbol moveToPoint:CGPointMake(p_drawArea.origin.x, y)];
[p_symbol addLineToPoint:CGPointMake(p_drawArea.origin.x+shapeSize.width, y)];
[self.color setStroke];
[p_symbol stroke];
}
break;
}
case SOLID:
{
[self.color setFill];
[p_symbol fill];
break;
}
default:
{
break;
}
}
}
Here are some images:
What am I doing wrong?
Adding to the clip is semi-permanent. Once the clipping area has been "reduced", you can't grow it again, per se. You can only restore it to a previous state. To do that you call CGContextSaveGState() before setting up context state (including clipping), do some drawing with that state, and then call CGContextRestoreGState() afterward to restore the context state to what it was before.
So, I suggest that you bracket your two methods with calls to save and restore the context state. Use UIGraphicsGetCurrentContext() to get a reference to the current context.
I have the following NSScroller subclass that creates a scroll bar with a rounded white knob and no arrows/slot (background):
#implementation IGScrollerVertical
- (void)drawKnob
{
NSRect knobRect = [self rectForPart:NSScrollerKnob];
NSRect newRect = NSMakeRect(knobRect.origin.x, knobRect.origin.y, knobRect.size.width - 4, knobRect.size.height);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:newRect xRadius:7 yRadius:7];
[[NSColor whiteColor] set];
[path fill];
}
- (void)drawArrow:(NSScrollerArrow)arrow highlightPart:(int)flag
{
// We don't want arrows
}
- (void)drawKnobSlotInRect:(NSRect)rect highlight:(BOOL)highlight
{
// Don't want a knob background
}
#end
This all works fine, except there is a noticable lag when I use the scroller. See this video:
http://twitvid.com/70E7C
I'm confused as to what I'm doing wrong, any suggestions?
Fixed the issue, just had to fill the rect in drawKnobSlotInRect: