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: 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: 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 56 private: 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 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 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 161 BarberPole::~BarberPole() 162 { 163 Stop(); 164 delete[] fColors; 165 } 166 167 168 void 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 184 BarberPole::Draw(BRect updateRect) 185 { 186 if (fIsSpinning) 187 _DrawSpin(updateRect); 188 else 189 _DrawNonSpin(updateRect); 190 } 191 192 193 void 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 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 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 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 303 BarberPole::Start() 304 { 305 if (fIsSpinning) 306 return; 307 MachineRoom::AttachBarberPole(this); 308 fIsSpinning = true; 309 } 310 311 312 void 313 BarberPole::Stop() 314 { 315 if (!fIsSpinning) 316 return; 317 MachineRoom::DetachBarberPole(this); 318 fIsSpinning = false; 319 Invalidate(); 320 } 321 322 323 void 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 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 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