Cocoa Internals/Buttons

From Lazarus wiki

Buttons Map

Despite of being a very basic control, buttons are complicated topic on macOS.

LCL Button macOS Button / Style Description
TButton Push Button

NSRoundedBezelStyle

Per macOS design guidelines, Push buttons should only have labels on them, and no Icons. This is exactly, how LCL TButton behaves.

The biggest issue is that macOS Push Buttons are of a fixed height. While LCL buttons can be any height.

The approach similar to the Carbon implementation could be used - after a certain height the button changes its bezel. currently r56760 it's disabled

TToggleBox Push Button

NSRoundedBezelStyle with NSButtonTypePushOnPushOff type

In order for ToggleBox to look exactly as TPushButton the same style is used.

However, Cocoa button Type is different. (It's required for the button to be drawn differently when "pressed".)

Prior to r62250 the bezel style was NSTexturedRoundedBezelStyle, with NSToggleButton for button type.

It's possible to use the same settings on revisions after 62250. All you need to do is modify global variables value of CocoaInt unit CocoaToggleBezel (which should be desired bezel) and CocoaToggleType (which should be the desired Cocoa button type for the toggle button). It's recommended to override them in the "initialization" section, prior to any button handle being created.

TBitBtn Image Button

NSRegularSquareBezelStyle

TBitBtn is a button that could hold an image in it's body.

The closest (not deprecated) to such tasks is NSRegularSquareBezelStyle in macOS

TBitBtn could also be used as a replacement for TButton. I.e. TBitBtn could be a "Default" button on a modal dialog. And there's no corresponding replacement for that in macOS

TSpeedButton This is not an actual control button, it's LCL-drawn button.

todo: no themes API customdrawn controls used?

The font used for caption of the button is NSFont.systemFontOfSize

Radio Buttons

From stackoverflow

NOTE: Use of NSMatrix is discouraged in apps that run in Mountain Lion 10.8 and later. If you need to create a radio button group in an app that runs in Mountain Lion 10.8 and later, create instances of NSButton that each specify a button type of NSRadioButton and specify the same action and the same superview for each button in the group.

If all buttons call the same action method and are in the same superview, Cocoa automatically selects the clicked button and deselects the previous button and -- no code is necessary to do that.

TrackBar

TTrackBar implementation is based on top of NSSlider class.

NSSlider doesn't have a defined property for vertical or horizontal. It draws itself depending on which size (height or width) is larger than the other. Determining of the preferred size also uses the current TTrackBar dimension. Thus the frame rect might needs to be updated to match the selected orientation.

Autosizing allows it to resize one of the dimensions. (For Horizontal oriented trackbar, the height cannot be changed, while width can.)

NSSlider doesn't support custom tick marks. Those are drawn by the TCocoaSlider class (refer to drawRect:) method.

Textured styles

There are number of styles for buttons named "Textured" (i.e. NSTexturedRoundedBezelStyle, NSTexturedSquareBezelStyle). These buttons a designed to be used in "Metal" aka "Textured" style windows, and Window Frames (borders/tool bars) Thus these styles should not be used.

To see the all styles of buttons available you can use the following example: https://github.com/skalogryz/lcltests/tree/master/cocoabuttonsmatrix

Customize Style

In order to change a style of a button, one should change the button bezel (in some cases type also needs to be changed). (i.e. the most common style change is to show the proper "HELP" button. Which has a specific style in Cocoa)

uses
  ...
  {$ifdef LCLCocoa} 
  ,CocoaAll 
  {$endif}

var
  btn : TButton; // the same approach will work for TToggleBox and TBitBtn

  if not btn.HandleAllocated then btn.HandleNeeded;
  NSbutton(btn.Handle).setButtonType(NSHelpButtonBezelStyle);
  NSbutton(btn.Handle).setBezelStyle(NSMomentaryLightButton);

Such approach is useful when changing button style on case-by-case basis. One should keep in mind, that if buttons handle is recreated, the same steps will need to be completed again.

Styles and Types deprecation

A number of bezel styles and button types have been deprecated from macOS API. (i.e. NSHelpButtonBezelStyle). In fact it's only name of the constant is deprecated. The constants has been actually renamed from NSxxxxxBezelStyle to NSBezelStylexxxxx. (i.e. NSHelpButtonBezelStyle to NSBezelStyleHelpButton)

Sizing

Cocoa vs Carbon

Styles are for Cocoa and Carbon are a little bit different for buttons. For example "default" height of Push button is different between Cocoa and Carbon. Thus a UI designed for Carbon might not look good for Cocoa.

This is a problem of buttons only, other rectangular controls (textbox, listbox) doesn't experience such problem.

Control Sizes

Most buttons in macOS design have fixed height (i.e. Push Button). For such buttons macOS API provides property "controlSize". Buttons are available in 3 variations: regular, small, mini. Each variant paints in its own manner:

cocoa button sizes.png

Since Lazarus r56773, the widgetset auto detects one of 3 variants from the button's Height.

Prior to that only "regular" size was used, causing visual artifacts, if height of a button was smaller than expected by controlSize.

Also, the font of a button is forced to match the detected controlSize. (Todo: should do it, only if standard font is selected.)

Example of artifact, if button's Height is 21 (it must be 32). Note that top edge is clipped, and some blue area is painted.

cocoa button height arterfact.png

Smart Style Selection

In macOS guidelines, style of a button should be used depending on the placement/usage of a button.

In LCL design, there's not such thing as "style". It's presumed that the button would look the same, no matter where on the screen it's or what the button's parent. (It's not uncommon to design the interface in such a way).

Thus some LCLs might look foreign to macOS native applications, just because wrong button styles are used. I.e. a push button is used in a tool bar.

There are a few approaches what could be used:

  • adding new TxxxButton classes into LCL (quite wasteful, and might not be applicable for other OSes)
  • adding a style property to TBitBtn button (non delphi compatible)
  • changes styles within Cocoa widgetset, automatically, depending on the placement of the button.

Ampersand in Captions

LCL is working in Windows compatible mode, where a button can have a hot key assigned by marking a desired letter with an ampersand in front of it.

For example

&Open

turns into

Open

A user can hit Alt+O to press the button.

macOS no longer have the similar concept. (Those was known as mnemonic, but APIs for mnemonics are deprecated).

LCL assigns button captions with the ampersand in it, and those needs to be removed prior to assigning to the actual button. (see ControlTitleToStr at CocoaUtils.pas) LCL also expects the "text" of the control to return the value WITH ampersand, for this reason GetText() for buttons returns FALSE. Indicating that LCL should use the cached "fCaption" value.

See Also