How to disable the mouse hover expand effect on a NSTextView's NSScrollView scroller? - objective-c

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];

Related

Drawing issues after subclassing NSCollectionView

OK, here's what I have done:
I have an NSCollectionView
I wanted to be able to enable "selecting" items, and drawing a custom border when an items is selected
I subclassed NSCollectionViewItem (to enable selection)
I subclassed NSView for the NSCollectionViewItem view, in order to draw the border
The code
The view item
#implementation MSLibraryCollectionViewItem
- (void)setSelected:(BOOL)flag
{
[super setSelected:flag];
[(MSLibraryCollectionViewView*)[self view] setSelected:flag];
[(MSLibraryCollectionViewView*)[self view] setNeedsDisplay:YES];
}
The custom view
#implementation MSLibraryCollectionViewView
/***************************************
Initialisation
***************************************/
- (MSLibraryCollectionViewView*)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
/***************************************
Drawing
***************************************/
- (void)drawRect:(NSRect)rect
{
if ([self selected]) {
//[[NSColor redColor] setFill];
//NSRectFill(rect);
//[super drawRect:rect];
NSColor* gS = [NSColor colorWithCalibratedRed:0.06 green:0.45 blue:0.86 alpha:1.0];
NSColor* gE = [NSColor colorWithCalibratedRed:0.12 green:0.64 blue:0.94 alpha:1.0];
NSGradient* g = [[NSGradient alloc] initWithStartingColor:gE endingColor:gS];
NSColor *borderColor = [NSColor colorFromGradient:g];
NSRect frameRect = [self bounds];
if(rect.size.height < frameRect.size.height)
return;
NSRect newRect = NSMakeRect(rect.origin.x+5, rect.origin.y+5, rect.size.width-10, rect.size.height-10);
NSBezierPath *textViewSurround = [NSBezierPath bezierPathWithRoundedRect:newRect xRadius:7 yRadius:7];
[textViewSurround setLineWidth:2.0];
[borderColor set];
[textViewSurround stroke];
}
}
However, the seems to be something wrong with drawing. For example:
When resizing the Collection View's container, a weird line appears at the outer box
When an Collection View item is not 100% visible (e.g. because it's been scrolled down), the selection border doesn't appear at all (while I would expect it to draw just the visible portion).
Some Examples
What's going on?
P.S. I'm not a guru with drawing and custom views in Cocoa - so any ideas/help is more than welcome!
You switched from asking about a collection view to talking about an outline view, but I assume that was just a mental hiccup.
When an Outline View item is not 100% visible (e.g. because it's been scrolled down), the selection border doesn't appear at all
(while I would expect it to draw just the visible portion).
That's because of this code in your -drawRect:.
if(rect.size.height < frameRect.size.height)
return;
It's specifically avoiding drawing a partial selection outline.
Regarding the weird line, I doubt that has to do with your collection item view's custom drawing. Does it stop happening if you disable the custom drawing? You could experiment with using an ordinary color rather than using the third-party +colorFromGradient: code you're using.
By the way, this line:
NSRect newRect = NSMakeRect(rect.origin.x+5, rect.origin.y+5, rect.size.width-10, rect.size.height-10);
could be written more simply as:
NSRect newRect = NSInsetRect(rect, 5, 5);

Receive global TouchEvents with NSEvent

I just try to receive Touch Events globally in the Window and not only in a view.
In my code, you can see below, i will get the absolute position of the touch in the magic trackpad. As long as the cursor is inside the view (the red NSRect) it works fine, but how can i receive touches outside of this view.
I searched for solutions in many communities and the apple devcenter but found nothing.
I think the problem is this: NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil]; Isn't there a method in NSEvent that gets every touch?
Hope anybody can help me.
Here my Implementation:
#implementation MyView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setAcceptsTouchEvents:YES];
myColor = [NSColor colorWithDeviceRed:1.0 green:0 blue:0 alpha:0.5];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
NSRect bounds = [self bounds];
[myColor set];
[NSBezierPath fillRect:bounds];
}
- (void)touchesBeganWithEvent:(NSEvent *)ev {
NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil];
for (NSTouch *touch in touches) {
NSPoint fraction = touch.normalizedPosition;
NSSize whole = touch.deviceSize;
NSPoint wholeInches = {whole.width / 72.0, whole.height / 72.0};
NSPoint pos = wholeInches;
pos.x *= fraction.x;
pos.y *= fraction.y;
NSLog(#"%s: Finger is touching %g inches right and %g inches up "
#"from lower left corner of trackpad.", __func__, pos.x, pos.y);
}
}
Calling touchesMatchingPhase:inView: with nil for the last parameter (the way you are doing) will get all touches. The problem is that touchesBeganWithEvent: will simply not fire for a control that isn't in the touch area.
You can make the view the first responder, which will send all events to it first. See becomeFirstResponder and Responder object

Selected Menu Item Color not aligned?

