Building a custom button appearance

Button is one of the most essential graphical components in UI. It can be in a lot of different variaties and forms.

See BrButtonModel BrWidgetModel subclass: #BrButtonModel instanceVariableNames: 'action actionsWithModifiers' classVariableNames: '' package: 'Brick-Button - Model' for a more detailed description.

Instantiating a button

A button widget is an instance of BrButton BlElement subclass: #BrButton uses: TBrEnableable + TBrLayoutAlignable + TBrLayoutResizable + TBrSizeAdjustable instanceVariableNames: 'labelModel iconModel interactiveModel' classVariableNames: '' package: 'Brick-Button - UI' . By default it does not have any attached look and therefore has no visual appearance:

newButton
	<gtExample>
	
	^ BrButton new
    

Behavior

A button widget is composed of multiple view models that add important functionality:

BrLabelModel BrWidgetModel subclass: #BrLabelModel instanceVariableNames: 'text' classVariableNames: '' package: 'Brick-! Core - Models' is responsible for button's label

BrIconModel BrWidgetModel subclass: #BrIconModel instanceVariableNames: 'icon' classVariableNames: '' package: 'Brick-! Core - Models' adds support of an icon

BrInteractiveModel BrWidgetModel subclass: #BrInteractiveModel instanceVariableNames: 'stateModels' classVariableNames: '' package: 'Brick-! Deprecated' lets users to enable or disable a button (make it non-interactable)

BrPressModel BrStateModel subclass: #BrPressModel instanceVariableNames: 'pressed' classVariableNames: '' package: 'Brick-! Deprecated' provides support of handling press state

BrHoverModel BrStateModel subclass: #BrHoverModel instanceVariableNames: 'hovered' classVariableNames: '' package: 'Brick-! Deprecated' adds support of mouse hover events

BrSizeAdjustmentModel BrStateModel subclass: #BrSizeAdjustmentModel instanceVariableNames: 'dimension' classVariableNames: '' package: 'Brick-! Deprecated' is responsible for managing the logical size of a button (tiny, small, large etc)

BrLayoutHorizontalResizerModel BrLayoutResizerModel subclass: #BrLayoutHorizontalResizerModel instanceVariableNames: '' classVariableNames: '' package: 'Brick-! Deprecated' provides support for horizontal resizing strategies (fit content, match parent, exact size)

BrLayoutVerticalResizerModel BrLayoutResizerModel subclass: #BrLayoutVerticalResizerModel instanceVariableNames: '' classVariableNames: '' package: 'Brick-! Deprecated' is similar to horizontal resizer but controls a vertical resizing strategy

Building a button

Button's look can be implemented by composing existing looks or implementing a new one.

Adding and styling a label

Having BrLabelModel BrWidgetModel subclass: #BrLabelModel instanceVariableNames: 'text' classVariableNames: '' package: 'Brick-! Core - Models' view model is not enough to create a button with a label. For that we would need to add BrTextLabelAptitude BrAptitude subclass: #BrTextLabelAptitude uses: TBlTextStyleable instanceVariableNames: 'labelElement' classVariableNames: '' package: 'Brick-! Core - Looks' :

buttonWithLabelLook
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	
	^ self newButton
		aptitude: aLabelLook
    

Of course, a text style can be customized to match user's preference:

buttonWithLabelLookAndCustomFont
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	aLabelLook
		bold;
		fontName: 'Source Sans Pro';
		fontSize: 20;
		foreground: Color gray.

	^ self newButton
		aptitude: aLabelLook
    

Quite often though, it may be needed to change label's style when users hover a button. BrHoverModel BrStateModel subclass: #BrHoverModel instanceVariableNames: 'hovered' classVariableNames: '' package: 'Brick-! Deprecated' handles widget hover state change. To style the text accordingly we can add BrInteractiveCommonAptitude BrInteractiveAptitude subclass: #BrInteractiveCommonAptitude instanceVariableNames: '' classVariableNames: '' package: 'Brick-! Deprecated' to the previosly created BrTextLabelAptitude BrAptitude subclass: #BrTextLabelAptitude uses: TBlTextStyleable instanceVariableNames: 'labelElement' classVariableNames: '' package: 'Brick-! Core - Looks' . Please note that we should also provide default values for all button properties that change due to interactivity in a default: style:

