Grady

This example shows how to use NSGradient.

Sources

AppDelegate.py

import objc
import Cocoa
from MyWindowController import MyWindowController

class AppDelegate (Cocoa.NSObject):
    myWindowController = objc.ivar()

    @objc.IBAction
    def newDocument_(self, sender):
        if self.myWindowController is None:
            self.myWindowController = MyWindowController.alloc().initWithWindowNibName_("TestWindow")

        self.myWindowController.showWindow_(self)


    def applicationDidFinishLaunching_(self, notification):
        self.newDocument_(self)

    def validateMenuItem_(self, theMenuItem):
        enable = self.respondsToSelector_(theMenuItem.action())

        # disable "New" if the window is already up
        if theMenuItem.action() == b'newDocument:':
            if self.myWindowController.window().isKeyWindow():
                enable = False

        return enable

MyBaseGradientView.py

import objc
import Cocoa


class MyBaseGradientView (Cocoa.NSView):
    myGradient = objc.ivar()
    myStartColor = objc.ivar()
    myEndColor = objc.ivar()

    forceColorChange = objc.ivar.bool()
    myAngle = objc.ivar.double()
    myIsRadial = objc.ivar.bool()
    myOffsetPt = objc.ivar.NSPoint()


    def resetGradient(self):
        if self.forceColorChange and self.myGradient is not None:
            self.myGradient = None

        if self.myGradient is None:
            self.myGradient = Cocoa.NSGradient.alloc().initWithStartingColor_endingColor_(
                    self.myStartColor, self.myEndColor)
            self.forceColorChange = False


    def setStartColor_(self, startColor):
        self.myStartColor = startColor
        self.forceColorChange = True
        self.setNeedsDisplay_(True)

    def setEndColor_(self, endColor):
        self.myEndColor = endColor;
        self.forceColorChange = True
        self.setNeedsDisplay_(True)

    def setAngle_(self, angle):
        self.myAngle = angle
        self.setNeedsDisplay_(True)

    def setRadialDraw_(self, isRadial):
        self.myIsRadial = isRadial
        self.setNeedsDisplay_(True)

    def getRelativeCenterPositionFromEvent_(self, theEvent):
        curMousePt = self.convertPoint_fromView_(theEvent.locationInWindow(), None)
        pt = Cocoa.NSMakePoint( (curMousePt.x - Cocoa.NSMidX(self.bounds())) / (self.bounds().size.width / 2.0),
                          (curMousePt.y - Cocoa.NSMidY(self.bounds())) / (self.bounds().size.height / 2.0))
        return pt

    def mouseDown_(self, theEvent):
        if self.myIsRadial:
            self.myOffsetPt = self.getRelativeCenterPositionFromEvent_(theEvent)
            self.setNeedsDisplay_(True)

    def mouseDragged_(self, theEvent):
        if self.myIsRadial:
            self.myOffsetPt = self.getRelativeCenterPositionFromEvent_(theEvent)
            self.setNeedsDisplay_(True)

MyBezierGradientView.py

from MyBaseGradientView import MyBaseGradientView
import Cocoa

class MyBezierGradientView (MyBaseGradientView):
    def init(self):
        self = super(MyBaseGradientView, self).init()
        if self is None:
            return None

        self.myOffsetPt = Cocoa.NSMakePoint(0.0, 0.0)
        return self

    def drawRect_(self, rect):
        self.resetGradient()

        bezierPath = Cocoa.NSBezierPath.alloc().init()
        bezierPath.appendBezierPathWithOvalInRect_(rect)

        if self.myIsRadial:
            self.myGradient.drawInBezierPath_relativeCenterPosition_(bezierPath, self.myOffsetPt)

        else:
            self.myGradient.drawInBezierPath_angle_(bezierPath, self.myAngle)

MyRectGradientView.py

from MyBaseGradientView import MyBaseGradientView
import Cocoa

class MyRectGradientView (MyBaseGradientView):
    def init(self):
        self = super(MyRectGradientView, self).init()
        if self is None:
            return self

        self.myOffsetPt = Cocoa.NSMakePoint(0.0, 0.0)
        return self

    def drawRect_(self, rect):
        self.resetGradient()

        # if the "Radial Gradient" checkbox is turned on, draw using 'myOffsetPt'
        if self.myIsRadial:
            self.myGradient.drawInRect_relativeCenterPosition_(self.bounds(), self.myOffsetPt)

        else:
            self.myGradient.drawInRect_angle_(self.bounds(), self.myAngle)