I thinks a image is better than a thousand words.
What you see in this picture are NSBox Subclass in a NSViewCollection. I set the fill color to "Selected Menu Item Color" is Interface builder.
Why is the color like that ?
Edit : After reading this SO post, I found out I probably have to set the setPatternPhase. But how/where ?
The "Selected Menu Item Color" pattern is designed to be used on "menu-high" items, and it looks like your NSBox is taller than the pattern is.
That is, you could mess around with the pattern phase origin using code from my answer, but I don't think it will do you any good because the pattern just isn't tall enough for your NSBox.
Inspired by Smilin Brian's solution I came up with this :
-(void) drawRect: (NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if(mouseOver) {
[NSGraphicsContext saveGraphicsState];
CGFloat yOffset = NSMaxY([self convertRect:self.bounds toView:nil]);
CGFloat xOffset = NSMinX([self convertRect:self.bounds toView:nil]);
[[NSGraphicsContext currentContext] setPatternPhase:NSMakePoint(xOffset, yOffset)];
[[NSColor selectedMenuItemColor ] setFill];
NSRectFill(dirtyRect);
[NSGraphicsContext restoreGraphicsState];
}
}
For the moment it just handles mouseOver events and not selection but it's pretty much the same.
Just for the who might need that for a NSBox I used :
- (void)awakeFromNib{
NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow )
owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
and
- (void)mouseEntered:(NSEvent *)theEvent {
mouseOver = true;
[self setNeedsDisplayInRect:self.bounds];
[self needsDisplay];
}
- (void)mouseExited:(NSEvent *)theEvent {
mouseOver= false;
[self setNeedsDisplayInRect:self.bounds];
[self needsDisplay];
}

Highlighting a NSMenuItem with a custom view?