buttonWithLabelLookReactingToHover
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	aLabelLook
		bold;
		fontName: 'Source Sans Pro';
		fontSize: 20.

	aLabelLook add: (BrInteractiveCommonAptitude new
		default: [ aLabelLook foreground: Color gray ];
		hovered: [ aLabelLook foreground: Color darkGray ]).

	^ self newButton
		aptitude: aLabelLook
    

In a similar fashing we can also change the text style on a button press which is managed by BrPressModel BrStateModel subclass: #BrPressModel instanceVariableNames: 'pressed' classVariableNames: '' package: 'Brick-! Deprecated' :

buttonWithLabelLookReactingToHoverAndPress
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	aLabelLook
		bold;
		fontName: 'Source Sans Pro';
		fontSize: 20.

	aLabelLook add: (BrInteractiveCommonAptitude new
		default: [ aLabelLook foreground: Color gray ];
		hovered: [ aLabelLook foreground: Color darkGray ];
		pressed: [ aLabelLook foreground: Color black ]).

	^ self newButton
		aptitude: aLabelLook
    

One more common use-case is to provide a text style for a disabled button which is modelled by BrInteractiveModel BrWidgetModel subclass: #BrInteractiveModel instanceVariableNames: 'stateModels' classVariableNames: '' package: 'Brick-! Deprecated' . Please note that disabled button does not react to click and hover events:

buttonWithLabelLookReactingToHoverAndPressAndDisabled
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	aLabelLook
		bold;
		fontName: 'Source Sans Pro';
		fontSize: 20.

	aLabelLook add: (BrInteractiveCommonAptitude new
		default: [ aLabelLook foreground: Color gray ];
		hovered: [ aLabelLook foreground: Color darkGray ];
		pressed: [ aLabelLook foreground: Color black ];
		disabled: [ aLabelLook foreground: Color veryLightGray ]).

	^ self newButton
		disable;
		aptitude: aLabelLook
    

Sometimes users may want to create a look that depends on a logical button size modelled by BrSizeAdjustmentModel BrStateModel subclass: #BrSizeAdjustmentModel instanceVariableNames: 'dimension' classVariableNames: '' package: 'Brick-! Deprecated' . For example a button can be made huge beHuge or tiny beTiny. To style a label based on the logical widget size we can use BrSizeAdjustmentAptitude BrLazyStyleAptitude subclass: #BrSizeAdjustmentAptitude instanceVariableNames: '' classVariableNames: '' package: 'Brick-! Core - Size Adjustment' which provides a fluent api for variuos sizes. Click on the label to randomly change its logial size:

buttonWithLabelLookOfDifferentSizes
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	aLabelLook
		bold;
		fontName: 'Source Sans Pro'.

	aLabelLook add: (BrDeprecatedSizeAdjustmentAptitude new
		default: [ aLabelLook fontSize: 20 ];
		tiny: [ aLabelLook fontSize: 10 ];
		small: [ aLabelLook fontSize: 16 ];
		large: [ aLabelLook fontSize: 26 ];
		huge: [ aLabelLook fontSize: 36 ]).

	^ self newButton
		action: [ :aButton | aButton perform: #(beNormalSize beTinySize beSmallSize beLargeSize beHugeSize) atRandom ];
		beSmallSize;
		aptitude: aLabelLook
    

Now lets put together the final label look that we will use later to build our custom button:

labelLookWithCommonAndSizeInteractions
	<gtExample>
	| aLabelLook |
	
	aLabelLook := BrTextLabelAptitude new.
	aLabelLook
		bold;
		fontName: 'Source Sans Pro'.

	aLabelLook add: (BrInteractiveCommonAptitude new
		default: [ aLabelLook foreground: Color gray ];
		hovered: [ aLabelLook foreground: Color darkGray ];
		pressed: [ aLabelLook foreground: Color black ];
		disabled: [ aLabelLook foreground: Color veryLightGray ]).

	aLabelLook add: (BrDeprecatedSizeAdjustmentAptitude new
		default: [ aLabelLook fontSize: 20 ];
		tiny: [ aLabelLook fontSize: 10 ];
		small: [ aLabelLook fontSize: 16 ];
		large: [ aLabelLook fontSize: 26 ];
		huge: [ aLabelLook fontSize: 36 ]).

	^ aLabelLook
    

Adding and styling the button's exterior

...