Component outline on different z-order than component

The paintable canvas can be used as an overlay to provide this effect without any regard to z-order or component positioning. Simply overlay the entire container with a paintable canvas, and use the canvas's mouse moved event to detect when the cursor intersects a component, so a top level outline can be painted around it:

Example:
Add the following custom properties to the canvas:
image

Add the following custom methods to the canvas:

passMouseEvent
#def passMouseEvent(self, event):
	"""
	Passes mouse event to the underlying component
	"""
	from java.awt import Rectangle
	from java.awt.event import MouseEvent
	for component in event.source.parent.components:
		if component != event.source: 
			
			# Components that use relative coordinates must be handled differently
			if not hasattr(component, 'relX') and component.bounds.contains(event.point):
				componentEvent = MouseEvent(
					component,
					event.getID(),
					event.when,
					event.modifiers,
					event.x - component.x,
					event.y - component.y,
					event.getXOnScreen(),
					event.getYOnScreen(),
					event.clickCount,
					event.isPopupTrigger(),
					event.button)
				component.dispatchEvent(componentEvent)
				return
			elif hasattr(component, 'relX') and Rectangle(int(component.relX), int(component.relY), int(component.relWidth), int(component.relHeight)).contains(event.point):
				componentEvent = MouseEvent(
					component,
					event.getID(),
					event.when,
					event.modifiers,
					event.x - int(component.relX),
					event.y - int(component.relY),
					event.getXOnScreen(),
					event.getYOnScreen(),
					event.clickCount,
					event.isPopupTrigger(),
					event.button)
				component.dispatchEvent(componentEvent)
				return
mapOutline
def mapOutline(self, event):
	"""
	Sets the custom properties on the paintable canvas that define an outline
	...and in doing so, triggers the repaint event
	"""
	from java.awt import Rectangle
	for component in event.source.parent.components:
		# Filter out components that shouldn't be outlined
		if component.__class__.__name__ not in ['PMIPaintableCanvas', 'PMILabel'] or 'Polygon' in component.name:
			
			# Handle components that use relative coordinates differently than standard components
			if not hasattr(component, 'relX') and component.bounds.contains(event.point):
				event.source.outlineX = component.x - event.source.outlineGap
				event.source.outlineY = component.y - event.source.outlineGap
				event.source.outlineWidth = component.width + (2 * event.source.outlineGap)
				event.source.outlineHeight = component.height + (2 * event.source.outlineGap)
				return
				
			elif hasattr(component, 'relX') and Rectangle(int(component.relX), int(component.relY), int(component.relWidth), int(component.relHeight)).contains(event.point):
				event.source.outlineX = component.relX - event.source.outlineGap
				event.source.outlineY = component.relY - event.source.outlineGap
				event.source.outlineWidth = component.relWidth + (2 * event.source.outlineGap)
				event.source.outlineHeight = component.relHeight + (2 * event.source.outlineGap)
				return
	event.source.outlineX = None

image

Both custom methods will take a single parameter called event. The
passMouseEvent will be used to provide to the underlying components any mouse event that would otherwise be consumed by the overlay.

The mapOutline will be used to set the custom properties on the canvas that define the outline. The top level if statement in the mapOutline method should be used to determine which components receive the outline.

Call the passMouseEvent from all paintable canvas mouse event handlers in this way:

event.source.passMouseEvent(event)

image

call the mapOutline method from the mouseMoved event handler in this way:

# All mouse events will have passMouseEvent,
#...but only mouseMoved will have mapOutline
event.source.passMouseEvent(event)
event.source.mapOutline(event)

Finally, use the following script on the canvas's repaint event handler to paint the outlines:

if event.source.outlineX is not None:
	from java.awt import BasicStroke
	graphics = event.graphics
	graphics.setStroke(BasicStroke(5))
	graphics.setColor(system.gui.color('yellow'))
	x = event.source.outlineX
	y = event.source.outlineY
	width = event.source.outlineWidth
	height = event.source.outlineHeight
	radius = event.source.outlineGap
	graphics.drawRoundRect(x, y, width, height, radius, radius)

Result:
PaintableCanvasFloatingOutlineDemo

1 Like