1 /*
2 * Copyright 2017 Julian Harnath <julian.harnath@rwth-aachen.de>
3 * All rights reserved. Distributed under the terms of the MIT license.
4 */
5
6
7 #include "BarberPole.h"
8
9 #include <AutoLocker.h>
10 #include <ControlLook.h>
11 #include <Locker.h>
12 #include <ObjectList.h>
13 #include <Messenger.h>
14
15 #include <pthread.h>
16 #include <stdio.h>
17
18
19 // #pragma mark - MachineRoom
20
21
22 /*! The machine room spins all the barber poles.
23 Keeps a list of all barber poles of this team and runs its own
24 thread to invalidate them in regular intervals.
25 */
26 class MachineRoom
27 {
28 private:
29 enum {
30 kSpinInterval = 20000 // us
31 };
32
33 private:
MachineRoom()34 MachineRoom()
35 :
36 fMessengers(20, true)
37 {
38 fSpinLoopLock = create_sem(0, "BarberPole lock");
39 fSpinLoopThread = spawn_thread(&MachineRoom::_StartSpinLoop,
40 "The Barber Machine", B_DISPLAY_PRIORITY, this);
41 resume_thread(fSpinLoopThread);
42 }
43
44 public:
AttachBarberPole(BarberPole * pole)45 static void AttachBarberPole(BarberPole* pole)
46 {
47 _InitializeIfNeeded();
48 sInstance->_Attach(pole);
49 }
50
DetachBarberPole(BarberPole * pole)51 static void DetachBarberPole(BarberPole* pole)
52 {
53 sInstance->_Detach(pole);
54 }
55
56 private:
_Initialize()57 static void _Initialize()
58 {
59 sInstance = new MachineRoom();
60 }
61
_StartSpinLoop(void * instance)62 static status_t _StartSpinLoop(void* instance)
63 {
64 static_cast<MachineRoom*>(instance)->_SpinLoop();
65 return B_OK;
66 }
67
_InitializeIfNeeded()68 static void _InitializeIfNeeded()
69 {
70 pthread_once(&sOnceControl, &MachineRoom::_Initialize);
71 }
72
_Attach(BarberPole * pole)73 void _Attach(BarberPole* pole)
74 {
75 AutoLocker<BLocker> locker(fLock);
76
77 bool wasEmpty = fMessengers.IsEmpty();
78
79 BMessenger* messenger = new BMessenger(pole);
80 fMessengers.AddItem(messenger);
81
82 if (wasEmpty)
83 release_sem(fSpinLoopLock);
84 }
85
_Detach(BarberPole * pole)86 void _Detach(BarberPole* pole)
87 {
88 AutoLocker<BLocker> locker(fLock);
89
90 for (int32 i = 0; i < fMessengers.CountItems(); i++) {
91 BMessenger* messenger = fMessengers.ItemAt(i);
92 if (messenger->Target(NULL) == pole) {
93 fMessengers.RemoveItem(messenger, true);
94 break;
95 }
96 }
97
98 if (fMessengers.IsEmpty())
99 acquire_sem(fSpinLoopLock);
100 }
101
_SpinLoop()102 void _SpinLoop()
103 {
104 for (;;) {
105 AutoLocker<BLocker> locker(fLock);
106
107 for (int32 i = 0; i < fMessengers.CountItems(); i++) {
108 BMessenger* messenger = fMessengers.ItemAt(i);
109 messenger->SendMessage(BarberPole::kRefreshMessage);
110 }
111
112 locker.Unset();
113
114 acquire_sem(fSpinLoopLock);
115 release_sem(fSpinLoopLock);
116
117 snooze(kSpinInterval);
118 }
119 }
120
121 private:
122 static MachineRoom* sInstance;
123 static pthread_once_t sOnceControl;
124
125 thread_id fSpinLoopThread;
126 sem_id fSpinLoopLock;
127
128 BLocker fLock;
129 BObjectList<BMessenger> fMessengers;
130 };
131
132
133 MachineRoom* MachineRoom::sInstance = NULL;
134 pthread_once_t MachineRoom::sOnceControl = PTHREAD_ONCE_INIT;
135
136
137 // #pragma mark - BarberPole
138
139
BarberPole(const char * name)140 BarberPole::BarberPole(const char* name)
141 :
142 BView(name, B_WILL_DRAW | B_FRAME_EVENTS),
143 fIsSpinning(false),
144 fSpinSpeed(0.05),
145 fColors(NULL),
146 fNumColors(0),
147 fScrollOffset(0.0),
148 fStripeWidth(0.0),
149 fNumStripes(0)
150 {
151 // Default colors, chosen from system color scheme
152 rgb_color defaultColors[2];
153 rgb_color otherColor = tint_color(ui_color(B_STATUS_BAR_COLOR), 1.3);
154 otherColor.alpha = 50;
155 defaultColors[0] = otherColor;
156 defaultColors[1] = B_TRANSPARENT_COLOR;
157 SetColors(defaultColors, 2);
158 }
159
160
~BarberPole()161 BarberPole::~BarberPole()
162 {
163 Stop();
164 delete[] fColors;
165 }
166
167
168 void
MessageReceived(BMessage * message)169 BarberPole::MessageReceived(BMessage* message)
170 {
171 switch (message->what) {
172 case kRefreshMessage:
173 _Spin();
174 break;
175
176 default:
177 BView::MessageReceived(message);
178 break;
179 }
180 }
181
182
183 void
Draw(BRect updateRect)184 BarberPole::Draw(BRect updateRect)
185 {
186 if (fIsSpinning)
187 _DrawSpin(updateRect);
188 else
189 _DrawNonSpin(updateRect);
190 }
191
192
193 void
_DrawSpin(BRect updateRect)194 BarberPole::_DrawSpin(BRect updateRect)
195 {
196 // Draw color stripes
197 float position = -fStripeWidth * (fNumColors + 0.5) + fScrollOffset;
198 // Starting position: beginning of the second color cycle
199 // The + 0.5 is so we start out without a partially visible stripe
200 // on the left side (makes it simpler to loop)
201 BRect bounds = Bounds();
202 bounds.InsetBy(-2, -2);
203 be_control_look->DrawStatusBar(this, bounds, updateRect,
204 ui_color(B_PANEL_BACKGROUND_COLOR), ui_color(B_STATUS_BAR_COLOR),
205 bounds.Width());
206 SetDrawingMode(B_OP_ALPHA);
207 uint32 colorIndex = 0;
208 for (uint32 i = 0; i < fNumStripes; i++) {
209 SetHighColor(fColors[colorIndex]);
210 colorIndex++;
211 if (colorIndex >= fNumColors)
212 colorIndex = 0;
213
214 BRect stripeFrame = fStripe.Frame();
215 fStripe.MapTo(stripeFrame,
216 stripeFrame.OffsetToCopy(position, 0.0));
217 FillPolygon(&fStripe);
218
219 position += fStripeWidth;
220 }
221
222 SetDrawingMode(B_OP_COPY);
223 // Draw box around it
224 bounds = Bounds();
225 be_control_look->DrawBorder(this, bounds, updateRect,
226 ui_color(B_PANEL_BACKGROUND_COLOR), B_PLAIN_BORDER);
227 }
228
229
230 /*! This will show something in the place of the spinner when there is no
231 spinning going on. The logic to render the striped background comes
232 from the 'drivesetup' application.
233 */
234
235 void
_DrawNonSpin(BRect updateRect)236 BarberPole::_DrawNonSpin(BRect updateRect)
237 {
238 // approach copied from the DiskSetup application.
239 static const pattern kStripes = { { 0xc7, 0x8f, 0x1f, 0x3e, 0x7c,
240 0xf8, 0xf1, 0xe3 } };
241 BRect bounds = Bounds();
242 SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
243 SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
244 FillRect(bounds, kStripes);
245 be_control_look->DrawBorder(this, bounds, updateRect,
246 ui_color(B_PANEL_BACKGROUND_COLOR), B_PLAIN_BORDER);
247 }
248
249
250 void
FrameResized(float width,float height)251 BarberPole::FrameResized(float width, float height)
252 {
253 // Choose stripe width so that at least 2 full stripes fit into the view,
254 // but with a minimum of 5px. Larger views get wider stripes, but they
255 // grow slower than the view and are capped to a maximum of 200px.
256 fStripeWidth = (width / 4) + 5;
257 if (fStripeWidth > 200)
258 fStripeWidth = 200;
259
260 BPoint stripePoints[4];
261 stripePoints[0].Set(fStripeWidth * 0.5, 0.0); // top left
262 stripePoints[1].Set(fStripeWidth * 1.5, 0.0); // top right
263 stripePoints[2].Set(fStripeWidth, height); // bottom right
264 stripePoints[3].Set(0.0, height); // bottom left
265
266 fStripe = BPolygon(stripePoints, 4);
267
268 fNumStripes = (int32)ceilf((width) / fStripeWidth) + 1 + fNumColors;
269 // Number of color stripes drawn in total for the barber pole, the
270 // user-visible part is a "window" onto the complete pole. We need
271 // as many stripes as are visible, an extra one on the right side
272 // (will be partially visible, that's the + 1); and then a whole color
273 // cycle of strips extra which we scroll into until we loop.
274 //
275 // Example with 3 colors and a visible area of 2*fStripeWidth (which means
276 // that 2 will be fully visible, and a third one partially):
277 // ........
278 // X___________v______v___
279 // / 1 / 2 / 3 / 1 / 2 / 3 /
280 // `````````````````````````
281 // Pole is scrolled to the right into the visible region, which is marked
282 // between the two 'v'. Once the left edge of the visible area reaches
283 // point X, we can jump back to the initial region position.
284 }
285
286
287 BSize
MinSize()288 BarberPole::MinSize()
289 {
290 BSize result = BView::MinSize();
291
292 if (result.width < 50)
293 result.SetWidth(50);
294
295 if (result.height < 5)
296 result.SetHeight(5);
297
298 return result;
299 }
300
301
302 void
Start()303 BarberPole::Start()
304 {
305 if (fIsSpinning)
306 return;
307 MachineRoom::AttachBarberPole(this);
308 fIsSpinning = true;
309 }
310
311
312 void
Stop()313 BarberPole::Stop()
314 {
315 if (!fIsSpinning)
316 return;
317 MachineRoom::DetachBarberPole(this);
318 fIsSpinning = false;
319 Invalidate();
320 }
321
322
323 void
SetSpinSpeed(float speed)324 BarberPole::SetSpinSpeed(float speed)
325 {
326 if (speed > 1.0f)
327 speed = 1.0f;
328 if (speed < -1.0f)
329 speed = -1.0f;
330 fSpinSpeed = speed;
331 }
332
333
334 void
SetColors(const rgb_color * colors,uint32 numColors)335 BarberPole::SetColors(const rgb_color* colors, uint32 numColors)
336 {
337 delete[] fColors;
338 rgb_color* colorsCopy = new rgb_color[numColors];
339 for (uint32 i = 0; i < numColors; i++)
340 colorsCopy[i] = colors[i];
341
342 fColors = colorsCopy;
343 fNumColors = numColors;
344 }
345
346
347 void
_Spin()348 BarberPole::_Spin()
349 {
350 fScrollOffset += fStripeWidth / (1.0f / fSpinSpeed);
351 if (fScrollOffset >= fStripeWidth * fNumColors) {
352 // Cycle completed, jump back to where we started
353 fScrollOffset = 0;
354 }
355 Invalidate();
356 //Parent()->Invalidate();
357 }
358