Wie erstellt man am besten solch ein Control in Cocoa für OS X (Circular Slider + Buttons in bestimmter Form)

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Wie erstellt man am besten solch ein Control in Cocoa für OS X (Circular Slider + Buttons in bestimmter Form)

    Ich habe ein Steuerelement in .Net (C#, WPF). Das würde ich gerne in Cocoa für OS X nachbauen.
    Mir ist nicht ganz klar wie ich das am besten bewerkstellige. Das Grüne auf dem Bild ist ein Button. Auf der blauen Oberfläche lässt sich der graue Knopf mit der Maus rotieren. In der Mitte ist ebenfalls ein Button zum zentrieren.

    Angefangen bei einem Button in einer bestimmten Form. Die Form kann ich zeichnen, soweit ist es kein Problem. Wie mache ich aber, dass die Klicks nur auf der gezeichneten Oberfläche und nicht auf der ganzen View ausgelöst werden?

    Bei dem Circular Slider war der erste Gedanke den NSSliderCell vom Typ NSCircularSlider im Aussehen zu verändern. Ich brauche aber einen Slider der sich nur Außen am Ring dreht.
    Dateien
    • control.png

      (26,68 kB, 171 mal heruntergeladen, zuletzt: )
  • Hallo,

    ich würde das einfach alles mit jeweils einem NSView machen.
    D.h. die vier Knöpfe und den Drehboppel alles getrennt.

    Mit -mouseDown: und -mouseMoved: würde ich einfach die x und y Werte in Grad umrechnen und das View entsprechend um den Mittelpunkt drehen.

    Einzige Rechenübung ist, ob die Maus im transparenten Bereich ist. Also z.B. beim linken Knopf in den 1/4-Kreisbogen.
    Ich weiß nicht, ob Du das mit einem Bilchen realisierst, oder selbst zeichnest. Schaue da mal nach dem -hitTest:
    Vielleicht ist es einfach zu "bescheißen", wenn Du einfach "fragst", ob das View an der Stelle transparent ist.

    Viele Grüße
  • Mit einzelnen Views könnte es schwer werden, wenn der Klick zwar im View, aber eigentlich ausserhalb der entsprechenden Fläche des jeweiligen Views landet. Dann müsste man den Klick quasi an den "richtigen" View weiterreichen.

    Daher würde es sich anbieten, alles mit einem View zu machen. Wenn Du die einzelnen Flächen per NSBezierPath zeichnest, dann kannst Du auch sehr leicht per containsPoint: feststellen, ob ein Klick in der jeweiligen Fläche gelandet ist und darauf dann entsprechend reagieren.
  • Hm... Ohne Erfahrung scheint es eine umständliche und langwierige Aufgabe zu sein.
    In WPF (.NET) sind die Buttons einfache Ellipse Elemente die durch Clipping beschnitten und gedreht sind. Diese Elemente reagieren auch nur in dem gezeichneten Bereich auf die Klicks.
    Zum Rotieren werden ebenfalls zwei Ellipse Elemente verwendet mit ein wenig Logik für die Bewegung. Aber nichts wildes.
  • Es ist wahrscheinlich, dass die User von Windows auf Mac wechseln. Andersrum ehe nicht. :)

    Ich glaube den Aufwand ist es auf jeden Fall nicht wert.

    Mir ist aber aufgefallen, dass der NSCircularSlider nicht Retina fähig ist. Der sieht verpixelt aus.
  • Aufwand? Das ist absolut easy - sei nicht so faul :D

    Ich habe grad nen Test gebaut und ca. sieben Minuten dafür benötigt.
    Es hat sogar Trackpad support. Siehe unten.

    Das Ganze muss natürlich gepimpt und optimiert werden.
    Für die Schleifarbeit brauch man vielleicht nochmals 20 Minuten.

    Aber als solches ist das keine Raketenwissenschaft.
    NSReadPixel halt bei mir in der Eile nicht funktioniert, deshalb lese ich den Teil von einem geränderten Bild aus.
    Das kann man deutlich schöner umsetzen.

    Aber als solches funktioniert es genau wie gewünscht.
    Das Ding dreht sich nur, wenn man auf dem Kreisding rummacht bzw. mit dem Trackpad die Rotationsgeste ausführt.

    Viele Grüße

    C-Quellcode

    1. -(instancetype)initWithFrame:(NSRect)frame
    2. {
    3. // …
    4. self = [super initWithFrame:frame];
    5. // …
    6. if(self == nil)
    7. {
    8. return nil;
    9. }
    10. // …
    11. [self prepare:self];
    12. // …
    13. return self;
    14. // …
    15. }
    16. -(NSView*)hitTest:(NSPoint)aPoint
    17. {
    18. // …
    19. NSRect rect = [self bounds];
    20. // …
    21. NSPoint point = [self convertPoint:aPoint
    22. fromView:nil];
    23. if(NSPointInRect(point, rect) == NO)
    24. {
    25. return (id)[self nextResponder];
    26. }
    27. // …
    28. NSBitmapImageRep *bitmapImageRep = [self bitmapImageRepForCachingDisplayInRect:rect];
    29. if(bitmapImageRep == nil)
    30. {
    31. return (id)[self nextResponder];
    32. }
    33. // …
    34. [bitmapImageRep setSize:rect.size];
    35. [self cacheDisplayInRect:rect
    36. toBitmapImageRep:bitmapImageRep];
    37. // …
    38. NSColor *color = [bitmapImageRep colorAtX:point.x
    39. y:point.y];
    40. if(color == nil)
    41. {
    42. return (id)[self nextResponder];
    43. }
    44. // …
    45. if([color alphaComponent] < 0.1)
    46. {
    47. return (id)[self nextResponder];
    48. }
    49. // …
    50. return [super hitTest:aPoint];
    51. // …
    52. }
    53. #pragma mark Draw
    54. -(void)drawRect:(NSRect)dirtyRect
    55. {
    56. // …
    57. [super drawRect:dirtyRect];
    58. // …
    59. [self drawBackground:self];
    60. // …
    61. [self drawKnob:self];
    62. // …
    63. }
    64. -(BOOL)drawBackground:(id)sender
    65. {
    66. // …
    67. NSRect rect = [self bounds];
    68. if(rect.size.width < 1.0)
    69. {
    70. return NO;
    71. }
    72. if(rect.size.height < 1.0)
    73. {
    74. return NO;
    75. }
    76. // …
    77. NSBezierPath *bezierPath = [NSBezierPath bezierPath];
    78. if(bezierPath == nil)
    79. {
    80. return NO;
    81. }
    82. // …
    83. NSColor *color = [NSColor yellowColor];
    84. if(color == nil)
    85. {
    86. return NO;
    87. }
    88. // …
    89. [color set];
    90. // …
    91. [bezierPath appendBezierPathWithOvalInRect:rect];
    92. [bezierPath stroke];
    93. [bezierPath fill];
    94. // …
    95. return YES;
    96. // …
    97. }
    98. -(BOOL)drawKnob:(id)sender
    99. {
    100. // …
    101. NSRect rect = [self bounds];
    102. if(rect.size.width < 1.0)
    103. {
    104. return NO;
    105. }
    106. if(rect.size.height < 1.0)
    107. {
    108. return NO;
    109. }
    110. // …
    111. rect.origin.x = rect.origin.x + (rect.size.width / 2.0) - 5.0;
    112. rect.size.width = 10.0;
    113. // …
    114. NSBezierPath *bezierPath = [NSBezierPath bezierPath];
    115. if(bezierPath == nil)
    116. {
    117. return NO;
    118. }
    119. // …
    120. NSColor *color = [NSColor blueColor];
    121. if(color == nil)
    122. {
    123. return NO;
    124. }
    125. // …
    126. [color set];
    127. // …
    128. [bezierPath appendBezierPathWithRect:rect];
    129. [bezierPath stroke];
    130. [bezierPath fill];
    131. // …
    132. return YES;
    133. // …
    134. }
    135. #pragma mark Prepare
    136. -(BOOL)prepare:(id)sender
    137. {
    138. // …
    139. NSView *view = self;
    140. if(view == nil)
    141. {
    142. return NO;
    143. }
    144. // …
    145. [view setWantsLayer:YES];
    146. // …
    147. CALayer *layer = [view layer];
    148. if(layer == nil)
    149. {
    150. return NO;
    151. }
    152. // …
    153. [layer setAnchorPoint:CGPointMake(0.5,0.5)];
    154. // …
    155. return YES;
    156. // …
    157. }
    158. #pragma mark Mouse
    159. -(void)mouseDragged:(NSEvent *)theEvent
    160. {
    161. // …
    162. [self rotateWithEvent:theEvent];
    163. // …
    164. }
    165. #pragma mark Rotate
    166. -(void)rotateWithEvent:(NSEvent *)event
    167. {
    168. // …
    169. CALayer *layer = [self layer];
    170. if(layer == nil)
    171. {
    172. return;
    173. }
    174. // …
    175. [layer setAnchorPoint:CGPointMake(0.5, 0.5)];
    176. // …
    177. CGFloat x = [event rotation] / 180.0 * M_PI;
    178. if(x < 0.05)
    179. {
    180. x = 0.05;
    181. }
    182. // …
    183. CATransform3D transform = [layer transform];
    184. // …
    185. transform = CATransform3DRotate(transform, x, 0.0, 0.0, 1.0);
    186. // …
    187. [layer setTransform:transform];
    188. // …
    189. }
    Alles anzeigen