text: /*
   * Copyright 2017 Julian Harnath <julian.harnath@rwth-aachen.de>
   * All rights reserved. Distributed under the terms of the MIT license.
   */
  
  
  #include "BarberPole.h"
  
  #include <AutoLocker.h>
  #include <ControlLook.h>
  #include <Locker.h>
  #include <ObjectList.h>
  #include <Messenger.h>
  
  #include <pthread.h>
  #include <stdio.h>
  
  
  // #pragma mark - MachineRoom
  
  
  /*! The machine room spins all the barber poles.
      Keeps a list of all barber poles of this team and runs its own
      thread to invalidate them in regular intervals.
  */
  class MachineRoom
  {
  private:
  	enum {
  		kSpinInterval = 20000 // us
  	};
  
  private:
  	MachineRoom()
  		:
  		fMessengers(20, true)
  	{
  		fSpinLoopLock = create_sem(0, "BarberPole lock");
  		fSpinLoopThread = spawn_thread(&MachineRoom::_StartSpinLoop,
  			"The Barber Machine", B_DISPLAY_PRIORITY, this);
  		resume_thread(fSpinLoopThread);
  	}
  
  public:
  	static void AttachBarberPole(BarberPole* pole)
  	{
  		_InitializeIfNeeded();
  		sInstance->_Attach(pole);
  	}
  
  	static void DetachBarberPole(BarberPole* pole)
  	{
  		sInstance->_Detach(pole);
  	}
  
  private:
  	static void _Initialize()
  	{
  		sInstance = new MachineRoom();
  	}
  
  	static status_t _StartSpinLoop(void* instance)
  	{
  		static_cast<MachineRoom*>(instance)->_SpinLoop();
  		return B_OK;
  	}
  
  	static void _InitializeIfNeeded()
  	{
  		pthread_once(&sOnceControl, &MachineRoom::_Initialize);
  	}
  
  	void _Attach(BarberPole* pole)
  	{
  		AutoLocker<BLocker> locker(fLock);
  
  		bool wasEmpty = fMessengers.IsEmpty();
  
  		BMessenger* messenger = new BMessenger(pole);
  		fMessengers.AddItem(messenger);
  
  		if (wasEmpty)
  			release_sem(fSpinLoopLock);
  	}
  
  	void _Detach(BarberPole* pole)
  	{
  		AutoLocker<BLocker> locker(fLock);
  
  		for (int32 i = 0; i < fMessengers.CountItems(); i++) {
  			BMessenger* messenger = fMessengers.ItemAt(i);
  			if (messenger->Target(NULL) == pole) {
  				fMessengers.RemoveItem(messenger, true);
  				break;
  			}
  		}
  
  		if (fMessengers.IsEmpty())
  			acquire_sem(fSpinLoopLock);
  	}
  
  	void _SpinLoop()
  	{
  		for (;;) {
  			AutoLocker<BLocker> locker(fLock);
  
  			for (int32 i = 0; i < fMessengers.CountItems(); i++) {
  				BMessenger* messenger = fMessengers.ItemAt(i);
  				messenger->SendMessage(BarberPole::kRefreshMessage);
  			}
  
  			locker.Unset();
  
  			acquire_sem(fSpinLoopLock);
  			release_sem(fSpinLoopLock);
  
  			snooze(kSpinInterval);
  		}
  	}
  
  private:
  	static MachineRoom*		sInstance;
  	static pthread_once_t	sOnceControl;
  
  	thread_id				fSpinLoopThread;
  	sem_id					fSpinLoopLock;
  
  	BLocker					fLock;
  	BObjectList<BMessenger>	fMessengers;
  };
  
  
  MachineRoom* MachineRoom::sInstance = NULL;
  pthread_once_t MachineRoom::sOnceControl = PTHREAD_ONCE_INIT;
  
  
  // #pragma mark - BarberPole
  
  
  BarberPole::BarberPole(const char* name)
  	:
  	BView(name, B_WILL_DRAW | B_FRAME_EVENTS),
  	fIsSpinning(false),
  	fSpinSpeed(0.05),
  	fColors(NULL),
  	fNumColors(0),
  	fScrollOffset(0.0),
  	fStripeWidth(0.0),
  	fNumStripes(0)
  {
  	// Default colors, chosen from system color scheme
  	rgb_color defaultColors[2];
  	rgb_color otherColor = tint_color(ui_color(B_STATUS_BAR_COLOR), 1.3);
  	otherColor.alpha = 50;
  	defaultColors[0] = otherColor;
  	defaultColors[1] = B_TRANSPARENT_COLOR;
  	SetColors(defaultColors, 2);
  }
  
  
  BarberPole::~BarberPole()
  {
  	Stop();
  	delete[] fColors;
  }
  
  
  void
  BarberPole::MessageReceived(BMessage* message)
  {
  	switch (message->what) {
  		case kRefreshMessage:
  			_Spin();
  			break;
  
  		default:
  			BView::MessageReceived(message);
  			break;
  	}
  }
  
  
  void
  BarberPole::Draw(BRect updateRect)
  {
  	if (fIsSpinning)
  		_DrawSpin(updateRect);
  	else
  		_DrawNonSpin(updateRect);
  }
  
  
  void
  BarberPole::_DrawSpin(BRect updateRect)
  {
  	// Draw color stripes
  	float position = -fStripeWidth * (fNumColors + 0.5) + fScrollOffset;
  		// Starting position: beginning of the second color cycle
  		// The + 0.5 is so we start out without a partially visible stripe
  		// on the left side (makes it simpler to loop)
  	BRect bounds = Bounds();
  	bounds.InsetBy(-2, -2);
  	be_control_look->DrawStatusBar(this, bounds, updateRect,
  		ui_color(B_PANEL_BACKGROUND_COLOR), ui_color(B_STATUS_BAR_COLOR),
  		bounds.Width());
  	SetDrawingMode(B_OP_ALPHA);
  	uint32 colorIndex = 0;
  	for (uint32 i = 0; i < fNumStripes; i++) {
  		SetHighColor(fColors[colorIndex]);
  		colorIndex++;
  		if (colorIndex >= fNumColors)
  			colorIndex = 0;
  
  		BRect stripeFrame = fStripe.Frame();
  		fStripe.MapTo(stripeFrame,
  			stripeFrame.OffsetToCopy(position, 0.0));
  		FillPolygon(&fStripe);
  
  		position += fStripeWidth;
  	}
  
  	SetDrawingMode(B_OP_COPY);
  	// Draw box around it
  	bounds = Bounds();
  	be_control_look->DrawBorder(this, bounds, updateRect,
  		ui_color(B_PANEL_BACKGROUND_COLOR), B_PLAIN_BORDER);
  }
  
  
  /*! This will show something in the place of the spinner when there is no
      spinning going on.  The logic to render the striped background comes
      from the 'drivesetup' application.
  */
  
  void
  BarberPole::_DrawNonSpin(BRect updateRect)
  {
  	// approach copied from the DiskSetup application.
  	static const pattern kStripes = { { 0xc7, 0x8f, 0x1f, 0x3e, 0x7c,
  		0xf8, 0xf1, 0xe3 } };
  	BRect bounds = Bounds();
  	SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
  	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
  	FillRect(bounds, kStripes);
  	be_control_look->DrawBorder(this, bounds, updateRect,
  		ui_color(B_PANEL_BACKGROUND_COLOR), B_PLAIN_BORDER);
  }
  
  
  void
  BarberPole::FrameResized(float width, float height)
  {
  	// Choose stripe width so that at least 2 full stripes fit into the view,
  	// but with a minimum of 5px. Larger views get wider stripes, but they
  	// grow slower than the view and are capped to a maximum of 200px.
  	fStripeWidth = (width / 4) + 5;
  	if (fStripeWidth > 200)
  		fStripeWidth = 200;
  
  	BPoint stripePoints[4];
  	stripePoints[0].Set(fStripeWidth * 0.5, 0.0); // top left
  	stripePoints[1].Set(fStripeWidth * 1.5, 0.0); // top right
  	stripePoints[2].Set(fStripeWidth, height);    // bottom right
  	stripePoints[3].Set(0.0, height);             // bottom left
  
  	fStripe = BPolygon(stripePoints, 4);
  
  	fNumStripes = (int32)ceilf((width) / fStripeWidth) + 1 + fNumColors;
  		// Number of color stripes drawn in total for the barber pole, the
  		// user-visible part is a "window" onto the complete pole. We need
  		// as many stripes as are visible, an extra one on the right side
  		// (will be partially visible, that's the + 1); and then a whole color
  		// cycle of strips extra which we scroll into until we loop.
  		//
  		// Example with 3 colors and a visible area of 2*fStripeWidth (which means
  		// that 2 will be fully visible, and a third one partially):
  		//               ........
  		//   X___________v______v___
  		//  / 1 / 2 / 3 / 1 / 2 / 3 /
  		//  `````````````````````````
  		// Pole is scrolled to the right into the visible region, which is marked
  		// between the two 'v'. Once the left edge of the visible area reaches
  		// point X, we can jump back to the initial region position.
  }
  
  
  BSize
  BarberPole::MinSize()
  {
  	BSize result = BView::MinSize();
  
  	if (result.width < 50)
  		result.SetWidth(50);
  
  	if (result.height < 5)
  		result.SetHeight(5);
  
  	return result;
  }
  
  
  void
  BarberPole::Start()
  {
  	if (fIsSpinning)
  		return;
  	MachineRoom::AttachBarberPole(this);
  	fIsSpinning = true;
  }
  
  
  void
  BarberPole::Stop()
  {
  	if (!fIsSpinning)
  		return;
  	MachineRoom::DetachBarberPole(this);
  	fIsSpinning = false;
  	Invalidate();
  }
  
  
  void
  BarberPole::SetSpinSpeed(float speed)
  {
  	if (speed > 1.0f)
  		speed = 1.0f;
  	if (speed < -1.0f)
  		speed = -1.0f;
  	fSpinSpeed = speed;
  }
  
  
  void
  BarberPole::SetColors(const rgb_color* colors, uint32 numColors)
  {
  	delete[] fColors;
  	rgb_color* colorsCopy = new rgb_color[numColors];
  	for (uint32 i = 0; i < numColors; i++)
  		colorsCopy[i] = colors[i];
  
  	fColors = colorsCopy;
  	fNumColors = numColors;
  }
  
  
  void
  BarberPole::_Spin()
  {
  	fScrollOffset += fStripeWidth / (1.0f / fSpinSpeed);
  	if (fScrollOffset >= fStripeWidth * fNumColors) {
  		// Cycle completed, jump back to where we started
  		fScrollOffset = 0;
  	}
  	Invalidate();
  	//Parent()->Invalidate();
  }
  