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*/
26class MachineRoom
27{
28private:
29	enum {
30		kSpinInterval = 20000 // us
31	};
32
33private:
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
44public:
45	static void AttachBarberPole(BarberPole* pole)
46	{
47		_InitializeIfNeeded();
48		sInstance->_Attach(pole);
49	}
50
51	static void DetachBarberPole(BarberPole* pole)
52	{
53		sInstance->_Detach(pole);
54	}
55
56private:
57	static void _Initialize()
58	{
59		sInstance = new MachineRoom();
60	}
61
62	static status_t _StartSpinLoop(void* instance)
63	{
64		static_cast<MachineRoom*>(instance)->_SpinLoop();
65		return B_OK;
66	}
67
68	static void _InitializeIfNeeded()
69	{
70		pthread_once(&sOnceControl, &MachineRoom::_Initialize);
71	}
72
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
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
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
121private:
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
133MachineRoom* MachineRoom::sInstance = NULL;
134pthread_once_t MachineRoom::sOnceControl = PTHREAD_ONCE_INIT;
135
136
137// #pragma mark - BarberPole
138
139
140BarberPole::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
161BarberPole::~BarberPole()
162{
163	Stop();
164	delete[] fColors;
165}
166
167
168void
169BarberPole::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
183void
184BarberPole::Draw(BRect updateRect)
185{
186	if (fIsSpinning)
187		_DrawSpin(updateRect);
188	else
189		_DrawNonSpin(updateRect);
190}
191
192
193void
194BarberPole::_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
235void
236BarberPole::_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
250void
251BarberPole::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
287BSize
288BarberPole::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
302void
303BarberPole::Start()
304{
305	if (fIsSpinning)
306		return;
307	MachineRoom::AttachBarberPole(this);
308	fIsSpinning = true;
309}
310
311
312void
313BarberPole::Stop()
314{
315	if (!fIsSpinning)
316		return;
317	MachineRoom::DetachBarberPole(this);
318	fIsSpinning = false;
319	Invalidate();
320}
321
322
323void
324BarberPole::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
334void
335BarberPole::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
347void
348BarberPole::_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