I have created a simple NSStatusBar with a NSMenu set as the menu. I have also added a few NSMenuItems to this menu, which work fine (including selectors and highlighting) but as soon as I add a custom view (setView:) no highlighting occurs.
CustomMenuItem *menuItem = [[CustomMenuItem alloc] initWithTitle:#"" action:#selector(openPreferences:) keyEquivalent:#""];
[menuItem foo];
[menuItem setTarget:self];
[statusMenu insertItem:menuItem atIndex:0];
[menuItem release];
And my foo method is:
- (void)foo {
NSView *view = [[NSView alloc] initWithFrame:CGRectMake(5, 10, 100, 20)];
[self setView:view];
}
If I remove the setView method, it will highlight.
I have searched and searched and cannot find a way of implementing/enabling this.
Edit
I implemented highlight by following the code in this question in my NSView SubClass:
An NSMenuItem's view (instance of an NSView subclass) isn't highlighting on hover
#define menuItem ([self enclosingMenuItem])
- (void) drawRect: (NSRect) rect {
BOOL isHighlighted = [menuItem isHighlighted];
if (isHighlighted) {
[[NSColor selectedMenuItemColor] set];
[NSBezierPath fillRect:rect];
} else {
[super drawRect: rect];
}
}
Here's a rather less long-winded version of the above. It's worked well for me. (backgroundColour is an ivar.)
- (void)drawRect:(NSRect)rect
{
if ([[self enclosingMenuItem] isHighlighted]) {
[[NSColor selectedMenuItemColor] set];
} else if (backgroundColour) {
[backgroundColour set];
}
NSRectFill(rect);
}
Update for 2019:
class CustomMenuItemView: NSView {
private var effectView: NSVisualEffectView
override init(frame: NSRect) {
effectView = NSVisualEffectView()
effectView.state = .active
effectView.material = .selection
effectView.isEmphasized = true
effectView.blendingMode = .behindWindow
super.init(frame: frame)
addSubview(effectView)
effectView.frame = bounds
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect) {
effectView.isHidden = !(enclosingMenuItem?.isHighlighted ?? false)
}
}
Set one of those to your menuItem.view.
(Credit belongs to Sam Soffes who helped me figure this out and sent me almost that code verbatim.)
If you're adding a view to a menu item, that view has to draw the highlight itself. You don't get that for free, I'm afraid. From the Menu Programming Topics:
A menu item with a view does not draw its title, state, font, or other standard drawing attributes, and assigns drawing responsibility entirely to the view.
Yes, as mentioned earlier you must draw it yourself. I use AppKit's NSDrawThreePartImage(…) to draw, and also include checks to use the user's control appearance (blue or graphite.) To get the images, I just took them from a screenshot (if anyone knows a better way, please add a comment.) Here's a piece of my MenuItemView's drawRect:
// draw the highlight gradient
if ([[self menuItem] isHighlighted]) {
NSInteger tint = [[NSUserDefaults standardUserDefaults] integerForKey:#"AppleAquaColorVariant"];
NSImage *image = (AppleAquaColorGraphite == tint) ? menuItemFillGray : menuItemFillBlue;
NSDrawThreePartImage(dirtyRect, nil, image, nil, NO,
NSCompositeSourceOver, 1.0, [self isFlipped]);
}
else if ([self backgroundColor]) {
[[self backgroundColor] set];
NSRectFill(dirtyRect);
}
EDIT
Should have defined these:
enum AppleAquaColorVariant {
AppleAquaColorBlue = 1,
AppleAquaColorGraphite = 6,
};
These correspond to the two appearance options in System Preferences. Also, menuItemFillGray & menuItemFillBlue are just NSImages of the standard menu item fill gradients.

Move NSView around until it hits a border

In a Cocoa-based App i'm having a canvas for drawing, inherited from NSView, as well as a rectangle, also inherited from NSView. Dragging the rectangle around inside of the canvas is no problem:
-(void)mouseDragged:(NSEvent *)theEvent {
NSPoint myOrigin = self.frame.origin;
[self setFrameOrigin:NSMakePoint(myOrigin.x + [theEvent deltaX],
myOrigin.y - [theEvent deltaY])];
}
Works like a charm. The issue i'm having now: How can i prevent the rectangle from being moved outside the canvas?
So, first of all i would like to fix this just for the left border, adapting the other edges afterwards. My first idea is: "check whether the x-origin of the rectangle is negative". But: once it is negative the rectangle can't be moved anymore around (naturally). I solved this with moving the rectangle to zero x-offset in the else-branch. This works but it's ... ugly.
So i'm little puzzled with this one, any hints? Definitely the solution is really near and easy. That easy, that i cannot figure it out (as always with easy solutions ;).
Regards
Macs
I'd suggest not using the deltaX and deltaY; try using the event's location in the superview. You'll need a reference to the subview.
// In the superview
- (void)mouseDragged:(NSEvent *)event {
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
// Could also add the width of the moving rectangle to this check
// to keep any part of it from going outside the superview
mousePoint.x = MAX(0, MIN(mousePoint.x, self.bounds.size.width));
mousePoint.y = MAX(0, MIN(mousePoint.y, self.bounds.size.height));
// position is a custom ivar that indicates the center of the object;
// you could also use frame.origin, but it looks nicer if objects are
// dragged from their centers
myMovingRectangle.position = mousePoint;
[self setNeedsDisplay:YES];
}
You'd do essentially the same bounds checking in mouseUp:.
UPDATE: You should also have a look at the View Programming Guide, which walks you through creating a draggable view: Creating a Custom View.
Sample code that should be helpful, though not strictly relevant to your original question:
In DotView.m:
- (void)drawRect:(NSRect)dirtyRect {
// Ignoring dirtyRect for simplicity
[[NSColor colorWithDeviceRed:0.85 green:0.8 blue:0.8 alpha:1] set];
NSRectFill([self bounds]);
// Dot is the custom shape class that can draw itself; see below
// dots is an NSMutableArray containing the shapes
for (Dot *dot in dots) {
[dot draw];
}
}
- (void)mouseDown:(NSEvent *)event {
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
currMovingDot = [self clickedDotForPoint:mousePoint];
// Move the dot to the point to indicate that the user has
// successfully "grabbed" it
if( currMovingDot ) currMovingDot.position = mousePoint;
[self setNeedsDisplay:YES];
}
// -mouseDragged: already defined earlier in post
- (void)mouseUp:(NSEvent *)event {
if( !currMovingDot ) return;
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
spot.x = MAX(0, MIN(mousePoint.x, self.bounds.size.width));
spot.y = MAX(0, MIN(mousePoint.y, self.bounds.size.height));
currMovingDot.position = mousePoint;
currMovingDot = nil;
[self setNeedsDisplay:YES];
}
- (Dot *)clickedDotForPoint:(NSPoint)point {
// DOT_NUCLEUS_RADIUS is the size of the
// dot's internal "handle"
for( Dot *dot in dots ){
if( (abs(dot.position.x - point.x) <= DOT_NUCLEUS_RADIUS) &&
(abs(dot.position.y - point.y) <= DOT_NUCLEUS_RADIUS)) {
return dot;
}
}
return nil;
}
Dot.h
#define DOT_NUCLEUS_RADIUS (5)
#interface Dot : NSObject {
NSPoint position;
}
#property (assign) NSPoint position;
- (void)draw;
#end
Dot.m
#import "Dot.h"
#implementation Dot
#synthesize position;
- (void)draw {
//!!!: Demo only: assume that focus is locked on a view.
NSColor *clr = [NSColor colorWithDeviceRed:0.3
green:0.2
blue:0.8
alpha:1];
// Draw a nice border
NSBezierPath *outerCirc;
outerCirc = [NSBezierPath bezierPathWithOvalInRect:
NSMakeRect(position.x - 23, position.y - 23, 46, 46)];
[clr set];
[outerCirc stroke];
[[clr colorWithAlphaComponent:0.7] set];
[outerCirc fill];
[clr set];
// Draw the "handle"
NSRect nucleusRect = NSMakeRect(position.x - DOT_NUCLEUS_RADIUS,
position.y - DOT_NUCLEUS_RADIUS,
DOT_NUCLEUS_RADIUS * 2,
DOT_NUCLEUS_RADIUS * 2);
[[NSBezierPath bezierPathWithOvalInRect:nucleusRect] fill];
}
#end
As you can see, the Dot class is very lightweight, and uses bezier paths to draw. The superview can handle the user interaction.