macOS Progress Indicators: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: macOS Progress Indicators
| Old | New | Line | |
|---|---|---|---|
| 1 | + | *(macOS only)* | |
| 2 | + | ||
| 3 | + | ## Overview | |
| 4 | + | ||
| 5 | + | Progress indicators can be *determinate* or *indeterminate*. A determinate indicator displays the completion percentage of a task. An indeterminate indicator shows that the application is busy without providing a visual indication of how long the task will take. | |
| 6 | + | ||
| 7 | + | The NSProgressIndicator class is an interface that provides visual feedback to the user about the status of an ongoing task and is part of the AppKit framework. The class is available in all versions of macOS since Mac OS X 10.0 (Cheetah). The *style* (spinning or bar), *sizeToFit* (resize based on style) and *isDisplayedWhenStopped* (hide when not active) properties were introduced in Mac OS X 10.2 (Jaguar). | |
| 8 | + | ||
| 9 | + | ## Example code | |
| 10 | + | ||
| 11 | + |  | |
| 12 | + | ||
| 13 | + | The code below implements the four progress indicators annotated in the above image: the determinate progress indicators bar and circle and the indeterminate progress indicators spinner and bar2. | |
| 14 | + | ||
| 15 | + | ``` pascal | |
| 16 | + | unit Unit1; | |
| 17 | + | ||
| 18 | + | {$mode objfpc}{$H+} | |
| 19 | + | {$modeswitch objectivec1} | |
| 20 | + | {$linkframework appkit} | |
| 21 | + | ||
| 22 | + | interface | |
| 23 | + | ||
| 24 | + | uses | |
| 25 | + | Forms, Controls, StdCtrls, | |
| 26 | + | ExtCtrls, // for timers | |
| 27 | + | CocoaAll, // for Cocoa vars etc | |
| 28 | + | MacOSAll, // for data types eg CGFloat | |
| 29 | + | CTypes; // for data types eg CUInt | |
| 30 | + | ||
| 31 | + | type | |
| 32 | + | ||
| 33 | + | NSProgressIndicator = objcclass external (NSView) | |
| 34 | + | private | |
| 35 | + | _isBezeled: ObjCBOOL; | |
| 36 | + | _isIndeterminate: ObjCBOOL; | |
| 37 | + | _threadedAnimation: ObjCBOOL; | |
| 38 | + | _minimum: double; | |
| 39 | + | _maximum: double; | |
| 40 | + | _value: double; | |
| 41 | + | _animationIndex: cuint; | |
| 42 | + | _animationDelay: NSTimeInterval; | |
| 43 | + | _timer: id; | |
| 44 | + | _drawingWidth: CGFloat; | |
| 45 | + | _roundColor: id; | |
| 46 | + | _reserved: id; | |
| 47 | + | __progressIndicatorFlags: bitpacked record | |
| 48 | + | case byte of | |
| 49 | + | 0: (_anonBitField___progressIndicatorFlags0: cuint); | |
| 50 | + | 1: ( | |
| 51 | + | isSpinning: 0..1; | |
| 52 | + | isVector: 0..1; | |
| 53 | + | isLocked: 0..1; | |
| 54 | + | controlTint: 0..((1 shl 3)-1); | |
| 55 | + | controlSize: 0..((1 shl 2)-1); | |
| 56 | + | style: 0..1; | |
| 57 | + | _delayedStartup: 0..1; | |
| 58 | + | hideWhenStopped: 0..1; | |
| 59 | + | revive: 0..1; | |
| 60 | + | _temporarilyBlockHeartBeating: 0..1; | |
| 61 | + | _isHidden: 0..1; | |
| 62 | + | _isHeartBeatInstalled: 0..1; | |
| 63 | + | _customRenderer: 0..1; | |
| 64 | + | _lastFrame: 0..((1 shl 8)-1); | |
| 65 | + | _isDetaching: 0..1; | |
| 66 | + | RESERVED: 0..((1 shl 7)-1); | |
| 67 | + | ); | |
| 68 | + | end; | |
| 69 | + | _NSProgressIndicatorReserved1: id; | |
| 70 | + | public | |
| 71 | + | procedure setIndeterminate(newValue: ObjCBOOL); message 'setIndeterminate:'; | |
| 72 | + | function isIndeterminate: ObjCBOOL; message 'isIndeterminate'; | |
| 73 | + | procedure setBezeled(newValue: ObjCBOOL); message 'setBezeled:'; | |
| 74 | + | function isBezeled: ObjCBOOL; message 'isBezeled'; | |
| 75 | + | procedure setControlTint(newValue: NSControlTint); message 'setControlTint:'; | |
| 76 | + | function controlTint: NSControlTint; message 'controlTint'; | |
| 77 | + | procedure setControlSize(newValue: NSControlSize); message 'setControlSize:'; | |
| 78 | + | function controlSize: NSControlSize; message 'controlSize'; | |
| 79 | + | procedure setDoubleValue(newValue: double); message 'setDoubleValue:'; | |
| 80 | + | function doubleValue: double; message 'doubleValue'; | |
| 81 | + | procedure incrementBy (delta: double); message 'incrementBy:'; | |
| 82 | + | procedure setMinValue(newValue: double); message 'setMinValue:'; | |
| 83 | + | function minValue: double; message 'minValue'; | |
| 84 | + | procedure setMaxValue(newValue: double); message 'setMaxValue:'; | |
| 85 | + | function maxValue: double; message 'maxValue'; | |
| 86 | + | procedure setUsesThreadedAnimation(newValue: ObjCBOOL); message 'setUsesThreadedAnimation:'; | |
| 87 | + | function usesThreadedAnimation: ObjCBOOL; message 'usesThreadedAnimation'; | |
| 88 | + | procedure startAnimation (sender: id); message 'startAnimation:'; | |
| 89 | + | procedure stopAnimation (sender: id); message 'stopAnimation:'; | |
| 90 | + | procedure setStyle(newValue: NSProgressIndicatorStyle); message 'setStyle:'; | |
| 91 | + | function style: NSProgressIndicatorStyle; message 'style'; | |
| 92 | + | procedure sizeToFit; message 'sizeToFit'; | |
| 93 | + | procedure setDisplayedWhenStopped(newValue: ObjCBOOL); message 'setDisplayedWhenStopped:'; | |
| 94 | + | function isDisplayedWhenStopped: ObjCBOOL; message 'isDisplayedWhenStopped'; | |
| 95 | + | end; | |
| 96 | + | ||
| 97 | + | { TForm1 } | |
| 98 | + | ||
| 99 | + | TForm1 = class(TForm) | |
| 100 | + | Button1: TButton; | |
| 101 | + | SpinnerButton: TButton; | |
| 102 | + | CircleButton: TButton; | |
| 103 | + | Bar2Button: TButton; | |
| 104 | + | StopButton: TButton; | |
| 105 | + | BarTimer: TTimer; | |
| 106 | + | CircleTimer: TTimer; | |
| 107 | + | procedure Button1Click(Sender: TObject); | |
| 108 | + | procedure Bar2ButtonClick(Sender: TObject); | |
| 109 | + | procedure CircleTimerUpdate(Sender: TObject); | |
| 110 | + | procedure SpinnerButtonClick(Sender: TObject); | |
| 111 | + | procedure CircleButtonClick(Sender: TObject); | |
| 112 | + | procedure StopButtonClick(Sender: TObject); | |
| 113 | + | procedure FormActivate(Sender: TObject); | |
| 114 | + | procedure BarTimerUpdate(Sender: TObject); | |
| 115 | + | private | |
| 116 | + | ||
| 117 | + | public | |
| 118 | + | ||
| 119 | + | end; | |
| 120 | + | ||
| 121 | + | var | |
| 122 | + | Form1: TForm1; | |
| 123 | + | myIndicatorBar: NSProgressIndicator; | |
| 124 | + | myCircleBar: NSProgressIndicator; | |
| 125 | + | myIndicatorBar2: NSProgressIndicator; | |
| 126 | + | mySpinner: NSProgressIndicator; | |
| 127 | + | myView: NSView; | |
| 128 | + | ||
| 129 | + | implementation | |
| 130 | + | ||
| 131 | + | {$R *.lfm} | |
| 132 | + | ||
| 133 | + | { TForm1 } | |
| 134 | + | ||
| 135 | + | // Determinate bar | |
| 136 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 137 | + | begin | |
| 138 | + | myIndicatorBar := NSProgressIndicator.alloc.initWithFrame(NSMakeRect(30,20,200,20)); // bar | |
| 139 | + | myIndicatorBar.setMinValue(0); | |
| 140 | + | myIndicatorBar.setMaxValue(30); | |
| 141 | + | myIndicatorBar.setStyle(0); // 1 = circle; 0 = bar | |
| 142 | + | myIndicatorBar.setIndeterminate(False); | |
| 143 | + | myView.addSubview(myIndicatorBar); | |
| 144 | + | BarTimer.enabled := True; | |
| 145 | + | end; | |
| 146 | + | ||
| 147 | + | // Determinate circle | |
| 148 | + | procedure TForm1.CircleButtonClick(Sender: TObject); | |
| 149 | + | begin | |
| 150 | + | myCircleBar := NSProgressIndicator.alloc.initWithFrame(NSMakeRect(10,30,80,80)); // circle | |
| 151 | + | myCircleBar.setStyle(1); // 1 = circle; 0 = bar | |
| 152 | + | myCircleBar.setIndeterminate(False); | |
| 153 | + | myView.addSubview(myCircleBar); | |
| 154 | + | CircleTimer.enabled := True; | |
| 155 | + | end; | |
| 156 | + | ||
| 157 | + | // Indeterminate spinner | |
| 158 | + | procedure TForm1.SpinnerButtonClick(Sender: TObject); | |
| 159 | + | begin | |
| 160 | + | mySpinner := NSProgressIndicator.alloc.initWithFrame(NSMakeRect(130,80,40,40)); | |
| 161 | + | mySpinner.setIndeterminate(True); | |
| 162 | + | mySpinner.setStyle(1); // 1 = spinner; 0 = bouncing bar | |
| 163 | + | mySpinner.setUsesThreadedAnimation(True); | |
| 164 | + | mySpinner.setDisplayedWhenStopped(False); | |
| 165 | + | mySpinner.startAnimation(Nil); | |
| 166 | + | myView.addSubview(mySpinner); | |
| 167 | + | end; | |
| 168 | + | ||
| 169 | + | // Indeterminate bar2 | |
| 170 | + | procedure TForm1.Bar2ButtonClick(Sender: TObject); | |
| 171 | + | begin | |
| 172 | + | myIndicatorBar2 := NSProgressIndicator.alloc.initWithFrame(NSMakeRect(30,130,200,20)); | |
| 173 | + | myIndicatorBar2.setIndeterminate(True); | |
| 174 | + | myIndicatorBar2.setStyle(0); // 1 = spinner; 0 = bouncing bar | |
| 175 | + | myIndicatorBar2.setUsesThreadedAnimation(True); | |
| 176 | + | myIndicatorBar2.setDisplayedWhenStopped(False); | |
| 177 | + | myIndicatorBar2.startAnimation(Nil); | |
| 178 | + | myView.addSubview(myIndicatorBar2); | |
| 179 | + | end; | |
| 180 | + | ||
| 181 | + | // Stop all | |
| 182 | + | procedure TForm1.StopButtonClick(Sender: TObject); | |
| 183 | + | begin | |
| 184 | + | BarTimer.Enabled := False; | |
| 185 | + | CircleTimer.Enabled := False; | |
| 186 | + | mySpinner.stopAnimation(Nil); | |
| 187 | + | myCircleBar.setDoubleValue(0); | |
| 188 | + | myIndicatorBar.setDoubleValue(0); | |
| 189 | + | myIndicatorBar2.stopAnimation(Nil); | |
| 190 | + | end; | |
| 191 | + | ||
| 192 | + | // Get the form's view once for use later | |
| 193 | + | procedure TForm1.FormActivate(Sender: TObject); | |
| 194 | + | begin | |
| 195 | + | myView := NSView(Form1.Handle); | |
| 196 | + | end; | |
| 197 | + | ||
| 198 | + | // Update bar progress | |
| 199 | + | procedure TForm1.BarTimerUpdate(Sender: TObject); | |
| 200 | + | begin | |
| 201 | + | myIndicatorBar.incrementBy(0.1); | |
| 202 | + | end; | |
| 203 | + | ||
| 204 | + | // Update circle progress | |
| 205 | + | procedure TForm1.CircleTimerUpdate(Sender: TObject); | |
| 206 | + | begin | |
| 207 | + | myCircleBar.incrementBy(0.5); | |
| 208 | + | end; | |
| 209 | + | ||
| 210 | + | finalization | |
| 211 | + | ||
| 212 | + | // Cleanup | |
| 213 | + | myIndicatorBar.release; | |
| 214 | + | myCircleBar.release; | |
| 215 | + | mySpinner.release; | |
| 216 | + | myIndicatorBar2.release; | |
| 217 | + | ||
| 218 | + | end. | |
| 219 | + | ``` | |
| 220 | + | ||
| 221 | + | Full project source code is available from [SourceForge](https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/laz_progress_indicators/). | |
| 222 | + | ||
| 223 | + | ## See also | |
| 224 | + | ||
| 225 | + | - [[Sample Graphics|BGRAControls BGRAFlashProgressBar control]] | |
| 226 | + | - [[KControls|KControls TKPercentProgressBar control]] | |
| 227 | + | - [[JVCL Components|JVCL Components JvSpecialProgress component]] | |
| 228 | + | ||
| 229 | + | ## External links | |
| 230 | + | ||
| 231 | + | - [Apple: NSProgressIndicator](https://developer.apple.com/documentation/appkit/nsprogressindicator) | |
| 232 | + | ||
| 233 | + | --- | |
| 234 | + | *From the Free Pascal wiki, page "macOS Progress Indicators", as it stood on 2021-11-20 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 235 | + | Category: Code | |
| 236 | + | Category: macOS |
