Grady
=====

* :download:`Download example <PyObjCExample-Grady.zip>`

This example shows how to use ``NSGradient``.


.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

AppDelegate.py
..............

.. sourcecode:: python

    import Cocoa
    import objc
    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

.. rst-class:: tabbertab

MyBaseGradientView.py
.....................

.. sourcecode:: python

    import Cocoa
    import objc
    
    
    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)

.. rst-class:: tabbertab

MyBezierGradientView.py
.......................

.. sourcecode:: python

    import Cocoa
    from MyBaseGradientView import MyBaseGradientView
    
    
    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)

.. rst-class:: tabbertab

MyRectGradientView.py
.....................

.. sourcecode:: python

    import Cocoa
    from MyBaseGradientView import MyBaseGradientView
    
    
    class MyRectGradientView(MyBaseGradientView):
        def init(self):
            self = super().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)

.. rst-class:: tabbertab

MyWindowController.py
.....................

.. sourcecode:: python

    import Cocoa
    import objc
    from objc import super  # noqa: A004
    
    
    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().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())

.. rst-class:: tabbertab

main.py
.......

.. sourcecode:: python

    import AppDelegate  # noqa: F401
    import MyBaseGradientView  # noqa: F401
    import MyBezierGradientView  # noqa: F401
    import MyRectGradientView  # noqa: F401
    import MyWindowController  # noqa: F401
    from PyObjCTools import AppHelper
    
    AppHelper.runEventLoop()

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    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"],
    )

