TLayer¶
A PyObjC Example without documentation
Sources¶
AppDelegate.py¶
import Cocoa
import objc
import TLayerDemo
class AppDelegate(Cocoa.NSObject):
shadowDemo = objc.ivar()
def applicationDidFinishLaunching_(self, notification):
self.showTLayerDemoWindow_(self)
@objc.IBAction
def showTLayerDemoWindow_(self, sender):
if self.shadowDemo is None:
self.shadowDemo = TLayerDemo.TLayerDemo.alloc().init()
self.shadowDemo.window().orderFront_(self)
def applicationShouldTerminateAfterLastWindowClosed_(self, app):
return True
Circle.py¶
import math
import Cocoa
import objc
import Quartz # noqa: F401
class Circle(Cocoa.NSObject):
radius = objc.ivar(type=objc._C_FLT)
center = objc.ivar(type=Cocoa.NSPoint.__typestr__)
color = objc.ivar()
def bounds(self):
return Cocoa.NSMakeRect(
self.center.x - self.radius,
self.center.y - self.radius,
2 * self.radius,
2 * self.radius,
)
def draw(self):
context = Cocoa.NSGraphicsContext.currentContext().graphicsPort()
self.color.set()
Cocoa.CGContextSetGrayStrokeColor(context, 0, 1)
Cocoa.CGContextSetLineWidth(context, 1.5)
Cocoa.CGContextSaveGState(context)
Cocoa.CGContextTranslateCTM(context, self.center.x, self.center.y)
Cocoa.CGContextScaleCTM(context, self.radius, self.radius)
Cocoa.CGContextMoveToPoint(context, 1, 0)
Cocoa.CGContextAddArc(context, 0, 0, 1, 0, 2 * math.pi, False)
Cocoa.CGContextClosePath(context)
Cocoa.CGContextRestoreGState(context)
Cocoa.CGContextDrawPath(context, Cocoa.kCGPathFill)
Extras.py¶
import random
import Cocoa
import objc
class NSColor(objc.Category(Cocoa.NSColor)):
@classmethod
def randomColor(self):
return Cocoa.NSColor.colorWithCalibratedRed_green_blue_alpha_(
random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1), 1
)
def makeRandomPointInRect(rect):
return Cocoa.NSPoint(
x=random.uniform(Cocoa.NSMinX(rect), Cocoa.NSMaxX(rect)),
y=random.uniform(Cocoa.NSMinY(rect), Cocoa.NSMaxY(rect)),
)
ShadowOffsetView.py¶
import math
import Cocoa
import objc
import Quartz
ShadowOffsetChanged = "ShadowOffsetChanged"
class ShadowOffsetView(Cocoa.NSView):
_offset = objc.ivar(type=Quartz.CGSize.__typestr__)
_scale = objc.ivar(type=objc._C_FLT)
def scale(self):
return self._scale
def setScale_(self, scale):
self._scale = scale
def offset(self):
return Quartz.CGSizeMake(
self._offset.width * self._scale, self._offset.height * self._scale
)
def setOffset_(self, offset):
offset = Quartz.CGSizeMake(
offset.width / self._scale, offset.height / self._scale
)
if self._offset != offset:
self._offset = offset
self.setNeedsDisplay_(True)
def isOpaque(self):
return False
def setOffsetFromPoint_(self, point):
bounds = self.bounds()
offset = Quartz.CGSize(
width=(point.x - Cocoa.NSMidX(bounds)) / (Cocoa.NSWidth(bounds) / 2),
height=(point.y - Cocoa.NSMidY(bounds)) / (Cocoa.NSHeight(bounds) / 2),
)
radius = math.sqrt(offset.width * offset.width + offset.height * offset.height)
if radius > 1:
offset.width /= radius
offset.height /= radius
if self._offset != offset:
self._offset = offset
self.setNeedsDisplay_(True)
Cocoa.NSNotificationCenter.defaultCenter().postNotificationName_object_(
ShadowOffsetChanged, self
)
def mouseDown_(self, event):
point = self.convertPoint_fromView_(event.locationInWindow(), None)
self.setOffsetFromPoint_(point)
def mouseDragged_(self, event):
point = self.convertPoint_fromView_(event.locationInWindow(), None)
self.setOffsetFromPoint_(point)
def drawRect_(self, rect):
bounds = self.bounds()
x = Cocoa.NSMinX(bounds)
y = Cocoa.NSMinY(bounds)
w = Cocoa.NSWidth(bounds)
h = Cocoa.NSHeight(bounds)
r = min(w / 2, h / 2)
context = Cocoa.NSGraphicsContext.currentContext().graphicsPort()
Quartz.CGContextTranslateCTM(context, x + w / 2, y + h / 2)
Quartz.CGContextAddArc(context, 0, 0, r, 0, math.pi, True)
Quartz.CGContextClip(context)
Quartz.CGContextSetGrayFillColor(context, 0.910, 1)
Quartz.CGContextFillRect(context, Quartz.CGRectMake(-w / 2, -h / 2, w, h))
Quartz.CGContextAddArc(context, 0, 0, r, 0, 2 * math.pi, True)
Quartz.CGContextSetGrayStrokeColor(context, 0.616, 1)
Quartz.CGContextStrokePath(context)
Quartz.CGContextAddArc(context, 0, -2, r, 0, 2 * math.pi, True)
Quartz.CGContextSetGrayStrokeColor(context, 0.784, 1)
Quartz.CGContextStrokePath(context)
Quartz.CGContextMoveToPoint(context, 0, 0)
Quartz.CGContextAddLineToPoint(
context, r * self._offset.width, r * self._offset.height
)
Quartz.CGContextSetLineWidth(context, 2)
Quartz.CGContextSetGrayStrokeColor(context, 0.33, 1)
Quartz.CGContextStrokePath(context)
TLayerDemo.py¶
import Cocoa
import objc
import Quartz
import ShadowOffsetView
from objc import super, nil # noqa: A004
class TLayerDemo(Cocoa.NSObject):
colorWell = objc.IBOutlet()
shadowOffsetView = objc.IBOutlet()
shadowRadiusSlider = objc.IBOutlet()
tlayerView = objc.IBOutlet()
transparencyLayerButton = objc.IBOutlet()
@classmethod
def initialize(self):
Cocoa.NSColorPanel.sharedColorPanel().setShowsAlpha_(True)
def init(self):
self = super().init()
if self is None:
return None
if not Cocoa.NSBundle.loadNibNamed_owner_("TLayerDemo", self):
Cocoa.NSLog("Failed to load TLayerDemo.nib")
return nil
self.shadowOffsetView.setScale_(40)
self.shadowOffsetView.setOffset_(Quartz.CGSizeMake(-30, -30))
self.tlayerView.setShadowOffset_(Quartz.CGSizeMake(-30, -30))
self.shadowRadiusChanged_(self.shadowRadiusSlider)
# Better to do this as a subclass of NSControl....
Cocoa.NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
self, b"shadowOffsetChanged:", ShadowOffsetView.ShadowOffsetChanged, None
)
return self
def dealloc(self):
Cocoa.NSNotificationCenter.defaultCenter().removeObserver_(self)
super().dealloc()
def window(self):
return self.tlayerView.window()
@objc.IBAction
def shadowRadiusChanged_(self, sender):
self.tlayerView.setShadowRadius_(self.shadowRadiusSlider.floatValue())
@objc.IBAction
def toggleTransparencyLayers_(self, sender):
self.tlayerView.setUsesTransparencyLayers_(self.transparencyLayerButton.state())
def shadowOffsetChanged_(self, notification):
offset = notification.object().offset()
self.tlayerView.setShadowOffset_(offset)
TLayerView.py¶
import Cocoa
import objc
import Quartz
from Circle import Circle
from Extras import makeRandomPointInRect
from objc import super # noqa: A004
gCircleCount = 3
class NSEvent(objc.Category(Cocoa.NSEvent)):
def locationInView_(self, view):
return view.convertPoint_fromView_(self.locationInWindow(), None)
class TLayerView(Cocoa.NSView):
circles = objc.ivar()
shadowRadius = objc.ivar(type=objc._C_FLT)
shadowOffset = objc.ivar(type=Quartz.CGSize.__typestr__)
useTLayer = objc.ivar(type=objc._C_BOOL)
def initWithFrame_(self, frame):
circleRadius = 100
colors = [(0.5, 0.0, 0.5, 1), (1.0, 0.7, 0.0, 1), (0.0, 0.5, 0.0, 1)]
self = super().initWithFrame_(frame)
if self is None:
return None
self.useTLayer = False
self.circles = []
for c in colors:
color = Cocoa.NSColor.colorWithCalibratedRed_green_blue_alpha_(*c)
circle = Circle.alloc().init()
circle.color = color
circle.radius = circleRadius
circle.center = makeRandomPointInRect(self.bounds())
self.circles.append(circle)
self.registerForDraggedTypes_([Cocoa.NSColorPboardType])
self.setNeedsDisplay_(True)
return self
def setShadowRadius_(self, radius):
if radius != self.shadowRadius:
self.shadowRadius = radius
self.setNeedsDisplay_(True)
def setShadowOffset_(self, offset):
if self.shadowOffset != offset:
self.shadowOffset = offset
self.setNeedsDisplay_(True)
def setUsesTransparencyLayers_(self, state):
if self.useTLayer != state:
self.useTLayer = state
self.setNeedsDisplay_(True)
def isOpaque(self):
return True
def acceptsFirstMouse_(self, event):
return True
def boundsForCircle_(self, circle):
dx = 2 * abs(self.shadowOffset.width) + 2 * self.shadowRadius
dy = 2 * abs(self.shadowOffset.height) + 2 * self.shadowRadius
return Cocoa.NSInsetRect(circle.bounds(), -dx, -dy)
def dragCircleAtIndex_withEvent_(self, index, event):
circle = self.circles[index]
del self.circles[index]
self.circles.append(circle)
self.setNeedsDisplayInRect_(self.boundsForCircle_(circle))
mask = Cocoa.NSLeftMouseDraggedMask | Cocoa.NSLeftMouseUpMask
start = event.locationInView_(self)
while 1:
event = self.window().nextEventMatchingMask_(mask)
if event.type() == Cocoa.NSLeftMouseUp:
break
self.setNeedsDisplayInRect_(self.boundsForCircle_(circle))
center = circle.center
point = event.locationInView_(self)
center.x += point.x - start.x
center.y += point.y - start.y
circle.center = center
self.setNeedsDisplayInRect_(self.boundsForCircle_(circle))
start = point
def indexOfCircleAtPoint_(self, point):
for idx, circle in reversed(list(enumerate(self.circles))):
center = circle.center
radius = circle.radius
dx = point.x - center.x
dy = point.y - center.y
if dx * dx + dy * dy < radius * radius:
return idx
return -1
def mouseDown_(self, event):
point = event.locationInView_(self)
index = self.indexOfCircleAtPoint_(point)
if index >= 0:
self.dragCircleAtIndex_withEvent_(index, event)
def setFrame_(self, frame):
super().setFrame_(frame)
self.setNeedsDisplay_(True)
def drawRect_(self, rect):
context = Cocoa.NSGraphicsContext.currentContext().graphicsPort()
Quartz.CGContextSetRGBFillColor(context, 0.7, 0.7, 0.9, 1)
Quartz.CGContextFillRect(context, rect)
Quartz.CGContextSetShadow(context, self.shadowOffset, self.shadowRadius)
if self.useTLayer:
Quartz.CGContextBeginTransparencyLayer(context, None)
for circle in self.circles:
bounds = self.boundsForCircle_(circle)
if Cocoa.NSIntersectsRect(bounds, rect):
circle.draw()
if self.useTLayer:
Quartz.CGContextEndTransparencyLayer(context)
def draggingEntered_(self, sender):
# Since we have only registered for NSColorPboardType drags, this is
# actually unneeded. If you were to register for any other drag types,
# though, this code would be necessary.
if (sender.draggingSourceOperationMask() & Cocoa.NSDragOperationGeneric) != 0:
pasteboard = sender.draggingPasteboard()
if pasteboard.types().containsObject_(Cocoa.NSColorPboardType):
return Cocoa.NSDragOperationGeneric
return Cocoa.NSDragOperationNone
def performDragOperation_(self, sender):
point = self.convertPoint_fromView_(sender.draggingLocation(), None)
index = self.indexOfCircleAtPoint_(point)
if index >= 0:
# The current drag location is inside the bounds of a circle so we
# accept the drop and move on to concludeDragOperation:.
return True
return False
def concludeDragOperation_(self, sender):
color = Cocoa.NSColor.colorFromPasteboard_(sender.draggingPasteboard())
point = self.convertPoint_fromView_(sender.draggingLocation(), None)
index = self.indexOfCircleAtPoint_(point)
if index >= 0:
circle = self.circles[index]
circle.color = color
self.setNeedsDisplayInRect_(self.boundsForCircle_(circle))
main.py¶
import AppDelegate # noqa: F401
import Circle # noqa: F401
import Extras # noqa: F401
import ShadowOffsetView # noqa: F401
import TLayerDemo # noqa: F401
import TLayerView # 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="TLayer",
app=["main.py"],
data_files=["English.lproj"],
setup_requires=["py2app", "pyobjc-framework-Cocoa", "pyobjc-framework-Quartz"],
)