1 /* 2 * Copyright 2004-2009, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Mike Berg <mike@berg-net.us> 7 * Julun <host.haiku@gmx.de> 8 * Stephan Aßmus <superstippi@gmx.de> 9 * Clemens <mail@Clemens-Zeidler.de> 10 */ 11 12 #include "AnalogClock.h" 13 14 #include <math.h> 15 #include <stdio.h> 16 17 #include <Bitmap.h> 18 #include <Message.h> 19 #include <Window.h> 20 21 #include "TimeMessages.h" 22 23 24 #define DRAG_DELTA_PHI 0.2 25 26 27 class OffscreenClock : public BView { 28 public: 29 OffscreenClock(BRect frame, const char *name); 30 virtual ~OffscreenClock(); 31 32 void SetTime(int32 hour, int32 minute, int32 second); 33 void GetTime(int32 *hour, int32 *minute, int32 *second); 34 bool IsDirty() const { return fDirty; } 35 void DrawClock(); 36 37 bool InHourHand(BPoint point); 38 bool InMinuteHand(BPoint point); 39 40 void SetHourHand(BPoint point); 41 void SetMinuteHand(BPoint point); 42 43 void SetHourDragging(bool dragging); 44 void SetMinuteDragging(bool dragging); 45 46 private: 47 float _GetPhi(BPoint point); 48 bool _InHand(BPoint point, int32 ticks, float radius); 49 void _DrawHands(float x, float y, float radius, 50 rgb_color hourHourColor, 51 rgb_color hourMinuteColor, 52 rgb_color secondsColor, rgb_color knobColor); 53 54 int32 fHours; 55 int32 fMinutes; 56 int32 fSeconds; 57 bool fDirty; 58 59 float fCenterX; 60 float fCenterY; 61 float fRadius; 62 63 bool fHourDragging; 64 bool fMinuteDragging; 65 }; 66 67 68 OffscreenClock::OffscreenClock(BRect frame, const char *name) 69 : 70 BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW), 71 fHours(0), 72 fMinutes(0), 73 fSeconds(0), 74 fDirty(true), 75 fHourDragging(false), 76 fMinuteDragging(false) 77 { 78 SetFlags(Flags() | B_SUBPIXEL_PRECISE); 79 80 BRect bounds = Bounds(); 81 fCenterX = floorf((bounds.left + bounds.right) / 2 + 0.5) + 0.5; 82 fCenterY = floorf((bounds.top + bounds.bottom) / 2 + 0.5) + 0.5; 83 // + 0.5 is for the offset to pixel centers 84 // (important when drawing with B_SUBPIXEL_PRECISE) 85 86 fRadius = floorf((MIN(bounds.Width(), bounds.Height()) / 2.0)) - 2.5; 87 fRadius -= 3; 88 } 89 90 91 OffscreenClock::~OffscreenClock() 92 { 93 } 94 95 96 void 97 OffscreenClock::SetTime(int32 hour, int32 minute, int32 second) 98 { 99 if (fHours == hour && fMinutes == minute && fSeconds == second) 100 return; 101 102 fHours = hour; 103 fMinutes = minute; 104 fSeconds = second; 105 106 fDirty = true; 107 } 108 109 110 void 111 OffscreenClock::GetTime(int32 *hour, int32 *minute, int32 *second) 112 { 113 *hour = fHours; 114 *minute = fMinutes; 115 *second = fSeconds; 116 } 117 118 119 void 120 OffscreenClock::DrawClock() 121 { 122 if (!LockLooper()) 123 return; 124 125 BRect bounds = Bounds(); 126 // clear background 127 rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR); 128 SetHighColor(background); 129 FillRect(bounds); 130 131 132 bounds.Set(fCenterX - fRadius, fCenterY - fRadius, 133 fCenterX + fRadius, fCenterY + fRadius); 134 135 SetPenSize(2.0); 136 137 SetHighColor(tint_color(background, B_DARKEN_1_TINT)); 138 StrokeEllipse(bounds.OffsetByCopy(-1, -1)); 139 140 SetHighColor(tint_color(background, B_LIGHTEN_2_TINT)); 141 StrokeEllipse(bounds.OffsetByCopy(1, 1)); 142 143 SetHighColor(tint_color(background, B_DARKEN_3_TINT)); 144 StrokeEllipse(bounds); 145 146 SetLowColor(255, 255, 255); 147 FillEllipse(bounds, B_SOLID_LOW); 148 149 SetHighColor(tint_color(HighColor(), B_DARKEN_2_TINT)); 150 151 // minutes 152 SetPenSize(1.0); 153 SetLineMode(B_BUTT_CAP, B_MITER_JOIN); 154 for (int32 minute = 1; minute < 60; minute++) { 155 if (minute % 5 == 0) 156 continue; 157 float x1 = fCenterX + sinf(minute * M_PI / 30.0) * fRadius; 158 float y1 = fCenterY + cosf(minute * M_PI / 30.0) * fRadius; 159 float x2 = fCenterX + sinf(minute * M_PI / 30.0) * (fRadius * 0.95); 160 float y2 = fCenterY + cosf(minute * M_PI / 30.0) * (fRadius * 0.95); 161 StrokeLine(BPoint(x1, y1), BPoint(x2, y2)); 162 } 163 164 SetHighColor(tint_color(HighColor(), B_DARKEN_1_TINT)); 165 166 // hours 167 SetPenSize(2.0); 168 SetLineMode(B_ROUND_CAP, B_MITER_JOIN); 169 for (int32 hour = 0; hour < 12; hour++) { 170 float x1 = fCenterX + sinf(hour * M_PI / 6.0) * fRadius; 171 float y1 = fCenterY + cosf(hour * M_PI / 6.0) * fRadius; 172 float x2 = fCenterX + sinf(hour * M_PI / 6.0) * (fRadius * 0.9); 173 float y2 = fCenterY + cosf(hour * M_PI / 6.0) * (fRadius * 0.9); 174 StrokeLine(BPoint(x1, y1), BPoint(x2, y2)); 175 } 176 177 rgb_color knobColor = tint_color(HighColor(), B_DARKEN_2_TINT);; 178 rgb_color hourColor; 179 if (fHourDragging) 180 hourColor = (rgb_color){ 0, 0, 255, 255 }; 181 else 182 hourColor = tint_color(HighColor(), B_DARKEN_2_TINT); 183 184 rgb_color minuteColor; 185 if (fMinuteDragging) 186 minuteColor = (rgb_color){ 0, 0, 255, 255 }; 187 else 188 minuteColor = tint_color(HighColor(), B_DARKEN_2_TINT); 189 190 rgb_color secondsColor = (rgb_color){ 255, 0, 0, 255 }; 191 rgb_color shadowColor = tint_color(LowColor(), 192 (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2); 193 194 _DrawHands(fCenterX + 1.5, fCenterY + 1.5, fRadius, 195 shadowColor, shadowColor, shadowColor, shadowColor); 196 _DrawHands(fCenterX, fCenterY, fRadius, 197 hourColor, minuteColor, secondsColor, knobColor); 198 199 Sync(); 200 201 UnlockLooper(); 202 } 203 204 205 bool 206 OffscreenClock::InHourHand(BPoint point) 207 { 208 int32 ticks = fHours; 209 if (ticks > 12) 210 ticks -= 12; 211 ticks *= 5; 212 ticks += int32(5. * fMinutes / 60.0); 213 if (ticks > 60) 214 ticks -= 60; 215 return _InHand(point, ticks, fRadius * 0.7); 216 } 217 218 219 bool 220 OffscreenClock::InMinuteHand(BPoint point) 221 { 222 return _InHand(point, fMinutes, fRadius * 0.9); 223 } 224 225 226 void 227 OffscreenClock::SetHourHand(BPoint point) 228 { 229 point.x -= fCenterX; 230 point.y -= fCenterY; 231 232 float pointPhi = _GetPhi(point); 233 float hoursExact = 6.0 * pointPhi / M_PI; 234 if (fHours >= 12) 235 fHours = 12; 236 else 237 fHours = 0; 238 fHours += int32(hoursExact); 239 240 SetTime(fHours, fMinutes, fSeconds); 241 } 242 243 244 void 245 OffscreenClock::SetMinuteHand(BPoint point) 246 { 247 point.x -= fCenterX; 248 point.y -= fCenterY; 249 250 float pointPhi = _GetPhi(point); 251 float minutesExact = 30.0 * pointPhi / M_PI; 252 fMinutes = int32(ceilf(minutesExact)); 253 254 SetTime(fHours, fMinutes, fSeconds); 255 } 256 257 258 void 259 OffscreenClock::SetHourDragging(bool dragging) 260 { 261 fHourDragging = dragging; 262 fDirty = true; 263 } 264 265 266 void 267 OffscreenClock::SetMinuteDragging(bool dragging) 268 { 269 fMinuteDragging = dragging; 270 fDirty = true; 271 } 272 273 274 float 275 OffscreenClock::_GetPhi(BPoint point) 276 { 277 if (point.x == 0 && point.y < 0) 278 return 2 * M_PI; 279 if (point.x == 0 && point.y > 0) 280 return M_PI; 281 if (point.y == 0 && point.x < 0) 282 return M_PI * 3 / 2; 283 if (point.y == 0 && point.x > 0) 284 return M_PI / 2; 285 286 float pointPhi = atanf(-1. * point.y / point.x); 287 if (point.y < 0. && point.x > 0.) // right upper corner 288 pointPhi = M_PI / 2. - pointPhi; 289 if (point.y > 0. && point.x > 0.) // right lower corner 290 pointPhi = M_PI / 2 - pointPhi; 291 if (point.y > 0. && point.x < 0.) // left lower corner 292 pointPhi = (M_PI * 3. / 2. - pointPhi); 293 if (point.y < 0. && point.x < 0.) // left upper corner 294 pointPhi = 3. / 2. * M_PI - pointPhi; 295 return pointPhi; 296 } 297 298 299 bool 300 OffscreenClock::_InHand(BPoint point, int32 ticks, float radius) 301 { 302 point.x -= fCenterX; 303 point.y -= fCenterY; 304 305 float pRadius = sqrt(pow(point.x, 2) + pow(point.y, 2)); 306 307 if (radius < pRadius) 308 return false; 309 310 float pointPhi = _GetPhi(point); 311 float handPhi = M_PI / 30.0 * ticks; 312 float delta = pointPhi - handPhi; 313 if (fabs(delta) > DRAG_DELTA_PHI) 314 return false; 315 316 return true; 317 } 318 319 320 void 321 OffscreenClock::_DrawHands(float x, float y, float radius, 322 rgb_color hourColor, 323 rgb_color minuteColor, 324 rgb_color secondsColor, 325 rgb_color knobColor) 326 { 327 float offsetX; 328 float offsetY; 329 330 // calc, draw hour hand 331 SetHighColor(hourColor); 332 SetPenSize(4.0); 333 float hours = fHours + float(fMinutes) / 60.0; 334 offsetX = (radius * 0.7) * sinf((hours * M_PI) / 6.0); 335 offsetY = (radius * 0.7) * cosf((hours * M_PI) / 6.0); 336 StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY)); 337 338 // calc, draw minute hand 339 SetHighColor(minuteColor); 340 SetPenSize(3.0); 341 float minutes = fMinutes + float(fSeconds) / 60.0; 342 offsetX = (radius * 0.9) * sinf((minutes * M_PI) / 30.0); 343 offsetY = (radius * 0.9) * cosf((minutes * M_PI) / 30.0); 344 StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY)); 345 346 // calc, draw second hand 347 SetHighColor(secondsColor); 348 SetPenSize(1.0); 349 offsetX = (radius * 0.95) * sinf((fSeconds * M_PI) / 30.0); 350 offsetY = (radius * 0.95) * cosf((fSeconds * M_PI) / 30.0); 351 StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY)); 352 353 // draw the center knob 354 SetHighColor(knobColor); 355 FillEllipse(BPoint(x, y), radius * 0.06, radius * 0.06); 356 } 357 358 359 // #pragma mark - 360 361 362 TAnalogClock::TAnalogClock(BRect frame, const char *name) 363 : 364 BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW | B_DRAW_ON_CHILDREN), 365 fBitmap(NULL), 366 fClock(NULL), 367 fDraggingHourHand(false), 368 fDraggingMinuteHand(false), 369 fTimeChangeIsOngoing(false) 370 { 371 _InitView(frame); 372 } 373 374 375 TAnalogClock::~TAnalogClock() 376 { 377 delete fBitmap; 378 } 379 380 381 void 382 TAnalogClock::_InitView(BRect rect) 383 { 384 fClock = new OffscreenClock(Bounds(), "offscreen"); 385 fBitmap = new BBitmap(Bounds(), B_RGB32, true); 386 fBitmap->Lock(); 387 fBitmap->AddChild(fClock); 388 fBitmap->Unlock(); 389 } 390 391 392 void 393 TAnalogClock::AttachedToWindow() 394 { 395 SetViewColor(B_TRANSPARENT_COLOR); 396 } 397 398 399 void 400 TAnalogClock::MessageReceived(BMessage *message) 401 { 402 int32 change; 403 switch (message->what) { 404 case B_OBSERVER_NOTICE_CHANGE: 405 message->FindInt32(B_OBSERVE_WHAT_CHANGE, &change); 406 switch (change) { 407 case H_TM_CHANGED: 408 { 409 int32 hour = 0; 410 int32 minute = 0; 411 int32 second = 0; 412 if (message->FindInt32("hour", &hour) == B_OK 413 && message->FindInt32("minute", &minute) == B_OK 414 && message->FindInt32("second", &second) == B_OK) 415 SetTime(hour, minute, second); 416 break; 417 } 418 default: 419 BView::MessageReceived(message); 420 break; 421 } 422 break; 423 default: 424 BView::MessageReceived(message); 425 break; 426 } 427 } 428 429 430 void 431 TAnalogClock::MouseDown(BPoint point) 432 { 433 fDraggingMinuteHand = fClock->InMinuteHand(point); 434 if (fDraggingMinuteHand) { 435 fClock->SetMinuteDragging(true); 436 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 437 Invalidate(); 438 return; 439 } 440 fDraggingHourHand = fClock->InHourHand(point); 441 if (fDraggingHourHand) { 442 fClock->SetHourDragging(true); 443 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 444 Invalidate(); 445 return; 446 } 447 } 448 449 450 void 451 TAnalogClock::MouseUp(BPoint point) 452 { 453 if (fDraggingHourHand || fDraggingMinuteHand) { 454 int32 hour, minute, second; 455 fClock->GetTime(&hour, &minute, &second); 456 BMessage message(H_USER_CHANGE); 457 message.AddBool("time", true); 458 message.AddInt32("hour", hour); 459 message.AddInt32("minute", minute); 460 Window()->PostMessage(&message); 461 fTimeChangeIsOngoing = true; 462 } 463 fDraggingHourHand = false; 464 fDraggingMinuteHand = false; 465 fClock->SetMinuteDragging(false); 466 fClock->SetHourDragging(false); 467 } 468 469 470 void 471 TAnalogClock::MouseMoved(BPoint point, uint32 transit, const BMessage *message) 472 { 473 474 if (fDraggingMinuteHand) 475 fClock->SetMinuteHand(point); 476 if (fDraggingHourHand) 477 fClock->SetHourHand(point); 478 479 Invalidate(); 480 } 481 482 483 void 484 TAnalogClock::Draw(BRect /*updateRect*/) 485 { 486 if (fBitmap) { 487 if (fClock->IsDirty()) 488 fClock->DrawClock(); 489 DrawBitmap(fBitmap, BPoint(0, 0)); 490 } 491 } 492 493 494 void 495 TAnalogClock::SetTime(int32 hour, int32 minute, int32 second) 496 { 497 // don't set the time if the hands are in a drag action 498 if (fDraggingHourHand || fDraggingMinuteHand || fTimeChangeIsOngoing) 499 return; 500 501 if (fClock) 502 fClock->SetTime(hour, minute, second); 503 504 Invalidate(); 505 } 506 507 508 void 509 TAnalogClock::ChangeTimeFinished() 510 { 511 fTimeChangeIsOngoing = false; 512 } 513