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