MyWindowController.py

import objc
from objc import super
import Cocoa

class MyWindowController (Cocoa.NSWindowController):
    rectGradientView = objc.IBOutlet()
    bezierGradientView = objc.IBOutlet()

    startColorWell = objc.IBOutlet()
    endColorWell = objc.IBOutlet()
    angle = objc.IBOutlet()
    angleSlider = objc.IBOutlet()

    radialCheck = objc.IBOutlet()
    radialExplainText = objc.IBOutlet()


    def initWithPath_(self, newPath):
        return super(MyWindowController, self).initWithWindowNibName_("TestWindow")

    def awakeFromNib(self):
        # make sure our angle text input keep the right format
        formatter = Cocoa.NSNumberFormatter.alloc().init()
        formatter.setNumberStyle_(Cocoa.NSNumberFormatterDecimalStyle)
        self.angle.cell().setFormatter_(formatter)

        # setup the initial start color
        self.rectGradientView.setStartColor_(Cocoa.NSColor.orangeColor())
        self.bezierGradientView.setStartColor_(Cocoa.NSColor.orangeColor())
        self.startColorWell.setColor_(Cocoa.NSColor.orangeColor())

        # setup the initial end color
        self.rectGradientView.setEndColor_(Cocoa.NSColor.blueColor())
        self.bezierGradientView.setEndColor_(Cocoa.NSColor.blueColor())
        self.endColorWell.setColor_(Cocoa.NSColor.blueColor())

        # setup the initial angle value
        self.rectGradientView.setAngle_(90.0)
        self.bezierGradientView.setAngle_(90.0)
        self.angle.setStringValue_("90.0")
        self.angleSlider.setFloatValue_(90.0)

    @objc.IBAction
    def swapColors_(self, sender):
        startColor = self.startColorWell.color()
        endColor = self.endColorWell.color()

        # change all our view's start and end colors
        self.rectGradientView.setStartColor_(endColor)
        self.rectGradientView.setEndColor_(startColor)

        self.bezierGradientView.setStartColor_(endColor)
        self.bezierGradientView.setEndColor_(startColor)

        # fix our color wells
        self.startColorWell.setColor_(endColor)
        self.endColorWell.setColor_(startColor)

    @objc.IBAction
    def startColor_(self, sender):
        newColor = sender.color()
        self.rectGradientView.setStartColor_(newColor)
        self.bezierGradientView.setStartColor_(newColor)

    @objc.IBAction
    def endColor_(self, sender):
        newColor = sender.color()
        self.rectGradientView.setEndColor_(newColor)
        self.bezierGradientView.setEndColor_(newColor)

    def controlTextDidEndEditing_(self, notification):
        theAngle = self.angle.floatValue()
        self.rectGradientView.setAngle_(theAngle)
        self.bezierGradientView.setAngle_(theAngle)

        theAngleDougle = self.angle.doubleValue()
        self.angleSlider.setDoubleValue_(theAngleDougle)
        self.angleSlider.setNeedsDisplay_(True)

    @objc.IBAction
    def angleSliderChange_(self, sender):
        angleValue = sender.floatValue()
        self.rectGradientView.setAngle_(angleValue)
        self.bezierGradientView.setAngle_(angleValue)
        self.angle.setDoubleValue_(angleValue)

    @objc.IBAction
    def radialDraw_(self, sender):
        self.rectGradientView.setRadialDraw_(sender.selectedCell().state())
        self.bezierGradientView.setRadialDraw_(sender.selectedCell().state())

        # angle factor does not relate to radial draws
        self.angleSlider.setEnabled_(not sender.selectedCell().state())
        self.angle.setEnabled_(not sender.selectedCell().state())

        # hide/show the explain text for radial gradients
        self.radialExplainText.setHidden_(not sender.selectedCell().state())

main.py

from PyObjCTools import AppHelper

import AppDelegate
import MyBaseGradientView
import MyBezierGradientView
import MyRectGradientView
import MyWindowController

AppHelper.runEventLoop()

setup.py

"""
Script for building the example.

Usage:
    python3 setup.py py2app
"""
from setuptools import setup

setup(
    name="Grady",
    app=["main.py"],
    data_files=["English.lproj"],
    setup_requires=[
        "py2app",
        "pyobjc-framework-Cocoa",
    ]
)

Resources