PathDemo

A PyObjC Example without documentation

Sources

DemoView.py

import random
from math import pi as PI

import Cocoa
import objc
import Quartz
from objc import super  # noqa: A004


class DemoView(Cocoa.NSView):
    _demoNumber = objc.ivar(type=objc._C_INT)

    def initWithFrame_(self, frameRect):
        self = super().initWithFrame_(frameRect)
        if self is None:
            return None

        self._demoNumber = 0
        return self

    def drawRect_(self, rect):
        context = Cocoa.NSGraphicsContext.currentContext().graphicsPort()

        Quartz.CGContextSetGrayFillColor(context, 1.0, 1.0)
        Quartz.CGContextFillRect(context, rect)

        if self._demoNumber == 0:
            rectangles(context, rect)

        elif self._demoNumber == 1:
            circles(context, rect)

        elif self._demoNumber == 2:
            bezierPaths(context, rect)

        elif self._demoNumber == 3:
            circleClipping(context, rect)

        else:
            Cocoa.NSLog("Invalid demo number.")

    def setDemoNumber_(self, number):
        self._demoNumber = number


# The various demo functions


def setRandomFillColor(context):
    Quartz.CGContextSetRGBFillColor(
        context,
        random.uniform(0, 1),
        random.uniform(0, 1),
        random.uniform(0, 1),
        random.uniform(0, 1),
    )


def setRandomStrokeColor(context):
    Quartz.CGContextSetRGBStrokeColor(
        context,
        random.uniform(0, 1),
        random.uniform(0, 1),
        random.uniform(0, 1),
        random.uniform(0, 1),
    )


def randomPointInRect(rect):
    return Quartz.CGPoint(
        x=random.uniform(Quartz.CGRectGetMinX(rect), Quartz.CGRectGetMaxX(rect)),
        y=random.uniform(Quartz.CGRectGetMinY(rect), Quartz.CGRectGetMaxY(rect)),
    )


def randomRectInRect(rect):
    p = randomPointInRect(rect)
    q = randomPointInRect(rect)
    return Quartz.CGRectMake(p.x, p.y, q.x - p.x, q.y - p.y)


def rectangles(context, rect):
    # Draw random rectangles (some stroked, some filled).

    for k in range(20):
        if k % 2 == 0:
            setRandomFillColor(context)
            Quartz.CGContextFillRect(context, randomRectInRect(rect))

        else:
            setRandomStrokeColor(context)
            Quartz.CGContextSetLineWidth(context, 2 + random.randint(0, 10))
            Quartz.CGContextStrokeRect(context, randomRectInRect(rect))


def circles(context, rect):
    # Draw random circles (some stroked, some filled).

    for k in range(20):
        r = randomRectInRect(rect)
        w = Quartz.CGRectGetWidth(r)
        h = Quartz.CGRectGetHeight(r)
        Quartz.CGContextBeginPath(context)

        if w < h:
            v = w
        else:
            v = h

        Quartz.CGContextAddArc(
            context,
            Quartz.CGRectGetMidX(r),
            Quartz.CGRectGetMidY(r),
            v,
            0,
            2 * PI,
            False,
        )
        Quartz.CGContextClosePath(context)

        if k % 2 == 0:
            setRandomFillColor(context)
            Quartz.CGContextFillPath(context)

        else:
            setRandomStrokeColor(context)
            Quartz.CGContextSetLineWidth(context, 2 + random.randint(0, 10))
            Quartz.CGContextStrokePath(context)


def bezierPaths(context, rect):
    for k in range(20):
        numberOfSegments = 1 + random.randint(0, 8)
        Quartz.CGContextBeginPath(context)
        p = randomPointInRect(rect)
        Quartz.CGContextMoveToPoint(context, p.x, p.y)
        for j in range(numberOfSegments):
            p = randomPointInRect(rect)

            if j % 2 == 0:
                Quartz.CGContextAddLineToPoint(context, p.x, p.y)

            else:
                c1 = randomPointInRect(rect)
                c2 = randomPointInRect(rect)
                Quartz.CGContextAddCurveToPoint(
                    context, c1.x, c1.y, c2.x, c2.y, p.x, p.y
                )

        if k % 2 == 0:
            setRandomFillColor(context)
            Quartz.CGContextClosePath(context)
            Quartz.CGContextFillPath(context)

        else:
            setRandomStrokeColor(context)
            Quartz.CGContextSetLineWidth(context, 2 + random.randint(0, 10))
            Quartz.CGContextStrokePath(context)


def circleClipping(context, rect):
    # Draw a random path through a circular clip.

    w = Quartz.CGRectGetWidth(rect)
    h = Quartz.CGRectGetHeight(rect)
    Quartz.CGContextBeginPath(context)
    if w < h:
        v = w
    else:
        v = h
    Quartz.CGContextAddArc(
        context,
        Quartz.CGRectGetMidX(rect),
        Quartz.CGRectGetMidY(rect),
        v / 2,
        0,
        2 * PI,
        False,
    )
    Quartz.CGContextClosePath(context)
    Quartz.CGContextClip(context)

    # Draw something into the clip.
    bezierPaths(context, rect)

    # Draw a clip path on top as a black stroked circle.
    Quartz.CGContextBeginPath(context)
    Quartz.CGContextAddArc(
        context,
        Quartz.CGRectGetMidX(rect),
        Quartz.CGRectGetMidY(rect),
        v / 2,
        0,
        2 * PI,
        False,
    )
    Quartz.CGContextClosePath(context)
    Quartz.CGContextSetLineWidth(context, 1)
    Quartz.CGContextSetRGBStrokeColor(context, 0, 0, 0, 1)
    Quartz.CGContextStrokePath(context)

PathDemoController.py

import Cocoa
import objc


class PathDemoController(Cocoa.NSObject):
    button = objc.ivar()
    popup = objc.ivar()
    window = objc.ivar()
    demoView = objc.ivar()

    def awakeFromNib(self):
        #  Add the title of your new demo to the END of this array.

        titles = ["Rectangles", "Circles", "Bezier Paths", "Circle Clipping"]

        self.popup.removeAllItems()

        for t in titles:
            self.popup.addItemWithTitle_(t)

    @objc.IBAction
    def runAgain_(self, sender):
        self.select_(self)

    @objc.IBAction
    def select_(self, sender):
        self.demoView.setDemoNumber_(self.popup.indexOfSelectedItem())
        self.demoView.setNeedsDisplay_(True)

    def applicationShouldTerminateAfterLastWindowClosed_(self, application):
        return True

main.py

import DemoView  # noqa: F401
import PathDemoController  # noqa: F401
from PyObjCTools import AppHelper

AppHelper.runEventLoop()

setup.py

"""
Script for building the example.

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

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