xref: /haiku/src/kits/shared/BarberPole.cpp (revision 0ba60bc7432a10047b3ba46667ccb692c2c7c1a1)
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