Element Border

A border is a visual property of a BlElement Object subclass: #BlElement uses: TBlTransformable + TBlEventTarget + TBlDebug instanceVariableNames: 'spaceReference parent children bounds measuredBounds boundsCache eventDispatcher constraints layout transformation taskQueue errorHandler userData visuals flags' classVariableNames: '' package: 'Bloc-Basic' . It is modeled by instances of BlBorder Object subclass: #BlBorder uses: TBlDebug instanceVariableNames: 'paint width style opacity' classVariableNames: '' package: 'Bloc-Basic-Border' , which encapsulates various border properties such as paint, width, style and opacity.

All border instances are immutable objects.

The simplest way to create a border is to use Color Object subclass: #Color instanceVariableNames: 'rgb cachedDepth cachedBitPattern alpha' classVariableNames: 'BlueShift CachedColormaps ColorRegistry ComponentMask ComponentMax GrayToIndexMap GreenShift HalfComponentMask IndexedColors MaskingMap RedShift' package: 'Colors-Base' instances.

colorGrayBorder
	<gtExample>
	| aBorder |

	aBorder := BlBorder paint: Color gray.

	self assert: (aBorder paint isKindOf: BlColorPaint).
	self assert: aBorder paint color equals: Color gray.

	self assert: (aBorder style isKindOf: BlStrokeStyle).
	self assert: aBorder style lineCap equals: BlStrokeLineCap butt.
	self assert: aBorder style lineJoin equals: BlStrokeLineJoin miter.
	self assert: aBorder style miterLimit equals: 4.0.
	self assert: aBorder style dashArray equals: #().
	self assert: aBorder style dashOffset equals: 0.0.

	self assert: aBorder width equals: 1.0.
	self assert: aBorder opacity equals: 1.0.

	^ aBorder
    

In addition users can provide a custom width along side with the paint, it is the most common way to create a border:

colorGrayBorderWithExplicitWidth
	<gtExample>
	| aBorder |

	aBorder := BlBorder paint: Color gray width: 10.

	self assert: (aBorder paint isKindOf: BlColorPaint).
	self assert: aBorder paint color equals: Color gray.

	self assert: (aBorder style isKindOf: BlStrokeStyle).
	self assert: aBorder style lineCap equals: BlStrokeLineCap butt.
	self assert: aBorder style lineJoin equals: BlStrokeLineJoin miter.
	self assert: aBorder style miterLimit equals: 4.0.
	self assert: aBorder style dashArray equals: #().
	self assert: aBorder style dashOffset equals: 0.0.

	self assert: aBorder width equals: 10.
	self assert: aBorder opacity equals: 1.0.

	^ aBorder
    

Immutability

As it was mentioned previously the border is immutable. Any change to its state once created results in BlImmutableObjectChangeError Error subclass: #BlImmutableObjectChangeError instanceVariableNames: 'object' classVariableNames: '' package: 'Bloc-Basic-Errors' .

The reason for the border to be immutable is to simplify overall design, decrease complexity and prevent users from making trivial mistakes. Some border properties influence the geometry of the element (stroked bounds) which means that every time the border width is changed we should somehow notify and invalidate the element that uses that border. Either the user would have to send some invalidation message to the element or the border should know its element. If we make users be responsible for manual invalidation sooner or later it may happen that they forget or just will have a lack of understanding of such details therefore they will start to think that Bloc does not work properly. That is why a clean and nice solution is to make border immutable and enfore users to use BlElement>>#border: border: aBlBorder "Change my border and invalidate me" <argument: #aBlBorder satisfies: #notNil> <event: #BlElementBorderChangedEvent> | aNewBorder | aNewBorder := aBlBorder asBlBorder. self border = aNewBorder ifTrue: [ ^ self ]. visuals := visuals border: aNewBorder. self geometry releaseStrokedBoundsCache. self eventDispatcher dispatchBorderChanged. self invalidate every time he or she wants to change the border.

In the following example we show what happens if user tries to change the width of the existing border:

changeWidthError
	<gtExample>
	| aBorder anError |

	aBorder := self colorGrayBorder.

	[ aBorder width: 20 ]
		on: BlImmutableObjectChangeError
		do: [ :e | anError := e ].
	
	self assert: (anError isKindOf: BlImmutableObjectChangeError).
	
	^ anError
    

Changing properties

The obvious downside of the immutability is the fact that it is not possible to mutate properties. To address this issue border provides a set of copyWith*: methods that create a new instance of the border with one one property modified.

Assume the following example in which we create a new border with a different width preserving all other properties:

copyWithWidth
	<gtExample>
	| anOldBorder aBorder |
	
	anOldBorder := self colorGrayBorder.
	aBorder := anOldBorder copyWithWidth: 10.
	
	self assert: (aBorder paint isKindOf: BlColorPaint).
	self assert: aBorder paint color equals: Color gray.

	self assert: (aBorder style isKindOf: BlStrokeStyle).
	self assert: aBorder style lineCap equals: BlStrokeLineCap butt.
	self assert: aBorder style lineJoin equals: BlStrokeLineJoin miter.
	self assert: aBorder style miterLimit equals: 4.0.
	self assert: aBorder style dashArray equals: #().
	self assert: aBorder style dashOffset equals: 0.0.

	self assert: aBorder width equals: 10.
	self assert: aBorder opacity equals: 1.0.
	
	^ aBorder